Subversion Repositories oidplus

Rev

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