[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/phpseclib/phpseclib/phpseclib/Net/ -> SSH2.php (source)

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer