Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Details | Last modification | View Log | RSS feed

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