Subversion Repositories oidplus

Rev

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