Subversion Repositories oidplus

Rev

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