Subversion Repositories oidplus

Rev

Rev 1424 | Rev 1441 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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