Subversion Repositories oidplus

Rev

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