xref: /dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php (revision 8e88a29b81301f78509349ab1152bb09c229123e)
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    protected $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) . ')', $data);
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        // earlier the SSH specs were quoted.
1491        // "The server MAY send other lines of data before sending the version string." they said.
1492        // the implication of this is that the lines of data before the server string are *not* a part of it
1493        // getting this right is important because the correct server identifier needs to be fed into the
1494        // exchange hash for the shared keys to be calculated correctly
1495        $data = explode("\r\n", trim($data, "\r\n"));
1496        $this->server_identifier = $data[count($data) - 1];
1497        if (strlen($extra)) {
1498            $this->errors[] = $data;
1499        }
1500
1501        if (version_compare($matches[3], '1.99', '<')) {
1502            $this->bitmap = 0;
1503            throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
1504        }
1505
1506        // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see
1507        // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info.
1508        // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses
1509        // when consolekit was incorporated.
1510        // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the
1511        // issues with how Ubuntu incorporated consolekit
1512        $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#';
1513        $match = preg_match($pattern, $this->server_identifier, $matches);
1514        $match = $match && version_compare('5.8', $matches[1], '<=');
1515        $match = $match && version_compare('6.9', $matches[1], '>=');
1516        $this->errorOnMultipleChannels = $match;
1517
1518        if (!$this->send_id_string_first) {
1519            $start = microtime(true);
1520            fputs($this->fsock, $this->identifier . "\r\n");
1521            $elapsed = round(microtime(true) - $start, 4);
1522            if (defined('NET_SSH2_LOGGING')) {
1523                $this->append_log("-> (network: $elapsed)", $this->identifier . "\r\n");
1524            }
1525        }
1526
1527        $this->last_packet = microtime(true);
1528
1529        if (!$this->send_kex_first) {
1530            $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXINIT);
1531            $this->key_exchange($response);
1532        }
1533
1534        if ($this->send_kex_first) {
1535            $this->key_exchange();
1536        }
1537
1538        $this->bitmap |= self::MASK_CONNECTED;
1539
1540        return true;
1541    }
1542
1543    /**
1544     * Generates the SSH identifier
1545     *
1546     * You should overwrite this method in your own class if you want to use another identifier
1547     *
1548     * @return string
1549     */
1550    private function generate_identifier()
1551    {
1552        $identifier = 'SSH-2.0-phpseclib_3.0';
1553
1554        $ext = [];
1555        if (extension_loaded('sodium')) {
1556            $ext[] = 'libsodium';
1557        }
1558
1559        if (extension_loaded('openssl')) {
1560            $ext[] = 'openssl';
1561        } elseif (extension_loaded('mcrypt')) {
1562            $ext[] = 'mcrypt';
1563        }
1564
1565        if (extension_loaded('gmp')) {
1566            $ext[] = 'gmp';
1567        } elseif (extension_loaded('bcmath')) {
1568            $ext[] = 'bcmath';
1569        }
1570
1571        if (!empty($ext)) {
1572            $identifier .= ' (' . implode(', ', $ext) . ')';
1573        }
1574
1575        return $identifier;
1576    }
1577
1578    /**
1579     * Key Exchange
1580     *
1581     * @return bool
1582     * @param string|bool $kexinit_payload_server optional
1583     * @throws \UnexpectedValueException on receipt of unexpected packets
1584     * @throws \RuntimeException on other errors
1585     * @throws NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
1586     */
1587    private function key_exchange($kexinit_payload_server = false)
1588    {
1589        $this->bytesTransferredSinceLastKEX = 0;
1590
1591        $preferred = $this->preferred;
1592        // for the initial key exchange $send_kex is true (no key re-exchange has been started)
1593        // for phpseclib initiated key exchanges $send_kex is false
1594        $send_kex = !$this->keyExchangeInProgress;
1595        $this->keyExchangeInProgress = true;
1596
1597        $kex_algorithms = isset($preferred['kex']) ?
1598            $preferred['kex'] :
1599            SSH2::getSupportedKEXAlgorithms();
1600        $server_host_key_algorithms = isset($preferred['hostkey']) ?
1601            $preferred['hostkey'] :
1602            SSH2::getSupportedHostKeyAlgorithms();
1603        $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ?
1604            $preferred['server_to_client']['crypt'] :
1605            SSH2::getSupportedEncryptionAlgorithms();
1606        $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ?
1607            $preferred['client_to_server']['crypt'] :
1608            SSH2::getSupportedEncryptionAlgorithms();
1609        $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ?
1610            $preferred['server_to_client']['mac'] :
1611            SSH2::getSupportedMACAlgorithms();
1612        $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ?
1613            $preferred['client_to_server']['mac'] :
1614            SSH2::getSupportedMACAlgorithms();
1615        $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ?
1616            $preferred['server_to_client']['comp'] :
1617            SSH2::getSupportedCompressionAlgorithms();
1618        $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ?
1619            $preferred['client_to_server']['comp'] :
1620            SSH2::getSupportedCompressionAlgorithms();
1621
1622        $kex_algorithms = array_merge($kex_algorithms, ['ext-info-c', 'kex-strict-c-v00@openssh.com']);
1623
1624        // some SSH servers have buggy implementations of some of the above algorithms
1625        switch (true) {
1626            case $this->server_identifier == 'SSH-2.0-SSHD':
1627            case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK':
1628                if (!isset($preferred['server_to_client']['mac'])) {
1629                    $s2c_mac_algorithms = array_values(array_diff(
1630                        $s2c_mac_algorithms,
1631                        ['hmac-sha1-96', 'hmac-md5-96']
1632                    ));
1633                }
1634                if (!isset($preferred['client_to_server']['mac'])) {
1635                    $c2s_mac_algorithms = array_values(array_diff(
1636                        $c2s_mac_algorithms,
1637                        ['hmac-sha1-96', 'hmac-md5-96']
1638                    ));
1639                }
1640                break;
1641            case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_':
1642                if (!isset($preferred['server_to_client']['crypt'])) {
1643                    $s2c_encryption_algorithms = array_values(array_diff(
1644                        $s2c_encryption_algorithms,
1645                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
1646                    ));
1647                }
1648                if (!isset($preferred['client_to_server']['crypt'])) {
1649                    $c2s_encryption_algorithms = array_values(array_diff(
1650                        $c2s_encryption_algorithms,
1651                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
1652                    ));
1653                }
1654        }
1655
1656        $client_cookie = Random::string(16);
1657
1658        $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie);
1659        $kexinit_payload_client .= Strings::packSSH2(
1660            'L10bN',
1661            $kex_algorithms,
1662            $server_host_key_algorithms,
1663            $c2s_encryption_algorithms,
1664            $s2c_encryption_algorithms,
1665            $c2s_mac_algorithms,
1666            $s2c_mac_algorithms,
1667            $c2s_compression_algorithms,
1668            $s2c_compression_algorithms,
1669            [], // language, client to server
1670            [], // language, server to client
1671            false, // first_kex_packet_follows
1672            0 // reserved for future extension
1673        );
1674
1675        if ($kexinit_payload_server === false && $send_kex) {
1676            $this->send_binary_packet($kexinit_payload_client);
1677
1678            while (true) {
1679                $kexinit_payload_server = $this->get_binary_packet();
1680                switch (ord($kexinit_payload_server[0])) {
1681                    case NET_SSH2_MSG_KEXINIT:
1682                        break 2;
1683                    case NET_SSH2_MSG_DISCONNECT:
1684                        return $this->handleDisconnect($kexinit_payload_server);
1685                }
1686                $this->kex_buffer[] = $kexinit_payload_server;
1687            }
1688
1689            $send_kex = false;
1690        }
1691
1692        $response = $kexinit_payload_server;
1693        Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
1694        $server_cookie = Strings::shift($response, 16);
1695
1696        list(
1697            $this->kex_algorithms,
1698            $this->server_host_key_algorithms,
1699            $this->encryption_algorithms_client_to_server,
1700            $this->encryption_algorithms_server_to_client,
1701            $this->mac_algorithms_client_to_server,
1702            $this->mac_algorithms_server_to_client,
1703            $this->compression_algorithms_client_to_server,
1704            $this->compression_algorithms_server_to_client,
1705            $this->languages_client_to_server,
1706            $this->languages_server_to_client,
1707            $first_kex_packet_follows
1708        ) = Strings::unpackSSH2('L10C', $response);
1709        if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) {
1710            if ($this->session_id === false) {
1711                // [kex-strict-s-v00@openssh.com is] only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
1712                // if [it is] present in subsequent SSH2_MSG_KEXINIT packets
1713                $this->strict_kex_flag = true;
1714                if (count($this->kex_buffer)) {
1715                    throw new \UnexpectedValueException('Possible Terrapin Attack detected');
1716                }
1717            }
1718        }
1719
1720        $this->supported_private_key_algorithms = $this->server_host_key_algorithms;
1721
1722        if ($send_kex) {
1723            $this->send_binary_packet($kexinit_payload_client);
1724        }
1725
1726        // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
1727
1728        // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
1729        // diffie-hellman key exchange as fast as possible
1730        $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
1731        if (!$decrypt || ($decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt)) === null) {
1732            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1733            throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
1734        }
1735
1736        $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
1737        if (!$encrypt || ($encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt)) === null) {
1738            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1739            throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
1740        }
1741
1742        // through diffie-hellman key exchange a symmetric key is obtained
1743        $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
1744        if ($this->kex_algorithm === false) {
1745            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1746            throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
1747        }
1748
1749        $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
1750        if ($server_host_key_algorithm === false) {
1751            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1752            throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
1753        }
1754
1755        $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
1756        if ($mac_algorithm_out === false) {
1757            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1758            throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
1759        }
1760
1761        $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
1762        if ($mac_algorithm_in === false) {
1763            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1764            throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
1765        }
1766
1767        $compression_map = [
1768            'none' => self::NET_SSH2_COMPRESSION_NONE,
1769            'zlib' => self::NET_SSH2_COMPRESSION_ZLIB,
1770            'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH
1771        ];
1772
1773        $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
1774        if ($compression_algorithm_in === false) {
1775            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1776            throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
1777        }
1778        $this->decompress = $compression_map[$compression_algorithm_in];
1779
1780        $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
1781        if ($compression_algorithm_out === false) {
1782            $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1783            throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
1784        }
1785        $this->compress = $compression_map[$compression_algorithm_out];
1786
1787        switch ($this->kex_algorithm) {
1788            case 'diffie-hellman-group15-sha512':
1789            case 'diffie-hellman-group16-sha512':
1790            case 'diffie-hellman-group17-sha512':
1791            case 'diffie-hellman-group18-sha512':
1792            case 'ecdh-sha2-nistp521':
1793                $kexHash = new Hash('sha512');
1794                break;
1795            case 'ecdh-sha2-nistp384':
1796                $kexHash = new Hash('sha384');
1797                break;
1798            case 'diffie-hellman-group-exchange-sha256':
1799            case 'diffie-hellman-group14-sha256':
1800            case 'ecdh-sha2-nistp256':
1801            case 'curve25519-sha256@libssh.org':
1802            case 'curve25519-sha256':
1803                $kexHash = new Hash('sha256');
1804                break;
1805            default:
1806                $kexHash = new Hash('sha1');
1807        }
1808
1809        // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
1810
1811        $exchange_hash_rfc4419 = '';
1812
1813        if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) {
1814            $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ?
1815                'Curve25519' :
1816                substr($this->kex_algorithm, 10);
1817            $ourPrivate = EC::createKey($curve);
1818            $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates();
1819            $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT';
1820            $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY';
1821        } else {
1822            if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
1823                $dh_group_sizes_packed = pack(
1824                    'NNN',
1825                    $this->kex_dh_group_size_min,
1826                    $this->kex_dh_group_size_preferred,
1827                    $this->kex_dh_group_size_max
1828                );
1829                $packet = pack(
1830                    'Ca*',
1831                    NET_SSH2_MSG_KEXDH_GEX_REQUEST,
1832                    $dh_group_sizes_packed
1833                );
1834                $this->send_binary_packet($packet);
1835                $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
1836
1837                $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXDH_GEX_GROUP);
1838                list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response);
1839                $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
1840                $prime = new BigInteger($primeBytes, -256);
1841                $g = new BigInteger($gBytes, -256);
1842
1843                $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2(
1844                    'ss',
1845                    $primeBytes,
1846                    $gBytes
1847                );
1848
1849                $params = DH::createParameters($prime, $g);
1850                $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT';
1851                $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY';
1852            } else {
1853                $params = DH::createParameters($this->kex_algorithm);
1854                $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT';
1855                $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY';
1856            }
1857
1858            $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength));
1859
1860            $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength
1861            $ourPublic = $ourPrivate->getPublicKey()->toBigInteger();
1862            $ourPublicBytes = $ourPublic->toBytes(true);
1863        }
1864
1865        $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes);
1866
1867        $this->send_binary_packet($data);
1868
1869        switch ($clientKexInitMessage) {
1870            case 'NET_SSH2_MSG_KEX_ECDH_INIT':
1871                $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
1872                break;
1873            case 'NET_SSH2_MSG_KEXDH_GEX_INIT':
1874                $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
1875        }
1876
1877        $response = $this->get_binary_packet_or_close(constant($serverKexReplyMessage));
1878
1879        list(
1880            $type,
1881            $server_public_host_key,
1882            $theirPublicBytes,
1883            $this->signature
1884        ) = Strings::unpackSSH2('Csss', $response);
1885
1886        switch ($serverKexReplyMessage) {
1887            case 'NET_SSH2_MSG_KEX_ECDH_REPLY':
1888                $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
1889                break;
1890            case 'NET_SSH2_MSG_KEXDH_GEX_REPLY':
1891                $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
1892        }
1893
1894        $this->server_public_host_key = $server_public_host_key;
1895        list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key);
1896        if (strlen($this->signature) < 4) {
1897            throw new \LengthException('The signature needs at least four bytes');
1898        }
1899        $temp = unpack('Nlength', substr($this->signature, 0, 4));
1900        $this->signature_format = substr($this->signature, 4, $temp['length']);
1901
1902        $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes);
1903        if (($keyBytes & "\xFF\x80") === "\x00\x00") {
1904            $keyBytes = substr($keyBytes, 1);
1905        } elseif (($keyBytes[0] & "\x80") === "\x80") {
1906            $keyBytes = "\0$keyBytes";
1907        }
1908
1909        $this->exchange_hash = Strings::packSSH2(
1910            's5',
1911            $this->identifier,
1912            $this->server_identifier,
1913            $kexinit_payload_client,
1914            $kexinit_payload_server,
1915            $this->server_public_host_key
1916        );
1917        $this->exchange_hash .= $exchange_hash_rfc4419;
1918        $this->exchange_hash .= Strings::packSSH2(
1919            's3',
1920            $ourPublicBytes,
1921            $theirPublicBytes,
1922            $keyBytes
1923        );
1924
1925        $this->exchange_hash = $kexHash->hash($this->exchange_hash);
1926
1927        if ($this->session_id === false) {
1928            $this->session_id = $this->exchange_hash;
1929        }
1930
1931        switch ($server_host_key_algorithm) {
1932            case 'rsa-sha2-256':
1933            case 'rsa-sha2-512':
1934            //case 'ssh-rsa':
1935                $expected_key_format = 'ssh-rsa';
1936                break;
1937            default:
1938                $expected_key_format = $server_host_key_algorithm;
1939        }
1940        if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) {
1941            switch (true) {
1942                case $this->signature_format == $server_host_key_algorithm:
1943                case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512':
1944                case $this->signature_format != 'ssh-rsa':
1945                    $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
1946                    throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')');
1947            }
1948        }
1949
1950        $packet = pack('C', NET_SSH2_MSG_NEWKEYS);
1951        $this->send_binary_packet($packet);
1952        $this->get_binary_packet_or_close(NET_SSH2_MSG_NEWKEYS);
1953
1954        $this->keyExchangeInProgress = false;
1955
1956        if ($this->strict_kex_flag) {
1957            $this->get_seq_no = $this->send_seq_no = 0;
1958        }
1959
1960        $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
1961
1962        $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
1963        if ($this->encrypt) {
1964            if (self::$crypto_engine) {
1965                $this->encrypt->setPreferredEngine(self::$crypto_engine);
1966            }
1967            if ($this->encrypt->getBlockLengthInBytes()) {
1968                $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes();
1969            }
1970            $this->encrypt->disablePadding();
1971
1972            if ($this->encrypt->usesIV()) {
1973                $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1974                while ($this->encrypt_block_size > strlen($iv)) {
1975                    $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1976                }
1977                $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
1978            }
1979
1980            switch ($encrypt) {
1981                case 'aes128-gcm@openssh.com':
1982                case 'aes256-gcm@openssh.com':
1983                    $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1984                    $this->encryptFixedPart = substr($nonce, 0, 4);
1985                    $this->encryptInvocationCounter = substr($nonce, 4, 8);
1986                    // fall-through
1987                case 'chacha20-poly1305@openssh.com':
1988                    break;
1989                default:
1990                    $this->encrypt->enableContinuousBuffer();
1991            }
1992
1993            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
1994            while ($encryptKeyLength > strlen($key)) {
1995                $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1996            }
1997            switch ($encrypt) {
1998                case 'chacha20-poly1305@openssh.com':
1999                    $encryptKeyLength = 32;
2000                    $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
2001                    $this->lengthEncrypt->setKey(substr($key, 32, 32));
2002            }
2003            $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
2004            $this->encryptName = $encrypt;
2005        }
2006
2007        $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
2008        if ($this->decrypt) {
2009            if (self::$crypto_engine) {
2010                $this->decrypt->setPreferredEngine(self::$crypto_engine);
2011            }
2012            if ($this->decrypt->getBlockLengthInBytes()) {
2013                $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes();
2014            }
2015            $this->decrypt->disablePadding();
2016
2017            if ($this->decrypt->usesIV()) {
2018                $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
2019                while ($this->decrypt_block_size > strlen($iv)) {
2020                    $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
2021                }
2022                $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
2023            }
2024
2025            switch ($decrypt) {
2026                case 'aes128-gcm@openssh.com':
2027                case 'aes256-gcm@openssh.com':
2028                    // see https://tools.ietf.org/html/rfc5647#section-7.1
2029                    $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
2030                    $this->decryptFixedPart = substr($nonce, 0, 4);
2031                    $this->decryptInvocationCounter = substr($nonce, 4, 8);
2032                    // fall-through
2033                case 'chacha20-poly1305@openssh.com':
2034                    break;
2035                default:
2036                    $this->decrypt->enableContinuousBuffer();
2037            }
2038
2039            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
2040            while ($decryptKeyLength > strlen($key)) {
2041                $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2042            }
2043            switch ($decrypt) {
2044                case 'chacha20-poly1305@openssh.com':
2045                    $decryptKeyLength = 32;
2046                    $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
2047                    $this->lengthDecrypt->setKey(substr($key, 32, 32));
2048            }
2049            $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
2050            $this->decryptName = $decrypt;
2051        }
2052
2053        /* The "arcfour128" algorithm is the RC4 cipher, as described in
2054           [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream
2055           generated by the cipher MUST be discarded, and the first byte of the
2056           first encrypted packet MUST be encrypted using the 1537th byte of
2057           keystream.
2058
2059           -- http://tools.ietf.org/html/rfc4345#section-4 */
2060        if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
2061            $this->encrypt->encrypt(str_repeat("\0", 1536));
2062        }
2063        if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
2064            $this->decrypt->decrypt(str_repeat("\0", 1536));
2065        }
2066
2067        if (!$this->encrypt->usesNonce()) {
2068            list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out);
2069        } else {
2070            $this->hmac_create = new \stdClass();
2071            $this->hmac_create_name = $mac_algorithm_out;
2072            //$mac_algorithm_out = 'none';
2073            $createKeyLength = 0;
2074        }
2075
2076        if ($this->hmac_create instanceof Hash) {
2077            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
2078            while ($createKeyLength > strlen($key)) {
2079                $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2080            }
2081            $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
2082            $this->hmac_create_name = $mac_algorithm_out;
2083            $this->hmac_create_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out);
2084        }
2085
2086        if (!$this->decrypt->usesNonce()) {
2087            list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in);
2088            $this->hmac_size = $this->hmac_check->getLengthInBytes();
2089        } else {
2090            $this->hmac_check = new \stdClass();
2091            $this->hmac_check_name = $mac_algorithm_in;
2092            //$mac_algorithm_in = 'none';
2093            $checkKeyLength = 0;
2094            $this->hmac_size = 0;
2095        }
2096
2097        if ($this->hmac_check instanceof Hash) {
2098            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
2099            while ($checkKeyLength > strlen($key)) {
2100                $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2101            }
2102            $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
2103            $this->hmac_check_name = $mac_algorithm_in;
2104            $this->hmac_check_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in);
2105        }
2106
2107        $this->regenerate_compression_context = $this->regenerate_decompression_context = true;
2108
2109        return true;
2110    }
2111
2112    /**
2113     * Maps an encryption algorithm name to the number of key bytes.
2114     *
2115     * @param string $algorithm Name of the encryption algorithm
2116     * @return int|null Number of bytes as an integer or null for unknown
2117     */
2118    private function encryption_algorithm_to_key_size($algorithm)
2119    {
2120        if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) {
2121            return 16;
2122        }
2123
2124        switch ($algorithm) {
2125            case 'none':
2126                return 0;
2127            case 'aes128-gcm@openssh.com':
2128            case 'aes128-cbc':
2129            case 'aes128-ctr':
2130            case 'arcfour':
2131            case 'arcfour128':
2132            case 'blowfish-cbc':
2133            case 'blowfish-ctr':
2134            case 'twofish128-cbc':
2135            case 'twofish128-ctr':
2136                return 16;
2137            case '3des-cbc':
2138            case '3des-ctr':
2139            case 'aes192-cbc':
2140            case 'aes192-ctr':
2141            case 'twofish192-cbc':
2142            case 'twofish192-ctr':
2143                return 24;
2144            case 'aes256-gcm@openssh.com':
2145            case 'aes256-cbc':
2146            case 'aes256-ctr':
2147            case 'arcfour256':
2148            case 'twofish-cbc':
2149            case 'twofish256-cbc':
2150            case 'twofish256-ctr':
2151                return 32;
2152            case 'chacha20-poly1305@openssh.com':
2153                return 64;
2154        }
2155        return null;
2156    }
2157
2158    /**
2159     * Maps an encryption algorithm name to an instance of a subclass of
2160     * \phpseclib3\Crypt\Common\SymmetricKey.
2161     *
2162     * @param string $algorithm Name of the encryption algorithm
2163     * @return SymmetricKey|null
2164     */
2165    private static function encryption_algorithm_to_crypt_instance($algorithm)
2166    {
2167        switch ($algorithm) {
2168            case '3des-cbc':
2169                return new TripleDES('cbc');
2170            case '3des-ctr':
2171                return new TripleDES('ctr');
2172            case 'aes256-cbc':
2173            case 'aes192-cbc':
2174            case 'aes128-cbc':
2175                return new Rijndael('cbc');
2176            case 'aes256-ctr':
2177            case 'aes192-ctr':
2178            case 'aes128-ctr':
2179                return new Rijndael('ctr');
2180            case 'blowfish-cbc':
2181                return new Blowfish('cbc');
2182            case 'blowfish-ctr':
2183                return new Blowfish('ctr');
2184            case 'twofish128-cbc':
2185            case 'twofish192-cbc':
2186            case 'twofish256-cbc':
2187            case 'twofish-cbc':
2188                return new Twofish('cbc');
2189            case 'twofish128-ctr':
2190            case 'twofish192-ctr':
2191            case 'twofish256-ctr':
2192                return new Twofish('ctr');
2193            case 'arcfour':
2194            case 'arcfour128':
2195            case 'arcfour256':
2196                return new RC4();
2197            case 'aes128-gcm@openssh.com':
2198            case 'aes256-gcm@openssh.com':
2199                return new Rijndael('gcm');
2200            case 'chacha20-poly1305@openssh.com':
2201                return new ChaCha20();
2202        }
2203        return null;
2204    }
2205
2206    /**
2207     * Maps an encryption algorithm name to an instance of a subclass of
2208     * \phpseclib3\Crypt\Hash.
2209     *
2210     * @param string $algorithm Name of the encryption algorithm
2211     * @return array{Hash, int}|null
2212     */
2213    private static function mac_algorithm_to_hash_instance($algorithm)
2214    {
2215        switch ($algorithm) {
2216            case 'umac-64@openssh.com':
2217            case 'umac-64-etm@openssh.com':
2218                return [new Hash('umac-64'), 16];
2219            case 'umac-128@openssh.com':
2220            case 'umac-128-etm@openssh.com':
2221                return [new Hash('umac-128'), 16];
2222            case 'hmac-sha2-512':
2223            case 'hmac-sha2-512-etm@openssh.com':
2224                return [new Hash('sha512'), 64];
2225            case 'hmac-sha2-256':
2226            case 'hmac-sha2-256-etm@openssh.com':
2227                return [new Hash('sha256'), 32];
2228            case 'hmac-sha1':
2229            case 'hmac-sha1-etm@openssh.com':
2230                return [new Hash('sha1'), 20];
2231            case 'hmac-sha1-96':
2232                return [new Hash('sha1-96'), 20];
2233            case 'hmac-md5':
2234                return [new Hash('md5'), 16];
2235            case 'hmac-md5-96':
2236                return [new Hash('md5-96'), 16];
2237        }
2238    }
2239
2240    /**
2241     * Tests whether or not proposed algorithm has a potential for issues
2242     *
2243     * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html
2244     * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291
2245     * @param string $algorithm Name of the encryption algorithm
2246     * @return bool
2247     */
2248    private static function bad_algorithm_candidate($algorithm)
2249    {
2250        switch ($algorithm) {
2251            case 'arcfour256':
2252            case 'aes192-ctr':
2253            case 'aes256-ctr':
2254                return true;
2255        }
2256
2257        return false;
2258    }
2259
2260    /**
2261     * Login
2262     *
2263     * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array
2264     *
2265     * @param string $username
2266     * @param string|PrivateKey|array[]|Agent|null ...$args
2267     * @return bool
2268     * @see self::_login()
2269     */
2270    public function login($username, ...$args)
2271    {
2272        if (!$this->login_credentials_finalized) {
2273            $this->auth[] = func_get_args();
2274        }
2275
2276        // try logging with 'none' as an authentication method first since that's what
2277        // PuTTY does
2278        if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) {
2279            if ($this->sublogin($username)) {
2280                return true;
2281            }
2282            if (!count($args)) {
2283                return false;
2284            }
2285        }
2286        return $this->sublogin($username, ...$args);
2287    }
2288
2289    /**
2290     * Login Helper
2291     *
2292     * @param string $username
2293     * @param string|PrivateKey|array[]|Agent|null ...$args
2294     * @return bool
2295     * @see self::_login_helper()
2296     */
2297    protected function sublogin($username, ...$args)
2298    {
2299        if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
2300            $this->connect();
2301        }
2302
2303        if (empty($args)) {
2304            return $this->login_helper($username);
2305        }
2306
2307        foreach ($args as $arg) {
2308            switch (true) {
2309                case $arg instanceof PublicKey:
2310                    throw new \UnexpectedValueException('A PublicKey object was passed to the login method instead of a PrivateKey object');
2311                case $arg instanceof PrivateKey:
2312                case $arg instanceof Agent:
2313                case is_array($arg):
2314                case Strings::is_stringable($arg):
2315                    break;
2316                default:
2317                    throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string');
2318            }
2319        }
2320
2321        while (count($args)) {
2322            if (!$this->auth_methods_to_continue || !$this->smartMFA) {
2323                $newargs = $args;
2324                $args = [];
2325            } else {
2326                $newargs = [];
2327                foreach ($this->auth_methods_to_continue as $method) {
2328                    switch ($method) {
2329                        case 'publickey':
2330                            foreach ($args as $key => $arg) {
2331                                if ($arg instanceof PrivateKey || $arg instanceof Agent) {
2332                                    $newargs[] = $arg;
2333                                    unset($args[$key]);
2334                                    break;
2335                                }
2336                            }
2337                            break;
2338                        case 'keyboard-interactive':
2339                            $hasArray = $hasString = false;
2340                            foreach ($args as $arg) {
2341                                if ($hasArray || is_array($arg)) {
2342                                    $hasArray = true;
2343                                    break;
2344                                }
2345                                if ($hasString || Strings::is_stringable($arg)) {
2346                                    $hasString = true;
2347                                    break;
2348                                }
2349                            }
2350                            if ($hasArray && $hasString) {
2351                                foreach ($args as $key => $arg) {
2352                                    if (is_array($arg)) {
2353                                        $newargs[] = $arg;
2354                                        break 2;
2355                                    }
2356                                }
2357                            }
2358                            // fall-through
2359                        case 'password':
2360                            foreach ($args as $key => $arg) {
2361                                $newargs[] = $arg;
2362                                unset($args[$key]);
2363                                break;
2364                            }
2365                    }
2366                }
2367            }
2368
2369            if (!count($newargs)) {
2370                return false;
2371            }
2372
2373            foreach ($newargs as $arg) {
2374                if ($this->login_helper($username, $arg)) {
2375                    $this->login_credentials_finalized = true;
2376                    return true;
2377                }
2378            }
2379        }
2380        return false;
2381    }
2382
2383    /**
2384     * Login Helper
2385     *
2386     * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
2387     *           by sending dummy SSH_MSG_IGNORE messages.}
2388     *
2389     * @param string $username
2390     * @param string|AsymmetricKey|array[]|Agent|null ...$args
2391     * @return bool
2392     * @throws \UnexpectedValueException on receipt of unexpected packets
2393     * @throws \RuntimeException on other errors
2394     */
2395    private function login_helper($username, $password = null)
2396    {
2397        if (!($this->bitmap & self::MASK_CONNECTED)) {
2398            return false;
2399        }
2400
2401        if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
2402            $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth');
2403            $this->send_binary_packet($packet);
2404
2405            try {
2406                $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_SERVICE_ACCEPT);
2407            } catch (InvalidPacketLengthException $e) {
2408                // the first opportunity to encounter the "bad key size" error
2409                if (!$this->bad_key_size_fix && $this->decryptName != null && self::bad_algorithm_candidate($this->decryptName)) {
2410                    // bad_key_size_fix is only ever re-assigned to true here
2411                    // retry the connection with that new setting but we'll
2412                    // only try it once.
2413                    $this->bad_key_size_fix = true;
2414                    return $this->reconnect();
2415                }
2416                throw $e;
2417            }
2418
2419            list($type) = Strings::unpackSSH2('C', $response);
2420            list($service) = Strings::unpackSSH2('s', $response);
2421
2422            if ($service != 'ssh-userauth') {
2423                $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
2424                throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
2425            }
2426            $this->bitmap |= self::MASK_LOGIN_REQ;
2427        }
2428
2429        if (strlen($this->last_interactive_response)) {
2430            return !Strings::is_stringable($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password);
2431        }
2432
2433        if ($password instanceof PrivateKey) {
2434            return $this->privatekey_login($username, $password);
2435        }
2436
2437        if ($password instanceof Agent) {
2438            return $this->ssh_agent_login($username, $password);
2439        }
2440
2441        if (is_array($password)) {
2442            if ($this->keyboard_interactive_login($username, $password)) {
2443                $this->bitmap |= self::MASK_LOGIN;
2444                return true;
2445            }
2446            return false;
2447        }
2448
2449        if (!isset($password)) {
2450            $packet = Strings::packSSH2(
2451                'Cs3',
2452                NET_SSH2_MSG_USERAUTH_REQUEST,
2453                $username,
2454                'ssh-connection',
2455                'none'
2456            );
2457
2458            $this->send_binary_packet($packet);
2459
2460            $response = $this->get_binary_packet_or_close();
2461
2462            list($type) = Strings::unpackSSH2('C', $response);
2463            switch ($type) {
2464                case NET_SSH2_MSG_USERAUTH_SUCCESS:
2465                    $this->bitmap |= self::MASK_LOGIN;
2466                    return true;
2467                case NET_SSH2_MSG_USERAUTH_FAILURE:
2468                    list($auth_methods) = Strings::unpackSSH2('L', $response);
2469                    $this->auth_methods_to_continue = $auth_methods;
2470                    // fall-through
2471                default:
2472                    return false;
2473            }
2474        }
2475
2476        $packet = Strings::packSSH2(
2477            'Cs3bs',
2478            NET_SSH2_MSG_USERAUTH_REQUEST,
2479            $username,
2480            'ssh-connection',
2481            'password',
2482            false,
2483            $password
2484        );
2485
2486        // remove the username and password from the logged packet
2487        if (!defined('NET_SSH2_LOGGING')) {
2488            $logged = null;
2489        } else {
2490            $logged = Strings::packSSH2(
2491                'Cs3bs',
2492                NET_SSH2_MSG_USERAUTH_REQUEST,
2493                $username,
2494                'ssh-connection',
2495                'password',
2496                false,
2497                'password'
2498            );
2499        }
2500
2501        $this->send_binary_packet($packet, $logged);
2502
2503        $response = $this->get_binary_packet_or_close();
2504        list($type) = Strings::unpackSSH2('C', $response);
2505        switch ($type) {
2506            case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
2507                $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
2508
2509                list($message) = Strings::unpackSSH2('s', $response);
2510                $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message;
2511
2512                return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
2513            case NET_SSH2_MSG_USERAUTH_FAILURE:
2514                // can we use keyboard-interactive authentication?  if not then either the login is bad or the server employees
2515                // multi-factor authentication
2516                list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response);
2517                $this->auth_methods_to_continue = $auth_methods;
2518                if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
2519                    if ($this->keyboard_interactive_login($username, $password)) {
2520                        $this->bitmap |= self::MASK_LOGIN;
2521                        return true;
2522                    }
2523                    return false;
2524                }
2525                return false;
2526            case NET_SSH2_MSG_USERAUTH_SUCCESS:
2527                $this->bitmap |= self::MASK_LOGIN;
2528                return true;
2529        }
2530
2531        return false;
2532    }
2533
2534    /**
2535     * Login via keyboard-interactive authentication
2536     *
2537     * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details.  This is not a full-featured keyboard-interactive authenticator.
2538     *
2539     * @param string $username
2540     * @param string|array $password
2541     * @return bool
2542     */
2543    private function keyboard_interactive_login($username, $password)
2544    {
2545        $packet = Strings::packSSH2(
2546            'Cs5',
2547            NET_SSH2_MSG_USERAUTH_REQUEST,
2548            $username,
2549            'ssh-connection',
2550            'keyboard-interactive',
2551            '', // language tag
2552            '' // submethods
2553        );
2554        $this->send_binary_packet($packet);
2555
2556        return $this->keyboard_interactive_process($password);
2557    }
2558
2559    /**
2560     * Handle the keyboard-interactive requests / responses.
2561     *
2562     * @param string|array ...$responses
2563     * @return bool
2564     * @throws \RuntimeException on connection error
2565     */
2566    private function keyboard_interactive_process(...$responses)
2567    {
2568        if (strlen($this->last_interactive_response)) {
2569            $response = $this->last_interactive_response;
2570        } else {
2571            $orig = $response = $this->get_binary_packet_or_close();
2572        }
2573
2574        list($type) = Strings::unpackSSH2('C', $response);
2575        switch ($type) {
2576            case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
2577                list(
2578                    , // name; may be empty
2579                    , // instruction; may be empty
2580                    , // language tag; may be empty
2581                    $num_prompts
2582                ) = Strings::unpackSSH2('s3N', $response);
2583
2584                for ($i = 0; $i < count($responses); $i++) {
2585                    if (is_array($responses[$i])) {
2586                        foreach ($responses[$i] as $key => $value) {
2587                            $this->keyboard_requests_responses[$key] = $value;
2588                        }
2589                        unset($responses[$i]);
2590                    }
2591                }
2592                $responses = array_values($responses);
2593
2594                if (isset($this->keyboard_requests_responses)) {
2595                    for ($i = 0; $i < $num_prompts; $i++) {
2596                        list(
2597                            $prompt, // prompt - ie. "Password: "; must not be empty
2598                            // echo
2599                        ) = Strings::unpackSSH2('sC', $response);
2600                        foreach ($this->keyboard_requests_responses as $key => $value) {
2601                            if (substr($prompt, 0, strlen($key)) == $key) {
2602                                $responses[] = $value;
2603                                break;
2604                            }
2605                        }
2606                    }
2607                }
2608
2609                // see http://tools.ietf.org/html/rfc4256#section-3.2
2610                if (strlen($this->last_interactive_response)) {
2611                    $this->last_interactive_response = '';
2612                } else {
2613                    $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
2614                }
2615
2616                if (!count($responses) && $num_prompts) {
2617                    $this->last_interactive_response = $orig;
2618                    return false;
2619                }
2620
2621                /*
2622                   After obtaining the requested information from the user, the client
2623                   MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
2624                */
2625                // see http://tools.ietf.org/html/rfc4256#section-3.4
2626                $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
2627                for ($i = 0; $i < count($responses); $i++) {
2628                    $packet .= Strings::packSSH2('s', $responses[$i]);
2629                    $logged .= Strings::packSSH2('s', 'dummy-answer');
2630                }
2631
2632                $this->send_binary_packet($packet, $logged);
2633
2634                $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
2635
2636                /*
2637                   After receiving the response, the server MUST send either an
2638                   SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another
2639                   SSH_MSG_USERAUTH_INFO_REQUEST message.
2640                */
2641                // maybe phpseclib should force close the connection after x request / responses?  unless something like that is done
2642                // there could be an infinite loop of request / responses.
2643                return $this->keyboard_interactive_process();
2644            case NET_SSH2_MSG_USERAUTH_SUCCESS:
2645                return true;
2646            case NET_SSH2_MSG_USERAUTH_FAILURE:
2647                list($auth_methods) = Strings::unpackSSH2('L', $response);
2648                $this->auth_methods_to_continue = $auth_methods;
2649                return false;
2650        }
2651
2652        return false;
2653    }
2654
2655    /**
2656     * Login with an ssh-agent provided key
2657     *
2658     * @param string $username
2659     * @param Agent $agent
2660     * @return bool
2661     */
2662    private function ssh_agent_login($username, Agent $agent)
2663    {
2664        $this->agent = $agent;
2665        $keys = $agent->requestIdentities();
2666        $orig_algorithms = $this->supported_private_key_algorithms;
2667        foreach ($keys as $key) {
2668            if ($this->privatekey_login($username, $key)) {
2669                return true;
2670            }
2671            $this->supported_private_key_algorithms = $orig_algorithms;
2672        }
2673
2674        return false;
2675    }
2676
2677    /**
2678     * Login with an RSA private key
2679     *
2680     * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
2681     *           by sending dummy SSH_MSG_IGNORE messages.}
2682     *
2683     * @param string $username
2684     * @param PrivateKey $privatekey
2685     * @return bool
2686     * @throws \RuntimeException on connection error
2687     */
2688    private function privatekey_login($username, PrivateKey $privatekey)
2689    {
2690        $publickey = $privatekey->getPublicKey();
2691
2692        if ($publickey instanceof RSA) {
2693            $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1);
2694            $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
2695            if (isset($this->preferred['hostkey'])) {
2696                $algos = array_intersect($algos, $this->preferred['hostkey']);
2697            }
2698            $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
2699            switch ($algo) {
2700                case 'rsa-sha2-512':
2701                    $hash = 'sha512';
2702                    $signatureType = 'rsa-sha2-512';
2703                    break;
2704                case 'rsa-sha2-256':
2705                    $hash = 'sha256';
2706                    $signatureType = 'rsa-sha2-256';
2707                    break;
2708                //case 'ssh-rsa':
2709                default:
2710                    $hash = 'sha1';
2711                    $signatureType = 'ssh-rsa';
2712            }
2713        } elseif ($publickey instanceof EC) {
2714            $privatekey = $privatekey->withSignatureFormat('SSH2');
2715            $curveName = $privatekey->getCurve();
2716            switch ($curveName) {
2717                case 'Ed25519':
2718                    $hash = 'sha512';
2719                    $signatureType = 'ssh-ed25519';
2720                    break;
2721                case 'secp256r1': // nistp256
2722                    $hash = 'sha256';
2723                    $signatureType = 'ecdsa-sha2-nistp256';
2724                    break;
2725                case 'secp384r1': // nistp384
2726                    $hash = 'sha384';
2727                    $signatureType = 'ecdsa-sha2-nistp384';
2728                    break;
2729                case 'secp521r1': // nistp521
2730                    $hash = 'sha512';
2731                    $signatureType = 'ecdsa-sha2-nistp521';
2732                    break;
2733                default:
2734                    if (is_array($curveName)) {
2735                        throw new UnsupportedCurveException('Specified Curves are not supported by SSH2');
2736                    }
2737                    throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation');
2738            }
2739        } elseif ($publickey instanceof DSA) {
2740            $privatekey = $privatekey->withSignatureFormat('SSH2');
2741            $hash = 'sha1';
2742            $signatureType = 'ssh-dss';
2743        } else {
2744            throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key');
2745        }
2746
2747        $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]);
2748
2749        $part1 = Strings::packSSH2(
2750            'Csss',
2751            NET_SSH2_MSG_USERAUTH_REQUEST,
2752            $username,
2753            'ssh-connection',
2754            'publickey'
2755        );
2756        $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr);
2757
2758        $packet = $part1 . chr(0) . $part2;
2759        $this->send_binary_packet($packet);
2760
2761        $response = $this->get_binary_packet_or_close(
2762            NET_SSH2_MSG_USERAUTH_SUCCESS,
2763            NET_SSH2_MSG_USERAUTH_FAILURE,
2764            NET_SSH2_MSG_USERAUTH_PK_OK
2765        );
2766
2767        list($type) = Strings::unpackSSH2('C', $response);
2768        switch ($type) {
2769            case NET_SSH2_MSG_USERAUTH_FAILURE:
2770                list($auth_methods) = Strings::unpackSSH2('L', $response);
2771                if (in_array('publickey', $auth_methods) && substr($signatureType, 0, 9) == 'rsa-sha2-') {
2772                    $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, ['rsa-sha2-256', 'rsa-sha2-512']);
2773                    return $this->privatekey_login($username, $privatekey);
2774                }
2775                $this->auth_methods_to_continue = $auth_methods;
2776                $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE';
2777                return false;
2778            case NET_SSH2_MSG_USERAUTH_PK_OK:
2779                // we'll just take it on faith that the public key blob and the public key algorithm name are as
2780                // they should be
2781                $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
2782                break;
2783            case NET_SSH2_MSG_USERAUTH_SUCCESS:
2784                $this->bitmap |= self::MASK_LOGIN;
2785                return true;
2786        }
2787
2788        $packet = $part1 . chr(1) . $part2;
2789        $privatekey = $privatekey->withHash($hash);
2790        $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet);
2791        if ($publickey instanceof RSA) {
2792            $signature = Strings::packSSH2('ss', $signatureType, $signature);
2793        }
2794        $packet .= Strings::packSSH2('s', $signature);
2795
2796        $this->send_binary_packet($packet);
2797
2798        $response = $this->get_binary_packet_or_close(
2799            NET_SSH2_MSG_USERAUTH_SUCCESS,
2800            NET_SSH2_MSG_USERAUTH_FAILURE
2801        );
2802
2803        list($type) = Strings::unpackSSH2('C', $response);
2804        switch ($type) {
2805            case NET_SSH2_MSG_USERAUTH_FAILURE:
2806                // either the login is bad or the server employs multi-factor authentication
2807                list($auth_methods) = Strings::unpackSSH2('L', $response);
2808                $this->auth_methods_to_continue = $auth_methods;
2809                return false;
2810            case NET_SSH2_MSG_USERAUTH_SUCCESS:
2811                $this->bitmap |= self::MASK_LOGIN;
2812                return true;
2813        }
2814    }
2815
2816    /**
2817     * Return the currently configured timeout
2818     *
2819     * @return int
2820     */
2821    public function getTimeout()
2822    {
2823        return $this->timeout;
2824    }
2825
2826    /**
2827     * Set Timeout
2828     *
2829     * $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.
2830     * Setting $timeout to false or 0 will revert to the default socket timeout.
2831     *
2832     * @param mixed $timeout
2833     */
2834    public function setTimeout($timeout)
2835    {
2836        $this->timeout = $this->curTimeout = $timeout;
2837    }
2838
2839    /**
2840     * Set Keep Alive
2841     *
2842     * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number.
2843     *
2844     * @param int $interval
2845     */
2846    public function setKeepAlive($interval)
2847    {
2848        $this->keepAlive = $interval;
2849    }
2850
2851    /**
2852     * Get the output from stdError
2853     *
2854     */
2855    public function getStdError()
2856    {
2857        return $this->stdErrorLog;
2858    }
2859
2860    /**
2861     * Execute Command
2862     *
2863     * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
2864     * In all likelihood, this is not a feature you want to be taking advantage of.
2865     *
2866     * @param string $command
2867     * @param callable $callback
2868     * @return string|bool
2869     * @psalm-return ($callback is callable ? bool : string|bool)
2870     * @throws \RuntimeException on connection error
2871     */
2872    public function exec($command, $callback = null)
2873    {
2874        $this->curTimeout = $this->timeout;
2875        $this->is_timeout = false;
2876        $this->stdErrorLog = '';
2877
2878        if (!$this->isAuthenticated()) {
2879            return false;
2880        }
2881
2882        //if ($this->isPTYOpen()) {
2883        //    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.');
2884        //}
2885
2886        $this->open_channel(self::CHANNEL_EXEC);
2887
2888        if ($this->request_pty === true) {
2889            $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
2890            $packet = Strings::packSSH2(
2891                'CNsCsN4s',
2892                NET_SSH2_MSG_CHANNEL_REQUEST,
2893                $this->server_channels[self::CHANNEL_EXEC],
2894                'pty-req',
2895                1,
2896                $this->term,
2897                $this->windowColumns,
2898                $this->windowRows,
2899                0,
2900                0,
2901                $terminal_modes
2902            );
2903
2904            $this->send_binary_packet($packet);
2905
2906            $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2907            if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2908                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
2909                throw new \RuntimeException('Unable to request pseudo-terminal');
2910            }
2911        }
2912
2913        // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2914        // down.  the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
2915        // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2916        // then immediately terminate.  without such a request exec() will loop indefinitely.  the ping process won't end but
2917        // neither will your script.
2918
2919        // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
2920        // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
2921        // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA.  RFC4254#section-5.2 corroborates.
2922        $packet = Strings::packSSH2(
2923            'CNsCs',
2924            NET_SSH2_MSG_CHANNEL_REQUEST,
2925            $this->server_channels[self::CHANNEL_EXEC],
2926            'exec',
2927            1,
2928            $command
2929        );
2930        $this->send_binary_packet($packet);
2931
2932        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2933
2934        if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2935            return false;
2936        }
2937
2938        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2939
2940        if ($this->request_pty === true) {
2941            $this->channel_id_last_interactive = self::CHANNEL_EXEC;
2942            return true;
2943        }
2944        if ($callback === false) {
2945            return true;
2946        }
2947
2948        $output = '';
2949        while (true) {
2950            $temp = $this->get_channel_packet(self::CHANNEL_EXEC);
2951            switch (true) {
2952                case $temp === true:
2953                    return is_callable($callback) ? true : $output;
2954                case $temp === false:
2955                    return false;
2956                default:
2957                    if (is_callable($callback)) {
2958                        if ($callback($temp) === true) {
2959                            $this->close_channel(self::CHANNEL_EXEC);
2960                            return true;
2961                        }
2962                    } else {
2963                        $output .= $temp;
2964                    }
2965            }
2966        }
2967    }
2968
2969    /**
2970     * How many channels are currently open?
2971     *
2972     * @return int
2973     */
2974    public function getOpenChannelCount()
2975    {
2976        return $this->channelCount;
2977    }
2978
2979    /**
2980     * Opens a channel
2981     *
2982     * @param string $channel
2983     * @param bool $skip_extended
2984     * @return bool
2985     */
2986    protected function open_channel($channel, $skip_extended = false)
2987    {
2988        if (isset($this->channel_status[$channel])) {
2989            throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
2990        }
2991
2992        $this->channelCount++;
2993
2994        if ($this->channelCount > 1 && $this->errorOnMultipleChannels) {
2995            throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels");
2996        }
2997
2998        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2999        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
3000        // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
3001        // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
3002        $this->window_size_server_to_client[$channel] = $this->window_size;
3003        // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
3004        // uses 0x4000, that's what will be used here, as well.
3005        $packet_size = 0x4000;
3006
3007        $packet = Strings::packSSH2(
3008            'CsN3',
3009            NET_SSH2_MSG_CHANNEL_OPEN,
3010            'session',
3011            $channel,
3012            $this->window_size_server_to_client[$channel],
3013            $packet_size
3014        );
3015
3016        $this->send_binary_packet($packet);
3017
3018        $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN;
3019
3020        return $this->get_channel_packet($channel, $skip_extended);
3021    }
3022
3023    /**
3024     * Creates an interactive shell
3025     *
3026     * Returns bool(true) if the shell was opened.
3027     * Returns bool(false) if the shell was already open.
3028     *
3029     * @see self::isShellOpen()
3030     * @see self::read()
3031     * @see self::write()
3032     * @return bool
3033     * @throws InsufficientSetupException if not authenticated
3034     * @throws \UnexpectedValueException on receipt of unexpected packets
3035     * @throws \RuntimeException on other errors
3036     */
3037    public function openShell()
3038    {
3039        if (!$this->isAuthenticated()) {
3040            throw new InsufficientSetupException('Operation disallowed prior to login()');
3041        }
3042
3043        $this->open_channel(self::CHANNEL_SHELL);
3044
3045        $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
3046        $packet = Strings::packSSH2(
3047            'CNsbsN4s',
3048            NET_SSH2_MSG_CHANNEL_REQUEST,
3049            $this->server_channels[self::CHANNEL_SHELL],
3050            'pty-req',
3051            true, // want reply
3052            $this->term,
3053            $this->windowColumns,
3054            $this->windowRows,
3055            0,
3056            0,
3057            $terminal_modes
3058        );
3059
3060        $this->send_binary_packet($packet);
3061
3062        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
3063
3064        if (!$this->get_channel_packet(self::CHANNEL_SHELL)) {
3065            throw new \RuntimeException('Unable to request pty');
3066        }
3067
3068        $packet = Strings::packSSH2(
3069            'CNsb',
3070            NET_SSH2_MSG_CHANNEL_REQUEST,
3071            $this->server_channels[self::CHANNEL_SHELL],
3072            'shell',
3073            true // want reply
3074        );
3075        $this->send_binary_packet($packet);
3076
3077        $response = $this->get_channel_packet(self::CHANNEL_SHELL);
3078        if ($response === false) {
3079            throw new \RuntimeException('Unable to request shell');
3080        }
3081
3082        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
3083
3084        $this->channel_id_last_interactive = self::CHANNEL_SHELL;
3085
3086        $this->bitmap |= self::MASK_SHELL;
3087
3088        return true;
3089    }
3090
3091    /**
3092     * Return the channel to be used with read(), write(), and reset(), if none were specified
3093     * @deprecated for lack of transparency in intended channel target, to be potentially replaced
3094     *             with method which guarantees open-ness of all yielded channels and throws
3095     *             error for multiple open channels
3096     * @see self::read()
3097     * @see self::write()
3098     * @return int
3099     */
3100    private function get_interactive_channel()
3101    {
3102        switch (true) {
3103            case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
3104                return self::CHANNEL_SUBSYSTEM;
3105            case $this->is_channel_status_data(self::CHANNEL_EXEC):
3106                return self::CHANNEL_EXEC;
3107            default:
3108                return self::CHANNEL_SHELL;
3109        }
3110    }
3111
3112    /**
3113     * Indicates the DATA status on the given channel
3114     *
3115     * @param int $channel The channel number to evaluate
3116     * @return bool
3117     */
3118    private function is_channel_status_data($channel)
3119    {
3120        return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
3121    }
3122
3123    /**
3124     * Return an available open channel
3125     *
3126     * @return int
3127     */
3128    private function get_open_channel()
3129    {
3130        $channel = self::CHANNEL_EXEC;
3131        do {
3132            if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
3133                return $channel;
3134            }
3135        } while ($channel++ < self::CHANNEL_SUBSYSTEM);
3136
3137        return false;
3138    }
3139
3140    /**
3141     * Request agent forwarding of remote server
3142     *
3143     * @return bool
3144     */
3145    public function requestAgentForwarding()
3146    {
3147        $request_channel = $this->get_open_channel();
3148        if ($request_channel === false) {
3149            return false;
3150        }
3151
3152        $packet = Strings::packSSH2(
3153            'CNsC',
3154            NET_SSH2_MSG_CHANNEL_REQUEST,
3155            $this->server_channels[$request_channel],
3156            'auth-agent-req@openssh.com',
3157            1
3158        );
3159
3160        $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
3161
3162        $this->send_binary_packet($packet);
3163
3164        if (!$this->get_channel_packet($request_channel)) {
3165            return false;
3166        }
3167
3168        $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
3169
3170        return true;
3171    }
3172
3173    /**
3174     * Returns the output of an interactive shell
3175     *
3176     * Returns when there's a match for $expect, which can take the form of a string literal or,
3177     * if $mode == self::READ_REGEX, a regular expression.
3178     *
3179     * If not specifying a channel, an open interactive channel will be selected, or, if there are
3180     * no open channels, an interactive shell will be created. If there are multiple open
3181     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
3182     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
3183     * channels, callers are discouraged from relying on this legacy behavior and should specify
3184     * the intended channel.
3185     *
3186     * @see self::write()
3187     * @param string $expect
3188     * @param int $mode One of the self::READ_* constants
3189     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3190     * @return string|bool|null
3191     * @throws \RuntimeException on connection error
3192     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
3193     */
3194    public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
3195    {
3196        if (!$this->isAuthenticated()) {
3197            throw new InsufficientSetupException('Operation disallowed prior to login()');
3198        }
3199
3200        $this->curTimeout = $this->timeout;
3201        $this->is_timeout = false;
3202
3203        if ($channel === null) {
3204            $channel = $this->get_interactive_channel();
3205        }
3206
3207        if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) {
3208            if ($channel != self::CHANNEL_SHELL) {
3209                throw new InsufficientSetupException('Data is not available on channel');
3210            } elseif (!$this->openShell()) {
3211                throw new \RuntimeException('Unable to initiate an interactive shell session');
3212            }
3213        }
3214
3215        if ($mode == self::READ_NEXT) {
3216            return $this->get_channel_packet($channel);
3217        }
3218
3219        $match = $expect;
3220        while (true) {
3221            if ($mode == self::READ_REGEX) {
3222                preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
3223                $match = isset($matches[0]) ? $matches[0] : '';
3224            }
3225            $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
3226            if ($pos !== false) {
3227                return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
3228            }
3229            $response = $this->get_channel_packet($channel);
3230            if ($response === true) {
3231                return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
3232            }
3233
3234            $this->interactiveBuffer .= $response;
3235        }
3236    }
3237
3238    /**
3239     * Inputs a command into an interactive shell.
3240     *
3241     * If not specifying a channel, an open interactive channel will be selected, or, if there are
3242     * no open channels, an interactive shell will be created. If there are multiple open
3243     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
3244     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
3245     * channels, callers are discouraged from relying on this legacy behavior and should specify
3246     * the intended channel.
3247     *
3248     * @see SSH2::read()
3249     * @param string $cmd
3250     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3251     * @return void
3252     * @throws \RuntimeException on connection error
3253     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
3254     * @throws TimeoutException if the write could not be completed within the requested self::setTimeout()
3255     */
3256    public function write($cmd, $channel = null)
3257    {
3258        if (!$this->isAuthenticated()) {
3259            throw new InsufficientSetupException('Operation disallowed prior to login()');
3260        }
3261
3262        if ($channel === null) {
3263            $channel = $this->get_interactive_channel();
3264        }
3265
3266        if (!$this->is_channel_status_data($channel)) {
3267            if ($channel != self::CHANNEL_SHELL) {
3268                throw new InsufficientSetupException('Data is not available on channel');
3269            } elseif (!$this->openShell()) {
3270                throw new \RuntimeException('Unable to initiate an interactive shell session');
3271            }
3272        }
3273
3274        $this->curTimeout = $this->timeout;
3275        $this->is_timeout = false;
3276        $this->send_channel_packet($channel, $cmd);
3277    }
3278
3279    /**
3280     * Start a subsystem.
3281     *
3282     * Right now only one subsystem at a time is supported. To support multiple subsystem's stopSubsystem() could accept
3283     * a string that contained the name of the subsystem, but at that point, only one subsystem of each type could be opened.
3284     * To support multiple subsystem's of the same name maybe it'd be best if startSubsystem() generated a new channel id and
3285     * returns that and then that that was passed into stopSubsystem() but that'll be saved for a future date and implemented
3286     * if there's sufficient demand for such a feature.
3287     *
3288     * @see self::stopSubsystem()
3289     * @param string $subsystem
3290     * @return bool
3291     */
3292    public function startSubsystem($subsystem)
3293    {
3294        $this->open_channel(self::CHANNEL_SUBSYSTEM);
3295
3296        $packet = Strings::packSSH2(
3297            'CNsCs',
3298            NET_SSH2_MSG_CHANNEL_REQUEST,
3299            $this->server_channels[self::CHANNEL_SUBSYSTEM],
3300            'subsystem',
3301            1,
3302            $subsystem
3303        );
3304        $this->send_binary_packet($packet);
3305
3306        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
3307
3308        if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
3309            return false;
3310        }
3311
3312        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
3313
3314        $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
3315
3316        return true;
3317    }
3318
3319    /**
3320     * Stops a subsystem.
3321     *
3322     * @see self::startSubsystem()
3323     * @return bool
3324     */
3325    public function stopSubsystem()
3326    {
3327        if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
3328            $this->close_channel(self::CHANNEL_SUBSYSTEM);
3329        }
3330        return true;
3331    }
3332
3333    /**
3334     * Closes a channel
3335     *
3336     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
3337     *
3338     * If not specifying a channel, an open interactive channel will be selected. If there are
3339     * multiple open interactive channels, a legacy behavior will apply in which channel selection
3340     * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
3341     * interactive channels, callers are discouraged from relying on this legacy behavior and
3342     * should specify the intended channel.
3343     *
3344     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3345     * @return void
3346     */
3347    public function reset($channel = null)
3348    {
3349        if ($channel === null) {
3350            $channel = $this->get_interactive_channel();
3351        }
3352        if ($this->isInteractiveChannelOpen($channel)) {
3353            $this->close_channel($channel);
3354        }
3355    }
3356
3357    /**
3358     * Send EOF on a channel
3359     *
3360     * Sends an EOF to the stream; this is typically used to close standard
3361     * input, while keeping output and error alive.
3362     *
3363     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3364     * @return void
3365     */
3366    public function sendEOF($channel = null)
3367    {
3368        if ($channel === null) {
3369            $channel = $this->get_interactive_channel();
3370        }
3371
3372        $excludeStatuses = [NET_SSH2_MSG_CHANNEL_EOF, NET_SSH2_MSG_CHANNEL_CLOSE];
3373        if (isset($this->channel_status[$channel]) && !in_array($this->channel_status[$channel], $excludeStatuses)) {
3374            $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$channel]));
3375        }
3376    }
3377
3378    /**
3379     * Is timeout?
3380     *
3381     * Did exec() or read() return because they timed out or because they encountered the end?
3382     *
3383     */
3384    public function isTimeout()
3385    {
3386        return $this->is_timeout;
3387    }
3388
3389    /**
3390     * Disconnect
3391     *
3392     */
3393    public function disconnect()
3394    {
3395        $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3396        if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
3397            fclose($this->realtime_log_file);
3398        }
3399        unset(self::$connections[$this->getResourceId()]);
3400    }
3401
3402    /**
3403     * Destructor.
3404     *
3405     * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
3406     * disconnect().
3407     *
3408     */
3409    public function __destruct()
3410    {
3411        $this->disconnect();
3412    }
3413
3414    /**
3415     * Is the connection still active?
3416     *
3417     * $level has 3x possible values:
3418     * 0 (default): phpseclib takes a passive approach to see if the connection is still active by calling feof()
3419     *    on the socket
3420     * 1: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_IGNORE
3421     *    packet that doesn't require a response
3422     * 2: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_CHANNEL_OPEN
3423     *    packet and imediately trying to close that channel. some routers, in particular, however, will only let you
3424     *    open one channel, so this approach could yield false positives
3425     *
3426     * @param int $level
3427     * @return bool
3428     */
3429    public function isConnected($level = 0)
3430    {
3431        if (!is_int($level) || $level < 0 || $level > 2) {
3432            throw new \InvalidArgumentException('$level must be 0, 1 or 2');
3433        }
3434
3435        if ($level == 0) {
3436            return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
3437        }
3438        try {
3439            if ($level == 1) {
3440                $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
3441            } else {
3442                $this->open_channel(self::CHANNEL_KEEP_ALIVE);
3443                $this->close_channel(self::CHANNEL_KEEP_ALIVE);
3444            }
3445            return true;
3446        } catch (\Exception $e) {
3447            return false;
3448        }
3449    }
3450
3451    /**
3452     * Have you successfully been logged in?
3453     *
3454     * @return bool
3455     */
3456    public function isAuthenticated()
3457    {
3458        return (bool) ($this->bitmap & self::MASK_LOGIN);
3459    }
3460
3461    /**
3462     * Is the interactive shell active?
3463     *
3464     * @return bool
3465     */
3466    public function isShellOpen()
3467    {
3468        return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
3469    }
3470
3471    /**
3472     * Is the exec pty active?
3473     *
3474     * @return bool
3475     */
3476    public function isPTYOpen()
3477    {
3478        return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
3479    }
3480
3481    /**
3482     * Is the given interactive channel active?
3483     *
3484     * @param int $channel Channel id returned by self::getInteractiveChannelId()
3485     * @return bool
3486     */
3487    public function isInteractiveChannelOpen($channel)
3488    {
3489        return $this->isAuthenticated() && $this->is_channel_status_data($channel);
3490    }
3491
3492    /**
3493     * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
3494     * Returns 0 if no interactive channel has been opened.
3495     *
3496     * @see self::isInteractiveChannelOpen()
3497     * @return int
3498     */
3499    public function getInteractiveChannelId()
3500    {
3501        return $this->channel_id_last_interactive;
3502    }
3503
3504    /**
3505     * Pings a server connection, or tries to reconnect if the connection has gone down
3506     *
3507     * Inspired by http://php.net/manual/en/mysqli.ping.php
3508     *
3509     * @return bool
3510     */
3511    public function ping()
3512    {
3513        if (!$this->isAuthenticated()) {
3514            if (!empty($this->auth)) {
3515                return $this->reconnect();
3516            }
3517            return false;
3518        }
3519
3520        try {
3521            $this->open_channel(self::CHANNEL_KEEP_ALIVE);
3522        } catch (\RuntimeException $e) {
3523            return $this->reconnect();
3524        }
3525
3526        $this->close_channel(self::CHANNEL_KEEP_ALIVE);
3527        return true;
3528    }
3529
3530    /**
3531     * In situ reconnect method
3532     *
3533     * @return boolean
3534     */
3535    private function reconnect()
3536    {
3537        $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3538        $this->connect();
3539        foreach ($this->auth as $auth) {
3540            $result = $this->login(...$auth);
3541        }
3542        return $result;
3543    }
3544
3545    /**
3546     * Resets a connection for re-use
3547     */
3548    protected function reset_connection()
3549    {
3550        if (is_resource($this->fsock) && get_resource_type($this->fsock) === 'stream') {
3551            fclose($this->fsock);
3552        }
3553        $this->fsock = null;
3554        $this->bitmap = 0;
3555        $this->binary_packet_buffer = null;
3556        $this->decrypt = $this->encrypt = false;
3557        $this->decrypt_block_size = $this->encrypt_block_size = 8;
3558        $this->hmac_check = $this->hmac_create = false;
3559        $this->hmac_size = false;
3560        $this->session_id = false;
3561        $this->last_packet = null;
3562        $this->get_seq_no = $this->send_seq_no = 0;
3563        $this->channel_status = [];
3564        $this->channel_id_last_interactive = 0;
3565        $this->channel_buffers = [];
3566        $this->channel_buffers_write = [];
3567    }
3568
3569    /**
3570     * @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.
3571     */
3572    private function get_stream_timeout()
3573    {
3574        $sec = ini_get('default_socket_timeout');
3575        $usec = 0;
3576        if ($this->curTimeout > 0) {
3577            $sec = (int) floor($this->curTimeout);
3578            $usec = (int) (1000000 * ($this->curTimeout - $sec));
3579        }
3580        if ($this->keepAlive > 0) {
3581            $elapsed = microtime(true) - $this->last_packet;
3582            $timeout = max($this->keepAlive - $elapsed, 0);
3583            if (!$this->curTimeout || $timeout < $this->curTimeout) {
3584                $sec = (int) floor($timeout);
3585                $usec = (int) (1000000 * ($timeout - $sec));
3586            }
3587        }
3588        return [$sec, $usec];
3589    }
3590
3591    /**
3592     * Retrieves the next packet with added timeout and type handling
3593     *
3594     * @param string $message_types Message types to enforce in response, closing if not met
3595     * @return string
3596     * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
3597     */
3598    private function get_binary_packet_or_close(...$message_types)
3599    {
3600        try {
3601            $packet = $this->get_binary_packet();
3602            if (count($message_types) > 0 && !in_array(ord($packet[0]), $message_types)) {
3603                $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3604                throw new ConnectionClosedException('Bad message type. Expected: #'
3605                    . implode(', #', $message_types) . '. Got: #' . ord($packet[0]));
3606            }
3607            return $packet;
3608        } catch (TimeoutException $e) {
3609            $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3610            throw new ConnectionClosedException('Connection closed due to timeout');
3611        }
3612    }
3613
3614    /**
3615     * Gets Binary Packets
3616     *
3617     * See '6. Binary Packet Protocol' of rfc4253 for more info.
3618     *
3619     * @see self::_send_binary_packet()
3620     * @return string
3621     * @throws TimeoutException If user requested timeout was reached while waiting for next packet
3622     * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
3623     */
3624    private function get_binary_packet()
3625    {
3626        if (!is_resource($this->fsock)) {
3627            throw new \InvalidArgumentException('fsock is not a resource.');
3628        }
3629        if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
3630            return $this->filter(array_shift($this->kex_buffer));
3631        }
3632        if ($this->binary_packet_buffer == null) {
3633            // buffer the packet to permit continued reads across timeouts
3634            $this->binary_packet_buffer = (object) [
3635                'read_time' => 0, // the time to read the packet from the socket
3636                'raw' => '', // the raw payload read from the socket
3637                'plain' => '', // the packet in plain text, excluding packet_length header
3638                'packet_length' => null, // the packet_length value pulled from the payload
3639                'size' => $this->decrypt_block_size, // the total size of this packet to be read from the socket
3640                                                     // initialize to read single block until packet_length is available
3641            ];
3642        }
3643        $packet = $this->binary_packet_buffer;
3644        while (strlen($packet->raw) < $packet->size) {
3645            if (feof($this->fsock)) {
3646                $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3647                throw new ConnectionClosedException('Connection closed by server');
3648            }
3649            if ($this->curTimeout < 0) {
3650                $this->is_timeout = true;
3651                throw new TimeoutException('Timed out waiting for server');
3652            }
3653            $this->send_keep_alive();
3654
3655            list($sec, $usec) = $this->get_stream_timeout();
3656            stream_set_timeout($this->fsock, $sec, $usec);
3657            $start = microtime(true);
3658            $raw = stream_get_contents($this->fsock, $packet->size - strlen($packet->raw));
3659            $elapsed = microtime(true) - $start;
3660            $packet->read_time += $elapsed;
3661            if ($this->curTimeout > 0) {
3662                $this->curTimeout -= $elapsed;
3663            }
3664            if ($raw === false) {
3665                $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3666                throw new ConnectionClosedException('Connection closed by server');
3667            } elseif (!strlen($raw)) {
3668                continue;
3669            }
3670            $packet->raw .= $raw;
3671            if (!$packet->packet_length) {
3672                $this->get_binary_packet_size($packet);
3673            }
3674        }
3675
3676        if (strlen($packet->raw) != $packet->size) {
3677            throw new \RuntimeException('Size of packet was not expected length');
3678        }
3679        // destroy buffer as packet represents the entire payload and should be processed in full
3680        $this->binary_packet_buffer = null;
3681        // copy the raw payload, so as not to destroy original
3682        $raw = $packet->raw;
3683        if ($this->hmac_check instanceof Hash) {
3684            $hmac = Strings::pop($raw, $this->hmac_size);
3685        }
3686        $packet_length_header_size = 4;
3687        if ($this->decrypt) {
3688            switch ($this->decryptName) {
3689                case 'aes128-gcm@openssh.com':
3690                case 'aes256-gcm@openssh.com':
3691                    $this->decrypt->setNonce(
3692                        $this->decryptFixedPart .
3693                        $this->decryptInvocationCounter
3694                    );
3695                    Strings::increment_str($this->decryptInvocationCounter);
3696                    $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
3697                    $this->decrypt->setTag(Strings::pop($raw, $this->decrypt_block_size));
3698                    $packet->plain = $this->decrypt->decrypt($raw);
3699                    break;
3700                case 'chacha20-poly1305@openssh.com':
3701                    // This should be impossible, but we are checking anyway to narrow the type for Psalm.
3702                    if (!($this->decrypt instanceof ChaCha20)) {
3703                        throw new \LogicException('$this->decrypt is not a ' . ChaCha20::class);
3704                    }
3705                    $this->decrypt->setNonce(pack('N2', 0, $this->get_seq_no));
3706                    $this->decrypt->setCounter(0);
3707                    // this is the same approach that's implemented in Salsa20::createPoly1305Key()
3708                    // but we don't want to use the same AEAD construction that RFC8439 describes
3709                    // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
3710                    $this->decrypt->setPoly1305Key(
3711                        $this->decrypt->encrypt(str_repeat("\0", 32))
3712                    );
3713                    $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
3714                    $this->decrypt->setCounter(1);
3715                    $this->decrypt->setTag(Strings::pop($raw, 16));
3716                    $packet->plain = $this->decrypt->decrypt($raw);
3717                    break;
3718                default:
3719                    if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
3720                        // first block was already decrypted for contained packet_length header
3721                        Strings::shift($raw, $this->decrypt_block_size);
3722                        if (strlen($raw) > 0) {
3723                            $packet->plain .= $this->decrypt->decrypt($raw);
3724                        }
3725                    } else {
3726                        Strings::shift($raw, $packet_length_header_size);
3727                        $packet->plain = $this->decrypt->decrypt($raw);
3728                    }
3729                    break;
3730            }
3731        } else {
3732            Strings::shift($raw, $packet_length_header_size);
3733            $packet->plain = $raw;
3734        }
3735        if ($this->hmac_check instanceof Hash) {
3736            $reconstructed = !$this->hmac_check_etm ?
3737                pack('Na*', $packet->packet_length, $packet->plain) :
3738                substr($packet->raw, 0, -$this->hmac_size);
3739            if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
3740                $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
3741                if ($hmac != $this->hmac_check->hash($reconstructed)) {
3742                    $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
3743                    throw new ConnectionClosedException('Invalid UMAC');
3744                }
3745            } else {
3746                if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
3747                    $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
3748                    throw new ConnectionClosedException('Invalid HMAC');
3749                }
3750            }
3751        }
3752        $padding_length = 0;
3753        $payload = $packet->plain;
3754        $padding_length = unpack('Cpadding_length', Strings::shift($payload, 1))['padding_length'];
3755        if ($padding_length > 0) {
3756            Strings::pop($payload, $padding_length);
3757        }
3758
3759        if (!$this->keyExchangeInProgress) {
3760            $this->bytesTransferredSinceLastKEX += $packet->packet_length + $padding_length + 5;
3761        }
3762
3763        if (empty($payload)) {
3764            $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3765            throw new ConnectionClosedException('Plaintext is too short');
3766        }
3767
3768        switch ($this->decompress) {
3769            case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
3770                if (!$this->isAuthenticated()) {
3771                    break;
3772                }
3773                // fall-through
3774            case self::NET_SSH2_COMPRESSION_ZLIB:
3775                if ($this->regenerate_decompression_context) {
3776                    $this->regenerate_decompression_context = false;
3777
3778                    $cmf = ord($payload[0]);
3779                    $cm = $cmf & 0x0F;
3780                    if ($cm != 8) { // deflate
3781                        throw new UnsupportedAlgorithmException("Only CM = 8 ('deflate') is supported ($cm)");
3782                    }
3783                    $cinfo = ($cmf & 0xF0) >> 4;
3784                    if ($cinfo > 7) {
3785                        throw new \RuntimeException("CINFO above 7 is not allowed ($cinfo)");
3786                    }
3787                    $windowSize = 1 << ($cinfo + 8);
3788
3789                    $flg = ord($payload[1]);
3790                    //$fcheck = $flg && 0x0F;
3791                    if ((($cmf << 8) | $flg) % 31) {
3792                        throw new \RuntimeException('fcheck failed');
3793                    }
3794                    $fdict = boolval($flg & 0x20);
3795                    $flevel = ($flg & 0xC0) >> 6;
3796
3797                    $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]);
3798                    $payload = substr($payload, 2);
3799                }
3800                if ($this->decompress_context) {
3801                    $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH);
3802                }
3803        }
3804
3805        $this->get_seq_no++;
3806
3807        if (defined('NET_SSH2_LOGGING')) {
3808            $current = microtime(true);
3809            $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
3810            $message_number = '<- ' . $message_number .
3811                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($packet->read_time, 4) . 's)';
3812            $this->append_log($message_number, $payload);
3813        }
3814        $this->last_packet = microtime(true);
3815
3816        if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
3817            $this->key_exchange();
3818        }
3819
3820        return $this->filter($payload);
3821    }
3822
3823    /**
3824     * @param object $packet The packet object being constructed, passed by reference
3825     *        The size, packet_length, and plain properties of this object may be modified in processing
3826     * @throws InvalidPacketLengthException if the packet length header is invalid
3827     */
3828    private function get_binary_packet_size(&$packet)
3829    {
3830        $packet_length_header_size = 4;
3831        if (strlen($packet->raw) < $packet_length_header_size) {
3832            return;
3833        }
3834        $packet_length = 0;
3835        $added_validation_length = 0; // indicates when the packet length header is included when validating packet length against block size
3836        if ($this->decrypt) {
3837            switch ($this->decryptName) {
3838                case 'aes128-gcm@openssh.com':
3839                case 'aes256-gcm@openssh.com':
3840                    $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
3841                    $packet->size = $packet_length_header_size + $packet_length + $this->decrypt_block_size; // expect tag
3842                    break;
3843                case 'chacha20-poly1305@openssh.com':
3844                    $this->lengthDecrypt->setNonce(pack('N2', 0, $this->get_seq_no));
3845                    $packet_length_header = $this->lengthDecrypt->decrypt(substr($packet->raw, 0, $packet_length_header_size));
3846                    $packet_length = unpack('Npacket_length', $packet_length_header)['packet_length'];
3847                    $packet->size = $packet_length_header_size + $packet_length + 16; // expect tag
3848                    break;
3849                default:
3850                    if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
3851                        if (strlen($packet->raw) < $this->decrypt_block_size) {
3852                            return;
3853                        }
3854                        $packet->plain = $this->decrypt->decrypt(substr($packet->raw, 0, $this->decrypt_block_size));
3855                        $packet_length = unpack('Npacket_length', Strings::shift($packet->plain, $packet_length_header_size))['packet_length'];
3856                        $packet->size = $packet_length_header_size + $packet_length;
3857                        $added_validation_length = $packet_length_header_size;
3858                    } else {
3859                        $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
3860                        $packet->size = $packet_length_header_size + $packet_length;
3861                    }
3862                    break;
3863            }
3864        } else {
3865            $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
3866            $packet->size = $packet_length_header_size + $packet_length;
3867            $added_validation_length = $packet_length_header_size;
3868        }
3869        // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
3870        // "implementations SHOULD check that the packet length is reasonable"
3871        // PuTTY uses 0x9000 as the actual max packet size and so to shall we
3872        if (
3873            $packet_length <= 0 || $packet_length > 0x9000
3874            || ($packet_length + $added_validation_length) % $this->decrypt_block_size != 0
3875        ) {
3876            $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3877            throw new InvalidPacketLengthException('Invalid packet length');
3878        }
3879        if ($this->hmac_check instanceof Hash) {
3880            $packet->size += $this->hmac_size;
3881        }
3882        $packet->packet_length = $packet_length;
3883    }
3884
3885    /**
3886     * Handle Disconnect
3887     *
3888     * Because some binary packets need to be ignored...
3889     *
3890     * @see self::filter()
3891     * @see self::key_exchange()
3892     * @return boolean
3893     * @access private
3894     */
3895    private function handleDisconnect($payload)
3896    {
3897        Strings::shift($payload, 1);
3898        list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
3899        $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
3900        $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3901        throw new ConnectionClosedException('Connection closed by server');
3902    }
3903
3904    /**
3905     * Filter Binary Packets
3906     *
3907     * Because some binary packets need to be ignored...
3908     *
3909     * @see self::_get_binary_packet()
3910     * @param string $payload
3911     * @return string
3912     */
3913    private function filter($payload)
3914    {
3915        if (ord($payload[0]) == NET_SSH2_MSG_DISCONNECT) {
3916            return $this->handleDisconnect($payload);
3917        }
3918
3919        if ($this->session_id === false && $this->keyExchangeInProgress) {
3920            return $payload;
3921        }
3922
3923        switch (ord($payload[0])) {
3924            case NET_SSH2_MSG_IGNORE:
3925                $payload = $this->get_binary_packet();
3926                break;
3927            case NET_SSH2_MSG_DEBUG:
3928                Strings::shift($payload, 2); // second byte is "always_display"
3929                list($message) = Strings::unpackSSH2('s', $payload);
3930                $this->errors[] = "SSH_MSG_DEBUG: $message";
3931                $payload = $this->get_binary_packet();
3932                break;
3933            case NET_SSH2_MSG_UNIMPLEMENTED:
3934                break; // return payload
3935            case NET_SSH2_MSG_KEXINIT:
3936                // this is here for server initiated key re-exchanges after the initial key exchange
3937                if (!$this->keyExchangeInProgress && $this->session_id !== false) {
3938                    if (!$this->key_exchange($payload)) {
3939                        $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
3940                        throw new ConnectionClosedException('Key exchange failed');
3941                    }
3942                    $payload = $this->get_binary_packet();
3943                }
3944                break;
3945            case NET_SSH2_MSG_EXT_INFO:
3946                Strings::shift($payload, 1);
3947                list($nr_extensions) = Strings::unpackSSH2('N', $payload);
3948                for ($i = 0; $i < $nr_extensions; $i++) {
3949                    list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $payload);
3950                    if ($extension_name == 'server-sig-algs') {
3951                        $this->supported_private_key_algorithms = explode(',', $extension_value);
3952                    }
3953                }
3954                $payload = $this->get_binary_packet();
3955        }
3956
3957        /*
3958           Once a party has sent a SSH_MSG_KEXINIT message for key exchange or
3959           re-exchange, until it has sent a SSH_MSG_NEWKEYS message (Section
3960           7.3), it MUST NOT send any messages other than:
3961
3962           o  Transport layer generic messages (1 to 19) (but
3963              SSH_MSG_SERVICE_REQUEST and SSH_MSG_SERVICE_ACCEPT MUST NOT be
3964              sent);
3965
3966           o  Algorithm negotiation messages (20 to 29) (but further
3967              SSH_MSG_KEXINIT messages MUST NOT be sent);
3968
3969           o  Specific key exchange method messages (30 to 49).
3970
3971           -- https://www.rfc-editor.org/rfc/rfc4253#section-7.1
3972        */
3973        if ($this->keyExchangeInProgress) {
3974            return $payload;
3975        }
3976
3977        // 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
3978        if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
3979            Strings::shift($payload, 1);
3980            list($this->banner_message) = Strings::unpackSSH2('s', $payload);
3981            $payload = $this->get_binary_packet();
3982        }
3983
3984        // only called when we've already logged in
3985        if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) {
3986            switch (ord($payload[0])) {
3987                case NET_SSH2_MSG_CHANNEL_REQUEST:
3988                    if (strlen($payload) == 31) {
3989                        $unpacked = unpack('cpacket_type/Nchannel/Nlength', $payload);
3990                        $packet_type = $unpacked['packet_type'];
3991                        $channel = $unpacked['channel'];
3992                        $length = $unpacked['length'];
3993                        if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) {
3994                            if (ord(substr($payload, 9 + $length))) { // want reply
3995                                $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
3996                            }
3997                            $payload = $this->get_binary_packet();
3998                        }
3999                    }
4000                    break;
4001                case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
4002                    Strings::shift($payload, 1);
4003                    list($request_name, $want_reply) = Strings::unpackSSH2('sb', $payload);
4004                    $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name";
4005                    if ($want_reply) {
4006                        $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE));
4007                    }
4008                    $payload = $this->get_binary_packet();
4009                    break;
4010                case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
4011                    Strings::shift($payload, 1);
4012                    list($data, $server_channel) = Strings::unpackSSH2('sN', $payload);
4013                    switch ($data) {
4014                        case 'auth-agent':
4015                        case 'auth-agent@openssh.com':
4016                            if (isset($this->agent)) {
4017                                $new_channel = self::CHANNEL_AGENT_FORWARD;
4018
4019                                list(
4020                                    $remote_window_size,
4021                                    $remote_maximum_packet_size
4022                                ) = Strings::unpackSSH2('NN', $payload);
4023
4024                                $this->packet_size_client_to_server[$new_channel] = $remote_window_size;
4025                                $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
4026                                $this->window_size_client_to_server[$new_channel] = $this->window_size;
4027
4028                                $packet_size = 0x4000;
4029
4030                                $packet = pack(
4031                                    'CN4',
4032                                    NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
4033                                    $server_channel,
4034                                    $new_channel,
4035                                    $packet_size,
4036                                    $packet_size
4037                                );
4038
4039                                $this->server_channels[$new_channel] = $server_channel;
4040                                $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
4041                                $this->send_binary_packet($packet);
4042                            }
4043                            break;
4044                        default:
4045                            $packet = Strings::packSSH2(
4046                                'CN2ss',
4047                                NET_SSH2_MSG_CHANNEL_OPEN_FAILURE,
4048                                $server_channel,
4049                                NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
4050                                '', // description
4051                                '' // language tag
4052                            );
4053                            $this->send_binary_packet($packet);
4054                    }
4055
4056                    $payload = $this->get_binary_packet();
4057                    break;
4058            }
4059        }
4060
4061        return $payload;
4062    }
4063
4064    /**
4065     * Enable Quiet Mode
4066     *
4067     * Suppress stderr from output
4068     *
4069     */
4070    public function enableQuietMode()
4071    {
4072        $this->quiet_mode = true;
4073    }
4074
4075    /**
4076     * Disable Quiet Mode
4077     *
4078     * Show stderr in output
4079     *
4080     */
4081    public function disableQuietMode()
4082    {
4083        $this->quiet_mode = false;
4084    }
4085
4086    /**
4087     * Returns whether Quiet Mode is enabled or not
4088     *
4089     * @see self::enableQuietMode()
4090     * @see self::disableQuietMode()
4091     * @return bool
4092     */
4093    public function isQuietModeEnabled()
4094    {
4095        return $this->quiet_mode;
4096    }
4097
4098    /**
4099     * Enable request-pty when using exec()
4100     *
4101     */
4102    public function enablePTY()
4103    {
4104        $this->request_pty = true;
4105    }
4106
4107    /**
4108     * Disable request-pty when using exec()
4109     *
4110     */
4111    public function disablePTY()
4112    {
4113        if ($this->isPTYOpen()) {
4114            $this->close_channel(self::CHANNEL_EXEC);
4115        }
4116        $this->request_pty = false;
4117    }
4118
4119    /**
4120     * Returns whether request-pty is enabled or not
4121     *
4122     * @see self::enablePTY()
4123     * @see self::disablePTY()
4124     * @return bool
4125     */
4126    public function isPTYEnabled()
4127    {
4128        return $this->request_pty;
4129    }
4130
4131    /**
4132     * Gets channel data
4133     *
4134     * Returns the data as a string. bool(true) is returned if:
4135     *
4136     * - the server closes the channel
4137     * - if the connection times out
4138     * - if a window adjust packet is received on the given negated client channel
4139     * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
4140     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
4141     * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
4142     *
4143     * bool(false) is returned if:
4144     *
4145     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
4146     *
4147     * @param int $client_channel Specifies the channel to return data for, and data received
4148     *        on other channels is buffered. The respective negative value of a channel is
4149     *        also supported for the case that the caller is awaiting adjustment of the data
4150     *        window, and where data received on that respective channel is also buffered.
4151     * @param bool $skip_extended
4152     * @return mixed
4153     * @throws \RuntimeException on connection error
4154     */
4155    protected function get_channel_packet($client_channel, $skip_extended = false)
4156    {
4157        if (!empty($this->channel_buffers[$client_channel])) {
4158            // in phpseclib 4.0 this should be changed to $this->channel_status[$client_channel] ?? null
4159            switch (isset($this->channel_status[$client_channel]) ? $this->channel_status[$client_channel] : null) {
4160                case NET_SSH2_MSG_CHANNEL_REQUEST:
4161                    foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
4162                        switch (ord($packet[0])) {
4163                            case NET_SSH2_MSG_CHANNEL_SUCCESS:
4164                            case NET_SSH2_MSG_CHANNEL_FAILURE:
4165                                unset($this->channel_buffers[$client_channel][$i]);
4166                                return substr($packet, 1);
4167                        }
4168                    }
4169                    break;
4170                default:
4171                    return substr(array_shift($this->channel_buffers[$client_channel]), 1);
4172            }
4173        }
4174
4175        while (true) {
4176            try {
4177                $response = $this->get_binary_packet();
4178            } catch (TimeoutException $e) {
4179                return true;
4180            }
4181            list($type) = Strings::unpackSSH2('C', $response);
4182            if (strlen($response) >= 4) {
4183                list($channel) = Strings::unpackSSH2('N', $response);
4184            }
4185
4186            // will not be setup yet on incoming channel open request
4187            if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
4188                $this->window_size_server_to_client[$channel] -= strlen($response);
4189
4190                // resize the window, if appropriate
4191                if ($this->window_size_server_to_client[$channel] < 0) {
4192                // PuTTY does something more analogous to the following:
4193                //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
4194                    $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
4195                    $this->send_binary_packet($packet);
4196                    $this->window_size_server_to_client[$channel] += $this->window_resize;
4197                }
4198
4199                switch ($type) {
4200                    case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
4201                        list($window_size) = Strings::unpackSSH2('N', $response);
4202                        $this->window_size_client_to_server[$channel] += $window_size;
4203                        if ($channel == -$client_channel) {
4204                            return true;
4205                        }
4206
4207                        continue 2;
4208                    case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
4209                        /*
4210                        if ($client_channel == self::CHANNEL_EXEC) {
4211                            $this->send_channel_packet($client_channel, chr(0));
4212                        }
4213                        */
4214                        // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
4215                        list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response);
4216                        $this->stdErrorLog .= $data;
4217                        if ($skip_extended || $this->quiet_mode) {
4218                            continue 2;
4219                        }
4220                        if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) {
4221                            return $data;
4222                        }
4223                        $this->channel_buffers[$channel][] = chr($type) . $data;
4224
4225                        continue 2;
4226                    case NET_SSH2_MSG_CHANNEL_REQUEST:
4227                        if (!isset($this->channel_status[$channel])) {
4228                            continue 2;
4229                        }
4230                        list($value) = Strings::unpackSSH2('s', $response);
4231                        switch ($value) {
4232                            case 'exit-signal':
4233                                list(
4234                                    , // FALSE
4235                                    $signal_name,
4236                                    , // core dumped
4237                                    $error_message
4238                                ) = Strings::unpackSSH2('bsbs', $response);
4239
4240                                $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name";
4241                                if (strlen($error_message)) {
4242                                    $this->errors[count($this->errors) - 1] .= "\r\n$error_message";
4243                                }
4244                                if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
4245                                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
4246                                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$channel]));
4247                                    }
4248                                    $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4249
4250                                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4251                                }
4252                                continue 3;
4253                            case 'exit-status':
4254                                list(, $this->exit_status) = Strings::unpackSSH2('CN', $response);
4255
4256                                // "The client MAY ignore these messages."
4257                                // -- http://tools.ietf.org/html/rfc4254#section-6.10
4258
4259                                continue 3;
4260                            default:
4261                                list($want_reply) = Strings::unpackSSH2('b', $response);
4262                                if ($want_reply) {
4263                                    // "If the request is not recognized or is not supported for the channel,
4264                                    //  SSH_MSG_CHANNEL_FAILURE is returned."
4265                                    // -- https://datatracker.ietf.org/doc/html/rfc4254#page-10
4266                                    $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_FAILURE, $this->server_channels[$channel]));
4267                                }
4268                                continue 3;
4269                        }
4270                }
4271
4272                switch ($this->channel_status[$channel]) {
4273                    case NET_SSH2_MSG_CHANNEL_OPEN:
4274                        switch ($type) {
4275                            case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
4276                                list(
4277                                    $this->server_channels[$channel],
4278                                    $window_size,
4279                                    $this->packet_size_client_to_server[$channel]
4280                                ) = Strings::unpackSSH2('NNN', $response);
4281
4282                                if ($window_size < 0) {
4283                                    $window_size &= 0x7FFFFFFF;
4284                                    $window_size += 0x80000000;
4285                                }
4286                                $this->window_size_client_to_server[$channel] = $window_size;
4287                                $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended);
4288                                $this->on_channel_open();
4289                                return $result;
4290                            case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
4291                                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4292                                throw new \RuntimeException('Unable to open channel');
4293                            default:
4294                                if ($client_channel == $channel) {
4295                                    $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4296                                    throw new \RuntimeException('Unexpected response to open request');
4297                                }
4298                                return $this->get_channel_packet($client_channel, $skip_extended);
4299                        }
4300                        break;
4301                    case NET_SSH2_MSG_CHANNEL_REQUEST:
4302                        switch ($type) {
4303                            case NET_SSH2_MSG_CHANNEL_SUCCESS:
4304                                return true;
4305                            case NET_SSH2_MSG_CHANNEL_FAILURE:
4306                                return false;
4307                            case NET_SSH2_MSG_CHANNEL_DATA:
4308                                list($data) = Strings::unpackSSH2('s', $response);
4309                                $this->channel_buffers[$channel][] = chr($type) . $data;
4310                                return $this->get_channel_packet($client_channel, $skip_extended);
4311                            default:
4312                                $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4313                                throw new \RuntimeException('Unable to fulfill channel request');
4314                        }
4315                    case NET_SSH2_MSG_CHANNEL_CLOSE:
4316                        if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
4317                            return true;
4318                        }
4319                        return $this->get_channel_packet($client_channel, $skip_extended);
4320                }
4321            }
4322
4323            // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
4324
4325            switch ($type) {
4326                case NET_SSH2_MSG_CHANNEL_DATA:
4327                    /*
4328                    if ($channel == self::CHANNEL_EXEC) {
4329                        // SCP requires null packets, such as this, be sent.  further, in the case of the ssh.com SSH server
4330                        // this actually seems to make things twice as fast.  more to the point, the message right after
4331                        // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
4332                        // in OpenSSH it slows things down but only by a couple thousandths of a second.
4333                        $this->send_channel_packet($channel, chr(0));
4334                    }
4335                    */
4336                    list($data) = Strings::unpackSSH2('s', $response);
4337
4338                    if ($channel == self::CHANNEL_AGENT_FORWARD) {
4339                        $agent_response = $this->agent->forwardData($data);
4340                        if (!is_bool($agent_response)) {
4341                            $this->send_channel_packet($channel, $agent_response);
4342                        }
4343                        break;
4344                    }
4345
4346                    if ($client_channel == $channel) {
4347                        return $data;
4348                    }
4349                    $this->channel_buffers[$channel][] = chr($type) . $data;
4350                    break;
4351                case NET_SSH2_MSG_CHANNEL_CLOSE:
4352                    $this->curTimeout = 5;
4353
4354                    $this->close_channel_bitmap($channel);
4355
4356                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
4357                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4358                    }
4359
4360                    unset($this->channel_status[$channel]);
4361                    $this->channelCount--;
4362
4363                    if ($client_channel == $channel) {
4364                        return true;
4365                    }
4366                    // fall-through
4367                case NET_SSH2_MSG_CHANNEL_EOF:
4368                    break;
4369                default:
4370                    $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4371                    throw new \RuntimeException("Error reading channel data ($type)");
4372            }
4373        }
4374    }
4375
4376    /**
4377     * Sends Binary Packets
4378     *
4379     * See '6. Binary Packet Protocol' of rfc4253 for more info.
4380     *
4381     * @param string $data
4382     * @param string $logged
4383     * @see self::_get_binary_packet()
4384     * @return void
4385     */
4386    protected function send_binary_packet($data, $logged = null)
4387    {
4388        if (!is_resource($this->fsock) || feof($this->fsock)) {
4389            $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
4390            throw new ConnectionClosedException('Connection closed prematurely');
4391        }
4392
4393        if (!isset($logged)) {
4394            $logged = $data;
4395        }
4396
4397        switch ($this->compress) {
4398            case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
4399                if (!$this->isAuthenticated()) {
4400                    break;
4401                }
4402                // fall-through
4403            case self::NET_SSH2_COMPRESSION_ZLIB:
4404                if (!$this->regenerate_compression_context) {
4405                    $header = '';
4406                } else {
4407                    $this->regenerate_compression_context = false;
4408                    $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]);
4409                    $header = "\x78\x9C";
4410                }
4411                if ($this->compress_context) {
4412                    $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH);
4413                }
4414        }
4415
4416        // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
4417        $packet_length = strlen($data) + 9;
4418        if ($this->encrypt && $this->encrypt->usesNonce()) {
4419            $packet_length -= 4;
4420        }
4421        // round up to the nearest $this->encrypt_block_size
4422        $packet_length += (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
4423        // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
4424        $padding_length = $packet_length - strlen($data) - 5;
4425        switch (true) {
4426            case $this->encrypt && $this->encrypt->usesNonce():
4427            case $this->hmac_create instanceof Hash && $this->hmac_create_etm:
4428                $padding_length += 4;
4429                $packet_length += 4;
4430        }
4431
4432        $padding = Random::string($padding_length);
4433
4434        // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
4435        $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
4436
4437        $hmac = '';
4438        if ($this->hmac_create instanceof Hash && !$this->hmac_create_etm) {
4439            if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
4440                $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
4441                $hmac = $this->hmac_create->hash($packet);
4442            } else {
4443                $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
4444            }
4445        }
4446
4447        if ($this->encrypt) {
4448            switch ($this->encryptName) {
4449                case 'aes128-gcm@openssh.com':
4450                case 'aes256-gcm@openssh.com':
4451                    $this->encrypt->setNonce(
4452                        $this->encryptFixedPart .
4453                        $this->encryptInvocationCounter
4454                    );
4455                    Strings::increment_str($this->encryptInvocationCounter);
4456                    $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF"));
4457                    $packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
4458                    break;
4459                case 'chacha20-poly1305@openssh.com':
4460                    // This should be impossible, but we are checking anyway to narrow the type for Psalm.
4461                    if (!($this->encrypt instanceof ChaCha20)) {
4462                        throw new \LogicException('$this->encrypt is not a ' . ChaCha20::class);
4463                    }
4464
4465                    $nonce = pack('N2', 0, $this->send_seq_no);
4466
4467                    $this->encrypt->setNonce($nonce);
4468                    $this->lengthEncrypt->setNonce($nonce);
4469
4470                    $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF");
4471
4472                    $this->encrypt->setCounter(0);
4473                    // this is the same approach that's implemented in Salsa20::createPoly1305Key()
4474                    // but we don't want to use the same AEAD construction that RFC8439 describes
4475                    // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
4476                    $this->encrypt->setPoly1305Key(
4477                        $this->encrypt->encrypt(str_repeat("\0", 32))
4478                    );
4479                    $this->encrypt->setAAD($length);
4480                    $this->encrypt->setCounter(1);
4481                    $packet = $length . $this->encrypt->encrypt(substr($packet, 4));
4482                    break;
4483                default:
4484                    $packet = $this->hmac_create instanceof Hash && $this->hmac_create_etm ?
4485                        ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
4486                        $this->encrypt->encrypt($packet);
4487            }
4488        }
4489
4490        if ($this->hmac_create instanceof Hash && $this->hmac_create_etm) {
4491            if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
4492                $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
4493                $hmac = $this->hmac_create->hash($packet);
4494            } else {
4495                $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
4496            }
4497        }
4498
4499        $this->send_seq_no++;
4500
4501        $packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
4502
4503        if (!$this->keyExchangeInProgress) {
4504            $this->bytesTransferredSinceLastKEX += strlen($packet);
4505        }
4506
4507        $start = microtime(true);
4508        $sent = @fputs($this->fsock, $packet);
4509        $stop = microtime(true);
4510
4511        if (defined('NET_SSH2_LOGGING')) {
4512            $current = microtime(true);
4513            $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
4514            $message_number = '-> ' . $message_number .
4515                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
4516            $this->append_log($message_number, $logged);
4517        }
4518        $this->last_packet = microtime(true);
4519
4520        if (strlen($packet) != $sent) {
4521            $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4522            $message = $sent === false ?
4523                'Unable to write ' . strlen($packet) . ' bytes' :
4524                "Only $sent of " . strlen($packet) . " bytes were sent";
4525            throw new \RuntimeException($message);
4526        }
4527
4528        if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
4529            $this->key_exchange();
4530        }
4531    }
4532
4533    /**
4534     * Sends a keep-alive message, if keep-alive is enabled and interval is met
4535     */
4536    private function send_keep_alive()
4537    {
4538        if ($this->bitmap & self::MASK_CONNECTED) {
4539            $elapsed = microtime(true) - $this->last_packet;
4540            if ($this->keepAlive > 0 && $elapsed >= $this->keepAlive) {
4541                $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
4542            }
4543        }
4544    }
4545
4546    /**
4547     * Logs data packets
4548     *
4549     * Makes sure that only the last 1MB worth of packets will be logged
4550     *
4551     * @param string $message_number
4552     * @param string $message
4553     */
4554    private function append_log($message_number, $message)
4555    {
4556        $this->append_log_helper(
4557            NET_SSH2_LOGGING,
4558            $message_number,
4559            $message,
4560            $this->message_number_log,
4561            $this->message_log,
4562            $this->log_size,
4563            $this->realtime_log_file,
4564            $this->realtime_log_wrap,
4565            $this->realtime_log_size
4566        );
4567    }
4568
4569    /**
4570     * Logs data packet helper
4571     *
4572     * @param int $constant
4573     * @param string $message_number
4574     * @param string $message
4575     * @param array &$message_number_log
4576     * @param array &$message_log
4577     * @param int &$log_size
4578     * @param resource &$realtime_log_file
4579     * @param bool &$realtime_log_wrap
4580     * @param int &$realtime_log_size
4581     */
4582    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)
4583    {
4584        // remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
4585        if (!in_array(substr($message_number, 0, 4), ['<- (', '-> (']) && strlen($message_number) > 2) {
4586            Strings::shift($message);
4587        }
4588
4589        switch ($constant) {
4590            // useful for benchmarks
4591            case self::LOG_SIMPLE:
4592                $message_number_log[] = $message_number;
4593                break;
4594            case self::LOG_SIMPLE_REALTIME:
4595                echo $message_number;
4596                echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
4597                @flush();
4598                @ob_flush();
4599                break;
4600            // the most useful log for SSH2
4601            case self::LOG_COMPLEX:
4602                $message_number_log[] = $message_number;
4603                $log_size += strlen($message);
4604                $message_log[] = $message;
4605                while ($log_size > self::LOG_MAX_SIZE) {
4606                    $log_size -= strlen(array_shift($message_log));
4607                    array_shift($message_number_log);
4608                }
4609                break;
4610            // dump the output out realtime; packets may be interspersed with non packets,
4611            // passwords won't be filtered out and select other packets may not be correctly
4612            // identified
4613            case self::LOG_REALTIME:
4614                switch (PHP_SAPI) {
4615                    case 'cli':
4616                        $start = $stop = "\r\n";
4617                        break;
4618                    default:
4619                        $start = '<pre>';
4620                        $stop = '</pre>';
4621                }
4622                echo $start . $this->format_log([$message], [$message_number]) . $stop;
4623                @flush();
4624                @ob_flush();
4625                break;
4626            // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
4627            // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
4628            // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
4629            // at the beginning of the file
4630            case self::LOG_REALTIME_FILE:
4631                if (!isset($realtime_log_file)) {
4632                    // PHP doesn't seem to like using constants in fopen()
4633                    $filename = NET_SSH2_LOG_REALTIME_FILENAME;
4634                    $fp = fopen($filename, 'w');
4635                    $realtime_log_file = $fp;
4636                }
4637                if (!is_resource($realtime_log_file)) {
4638                    break;
4639                }
4640                $entry = $this->format_log([$message], [$message_number]);
4641                if ($realtime_log_wrap) {
4642                    $temp = "<<< START >>>\r\n";
4643                    $entry .= $temp;
4644                    fseek($realtime_log_file, ftell($realtime_log_file) - strlen($temp));
4645                }
4646                $realtime_log_size += strlen($entry);
4647                if ($realtime_log_size > self::LOG_MAX_SIZE) {
4648                    fseek($realtime_log_file, 0);
4649                    $realtime_log_size = strlen($entry);
4650                    $realtime_log_wrap = true;
4651                }
4652                fputs($realtime_log_file, $entry);
4653                break;
4654            case self::LOG_REALTIME_SIMPLE:
4655                echo $message_number;
4656                echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
4657        }
4658    }
4659
4660    /**
4661     * Sends channel data
4662     *
4663     * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
4664     *
4665     * @param int $client_channel
4666     * @param string $data
4667     * @return void
4668     */
4669    protected function send_channel_packet($client_channel, $data)
4670    {
4671        if (
4672            isset($this->channel_buffers_write[$client_channel])
4673            && strpos($data, $this->channel_buffers_write[$client_channel]) === 0
4674        ) {
4675            // if buffer holds identical initial data content, resume send from the unmatched data portion
4676            $data = substr($data, strlen($this->channel_buffers_write[$client_channel]));
4677        } else {
4678            $this->channel_buffers_write[$client_channel] = '';
4679        }
4680        while (strlen($data)) {
4681            if (!$this->window_size_client_to_server[$client_channel]) {
4682                // using an invalid channel will let the buffers be built up for the valid channels
4683                $this->get_channel_packet(-$client_channel);
4684                if ($this->isTimeout()) {
4685                    throw new TimeoutException('Timed out waiting for server');
4686                } elseif (!$this->window_size_client_to_server[$client_channel]) {
4687                    throw new \RuntimeException('Data window was not adjusted');
4688                }
4689            }
4690
4691            /* The maximum amount of data allowed is determined by the maximum
4692               packet size for the channel, and the current window size, whichever
4693               is smaller.
4694                 -- http://tools.ietf.org/html/rfc4254#section-5.2 */
4695            $max_size = min(
4696                $this->packet_size_client_to_server[$client_channel],
4697                $this->window_size_client_to_server[$client_channel]
4698            );
4699
4700            $temp = Strings::shift($data, $max_size);
4701            $packet = Strings::packSSH2(
4702                'CNs',
4703                NET_SSH2_MSG_CHANNEL_DATA,
4704                $this->server_channels[$client_channel],
4705                $temp
4706            );
4707            $this->window_size_client_to_server[$client_channel] -= strlen($temp);
4708            $this->send_binary_packet($packet);
4709            $this->channel_buffers_write[$client_channel] .= $temp;
4710        }
4711        unset($this->channel_buffers_write[$client_channel]);
4712    }
4713
4714    /**
4715     * Closes and flushes a channel
4716     *
4717     * \phpseclib3\Net\SSH2 doesn't properly close most channels.  For exec() channels are normally closed by the server
4718     * and for SFTP channels are presumably closed when the client disconnects.  This functions is intended
4719     * for SCP more than anything.
4720     *
4721     * @param int $client_channel
4722     * @param bool $want_reply
4723     * @return void
4724     */
4725    protected function close_channel($client_channel)
4726    {
4727        // see http://tools.ietf.org/html/rfc4254#section-5.3
4728
4729        if ($this->channel_status[$client_channel] != NET_SSH2_MSG_CHANNEL_EOF) {
4730            $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
4731        }
4732        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4733
4734        $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4735
4736        $this->channelCount--;
4737
4738        $this->curTimeout = 5;
4739        while (!is_bool($this->get_channel_packet($client_channel))) {
4740        }
4741
4742        unset($this->channel_status[$client_channel]);
4743
4744        $this->close_channel_bitmap($client_channel);
4745    }
4746
4747    /**
4748     * Maintains execution state bitmap in response to channel closure
4749     *
4750     * @param int $client_channel The channel number to maintain closure status of
4751     * @return void
4752     */
4753    private function close_channel_bitmap($client_channel)
4754    {
4755        switch ($client_channel) {
4756            case self::CHANNEL_SHELL:
4757                // Shell status has been maintained in the bitmap for backwards
4758                //  compatibility sake, but can be removed going forward
4759                if ($this->bitmap & self::MASK_SHELL) {
4760                    $this->bitmap &= ~self::MASK_SHELL;
4761                }
4762                break;
4763        }
4764    }
4765
4766    /**
4767     * Disconnect
4768     *
4769     * @param int $reason
4770     * @return false
4771     */
4772    protected function disconnect_helper($reason)
4773    {
4774        if ($this->bitmap & self::MASK_DISCONNECT) {
4775            // Disregard subsequent disconnect requests
4776            return false;
4777        }
4778        $this->bitmap |= self::MASK_DISCONNECT;
4779        if ($this->isConnected()) {
4780            $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
4781            try {
4782                $this->send_binary_packet($data);
4783            } catch (\Exception $e) {
4784            }
4785        }
4786
4787        $this->reset_connection();
4788
4789        return false;
4790    }
4791
4792    /**
4793     * Define Array
4794     *
4795     * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
4796     * named constants from it, using the value as the name of the constant and the index as the value of the constant.
4797     * If any of the constants that would be defined already exists, none of the constants will be defined.
4798     *
4799     * @param mixed[] ...$args
4800     * @access protected
4801     */
4802    protected static function define_array(...$args)
4803    {
4804        foreach ($args as $arg) {
4805            foreach ($arg as $key => $value) {
4806                if (!defined($value)) {
4807                    define($value, $key);
4808                } else {
4809                    break 2;
4810                }
4811            }
4812        }
4813    }
4814
4815    /**
4816     * Returns a log of the packets that have been sent and received.
4817     *
4818     * 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')
4819     *
4820     * @return array|false|string
4821     */
4822    public function getLog()
4823    {
4824        if (!defined('NET_SSH2_LOGGING')) {
4825            return false;
4826        }
4827
4828        switch (NET_SSH2_LOGGING) {
4829            case self::LOG_SIMPLE:
4830                return $this->message_number_log;
4831            case self::LOG_COMPLEX:
4832                $log = $this->format_log($this->message_log, $this->message_number_log);
4833                return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>';
4834            default:
4835                return false;
4836        }
4837    }
4838
4839    /**
4840     * Formats a log for printing
4841     *
4842     * @param array $message_log
4843     * @param array $message_number_log
4844     * @return string
4845     */
4846    protected function format_log(array $message_log, array $message_number_log)
4847    {
4848        $output = '';
4849        for ($i = 0; $i < count($message_log); $i++) {
4850            $output .= $message_number_log[$i];
4851            $current_log = $message_log[$i];
4852            $j = 0;
4853            if (strlen($current_log)) {
4854                $output .= "\r\n";
4855            }
4856            do {
4857                if (strlen($current_log)) {
4858                    $output .= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0  ';
4859                }
4860                $fragment = Strings::shift($current_log, $this->log_short_width);
4861                $hex = substr(preg_replace_callback('#.#s', function ($matches) {
4862                    return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
4863                }, $fragment), strlen($this->log_boundary));
4864                // replace non ASCII printable characters with dots
4865                // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
4866                // also replace < with a . since < messes up the output on web browsers
4867                $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
4868                $output .= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
4869                $j++;
4870            } while (strlen($current_log));
4871            $output .= "\r\n";
4872        }
4873
4874        return $output;
4875    }
4876
4877    /**
4878     * Helper function for agent->on_channel_open()
4879     *
4880     * Used when channels are created to inform agent
4881     * of said channel opening. Must be called after
4882     * channel open confirmation received
4883     *
4884     */
4885    private function on_channel_open()
4886    {
4887        if (isset($this->agent)) {
4888            $this->agent->registerChannelOpen($this);
4889        }
4890    }
4891
4892    /**
4893     * Returns the first value of the intersection of two arrays or false if
4894     * the intersection is empty. The order is defined by the first parameter.
4895     *
4896     * @param array $array1
4897     * @param array $array2
4898     * @return mixed False if intersection is empty, else intersected value.
4899     */
4900    private static function array_intersect_first(array $array1, array $array2)
4901    {
4902        foreach ($array1 as $value) {
4903            if (in_array($value, $array2)) {
4904                return $value;
4905            }
4906        }
4907        return false;
4908    }
4909
4910    /**
4911     * Returns all errors / debug messages on the SSH layer
4912     *
4913     * If you are looking for messages from the SFTP layer, please see SFTP::getSFTPErrors()
4914     *
4915     * @return string[]
4916     */
4917    public function getErrors()
4918    {
4919        return $this->errors;
4920    }
4921
4922    /**
4923     * Returns the last error received on the SSH layer
4924     *
4925     * If you are looking for messages from the SFTP layer, please see SFTP::getLastSFTPError()
4926     *
4927     * @return string
4928     */
4929    public function getLastError()
4930    {
4931        $count = count($this->errors);
4932
4933        if ($count > 0) {
4934            return $this->errors[$count - 1];
4935        }
4936    }
4937
4938    /**
4939     * Return the server identification.
4940     *
4941     * @return string|false
4942     */
4943    public function getServerIdentification()
4944    {
4945        $this->connect();
4946
4947        return $this->server_identifier;
4948    }
4949
4950    /**
4951     * Returns a list of algorithms the server supports
4952     *
4953     * @return array
4954     */
4955    public function getServerAlgorithms()
4956    {
4957        $this->connect();
4958
4959        return [
4960            'kex' => $this->kex_algorithms,
4961            'hostkey' => $this->server_host_key_algorithms,
4962            'client_to_server' => [
4963                'crypt' => $this->encryption_algorithms_client_to_server,
4964                'mac' => $this->mac_algorithms_client_to_server,
4965                'comp' => $this->compression_algorithms_client_to_server,
4966                'lang' => $this->languages_client_to_server
4967            ],
4968            'server_to_client' => [
4969                'crypt' => $this->encryption_algorithms_server_to_client,
4970                'mac' => $this->mac_algorithms_server_to_client,
4971                'comp' => $this->compression_algorithms_server_to_client,
4972                'lang' => $this->languages_server_to_client
4973            ]
4974        ];
4975    }
4976
4977    /**
4978     * Returns a list of KEX algorithms that phpseclib supports
4979     *
4980     * @return array
4981     */
4982    public static function getSupportedKEXAlgorithms()
4983    {
4984        $kex_algorithms = [
4985            // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
4986            // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
4987            // libssh repository for more information.
4988            'curve25519-sha256',
4989            'curve25519-sha256@libssh.org',
4990
4991            'ecdh-sha2-nistp256', // RFC 5656
4992            'ecdh-sha2-nistp384', // RFC 5656
4993            'ecdh-sha2-nistp521', // RFC 5656
4994
4995            'diffie-hellman-group-exchange-sha256',// RFC 4419
4996            'diffie-hellman-group-exchange-sha1',  // RFC 4419
4997
4998            // Diffie-Hellman Key Agreement (DH) using integer modulo prime
4999            // groups.
5000            'diffie-hellman-group14-sha256',
5001            'diffie-hellman-group14-sha1', // REQUIRED
5002            'diffie-hellman-group15-sha512',
5003            'diffie-hellman-group16-sha512',
5004            'diffie-hellman-group17-sha512',
5005            'diffie-hellman-group18-sha512',
5006
5007            'diffie-hellman-group1-sha1', // REQUIRED
5008        ];
5009
5010        return $kex_algorithms;
5011    }
5012
5013    /**
5014     * Returns a list of host key algorithms that phpseclib supports
5015     *
5016     * @return array
5017     */
5018    public static function getSupportedHostKeyAlgorithms()
5019    {
5020        return [
5021            'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
5022            'ecdsa-sha2-nistp256', // RFC 5656
5023            'ecdsa-sha2-nistp384', // RFC 5656
5024            'ecdsa-sha2-nistp521', // RFC 5656
5025            'rsa-sha2-256', // RFC 8332
5026            'rsa-sha2-512', // RFC 8332
5027            'ssh-rsa', // RECOMMENDED  sign   Raw RSA Key
5028            'ssh-dss'  // REQUIRED     sign   Raw DSS Key
5029        ];
5030    }
5031
5032    /**
5033     * Returns a list of symmetric key algorithms that phpseclib supports
5034     *
5035     * @return array
5036     */
5037    public static function getSupportedEncryptionAlgorithms()
5038    {
5039        $algos = [
5040            // from <https://tools.ietf.org/html/rfc5647>:
5041            'aes128-gcm@openssh.com',
5042            'aes256-gcm@openssh.com',
5043
5044            // from <http://tools.ietf.org/html/rfc4345#section-4>:
5045            'arcfour256',
5046            'arcfour128',
5047
5048            //'arcfour',      // OPTIONAL          the ARCFOUR stream cipher with a 128-bit key
5049
5050            // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
5051            'aes128-ctr',     // RECOMMENDED       AES (Rijndael) in SDCTR mode, with 128-bit key
5052            'aes192-ctr',     // RECOMMENDED       AES with 192-bit key
5053            'aes256-ctr',     // RECOMMENDED       AES with 256-bit key
5054
5055            // from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>:
5056            // one of the big benefits of chacha20-poly1305 is speed. the problem is...
5057            // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
5058            // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
5059            // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
5060            // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
5061            // (which is always gonna be super fast to compute thanks to the hash extension, which
5062            // "is bundled and compiled into PHP by default")
5063            'chacha20-poly1305@openssh.com',
5064
5065            'twofish128-ctr', // OPTIONAL          Twofish in SDCTR mode, with 128-bit key
5066            'twofish192-ctr', // OPTIONAL          Twofish with 192-bit key
5067            'twofish256-ctr', // OPTIONAL          Twofish with 256-bit key
5068
5069            'aes128-cbc',     // RECOMMENDED       AES with a 128-bit key
5070            'aes192-cbc',     // OPTIONAL          AES with a 192-bit key
5071            'aes256-cbc',     // OPTIONAL          AES in CBC mode, with a 256-bit key
5072
5073            'twofish128-cbc', // OPTIONAL          Twofish with a 128-bit key
5074            'twofish192-cbc', // OPTIONAL          Twofish with a 192-bit key
5075            'twofish256-cbc',
5076            'twofish-cbc',    // OPTIONAL          alias for "twofish256-cbc"
5077                              //                   (this is being retained for historical reasons)
5078
5079            'blowfish-ctr',   // OPTIONAL          Blowfish in SDCTR mode
5080
5081            'blowfish-cbc',   // OPTIONAL          Blowfish in CBC mode
5082
5083            '3des-ctr',       // RECOMMENDED       Three-key 3DES in SDCTR mode
5084
5085            '3des-cbc',       // REQUIRED          three-key 3DES in CBC mode
5086
5087             //'none'           // OPTIONAL          no encryption; NOT RECOMMENDED
5088        ];
5089
5090        if (self::$crypto_engine) {
5091            $engines = [self::$crypto_engine];
5092        } else {
5093            $engines = [
5094                'libsodium',
5095                'OpenSSL (GCM)',
5096                'OpenSSL',
5097                'mcrypt',
5098                'Eval',
5099                'PHP'
5100            ];
5101        }
5102
5103        $ciphers = [];
5104
5105        foreach ($engines as $engine) {
5106            foreach ($algos as $algo) {
5107                $obj = self::encryption_algorithm_to_crypt_instance($algo);
5108                if ($obj instanceof Rijndael) {
5109                    $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo));
5110                }
5111                switch ($algo) {
5112                    // Eval engines do not exist for ChaCha20 or RC4 because they would not benefit from one.
5113                    // to benefit from an Eval engine they'd need to loop a variable amount of times, they'd
5114                    // need to do table lookups (eg. sbox subsitutions). ChaCha20 doesn't do either because
5115                    // it's a so-called ARX cipher, meaning that the only operations it does are add (A), rotate (R)
5116                    // and XOR (X). RC4 does do table lookups but being a stream cipher it works differently than
5117                    // block ciphers. with RC4 you XOR the plaintext against a keystream and the keystream changes
5118                    // as you encrypt stuff. the only table lookups are made against this keystream and thus table
5119                    // lookups are kinda unavoidable. with AES and DES, however, the table lookups that are done
5120                    // are done against substitution boxes (sboxes), which are invariant.
5121
5122                    // OpenSSL can't be used as an engine, either, because OpenSSL doesn't support continuous buffers
5123                    // as SSH2 uses and altho you can emulate a continuous buffer with block ciphers you can't do so
5124                    // with stream ciphers. As for ChaCha20...  for the ChaCha20 part OpenSSL could prob be used but
5125                    // the big slow down isn't with ChaCha20 - it's with Poly1305. SSH constructs the key for that
5126                    // differently than how OpenSSL does it (OpenSSL does it as the RFC describes, SSH doesn't).
5127
5128                    // libsodium can't be used because it doesn't support RC4 and it doesn't construct the Poly1305
5129                    // keys in the same way that SSH does
5130
5131                    // mcrypt could prob be used for RC4 but mcrypt hasn't been included in PHP core for yearss
5132                    case 'chacha20-poly1305@openssh.com':
5133                    case 'arcfour128':
5134                    case 'arcfour256':
5135                        if ($engine != 'PHP') {
5136                            continue 2;
5137                        }
5138                        break;
5139                    case 'aes128-gcm@openssh.com':
5140                    case 'aes256-gcm@openssh.com':
5141                        if ($engine == 'OpenSSL') {
5142                            continue 2;
5143                        }
5144                        $obj->setNonce('dummydummydu');
5145                }
5146                if ($obj->isValidEngine($engine)) {
5147                    $algos = array_diff($algos, [$algo]);
5148                    $ciphers[] = $algo;
5149                }
5150            }
5151        }
5152
5153        return $ciphers;
5154    }
5155
5156    /**
5157     * Returns a list of MAC algorithms that phpseclib supports
5158     *
5159     * @return array
5160     */
5161    public static function getSupportedMACAlgorithms()
5162    {
5163        return [
5164            'hmac-sha2-256-etm@openssh.com',
5165            'hmac-sha2-512-etm@openssh.com',
5166            'hmac-sha1-etm@openssh.com',
5167
5168            // from <http://www.ietf.org/rfc/rfc6668.txt>:
5169            'hmac-sha2-256',// RECOMMENDED     HMAC-SHA256 (digest length = key length = 32)
5170            'hmac-sha2-512',// OPTIONAL        HMAC-SHA512 (digest length = key length = 64)
5171
5172            'hmac-sha1-96', // RECOMMENDED     first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
5173            'hmac-sha1',    // REQUIRED        HMAC-SHA1 (digest length = key length = 20)
5174            'hmac-md5-96',  // OPTIONAL        first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
5175            'hmac-md5',     // OPTIONAL        HMAC-MD5 (digest length = key length = 16)
5176
5177            'umac-64-etm@openssh.com',
5178            'umac-128-etm@openssh.com',
5179
5180            // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
5181            'umac-64@openssh.com',
5182            'umac-128@openssh.com',
5183
5184            //'none'          // OPTIONAL        no MAC; NOT RECOMMENDED
5185        ];
5186    }
5187
5188    /**
5189     * Returns a list of compression algorithms that phpseclib supports
5190     *
5191     * @return array
5192     */
5193    public static function getSupportedCompressionAlgorithms()
5194    {
5195        $algos = ['none']; // REQUIRED        no compression
5196        if (function_exists('deflate_init')) {
5197            $algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed
5198            $algos[] = 'zlib';
5199        }
5200        return $algos;
5201    }
5202
5203    /**
5204     * Return list of negotiated algorithms
5205     *
5206     * Uses the same format as https://www.php.net/ssh2-methods-negotiated
5207     *
5208     * @return array
5209     */
5210    public function getAlgorithmsNegotiated()
5211    {
5212        $this->connect();
5213
5214        $compression_map = [
5215            self::NET_SSH2_COMPRESSION_NONE => 'none',
5216            self::NET_SSH2_COMPRESSION_ZLIB => 'zlib',
5217            self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com'
5218        ];
5219
5220        return [
5221            'kex' => $this->kex_algorithm,
5222            'hostkey' => $this->signature_format,
5223            'client_to_server' => [
5224                'crypt' => $this->encryptName,
5225                'mac' => $this->hmac_create_name,
5226                'comp' => $compression_map[$this->compress],
5227            ],
5228            'server_to_client' => [
5229                'crypt' => $this->decryptName,
5230                'mac' => $this->hmac_check_name,
5231                'comp' => $compression_map[$this->decompress],
5232            ]
5233        ];
5234    }
5235
5236    /**
5237     * Force multiple channels (even if phpseclib has decided to disable them)
5238     */
5239    public function forceMultipleChannels()
5240    {
5241        $this->errorOnMultipleChannels = false;
5242    }
5243
5244    /**
5245     * Allows you to set the terminal
5246     *
5247     * @param string $term
5248     */
5249    public function setTerminal($term)
5250    {
5251        $this->term = $term;
5252    }
5253
5254    /**
5255     * Accepts an associative array with up to four parameters as described at
5256     * <https://www.php.net/manual/en/function.ssh2-connect.php>
5257     *
5258     * @param array $methods
5259     */
5260    public function setPreferredAlgorithms(array $methods)
5261    {
5262        $keys = ['client_to_server', 'server_to_client'];
5263
5264        if (isset($methods['kex']) && is_string($methods['kex'])) {
5265            $methods['kex'] = explode(',', $methods['kex']);
5266        }
5267
5268        if (isset($methods['hostkey']) && is_string($methods['hostkey'])) {
5269            $methods['hostkey'] = explode(',', $methods['hostkey']);
5270        }
5271
5272        foreach ($keys as $key) {
5273            if (isset($methods[$key])) {
5274                $a = &$methods[$key];
5275                if (isset($a['crypt']) && is_string($a['crypt'])) {
5276                    $a['crypt'] = explode(',', $a['crypt']);
5277                }
5278                if (isset($a['comp']) && is_string($a['comp'])) {
5279                    $a['comp'] = explode(',', $a['comp']);
5280                }
5281                if (isset($a['mac']) && is_string($a['mac'])) {
5282                    $a['mac'] = explode(',', $a['mac']);
5283                }
5284            }
5285        }
5286
5287        $preferred = $methods;
5288
5289        if (isset($preferred['kex'])) {
5290            $preferred['kex'] = array_intersect(
5291                $preferred['kex'],
5292                static::getSupportedKEXAlgorithms()
5293            );
5294        }
5295
5296        if (isset($preferred['hostkey'])) {
5297            $preferred['hostkey'] = array_intersect(
5298                $preferred['hostkey'],
5299                static::getSupportedHostKeyAlgorithms()
5300            );
5301        }
5302
5303        foreach ($keys as $key) {
5304            if (isset($preferred[$key])) {
5305                $a = &$preferred[$key];
5306                if (isset($a['crypt'])) {
5307                    $a['crypt'] = array_intersect(
5308                        $a['crypt'],
5309                        static::getSupportedEncryptionAlgorithms()
5310                    );
5311                }
5312                if (isset($a['comp'])) {
5313                    $a['comp'] = array_intersect(
5314                        $a['comp'],
5315                        static::getSupportedCompressionAlgorithms()
5316                    );
5317                }
5318                if (isset($a['mac'])) {
5319                    $a['mac'] = array_intersect(
5320                        $a['mac'],
5321                        static::getSupportedMACAlgorithms()
5322                    );
5323                }
5324            }
5325        }
5326
5327        $keys = [
5328            'kex',
5329            'hostkey',
5330            'client_to_server/crypt',
5331            'client_to_server/comp',
5332            'client_to_server/mac',
5333            'server_to_client/crypt',
5334            'server_to_client/comp',
5335            'server_to_client/mac',
5336        ];
5337        foreach ($keys as $key) {
5338            $p = $preferred;
5339            $m = $methods;
5340
5341            $subkeys = explode('/', $key);
5342            foreach ($subkeys as $subkey) {
5343                if (!isset($p[$subkey])) {
5344                    continue 2;
5345                }
5346                $p = $p[$subkey];
5347                $m = $m[$subkey];
5348            }
5349
5350            if (count($p) != count($m)) {
5351                $diff = array_diff($m, $p);
5352                $msg = count($diff) == 1 ?
5353                    ' is not a supported algorithm' :
5354                    ' are not supported algorithms';
5355                throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg);
5356            }
5357        }
5358
5359        $this->preferred = $preferred;
5360    }
5361
5362    /**
5363     * Returns the banner message.
5364     *
5365     * Quoting from the RFC, "in some jurisdictions, sending a warning message before
5366     * authentication may be relevant for getting legal protection."
5367     *
5368     * @return string
5369     */
5370    public function getBannerMessage()
5371    {
5372        return $this->banner_message;
5373    }
5374
5375    /**
5376     * Returns the server public host key.
5377     *
5378     * Caching this the first time you connect to a server and checking the result on subsequent connections
5379     * is recommended.  Returns false if the server signature is not signed correctly with the public host key.
5380     *
5381     * @return string|false
5382     * @throws \RuntimeException on badly formatted keys
5383     * @throws NoSupportedAlgorithmsException when the key isn't in a supported format
5384     */
5385    public function getServerPublicHostKey()
5386    {
5387        if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
5388            $this->connect();
5389        }
5390
5391        $signature = $this->signature;
5392        $server_public_host_key = base64_encode($this->server_public_host_key);
5393
5394        if ($this->signature_validated) {
5395            return $this->bitmap ?
5396                $this->signature_format . ' ' . $server_public_host_key :
5397                false;
5398        }
5399
5400        $this->signature_validated = true;
5401
5402        switch ($this->signature_format) {
5403            case 'ssh-ed25519':
5404            case 'ecdsa-sha2-nistp256':
5405            case 'ecdsa-sha2-nistp384':
5406            case 'ecdsa-sha2-nistp521':
5407                $key = EC::loadFormat('OpenSSH', $server_public_host_key)
5408                    ->withSignatureFormat('SSH2');
5409                switch ($this->signature_format) {
5410                    case 'ssh-ed25519':
5411                        $hash = 'sha512';
5412                        break;
5413                    case 'ecdsa-sha2-nistp256':
5414                        $hash = 'sha256';
5415                        break;
5416                    case 'ecdsa-sha2-nistp384':
5417                        $hash = 'sha384';
5418                        break;
5419                    case 'ecdsa-sha2-nistp521':
5420                        $hash = 'sha512';
5421                }
5422                $key = $key->withHash($hash);
5423                break;
5424            case 'ssh-dss':
5425                $key = DSA::loadFormat('OpenSSH', $server_public_host_key)
5426                    ->withSignatureFormat('SSH2')
5427                    ->withHash('sha1');
5428                break;
5429            case 'ssh-rsa':
5430            case 'rsa-sha2-256':
5431            case 'rsa-sha2-512':
5432                // could be ssh-rsa, rsa-sha2-256, rsa-sha2-512
5433                // we don't check here because we already checked in key_exchange
5434                // some signatures have the type embedded within the message and some don't
5435                list(, $signature) = Strings::unpackSSH2('ss', $signature);
5436
5437                $key = RSA::loadFormat('OpenSSH', $server_public_host_key)
5438                    ->withPadding(RSA::SIGNATURE_PKCS1);
5439                switch ($this->signature_format) {
5440                    case 'rsa-sha2-512':
5441                        $hash = 'sha512';
5442                        break;
5443                    case 'rsa-sha2-256':
5444                        $hash = 'sha256';
5445                        break;
5446                    //case 'ssh-rsa':
5447                    default:
5448                        $hash = 'sha1';
5449                }
5450                $key = $key->withHash($hash);
5451                break;
5452            default:
5453                $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
5454                throw new NoSupportedAlgorithmsException('Unsupported signature format');
5455        }
5456
5457        if (!$key->verify($this->exchange_hash, $signature)) {
5458            return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
5459        };
5460
5461        return $this->signature_format . ' ' . $server_public_host_key;
5462    }
5463
5464    /**
5465     * Returns the exit status of an SSH command or false.
5466     *
5467     * @return false|int
5468     */
5469    public function getExitStatus()
5470    {
5471        if (is_null($this->exit_status)) {
5472            return false;
5473        }
5474        return $this->exit_status;
5475    }
5476
5477    /**
5478     * Returns the number of columns for the terminal window size.
5479     *
5480     * @return int
5481     */
5482    public function getWindowColumns()
5483    {
5484        return $this->windowColumns;
5485    }
5486
5487    /**
5488     * Returns the number of rows for the terminal window size.
5489     *
5490     * @return int
5491     */
5492    public function getWindowRows()
5493    {
5494        return $this->windowRows;
5495    }
5496
5497    /**
5498     * Sets the number of columns for the terminal window size.
5499     *
5500     * @param int $value
5501     */
5502    public function setWindowColumns($value)
5503    {
5504        $this->windowColumns = $value;
5505    }
5506
5507    /**
5508     * Sets the number of rows for the terminal window size.
5509     *
5510     * @param int $value
5511     */
5512    public function setWindowRows($value)
5513    {
5514        $this->windowRows = $value;
5515    }
5516
5517    /**
5518     * Sets the number of columns and rows for the terminal window size.
5519     *
5520     * @param int $columns
5521     * @param int $rows
5522     */
5523    public function setWindowSize($columns = 80, $rows = 24)
5524    {
5525        $this->windowColumns = $columns;
5526        $this->windowRows = $rows;
5527    }
5528
5529    /**
5530     * To String Magic Method
5531     *
5532     * @return string
5533     */
5534    #[\ReturnTypeWillChange]
5535    public function __toString()
5536    {
5537        return $this->getResourceId();
5538    }
5539
5540    /**
5541     * Get Resource ID
5542     *
5543     * We use {} because that symbols should not be in URL according to
5544     * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}.
5545     * It will safe us from any conflicts, because otherwise regexp will
5546     * match all alphanumeric domains.
5547     *
5548     * @return string
5549     */
5550    public function getResourceId()
5551    {
5552        return '{' . spl_object_hash($this) . '}';
5553    }
5554
5555    /**
5556     * Return existing connection
5557     *
5558     * @param string $id
5559     *
5560     * @return bool|SSH2 will return false if no such connection
5561     */
5562    public static function getConnectionByResourceId($id)
5563    {
5564        if (isset(self::$connections[$id])) {
5565            return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id];
5566        }
5567        return false;
5568    }
5569
5570    /**
5571     * Return all excising connections
5572     *
5573     * @return array<string, SSH2>
5574     */
5575    public static function getConnections()
5576    {
5577        if (!class_exists('WeakReference')) {
5578            /** @var array<string, SSH2> */
5579            return self::$connections;
5580        }
5581        $temp = [];
5582        foreach (self::$connections as $key => $ref) {
5583            $temp[$key] = $ref->get();
5584        }
5585        return $temp;
5586    }
5587
5588    /*
5589     * Update packet types in log history
5590     *
5591     * @param string $old
5592     * @param string $new
5593     */
5594    private function updateLogHistory($old, $new)
5595    {
5596        if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
5597            $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
5598                $old,
5599                $new,
5600                $this->message_number_log[count($this->message_number_log) - 1]
5601            );
5602        }
5603    }
5604
5605    /**
5606     * Return the list of authentication methods that may productively continue authentication.
5607     *
5608     * @see https://tools.ietf.org/html/rfc4252#section-5.1
5609     * @return array|null
5610     */
5611    public function getAuthMethodsToContinue()
5612    {
5613        return $this->auth_methods_to_continue;
5614    }
5615
5616    /**
5617     * Enables "smart" multi-factor authentication (MFA)
5618     */
5619    public function enableSmartMFA()
5620    {
5621        $this->smartMFA = true;
5622    }
5623
5624    /**
5625     * Disables "smart" multi-factor authentication (MFA)
5626     */
5627    public function disableSmartMFA()
5628    {
5629        $this->smartMFA = false;
5630    }
5631
5632    /**
5633     * How many bytes until the next key re-exchange?
5634     *
5635     * @param int $bytes
5636     */
5637    public function bytesUntilKeyReexchange($bytes)
5638    {
5639        $this->doKeyReexchangeAfterXBytes = $bytes;
5640    }
5641}
5642