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