Subversion Repositories oidplus

Rev

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