[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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