Subversion Repositories oidplus

Rev

Rev 1441 | Rev 1463 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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