Subversion Repositories oidplus

Rev

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