2007-07-23 05:21:39 +00:00
< ? php
/**
2009-06-09 04:00:38 +00:00
* Pure - PHP implementation of SSHv2 .
2007-07-23 05:21:39 +00:00
*
2015-04-02 10:57:52 +00:00
* PHP version 5
2007-07-23 05:21:39 +00:00
*
2009-12-03 19:04:10 +00:00
* Here are some examples of how to use this library :
2007-07-25 21:49:33 +00:00
* < code >
* < ? php
2014-12-10 01:31:41 +00:00
* include 'vendor/autoload.php' ;
2007-07-25 21:49:33 +00:00
*
2019-11-07 05:41:40 +00:00
* $ssh = new \phpseclib3\Net\SSH2 ( 'www.domain.tld' );
2007-07-25 21:49:33 +00:00
* if ( ! $ssh -> login ( 'username' , 'password' )) {
* exit ( 'Login Failed' );
* }
*
* echo $ssh -> exec ( 'pwd' );
* echo $ssh -> exec ( 'ls -la' );
* ?>
* </ code >
*
2009-12-03 08:19:00 +00:00
* < code >
* < ? php
2014-12-10 01:31:41 +00:00
* include 'vendor/autoload.php' ;
2009-12-03 08:19:00 +00:00
*
2019-11-07 05:41:40 +00:00
* $key = \phpseclib3\Crypt\PublicKeyLoader :: load ( '...' , '(optional) password' );
2009-12-03 08:19:00 +00:00
*
2019-11-07 05:41:40 +00:00
* $ssh = new \phpseclib3\Net\SSH2 ( 'www.domain.tld' );
2009-12-03 08:19:00 +00:00
* if ( ! $ssh -> login ( 'username' , $key )) {
* exit ( 'Login Failed' );
* }
*
2011-05-08 23:53:30 +00:00
* echo $ssh -> read ( 'username@username:~$' );
* $ssh -> write ( " ls -la \n " );
* echo $ssh -> read ( 'username@username:~$' );
2009-12-03 08:19:00 +00:00
* ?>
* </ code >
*
2013-12-10 19:10:37 +00:00
* @ author Jim Wigginton < terrafrost @ php . net >
2014-12-09 23:02:44 +00:00
* @ copyright 2007 Jim Wigginton
2013-12-10 19:10:37 +00:00
* @ license http :// www . opensource . org / licenses / mit - license . html MIT License
* @ link http :// phpseclib . sourceforge . net
2007-07-23 05:21:39 +00:00
*/
2019-11-07 05:41:40 +00:00
namespace phpseclib3\Net ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Common\Functions\Strings ;
2019-11-07 05:41:40 +00:00
use phpseclib3\Crypt\Blowfish ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Crypt\ChaCha20 ;
use phpseclib3\Crypt\Common\AsymmetricKey ;
use phpseclib3\Crypt\Common\PrivateKey ;
use phpseclib3\Crypt\Common\PublicKey ;
2022-01-22 18:03:07 +00:00
use phpseclib3\Crypt\Common\SymmetricKey ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Crypt\DH ;
use phpseclib3\Crypt\DSA ;
use phpseclib3\Crypt\EC ;
2019-11-07 05:41:40 +00:00
use phpseclib3\Crypt\Hash ;
use phpseclib3\Crypt\Random ;
use phpseclib3\Crypt\RC4 ;
use phpseclib3\Crypt\Rijndael ;
use phpseclib3\Crypt\RSA ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Crypt\TripleDES ; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
2019-11-07 05:41:40 +00:00
use phpseclib3\Crypt\Twofish ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Exception\ConnectionClosedException ;
use phpseclib3\Exception\InsufficientSetupException ;
2019-11-07 05:41:40 +00:00
use phpseclib3\Exception\NoSupportedAlgorithmsException ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Exception\UnableToConnectException ;
2019-11-07 05:41:40 +00:00
use phpseclib3\Exception\UnsupportedAlgorithmException ;
use phpseclib3\Exception\UnsupportedCurveException ;
2022-01-30 15:34:42 +00:00
use phpseclib3\Math\BigInteger ;
use phpseclib3\System\SSH\Agent ;
2014-06-02 18:09:47 +00:00
2007-07-23 05:21:39 +00:00
/**
* Pure - PHP implementation of SSHv2 .
*
* @ author Jim Wigginton < terrafrost @ php . net >
*/
2014-12-10 01:31:41 +00:00
class SSH2
2013-12-03 18:34:41 +00:00
{
2021-12-14 15:34:41 +00:00
/** #@+
* Compression Types
*
*/
/**
* No compression
*/
const NET_SSH2_COMPRESSION_NONE = 1 ;
/**
* zlib compression
*/
const NET_SSH2_COMPRESSION_ZLIB = 2 ;
/**
* zlib @ openssh . com
*/
const NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH = 3 ;
/**#@-*/
2020-12-30 15:08:05 +00:00
// Execution Bitmap Masks
2014-12-04 21:45:13 +00:00
const MASK_CONSTRUCTOR = 0x00000001 ;
const MASK_CONNECTED = 0x00000002 ;
const MASK_LOGIN_REQ = 0x00000004 ;
const MASK_LOGIN = 0x00000008 ;
const MASK_SHELL = 0x00000010 ;
const MASK_WINDOW_ADJUST = 0x00000020 ;
2020-12-30 15:08:05 +00:00
/*
2014-12-04 21:45:13 +00:00
* Channel constants
*
* RFC4254 refers not to client and server channels but rather to sender and recipient channels . we don ' t refer
* to them in that way because RFC4254 toggles the meaning . the client sends a SSH_MSG_CHANNEL_OPEN message with
* a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response , with a sender and a
2018-03-03 12:07:14 +00:00
* recipient channel . at first glance , you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION ' s sender channel
* would be the same thing as SSH_MSG_CHANNEL_OPEN 's sender channel, but it' s not , per this snippet :
2014-12-04 21:45:13 +00:00
* The 'recipient channel' is the channel number given in the original
* open request , and 'sender channel' is the channel number allocated by
* the other side .
*
2019-11-07 05:41:40 +00:00
* @ see \phpseclib3\Net\SSH2 :: send_channel_packet ()
* @ see \phpseclib3\Net\SSH2 :: get_channel_packet ()
2020-12-30 15:08:05 +00:00
*/
2017-09-06 04:22:09 +00:00
const CHANNEL_EXEC = 1 ; // PuTTy uses 0x100
const CHANNEL_SHELL = 2 ;
const CHANNEL_SUBSYSTEM = 3 ;
const CHANNEL_AGENT_FORWARD = 4 ;
2019-01-20 15:15:53 +00:00
const CHANNEL_KEEP_ALIVE = 5 ;
2014-12-04 21:45:13 +00:00
/**
* Returns the message numbers
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: getLog ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const LOG_SIMPLE = 1 ;
/**
* Returns the message content
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: getLog ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const LOG_COMPLEX = 2 ;
/**
* Outputs the content real - time
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: getLog ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const LOG_REALTIME = 3 ;
/**
* Dumps the content real - time to a file
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: getLog ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const LOG_REALTIME_FILE = 4 ;
2017-06-20 03:35:22 +00:00
/**
* Make sure that the log never gets larger than this
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: getLog ()
2017-06-20 03:35:22 +00:00
*/
const LOG_MAX_SIZE = 1048576 ; // 1024 * 1024
2014-12-04 21:45:13 +00:00
/**
* Returns when a string matching $expect exactly is found
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: read ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const READ_SIMPLE = 1 ;
/**
* Returns when a string matching the regular expression $expect is found
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: read ()
2016-04-10 16:30:59 +00:00
*/
2014-12-04 21:45:13 +00:00
const READ_REGEX = 2 ;
2017-06-20 03:34:20 +00:00
/**
2019-04-12 02:56:17 +00:00
* Returns whenever a data packet is received .
*
* Some data packets may only contain a single character so it may be necessary
* to call read () multiple times when using this option
2020-12-30 15:08:05 +00:00
*
* @ see \phpseclib3\Net\SSH2 :: read ()
2017-06-20 03:34:20 +00:00
*/
const READ_NEXT = 3 ;
2014-12-04 21:45:13 +00:00
2007-07-23 05:21:39 +00:00
/**
* The SSH identifier
*
2016-04-10 16:30:59 +00:00
* @ var string
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $identifier ;
2007-07-23 05:21:39 +00:00
/**
* The Socket Object
*
2022-01-28 19:14:38 +00:00
* @ var resource | closed - resource | null
2007-07-23 05:21:39 +00:00
*/
2021-02-23 11:14:25 +00:00
public $fsock ;
2007-07-23 05:21:39 +00:00
/**
* Execution Bitmap
*
2012-08-14 17:07:18 +00:00
* The bits that are set represent functions that have been called already . This is used to determine
2007-07-23 05:21:39 +00:00
* if a requisite function has been successfully executed . If not , an error should be thrown .
*
2016-04-10 16:30:59 +00:00
* @ var int
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
protected $bitmap = 0 ;
2007-07-23 05:21:39 +00:00
/**
2010-02-12 23:02:13 +00:00
* Error information
2007-07-23 05:21:39 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: getErrors ()
* @ see self :: getLastError ()
2017-01-08 01:51:56 +00:00
* @ var array
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $errors = [];
2007-07-23 05:21:39 +00:00
/**
* Server Identifier
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerIdentification ()
2022-01-22 18:03:07 +00:00
* @ var string | false
2007-07-23 05:21:39 +00:00
*/
2021-09-16 23:30:47 +00:00
protected $server_identifier = false ;
2007-07-23 05:21:39 +00:00
/**
* Key Exchange Algorithms
*
2016-04-10 16:30:59 +00:00
* @ see self :: getKexAlgorithims ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $kex_algorithms = false ;
2007-07-23 05:21:39 +00:00
2019-03-28 18:57:26 +00:00
/**
* Key Exchange Algorithm
*
* @ see self :: getMethodsNegotiated ()
* @ var string | false
*/
private $kex_algorithm = false ;
2015-06-24 22:50:00 +00:00
/**
* Minimum Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 16:30:59 +00:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-24 22:50:00 +00:00
*/
2017-01-08 01:51:56 +00:00
private $kex_dh_group_size_min = 1536 ;
2015-06-24 22:50:00 +00:00
/**
* Preferred Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 16:30:59 +00:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-24 22:50:00 +00:00
*/
2017-01-08 01:51:56 +00:00
private $kex_dh_group_size_preferred = 2048 ;
2015-06-24 22:50:00 +00:00
/**
* Maximum Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 16:30:59 +00:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-24 22:50:00 +00:00
*/
2017-01-08 01:51:56 +00:00
private $kex_dh_group_size_max = 4096 ;
2015-06-24 22:50:00 +00:00
2007-07-23 05:21:39 +00:00
/**
* Server Host Key Algorithms
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerHostKeyAlgorithms ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $server_host_key_algorithms = false ;
2007-07-23 05:21:39 +00:00
/**
* Encryption Algorithms : Client to Server
*
2016-04-10 16:30:59 +00:00
* @ see self :: getEncryptionAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $encryption_algorithms_client_to_server = false ;
2007-07-23 05:21:39 +00:00
/**
* Encryption Algorithms : Server to Client
*
2016-04-10 16:30:59 +00:00
* @ see self :: getEncryptionAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $encryption_algorithms_server_to_client = false ;
2007-07-23 05:21:39 +00:00
/**
* MAC Algorithms : Client to Server
*
2016-04-10 16:30:59 +00:00
* @ see self :: getMACAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $mac_algorithms_client_to_server = false ;
2007-07-23 05:21:39 +00:00
/**
* MAC Algorithms : Server to Client
*
2016-04-10 16:30:59 +00:00
* @ see self :: getMACAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $mac_algorithms_server_to_client = false ;
2007-07-23 05:21:39 +00:00
/**
* Compression Algorithms : Client to Server
*
2016-04-10 16:30:59 +00:00
* @ see self :: getCompressionAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $compression_algorithms_client_to_server = false ;
2007-07-23 05:21:39 +00:00
/**
* Compression Algorithms : Server to Client
*
2016-04-10 16:30:59 +00:00
* @ see self :: getCompressionAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $compression_algorithms_server_to_client = false ;
2007-07-23 05:21:39 +00:00
/**
* Languages : Server to Client
*
2016-04-10 16:30:59 +00:00
* @ see self :: getLanguagesServer2Client ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $languages_server_to_client = false ;
2007-07-23 05:21:39 +00:00
/**
* Languages : Client to Server
*
2016-04-10 16:30:59 +00:00
* @ see self :: getLanguagesClient2Server ()
* @ var array | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $languages_client_to_server = false ;
2007-07-23 05:21:39 +00:00
2019-03-29 23:44:31 +00:00
/**
* Preferred Algorithms
*
* @ see self :: setPreferredAlgorithms ()
* @ var array
*/
private $preferred = [];
2007-07-23 05:21:39 +00:00
/**
2009-02-16 22:22:13 +00:00
* Block Size for Server to Client Encryption
*
* " Note that the length of the concatenation of 'packet_length',
* 'padding_length' , 'payload' , and 'random padding' MUST be a multiple
* of the cipher block size or 8 , whichever is larger . This constraint
* MUST be enforced , even when using stream ciphers . "
*
* -- http :// tools . ietf . org / html / rfc4253 #section-6
2007-07-23 05:21:39 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ see self :: _send_binary_packet ()
* @ var int
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $encrypt_block_size = 8 ;
2009-02-16 22:22:13 +00:00
/**
* Block Size for Client to Server Encryption
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ see self :: _get_binary_packet ()
* @ var int
2009-02-16 22:22:13 +00:00
*/
2017-01-08 01:51:56 +00:00
private $decrypt_block_size = 8 ;
2007-07-23 05:21:39 +00:00
/**
* Server to Client Encryption Object
*
2016-04-10 16:30:59 +00:00
* @ see self :: _get_binary_packet ()
2022-01-30 17:20:45 +00:00
* @ var SymmetricKey | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $decrypt = false ;
2007-07-23 05:21:39 +00:00
2022-01-30 07:36:02 +00:00
/**
* Decryption Algorithm Name
*
* @ var string | null
*/
private $decryptName ;
/**
* Decryption Invocation Counter
*
* Used by GCM
*
* @ var string | null
*/
private $decryptInvocationCounter ;
2022-02-02 03:17:10 +00:00
/**
* Fixed Part of Nonce
*
* Used by GCM
*
* @ var string | null
*/
private $decryptFixedPart ;
2019-03-18 11:59:00 +00:00
/**
* Server to Client Length Encryption Object
*
* @ see self :: _get_binary_packet ()
* @ var object
*/
private $lengthDecrypt = false ;
2007-07-23 05:21:39 +00:00
/**
* Client to Server Encryption Object
*
2016-04-10 16:30:59 +00:00
* @ see self :: _send_binary_packet ()
2022-01-30 17:20:45 +00:00
* @ var SymmetricKey | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $encrypt = false ;
2007-07-23 05:21:39 +00:00
2022-01-30 07:36:02 +00:00
/**
* Encryption Algorithm Name
*
* @ var string | null
*/
private $encryptName ;
/**
* Encryption Invocation Counter
*
* Used by GCM
*
* @ var string | null
*/
private $encryptInvocationCounter ;
2022-02-02 03:17:10 +00:00
/**
* Fixed Part of Nonce
*
* Used by GCM
*
* @ var string | null
*/
private $encryptFixedPart ;
2019-03-18 11:59:00 +00:00
/**
* Client to Server Length Encryption Object
*
* @ see self :: _send_binary_packet ()
* @ var object
*/
private $lengthEncrypt = false ;
2007-07-23 05:21:39 +00:00
/**
* Client to Server HMAC Object
*
2016-04-10 16:30:59 +00:00
* @ see self :: _send_binary_packet ()
* @ var object
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $hmac_create = false ;
2007-07-23 05:21:39 +00:00
2022-01-30 07:52:31 +00:00
/**
* Client to Server HMAC Name
*
* @ var string | false
*/
private $hmac_create_name ;
/**
* Client to Server ETM
*
* @ var int | false
*/
private $hmac_create_etm ;
2007-07-23 05:21:39 +00:00
/**
* Server to Client HMAC Object
*
2016-04-10 16:30:59 +00:00
* @ see self :: _get_binary_packet ()
* @ var object
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $hmac_check = false ;
2007-07-23 05:21:39 +00:00
2022-01-30 07:52:31 +00:00
/**
* Server to Client HMAC Name
*
* @ var string | false
*/
private $hmac_check_name ;
/**
* Server to Client ETM
*
* @ var int | false
*/
private $hmac_check_etm ;
2007-07-23 05:21:39 +00:00
/**
* Size of server to client HMAC
*
* 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 .
* 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
* append it .
*
2016-04-10 16:30:59 +00:00
* @ see self :: _get_binary_packet ()
* @ var int
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $hmac_size = false ;
2007-07-23 05:21:39 +00:00
/**
* Server Public Host Key
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $server_public_host_key ;
2007-07-23 05:21:39 +00:00
/**
2016-07-31 12:41:23 +00:00
* Session identifier
2007-07-23 05:21:39 +00:00
*
* " The exchange hash H from the first key exchange is additionally
* used as the session identifier , which is a unique identifier for
* this connection . "
*
* -- http :// tools . ietf . org / html / rfc4253 #section-7.2
*
2016-04-10 16:30:59 +00:00
* @ see self :: _key_exchange ()
* @ var string
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private $session_id = false ;
2007-07-23 05:21:39 +00:00
2010-10-24 01:24:30 +00:00
/**
* Exchange hash
*
* The current exchange hash
*
2016-04-10 16:30:59 +00:00
* @ see self :: _key_exchange ()
* @ var string
2010-10-24 01:24:30 +00:00
*/
2017-01-08 01:51:56 +00:00
private $exchange_hash = false ;
2010-10-24 01:24:30 +00:00
2008-05-26 19:42:01 +00:00
/**
* Message Numbers
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ var array
2008-05-26 19:42:01 +00:00
* @ access private
*/
2017-01-08 01:51:56 +00:00
private $message_numbers = [];
2008-05-26 19:42:01 +00:00
/**
* Disconnection Message 'reason codes' defined in RFC4253
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ var array
2008-05-26 19:42:01 +00:00
* @ access private
*/
2017-01-08 01:51:56 +00:00
private $disconnect_reasons = [];
2008-05-26 19:42:01 +00:00
/**
* SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes' , defined in RFC4254
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ var array
2008-05-26 19:42:01 +00:00
* @ access private
*/
2017-01-08 01:51:56 +00:00
private $channel_open_failure_reasons = [];
2008-05-26 19:42:01 +00:00
/**
* Terminal Modes
*
* @ link http :// tools . ietf . org / html / rfc4254 #section-8
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ var array
2008-05-26 19:42:01 +00:00
* @ access private
*/
2017-01-08 01:51:56 +00:00
private $terminal_modes = [];
2008-05-26 19:42:01 +00:00
/**
* SSH_MSG_CHANNEL_EXTENDED_DATA ' s data_type_codes
*
* @ link http :// tools . ietf . org / html / rfc4254 #section-5.2
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ var array
2008-05-26 19:42:01 +00:00
* @ access private
*/
2017-01-08 01:51:56 +00:00
private $channel_extended_data_type_codes = [];
2008-05-26 19:42:01 +00:00
/**
* Send Sequence Number
*
* See 'Section 6.4. Data Integrity' of rfc4253 for more info .
*
2016-04-10 16:30:59 +00:00
* @ see self :: _send_binary_packet ()
* @ var int
2008-05-26 19:42:01 +00:00
*/
2017-01-08 01:51:56 +00:00
private $send_seq_no = 0 ;
2008-05-26 19:42:01 +00:00
/**
* Get Sequence Number
*
* See 'Section 6.4. Data Integrity' of rfc4253 for more info .
*
2016-04-10 16:30:59 +00:00
* @ see self :: _get_binary_packet ()
* @ var int
2008-05-26 19:42:01 +00:00
*/
2017-01-08 01:51:56 +00:00
private $get_seq_no = 0 ;
2008-05-26 19:42:01 +00:00
2009-10-16 03:37:24 +00:00
/**
2009-12-14 18:14:54 +00:00
* Server Channels
2009-10-16 03:37:24 +00:00
*
2009-12-14 18:14:54 +00:00
* Maps client channels to server channels
2009-10-16 03:37:24 +00:00
*
2018-09-10 15:17:20 +00:00
* @ see self :: get_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ see self :: exec ()
* @ var array
2009-10-16 03:37:24 +00:00
*/
2017-01-08 01:51:56 +00:00
protected $server_channels = [];
2009-10-16 03:37:24 +00:00
2009-08-23 03:40:50 +00:00
/**
2009-12-14 18:14:54 +00:00
* Channel Buffers
2009-08-23 03:40:50 +00:00
*
2009-12-14 18:14:54 +00:00
* If a client requests a packet from one channel but receives two packets from another those packets should
* be placed in a buffer
2009-08-23 03:40:50 +00:00
*
2018-09-10 15:17:20 +00:00
* @ see self :: get_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ see self :: exec ()
* @ var array
2009-08-23 03:40:50 +00:00
*/
2017-01-08 01:51:56 +00:00
private $channel_buffers = [];
2009-12-14 18:14:54 +00:00
/**
* Channel Status
*
* Contains the type of the last sent message
*
2018-09-10 15:17:20 +00:00
* @ see self :: get_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ var array
2009-12-14 18:14:54 +00:00
*/
2017-01-08 01:51:56 +00:00
protected $channel_status = [];
2009-08-23 03:40:50 +00:00
2009-09-17 03:19:20 +00:00
/**
* Packet Size
*
2009-12-14 18:14:54 +00:00
* Maximum packet size indexed by channel
2009-09-17 03:19:20 +00:00
*
2018-09-10 15:17:20 +00:00
* @ see self :: send_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ var array
2009-09-17 03:19:20 +00:00
*/
2017-01-08 01:51:56 +00:00
private $packet_size_client_to_server = [];
2009-09-17 03:19:20 +00:00
2009-03-25 22:29:42 +00:00
/**
* Message Number Log
*
2016-04-10 16:30:59 +00:00
* @ see self :: getLog ()
* @ var array
2009-03-25 22:29:42 +00:00
*/
2017-01-08 01:51:56 +00:00
private $message_number_log = [];
2009-03-25 22:29:42 +00:00
/**
* Message Log
*
2016-04-10 16:30:59 +00:00
* @ see self :: getLog ()
* @ var array
2009-03-25 22:29:42 +00:00
*/
2017-01-08 01:51:56 +00:00
private $message_log = [];
2009-03-25 22:29:42 +00:00
2010-02-11 07:02:51 +00:00
/**
* The Window Size
*
2013-05-10 21:48:10 +00:00
* Bytes the other party can send before it must wait for the window to be adjusted ( 0x7FFFFFFF = 2 GB )
2010-02-11 07:02:51 +00:00
*
2016-04-10 16:30:59 +00:00
* @ var int
2018-09-10 15:17:20 +00:00
* @ see self :: send_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ see self :: exec ()
2010-02-11 07:02:51 +00:00
*/
2017-01-08 01:51:56 +00:00
protected $window_size = 0x7FFFFFFF ;
2010-02-11 07:02:51 +00:00
2019-11-02 17:42:34 +00:00
/**
* What we resize the window to
*
* When PuTTY resizes the window it doesn ' t add an additional 0x7FFFFFFF bytes - it adds 0x40000000 bytes .
* Some SFTP clients ( GoAnywhere ) don ' t support adding 0x7FFFFFFF to the window size after the fact so
* we ' ll just do what PuTTY does
*
* @ var int
* @ see self :: _send_channel_packet ()
* @ see self :: exec ()
*/
2021-04-12 11:17:04 +00:00
private $window_resize = 0x40000000 ;
2019-11-02 17:42:34 +00:00
2010-02-11 07:02:51 +00:00
/**
2013-07-07 20:57:15 +00:00
* Window size , server to client
2010-02-11 07:02:51 +00:00
*
* Window size indexed by channel
*
2018-09-10 15:17:20 +00:00
* @ see self :: send_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ var array
2010-02-11 07:02:51 +00:00
*/
2017-01-08 01:51:56 +00:00
protected $window_size_server_to_client = [];
2010-02-11 07:02:51 +00:00
2013-07-07 20:57:15 +00:00
/**
* Window size , client to server
*
* Window size indexed by channel
*
2018-09-10 15:17:20 +00:00
* @ see self :: get_channel_packet ()
2016-04-10 16:30:59 +00:00
* @ var array
2013-07-07 20:57:15 +00:00
*/
2017-01-08 01:51:56 +00:00
private $window_size_client_to_server = [];
2013-07-07 20:57:15 +00:00
2010-04-22 16:06:43 +00:00
/**
* Server signature
*
* Verified against $this -> session_id
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2010-04-22 16:06:43 +00:00
*/
2017-01-08 01:51:56 +00:00
private $signature = '' ;
2010-04-22 16:06:43 +00:00
/**
* Server signature format
*
* ssh - rsa or ssh - dss .
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2010-04-22 16:06:43 +00:00
*/
2017-01-08 01:51:56 +00:00
private $signature_format = '' ;
2010-04-22 16:06:43 +00:00
2011-02-28 05:24:09 +00:00
/**
* Interactive Buffer
*
2016-04-10 16:30:59 +00:00
* @ see self :: read ()
2022-01-28 19:14:38 +00:00
* @ var string
2011-02-28 05:24:09 +00:00
*/
2017-01-08 01:51:56 +00:00
private $interactiveBuffer = '' ;
2011-02-28 05:24:09 +00:00
2011-06-04 17:06:53 +00:00
/**
* Current log size
*
2014-12-04 21:45:13 +00:00
* Should never exceed self :: LOG_MAX_SIZE
2011-06-04 17:06:53 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: _send_binary_packet ()
* @ see self :: _get_binary_packet ()
* @ var int
2011-06-04 17:06:53 +00:00
*/
2017-01-08 01:51:56 +00:00
private $log_size ;
2011-06-04 17:06:53 +00:00
2012-03-03 17:49:16 +00:00
/**
* Timeout
*
2016-04-10 16:30:59 +00:00
* @ see self :: setTimeout ()
2012-03-03 17:49:16 +00:00
*/
2019-05-28 13:50:03 +00:00
protected $timeout ;
2012-03-03 17:49:16 +00:00
/**
* Current Timeout
*
2018-09-10 15:17:20 +00:00
* @ see self :: get_channel_packet ()
2012-03-03 17:49:16 +00:00
*/
2017-08-03 07:19:11 +00:00
protected $curTimeout ;
2012-03-03 17:49:16 +00:00
2020-09-28 11:17:49 +00:00
/**
* Keep Alive Interval
*
* @ see self :: setKeepAlive ()
*/
2021-04-12 11:17:04 +00:00
private $keepAlive ;
2020-09-28 11:17:49 +00:00
2012-04-30 16:25:37 +00:00
/**
* Real - time log file pointer
*
2016-04-10 16:30:59 +00:00
* @ see self :: _append_log ()
2022-01-28 19:14:38 +00:00
* @ var resource | closed - resource
2012-04-30 16:25:37 +00:00
*/
2017-01-08 01:51:56 +00:00
private $realtime_log_file ;
2012-04-30 16:25:37 +00:00
/**
* Real - time log file size
*
2016-04-10 16:30:59 +00:00
* @ see self :: _append_log ()
* @ var int
2012-04-30 16:25:37 +00:00
*/
2017-01-08 01:51:56 +00:00
private $realtime_log_size ;
2012-04-30 16:25:37 +00:00
2012-07-04 18:36:26 +00:00
/**
* Has the signature been validated ?
*
2016-04-10 16:30:59 +00:00
* @ see self :: getServerPublicHostKey ()
* @ var bool
2012-07-04 18:36:26 +00:00
*/
2017-01-08 01:51:56 +00:00
private $signature_validated = false ;
2012-07-04 18:36:26 +00:00
2012-04-30 16:25:37 +00:00
/**
* Real - time log file wrap boolean
*
2016-04-10 16:30:59 +00:00
* @ see self :: _append_log ()
2012-04-30 16:25:37 +00:00
*/
2017-01-08 01:51:56 +00:00
private $realtime_log_wrap ;
2012-04-30 16:25:37 +00:00
2012-07-23 12:17:53 +00:00
/**
* Flag to suppress stderr from output
*
2016-04-10 16:30:59 +00:00
* @ see self :: enableQuietMode ()
2012-07-23 12:17:53 +00:00
*/
2017-01-08 01:51:56 +00:00
private $quiet_mode = false ;
2012-07-23 12:17:53 +00:00
2012-11-22 19:08:30 +00:00
/**
* Time of first network activity
*
2022-01-28 19:14:38 +00:00
* @ var float
2012-11-22 19:08:30 +00:00
*/
2017-01-08 01:51:56 +00:00
private $last_packet ;
2012-11-22 19:08:30 +00:00
2013-01-17 18:47:42 +00:00
/**
* Exit status returned from ssh if any
*
2016-04-10 16:30:59 +00:00
* @ var int
2013-01-17 18:47:42 +00:00
*/
2017-01-08 01:51:56 +00:00
private $exit_status ;
2013-01-17 18:47:42 +00:00
2013-02-08 22:04:52 +00:00
/**
* Flag to request a PTY when using exec ()
*
2016-04-10 16:30:59 +00:00
* @ var bool
* @ see self :: enablePTY ()
2013-02-08 22:04:52 +00:00
*/
2017-01-08 01:51:56 +00:00
private $request_pty = false ;
2013-02-08 22:04:52 +00:00
/**
* Flag set while exec () is running when using enablePTY ()
*
2016-04-10 16:30:59 +00:00
* @ var bool
2013-02-08 22:04:52 +00:00
*/
2017-01-08 01:51:56 +00:00
private $in_request_pty_exec = false ;
2013-02-08 22:04:52 +00:00
2013-10-25 17:35:30 +00:00
/**
* Flag set after startSubsystem () is called
*
2016-04-10 16:30:59 +00:00
* @ var bool
2013-10-25 17:35:30 +00:00
*/
2017-01-08 01:51:56 +00:00
private $in_subsystem ;
2013-10-25 17:35:30 +00:00
2013-03-21 22:18:31 +00:00
/**
* Contents of stdError
2013-04-20 03:23:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ var string
2013-03-21 22:18:31 +00:00
*/
2017-01-08 01:51:56 +00:00
private $stdErrorLog ;
2013-03-21 22:18:31 +00:00
2013-04-20 03:23:06 +00:00
/**
* The Last Interactive Response
*
2016-04-10 16:30:59 +00:00
* @ see self :: _keyboard_interactive_process ()
* @ var string
2013-04-20 03:23:06 +00:00
*/
2017-01-08 01:51:56 +00:00
private $last_interactive_response = '' ;
2013-04-20 03:23:06 +00:00
2013-04-20 19:35:08 +00:00
/**
* Keyboard Interactive Request / Responses
*
2016-04-10 16:30:59 +00:00
* @ see self :: _keyboard_interactive_process ()
* @ var array
2013-04-20 19:35:08 +00:00
*/
2017-01-08 01:51:56 +00:00
private $keyboard_requests_responses = [];
2013-04-20 19:35:08 +00:00
2013-04-28 00:58:24 +00:00
/**
* Banner Message
*
* Quoting from the RFC , " in some jurisdictions, sending a warning message before
* authentication may be relevant for getting legal protection . "
*
2016-04-10 16:30:59 +00:00
* @ see self :: _filter ()
* @ see self :: getBannerMessage ()
* @ var string
2013-04-28 00:58:24 +00:00
*/
2017-01-08 01:51:56 +00:00
private $banner_message = '' ;
2013-04-28 00:58:24 +00:00
2013-06-07 22:21:11 +00:00
/**
* Did read () timeout or return normally ?
*
2016-04-10 16:30:59 +00:00
* @ see self :: isTimeout ()
* @ var bool
2013-06-07 22:21:11 +00:00
*/
2017-01-08 01:51:56 +00:00
private $is_timeout = false ;
2013-06-07 22:21:11 +00:00
2013-12-16 17:27:12 +00:00
/**
* Log Boundary
*
2016-04-10 16:30:59 +00:00
* @ see self :: _format_log ()
* @ var string
2013-12-16 17:27:12 +00:00
*/
2017-01-08 01:51:56 +00:00
private $log_boundary = ':' ;
2013-12-16 17:27:12 +00:00
/**
* Log Long Width
*
2016-04-10 16:30:59 +00:00
* @ see self :: _format_log ()
* @ var int
2013-12-16 17:27:12 +00:00
*/
2017-01-08 01:51:56 +00:00
private $log_long_width = 65 ;
2013-12-16 17:27:12 +00:00
/**
* Log Short Width
*
2016-04-10 16:30:59 +00:00
* @ see self :: _format_log ()
* @ var int
2013-12-16 17:27:12 +00:00
*/
2017-01-08 01:51:56 +00:00
private $log_short_width = 16 ;
2013-12-16 17:27:12 +00:00
2007-07-23 05:21:39 +00:00
/**
2014-04-07 04:45:25 +00:00
* Hostname
2007-07-23 05:21:39 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ see self :: _connect ()
* @ var string
2014-04-07 04:45:25 +00:00
*/
2017-01-08 01:51:56 +00:00
private $host ;
2014-04-07 04:45:25 +00:00
/**
* Port Number
*
2016-04-10 16:30:59 +00:00
* @ see self :: __construct ()
* @ see self :: _connect ()
* @ var int
2014-04-07 04:45:25 +00:00
*/
2017-01-08 01:51:56 +00:00
private $port ;
2014-04-07 04:45:25 +00:00
2014-06-20 10:04:17 +00:00
/**
* Number of columns for terminal window size
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: getWindowColumns ()
* @ see self :: setWindowColumns ()
* @ see self :: setWindowSize ()
* @ var int
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
private $windowColumns = 80 ;
2014-06-20 10:04:17 +00:00
/**
* Number of columns for terminal window size
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: getWindowRows ()
* @ see self :: setWindowRows ()
* @ see self :: setWindowSize ()
* @ var int
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
private $windowRows = 24 ;
2014-06-20 10:04:17 +00:00
2014-10-12 04:26:46 +00:00
/**
* Crypto Engine
*
2016-04-10 16:30:59 +00:00
* @ see self :: setCryptoEngine ()
* @ see self :: _key_exchange ()
* @ var int
2014-10-12 04:26:46 +00:00
*/
2020-08-01 02:27:38 +00:00
private static $crypto_engine = false ;
2014-10-12 04:26:46 +00:00
2014-12-30 02:44:31 +00:00
/**
* A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
*
2022-01-28 19:14:38 +00:00
* @ var Agent
2014-12-30 02:44:31 +00:00
*/
2017-01-08 01:51:56 +00:00
private $agent ;
2014-12-30 02:44:31 +00:00
2016-04-30 21:23:35 +00:00
/**
* Connection storage to replicates ssh2 extension functionality :
* { @ link http :// php . net / manual / en / wrappers . ssh2 . php #refsect1-wrappers.ssh2-examples}
*
2022-01-28 19:14:38 +00:00
* @ var array < string , SSH2 | \WeakReference < SSH2 >>
2016-04-30 21:23:35 +00:00
*/
2017-01-08 01:51:56 +00:00
private static $connections ;
2016-04-30 21:23:35 +00:00
2017-08-07 05:15:50 +00:00
/**
* Send the identification string first ?
*
* @ var bool
*/
2017-08-08 01:09:26 +00:00
private $send_id_string_first = true ;
2017-08-07 05:15:50 +00:00
/**
* Send the key exchange initiation packet first ?
*
* @ var bool
*/
2017-08-08 01:09:26 +00:00
private $send_kex_first = true ;
2017-08-07 05:15:50 +00:00
2017-08-27 00:50:17 +00:00
/**
* Some versions of OpenSSH incorrectly calculate the key size
*
* @ var bool
*/
2017-08-27 17:33:19 +00:00
private $bad_key_size_fix = false ;
2017-08-27 00:50:17 +00:00
/**
* Should we try to re - connect to re - establish keys ?
*
* @ var bool
*/
2017-08-27 17:33:19 +00:00
private $retry_connect = false ;
2017-08-27 00:50:17 +00:00
2017-08-29 03:37:35 +00:00
/**
* Binary Packet Buffer
*
* @ var string | false
*/
2017-09-06 04:42:05 +00:00
private $binary_packet_buffer = false ;
2017-08-29 03:37:35 +00:00
2018-05-27 14:48:44 +00:00
/**
* Preferred Signature Format
*
* @ var string | false
*/
2019-09-11 00:52:35 +00:00
protected $preferred_signature_format = false ;
2018-05-27 14:48:44 +00:00
2018-10-06 06:19:14 +00:00
/**
* Authentication Credentials
*
* @ var array
*/
2019-09-11 00:52:35 +00:00
protected $auth = [];
2018-10-06 06:19:14 +00:00
2021-03-17 02:18:56 +00:00
/**
* Terminal
*
* @ var string
*/
private $term = 'vt100' ;
2021-04-29 21:22:24 +00:00
/**
* The authentication methods that may productively continue authentication .
2022-01-22 18:03:07 +00:00
*
2021-04-29 21:22:24 +00:00
* @ see https :// tools . ietf . org / html / rfc4252 #section-5.1
2021-05-02 13:37:19 +00:00
* @ var array | null
2021-04-29 21:22:24 +00:00
*/
private $auth_methods_to_continue = null ;
2021-10-27 01:04:53 +00:00
/**
* Compression method
*
* @ var int
*/
2021-12-14 15:34:41 +00:00
private $compress = self :: NET_SSH2_COMPRESSION_NONE ;
2021-10-27 01:04:53 +00:00
/**
* Decompression method
*
2022-01-28 19:14:38 +00:00
* @ var int
2021-10-27 01:04:53 +00:00
*/
2021-12-14 15:34:41 +00:00
private $decompress = self :: NET_SSH2_COMPRESSION_NONE ;
2021-10-27 01:04:53 +00:00
/**
* Compression context
*
2022-01-28 19:14:38 +00:00
* @ var resource | false | null
2021-10-27 01:04:53 +00:00
*/
2021-10-27 01:48:46 +00:00
private $compress_context ;
2021-10-27 01:04:53 +00:00
/**
* Decompression context
*
* @ var resource | object
*/
2021-10-27 01:48:46 +00:00
private $decompress_context ;
2021-10-27 01:04:53 +00:00
/**
* Regenerate Compression Context
*
* @ var bool
*/
2021-10-27 01:48:46 +00:00
private $regenerate_compression_context = false ;
2021-10-27 01:04:53 +00:00
/**
* Regenerate Decompression Context
*
* @ var bool
2021-04-30 18:48:19 +00:00
*/
2021-10-27 01:48:46 +00:00
private $regenerate_decompression_context = false ;
2021-04-30 18:48:19 +00:00
2021-11-04 03:16:14 +00:00
/**
* Smart multi - factor authentication flag
*
* @ var bool
*/
2021-11-04 03:28:16 +00:00
private $smartMFA = true ;
2021-11-04 03:16:14 +00:00
2007-07-23 05:21:39 +00:00
/**
* Default Constructor .
*
2015-07-17 17:30:44 +00:00
* $host can either be a string , representing the host , or a stream resource .
*
2016-04-10 16:30:59 +00:00
* @ param mixed $host
* @ param int $port
* @ param int $timeout
* @ see self :: login ()
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
public function __construct ( $host , $port = 22 , $timeout = 10 )
2007-07-23 05:21:39 +00:00
{
2016-11-30 21:19:23 +00:00
$this -> message_numbers = [
2008-05-26 19:42:01 +00:00
1 => 'NET_SSH2_MSG_DISCONNECT' ,
2 => 'NET_SSH2_MSG_IGNORE' ,
3 => 'NET_SSH2_MSG_UNIMPLEMENTED' ,
4 => 'NET_SSH2_MSG_DEBUG' ,
5 => 'NET_SSH2_MSG_SERVICE_REQUEST' ,
6 => 'NET_SSH2_MSG_SERVICE_ACCEPT' ,
20 => 'NET_SSH2_MSG_KEXINIT' ,
21 => 'NET_SSH2_MSG_NEWKEYS' ,
30 => 'NET_SSH2_MSG_KEXDH_INIT' ,
31 => 'NET_SSH2_MSG_KEXDH_REPLY' ,
50 => 'NET_SSH2_MSG_USERAUTH_REQUEST' ,
51 => 'NET_SSH2_MSG_USERAUTH_FAILURE' ,
52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS' ,
53 => 'NET_SSH2_MSG_USERAUTH_BANNER' ,
80 => 'NET_SSH2_MSG_GLOBAL_REQUEST' ,
81 => 'NET_SSH2_MSG_REQUEST_SUCCESS' ,
82 => 'NET_SSH2_MSG_REQUEST_FAILURE' ,
90 => 'NET_SSH2_MSG_CHANNEL_OPEN' ,
91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION' ,
92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE' ,
93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST' ,
94 => 'NET_SSH2_MSG_CHANNEL_DATA' ,
95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA' ,
96 => 'NET_SSH2_MSG_CHANNEL_EOF' ,
97 => 'NET_SSH2_MSG_CHANNEL_CLOSE' ,
98 => 'NET_SSH2_MSG_CHANNEL_REQUEST' ,
99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS' ,
100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
2016-11-30 21:19:23 +00:00
];
$this -> disconnect_reasons = [
2008-05-26 19:42:01 +00:00
1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT' ,
2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR' ,
3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED' ,
4 => 'NET_SSH2_DISCONNECT_RESERVED' ,
5 => 'NET_SSH2_DISCONNECT_MAC_ERROR' ,
6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR' ,
7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE' ,
8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED' ,
9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE' ,
10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST' ,
11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION' ,
12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS' ,
13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER' ,
14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE' ,
15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
2016-11-30 21:19:23 +00:00
];
$this -> channel_open_failure_reasons = [
2008-05-26 19:42:01 +00:00
1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
2016-11-30 21:19:23 +00:00
];
$this -> terminal_modes = [
2008-05-26 19:42:01 +00:00
0 => 'NET_SSH2_TTY_OP_END'
2016-11-30 21:19:23 +00:00
];
$this -> channel_extended_data_type_codes = [
2008-05-26 19:42:01 +00:00
1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
2016-11-30 21:19:23 +00:00
];
2008-05-26 19:42:01 +00:00
2017-01-08 01:51:56 +00:00
$this -> define_array (
2008-05-26 19:42:01 +00:00
$this -> message_numbers ,
$this -> disconnect_reasons ,
$this -> channel_open_failure_reasons ,
$this -> terminal_modes ,
2009-12-03 08:19:00 +00:00
$this -> channel_extended_data_type_codes ,
2016-11-30 21:19:23 +00:00
[ 60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' ],
[ 60 => 'NET_SSH2_MSG_USERAUTH_PK_OK' ],
[ 60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST' ,
61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE' ],
2015-06-24 22:50:00 +00:00
// RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
2016-11-30 21:19:23 +00:00
[ 30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD' ,
2015-06-24 22:50:00 +00:00
31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP' ,
32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT' ,
33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY' ,
2016-11-30 21:19:23 +00:00
34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST' ],
2015-07-23 01:02:28 +00:00
// RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
2016-11-30 21:19:23 +00:00
[ 30 => 'NET_SSH2_MSG_KEX_ECDH_INIT' ,
31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY' ]
2008-05-26 19:42:01 +00:00
);
2022-01-28 19:14:38 +00:00
/**
* Typehint is required due to a bug in Psalm : https :// github . com / vimeo / psalm / issues / 7508
* @ var \WeakReference < SSH2 >| SSH2
*/
2022-01-30 01:56:43 +00:00
self :: $connections [ $this -> getResourceId ()] = class_exists ( 'WeakReference' )
2022-01-28 19:14:38 +00:00
? \WeakReference :: create ( $this )
: $this ;
2016-04-30 21:23:35 +00:00
2015-07-17 17:30:44 +00:00
if ( is_resource ( $host )) {
$this -> fsock = $host ;
return ;
}
2021-12-08 04:20:55 +00:00
if ( Strings :: is_stringable ( $host )) {
2015-07-17 17:30:44 +00:00
$this -> host = $host ;
$this -> port = $port ;
$this -> timeout = $timeout ;
}
2014-04-07 04:45:25 +00:00
}
2014-10-12 04:26:46 +00:00
/**
* Set Crypto Engine Mode
*
* Possible $engine values :
2017-06-28 03:34:36 +00:00
* OpenSSL , mcrypt , Eval , PHP
2014-10-12 04:26:46 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param int $engine
2014-10-12 04:26:46 +00:00
*/
2020-08-01 02:27:38 +00:00
public static function setCryptoEngine ( $engine )
2014-10-12 04:26:46 +00:00
{
2020-08-01 02:27:38 +00:00
self :: $crypto_engine = $engine ;
2014-10-12 04:26:46 +00:00
}
2017-08-07 05:15:50 +00:00
/**
* Send Identification String First
*
* https :// tools . ietf . org / html / rfc4253 #section-4.2 says "when the connection has been established,
* both sides MUST send an identification string " . It does not say which side sends it first. In
* theory it shouldn ' t matter but it is a fact of life that some SSH servers are simply buggy
*
*/
2017-08-27 17:33:19 +00:00
public function sendIdentificationStringFirst ()
2017-08-07 05:15:50 +00:00
{
$this -> send_id_string_first = true ;
}
/**
* Send Identification String Last
*
* https :// tools . ietf . org / html / rfc4253 #section-4.2 says "when the connection has been established,
* both sides MUST send an identification string " . It does not say which side sends it first. In
* theory it shouldn ' t matter but it is a fact of life that some SSH servers are simply buggy
*
*/
2017-08-27 17:33:19 +00:00
public function sendIdentificationStringLast ()
2017-08-07 05:15:50 +00:00
{
$this -> send_id_string_first = false ;
}
/**
* Send SSH_MSG_KEXINIT First
*
* https :// tools . ietf . org / html / rfc4253 #section-7.1 says "key exchange begins by each sending
* sending the [ SSH_MSG_KEXINIT ] packet " . It does not say which side sends it first. In theory
* it shouldn ' t matter but it is a fact of life that some SSH servers are simply buggy
*
*/
2017-08-27 17:33:19 +00:00
public function sendKEXINITFirst ()
2017-08-07 05:15:50 +00:00
{
$this -> send_kex_first = true ;
}
/**
* Send SSH_MSG_KEXINIT Last
*
* https :// tools . ietf . org / html / rfc4253 #section-7.1 says "key exchange begins by each sending
* sending the [ SSH_MSG_KEXINIT ] packet " . It does not say which side sends it first. In theory
* it shouldn ' t matter but it is a fact of life that some SSH servers are simply buggy
*
*/
2017-08-27 17:33:19 +00:00
public function sendKEXINITLast ()
2017-08-07 05:15:50 +00:00
{
$this -> send_kex_first = false ;
}
2014-04-07 04:56:21 +00:00
/**
* Connect to an SSHv2 server
*
2016-04-30 21:23:35 +00:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2014-04-07 04:56:21 +00:00
*/
2017-01-08 01:51:56 +00:00
private function connect ()
2014-04-07 04:45:25 +00:00
{
2014-12-04 21:45:13 +00:00
if ( $this -> bitmap & self :: MASK_CONSTRUCTOR ) {
2021-05-21 13:49:52 +00:00
return ;
2014-08-05 01:11:34 +00:00
}
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_CONSTRUCTOR ;
2014-08-05 01:11:34 +00:00
2015-03-19 12:53:19 +00:00
$this -> curTimeout = $this -> timeout ;
2014-04-10 16:00:38 +00:00
$this -> last_packet = microtime ( true );
2014-04-07 04:45:25 +00:00
2015-07-17 17:30:44 +00:00
if ( ! is_resource ( $this -> fsock )) {
2015-07-17 17:43:50 +00:00
$start = microtime ( true );
2016-11-20 17:08:53 +00:00
// with stream_select a timeout of 0 means that no timeout takes place;
// with fsockopen a timeout of 0 means that you instantly timeout
// to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0
$this -> fsock = @ fsockopen ( $this -> host , $this -> port , $errno , $errstr , $this -> curTimeout == 0 ? 100000 : $this -> curTimeout );
2015-07-17 17:30:44 +00:00
if ( ! $this -> fsock ) {
2015-07-17 18:08:24 +00:00
$host = $this -> host . ':' . $this -> port ;
2019-09-17 04:31:46 +00:00
throw new UnableToConnectException ( rtrim ( " Cannot connect to $host . Error $errno . $errstr " ));
2015-07-17 17:30:44 +00:00
}
2015-07-17 17:43:50 +00:00
$elapsed = microtime ( true ) - $start ;
2015-07-17 17:30:44 +00:00
2018-05-19 11:26:22 +00:00
if ( $this -> curTimeout ) {
2022-02-17 02:25:59 +00:00
$this -> curTimeout -= $elapsed ;
2018-05-19 11:26:22 +00:00
if ( $this -> curTimeout < 0 ) {
2021-05-21 13:49:52 +00:00
throw new \RuntimeException ( 'Connection timed out whilst attempting to open socket connection' );
2018-05-19 11:26:22 +00:00
}
2015-07-17 17:30:44 +00:00
}
2012-06-23 22:16:42 +00:00
}
2017-01-08 01:51:56 +00:00
$this -> identifier = $this -> generate_identifier ();
2016-11-08 15:11:57 +00:00
2017-08-07 05:15:50 +00:00
if ( $this -> send_id_string_first ) {
fputs ( $this -> fsock , $this -> identifier . " \r \n " );
}
2016-11-08 15:11:57 +00:00
2007-07-23 05:21:39 +00:00
/* According to the SSH2 specs ,
" The server MAY send other lines of data before sending the version
string . Each line SHOULD be terminated by a Carriage Return and Line
Feed . Such lines MUST NOT begin with " SSH- " , and SHOULD be encoded
in ISO - 10646 UTF - 8 [ RFC3629 ] ( language is not specified ) . Clients
MUST be able to process such lines . " */
2016-05-22 19:37:12 +00:00
$data = '' ;
while ( ! feof ( $this -> fsock ) && ! preg_match ( '#(.*)^(SSH-(\d\.\d+).*)#ms' , $data , $matches )) {
$line = '' ;
while ( true ) {
if ( $this -> curTimeout ) {
if ( $this -> curTimeout < 0 ) {
2021-05-21 13:49:52 +00:00
throw new \RuntimeException ( 'Connection timed out whilst receiving server identification string' );
2016-05-22 19:37:12 +00:00
}
2016-11-30 21:19:23 +00:00
$read = [ $this -> fsock ];
2016-05-22 19:37:12 +00:00
$write = $except = null ;
2016-06-07 04:05:15 +00:00
$start = microtime ( true );
2022-01-28 06:52:05 +00:00
$sec = ( int ) floor ( $this -> curTimeout );
2022-01-29 17:35:38 +00:00
$usec = ( int ) ( 1000000 * ( $this -> curTimeout - $sec ));
2020-12-23 16:39:00 +00:00
if ( @ stream_select ( $read , $write , $except , $sec , $usec ) === false ) {
2021-05-21 13:49:52 +00:00
throw new \RuntimeException ( 'Connection timed out whilst receiving server identification string' );
2016-05-22 19:37:12 +00:00
}
2016-06-07 04:05:15 +00:00
$elapsed = microtime ( true ) - $start ;
2022-02-17 02:25:59 +00:00
$this -> curTimeout -= $elapsed ;
2016-05-22 19:37:12 +00:00
}
2015-03-19 12:53:19 +00:00
2016-05-22 19:37:12 +00:00
$temp = stream_get_line ( $this -> fsock , 255 , " \n " );
2021-05-21 13:49:52 +00:00
if ( $temp === false ) {
throw new \RuntimeException ( 'Error reading from socket' );
}
2016-05-22 19:37:12 +00:00
if ( strlen ( $temp ) == 255 ) {
continue ;
2015-03-19 12:53:19 +00:00
}
2016-06-07 04:05:15 +00:00
2022-02-17 02:25:59 +00:00
$line .= " $temp\n " ;
2016-08-12 04:09:38 +00:00
// quoting RFC4253, "Implementers who wish to maintain
// compatibility with older, undocumented versions of this protocol may
// want to process the identification string without expecting the
// presence of the carriage return character for reasons described in
// Section 5 of this document."
//if (substr($line, -2) == "\r\n") {
// break;
//}
break ;
2015-03-19 12:53:19 +00:00
}
2016-08-12 04:09:38 +00:00
2022-02-17 02:25:59 +00:00
$data .= $line ;
2007-07-23 05:21:39 +00:00
}
2009-10-17 03:49:16 +00:00
2010-05-10 16:16:35 +00:00
if ( feof ( $this -> fsock )) {
2018-10-06 02:27:59 +00:00
$this -> bitmap = 0 ;
2019-09-11 12:55:29 +00:00
throw new ConnectionClosedException ( 'Connection closed by server' );
2010-05-10 16:16:35 +00:00
}
2016-05-22 19:37:12 +00:00
$extra = $matches [ 1 ];
2009-10-17 03:49:16 +00:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2017-01-08 01:51:56 +00:00
$this -> append_log ( '<-' , $matches [ 0 ]);
$this -> append_log ( '->' , $this -> identifier . " \r \n " );
2009-10-17 03:49:16 +00:00
}
2010-04-27 21:29:36 +00:00
$this -> server_identifier = trim ( $temp , " \r \n " );
2013-01-12 16:46:19 +00:00
if ( strlen ( $extra )) {
2018-08-21 02:41:18 +00:00
$this -> errors [] = $data ;
2010-02-27 23:34:46 +00:00
}
2007-07-23 05:21:39 +00:00
2017-08-25 04:55:27 +00:00
if ( version_compare ( $matches [ 3 ], '1.99' , '<' )) {
2020-02-24 07:02:26 +00:00
$this -> bitmap = 0 ;
2019-09-17 04:31:46 +00:00
throw new UnableToConnectException ( " Cannot connect to SSH $matches[3] servers " );
2007-07-23 05:21:39 +00:00
}
2017-08-07 05:15:50 +00:00
if ( ! $this -> send_id_string_first ) {
fputs ( $this -> fsock , $this -> identifier . " \r \n " );
2007-07-23 05:21:39 +00:00
}
2017-08-07 05:15:50 +00:00
if ( ! $this -> send_kex_first ) {
2017-08-08 01:09:26 +00:00
$response = $this -> get_binary_packet ();
2017-08-07 05:15:50 +00:00
2022-01-30 01:56:43 +00:00
if ( is_bool ( $response ) || ! strlen ( $response ) || ord ( $response [ 0 ]) != NET_SSH2_MSG_KEXINIT ) {
2020-02-24 07:02:26 +00:00
$this -> bitmap = 0 ;
2017-08-08 01:09:26 +00:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEXINIT' );
2017-08-07 05:15:50 +00:00
}
2021-05-21 13:49:52 +00:00
$this -> key_exchange ( $response );
2007-07-23 05:21:39 +00:00
}
2021-05-21 13:49:52 +00:00
if ( $this -> send_kex_first ) {
$this -> key_exchange ();
2007-07-23 05:21:39 +00:00
}
2022-02-17 02:25:59 +00:00
$this -> bitmap |= self :: MASK_CONNECTED ;
2014-04-07 04:45:25 +00:00
return true ;
2007-07-23 05:21:39 +00:00
}
2013-12-16 19:17:10 +00:00
/**
* Generates the SSH identifier
2013-12-28 19:47:24 +00:00
*
2013-12-28 17:16:09 +00:00
* You should overwrite this method in your own class if you want to use another identifier
2013-12-16 19:17:10 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return string
2013-12-16 19:17:10 +00:00
*/
2017-01-08 01:51:56 +00:00
private function generate_identifier ()
2013-12-16 19:17:10 +00:00
{
2020-03-08 03:19:00 +00:00
$identifier = 'SSH-2.0-phpseclib_3.0' ;
2013-12-16 19:17:10 +00:00
2016-11-30 21:19:23 +00:00
$ext = [];
2019-08-08 04:29:33 +00:00
if ( extension_loaded ( 'sodium' )) {
2015-07-23 01:02:28 +00:00
$ext [] = 'libsodium' ;
}
2014-12-30 04:10:26 +00:00
if ( extension_loaded ( 'openssl' )) {
$ext [] = 'openssl' ;
} elseif ( extension_loaded ( 'mcrypt' )) {
2013-12-16 19:17:10 +00:00
$ext [] = 'mcrypt' ;
}
if ( extension_loaded ( 'gmp' )) {
$ext [] = 'gmp' ;
2013-12-17 17:44:37 +00:00
} elseif ( extension_loaded ( 'bcmath' )) {
$ext [] = 'bcmath' ;
2013-12-16 19:17:10 +00:00
}
if ( ! empty ( $ext )) {
$identifier .= ' (' . implode ( ', ' , $ext ) . ')' ;
}
return $identifier ;
}
2007-07-23 05:21:39 +00:00
/**
* Key Exchange
2019-07-27 22:28:18 +00:00
*
2017-11-05 20:35:27 +00:00
* @ return bool
* @ param string | bool $kexinit_payload_server optional
2016-04-30 21:23:35 +00:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2019-11-07 05:41:40 +00:00
* @ throws \phpseclib3\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
2007-07-23 05:21:39 +00:00
*/
2017-08-08 01:09:26 +00:00
private function key_exchange ( $kexinit_payload_server = false )
2007-07-23 05:21:39 +00:00
{
2019-03-29 23:44:31 +00:00
$preferred = $this -> preferred ;
2021-05-16 03:22:14 +00:00
$send_kex = true ;
2019-03-29 23:44:31 +00:00
$kex_algorithms = isset ( $preferred [ 'kex' ]) ?
$preferred [ 'kex' ] :
SSH2 :: getSupportedKEXAlgorithms ();
$server_host_key_algorithms = isset ( $preferred [ 'hostkey' ]) ?
$preferred [ 'hostkey' ] :
SSH2 :: getSupportedHostKeyAlgorithms ();
$s2c_encryption_algorithms = isset ( $preferred [ 'server_to_client' ][ 'crypt' ]) ?
$preferred [ 'server_to_client' ][ 'crypt' ] :
SSH2 :: getSupportedEncryptionAlgorithms ();
$c2s_encryption_algorithms = isset ( $preferred [ 'client_to_server' ][ 'crypt' ]) ?
$preferred [ 'client_to_server' ][ 'crypt' ] :
SSH2 :: getSupportedEncryptionAlgorithms ();
$s2c_mac_algorithms = isset ( $preferred [ 'server_to_client' ][ 'mac' ]) ?
$preferred [ 'server_to_client' ][ 'mac' ] :
SSH2 :: getSupportedMACAlgorithms ();
$c2s_mac_algorithms = isset ( $preferred [ 'client_to_server' ][ 'mac' ]) ?
$preferred [ 'client_to_server' ][ 'mac' ] :
SSH2 :: getSupportedMACAlgorithms ();
$s2c_compression_algorithms = isset ( $preferred [ 'server_to_client' ][ 'comp' ]) ?
$preferred [ 'server_to_client' ][ 'comp' ] :
SSH2 :: getSupportedCompressionAlgorithms ();
$c2s_compression_algorithms = isset ( $preferred [ 'client_to_server' ][ 'comp' ]) ?
$preferred [ 'client_to_server' ][ 'comp' ] :
SSH2 :: getSupportedCompressionAlgorithms ();
2007-07-23 05:21:39 +00:00
2012-09-17 07:33:03 +00:00
// some SSH servers have buggy implementations of some of the above algorithms
2017-06-02 12:46:51 +00:00
switch ( true ) {
case $this -> server_identifier == 'SSH-2.0-SSHD' :
case substr ( $this -> server_identifier , 0 , 13 ) == 'SSH-2.0-DLINK' :
2019-03-29 23:44:31 +00:00
if ( ! isset ( $preferred [ 'server_to_client' ][ 'mac' ])) {
$s2c_mac_algorithms = array_values ( array_diff (
$s2c_mac_algorithms ,
[ 'hmac-sha1-96' , 'hmac-md5-96' ]
));
}
if ( ! isset ( $preferred [ 'client_to_server' ][ 'mac' ])) {
$c2s_mac_algorithms = array_values ( array_diff (
$c2s_mac_algorithms ,
[ 'hmac-sha1-96' , 'hmac-md5-96' ]
));
}
2012-09-17 07:33:03 +00:00
}
2014-12-02 17:20:40 +00:00
$client_cookie = Random :: string ( 16 );
2007-07-23 05:21:39 +00:00
2019-04-02 05:09:19 +00:00
$kexinit_payload_client = pack ( 'Ca*' , NET_SSH2_MSG_KEXINIT , $client_cookie );
2022-02-17 02:25:59 +00:00
$kexinit_payload_client .= Strings :: packSSH2 (
2019-04-02 05:09:19 +00:00
'L10bN' ,
$kex_algorithms ,
$server_host_key_algorithms ,
$c2s_encryption_algorithms ,
$s2c_encryption_algorithms ,
$c2s_mac_algorithms ,
$s2c_mac_algorithms ,
$c2s_compression_algorithms ,
$s2c_compression_algorithms ,
[], // language, client to server
[], // language, server to client
false , // first_kex_packet_follows
0 // reserved for future extension
2017-08-07 05:15:50 +00:00
);
2021-05-16 03:22:14 +00:00
if ( $kexinit_payload_server === false ) {
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $kexinit_payload_client );
2017-08-07 05:15:50 +00:00
2017-08-08 01:09:26 +00:00
$kexinit_payload_server = $this -> get_binary_packet ();
2017-08-07 05:15:50 +00:00
2022-01-28 19:14:38 +00:00
if (
2022-01-30 01:56:43 +00:00
is_bool ( $kexinit_payload_server )
2022-01-28 19:14:38 +00:00
|| ! strlen ( $kexinit_payload_server )
|| ord ( $kexinit_payload_server [ 0 ]) != NET_SSH2_MSG_KEXINIT
) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_PROTOCOL_ERROR );
2017-08-08 01:09:26 +00:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEXINIT' );
2017-08-07 05:15:50 +00:00
}
2021-05-16 03:22:14 +00:00
$send_kex = false ;
2017-08-07 05:15:50 +00:00
}
2007-07-23 05:21:39 +00:00
$response = $kexinit_payload_server ;
2016-07-31 03:18:06 +00:00
Strings :: shift ( $response , 1 ); // skip past the message number (it should be SSH_MSG_KEXINIT)
$server_cookie = Strings :: shift ( $response , 16 );
2007-07-23 05:21:39 +00:00
2019-04-02 05:09:19 +00:00
list (
$this -> kex_algorithms ,
$this -> server_host_key_algorithms ,
$this -> encryption_algorithms_client_to_server ,
$this -> encryption_algorithms_server_to_client ,
$this -> mac_algorithms_client_to_server ,
$this -> mac_algorithms_server_to_client ,
$this -> compression_algorithms_client_to_server ,
$this -> compression_algorithms_server_to_client ,
$this -> languages_client_to_server ,
$this -> languages_server_to_client ,
$first_kex_packet_follows
) = Strings :: unpackSSH2 ( 'L10C' , $response );
2007-07-23 05:21:39 +00:00
2021-05-16 03:28:36 +00:00
if ( $send_kex ) {
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $kexinit_payload_client );
2007-07-23 05:21:39 +00:00
}
// we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
2016-04-30 21:23:35 +00:00
2007-07-23 05:21:39 +00:00
// we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
// diffie-hellman key exchange as fast as possible
2019-09-11 04:14:29 +00:00
$decrypt = self :: array_intersect_first ( $s2c_encryption_algorithms , $this -> encryption_algorithms_server_to_client );
2017-01-08 01:51:56 +00:00
$decryptKeyLength = $this -> encryption_algorithm_to_key_size ( $decrypt );
2015-07-24 10:31:50 +00:00
if ( $decryptKeyLength === null ) {
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 21:23:35 +00:00
throw new NoSupportedAlgorithmsException ( 'No compatible server to client encryption algorithms found' );
2007-07-23 05:21:39 +00:00
}
2019-09-11 04:14:29 +00:00
$encrypt = self :: array_intersect_first ( $c2s_encryption_algorithms , $this -> encryption_algorithms_client_to_server );
2017-01-08 01:51:56 +00:00
$encryptKeyLength = $this -> encryption_algorithm_to_key_size ( $encrypt );
2015-07-24 10:31:50 +00:00
if ( $encryptKeyLength === null ) {
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 21:23:35 +00:00
throw new NoSupportedAlgorithmsException ( 'No compatible client to server encryption algorithms found' );
2007-07-23 05:21:39 +00:00
}
// through diffie-hellman key exchange a symmetric key is obtained
2019-09-11 04:14:29 +00:00
$this -> kex_algorithm = self :: array_intersect_first ( $kex_algorithms , $this -> kex_algorithms );
2019-03-28 18:57:26 +00:00
if ( $this -> kex_algorithm === false ) {
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 21:23:35 +00:00
throw new NoSupportedAlgorithmsException ( 'No compatible key exchange algorithms found' );
2007-07-23 05:21:39 +00:00
}
2015-06-24 22:50:00 +00:00
2020-12-07 14:04:12 +00:00
$server_host_key_algorithm = self :: array_intersect_first ( $server_host_key_algorithms , $this -> server_host_key_algorithms );
2020-12-07 14:00:44 +00:00
if ( $server_host_key_algorithm === false ) {
2020-12-07 14:04:12 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new NoSupportedAlgorithmsException ( 'No compatible server host key algorithms found' );
2020-12-07 14:00:44 +00:00
}
2020-12-07 14:04:12 +00:00
$mac_algorithm_out = self :: array_intersect_first ( $c2s_mac_algorithms , $this -> mac_algorithms_client_to_server );
if ( $mac_algorithm_out === false ) {
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new NoSupportedAlgorithmsException ( 'No compatible client to server message authentication algorithms found' );
2020-12-07 14:00:44 +00:00
}
2020-12-07 14:04:12 +00:00
$mac_algorithm_in = self :: array_intersect_first ( $s2c_mac_algorithms , $this -> mac_algorithms_server_to_client );
if ( $mac_algorithm_in === false ) {
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new NoSupportedAlgorithmsException ( 'No compatible server to client message authentication algorithms found' );
2020-12-07 14:00:44 +00:00
}
2021-10-27 01:48:46 +00:00
$compression_map = [
2021-12-14 15:34:41 +00:00
'none' => self :: NET_SSH2_COMPRESSION_NONE ,
'zlib' => self :: NET_SSH2_COMPRESSION_ZLIB ,
'zlib@openssh.com' => self :: NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH
2021-10-27 01:48:46 +00:00
];
2020-12-07 14:04:12 +00:00
$compression_algorithm_in = self :: array_intersect_first ( $s2c_compression_algorithms , $this -> compression_algorithms_server_to_client );
2020-12-07 14:00:44 +00:00
if ( $compression_algorithm_in === false ) {
2020-12-07 14:04:12 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new NoSupportedAlgorithmsException ( 'No compatible server to client compression algorithms found' );
}
2021-10-27 01:48:46 +00:00
$this -> decompress = $compression_map [ $compression_algorithm_in ];
2020-12-07 14:04:12 +00:00
$compression_algorithm_out = self :: array_intersect_first ( $c2s_compression_algorithms , $this -> compression_algorithms_client_to_server );
if ( $compression_algorithm_out === false ) {
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new NoSupportedAlgorithmsException ( 'No compatible client to server compression algorithms found' );
}
2021-10-27 01:04:53 +00:00
$this -> compress = $compression_map [ $compression_algorithm_out ];
2020-12-07 14:04:12 +00:00
2019-10-10 04:59:32 +00:00
switch ( $this -> kex_algorithm ) {
case 'diffie-hellman-group15-sha512' :
case 'diffie-hellman-group16-sha512' :
case 'diffie-hellman-group17-sha512' :
case 'diffie-hellman-group18-sha512' :
case 'ecdh-sha2-nistp521' :
$kexHash = new Hash ( 'sha512' );
break ;
case 'ecdh-sha2-nistp384' :
$kexHash = new Hash ( 'sha384' );
break ;
case 'diffie-hellman-group-exchange-sha256' :
case 'diffie-hellman-group14-sha256' :
case 'ecdh-sha2-nistp256' :
case 'curve25519-sha256@libssh.org' :
case 'curve25519-sha256' :
$kexHash = new Hash ( 'sha256' );
break ;
default :
$kexHash = new Hash ( 'sha1' );
}
2015-07-23 01:02:28 +00:00
// Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
2019-10-10 04:59:32 +00:00
2015-07-23 01:02:28 +00:00
$exchange_hash_rfc4419 = '' ;
2015-06-24 22:50:00 +00:00
2019-07-27 22:28:18 +00:00
if ( strpos ( $this -> kex_algorithm , 'curve25519-sha256' ) === 0 || strpos ( $this -> kex_algorithm , 'ecdh-sha2-nistp' ) === 0 ) {
$curve = strpos ( $this -> kex_algorithm , 'curve25519-sha256' ) === 0 ?
'Curve25519' :
substr ( $this -> kex_algorithm , 10 );
$ourPrivate = EC :: createKey ( $curve );
$ourPublicBytes = $ourPrivate -> getPublicKey () -> getEncodedCoordinates ();
2020-01-17 09:59:18 +00:00
$clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT' ;
$serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY' ;
2015-06-24 22:50:00 +00:00
} else {
2019-03-28 18:57:26 +00:00
if ( strpos ( $this -> kex_algorithm , 'diffie-hellman-group-exchange' ) === 0 ) {
2015-07-23 01:02:28 +00:00
$dh_group_sizes_packed = pack (
'NNN' ,
$this -> kex_dh_group_size_min ,
$this -> kex_dh_group_size_preferred ,
$this -> kex_dh_group_size_max
);
$packet = pack (
'Ca*' ,
NET_SSH2_MSG_KEXDH_GEX_REQUEST ,
$dh_group_sizes_packed
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (34)' , 'NET_SSH2_MSG_KEXDH_GEX_REQUEST' );
2015-07-23 01:02:28 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2019-04-02 05:09:19 +00:00
2019-07-27 22:28:18 +00:00
list ( $type , $primeBytes , $gBytes ) = Strings :: unpackSSH2 ( 'Css' , $response );
2015-07-23 01:02:28 +00:00
if ( $type != NET_SSH2_MSG_KEXDH_GEX_GROUP ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_PROTOCOL_ERROR );
2018-12-31 15:06:12 +00:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEX_DH_GEX_GROUP' );
2015-07-23 01:02:28 +00:00
}
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'NET_SSH2_MSG_KEXDH_REPLY' , 'NET_SSH2_MSG_KEXDH_GEX_GROUP' );
2015-07-23 01:02:28 +00:00
$prime = new BigInteger ( $primeBytes , - 256 );
$g = new BigInteger ( $gBytes , - 256 );
2019-04-02 05:09:19 +00:00
$exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings :: packSSH2 (
'ss' ,
2015-07-23 01:02:28 +00:00
$primeBytes ,
$gBytes
);
2019-07-27 22:28:18 +00:00
$params = DH :: createParameters ( $prime , $g );
2020-01-17 09:59:18 +00:00
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT' ;
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY' ;
2015-07-23 01:02:28 +00:00
} else {
2019-07-27 22:28:18 +00:00
$params = DH :: createParameters ( $this -> kex_algorithm );
2020-01-17 09:59:18 +00:00
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT' ;
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY' ;
2015-07-23 01:02:28 +00:00
}
2019-10-10 04:59:32 +00:00
$keyLength = min ( $kexHash -> getLengthInBytes (), max ( $encryptKeyLength , $decryptKeyLength ));
2019-07-27 22:28:18 +00:00
$ourPrivate = DH :: createKey ( $params , 16 * $keyLength ); // 2 * 8 * $keyLength
$ourPublic = $ourPrivate -> getPublicKey () -> toBigInteger ();
$ourPublicBytes = $ourPublic -> toBytes ( true );
}
2007-07-23 05:21:39 +00:00
2020-01-17 10:09:49 +00:00
$data = pack ( 'CNa*' , constant ( $clientKexInitMessage ), strlen ( $ourPublicBytes ), $ourPublicBytes );
2007-07-23 05:21:39 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $data );
2007-07-23 05:21:39 +00:00
2020-01-17 09:59:18 +00:00
switch ( $clientKexInitMessage ) {
case 'NET_SSH2_MSG_KEX_ECDH_INIT' :
2020-01-17 10:09:49 +00:00
$this -> updateLogHistory ( 'NET_SSH2_MSG_KEXDH_INIT' , 'NET_SSH2_MSG_KEX_ECDH_INIT' );
2020-01-17 09:59:18 +00:00
break ;
case 'NET_SSH2_MSG_KEXDH_GEX_INIT' :
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (32)' , 'NET_SSH2_MSG_KEXDH_GEX_INIT' );
2020-01-17 09:37:25 +00:00
}
2007-07-23 05:21:39 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2007-07-23 05:21:39 +00:00
2019-04-02 05:09:19 +00:00
list (
$type ,
$server_public_host_key ,
2019-07-27 22:28:18 +00:00
$theirPublicBytes ,
2019-04-02 05:09:19 +00:00
$this -> signature
) = Strings :: unpackSSH2 ( 'Csss' , $response );
2007-07-23 05:21:39 +00:00
2020-01-17 10:09:49 +00:00
if ( $type != constant ( $serverKexReplyMessage )) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_PROTOCOL_ERROR );
2020-01-17 10:09:49 +00:00
throw new \UnexpectedValueException ( " Expected $serverKexReplyMessage " );
2016-10-23 03:13:17 +00:00
}
2020-01-17 11:42:29 +00:00
switch ( $serverKexReplyMessage ) {
case 'NET_SSH2_MSG_KEX_ECDH_REPLY' :
$this -> updateLogHistory ( 'NET_SSH2_MSG_KEXDH_REPLY' , 'NET_SSH2_MSG_KEX_ECDH_REPLY' );
break ;
case 'NET_SSH2_MSG_KEXDH_GEX_REPLY' :
$this -> updateLogHistory ( 'UNKNOWN (33)' , 'NET_SSH2_MSG_KEXDH_GEX_REPLY' );
}
2007-07-23 05:21:39 +00:00
2019-04-02 05:09:19 +00:00
$this -> server_public_host_key = $server_public_host_key ;
list ( $public_key_format ) = Strings :: unpackSSH2 ( 's' , $server_public_host_key );
2016-10-23 03:13:17 +00:00
if ( strlen ( $this -> signature ) < 4 ) {
2021-05-21 13:49:52 +00:00
throw new \LengthException ( 'The signature needs at least four bytes' );
2016-10-23 03:13:17 +00:00
}
2019-03-30 18:35:16 +00:00
$temp = unpack ( 'Nlength' , substr ( $this -> signature , 0 , 4 ));
$this -> signature_format = substr ( $this -> signature , 4 , $temp [ 'length' ]);
2021-05-21 13:49:52 +00:00
2019-07-27 22:28:18 +00:00
$keyBytes = DH :: computeSecret ( $ourPrivate , $theirPublicBytes );
2020-01-07 06:20:47 +00:00
if (( $keyBytes & " \xFF \x80 " ) === " \x00 \x00 " ) {
$keyBytes = substr ( $keyBytes , 1 );
} elseif (( $keyBytes [ 0 ] & " \x80 " ) === " \x80 " ) {
2019-07-27 22:28:18 +00:00
$keyBytes = " \0 $keyBytes " ;
2015-07-23 01:02:28 +00:00
}
2007-07-23 05:21:39 +00:00
2022-02-17 02:25:59 +00:00
$this -> exchange_hash = Strings :: packSSH2 (
's5' ,
2015-07-15 01:52:31 +00:00
$this -> identifier ,
$this -> server_identifier ,
$kexinit_payload_client ,
$kexinit_payload_server ,
2019-04-02 05:09:19 +00:00
$this -> server_public_host_key
);
2022-02-17 02:25:59 +00:00
$this -> exchange_hash .= $exchange_hash_rfc4419 ;
$this -> exchange_hash .= Strings :: packSSH2 (
's3' ,
2019-07-27 22:28:18 +00:00
$ourPublicBytes ,
$theirPublicBytes ,
2015-07-15 01:52:31 +00:00
$keyBytes
2010-10-24 01:24:30 +00:00
);
2007-07-23 05:21:39 +00:00
2013-06-06 22:38:38 +00:00
$this -> exchange_hash = $kexHash -> hash ( $this -> exchange_hash );
2007-07-23 05:21:39 +00:00
2010-10-24 01:24:30 +00:00
if ( $this -> session_id === false ) {
$this -> session_id = $this -> exchange_hash ;
2007-07-23 05:21:39 +00:00
}
2018-05-27 14:48:44 +00:00
switch ( $server_host_key_algorithm ) {
2019-03-30 18:35:16 +00:00
case 'rsa-sha2-256' :
case 'rsa-sha2-512' :
2018-05-27 14:48:44 +00:00
//case 'ssh-rsa':
$expected_key_format = 'ssh-rsa' ;
2019-03-30 18:35:16 +00:00
break ;
default :
$expected_key_format = $server_host_key_algorithm ;
2018-05-27 14:48:44 +00:00
}
if ( $public_key_format != $expected_key_format || $this -> signature_format != $server_host_key_algorithm ) {
2019-01-17 03:15:11 +00:00
switch ( true ) {
case $this -> signature_format == $server_host_key_algorithm :
case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512' :
case $this -> signature_format != 'ssh-rsa' :
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE );
2021-05-21 13:49:52 +00:00
throw new \RuntimeException ( 'Server Host Key Algorithm Mismatch (' . $this -> signature_format . ' vs ' . $server_host_key_algorithm . ')' );
2019-01-17 03:15:11 +00:00
}
2007-07-23 05:21:39 +00:00
}
2019-04-02 05:09:19 +00:00
$packet = pack ( 'C' , NET_SSH2_MSG_NEWKEYS );
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2007-07-23 05:21:39 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2007-07-23 05:21:39 +00:00
if ( $response === false ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2019-09-11 12:55:29 +00:00
throw new ConnectionClosedException ( 'Connection closed by server' );
2007-07-23 05:21:39 +00:00
}
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2008-05-25 07:28:57 +00:00
if ( $type != NET_SSH2_MSG_NEWKEYS ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_PROTOCOL_ERROR );
2016-04-30 21:23:35 +00:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_NEWKEYS' );
2007-07-23 05:21:39 +00:00
}
2009-05-16 17:09:37 +00:00
$keyBytes = pack ( 'Na*' , strlen ( $keyBytes ), $keyBytes );
2019-03-29 02:45:28 +00:00
$this -> encrypt = self :: encryption_algorithm_to_crypt_instance ( $encrypt );
2009-05-16 17:09:37 +00:00
if ( $this -> encrypt ) {
2020-08-01 02:27:38 +00:00
if ( self :: $crypto_engine ) {
$this -> encrypt -> setPreferredEngine ( self :: $crypto_engine );
2014-10-12 04:26:46 +00:00
}
2016-12-03 20:39:11 +00:00
if ( $this -> encrypt -> getBlockLengthInBytes ()) {
$this -> encrypt_block_size = $this -> encrypt -> getBlockLengthInBytes ();
2015-07-28 09:22:17 +00:00
}
2009-05-16 17:09:37 +00:00
$this -> encrypt -> disablePadding ();
2016-04-30 21:23:35 +00:00
if ( $this -> encrypt -> usesIV ()) {
$iv = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'A' . $this -> session_id );
while ( $this -> encrypt_block_size > strlen ( $iv )) {
2022-02-17 02:25:59 +00:00
$iv .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $iv );
2016-04-30 21:23:35 +00:00
}
$this -> encrypt -> setIV ( substr ( $iv , 0 , $this -> encrypt_block_size ));
2009-05-16 17:09:37 +00:00
}
2019-03-18 11:59:00 +00:00
switch ( $encrypt ) {
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$nonce = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'A' . $this -> session_id );
2022-02-02 03:17:10 +00:00
$this -> encryptFixedPart = substr ( $nonce , 0 , 4 );
2022-01-30 07:36:02 +00:00
$this -> encryptInvocationCounter = substr ( $nonce , 4 , 8 );
2022-02-23 02:48:51 +00:00
// fall-through
2019-03-18 11:59:00 +00:00
case 'chacha20-poly1305@openssh.com' :
break ;
default :
$this -> encrypt -> enableContinuousBuffer ();
2018-12-27 14:31:35 +00:00
}
2013-06-06 22:38:38 +00:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'C' . $this -> session_id );
2009-05-16 17:09:37 +00:00
while ( $encryptKeyLength > strlen ( $key )) {
2022-02-17 02:25:59 +00:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2009-05-16 17:09:37 +00:00
}
2019-03-18 11:59:00 +00:00
switch ( $encrypt ) {
case 'chacha20-poly1305@openssh.com' :
$encryptKeyLength = 32 ;
2019-03-29 02:45:28 +00:00
$this -> lengthEncrypt = self :: encryption_algorithm_to_crypt_instance ( $encrypt );
2019-03-18 11:59:00 +00:00
$this -> lengthEncrypt -> setKey ( substr ( $key , 32 , 32 ));
}
2009-05-16 17:09:37 +00:00
$this -> encrypt -> setKey ( substr ( $key , 0 , $encryptKeyLength ));
2022-01-30 07:36:02 +00:00
$this -> encryptName = $encrypt ;
2009-05-16 17:09:37 +00:00
}
2019-03-29 02:45:28 +00:00
$this -> decrypt = self :: encryption_algorithm_to_crypt_instance ( $decrypt );
2009-05-16 17:09:37 +00:00
if ( $this -> decrypt ) {
2020-08-01 02:27:38 +00:00
if ( self :: $crypto_engine ) {
$this -> decrypt -> setPreferredEngine ( self :: $crypto_engine );
2014-10-12 04:26:46 +00:00
}
2016-12-03 20:39:11 +00:00
if ( $this -> decrypt -> getBlockLengthInBytes ()) {
$this -> decrypt_block_size = $this -> decrypt -> getBlockLengthInBytes ();
2015-07-28 09:22:17 +00:00
}
2009-05-16 17:09:37 +00:00
$this -> decrypt -> disablePadding ();
2016-04-30 21:23:35 +00:00
if ( $this -> decrypt -> usesIV ()) {
$iv = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'B' . $this -> session_id );
while ( $this -> decrypt_block_size > strlen ( $iv )) {
2022-02-17 02:25:59 +00:00
$iv .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $iv );
2016-04-30 21:23:35 +00:00
}
$this -> decrypt -> setIV ( substr ( $iv , 0 , $this -> decrypt_block_size ));
2009-05-16 17:09:37 +00:00
}
2007-07-23 05:21:39 +00:00
2019-03-29 23:44:31 +00:00
switch ( $decrypt ) {
2019-03-18 11:59:00 +00:00
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
// see https://tools.ietf.org/html/rfc5647#section-7.1
$nonce = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'B' . $this -> session_id );
2022-02-02 03:17:10 +00:00
$this -> decryptFixedPart = substr ( $nonce , 0 , 4 );
2022-01-30 07:36:02 +00:00
$this -> decryptInvocationCounter = substr ( $nonce , 4 , 8 );
2022-02-23 02:48:51 +00:00
// fall-through
2019-03-18 11:59:00 +00:00
case 'chacha20-poly1305@openssh.com' :
break ;
default :
$this -> decrypt -> enableContinuousBuffer ();
2018-12-27 14:31:35 +00:00
}
2013-06-06 22:38:38 +00:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'D' . $this -> session_id );
2009-05-16 17:09:37 +00:00
while ( $decryptKeyLength > strlen ( $key )) {
2022-02-17 02:25:59 +00:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2009-05-16 17:09:37 +00:00
}
2019-03-18 11:59:00 +00:00
switch ( $decrypt ) {
case 'chacha20-poly1305@openssh.com' :
$decryptKeyLength = 32 ;
2019-03-29 02:45:28 +00:00
$this -> lengthDecrypt = self :: encryption_algorithm_to_crypt_instance ( $decrypt );
2019-03-18 11:59:00 +00:00
$this -> lengthDecrypt -> setKey ( substr ( $key , 32 , 32 ));
}
2009-05-16 17:09:37 +00:00
$this -> decrypt -> setKey ( substr ( $key , 0 , $decryptKeyLength ));
2022-01-30 07:36:02 +00:00
$this -> decryptName = $decrypt ;
2009-05-16 17:09:37 +00:00
}
2007-07-23 05:21:39 +00:00
2010-02-09 06:10:26 +00:00
/* The " arcfour128 " algorithm is the RC4 cipher , as described in
[ SCHNEIER ], using a 128 - bit key . The first 1536 bytes of keystream
generated by the cipher MUST be discarded , and the first byte of the
first encrypted packet MUST be encrypted using the 1537 th byte of
keystream .
-- http :// tools . ietf . org / html / rfc4345 #section-4 */
if ( $encrypt == 'arcfour128' || $encrypt == 'arcfour256' ) {
$this -> encrypt -> encrypt ( str_repeat ( " \0 " , 1536 ));
}
if ( $decrypt == 'arcfour128' || $decrypt == 'arcfour256' ) {
$this -> decrypt -> decrypt ( str_repeat ( " \0 " , 1536 ));
}
2019-09-11 04:01:26 +00:00
if ( ! $this -> encrypt -> usesNonce ()) {
2020-12-07 14:04:12 +00:00
list ( $this -> hmac_create , $createKeyLength ) = self :: mac_algorithm_to_hash_instance ( $mac_algorithm_out );
2019-09-11 04:01:26 +00:00
} else {
2022-02-17 02:25:59 +00:00
$this -> hmac_create = new \stdClass ();
2022-01-30 07:52:31 +00:00
$this -> hmac_create_name = $mac_algorithm_out ;
2020-12-07 14:04:12 +00:00
//$mac_algorithm_out = 'none';
2019-09-11 04:01:26 +00:00
$createKeyLength = 0 ;
2007-07-23 05:21:39 +00:00
}
2019-03-29 02:45:28 +00:00
if ( $this -> hmac_create instanceof Hash ) {
2018-12-27 14:31:35 +00:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'E' . $this -> session_id );
while ( $createKeyLength > strlen ( $key )) {
2022-02-17 02:25:59 +00:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2018-12-27 14:31:35 +00:00
}
$this -> hmac_create -> setKey ( substr ( $key , 0 , $createKeyLength ));
2022-01-30 07:52:31 +00:00
$this -> hmac_create_name = $mac_algorithm_out ;
$this -> hmac_create_etm = preg_match ( '#-etm@openssh\.com$#' , $mac_algorithm_out );
2007-07-23 05:21:39 +00:00
}
2019-09-11 04:01:26 +00:00
if ( ! $this -> decrypt -> usesNonce ()) {
2020-12-07 14:04:12 +00:00
list ( $this -> hmac_check , $checkKeyLength ) = self :: mac_algorithm_to_hash_instance ( $mac_algorithm_in );
2019-09-11 04:43:09 +00:00
$this -> hmac_size = $this -> hmac_check -> getLengthInBytes ();
2019-09-11 04:01:26 +00:00
} else {
2022-02-17 02:25:59 +00:00
$this -> hmac_check = new \stdClass ();
2022-01-30 07:52:31 +00:00
$this -> hmac_check_name = $mac_algorithm_in ;
2020-12-07 14:04:12 +00:00
//$mac_algorithm_in = 'none';
2019-09-11 04:01:26 +00:00
$checkKeyLength = 0 ;
$this -> hmac_size = 0 ;
2007-07-23 05:21:39 +00:00
}
2019-03-29 02:45:28 +00:00
if ( $this -> hmac_check instanceof Hash ) {
2018-12-27 14:31:35 +00:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'F' . $this -> session_id );
while ( $checkKeyLength > strlen ( $key )) {
2022-02-17 02:25:59 +00:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2018-12-27 14:31:35 +00:00
}
$this -> hmac_check -> setKey ( substr ( $key , 0 , $checkKeyLength ));
2022-01-30 07:52:31 +00:00
$this -> hmac_check_name = $mac_algorithm_in ;
$this -> hmac_check_etm = preg_match ( '#-etm@openssh\.com$#' , $mac_algorithm_in );
2009-05-16 17:09:37 +00:00
}
2009-05-30 16:40:31 +00:00
2021-10-27 01:04:53 +00:00
$this -> regenerate_compression_context = $this -> regenerate_decompression_context = true ;
2009-05-30 16:40:31 +00:00
return true ;
2007-07-23 05:21:39 +00:00
}
2015-07-24 10:31:50 +00:00
/**
* Maps an encryption algorithm name to the number of key bytes .
*
2016-04-10 16:30:59 +00:00
* @ param string $algorithm Name of the encryption algorithm
* @ return int | null Number of bytes as an integer or null for unknown
2015-07-24 10:31:50 +00:00
*/
2017-01-08 01:51:56 +00:00
private function encryption_algorithm_to_key_size ( $algorithm )
2015-07-24 10:31:50 +00:00
{
2018-12-30 02:54:18 +00:00
if ( $this -> bad_key_size_fix && self :: bad_algorithm_candidate ( $algorithm )) {
2017-08-27 00:50:17 +00:00
return 16 ;
}
2015-07-24 10:31:50 +00:00
switch ( $algorithm ) {
case 'none' :
return 0 ;
2018-12-27 14:31:35 +00:00
case 'aes128-gcm@openssh.com' :
2015-07-24 10:31:50 +00:00
case 'aes128-cbc' :
case 'aes128-ctr' :
case 'arcfour' :
case 'arcfour128' :
case 'blowfish-cbc' :
case 'blowfish-ctr' :
case 'twofish128-cbc' :
case 'twofish128-ctr' :
return 16 ;
case '3des-cbc' :
case '3des-ctr' :
case 'aes192-cbc' :
case 'aes192-ctr' :
case 'twofish192-cbc' :
case 'twofish192-ctr' :
return 24 ;
2018-12-27 14:31:35 +00:00
case 'aes256-gcm@openssh.com' :
2015-07-24 10:31:50 +00:00
case 'aes256-cbc' :
case 'aes256-ctr' :
case 'arcfour256' :
case 'twofish-cbc' :
case 'twofish256-cbc' :
case 'twofish256-ctr' :
return 32 ;
2019-03-18 11:59:00 +00:00
case 'chacha20-poly1305@openssh.com' :
return 64 ;
2015-07-24 10:31:50 +00:00
}
return null ;
}
2015-07-28 09:22:17 +00:00
/**
* Maps an encryption algorithm name to an instance of a subclass of
2019-11-07 05:41:40 +00:00
* \phpseclib3\Crypt\Common\SymmetricKey .
2015-07-28 09:22:17 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param string $algorithm Name of the encryption algorithm
2022-01-22 18:03:07 +00:00
* @ return SymmetricKey | null
2015-07-28 09:22:17 +00:00
*/
2019-03-29 02:45:28 +00:00
private static function encryption_algorithm_to_crypt_instance ( $algorithm )
2015-07-28 09:22:17 +00:00
{
switch ( $algorithm ) {
case '3des-cbc' :
2017-06-28 03:34:36 +00:00
return new TripleDES ( 'cbc' );
2015-07-28 09:22:17 +00:00
case '3des-ctr' :
2017-06-28 03:34:36 +00:00
return new TripleDES ( 'ctr' );
2015-07-28 09:22:17 +00:00
case 'aes256-cbc' :
case 'aes192-cbc' :
case 'aes128-cbc' :
2017-06-28 03:34:36 +00:00
return new Rijndael ( 'cbc' );
2015-07-28 09:22:17 +00:00
case 'aes256-ctr' :
case 'aes192-ctr' :
case 'aes128-ctr' :
2017-06-28 03:34:36 +00:00
return new Rijndael ( 'ctr' );
2015-07-28 09:22:17 +00:00
case 'blowfish-cbc' :
2017-06-28 03:34:36 +00:00
return new Blowfish ( 'cbc' );
2015-07-28 09:22:17 +00:00
case 'blowfish-ctr' :
2017-06-28 03:34:36 +00:00
return new Blowfish ( 'ctr' );
2015-07-28 09:22:17 +00:00
case 'twofish128-cbc' :
case 'twofish192-cbc' :
case 'twofish256-cbc' :
case 'twofish-cbc' :
2017-06-28 03:34:36 +00:00
return new Twofish ( 'cbc' );
2015-07-28 09:22:17 +00:00
case 'twofish128-ctr' :
case 'twofish192-ctr' :
case 'twofish256-ctr' :
2017-06-28 03:34:36 +00:00
return new Twofish ( 'ctr' );
2015-07-28 09:22:17 +00:00
case 'arcfour' :
case 'arcfour128' :
case 'arcfour256' :
return new RC4 ();
2018-12-27 14:31:35 +00:00
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
2019-03-24 01:53:20 +00:00
return new Rijndael ( 'gcm' );
2019-03-18 11:59:00 +00:00
case 'chacha20-poly1305@openssh.com' :
return new ChaCha20 ();
2015-07-28 09:22:17 +00:00
}
return null ;
}
2019-09-11 04:01:26 +00:00
/**
* Maps an encryption algorithm name to an instance of a subclass of
2019-11-07 05:41:40 +00:00
* \phpseclib3\Crypt\Hash .
2019-09-11 04:01:26 +00:00
*
* @ param string $algorithm Name of the encryption algorithm
2022-01-22 18:03:07 +00:00
* @ return array { Hash , int } | null
2019-09-11 04:01:26 +00:00
*/
private static function mac_algorithm_to_hash_instance ( $algorithm )
{
switch ( $algorithm ) {
case 'umac-64@openssh.com' :
case 'umac-64-etm@openssh.com' :
return [ new Hash ( 'umac-64' ), 16 ];
case 'umac-128@openssh.com' :
case 'umac-128-etm@openssh.com' :
return [ new Hash ( 'umac-128' ), 16 ];
case 'hmac-sha2-512' :
case 'hmac-sha2-512-etm@openssh.com' :
return [ new Hash ( 'sha512' ), 64 ];
case 'hmac-sha2-256' :
case 'hmac-sha2-256-etm@openssh.com' :
return [ new Hash ( 'sha256' ), 32 ];
case 'hmac-sha1' :
case 'hmac-sha1-etm@openssh.com' :
return [ new Hash ( 'sha1' ), 20 ];
case 'hmac-sha1-96' :
return [ new Hash ( 'sha1-96' ), 20 ];
case 'hmac-md5' :
return [ new Hash ( 'md5' ), 16 ];
case 'hmac-md5-96' :
return [ new Hash ( 'md5-96' ), 16 ];
}
}
2017-08-27 07:43:31 +00:00
/*
2017-08-27 00:50:17 +00:00
* Tests whether or not proposed algorithm has a potential for issues
*
* @ link https :// www . chiark . greenend . org . uk /~ sgtatham / putty / wishlist / ssh2 - aesctr - openssh . html
* @ link https :// bugzilla . mindrot . org / show_bug . cgi ? id = 1291
* @ param string $algorithm Name of the encryption algorithm
* @ return bool
*/
2018-12-30 02:54:18 +00:00
private static function bad_algorithm_candidate ( $algorithm )
2017-08-27 00:50:17 +00:00
{
switch ( $algorithm ) {
case 'arcfour256' :
case 'aes192-ctr' :
case 'aes256-ctr' :
return true ;
}
return false ;
}
2007-07-23 05:21:39 +00:00
/**
* Login
*
2021-03-16 11:55:20 +00:00
* The $password parameter can be a plaintext password , a \phpseclib3\Crypt\RSA | EC | DSA object , a \phpseclib3\System\SSH\Agent object or an array
2010-08-28 17:26:22 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2021-03-16 11:55:20 +00:00
* @ param string | AsymmetricKey | array [] | Agent | null ... $args
2016-04-10 16:30:59 +00:00
* @ return bool
* @ see self :: _login ()
2013-04-20 19:33:07 +00:00
*/
2017-11-21 08:36:28 +00:00
public function login ( $username , ... $args )
2013-12-23 00:14:49 +00:00
{
2020-03-10 01:02:17 +00:00
$this -> auth [] = func_get_args ();
2020-03-08 03:34:38 +00:00
// try logging with 'none' as an authentication method first since that's what
// PuTTY does
2021-10-09 12:50:10 +00:00
if ( substr ( $this -> server_identifier , 0 , 15 ) != 'SSH-2.0-CoreFTP' && $this -> auth_methods_to_continue === null ) {
2020-07-31 09:11:26 +00:00
if ( $this -> sublogin ( $username )) {
2020-07-31 07:49:21 +00:00
return true ;
}
2020-07-31 09:11:26 +00:00
if ( ! count ( $args )) {
2020-07-31 07:49:21 +00:00
return false ;
}
2020-03-08 03:34:38 +00:00
}
2017-11-21 08:36:28 +00:00
return $this -> sublogin ( $username , ... $args );
2013-12-23 00:14:49 +00:00
}
/**
* Login Helper
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2022-01-28 19:14:38 +00:00
* @ param string ... $args
2016-04-10 16:30:59 +00:00
* @ return bool
* @ see self :: _login_helper ()
2013-12-23 00:14:49 +00:00
*/
2017-11-21 08:36:28 +00:00
protected function sublogin ( $username , ... $args )
2013-04-20 19:33:07 +00:00
{
2014-12-04 21:45:13 +00:00
if ( ! ( $this -> bitmap & self :: MASK_CONSTRUCTOR )) {
2021-05-21 13:49:52 +00:00
$this -> connect ();
2014-04-08 13:48:12 +00:00
}
2013-04-20 19:33:07 +00:00
if ( empty ( $args )) {
2017-01-08 01:51:56 +00:00
return $this -> login_helper ( $username );
2013-04-20 19:33:07 +00:00
}
2013-04-20 19:35:08 +00:00
2021-12-08 04:10:29 +00:00
foreach ( $args as $arg ) {
switch ( true ) {
case $arg instanceof PublicKey :
throw new \UnexpectedValueException ( 'A PublicKey object was passed to the login method instead of a PrivateKey object' );
case $arg instanceof PrivateKey :
case $arg instanceof Agent :
case is_array ( $arg ) :
2021-12-08 04:20:55 +00:00
case Strings :: is_stringable ( $arg ) :
2021-12-08 04:10:29 +00:00
break ;
default :
throw new \UnexpectedValueException ( '$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string' );
}
}
2021-11-04 03:16:14 +00:00
while ( count ( $args )) {
if ( ! $this -> auth_methods_to_continue || ! $this -> smartMFA ) {
$newargs = $args ;
2021-11-04 03:28:16 +00:00
$args = [];
2021-11-04 03:16:14 +00:00
} else {
2021-11-04 03:28:16 +00:00
$newargs = [];
2021-11-04 03:16:14 +00:00
foreach ( $this -> auth_methods_to_continue as $method ) {
switch ( $method ) {
case 'publickey' :
foreach ( $args as $key => $arg ) {
2021-11-04 03:28:16 +00:00
if ( $arg instanceof PrivateKey || $arg instanceof Agent ) {
2021-11-04 03:16:14 +00:00
$newargs [] = $arg ;
unset ( $args [ $key ]);
break ;
}
}
break ;
case 'keyboard-interactive' :
$hasArray = $hasString = false ;
foreach ( $args as $arg ) {
if ( $hasArray || is_array ( $arg )) {
$hasArray = true ;
break ;
}
2021-12-08 04:20:55 +00:00
if ( $hasString || Strings :: is_stringable ( $arg )) {
2021-11-04 03:16:14 +00:00
$hasString = true ;
break ;
}
}
if ( $hasArray && $hasString ) {
foreach ( $args as $key => $arg ) {
if ( is_array ( $arg )) {
$newargs [] = $arg ;
break 2 ;
}
}
}
2022-02-23 02:48:51 +00:00
// fall-through
2021-11-04 03:16:14 +00:00
case 'password' :
foreach ( $args as $key => $arg ) {
$newargs [] = $arg ;
unset ( $args [ $key ]);
break ;
}
}
}
}
2021-12-08 03:57:55 +00:00
if ( ! count ( $newargs )) {
return false ;
}
2021-11-04 03:16:14 +00:00
foreach ( $newargs as $arg ) {
2021-11-04 03:28:16 +00:00
if ( $this -> login_helper ( $username , $arg )) {
2021-11-04 03:16:14 +00:00
return true ;
}
2013-04-20 19:33:07 +00:00
}
}
return false ;
}
/**
* Login Helper
*
2020-12-30 11:05:54 +00:00
* { @ internal It might be worthwhile , at some point , to protect against { @ link http :// tools . ietf . org / html / rfc4251 #section-9.3.9 traffic analysis}
* by sending dummy SSH_MSG_IGNORE messages . }
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2021-03-16 11:55:20 +00:00
* @ param string | AsymmetricKey | array [] | Agent | null ... $args
2016-04-10 16:30:59 +00:00
* @ return bool
2016-04-30 21:23:35 +00:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
private function login_helper ( $username , $password = null )
2007-07-23 05:21:39 +00:00
{
2014-12-04 21:45:13 +00:00
if ( ! ( $this -> bitmap & self :: MASK_CONNECTED )) {
2007-07-23 05:21:39 +00:00
return false ;
}
2014-12-04 21:45:13 +00:00
if ( ! ( $this -> bitmap & self :: MASK_LOGIN_REQ )) {
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 ( 'Cs' , NET_SSH2_MSG_SERVICE_REQUEST , 'ssh-userauth' );
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2007-07-23 05:21:39 +00:00
2021-05-21 13:49:52 +00:00
try {
$response = $this -> get_binary_packet ();
} catch ( \Exception $e ) {
2017-08-27 00:50:17 +00:00
if ( $this -> retry_connect ) {
$this -> retry_connect = false ;
2021-05-21 13:49:52 +00:00
$this -> connect ();
2017-08-27 17:33:19 +00:00
return $this -> login_helper ( $username , $password );
2017-08-27 00:50:17 +00:00
}
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2019-09-11 12:55:29 +00:00
throw new ConnectionClosedException ( 'Connection closed by server' );
2013-04-20 03:23:06 +00:00
}
2007-07-23 05:21:39 +00:00
2019-04-02 05:09:19 +00:00
list ( $type , $service ) = Strings :: unpackSSH2 ( 'Cs' , $response );
if ( $type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth' ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_PROTOCOL_ERROR );
2016-04-30 21:23:35 +00:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_SERVICE_ACCEPT' );
2013-04-20 03:23:06 +00:00
}
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN_REQ ;
2013-04-20 03:23:06 +00:00
}
if ( strlen ( $this -> last_interactive_response )) {
2021-12-08 04:20:55 +00:00
return ! Strings :: is_stringable ( $password ) && ! is_array ( $password ) ? false : $this -> keyboard_interactive_process ( $password );
2007-07-23 05:21:39 +00:00
}
2019-05-19 20:35:29 +00:00
if ( $password instanceof PrivateKey ) {
2017-01-08 01:51:56 +00:00
return $this -> privatekey_login ( $username , $password );
2019-05-19 20:35:29 +00:00
}
if ( $password instanceof Agent ) {
2017-01-08 01:51:56 +00:00
return $this -> ssh_agent_login ( $username , $password );
2009-12-03 08:19:00 +00:00
}
2013-04-20 19:35:08 +00:00
if ( is_array ( $password )) {
2017-01-08 01:51:56 +00:00
if ( $this -> keyboard_interactive_login ( $username , $password )) {
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2013-04-20 19:35:08 +00:00
return true ;
}
return false ;
}
2012-11-22 20:25:57 +00:00
if ( ! isset ( $password )) {
2022-02-17 02:25:59 +00:00
$packet = Strings :: packSSH2 (
'Cs3' ,
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'none'
2012-11-22 20:25:57 +00:00
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2012-11-22 20:25:57 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2012-11-22 20:25:57 +00:00
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2012-11-22 20:25:57 +00:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2012-11-22 20:25:57 +00:00
return true ;
2021-04-29 21:22:24 +00:00
case NET_SSH2_MSG_USERAUTH_FAILURE :
list ( $auth_methods ) = Strings :: unpackSSH2 ( 'L' , $response );
$this -> auth_methods_to_continue = $auth_methods ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-22 20:25:57 +00:00
default :
return false ;
}
}
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'Cs3bs' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'password' ,
2019-04-02 05:09:19 +00:00
false ,
2015-07-15 01:52:31 +00:00
$password
2007-07-23 05:21:39 +00:00
);
2013-09-12 13:45:13 +00:00
// remove the username and password from the logged packet
if ( ! defined ( 'NET_SSH2_LOGGING' )) {
2013-12-03 17:54:43 +00:00
$logged = null ;
2013-09-12 13:45:13 +00:00
} else {
2019-04-02 05:09:19 +00:00
$logged = Strings :: packSSH2 (
'Cs3bs' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
2019-04-02 05:09:19 +00:00
$username ,
2015-07-15 01:52:31 +00:00
'ssh-connection' ,
'password' ,
2019-04-02 05:09:19 +00:00
false ,
2015-07-15 01:52:31 +00:00
'password'
2009-05-23 14:42:17 +00:00
);
2013-09-12 13:45:13 +00:00
}
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet , $logged );
2009-05-23 14:42:17 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2022-05-04 06:18:33 +00:00
if ( $response === false ) {
return false ;
}
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2007-07-23 05:21:39 +00:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ : // in theory, the password can be changed
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (60)' , 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' );
2016-11-30 01:35:50 +00:00
2019-04-02 05:09:19 +00:00
list ( $message ) = Strings :: unpackSSH2 ( 's' , $response );
$this -> errors [] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message ;
2016-11-30 01:35:50 +00:00
2017-01-08 01:51:56 +00:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER );
2009-12-03 08:19:00 +00:00
case NET_SSH2_MSG_USERAUTH_FAILURE :
2010-08-28 17:26:22 +00:00
// can we use keyboard-interactive authentication? if not then either the login is bad or the server employees
// multi-factor authentication
2019-04-02 05:09:19 +00:00
list ( $auth_methods , $partial_success ) = Strings :: unpackSSH2 ( 'Lb' , $response );
2021-04-29 21:22:24 +00:00
$this -> auth_methods_to_continue = $auth_methods ;
2013-04-20 03:23:06 +00:00
if ( ! $partial_success && in_array ( 'keyboard-interactive' , $auth_methods )) {
2017-01-08 01:51:56 +00:00
if ( $this -> keyboard_interactive_login ( $username , $password )) {
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2010-09-12 22:12:45 +00:00
return true ;
}
return false ;
}
return false ;
2009-12-03 08:19:00 +00:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2009-12-03 08:19:00 +00:00
return true ;
}
return false ;
}
2010-08-28 17:26:22 +00:00
/**
* Login via keyboard - interactive authentication
*
* See { @ link http :// tools . ietf . org / html / rfc4256 RFC4256 } for details . This is not a full - featured keyboard - interactive authenticator .
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2022-01-28 19:14:38 +00:00
* @ param string | array $password
2016-04-10 16:30:59 +00:00
* @ return bool
2010-08-28 17:26:22 +00:00
*/
2017-01-08 01:51:56 +00:00
private function keyboard_interactive_login ( $username , $password )
2010-08-28 17:26:22 +00:00
{
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'Cs5' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'keyboard-interactive' ,
2019-04-02 05:09:19 +00:00
'' , // language tag
'' // submethods
2010-08-28 17:26:22 +00:00
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2010-08-28 17:26:22 +00:00
2017-01-08 01:51:56 +00:00
return $this -> keyboard_interactive_process ( $password );
2010-08-28 17:26:22 +00:00
}
/**
* Handle the keyboard - interactive requests / responses .
*
2022-01-28 19:14:38 +00:00
* @ param string | array ... $responses
2016-04-10 16:30:59 +00:00
* @ return bool
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2010-08-28 17:26:22 +00:00
*/
2017-11-21 08:36:28 +00:00
private function keyboard_interactive_process ( ... $responses )
2010-08-28 17:26:22 +00:00
{
2013-04-20 03:23:06 +00:00
if ( strlen ( $this -> last_interactive_response )) {
$response = $this -> last_interactive_response ;
} else {
2017-01-08 01:51:56 +00:00
$orig = $response = $this -> get_binary_packet ();
2010-08-28 17:26:22 +00:00
}
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2010-08-28 17:26:22 +00:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_INFO_REQUEST :
2019-04-02 05:09:19 +00:00
list (
, // name; may be empty
, // instruction; may be empty
, // language tag; may be empty
$num_prompts
) = Strings :: unpackSSH2 ( 's3N' , $response );
2013-04-20 19:35:08 +00:00
for ( $i = 0 ; $i < count ( $responses ); $i ++ ) {
if ( is_array ( $responses [ $i ])) {
foreach ( $responses [ $i ] as $key => $value ) {
$this -> keyboard_requests_responses [ $key ] = $value ;
}
unset ( $responses [ $i ]);
}
}
$responses = array_values ( $responses );
if ( isset ( $this -> keyboard_requests_responses )) {
for ( $i = 0 ; $i < $num_prompts ; $i ++ ) {
2019-04-02 05:09:19 +00:00
list (
$prompt , // prompt - ie. "Password: "; must not be empty
// echo
) = Strings :: unpackSSH2 ( 'sC' , $response );
2013-04-20 19:35:08 +00:00
foreach ( $this -> keyboard_requests_responses as $key => $value ) {
if ( substr ( $prompt , 0 , strlen ( $key )) == $key ) {
$responses [] = $value ;
break ;
}
}
}
2010-08-28 17:26:22 +00:00
}
2013-04-20 03:23:06 +00:00
// see http://tools.ietf.org/html/rfc4256#section-3.2
if ( strlen ( $this -> last_interactive_response )) {
$this -> last_interactive_response = '' ;
2020-01-17 09:37:25 +00:00
} else {
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (60)' , 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST' );
2013-04-20 03:23:06 +00:00
}
if ( ! count ( $responses ) && $num_prompts ) {
$this -> last_interactive_response = $orig ;
return false ;
}
2010-08-28 17:26:22 +00:00
/*
After obtaining the requested information from the user , the client
MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message .
*/
// see http://tools.ietf.org/html/rfc4256#section-3.4
2010-08-29 03:27:02 +00:00
$packet = $logged = pack ( 'CN' , NET_SSH2_MSG_USERAUTH_INFO_RESPONSE , count ( $responses ));
2010-08-28 17:26:22 +00:00
for ( $i = 0 ; $i < count ( $responses ); $i ++ ) {
2022-02-17 02:25:59 +00:00
$packet .= Strings :: packSSH2 ( 's' , $responses [ $i ]);
$logged .= Strings :: packSSH2 ( 's' , 'dummy-answer' );
2010-08-28 17:26:22 +00:00
}
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet , $logged );
2010-08-28 17:26:22 +00:00
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (61)' , 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE' );
2010-08-28 17:26:22 +00:00
/*
After receiving the response , the server MUST send either an
SSH_MSG_USERAUTH_SUCCESS , SSH_MSG_USERAUTH_FAILURE , or another
SSH_MSG_USERAUTH_INFO_REQUEST message .
*/
// maybe phpseclib should force close the connection after x request / responses? unless something like that is done
// there could be an infinite loop of request / responses.
2017-01-08 01:51:56 +00:00
return $this -> keyboard_interactive_process ();
2010-08-28 17:26:22 +00:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
return true ;
case NET_SSH2_MSG_USERAUTH_FAILURE :
2021-04-29 21:22:24 +00:00
list ( $auth_methods ) = Strings :: unpackSSH2 ( 'L' , $response );
$this -> auth_methods_to_continue = $auth_methods ;
2010-08-28 17:26:22 +00:00
return false ;
}
return false ;
}
2014-02-10 06:04:16 +00:00
/**
* Login with an ssh - agent provided key
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2019-11-07 05:41:40 +00:00
* @ param \phpseclib3\System\SSH\Agent $agent
2016-04-10 16:30:59 +00:00
* @ return bool
2014-02-10 06:04:16 +00:00
*/
2019-05-19 20:35:29 +00:00
private function ssh_agent_login ( $username , Agent $agent )
2014-02-10 06:04:16 +00:00
{
2014-12-30 02:44:31 +00:00
$this -> agent = $agent ;
2014-02-10 06:04:16 +00:00
$keys = $agent -> requestIdentities ();
foreach ( $keys as $key ) {
2017-01-08 01:51:56 +00:00
if ( $this -> privatekey_login ( $username , $key )) {
2014-02-10 06:04:16 +00:00
return true ;
}
}
return false ;
}
2009-12-03 08:19:00 +00:00
/**
* Login with an RSA private key
*
2020-12-30 11:05:54 +00:00
* { @ internal It might be worthwhile , at some point , to protect against { @ link http :// tools . ietf . org / html / rfc4251 #section-9.3.9 traffic analysis}
* by sending dummy SSH_MSG_IGNORE messages . }
*
2016-04-10 16:30:59 +00:00
* @ param string $username
2019-11-07 05:41:40 +00:00
* @ param \phpseclib3\Crypt\Common\PrivateKey $privatekey
2016-04-10 16:30:59 +00:00
* @ return bool
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2009-12-03 08:19:00 +00:00
*/
2019-05-19 20:35:29 +00:00
private function privatekey_login ( $username , PrivateKey $privatekey )
2009-12-03 08:19:00 +00:00
{
2019-05-19 20:35:29 +00:00
$publickey = $privatekey -> getPublicKey ();
if ( $publickey instanceof RSA ) {
$privatekey = $privatekey -> withPadding ( RSA :: SIGNATURE_PKCS1 );
2020-02-25 01:20:00 +00:00
$algos = [ 'rsa-sha2-256' , 'rsa-sha2-512' , 'ssh-rsa' ];
if ( isset ( $this -> preferred [ 'hostkey' ])) {
2022-02-17 02:25:59 +00:00
$algos = array_intersect ( $this -> preferred [ 'hostkey' ], $algos );
2020-02-25 01:20:00 +00:00
}
$algo = self :: array_intersect_first ( $algos , $this -> server_host_key_algorithms );
switch ( $algo ) {
2019-05-19 20:35:29 +00:00
case 'rsa-sha2-512' :
$hash = 'sha512' ;
$signatureType = 'rsa-sha2-512' ;
break ;
case 'rsa-sha2-256' :
$hash = 'sha256' ;
$signatureType = 'rsa-sha2-256' ;
break ;
//case 'ssh-rsa':
default :
$hash = 'sha1' ;
$signatureType = 'ssh-rsa' ;
}
2022-02-17 02:25:59 +00:00
} elseif ( $publickey instanceof EC ) {
2019-05-19 20:35:29 +00:00
$privatekey = $privatekey -> withSignatureFormat ( 'SSH2' );
$curveName = $privatekey -> getCurve ();
switch ( $curveName ) {
case 'Ed25519' :
$hash = 'sha512' ;
$signatureType = 'ssh-ed25519' ;
break ;
case 'secp256r1' : // nistp256
$hash = 'sha256' ;
$signatureType = 'ecdsa-sha2-nistp256' ;
break ;
case 'secp384r1' : // nistp384
$hash = 'sha384' ;
$signatureType = 'ecdsa-sha2-nistp384' ;
break ;
case 'secp521r1' : // nistp521
$hash = 'sha512' ;
$signatureType = 'ecdsa-sha2-nistp521' ;
break ;
default :
if ( is_array ( $curveName )) {
throw new UnsupportedCurveException ( 'Specified Curves are not supported by SSH2' );
}
2019-11-07 05:41:40 +00:00
throw new UnsupportedCurveException ( 'Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation' );
2019-05-19 20:35:29 +00:00
}
2022-02-17 02:25:59 +00:00
} elseif ( $publickey instanceof DSA ) {
2019-05-19 20:35:29 +00:00
$privatekey = $privatekey -> withSignatureFormat ( 'SSH2' );
$hash = 'sha1' ;
$signatureType = 'ssh-dss' ;
} else {
2019-06-28 00:10:40 +00:00
throw new UnsupportedAlgorithmException ( 'Please use either an RSA key, an EC one or a DSA key' );
2009-12-06 07:26:52 +00:00
}
2019-06-01 18:23:11 +00:00
$publickeyStr = $publickey -> toString ( 'OpenSSH' , [ 'binary' => true ]);
2019-01-16 05:41:49 +00:00
2019-04-02 05:09:19 +00:00
$part1 = Strings :: packSSH2 (
'Csss' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'publickey'
2009-12-03 08:19:00 +00:00
);
2019-05-19 20:35:29 +00:00
$part2 = Strings :: packSSH2 ( 'ss' , $signatureType , $publickeyStr );
2009-12-03 08:19:00 +00:00
$packet = $part1 . chr ( 0 ) . $part2 ;
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2009-12-03 08:19:00 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2009-12-03 08:19:00 +00:00
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2009-12-03 08:19:00 +00:00
switch ( $type ) {
2007-07-23 05:21:39 +00:00
case NET_SSH2_MSG_USERAUTH_FAILURE :
2021-04-29 21:22:24 +00:00
list ( $auth_methods ) = Strings :: unpackSSH2 ( 'L' , $response );
$this -> auth_methods_to_continue = $auth_methods ;
$this -> errors [] = 'SSH_MSG_USERAUTH_FAILURE' ;
2013-04-20 03:23:06 +00:00
return false ;
2009-12-03 08:19:00 +00:00
case NET_SSH2_MSG_USERAUTH_PK_OK :
// we'll just take it on faith that the public key blob and the public key algorithm name are as
// they should be
2020-01-17 11:42:29 +00:00
$this -> updateLogHistory ( 'UNKNOWN (60)' , 'NET_SSH2_MSG_USERAUTH_PK_OK' );
2021-01-13 05:12:19 +00:00
break ;
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2021-01-13 05:20:47 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2021-01-13 05:12:19 +00:00
return true ;
default :
2021-01-13 05:23:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
throw new ConnectionClosedException ( 'Unexpected response to publickey authentication pt 1' );
2009-12-03 08:19:00 +00:00
}
$packet = $part1 . chr ( 1 ) . $part2 ;
2019-05-19 20:35:29 +00:00
$privatekey = $privatekey -> withHash ( $hash );
$signature = $privatekey -> sign ( Strings :: packSSH2 ( 's' , $this -> session_id ) . $packet );
if ( $publickey instanceof RSA ) {
$signature = Strings :: packSSH2 ( 'ss' , $signatureType , $signature );
}
2022-02-17 02:25:59 +00:00
$packet .= Strings :: packSSH2 ( 's' , $signature );
2009-12-03 08:19:00 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2009-12-03 08:19:00 +00:00
2017-01-08 01:51:56 +00:00
$response = $this -> get_binary_packet ();
2009-12-03 08:19:00 +00:00
2019-04-02 05:09:19 +00:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2009-12-03 08:19:00 +00:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_FAILURE :
2012-08-14 17:07:18 +00:00
// either the login is bad or the server employs multi-factor authentication
2021-04-29 21:22:24 +00:00
list ( $auth_methods ) = Strings :: unpackSSH2 ( 'L' , $response );
$this -> auth_methods_to_continue = $auth_methods ;
2009-12-03 08:19:00 +00:00
return false ;
2007-07-23 05:21:39 +00:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_LOGIN ;
2007-07-23 05:21:39 +00:00
return true ;
}
2021-01-13 05:23:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
throw new ConnectionClosedException ( 'Unexpected response to publickey authentication pt 2' );
2007-07-23 05:21:39 +00:00
}
2012-03-03 17:49:16 +00:00
/**
* Set Timeout
*
* $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 .
* Setting $timeout to false or 0 will mean there is no timeout .
*
2016-04-10 16:30:59 +00:00
* @ param mixed $timeout
2012-03-03 17:49:16 +00:00
*/
2017-01-08 01:51:56 +00:00
public function setTimeout ( $timeout )
2012-03-03 17:49:16 +00:00
{
2012-03-03 19:56:22 +00:00
$this -> timeout = $this -> curTimeout = $timeout ;
2012-03-03 17:49:16 +00:00
}
2020-09-28 11:17:49 +00:00
/**
* Set Keep Alive
*
* Sends an SSH2_MSG_IGNORE message every x seconds , if x is a positive non - zero number .
*
2020-12-19 20:28:29 +00:00
* @ param int $interval
2020-09-28 11:17:49 +00:00
*/
2022-02-17 02:25:59 +00:00
public function setKeepAlive ( $interval )
2020-09-28 11:17:49 +00:00
{
$this -> keepAlive = $interval ;
}
2013-03-21 22:18:31 +00:00
/**
* Get the output from stdError
2013-12-25 23:33:08 +00:00
*
2013-03-21 22:18:31 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getStdError ()
2013-04-27 19:10:36 +00:00
{
2013-03-21 22:18:31 +00:00
return $this -> stdErrorLog ;
}
2007-07-25 21:49:33 +00:00
/**
* Execute Command
*
2019-11-07 05:41:40 +00:00
* If $callback is set to false then \phpseclib3\Net\SSH2 :: get_channel_packet ( self :: CHANNEL_EXEC ) will need to be called manually .
2010-11-30 04:34:31 +00:00
* In all likelihood , this is not a feature you want to be taking advantage of .
*
2016-04-10 16:30:59 +00:00
* @ param string $command
2022-01-28 19:14:38 +00:00
* @ return string | bool
* @ psalm - return ( $callback is callable ? bool : string | bool )
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2007-07-25 21:49:33 +00:00
*/
2022-01-28 19:14:38 +00:00
public function exec ( $command , callable $callback = null )
2007-07-25 21:49:33 +00:00
{
2012-03-03 17:49:16 +00:00
$this -> curTimeout = $this -> timeout ;
2013-06-07 22:21:11 +00:00
$this -> is_timeout = false ;
2013-03-21 22:18:31 +00:00
$this -> stdErrorLog = '' ;
2012-03-03 17:49:16 +00:00
2016-12-16 16:27:56 +00:00
if ( ! $this -> isAuthenticated ()) {
2007-07-25 21:49:33 +00:00
return false ;
}
2016-12-17 22:09:48 +00:00
if ( $this -> in_request_pty_exec ) {
2017-12-23 20:32:22 +00:00
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.' );
2007-07-25 21:49:33 +00:00
}
2009-09-17 03:19:20 +00:00
// RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2013-12-25 23:33:08 +00:00
// be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
2016-07-31 12:41:23 +00:00
// honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
2009-05-16 17:09:37 +00:00
// see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
2014-12-04 21:45:13 +00:00
$this -> window_size_server_to_client [ self :: CHANNEL_EXEC ] = $this -> window_size ;
2009-05-16 17:09:37 +00:00
// 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
2009-09-17 03:19:20 +00:00
// uses 0x4000, that's what will be used here, as well.
2009-05-16 17:09:37 +00:00
$packet_size = 0x4000 ;
2007-07-25 21:49:33 +00:00
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 11:36:18 +00:00
self :: CHANNEL_EXEC ,
$this -> window_size_server_to_client [ self :: CHANNEL_EXEC ],
2015-07-15 01:52:31 +00:00
$packet_size
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2007-07-25 21:49:33 +00:00
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2007-07-25 21:49:33 +00:00
2021-05-09 06:07:09 +00:00
$this -> get_channel_packet ( self :: CHANNEL_EXEC );
2007-07-25 21:49:33 +00:00
2013-02-08 22:04:52 +00:00
if ( $this -> request_pty === true ) {
$terminal_modes = pack ( 'C' , NET_SSH2_TTY_OP_END );
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNsCsN4s' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 11:36:18 +00:00
$this -> server_channels [ self :: CHANNEL_EXEC ],
2015-07-15 01:52:31 +00:00
'pty-req' ,
1 ,
2021-03-17 02:18:56 +00:00
$this -> term ,
2015-07-15 01:52:31 +00:00
$this -> windowColumns ,
$this -> windowRows ,
0 ,
0 ,
$terminal_modes
);
2013-02-08 22:04:52 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2014-12-30 02:44:31 +00:00
2021-10-09 18:31:14 +00:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2021-10-09 18:32:07 +00:00
if ( ! $this -> get_channel_packet ( self :: CHANNEL_EXEC )) {
2021-10-09 18:27:40 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
throw new \RuntimeException ( 'Unable to request pseudo-terminal' );
2013-02-08 22:04:52 +00:00
}
$this -> in_request_pty_exec = true ;
}
2010-03-22 22:01:38 +00:00
// sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2019-11-07 05:41:40 +00:00
// down. the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
2010-05-16 16:10:50 +00:00
// with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2010-03-22 22:01:38 +00:00
// then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but
// neither will your script.
2007-07-25 21:49:33 +00:00
2009-09-17 03:19:20 +00:00
// although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
2013-12-25 23:33:08 +00:00
// SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
2009-09-17 03:19:20 +00:00
// "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNsCs' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 11:36:18 +00:00
$this -> server_channels [ self :: CHANNEL_EXEC ],
2015-07-15 01:52:31 +00:00
'exec' ,
1 ,
$command
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2007-07-25 21:49:33 +00:00
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2009-12-14 18:14:54 +00:00
2021-05-09 06:07:09 +00:00
if ( ! $this -> get_channel_packet ( self :: CHANNEL_EXEC )) {
2007-07-25 21:49:33 +00:00
return false ;
}
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_DATA ;
2007-07-25 21:49:33 +00:00
2013-05-28 22:02:27 +00:00
if ( $callback === false || $this -> in_request_pty_exec ) {
2010-11-30 04:34:31 +00:00
return true ;
}
2007-07-25 21:49:33 +00:00
$output = '' ;
while ( true ) {
2017-01-08 01:51:56 +00:00
$temp = $this -> get_channel_packet ( self :: CHANNEL_EXEC );
2009-05-23 14:42:17 +00:00
switch ( true ) {
case $temp === true :
2013-05-28 22:02:27 +00:00
return is_callable ( $callback ) ? true : $output ;
2009-05-23 14:42:17 +00:00
case $temp === false :
return false ;
2007-07-25 21:49:33 +00:00
default :
2013-05-28 22:02:27 +00:00
if ( is_callable ( $callback )) {
2020-04-05 15:56:30 +00:00
if ( $callback ( $temp ) === true ) {
2017-01-08 01:51:56 +00:00
$this -> close_channel ( self :: CHANNEL_EXEC );
2014-05-26 22:37:32 +00:00
return true ;
}
2013-05-28 22:02:27 +00:00
} else {
2022-02-17 02:25:59 +00:00
$output .= $temp ;
2013-05-28 22:02:27 +00:00
}
2007-07-25 21:49:33 +00:00
}
}
}
2011-02-28 05:24:09 +00:00
/**
* Creates an interactive shell
*
2016-04-10 16:30:59 +00:00
* @ see self :: read ()
* @ see self :: write ()
* @ return bool
2016-04-30 21:23:35 +00:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2011-02-28 05:24:09 +00:00
*/
2017-01-08 01:51:56 +00:00
private function initShell ()
2011-02-28 05:24:09 +00:00
{
2013-02-08 22:04:52 +00:00
if ( $this -> in_request_pty_exec === true ) {
return true ;
}
2014-12-04 21:45:13 +00:00
$this -> window_size_server_to_client [ self :: CHANNEL_SHELL ] = $this -> window_size ;
2011-02-28 05:24:09 +00:00
$packet_size = 0x4000 ;
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 11:36:18 +00:00
self :: CHANNEL_SHELL ,
$this -> window_size_server_to_client [ self :: CHANNEL_SHELL ],
2015-07-15 01:52:31 +00:00
$packet_size
);
2011-02-28 05:24:09 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2011-02-28 05:24:09 +00:00
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2011-02-28 05:24:09 +00:00
2021-05-09 06:07:09 +00:00
$this -> get_channel_packet ( self :: CHANNEL_SHELL );
2011-02-28 05:24:09 +00:00
$terminal_modes = pack ( 'C' , NET_SSH2_TTY_OP_END );
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
2021-03-29 13:15:16 +00:00
'CNsbsN4s' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 11:36:18 +00:00
$this -> server_channels [ self :: CHANNEL_SHELL ],
2015-07-15 01:52:31 +00:00
'pty-req' ,
2021-03-27 15:33:46 +00:00
true , // want reply
2021-03-17 02:18:56 +00:00
$this -> term ,
2015-07-15 01:52:31 +00:00
$this -> windowColumns ,
$this -> windowRows ,
0 ,
0 ,
$terminal_modes
);
2011-02-28 05:24:09 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2011-02-28 05:24:09 +00:00
2021-10-11 14:02:22 +00:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2021-10-11 13:47:51 +00:00
2021-10-11 14:15:37 +00:00
if ( ! $this -> get_channel_packet ( self :: CHANNEL_SHELL )) {
throw new \RuntimeException ( 'Unable to request pty' );
2021-10-11 13:47:51 +00:00
}
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNsb' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 11:36:18 +00:00
$this -> server_channels [ self :: CHANNEL_SHELL ],
2015-07-15 01:52:31 +00:00
'shell' ,
2019-04-02 05:09:19 +00:00
true // want reply
2015-07-15 01:52:31 +00:00
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2011-02-28 05:24:09 +00:00
2021-10-11 14:15:37 +00:00
$response = $this -> get_channel_packet ( self :: CHANNEL_SHELL );
2021-10-11 13:47:51 +00:00
if ( $response === false ) {
2021-10-11 14:15:37 +00:00
throw new \RuntimeException ( 'Unable to request shell' );
2021-10-11 13:47:51 +00:00
}
2021-10-11 14:02:22 +00:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_DATA ;
2011-02-28 05:24:09 +00:00
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_SHELL ;
2011-02-28 05:24:09 +00:00
return true ;
}
2013-10-25 17:35:30 +00:00
/**
* Return the channel to be used with read () / write ()
*
2016-04-10 16:30:59 +00:00
* @ see self :: read ()
* @ see self :: write ()
* @ return int
2013-10-25 17:35:30 +00:00
*/
2017-01-08 01:51:56 +00:00
private function get_interactive_channel ()
2013-10-25 17:35:30 +00:00
{
switch ( true ) {
case $this -> in_subsystem :
2014-12-04 21:45:13 +00:00
return self :: CHANNEL_SUBSYSTEM ;
2013-10-25 17:35:30 +00:00
case $this -> in_request_pty_exec :
2014-12-04 21:45:13 +00:00
return self :: CHANNEL_EXEC ;
2013-10-25 17:35:30 +00:00
default :
2014-12-04 21:45:13 +00:00
return self :: CHANNEL_SHELL ;
2013-10-25 17:35:30 +00:00
}
}
2015-02-06 03:28:23 +00:00
/**
* Return an available open channel
*
2016-04-10 16:30:59 +00:00
* @ return int
2015-02-06 03:28:23 +00:00
*/
2017-01-08 01:51:56 +00:00
private function get_open_channel ()
2015-02-06 03:28:23 +00:00
{
2015-03-29 02:54:51 +00:00
$channel = self :: CHANNEL_EXEC ;
2015-02-06 03:28:23 +00:00
do {
2015-03-24 05:38:56 +00:00
if ( isset ( $this -> channel_status [ $channel ]) && $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_OPEN ) {
2015-02-06 03:28:23 +00:00
return $channel ;
}
2015-03-29 02:54:51 +00:00
} while ( $channel ++ < self :: CHANNEL_SUBSYSTEM );
2015-02-06 03:28:23 +00:00
2015-03-24 05:38:56 +00:00
return false ;
2015-02-06 03:28:23 +00:00
}
2019-03-31 20:15:32 +00:00
/**
* Request agent forwarding of remote server
*
* @ return bool
*/
public function requestAgentForwarding ()
{
$request_channel = $this -> get_open_channel ();
if ( $request_channel === false ) {
return false ;
}
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNsC' ,
2019-03-31 20:15:32 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
$this -> server_channels [ $request_channel ],
'auth-agent-req@openssh.com' ,
1
);
$this -> channel_status [ $request_channel ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
$this -> send_binary_packet ( $packet );
2021-05-09 06:07:09 +00:00
if ( ! $this -> get_channel_packet ( $request_channel )) {
2019-03-31 20:15:32 +00:00
return false ;
}
$this -> channel_status [ $request_channel ] = NET_SSH2_MSG_CHANNEL_OPEN ;
return true ;
}
2011-02-28 05:24:09 +00:00
/**
* Returns the output of an interactive shell
*
* Returns when there ' s a match for $expect , which can take the form of a string literal or ,
2014-12-04 21:45:13 +00:00
* if $mode == self :: READ_REGEX , a regular expression .
2011-02-28 05:24:09 +00:00
*
2016-04-10 16:30:59 +00:00
* @ see self :: write ()
* @ param string $expect
* @ param int $mode
2018-07-22 11:17:15 +00:00
* @ return string | bool | null
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2011-02-28 05:24:09 +00:00
*/
2017-01-08 01:51:56 +00:00
public function read ( $expect = '' , $mode = self :: READ_SIMPLE )
2011-02-28 05:24:09 +00:00
{
2012-03-03 17:49:16 +00:00
$this -> curTimeout = $this -> timeout ;
2013-06-07 22:21:11 +00:00
$this -> is_timeout = false ;
2012-03-03 17:49:16 +00:00
2016-12-16 16:27:56 +00:00
if ( ! $this -> isAuthenticated ()) {
2019-09-11 12:55:29 +00:00
throw new InsufficientSetupException ( 'Operation disallowed prior to login()' );
2011-02-28 05:24:09 +00:00
}
2017-01-08 01:51:56 +00:00
if ( ! ( $this -> bitmap & self :: MASK_SHELL ) && ! $this -> initShell ()) {
2016-04-30 21:23:35 +00:00
throw new \RuntimeException ( 'Unable to initiate an interactive shell session' );
2011-02-28 05:24:09 +00:00
}
2017-01-08 01:51:56 +00:00
$channel = $this -> get_interactive_channel ();
2013-02-08 22:04:52 +00:00
2017-06-20 03:34:20 +00:00
if ( $mode == self :: READ_NEXT ) {
2018-09-10 15:17:20 +00:00
return $this -> get_channel_packet ( $channel );
2017-06-13 23:56:50 +00:00
}
2011-05-08 02:34:36 +00:00
$match = $expect ;
2011-02-28 05:24:09 +00:00
while ( true ) {
2014-12-04 21:45:13 +00:00
if ( $mode == self :: READ_REGEX ) {
2015-07-15 00:05:52 +00:00
preg_match ( $expect , substr ( $this -> interactiveBuffer , - 1024 ), $matches );
2013-01-13 16:49:03 +00:00
$match = isset ( $matches [ 0 ]) ? $matches [ 0 ] : '' ;
2011-02-28 05:24:09 +00:00
}
2013-01-12 16:46:19 +00:00
$pos = strlen ( $match ) ? strpos ( $this -> interactiveBuffer , $match ) : false ;
2011-02-28 05:24:09 +00:00
if ( $pos !== false ) {
2016-07-31 03:18:06 +00:00
return Strings :: shift ( $this -> interactiveBuffer , $pos + strlen ( $match ));
2011-02-28 05:24:09 +00:00
}
2017-01-08 01:51:56 +00:00
$response = $this -> get_channel_packet ( $channel );
2021-05-09 06:07:09 +00:00
if ( $response === true ) {
2013-02-08 22:04:52 +00:00
$this -> in_request_pty_exec = false ;
2021-05-09 06:07:09 +00:00
return Strings :: shift ( $this -> interactiveBuffer , strlen ( $this -> interactiveBuffer ));
2012-03-03 17:49:16 +00:00
}
2011-02-28 05:24:09 +00:00
2022-02-17 02:25:59 +00:00
$this -> interactiveBuffer .= $response ;
2011-02-28 05:24:09 +00:00
}
}
/**
* Inputs a command into an interactive shell .
*
2022-03-09 00:59:30 +00:00
* @ see SSH2 :: read ()
2016-04-10 16:30:59 +00:00
* @ param string $cmd
2022-03-09 00:59:30 +00:00
* @ return void
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2011-02-28 05:24:09 +00:00
*/
2017-01-08 01:51:56 +00:00
public function write ( $cmd )
2011-02-28 05:24:09 +00:00
{
2016-12-16 16:27:56 +00:00
if ( ! $this -> isAuthenticated ()) {
2019-09-11 12:55:29 +00:00
throw new InsufficientSetupException ( 'Operation disallowed prior to login()' );
2011-02-28 05:24:09 +00:00
}
2017-01-08 01:51:56 +00:00
if ( ! ( $this -> bitmap & self :: MASK_SHELL ) && ! $this -> initShell ()) {
2016-04-30 21:23:35 +00:00
throw new \RuntimeException ( 'Unable to initiate an interactive shell session' );
2011-02-28 05:24:09 +00:00
}
2022-03-09 00:59:30 +00:00
$this -> send_channel_packet ( $this -> get_interactive_channel (), $cmd );
2013-10-25 17:35:30 +00:00
}
/**
* Start a subsystem .
*
* Right now only one subsystem at a time is supported . To support multiple subsystem ' s stopSubsystem () could accept
* a string that contained the name of the subsystem , but at that point , only one subsystem of each type could be opened .
* To support multiple subsystem 's of the same name maybe it' d be best if startSubsystem () generated a new channel id and
* returns that and then that that was passed into stopSubsystem () but that ' ll be saved for a future date and implemented
* if there ' s sufficient demand for such a feature .
*
2016-04-10 16:30:59 +00:00
* @ see self :: stopSubsystem ()
* @ param string $subsystem
* @ return bool
2013-10-25 17:35:30 +00:00
*/
2017-01-08 01:51:56 +00:00
public function startSubsystem ( $subsystem )
2013-10-25 17:35:30 +00:00
{
2014-12-04 21:45:13 +00:00
$this -> window_size_server_to_client [ self :: CHANNEL_SUBSYSTEM ] = $this -> window_size ;
2013-10-25 17:35:30 +00:00
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 11:36:18 +00:00
self :: CHANNEL_SUBSYSTEM ,
2015-07-15 01:52:31 +00:00
$this -> window_size ,
0x4000
);
2013-10-25 17:35:30 +00:00
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2013-10-25 17:35:30 +00:00
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2013-10-25 17:35:30 +00:00
2021-05-09 06:07:09 +00:00
$this -> get_channel_packet ( self :: CHANNEL_SUBSYSTEM );
2013-10-25 17:35:30 +00:00
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNsCs' ,
2015-07-15 01:52:31 +00:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 11:36:18 +00:00
$this -> server_channels [ self :: CHANNEL_SUBSYSTEM ],
2015-07-15 01:52:31 +00:00
'subsystem' ,
1 ,
$subsystem
);
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2013-10-25 17:35:30 +00:00
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2013-10-25 17:35:30 +00:00
2021-05-09 06:07:09 +00:00
if ( ! $this -> get_channel_packet ( self :: CHANNEL_SUBSYSTEM )) {
2015-07-15 01:52:31 +00:00
return false ;
2013-10-25 17:35:30 +00:00
}
2014-12-04 21:45:13 +00:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_DATA ;
2013-10-25 17:35:30 +00:00
2014-12-04 21:45:13 +00:00
$this -> bitmap |= self :: MASK_SHELL ;
2013-10-25 17:35:30 +00:00
$this -> in_subsystem = true ;
return true ;
}
/**
* Stops a subsystem .
*
2016-04-10 16:30:59 +00:00
* @ see self :: startSubsystem ()
* @ return bool
2013-10-25 17:35:30 +00:00
*/
2017-01-08 01:51:56 +00:00
public function stopSubsystem ()
2013-10-25 17:35:30 +00:00
{
$this -> in_subsystem = false ;
2017-01-08 01:51:56 +00:00
$this -> close_channel ( self :: CHANNEL_SUBSYSTEM );
2013-10-25 17:35:30 +00:00
return true ;
2011-02-28 05:24:09 +00:00
}
2013-06-07 22:21:11 +00:00
/**
* Closes a channel
*
* If read () timed out you might want to just close the channel and have it auto - restart on the next read () call
*
*/
2017-01-08 01:51:56 +00:00
public function reset ()
2013-06-07 22:21:11 +00:00
{
2017-01-08 01:51:56 +00:00
$this -> close_channel ( $this -> get_interactive_channel ());
2013-06-07 22:21:11 +00:00
}
/**
* Is timeout ?
*
* Did exec () or read () return because they timed out or because they encountered the end ?
*
*/
2017-01-08 01:51:56 +00:00
public function isTimeout ()
2013-06-07 22:21:11 +00:00
{
return $this -> is_timeout ;
}
2008-05-15 17:40:03 +00:00
/**
* Disconnect
*
*/
2017-01-08 01:51:56 +00:00
public function disconnect ()
2008-05-15 17:40:03 +00:00
{
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2012-04-30 16:25:37 +00:00
if ( isset ( $this -> realtime_log_file ) && is_resource ( $this -> realtime_log_file )) {
fclose ( $this -> realtime_log_file );
}
2016-04-30 21:23:35 +00:00
unset ( self :: $connections [ $this -> getResourceId ()]);
2008-05-15 17:40:03 +00:00
}
2008-05-15 16:33:08 +00:00
/**
* Destructor .
*
2008-05-15 17:40:03 +00:00
* Will be called , automatically , if you 're supporting just PHP5. If you' re supporting PHP4 , you ' ll need to call
* disconnect () .
2008-05-15 16:33:08 +00:00
*
*/
2017-01-08 01:51:56 +00:00
public function __destruct ()
2008-05-15 16:33:08 +00:00
{
2008-05-15 17:40:03 +00:00
$this -> disconnect ();
2008-05-15 16:33:08 +00:00
}
2013-02-16 20:58:12 +00:00
/**
* Is the connection still active ?
*
2016-04-10 16:30:59 +00:00
* @ return bool
2013-02-16 20:58:12 +00:00
*/
2017-01-08 01:51:56 +00:00
public function isConnected ()
2013-02-16 20:58:12 +00:00
{
2014-12-04 21:45:13 +00:00
return ( bool ) ( $this -> bitmap & self :: MASK_CONNECTED );
2013-02-16 20:58:12 +00:00
}
2016-04-10 16:30:59 +00:00
/**
* Have you successfully been logged in ?
*
* @ return bool
*/
2017-01-08 01:51:56 +00:00
public function isAuthenticated ()
2016-04-10 16:30:59 +00:00
{
return ( bool ) ( $this -> bitmap & self :: MASK_LOGIN );
}
2017-08-27 00:50:17 +00:00
/**
2018-10-06 06:19:14 +00:00
* Pings a server connection , or tries to reconnect if the connection has gone down
*
* Inspired by http :// php . net / manual / en / mysqli . ping . php
*
* @ return bool
*/
2018-10-06 13:02:59 +00:00
public function ping ()
2018-10-06 06:19:14 +00:00
{
if ( ! $this -> isAuthenticated ()) {
2019-09-10 22:31:43 +00:00
if ( ! empty ( $this -> auth )) {
2019-09-10 23:13:12 +00:00
return $this -> reconnect ();
2019-09-10 22:31:43 +00:00
}
2018-10-06 06:19:14 +00:00
return false ;
}
2019-01-20 15:15:53 +00:00
$this -> window_size_server_to_client [ self :: CHANNEL_KEEP_ALIVE ] = $this -> window_size ;
2018-10-06 06:19:14 +00:00
$packet_size = 0x4000 ;
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2018-10-06 06:19:14 +00:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2019-01-20 15:15:53 +00:00
self :: CHANNEL_KEEP_ALIVE ,
$this -> window_size_server_to_client [ self :: CHANNEL_KEEP_ALIVE ],
2018-10-06 06:19:14 +00:00
$packet_size
);
2018-10-06 16:41:15 +00:00
try {
$this -> send_binary_packet ( $packet );
2018-10-06 06:19:14 +00:00
2019-01-20 15:17:57 +00:00
$this -> channel_status [ self :: CHANNEL_KEEP_ALIVE ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2018-10-06 06:19:14 +00:00
2019-01-20 15:17:57 +00:00
$response = $this -> get_channel_packet ( self :: CHANNEL_KEEP_ALIVE );
2018-10-06 16:41:15 +00:00
} catch ( \RuntimeException $e ) {
return $this -> reconnect ();
2018-10-06 06:19:14 +00:00
}
2021-06-16 13:12:09 +00:00
$this -> close_channel ( self :: CHANNEL_KEEP_ALIVE );
2018-10-06 16:41:15 +00:00
return true ;
2018-10-06 06:19:14 +00:00
}
/**
* In situ reconnect method
*
* @ return boolean
*/
2018-10-06 13:02:59 +00:00
private function reconnect ()
2018-10-06 06:19:14 +00:00
{
2018-10-06 13:02:59 +00:00
$this -> reset_connection ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2018-10-06 06:19:14 +00:00
$this -> retry_connect = true ;
2021-05-21 13:49:52 +00:00
$this -> connect ();
2018-10-06 06:19:14 +00:00
foreach ( $this -> auth as $auth ) {
2019-03-24 00:20:06 +00:00
$result = $this -> login ( ... $auth );
2018-10-06 06:19:14 +00:00
}
return $result ;
}
2017-08-27 00:50:17 +00:00
/**
* Resets a connection for re - use
*
* @ param int $reason
*/
2019-09-10 23:13:12 +00:00
protected function reset_connection ( $reason )
2017-08-27 00:50:17 +00:00
{
2017-08-27 17:33:19 +00:00
$this -> disconnect_helper ( $reason );
2017-08-27 00:50:17 +00:00
$this -> decrypt = $this -> encrypt = false ;
$this -> decrypt_block_size = $this -> encrypt_block_size = 8 ;
$this -> hmac_check = $this -> hmac_create = false ;
$this -> hmac_size = false ;
$this -> session_id = false ;
$this -> retry_connect = true ;
$this -> get_seq_no = $this -> send_seq_no = 0 ;
}
2007-07-23 05:21:39 +00:00
/**
* Gets Binary Packets
*
* See '6. Binary Packet Protocol' of rfc4253 for more info .
*
2016-04-10 16:30:59 +00:00
* @ see self :: _send_binary_packet ()
2020-09-09 08:01:39 +00:00
* @ param bool $skip_channel_filter
2022-01-28 19:14:38 +00:00
* @ return bool | string
2007-07-23 05:21:39 +00:00
*/
2017-11-11 22:13:48 +00:00
private function get_binary_packet ( $skip_channel_filter = false )
2007-07-23 05:21:39 +00:00
{
2020-12-15 14:25:42 +00:00
if ( $skip_channel_filter ) {
2022-01-30 01:56:43 +00:00
if ( ! is_resource ( $this -> fsock )) {
2022-01-28 19:14:38 +00:00
throw new \InvalidArgumentException ( 'fsock is not a resource.' );
}
2020-12-15 14:52:43 +00:00
$read = [ $this -> fsock ];
2020-12-15 14:25:42 +00:00
$write = $except = null ;
2021-05-14 17:55:06 +00:00
if ( ! $this -> curTimeout ) {
2020-12-15 14:25:42 +00:00
if ( $this -> keepAlive <= 0 ) {
2020-12-23 16:39:00 +00:00
@ stream_select ( $read , $write , $except , null );
2020-12-15 14:25:42 +00:00
} else {
2020-12-23 16:39:00 +00:00
if ( !@ stream_select ( $read , $write , $except , $this -> keepAlive )) {
2020-12-15 14:52:43 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_IGNORE , 0 ));
return $this -> get_binary_packet ( true );
2020-12-15 14:25:42 +00:00
}
}
} else {
if ( $this -> curTimeout < 0 ) {
$this -> is_timeout = true ;
return true ;
}
2020-12-15 14:43:21 +00:00
$start = microtime ( true );
2020-12-15 14:25:42 +00:00
if ( $this -> keepAlive > 0 && $this -> keepAlive < $this -> curTimeout ) {
2020-12-23 16:39:00 +00:00
if ( !@ stream_select ( $read , $write , $except , $this -> keepAlive )) {
2020-12-15 14:52:43 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_IGNORE , 0 ));
2020-12-15 14:43:21 +00:00
$elapsed = microtime ( true ) - $start ;
2022-02-17 02:25:59 +00:00
$this -> curTimeout -= $elapsed ;
2020-12-15 14:52:43 +00:00
return $this -> get_binary_packet ( true );
2020-12-15 14:25:42 +00:00
}
2020-12-15 14:43:21 +00:00
$elapsed = microtime ( true ) - $start ;
2022-02-17 02:25:59 +00:00
$this -> curTimeout -= $elapsed ;
2020-12-15 14:25:42 +00:00
}
2022-01-29 17:35:38 +00:00
$sec = ( int ) floor ( $this -> curTimeout );
$usec = ( int ) ( 1000000 * ( $this -> curTimeout - $sec ));
2020-12-15 14:25:42 +00:00
2020-12-23 16:39:00 +00:00
// this can return a "stream_select(): unable to select [4]: Interrupted system call" error
if ( !@ stream_select ( $read , $write , $except , $sec , $usec )) {
2020-12-15 14:25:42 +00:00
$this -> is_timeout = true ;
return true ;
}
2020-12-15 14:43:21 +00:00
$elapsed = microtime ( true ) - $start ;
2022-02-17 02:25:59 +00:00
$this -> curTimeout -= $elapsed ;
2020-12-15 14:25:42 +00:00
}
}
2012-04-24 05:01:55 +00:00
if ( ! is_resource ( $this -> fsock ) || feof ( $this -> fsock )) {
2013-11-15 18:34:31 +00:00
$this -> bitmap = 0 ;
2022-06-14 05:02:10 +00:00
$str = 'Connection closed (by server) prematurely' ;
if ( isset ( $elapsed )) {
$str .= ' ' . $elapsed . 's' ;
}
2022-06-14 05:03:13 +00:00
throw new ConnectionClosedException ( $str );
2007-07-23 05:21:39 +00:00
}
2013-12-05 17:46:46 +00:00
$start = microtime ( true );
2016-05-22 19:37:12 +00:00
$raw = stream_get_contents ( $this -> fsock , $this -> decrypt_block_size );
2007-07-23 05:21:39 +00:00
2013-01-12 16:46:19 +00:00
if ( ! strlen ( $raw )) {
2021-11-12 01:39:49 +00:00
$this -> bitmap = 0 ;
throw new ConnectionClosedException ( 'No data received from server' );
2011-04-22 07:50:24 +00:00
}
2018-12-31 20:14:43 +00:00
if ( $this -> decrypt ) {
2022-01-30 07:36:02 +00:00
switch ( $this -> decryptName ) {
2019-03-18 11:59:00 +00:00
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$this -> decrypt -> setNonce (
2022-02-02 03:17:10 +00:00
$this -> decryptFixedPart .
2022-01-30 07:36:02 +00:00
$this -> decryptInvocationCounter
2019-03-18 11:59:00 +00:00
);
2022-01-30 07:36:02 +00:00
Strings :: increment_str ( $this -> decryptInvocationCounter );
2019-03-18 11:59:00 +00:00
$this -> decrypt -> setAAD ( $temp = Strings :: shift ( $raw , 4 ));
extract ( unpack ( 'Npacket_length' , $temp ));
/**
* @ var integer $packet_length
*/
2022-02-17 02:25:59 +00:00
$raw .= $this -> read_remaining_bytes ( $packet_length - $this -> decrypt_block_size + 4 );
2019-03-18 11:59:00 +00:00
$stop = microtime ( true );
$tag = stream_get_contents ( $this -> fsock , $this -> decrypt_block_size );
$this -> decrypt -> setTag ( $tag );
$raw = $this -> decrypt -> decrypt ( $raw );
$raw = $temp . $raw ;
$remaining_length = 0 ;
break ;
case 'chacha20-poly1305@openssh.com' :
2022-01-30 17:20:45 +00:00
// This should be impossible, but we are checking anyway to narrow the type for Psalm.
if ( ! ( $this -> decrypt instanceof ChaCha20 )) {
throw new \LogicException ( '$this->decrypt is not a ' . ChaCha20 :: class );
}
2019-03-18 11:59:00 +00:00
$nonce = pack ( 'N2' , 0 , $this -> get_seq_no );
$this -> lengthDecrypt -> setNonce ( $nonce );
$temp = $this -> lengthDecrypt -> decrypt ( $aad = Strings :: shift ( $raw , 4 ));
extract ( unpack ( 'Npacket_length' , $temp ));
/**
* @ var integer $packet_length
*/
2022-02-17 02:25:59 +00:00
$raw .= $this -> read_remaining_bytes ( $packet_length - $this -> decrypt_block_size + 4 );
2019-03-18 11:59:00 +00:00
$stop = microtime ( true );
$tag = stream_get_contents ( $this -> fsock , 16 );
$this -> decrypt -> setNonce ( $nonce );
$this -> decrypt -> setCounter ( 0 );
// this is the same approach that's implemented in Salsa20::createPoly1305Key()
// but we don't want to use the same AEAD construction that RFC8439 describes
// for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
$this -> decrypt -> setPoly1305Key (
$this -> decrypt -> encrypt ( str_repeat ( " \0 " , 32 ))
);
$this -> decrypt -> setAAD ( $aad );
$this -> decrypt -> setCounter ( 1 );
$this -> decrypt -> setTag ( $tag );
$raw = $this -> decrypt -> decrypt ( $raw );
$raw = $temp . $raw ;
$remaining_length = 0 ;
break ;
default :
2022-01-30 07:52:31 +00:00
if ( ! $this -> hmac_check instanceof Hash || ! $this -> hmac_check_etm ) {
2019-09-08 22:51:53 +00:00
$raw = $this -> decrypt -> decrypt ( $raw );
break ;
}
extract ( unpack ( 'Npacket_length' , $temp = Strings :: shift ( $raw , 4 )));
/**
* @ var integer $packet_length
*/
2022-02-17 02:25:59 +00:00
$raw .= $this -> read_remaining_bytes ( $packet_length - $this -> decrypt_block_size + 4 );
2019-09-08 22:51:53 +00:00
$stop = microtime ( true );
$encrypted = $temp . $raw ;
$raw = $temp . $this -> decrypt -> decrypt ( $raw );
$remaining_length = 0 ;
2018-12-27 14:31:35 +00:00
}
2007-07-23 05:21:39 +00:00
}
2016-10-23 03:13:17 +00:00
if ( strlen ( $raw ) < 5 ) {
2021-05-09 06:07:09 +00:00
$this -> bitmap = 0 ;
throw new \RuntimeException ( 'Plaintext is too short' );
2016-10-23 03:13:17 +00:00
}
2016-07-31 03:18:06 +00:00
extract ( unpack ( 'Npacket_length/Cpadding_length' , Strings :: shift ( $raw , 5 )));
2017-12-04 10:11:43 +00:00
/**
* @ var integer $packet_length
* @ var integer $padding_length
*/
2007-07-23 05:21:39 +00:00
2018-12-27 14:31:35 +00:00
if ( ! isset ( $remaining_length )) {
$remaining_length = $packet_length + 4 - $this -> decrypt_block_size ;
}
2013-01-26 06:17:23 +00:00
2018-12-27 14:31:35 +00:00
$buffer = $this -> read_remaining_bytes ( $remaining_length );
2017-08-29 03:37:35 +00:00
2018-12-27 14:31:35 +00:00
if ( ! isset ( $stop )) {
$stop = microtime ( true );
}
2013-01-12 16:46:19 +00:00
if ( strlen ( $buffer )) {
2022-02-17 02:25:59 +00:00
$raw .= $this -> decrypt ? $this -> decrypt -> decrypt ( $buffer ) : $buffer ;
2009-02-16 22:22:13 +00:00
}
2007-07-23 05:21:39 +00:00
2016-07-31 03:18:06 +00:00
$payload = Strings :: shift ( $raw , $packet_length - $padding_length - 1 );
$padding = Strings :: shift ( $raw , $padding_length ); // should leave $raw empty
2007-07-23 05:21:39 +00:00
2019-03-29 02:45:28 +00:00
if ( $this -> hmac_check instanceof Hash ) {
2016-05-22 19:37:12 +00:00
$hmac = stream_get_contents ( $this -> fsock , $this -> hmac_size );
2014-04-18 21:58:00 +00:00
if ( $hmac === false || strlen ( $hmac ) != $this -> hmac_size ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_MAC_ERROR );
2016-04-30 21:23:35 +00:00
throw new \RuntimeException ( 'Error reading socket' );
2019-09-08 16:23:29 +00:00
}
2022-01-30 07:52:31 +00:00
$reconstructed = ! $this -> hmac_check_etm ?
2019-09-08 22:51:53 +00:00
pack ( 'NCa*' , $packet_length , $padding_length , $payload . $padding ) :
$encrypted ;
2019-09-08 16:23:29 +00:00
if (( $this -> hmac_check -> getHash () & " \xFF \xFF \xFF \xFF " ) == 'umac' ) {
$this -> hmac_check -> setNonce ( " \0 \0 \0 \0 " . pack ( 'N' , $this -> get_seq_no ));
if ( $hmac != $this -> hmac_check -> hash ( $reconstructed )) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_MAC_ERROR );
2019-09-08 16:23:29 +00:00
throw new \RuntimeException ( 'Invalid UMAC' );
}
} else {
if ( $hmac != $this -> hmac_check -> hash ( pack ( 'Na*' , $this -> get_seq_no , $reconstructed ))) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_MAC_ERROR );
2019-09-08 16:23:29 +00:00
throw new \RuntimeException ( 'Invalid HMAC' );
}
2007-07-23 05:21:39 +00:00
}
}
2021-10-27 01:04:53 +00:00
switch ( $this -> decompress ) {
2021-12-14 15:34:41 +00:00
case self :: NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH :
2021-10-27 01:04:53 +00:00
if ( ! $this -> isAuthenticated ()) {
break ;
}
2022-02-23 02:48:51 +00:00
// fall-through
2021-12-14 15:34:41 +00:00
case self :: NET_SSH2_COMPRESSION_ZLIB :
2021-10-27 01:04:53 +00:00
if ( $this -> regenerate_decompression_context ) {
$this -> regenerate_decompression_context = false ;
$cmf = ord ( $payload [ 0 ]);
$cm = $cmf & 0x0F ;
if ( $cm != 8 ) { // deflate
user_error ( " Only CM = 8 ('deflate') is supported ( $cm ) " );
}
$cinfo = ( $cmf & 0xF0 ) >> 4 ;
if ( $cinfo > 7 ) {
user_error ( " CINFO above 7 is not allowed ( $cinfo ) " );
}
$windowSize = 1 << ( $cinfo + 8 );
$flg = ord ( $payload [ 1 ]);
//$fcheck = $flg && 0x0F;
if ((( $cmf << 8 ) | $flg ) % 31 ) {
user_error ( 'fcheck failed' );
}
$fdict = boolval ( $flg & 0x20 );
$flevel = ( $flg & 0xC0 ) >> 6 ;
2021-10-30 23:16:23 +00:00
$this -> decompress_context = inflate_init ( ZLIB_ENCODING_RAW , [ 'window' => $cinfo + 8 ]);
2021-10-27 01:04:53 +00:00
$payload = substr ( $payload , 2 );
}
if ( $this -> decompress_context ) {
$payload = inflate_add ( $this -> decompress_context , $payload , ZLIB_PARTIAL_FLUSH );
}
}
2009-05-16 17:09:37 +00:00
2008-05-26 19:42:01 +00:00
$this -> get_seq_no ++ ;
2007-07-23 05:21:39 +00:00
2009-03-25 22:29:42 +00:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2013-12-05 17:46:46 +00:00
$current = microtime ( true );
2012-04-30 16:25:37 +00:00
$message_number = isset ( $this -> message_numbers [ ord ( $payload [ 0 ])]) ? $this -> message_numbers [ ord ( $payload [ 0 ])] : 'UNKNOWN (' . ord ( $payload [ 0 ]) . ')' ;
$message_number = '<- ' . $message_number .
2012-11-22 19:08:30 +00:00
' (since last: ' . round ( $current - $this -> last_packet , 4 ) . ', network: ' . round ( $stop - $start , 4 ) . 's)' ;
2017-01-08 01:51:56 +00:00
$this -> append_log ( $message_number , $payload );
2012-11-22 19:08:30 +00:00
$this -> last_packet = $current ;
2009-03-25 22:29:42 +00:00
}
2017-11-11 22:13:48 +00:00
return $this -> filter ( $payload , $skip_channel_filter );
2007-07-26 14:53:45 +00:00
}
2018-12-27 14:31:35 +00:00
/**
* Read Remaining Bytes
*
* @ see self :: get_binary_packet ()
* @ param int $remaining_length
* @ return string
*/
private function read_remaining_bytes ( $remaining_length )
{
2019-03-18 11:59:00 +00:00
if ( ! $remaining_length ) {
return '' ;
}
$adjustLength = false ;
if ( $this -> decrypt ) {
2019-09-08 22:51:53 +00:00
switch ( true ) {
2022-01-30 07:36:02 +00:00
case $this -> decryptName == 'aes128-gcm@openssh.com' :
case $this -> decryptName == 'aes256-gcm@openssh.com' :
case $this -> decryptName == 'chacha20-poly1305@openssh.com' :
2022-01-30 07:52:31 +00:00
case $this -> hmac_check instanceof Hash && $this -> hmac_check_etm :
2022-02-17 02:25:59 +00:00
$remaining_length += $this -> decrypt_block_size - 4 ;
2019-03-18 11:59:00 +00:00
$adjustLength = true ;
}
}
// quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
// "implementations SHOULD check that the packet length is reasonable"
// PuTTY uses 0x9000 as the actual max packet size and so to shall we
// don't do this when GCM mode is used since GCM mode doesn't encrypt the length
if ( $remaining_length < - $this -> decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this -> decrypt_block_size != 0 ) {
2022-01-30 07:36:02 +00:00
if ( ! $this -> bad_key_size_fix && self :: bad_algorithm_candidate ( $this -> decrypt ? $this -> decryptName : '' ) && ! ( $this -> bitmap & SSH2 :: MASK_LOGIN )) {
2019-03-18 11:59:00 +00:00
$this -> bad_key_size_fix = true ;
$this -> reset_connection ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
return false ;
}
throw new \RuntimeException ( 'Invalid size' );
}
if ( $adjustLength ) {
2022-02-17 02:25:59 +00:00
$remaining_length -= $this -> decrypt_block_size - 4 ;
2019-03-18 11:59:00 +00:00
}
2018-12-27 14:31:35 +00:00
$buffer = '' ;
while ( $remaining_length > 0 ) {
$temp = stream_get_contents ( $this -> fsock , $remaining_length );
if ( $temp === false || feof ( $this -> fsock )) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2018-12-27 14:31:35 +00:00
throw new \RuntimeException ( 'Error reading from socket' );
}
2022-02-17 02:25:59 +00:00
$buffer .= $temp ;
$remaining_length -= strlen ( $temp );
2018-12-27 14:31:35 +00:00
}
return $buffer ;
}
2007-07-26 14:53:45 +00:00
/**
* Filter Binary Packets
*
* Because some binary packets need to be ignored ...
*
2016-04-10 16:30:59 +00:00
* @ see self :: _get_binary_packet ()
2017-11-05 20:35:27 +00:00
* @ param string $payload
2020-09-09 08:01:39 +00:00
* @ param bool $skip_channel_filter
2022-01-28 19:14:38 +00:00
* @ return string | bool
2007-07-26 14:53:45 +00:00
*/
2017-11-11 22:13:48 +00:00
private function filter ( $payload , $skip_channel_filter )
2007-07-26 14:53:45 +00:00
{
2007-07-23 05:21:39 +00:00
switch ( ord ( $payload [ 0 ])) {
case NET_SSH2_MSG_DISCONNECT :
2016-07-31 03:18:06 +00:00
Strings :: shift ( $payload , 1 );
2019-04-02 05:09:19 +00:00
list ( $reason_code , $message ) = Strings :: unpackSSH2 ( 'Ns' , $payload );
$this -> errors [] = 'SSH_MSG_DISCONNECT: ' . $this -> disconnect_reasons [ $reason_code ] . " \r \n $message " ;
2013-11-15 18:34:31 +00:00
$this -> bitmap = 0 ;
2007-07-23 05:21:39 +00:00
return false ;
case NET_SSH2_MSG_IGNORE :
2017-11-12 05:16:23 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 05:21:39 +00:00
break ;
case NET_SSH2_MSG_DEBUG :
2019-04-02 05:09:19 +00:00
Strings :: shift ( $payload , 2 ); // second byte is "always_display"
list ( $message ) = Strings :: unpackSSH2 ( 's' , $payload );
$this -> errors [] = " SSH_MSG_DEBUG: $message " ;
2017-11-12 05:16:23 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 05:21:39 +00:00
break ;
case NET_SSH2_MSG_UNIMPLEMENTED :
return false ;
case NET_SSH2_MSG_KEXINIT :
if ( $this -> session_id !== false ) {
2017-01-08 01:51:56 +00:00
if ( ! $this -> key_exchange ( $payload )) {
2013-11-15 18:34:31 +00:00
$this -> bitmap = 0 ;
2007-07-23 05:21:39 +00:00
return false ;
}
2017-11-12 05:16:23 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 05:21:39 +00:00
}
}
2007-07-25 21:49:33 +00:00
// 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
2022-01-30 01:56:43 +00:00
if (( $this -> bitmap & self :: MASK_CONNECTED ) && ! $this -> isAuthenticated () && ! is_bool ( $payload ) && ord ( $payload [ 0 ]) == NET_SSH2_MSG_USERAUTH_BANNER ) {
2016-07-31 03:18:06 +00:00
Strings :: shift ( $payload , 1 );
2019-07-27 22:28:18 +00:00
list ( $this -> banner_message ) = Strings :: unpackSSH2 ( 's' , $payload );
2017-01-08 01:51:56 +00:00
$payload = $this -> get_binary_packet ();
2007-07-25 21:49:33 +00:00
}
// only called when we've already logged in
2016-12-17 23:03:08 +00:00
if (( $this -> bitmap & self :: MASK_CONNECTED ) && $this -> isAuthenticated ()) {
2022-01-30 01:56:43 +00:00
if ( is_bool ( $payload )) {
2022-01-28 19:14:38 +00:00
return $payload ;
2021-05-19 03:00:59 +00:00
}
2007-07-25 21:49:33 +00:00
switch ( ord ( $payload [ 0 ])) {
2020-12-15 14:25:42 +00:00
case NET_SSH2_MSG_CHANNEL_REQUEST :
if ( strlen ( $payload ) == 31 ) {
extract ( unpack ( 'cpacket_type/Nchannel/Nlength' , $payload ));
if ( substr ( $payload , 9 , $length ) == 'keepalive@openssh.com' && isset ( $this -> server_channels [ $channel ])) {
if ( ord ( substr ( $payload , 9 + $length ))) { // want reply
2020-12-15 14:52:43 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_SUCCESS , $this -> server_channels [ $channel ]));
2020-12-15 14:25:42 +00:00
}
2020-12-15 14:52:43 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2020-12-15 14:25:42 +00:00
}
}
break ;
2017-08-29 03:37:35 +00:00
case NET_SSH2_MSG_CHANNEL_DATA :
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA :
case NET_SSH2_MSG_CHANNEL_CLOSE :
case NET_SSH2_MSG_CHANNEL_EOF :
2017-11-09 06:12:06 +00:00
if ( ! $skip_channel_filter && ! empty ( $this -> server_channels )) {
2017-08-29 03:37:35 +00:00
$this -> binary_packet_buffer = $payload ;
2017-09-06 05:44:38 +00:00
$this -> get_channel_packet ( true );
2017-11-11 22:13:48 +00:00
$payload = $this -> get_binary_packet ();
2017-08-29 03:37:35 +00:00
}
break ;
2007-07-25 21:49:33 +00:00
case NET_SSH2_MSG_GLOBAL_REQUEST : // see http://tools.ietf.org/html/rfc4254#section-4
2019-04-02 05:09:19 +00:00
Strings :: shift ( $payload , 1 );
list ( $request_name ) = Strings :: unpackSSH2 ( 's' , $payload );
$this -> errors [] = " SSH_MSG_GLOBAL_REQUEST: $request_name " ;
2007-07-25 21:49:33 +00:00
2019-03-30 19:12:52 +00:00
try {
$this -> send_binary_packet ( pack ( 'C' , NET_SSH2_MSG_REQUEST_FAILURE ));
} catch ( \RuntimeException $e ) {
2017-01-08 01:51:56 +00:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2007-07-25 21:49:33 +00:00
}
2017-11-11 22:13:48 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 21:49:33 +00:00
break ;
case NET_SSH2_MSG_CHANNEL_OPEN : // see http://tools.ietf.org/html/rfc4254#section-5.1
2016-07-31 03:18:06 +00:00
Strings :: shift ( $payload , 1 );
2019-04-02 05:09:19 +00:00
list ( $data , $server_channel ) = Strings :: unpackSSH2 ( 'sN' , $payload );
2015-07-15 01:52:31 +00:00
switch ( $data ) {
2015-02-06 03:28:23 +00:00
case 'auth-agent' :
case 'auth-agent@openssh.com' :
if ( isset ( $this -> agent )) {
2015-07-17 11:36:18 +00:00
$new_channel = self :: CHANNEL_AGENT_FORWARD ;
2015-07-15 01:52:31 +00:00
2019-04-02 05:09:19 +00:00
list (
$remote_window_size ,
$remote_maximum_packet_size
) = Strings :: unpackSSH2 ( 'NN' , $payload );
2015-07-15 01:52:31 +00:00
$this -> packet_size_client_to_server [ $new_channel ] = $remote_window_size ;
$this -> window_size_server_to_client [ $new_channel ] = $remote_maximum_packet_size ;
$this -> window_size_client_to_server [ $new_channel ] = $this -> window_size ;
$packet_size = 0x4000 ;
$packet = pack (
'CN4' ,
NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ,
$server_channel ,
$new_channel ,
$packet_size ,
$packet_size
);
$this -> server_channels [ $new_channel ] = $server_channel ;
$this -> channel_status [ $new_channel ] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ;
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2015-02-06 03:28:23 +00:00
}
2015-03-24 05:38:56 +00:00
break ;
2015-02-06 03:28:23 +00:00
default :
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CN2ss' ,
NET_SSH2_MSG_CHANNEL_OPEN_FAILURE ,
2015-07-15 01:52:31 +00:00
$server_channel ,
NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED ,
2019-04-02 05:09:19 +00:00
'' , // description
'' // language tag
2015-07-15 01:52:31 +00:00
);
2007-07-25 21:49:33 +00:00
2019-03-30 19:12:52 +00:00
try {
$this -> send_binary_packet ( $packet );
} catch ( \RuntimeException $e ) {
2017-01-08 01:51:56 +00:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2015-02-06 03:28:23 +00:00
}
}
2017-11-11 22:13:48 +00:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 21:49:33 +00:00
break ;
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST :
2016-07-31 03:18:06 +00:00
Strings :: shift ( $payload , 1 );
2019-04-02 05:09:19 +00:00
list ( $channel , $window_size ) = Strings :: unpackSSH2 ( 'NN' , $payload );
2016-11-30 01:35:50 +00:00
2022-02-17 02:25:59 +00:00
$this -> window_size_client_to_server [ $channel ] += $window_size ;
2013-07-07 20:57:15 +00:00
2017-11-11 22:13:48 +00:00
$payload = ( $this -> bitmap & self :: MASK_WINDOW_ADJUST ) ? true : $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 21:49:33 +00:00
}
}
2007-07-23 05:21:39 +00:00
return $payload ;
}
2012-07-23 12:17:53 +00:00
/**
* Enable Quiet Mode
*
* Suppress stderr from output
*
*/
2017-01-08 01:51:56 +00:00
public function enableQuietMode ()
2012-07-23 12:17:53 +00:00
{
$this -> quiet_mode = true ;
}
/**
* Disable Quiet Mode
*
* Show stderr in output
*
*/
2017-01-08 01:51:56 +00:00
public function disableQuietMode ()
2012-07-23 12:17:53 +00:00
{
$this -> quiet_mode = false ;
}
2013-12-28 20:27:02 +00:00
/**
* Returns whether Quiet Mode is enabled or not
*
2016-04-10 16:30:59 +00:00
* @ see self :: enableQuietMode ()
* @ see self :: disableQuietMode ()
* @ return bool
2013-12-28 20:27:02 +00:00
*/
2017-01-08 01:51:56 +00:00
public function isQuietModeEnabled ()
2013-12-28 20:27:02 +00:00
{
return $this -> quiet_mode ;
}
2013-02-08 22:04:52 +00:00
/**
* Enable request - pty when using exec ()
*
*/
2017-01-08 01:51:56 +00:00
public function enablePTY ()
2013-02-08 22:04:52 +00:00
{
$this -> request_pty = true ;
}
/**
* Disable request - pty when using exec ()
*
*/
2017-01-08 01:51:56 +00:00
public function disablePTY ()
2013-02-08 22:04:52 +00:00
{
2016-12-17 22:09:48 +00:00
if ( $this -> in_request_pty_exec ) {
2017-05-28 14:44:38 +00:00
$this -> close_channel ( self :: CHANNEL_EXEC );
2016-12-17 22:09:48 +00:00
$this -> in_request_pty_exec = false ;
}
2013-02-08 22:04:52 +00:00
$this -> request_pty = false ;
}
2013-12-28 20:27:02 +00:00
/**
* Returns whether request - pty is enabled or not
*
2016-04-10 16:30:59 +00:00
* @ see self :: enablePTY ()
* @ see self :: disablePTY ()
* @ return bool
2013-12-28 20:27:02 +00:00
*/
2017-01-08 01:51:56 +00:00
public function isPTYEnabled ()
2013-12-28 20:27:02 +00:00
{
return $this -> request_pty ;
}
2009-05-23 14:42:17 +00:00
/**
* Gets channel data
*
2021-05-09 06:07:09 +00:00
* Returns the data as a string . bool ( true ) is returned if :
*
* - the server closes the channel
* - if the connection times out
* - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
* - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
*
* bool ( false ) is returned if :
*
* - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
2009-05-23 14:42:17 +00:00
*
2017-11-05 20:35:27 +00:00
* @ param int $client_channel
* @ param bool $skip_extended
2016-04-10 16:30:59 +00:00
* @ return mixed
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on connection error
2009-05-23 14:42:17 +00:00
*/
2017-01-08 01:51:56 +00:00
protected function get_channel_packet ( $client_channel , $skip_extended = false )
2009-05-23 14:42:17 +00:00
{
2013-01-13 17:36:57 +00:00
if ( ! empty ( $this -> channel_buffers [ $client_channel ])) {
2021-10-11 13:47:51 +00:00
switch ( $this -> channel_status [ $client_channel ]) {
case NET_SSH2_MSG_CHANNEL_REQUEST :
2021-10-11 13:58:43 +00:00
foreach ( $this -> channel_buffers [ $client_channel ] as $i => $packet ) {
2021-10-11 13:47:51 +00:00
switch ( ord ( $packet [ 0 ])) {
case NET_SSH2_MSG_CHANNEL_SUCCESS :
case NET_SSH2_MSG_CHANNEL_FAILURE :
unset ( $this -> channel_buffers [ $client_channel ][ $i ]);
return substr ( $packet , 1 );
}
}
break ;
default :
return substr ( array_shift ( $this -> channel_buffers [ $client_channel ]), 1 );
}
2009-12-14 18:14:54 +00:00
}
2009-05-23 14:42:17 +00:00
while ( true ) {
2017-08-29 03:37:35 +00:00
if ( $this -> binary_packet_buffer !== false ) {
$response = $this -> binary_packet_buffer ;
$this -> binary_packet_buffer = false ;
} else {
2017-11-11 22:13:48 +00:00
$response = $this -> get_binary_packet ( true );
2020-12-16 15:13:05 +00:00
if ( $response === true && $this -> is_timeout ) {
2020-12-16 14:27:50 +00:00
if ( $client_channel == self :: CHANNEL_EXEC && ! $this -> request_pty ) {
2020-12-16 14:36:43 +00:00
$this -> close_channel ( $client_channel );
2020-12-16 14:26:03 +00:00
}
return true ;
2020-12-16 05:11:54 +00:00
}
2017-08-29 03:37:35 +00:00
if ( $response === false ) {
2020-02-24 07:02:26 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2019-09-11 12:55:29 +00:00
throw new ConnectionClosedException ( 'Connection closed by server' );
2012-03-03 17:49:16 +00:00
}
}
2013-07-14 09:09:16 +00:00
if ( $client_channel == - 1 && $response === true ) {
return true ;
}
2019-04-02 05:09:19 +00:00
list ( $type , $channel ) = Strings :: unpackSSH2 ( 'CN' , $response );
2013-05-14 03:37:32 +00:00
2014-12-30 02:44:31 +00:00
// will not be setup yet on incoming channel open request
if ( isset ( $channel ) && isset ( $this -> channel_status [ $channel ]) && isset ( $this -> window_size_server_to_client [ $channel ])) {
2022-02-17 02:25:59 +00:00
$this -> window_size_server_to_client [ $channel ] -= strlen ( $response );
2014-12-30 02:44:31 +00:00
// resize the window, if appropriate
if ( $this -> window_size_server_to_client [ $channel ] < 0 ) {
2019-11-02 17:42:34 +00:00
// PuTTY does something more analogous to the following:
//if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
$packet = pack ( 'CNN' , NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST , $this -> server_channels [ $channel ], $this -> window_resize );
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2022-02-17 02:25:59 +00:00
$this -> window_size_server_to_client [ $channel ] += $this -> window_resize ;
2014-12-30 02:44:31 +00:00
}
2017-12-25 16:26:26 +00:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA :
/*
2019-07-12 12:53:49 +00:00
if ( $client_channel == self :: CHANNEL_EXEC ) {
2018-09-10 15:17:20 +00:00
$this -> send_channel_packet ( $client_channel , chr ( 0 ));
2017-12-25 16:26:26 +00:00
}
*/
// currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
2019-04-02 05:09:19 +00:00
list ( $data_type_code , $data ) = Strings :: unpackSSH2 ( 'Ns' , $response );
2022-02-17 02:25:59 +00:00
$this -> stdErrorLog .= $data ;
2017-12-25 16:26:26 +00:00
if ( $skip_extended || $this -> quiet_mode ) {
continue 2 ;
}
if ( $client_channel == $channel && $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_DATA ) {
return $data ;
}
2021-10-11 13:47:51 +00:00
$this -> channel_buffers [ $channel ][] = chr ( $type ) . $data ;
2017-11-09 06:12:06 +00:00
2017-12-25 16:26:26 +00:00
continue 2 ;
case NET_SSH2_MSG_CHANNEL_REQUEST :
if ( $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_CLOSE ) {
continue 2 ;
}
2019-04-02 05:09:19 +00:00
list ( $value ) = Strings :: unpackSSH2 ( 's' , $response );
2017-12-25 16:26:26 +00:00
switch ( $value ) {
case 'exit-signal' :
2019-04-02 05:09:19 +00:00
list (
, // FALSE
$signal_name ,
, // core dumped
$error_message
) = Strings :: unpackSSH2 ( 'bsbs' , $response );
$this -> errors [] = " SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name " ;
if ( strlen ( $error_message )) {
2022-02-17 02:25:59 +00:00
$this -> errors [ count ( $this -> errors ) - 1 ] .= " \r \n $error_message " ;
2017-12-25 16:26:26 +00:00
}
2017-12-26 01:12:57 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_EOF , $this -> server_channels [ $client_channel ]));
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $channel ]));
2017-12-25 16:26:26 +00:00
$this -> channel_status [ $channel ] = NET_SSH2_MSG_CHANNEL_EOF ;
continue 3 ;
case 'exit-status' :
2019-04-02 05:09:19 +00:00
list (, $this -> exit_status ) = Strings :: unpackSSH2 ( 'CN' , $response );
2017-11-09 06:12:06 +00:00
2017-12-25 16:26:26 +00:00
// "The client MAY ignore these messages."
// -- http://tools.ietf.org/html/rfc4254#section-6.10
continue 3 ;
default :
// "Some systems may not implement signals, in which case they SHOULD ignore this message."
// -- http://tools.ietf.org/html/rfc4254#section-6.9
continue 3 ;
}
2017-11-09 06:12:06 +00:00
}
2014-12-30 02:44:31 +00:00
switch ( $this -> channel_status [ $channel ]) {
2015-01-12 09:13:33 +00:00
case NET_SSH2_MSG_CHANNEL_OPEN :
2014-12-30 02:44:31 +00:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION :
2019-04-02 05:09:19 +00:00
list (
$this -> server_channels [ $channel ],
$window_size ,
$this -> packet_size_client_to_server [ $channel ]
) = Strings :: unpackSSH2 ( 'NNN' , $response );
2016-11-30 01:35:50 +00:00
2015-06-25 04:45:14 +00:00
if ( $window_size < 0 ) {
2022-02-17 02:25:59 +00:00
$window_size &= 0x7FFFFFFF ;
$window_size += 0x80000000 ;
2015-06-25 04:45:14 +00:00
}
2014-12-30 02:44:31 +00:00
$this -> window_size_client_to_server [ $channel ] = $window_size ;
2017-01-08 01:51:56 +00:00
$result = $client_channel == $channel ? true : $this -> get_channel_packet ( $client_channel , $skip_extended );
$this -> on_channel_open ();
2015-02-06 03:28:23 +00:00
return $result ;
2021-10-09 18:21:09 +00:00
case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE :
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 21:23:35 +00:00
throw new \RuntimeException ( 'Unable to open channel' );
2021-10-09 18:21:09 +00:00
default :
if ( $client_channel == $channel ) {
2021-10-09 18:27:40 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
throw new \RuntimeException ( 'Unexpected response to open request' );
2021-10-09 18:21:09 +00:00
}
2021-10-09 18:27:40 +00:00
return $this -> get_channel_packet ( $client_channel , $skip_extended );
2014-12-30 02:44:31 +00:00
}
break ;
case NET_SSH2_MSG_CHANNEL_REQUEST :
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_SUCCESS :
return true ;
case NET_SSH2_MSG_CHANNEL_FAILURE :
return false ;
2021-10-11 13:47:51 +00:00
case NET_SSH2_MSG_CHANNEL_DATA :
2021-10-11 14:15:37 +00:00
list ( $data ) = Strings :: unpackSSH2 ( 's' , $response );
2021-10-11 13:47:51 +00:00
$this -> channel_buffers [ $channel ][] = chr ( $type ) . $data ;
2021-10-11 14:15:37 +00:00
return $this -> get_channel_packet ( $client_channel , $skip_extended );
2014-12-30 02:44:31 +00:00
default :
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 21:23:35 +00:00
throw new \RuntimeException ( 'Unable to fulfill channel request' );
2014-12-30 02:44:31 +00:00
}
case NET_SSH2_MSG_CHANNEL_CLOSE :
2017-01-08 01:51:56 +00:00
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this -> get_channel_packet ( $client_channel , $skip_extended );
2014-12-30 02:44:31 +00:00
}
2009-12-14 18:14:54 +00:00
}
2009-05-23 14:42:17 +00:00
2013-07-14 09:09:16 +00:00
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
2009-05-23 14:42:17 +00:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_DATA :
2011-02-13 07:46:01 +00:00
/*
2014-12-04 21:45:13 +00:00
if ( $channel == self :: CHANNEL_EXEC ) {
2009-12-14 18:14:54 +00:00
// SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
2013-12-25 23:33:08 +00:00
// this actually seems to make things twice as fast. more to the point, the message right after
2009-12-14 18:14:54 +00:00
// SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
// in OpenSSH it slows things down but only by a couple thousandths of a second.
2017-01-08 01:51:56 +00:00
$this -> send_channel_packet ( $channel , chr ( 0 ));
2009-12-14 18:14:54 +00:00
}
2011-02-13 07:46:01 +00:00
*/
2019-04-02 05:09:19 +00:00
list ( $data ) = Strings :: unpackSSH2 ( 's' , $response );
2014-12-30 02:44:31 +00:00
2015-03-29 15:58:05 +00:00
if ( $channel == self :: CHANNEL_AGENT_FORWARD ) {
2019-03-31 20:15:32 +00:00
$agent_response = $this -> agent -> forwardData ( $data );
2014-12-30 02:44:31 +00:00
if ( ! is_bool ( $agent_response )) {
2017-01-08 01:51:56 +00:00
$this -> send_channel_packet ( $channel , $agent_response );
2014-12-30 02:44:31 +00:00
}
break ;
}
2010-02-15 22:24:08 +00:00
if ( $client_channel == $channel ) {
return $data ;
}
2021-10-11 13:47:51 +00:00
$this -> channel_buffers [ $channel ][] = chr ( $type ) . $data ;
2009-05-23 14:42:17 +00:00
break ;
case NET_SSH2_MSG_CHANNEL_CLOSE :
2020-05-29 13:10:03 +00:00
$this -> curTimeout = 5 ;
2012-06-11 02:52:30 +00:00
2014-12-04 21:45:13 +00:00
if ( $this -> bitmap & self :: MASK_SHELL ) {
2022-02-17 02:25:59 +00:00
$this -> bitmap &= ~ self :: MASK_SHELL ;
2012-06-11 02:52:30 +00:00
}
if ( $this -> channel_status [ $channel ] != NET_SSH2_MSG_CHANNEL_EOF ) {
2017-01-08 01:51:56 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $channel ]));
2012-06-11 02:52:30 +00:00
}
$this -> channel_status [ $channel ] = NET_SSH2_MSG_CHANNEL_CLOSE ;
2016-06-29 05:30:28 +00:00
if ( $client_channel == $channel ) {
return true ;
}
2022-02-23 02:48:51 +00:00
// fall-through
2009-05-23 14:42:17 +00:00
case NET_SSH2_MSG_CHANNEL_EOF :
break ;
default :
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2021-10-11 14:15:37 +00:00
throw new \RuntimeException ( " Error reading channel data ( $type ) " );
2009-05-23 14:42:17 +00:00
}
}
}
2007-07-23 05:21:39 +00:00
/**
* Sends Binary Packets
*
* See '6. Binary Packet Protocol' of rfc4253 for more info .
*
2016-04-10 16:30:59 +00:00
* @ param string $data
* @ param string $logged
* @ see self :: _get_binary_packet ()
2022-01-22 18:03:07 +00:00
* @ return void
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
protected function send_binary_packet ( $data , $logged = null )
2007-07-23 05:21:39 +00:00
{
2012-04-24 05:01:55 +00:00
if ( ! is_resource ( $this -> fsock ) || feof ( $this -> fsock )) {
2013-11-15 18:34:31 +00:00
$this -> bitmap = 0 ;
2019-09-11 12:55:29 +00:00
throw new ConnectionClosedException ( 'Connection closed prematurely' );
2007-07-23 05:21:39 +00:00
}
2021-10-27 01:04:53 +00:00
if ( ! isset ( $logged )) {
$logged = $data ;
}
switch ( $this -> compress ) {
2021-12-14 15:34:41 +00:00
case self :: NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH :
2021-10-27 01:04:53 +00:00
if ( ! $this -> isAuthenticated ()) {
break ;
}
2022-02-23 02:48:51 +00:00
// fall-through
2021-12-14 15:34:41 +00:00
case self :: NET_SSH2_COMPRESSION_ZLIB :
2021-10-27 01:04:53 +00:00
if ( ! $this -> regenerate_compression_context ) {
$header = '' ;
} else {
$this -> regenerate_compression_context = false ;
2022-01-15 00:32:23 +00:00
$this -> compress_context = deflate_init ( ZLIB_ENCODING_RAW , [ 'window' => 15 ]);
2021-10-27 01:04:53 +00:00
$header = " \x78 \x9C " ;
}
if ( $this -> compress_context ) {
$data = $header . deflate_add ( $this -> compress_context , $data , ZLIB_PARTIAL_FLUSH );
}
}
2009-05-16 17:09:37 +00:00
2009-02-16 22:22:13 +00:00
// 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
2007-07-23 05:21:39 +00:00
$packet_length = strlen ( $data ) + 9 ;
2018-12-31 20:14:43 +00:00
if ( $this -> encrypt && $this -> encrypt -> usesNonce ()) {
2022-02-17 02:25:59 +00:00
$packet_length -= 4 ;
2018-12-27 14:31:35 +00:00
}
2009-02-16 22:22:13 +00:00
// round up to the nearest $this->encrypt_block_size
2022-02-17 02:25:59 +00:00
$packet_length += (( $this -> encrypt_block_size - 1 ) * $packet_length ) % $this -> encrypt_block_size ;
2007-07-23 05:21:39 +00:00
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen ( $data ) - 5 ;
2019-09-08 22:51:53 +00:00
switch ( true ) {
case $this -> encrypt && $this -> encrypt -> usesNonce () :
2022-01-30 07:52:31 +00:00
case $this -> hmac_create instanceof Hash && $this -> hmac_create_etm :
2022-02-17 02:25:59 +00:00
$padding_length += 4 ;
$packet_length += 4 ;
2018-12-27 14:31:35 +00:00
}
2019-09-08 22:51:53 +00:00
2014-12-02 17:20:40 +00:00
$padding = Random :: string ( $padding_length );
2007-07-23 05:21:39 +00:00
// we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
$packet = pack ( 'NCa*' , $packet_length - 4 , $padding_length , $data . $padding );
2019-09-08 22:51:53 +00:00
$hmac = '' ;
2022-01-30 07:52:31 +00:00
if ( $this -> hmac_create instanceof Hash && ! $this -> hmac_create_etm ) {
2019-09-08 16:23:29 +00:00
if (( $this -> hmac_create -> getHash () & " \xFF \xFF \xFF \xFF " ) == 'umac' ) {
$this -> hmac_create -> setNonce ( " \0 \0 \0 \0 " . pack ( 'N' , $this -> send_seq_no ));
$hmac = $this -> hmac_create -> hash ( $packet );
} else {
$hmac = $this -> hmac_create -> hash ( pack ( 'Na*' , $this -> send_seq_no , $packet ));
}
}
2007-07-23 05:21:39 +00:00
2018-12-31 20:14:43 +00:00
if ( $this -> encrypt ) {
2022-01-30 07:36:02 +00:00
switch ( $this -> encryptName ) {
2019-03-18 11:59:00 +00:00
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$this -> encrypt -> setNonce (
2022-02-02 03:17:10 +00:00
$this -> encryptFixedPart .
2022-01-30 07:36:02 +00:00
$this -> encryptInvocationCounter
2019-03-18 11:59:00 +00:00
);
2022-01-30 07:36:02 +00:00
Strings :: increment_str ( $this -> encryptInvocationCounter );
2019-03-26 13:36:07 +00:00
$this -> encrypt -> setAAD ( $temp = ( $packet & " \xFF \xFF \xFF \xFF " ));
2019-03-18 11:59:00 +00:00
$packet = $temp . $this -> encrypt -> encrypt ( substr ( $packet , 4 ));
break ;
case 'chacha20-poly1305@openssh.com' :
2022-01-30 17:20:45 +00:00
// This should be impossible, but we are checking anyway to narrow the type for Psalm.
if ( ! ( $this -> encrypt instanceof ChaCha20 )) {
throw new \LogicException ( '$this->encrypt is not a ' . ChaCha20 :: class );
}
2019-09-11 04:43:09 +00:00
$nonce = pack ( 'N2' , 0 , $this -> send_seq_no );
2019-03-18 11:59:00 +00:00
$this -> encrypt -> setNonce ( $nonce );
$this -> lengthEncrypt -> setNonce ( $nonce );
2019-03-26 13:36:07 +00:00
$length = $this -> lengthEncrypt -> encrypt ( $packet & " \xFF \xFF \xFF \xFF " );
2019-03-18 11:59:00 +00:00
$this -> encrypt -> setCounter ( 0 );
// this is the same approach that's implemented in Salsa20::createPoly1305Key()
// but we don't want to use the same AEAD construction that RFC8439 describes
// for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
$this -> encrypt -> setPoly1305Key (
$this -> encrypt -> encrypt ( str_repeat ( " \0 " , 32 ))
);
$this -> encrypt -> setAAD ( $length );
$this -> encrypt -> setCounter ( 1 );
$packet = $length . $this -> encrypt -> encrypt ( substr ( $packet , 4 ));
break ;
default :
2022-01-30 07:52:31 +00:00
$packet = $this -> hmac_create instanceof Hash && $this -> hmac_create_etm ?
2019-09-08 22:51:53 +00:00
( $packet & " \xFF \xFF \xFF \xFF " ) . $this -> encrypt -> encrypt ( substr ( $packet , 4 )) :
$this -> encrypt -> encrypt ( $packet );
}
}
2022-01-30 07:52:31 +00:00
if ( $this -> hmac_create instanceof Hash && $this -> hmac_create_etm ) {
2019-09-08 22:51:53 +00:00
if (( $this -> hmac_create -> getHash () & " \xFF \xFF \xFF \xFF " ) == 'umac' ) {
2019-09-11 04:43:09 +00:00
$this -> hmac_create -> setNonce ( " \0 \0 \0 \0 " . pack ( 'N' , $this -> send_seq_no ));
2019-09-08 22:51:53 +00:00
$hmac = $this -> hmac_create -> hash ( $packet );
} else {
2019-09-11 04:43:09 +00:00
$hmac = $this -> hmac_create -> hash ( pack ( 'Na*' , $this -> send_seq_no , $packet ));
2018-12-27 14:31:35 +00:00
}
2007-07-23 05:21:39 +00:00
}
2019-09-11 04:43:09 +00:00
$this -> send_seq_no ++ ;
2022-02-17 02:25:59 +00:00
$packet .= $this -> encrypt && $this -> encrypt -> usesNonce () ? $this -> encrypt -> getTag () : $hmac ;
2007-07-23 05:21:39 +00:00
2013-12-05 17:46:46 +00:00
$start = microtime ( true );
2020-08-17 12:30:46 +00:00
$sent = @ fputs ( $this -> fsock , $packet );
2013-12-05 17:46:46 +00:00
$stop = microtime ( true );
2009-08-29 19:23:25 +00:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2013-12-05 17:46:46 +00:00
$current = microtime ( true );
2021-10-27 01:04:53 +00:00
$message_number = isset ( $this -> message_numbers [ ord ( $logged [ 0 ])]) ? $this -> message_numbers [ ord ( $logged [ 0 ])] : 'UNKNOWN (' . ord ( $logged [ 0 ]) . ')' ;
2012-04-30 16:25:37 +00:00
$message_number = '-> ' . $message_number .
2020-07-08 13:48:48 +00:00
' (since last: ' . round ( $current - $this -> last_packet , 4 ) . ', network: ' . round ( $stop - $start , 4 ) . 's)' ;
2021-10-27 01:48:46 +00:00
$this -> append_log ( $message_number , $logged );
2012-11-22 19:08:30 +00:00
$this -> last_packet = $current ;
2009-08-29 19:23:25 +00:00
}
2019-03-30 19:12:52 +00:00
if ( strlen ( $packet ) != $sent ) {
$this -> bitmap = 0 ;
throw new \RuntimeException ( " Only $sent of " . strlen ( $packet ) . " bytes were sent " );
}
2007-07-23 05:21:39 +00:00
}
2011-06-04 17:06:53 +00:00
/**
* Logs data packets
*
* Makes sure that only the last 1 MB worth of packets will be logged
*
2017-11-05 20:35:27 +00:00
* @ param string $message_number
* @ param string $message
2011-06-04 17:06:53 +00:00
*/
2017-01-08 01:51:56 +00:00
private function append_log ( $message_number , $message )
2011-06-04 17:06:53 +00:00
{
2013-12-26 10:45:24 +00:00
// remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
if ( strlen ( $message_number ) > 2 ) {
2016-07-31 03:18:06 +00:00
Strings :: shift ( $message );
2013-12-26 10:45:24 +00:00
}
2013-07-26 14:57:20 +00:00
2013-12-26 10:45:24 +00:00
switch ( NET_SSH2_LOGGING ) {
// useful for benchmarks
2014-12-04 21:45:13 +00:00
case self :: LOG_SIMPLE :
2013-12-26 10:45:24 +00:00
$this -> message_number_log [] = $message_number ;
break ;
// the most useful log for SSH2
2014-12-04 21:45:13 +00:00
case self :: LOG_COMPLEX :
2013-12-26 10:45:24 +00:00
$this -> message_number_log [] = $message_number ;
2022-02-17 02:25:59 +00:00
$this -> log_size += strlen ( $message );
2013-12-26 10:45:24 +00:00
$this -> message_log [] = $message ;
2014-12-04 21:45:13 +00:00
while ( $this -> log_size > self :: LOG_MAX_SIZE ) {
2022-02-17 02:25:59 +00:00
$this -> log_size -= strlen ( array_shift ( $this -> message_log ));
2013-12-26 10:45:24 +00:00
array_shift ( $this -> message_number_log );
}
break ;
// dump the output out realtime; packets may be interspersed with non packets,
// passwords won't be filtered out and select other packets may not be correctly
// identified
2014-12-04 21:45:13 +00:00
case self :: LOG_REALTIME :
2013-12-26 10:45:24 +00:00
switch ( PHP_SAPI ) {
case 'cli' :
$start = $stop = " \r \n " ;
2012-04-30 16:25:37 +00:00
break ;
2013-12-26 10:45:24 +00:00
default :
$start = '<pre>' ;
$stop = '</pre>' ;
}
2017-01-08 01:51:56 +00:00
echo $start . $this -> format_log ([ $message ], [ $message_number ]) . $stop ;
2013-12-26 10:45:24 +00:00
@ flush ();
@ ob_flush ();
break ;
2017-08-29 12:42:51 +00:00
// basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
2014-12-04 21:45:13 +00:00
// needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
2013-12-26 10:45:24 +00:00
// the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
// at the beginning of the file
2014-12-04 21:45:13 +00:00
case self :: LOG_REALTIME_FILE :
2013-12-26 10:45:24 +00:00
if ( ! isset ( $this -> realtime_log_file )) {
// PHP doesn't seem to like using constants in fopen()
2016-04-30 21:23:35 +00:00
$filename = NET_SSH2_LOG_REALTIME_FILENAME ;
2013-12-26 10:45:24 +00:00
$fp = fopen ( $filename , 'w' );
$this -> realtime_log_file = $fp ;
}
if ( ! is_resource ( $this -> realtime_log_file )) {
break ;
}
2017-01-08 01:51:56 +00:00
$entry = $this -> format_log ([ $message ], [ $message_number ]);
2013-12-26 10:45:24 +00:00
if ( $this -> realtime_log_wrap ) {
$temp = " <<< START >>> \r \n " ;
2022-02-17 02:25:59 +00:00
$entry .= $temp ;
2013-12-26 10:45:24 +00:00
fseek ( $this -> realtime_log_file , ftell ( $this -> realtime_log_file ) - strlen ( $temp ));
}
2022-02-17 02:25:59 +00:00
$this -> realtime_log_size += strlen ( $entry );
2014-12-04 21:45:13 +00:00
if ( $this -> realtime_log_size > self :: LOG_MAX_SIZE ) {
2013-12-26 10:45:24 +00:00
fseek ( $this -> realtime_log_file , 0 );
$this -> realtime_log_size = strlen ( $entry );
$this -> realtime_log_wrap = true ;
}
fputs ( $this -> realtime_log_file , $entry );
}
2011-06-04 17:06:53 +00:00
}
2009-09-17 03:19:20 +00:00
/**
* Sends channel data
*
* Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
*
2016-04-10 16:30:59 +00:00
* @ param int $client_channel
* @ param string $data
2022-03-09 00:59:30 +00:00
* @ return void
2009-09-17 03:19:20 +00:00
*/
2017-01-08 01:51:56 +00:00
protected function send_channel_packet ( $client_channel , $data )
2009-09-17 03:19:20 +00:00
{
2014-12-06 04:01:39 +00:00
while ( strlen ( $data )) {
2013-09-12 13:29:14 +00:00
if ( ! $this -> window_size_client_to_server [ $client_channel ]) {
2022-02-17 02:25:59 +00:00
$this -> bitmap ^= self :: MASK_WINDOW_ADJUST ;
2013-09-12 13:29:14 +00:00
// using an invalid channel will let the buffers be built up for the valid channels
2017-01-08 01:51:56 +00:00
$this -> get_channel_packet ( - 1 );
2022-02-17 02:25:59 +00:00
$this -> bitmap ^= self :: MASK_WINDOW_ADJUST ;
2013-09-12 13:29:14 +00:00
}
2014-12-06 04:01:39 +00:00
/* The maximum amount of data allowed is determined by the maximum
packet size for the channel , and the current window size , whichever
is smaller .
-- http :// tools . ietf . org / html / rfc4254 #section-5.2 */
$max_size = min (
$this -> packet_size_client_to_server [ $client_channel ],
$this -> window_size_client_to_server [ $client_channel ]
);
2016-07-31 03:18:06 +00:00
$temp = Strings :: shift ( $data , $max_size );
2019-04-02 05:09:19 +00:00
$packet = Strings :: packSSH2 (
'CNs' ,
2009-09-17 03:19:20 +00:00
NET_SSH2_MSG_CHANNEL_DATA ,
2009-12-14 18:14:54 +00:00
$this -> server_channels [ $client_channel ],
2014-03-21 07:53:43 +00:00
$temp
2009-09-17 03:19:20 +00:00
);
2022-02-17 02:25:59 +00:00
$this -> window_size_client_to_server [ $client_channel ] -= strlen ( $temp );
2019-03-30 19:12:52 +00:00
$this -> send_binary_packet ( $packet );
2013-07-14 09:09:16 +00:00
}
2009-09-17 03:19:20 +00:00
}
2011-02-13 07:46:01 +00:00
/**
* Closes and flushes a channel
*
2019-11-07 05:41:40 +00:00
* \phpseclib3\Net\SSH2 doesn ' t properly close most channels . For exec () channels are normally closed by the server
2011-02-13 07:46:01 +00:00
* and for SFTP channels are presumably closed when the client disconnects . This functions is intended
* for SCP more than anything .
*
2016-04-10 16:30:59 +00:00
* @ param int $client_channel
* @ param bool $want_reply
2022-01-22 18:03:07 +00:00
* @ return void
2011-02-13 07:46:01 +00:00
*/
2017-01-08 01:51:56 +00:00
private function close_channel ( $client_channel , $want_reply = false )
2011-02-13 07:46:01 +00:00
{
// see http://tools.ietf.org/html/rfc4254#section-5.3
2017-01-08 01:51:56 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_EOF , $this -> server_channels [ $client_channel ]));
2012-03-05 02:06:13 +00:00
2013-12-15 06:43:20 +00:00
if ( ! $want_reply ) {
2017-01-08 01:51:56 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $client_channel ]));
2013-12-15 06:43:20 +00:00
}
2011-02-13 07:46:01 +00:00
2012-03-03 17:49:16 +00:00
$this -> channel_status [ $client_channel ] = NET_SSH2_MSG_CHANNEL_CLOSE ;
2020-05-29 13:10:03 +00:00
$this -> curTimeout = 5 ;
2012-03-03 17:49:16 +00:00
2017-01-08 01:51:56 +00:00
while ( ! is_bool ( $this -> get_channel_packet ( $client_channel ))) {
2015-07-15 01:52:31 +00:00
}
2012-03-03 17:49:16 +00:00
2020-05-29 13:10:03 +00:00
if ( $this -> is_timeout ) {
$this -> disconnect ();
}
2013-12-15 06:43:20 +00:00
if ( $want_reply ) {
2017-01-08 01:51:56 +00:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $client_channel ]));
2013-12-15 06:43:20 +00:00
}
2014-12-04 21:45:13 +00:00
if ( $this -> bitmap & self :: MASK_SHELL ) {
2022-02-17 02:25:59 +00:00
$this -> bitmap &= ~ self :: MASK_SHELL ;
2012-03-03 17:49:16 +00:00
}
2011-02-13 07:46:01 +00:00
}
2007-07-23 05:21:39 +00:00
/**
* Disconnect
*
2016-04-10 16:30:59 +00:00
* @ param int $reason
2022-01-22 18:03:07 +00:00
* @ return false
2007-07-23 05:21:39 +00:00
*/
2017-08-03 07:19:11 +00:00
protected function disconnect_helper ( $reason )
2007-07-23 05:21:39 +00:00
{
2015-01-04 10:38:03 +00:00
if ( $this -> bitmap & self :: MASK_CONNECTED ) {
2019-04-02 05:09:19 +00:00
$data = Strings :: packSSH2 ( 'CNss' , NET_SSH2_MSG_DISCONNECT , $reason , '' , '' );
2020-02-24 07:02:26 +00:00
try {
$this -> send_binary_packet ( $data );
} catch ( \Exception $e ) {
}
2008-05-15 17:40:03 +00:00
}
2019-09-10 22:31:43 +00:00
$this -> bitmap = 0 ;
2022-01-28 19:14:38 +00:00
if ( is_resource ( $this -> fsock ) && get_resource_type ( $this -> fsock ) === 'stream' ) {
2019-09-16 12:55:47 +00:00
fclose ( $this -> fsock );
}
2019-09-10 22:31:43 +00:00
return false ;
2007-07-23 05:21:39 +00:00
}
2008-05-26 19:42:01 +00:00
/**
* Define Array
*
* Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
* named constants from it , using the value as the name of the constant and the index as the value of the constant .
* If any of the constants that would be defined already exists , none of the constants will be defined .
*
2020-09-09 08:01:39 +00:00
* @ param mixed [] ... $args
2017-08-04 10:06:25 +00:00
* @ access protected
2008-05-26 19:42:01 +00:00
*/
2017-11-21 08:36:28 +00:00
protected function define_array ( ... $args )
2008-05-26 19:42:01 +00:00
{
foreach ( $args as $arg ) {
2015-07-15 01:52:31 +00:00
foreach ( $arg as $key => $value ) {
2008-05-26 19:42:01 +00:00
if ( ! defined ( $value )) {
define ( $value , $key );
} else {
break 2 ;
}
}
}
}
2009-03-25 22:29:42 +00:00
/**
* Returns a log of the packets that have been sent and received .
*
2014-12-04 21:45:13 +00:00
* 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' )
2009-03-25 22:29:42 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return array | false | string
2009-03-25 22:29:42 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getLog ()
2009-03-25 22:29:42 +00:00
{
2010-04-07 03:50:54 +00:00
if ( ! defined ( 'NET_SSH2_LOGGING' )) {
return false ;
}
switch ( NET_SSH2_LOGGING ) {
2014-12-04 21:45:13 +00:00
case self :: LOG_SIMPLE :
2010-04-07 03:50:54 +00:00
return $this -> message_number_log ;
2014-12-04 21:45:13 +00:00
case self :: LOG_COMPLEX :
2017-05-28 14:44:38 +00:00
$log = $this -> format_log ( $this -> message_log , $this -> message_number_log );
2017-03-02 04:14:58 +00:00
return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>' ;
2010-04-07 03:50:54 +00:00
default :
return false ;
2009-03-25 22:29:42 +00:00
}
2010-04-07 03:50:54 +00:00
}
2009-03-25 22:29:42 +00:00
2010-04-07 03:50:54 +00:00
/**
* Formats a log for printing
*
2016-04-10 16:30:59 +00:00
* @ param array $message_log
* @ param array $message_number_log
* @ return string
2010-04-07 03:50:54 +00:00
*/
2017-01-08 01:51:56 +00:00
protected function format_log ( $message_log , $message_number_log )
2010-04-07 03:50:54 +00:00
{
2009-03-25 22:29:42 +00:00
$output = '' ;
2010-04-07 03:50:54 +00:00
for ( $i = 0 ; $i < count ( $message_log ); $i ++ ) {
2022-02-17 02:25:59 +00:00
$output .= $message_number_log [ $i ] . " \r \n " ;
2010-04-07 03:50:54 +00:00
$current_log = $message_log [ $i ];
2010-04-24 06:40:49 +00:00
$j = 0 ;
2009-03-25 22:29:42 +00:00
do {
2013-01-12 16:46:19 +00:00
if ( strlen ( $current_log )) {
2022-02-17 02:25:59 +00:00
$output .= str_pad ( dechex ( $j ), 7 , '0' , STR_PAD_LEFT ) . '0 ' ;
2010-04-24 06:40:49 +00:00
}
2016-07-31 03:18:06 +00:00
$fragment = Strings :: shift ( $current_log , $this -> log_short_width );
2020-04-05 15:29:57 +00:00
$hex = substr ( preg_replace_callback ( '#.#s' , function ( $matches ) {
return $this -> log_boundary . str_pad ( dechex ( ord ( $matches [ 0 ])), 2 , '0' , STR_PAD_LEFT );
}, $fragment ), strlen ( $this -> log_boundary ));
2009-03-25 22:29:42 +00:00
// replace non ASCII printable characters with dots
// http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
2010-04-25 14:19:43 +00:00
// also replace < with a . since < messes up the output on web browsers
$raw = preg_replace ( '#[^\x20-\x7E]|<#' , '.' , $fragment );
2022-02-17 02:25:59 +00:00
$output .= str_pad ( $hex , $this -> log_long_width - $this -> log_short_width , ' ' ) . $raw . " \r \n " ;
2010-04-24 06:40:49 +00:00
$j ++ ;
2013-01-12 16:46:19 +00:00
} while ( strlen ( $current_log ));
2022-02-17 02:25:59 +00:00
$output .= " \r \n " ;
2009-03-25 22:29:42 +00:00
}
return $output ;
}
2015-02-06 03:28:23 +00:00
/**
2017-01-08 01:51:56 +00:00
* Helper function for agent -> on_channel_open ()
2015-02-06 03:28:23 +00:00
*
* Used when channels are created to inform agent
* of said channel opening . Must be called after
* channel open confirmation received
*
*/
2017-01-08 01:51:56 +00:00
private function on_channel_open ()
2015-02-06 03:28:23 +00:00
{
if ( isset ( $this -> agent )) {
2019-03-31 20:15:32 +00:00
$this -> agent -> registerChannelOpen ( $this );
2015-02-06 03:28:23 +00:00
}
}
2015-07-17 16:20:42 +00:00
/**
* Returns the first value of the intersection of two arrays or false if
* the intersection is empty . The order is defined by the first parameter .
*
2016-04-10 16:30:59 +00:00
* @ param array $array1
* @ param array $array2
* @ return mixed False if intersection is empty , else intersected value .
2015-07-17 16:20:42 +00:00
*/
2019-09-11 04:14:29 +00:00
private static function array_intersect_first ( $array1 , $array2 )
2015-07-17 16:20:42 +00:00
{
foreach ( $array1 as $value ) {
if ( in_array ( $value , $array2 )) {
return $value ;
}
}
return false ;
}
2007-07-23 05:21:39 +00:00
/**
2010-02-12 23:02:13 +00:00
* Returns all errors
2007-07-23 05:21:39 +00:00
*
2016-04-13 09:59:55 +00:00
* @ return string []
2010-02-12 23:02:13 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getErrors ()
2010-02-12 23:02:13 +00:00
{
return $this -> errors ;
}
/**
* Returns the last error
2007-07-23 05:21:39 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return string
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getLastError ()
2007-07-23 05:21:39 +00:00
{
2016-04-10 16:30:59 +00:00
$count = count ( $this -> errors );
if ( $count > 0 ) {
return $this -> errors [ $count - 1 ];
}
2007-07-23 05:21:39 +00:00
}
/**
* Return the server identification .
*
2022-01-22 18:03:07 +00:00
* @ return string | false
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getServerIdentification ()
2007-07-23 05:21:39 +00:00
{
2017-01-08 01:51:56 +00:00
$this -> connect ();
2014-07-25 15:03:31 +00:00
2007-07-23 05:21:39 +00:00
return $this -> server_identifier ;
}
/**
2019-03-29 23:44:31 +00:00
* Returns a list of algorithms the server supports
2007-07-23 05:21:39 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return array
2007-07-23 05:21:39 +00:00
*/
2019-09-25 03:21:07 +00:00
public function getServerAlgorithms ()
2007-07-23 05:21:39 +00:00
{
2017-01-08 01:51:56 +00:00
$this -> connect ();
2014-07-25 15:03:31 +00:00
2019-03-29 23:44:31 +00:00
return [
'kex' => $this -> kex_algorithms ,
'hostkey' => $this -> server_host_key_algorithms ,
'client_to_server' => [
'crypt' => $this -> encryption_algorithms_client_to_server ,
'mac' => $this -> mac_algorithms_client_to_server ,
'comp' => $this -> compression_algorithms_client_to_server ,
'lang' => $this -> languages_client_to_server
],
'server_to_client' => [
'crypt' => $this -> encryption_algorithms_server_to_client ,
'mac' => $this -> mac_algorithms_server_to_client ,
'comp' => $this -> compression_algorithms_server_to_client ,
'lang' => $this -> languages_server_to_client
]
];
2007-07-23 05:21:39 +00:00
}
2019-03-29 02:45:28 +00:00
/**
* Returns a list of KEX algorithms that phpseclib supports
*
* @ return array
*/
public static function getSupportedKEXAlgorithms ()
{
$kex_algorithms = [
// Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
// Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
// libssh repository for more information.
2019-07-27 22:28:18 +00:00
'curve25519-sha256' ,
2019-03-29 02:45:28 +00:00
'curve25519-sha256@libssh.org' ,
2019-07-27 22:28:18 +00:00
'ecdh-sha2-nistp256' , // RFC 5656
'ecdh-sha2-nistp384' , // RFC 5656
'ecdh-sha2-nistp521' , // RFC 5656
'diffie-hellman-group-exchange-sha256' , // RFC 4419
'diffie-hellman-group-exchange-sha1' , // RFC 4419
2019-03-29 02:45:28 +00:00
// Diffie-Hellman Key Agreement (DH) using integer modulo prime
// groups.
2019-07-27 22:28:18 +00:00
'diffie-hellman-group14-sha256' ,
2019-03-29 02:45:28 +00:00
'diffie-hellman-group14-sha1' , // REQUIRED
2019-07-27 22:28:18 +00:00
'diffie-hellman-group15-sha512' ,
'diffie-hellman-group16-sha512' ,
'diffie-hellman-group17-sha512' ,
'diffie-hellman-group18-sha512' ,
2019-03-29 02:45:28 +00:00
2019-07-27 22:28:18 +00:00
'diffie-hellman-group1-sha1' , // REQUIRED
];
2019-03-29 02:45:28 +00:00
return $kex_algorithms ;
}
/**
* Returns a list of host key algorithms that phpseclib supports
*
* @ return array
*/
public static function getSupportedHostKeyAlgorithms ()
{
return [
2019-03-30 18:35:16 +00:00
'ssh-ed25519' , // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
'ecdsa-sha2-nistp256' , // RFC 5656
2019-05-19 20:35:29 +00:00
'ecdsa-sha2-nistp384' , // RFC 5656
'ecdsa-sha2-nistp521' , // RFC 5656
2019-03-29 02:45:28 +00:00
'rsa-sha2-256' , // RFC 8332
'rsa-sha2-512' , // RFC 8332
'ssh-rsa' , // RECOMMENDED sign Raw RSA Key
'ssh-dss' // REQUIRED sign Raw DSS Key
];
}
/**
* Returns a list of symmetric key algorithms that phpseclib supports
*
* @ return array
*/
public static function getSupportedEncryptionAlgorithms ()
{
$algos = [
// from <https://tools.ietf.org/html/rfc5647>:
'aes128-gcm@openssh.com' ,
'aes256-gcm@openssh.com' ,
// from <http://tools.ietf.org/html/rfc4345#section-4>:
'arcfour256' ,
'arcfour128' ,
//'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
// CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
'aes128-ctr' , // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
'aes192-ctr' , // RECOMMENDED AES with 192-bit key
'aes256-ctr' , // RECOMMENDED AES with 256-bit key
2022-05-06 21:25:22 +00:00
// from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>:
2019-03-29 02:45:28 +00:00
// one of the big benefits of chacha20-poly1305 is speed. the problem is...
// libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
// seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
// part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
// speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
// (which is always gonna be super fast to compute thanks to the hash extension, which
// "is bundled and compiled into PHP by default")
'chacha20-poly1305@openssh.com' ,
'twofish128-ctr' , // OPTIONAL Twofish in SDCTR mode, with 128-bit key
'twofish192-ctr' , // OPTIONAL Twofish with 192-bit key
'twofish256-ctr' , // OPTIONAL Twofish with 256-bit key
'aes128-cbc' , // RECOMMENDED AES with a 128-bit key
'aes192-cbc' , // OPTIONAL AES with a 192-bit key
'aes256-cbc' , // OPTIONAL AES in CBC mode, with a 256-bit key
'twofish128-cbc' , // OPTIONAL Twofish with a 128-bit key
'twofish192-cbc' , // OPTIONAL Twofish with a 192-bit key
'twofish256-cbc' ,
'twofish-cbc' , // OPTIONAL alias for "twofish256-cbc"
// (this is being retained for historical reasons)
'blowfish-ctr' , // OPTIONAL Blowfish in SDCTR mode
'blowfish-cbc' , // OPTIONAL Blowfish in CBC mode
'3des-ctr' , // RECOMMENDED Three-key 3DES in SDCTR mode
'3des-cbc' , // REQUIRED three-key 3DES in CBC mode
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
];
2020-08-01 02:27:38 +00:00
if ( self :: $crypto_engine ) {
$engines = [ self :: $crypto_engine ];
2020-07-31 13:05:31 +00:00
} else {
2020-08-01 02:27:38 +00:00
$engines = [
'libsodium' ,
'OpenSSL (GCM)' ,
'OpenSSL' ,
'mcrypt' ,
'Eval' ,
'PHP'
2022-01-29 17:35:38 +00:00
];
2020-07-31 13:05:31 +00:00
}
2019-03-29 02:45:28 +00:00
$ciphers = [];
2020-08-01 02:27:38 +00:00
2019-03-29 02:45:28 +00:00
foreach ( $engines as $engine ) {
foreach ( $algos as $algo ) {
$obj = self :: encryption_algorithm_to_crypt_instance ( $algo );
2021-04-13 13:05:55 +00:00
if ( $obj instanceof Rijndael ) {
2019-03-29 02:45:28 +00:00
$obj -> setKeyLength ( preg_replace ( '#[^\d]#' , '' , $algo ));
}
switch ( $algo ) {
case 'chacha20-poly1305@openssh.com' :
case 'arcfour128' :
case 'arcfour256' :
2019-09-28 18:05:42 +00:00
if ( $engine != 'Eval' ) {
2019-03-29 02:45:28 +00:00
continue 2 ;
}
break ;
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
if ( $engine == 'OpenSSL' ) {
continue 2 ;
}
$obj -> setNonce ( 'dummydummydu' );
}
if ( $obj -> isValidEngine ( $engine )) {
$algos = array_diff ( $algos , [ $algo ]);
$ciphers [] = $algo ;
}
}
}
return $ciphers ;
}
/**
* Returns a list of MAC algorithms that phpseclib supports
*
* @ return array
*/
public static function getSupportedMACAlgorithms ()
{
return [
2019-09-08 22:51:53 +00:00
'hmac-sha2-256-etm@openssh.com' ,
'hmac-sha2-512-etm@openssh.com' ,
'umac-64-etm@openssh.com' ,
'umac-128-etm@openssh.com' ,
'hmac-sha1-etm@openssh.com' ,
2019-03-29 02:45:28 +00:00
// from <http://www.ietf.org/rfc/rfc6668.txt>:
'hmac-sha2-256' , // RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
2019-09-08 22:51:53 +00:00
'hmac-sha2-512' , // OPTIONAL HMAC-SHA512 (digest length = key length = 64)
2019-03-29 02:45:28 +00:00
2019-09-08 16:23:29 +00:00
// from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
'umac-64@openssh.com' ,
'umac-128@openssh.com' ,
2019-03-29 02:45:28 +00:00
'hmac-sha1-96' , // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
'hmac-sha1' , // REQUIRED HMAC-SHA1 (digest length = key length = 20)
'hmac-md5-96' , // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
'hmac-md5' , // OPTIONAL HMAC-MD5 (digest length = key length = 16)
//'none' // OPTIONAL no MAC; NOT RECOMMENDED
];
}
/**
* Returns a list of compression algorithms that phpseclib supports
*
* @ return array
*/
public static function getSupportedCompressionAlgorithms ()
{
2021-10-27 01:48:46 +00:00
$algos = [ 'none' ]; // REQUIRED no compression
2021-10-27 01:04:53 +00:00
if ( function_exists ( 'deflate_init' )) {
$algos [] = 'zlib@openssh.com' ; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed
$algos [] = 'zlib' ;
}
return $algos ;
2019-03-29 02:45:28 +00:00
}
2019-03-28 18:57:26 +00:00
/**
2019-03-29 23:44:31 +00:00
* Return list of negotiated algorithms
2019-03-28 18:57:26 +00:00
*
* Uses the same format as https :// www . php . net / ssh2 - methods - negotiated
*
* @ return array
*/
2019-03-29 23:44:31 +00:00
public function getAlgorithmsNegotiated ()
2019-03-28 18:57:26 +00:00
{
$this -> connect ();
2021-10-27 01:48:46 +00:00
$compression_map = [
2021-12-14 15:34:41 +00:00
self :: NET_SSH2_COMPRESSION_NONE => 'none' ,
self :: NET_SSH2_COMPRESSION_ZLIB => 'zlib' ,
self :: NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com'
2021-10-27 01:48:46 +00:00
];
2021-10-27 01:04:53 +00:00
2019-03-28 18:57:26 +00:00
return [
'kex' => $this -> kex_algorithm ,
'hostkey' => $this -> signature_format ,
'client_to_server' => [
2022-01-30 07:36:02 +00:00
'crypt' => $this -> encryptName ,
2022-01-30 07:52:31 +00:00
'mac' => $this -> hmac_create_name ,
2021-10-27 01:04:53 +00:00
'comp' => $compression_map [ $this -> compress ],
2019-03-28 18:57:26 +00:00
],
'server_to_client' => [
2022-01-30 07:36:02 +00:00
'crypt' => $this -> decryptName ,
2022-01-30 07:52:31 +00:00
'mac' => $this -> hmac_check_name ,
2021-10-27 01:04:53 +00:00
'comp' => $compression_map [ $this -> decompress ],
2019-03-28 18:57:26 +00:00
]
];
}
2021-03-17 02:18:56 +00:00
/**
* Allows you to set the terminal
*
* @ param string $term
*/
public function setTerminal ( $term )
{
$this -> term = $term ;
}
2019-03-29 23:44:31 +00:00
/**
* Accepts an associative array with up to four parameters as described at
* < https :// www . php . net / manual / en / function . ssh2 - connect . php >
*
* @ param array $methods
*/
public function setPreferredAlgorithms ( array $methods )
{
$preferred = $methods ;
if ( isset ( $preferred [ 'kex' ])) {
$preferred [ 'kex' ] = array_intersect (
$preferred [ 'kex' ],
static :: getSupportedKEXAlgorithms ()
);
}
if ( isset ( $preferred [ 'hostkey' ])) {
$preferred [ 'hostkey' ] = array_intersect (
$preferred [ 'hostkey' ],
static :: getSupportedHostKeyAlgorithms ()
);
}
$keys = [ 'client_to_server' , 'server_to_client' ];
foreach ( $keys as $key ) {
if ( isset ( $preferred [ $key ])) {
$a = & $preferred [ $key ];
if ( isset ( $a [ 'crypt' ])) {
$a [ 'crypt' ] = array_intersect (
$a [ 'crypt' ],
static :: getSupportedEncryptionAlgorithms ()
);
}
if ( isset ( $a [ 'comp' ])) {
$a [ 'comp' ] = array_intersect (
$a [ 'comp' ],
static :: getSupportedCompressionAlgorithms ()
);
}
if ( isset ( $a [ 'mac' ])) {
$a [ 'mac' ] = array_intersect (
$a [ 'mac' ],
static :: getSupportedMACAlgorithms ()
);
}
}
}
$keys = [
'kex' ,
'hostkey' ,
'client_to_server/crypt' ,
'client_to_server/comp' ,
'client_to_server/mac' ,
'server_to_client/crypt' ,
'server_to_client/comp' ,
'server_to_client/mac' ,
];
foreach ( $keys as $key ) {
$p = $preferred ;
$m = $methods ;
$subkeys = explode ( '/' , $key );
foreach ( $subkeys as $subkey ) {
if ( ! isset ( $p [ $subkey ])) {
continue 2 ;
}
$p = $p [ $subkey ];
$m = $m [ $subkey ];
}
if ( count ( $p ) != count ( $m )) {
$diff = array_diff ( $m , $p );
$msg = count ( $diff ) == 1 ?
' is not a supported algorithm' :
' are not supported algorithms' ;
throw new UnsupportedAlgorithmException ( implode ( ', ' , $diff ) . $msg );
}
}
$this -> preferred = $preferred ;
}
2013-04-28 00:58:24 +00:00
/**
* Returns the banner message .
*
* Quoting from the RFC , " in some jurisdictions, sending a warning message before
* authentication may be relevant for getting legal protection . "
*
2016-04-10 16:30:59 +00:00
* @ return string
2013-04-28 00:58:24 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getBannerMessage ()
2013-04-28 00:58:24 +00:00
{
return $this -> banner_message ;
}
2007-07-23 05:21:39 +00:00
/**
* Returns the server public host key .
*
* Caching this the first time you connect to a server and checking the result on subsequent connections
2010-04-22 16:06:43 +00:00
* is recommended . Returns false if the server signature is not signed correctly with the public host key .
2007-07-23 05:21:39 +00:00
*
2022-01-22 18:03:07 +00:00
* @ return string | false
2016-04-30 21:23:35 +00:00
* @ throws \RuntimeException on badly formatted keys
2019-11-07 05:41:40 +00:00
* @ throws \phpseclib3\Exception\NoSupportedAlgorithmsException when the key isn ' t in a supported format
2007-07-23 05:21:39 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getServerPublicHostKey ()
2007-07-23 05:21:39 +00:00
{
2014-12-04 21:45:13 +00:00
if ( ! ( $this -> bitmap & self :: MASK_CONSTRUCTOR )) {
2021-05-21 13:49:52 +00:00
$this -> connect ();
2014-06-16 15:19:34 +00:00
}
2010-04-22 16:06:43 +00:00
$signature = $this -> signature ;
2019-03-30 18:35:16 +00:00
$server_public_host_key = base64_encode ( $this -> server_public_host_key );
2010-04-22 16:06:43 +00:00
2012-07-04 18:36:26 +00:00
if ( $this -> signature_validated ) {
return $this -> bitmap ?
2019-05-19 20:35:29 +00:00
$this -> signature_format . ' ' . $server_public_host_key :
2012-07-04 18:36:26 +00:00
false ;
}
$this -> signature_validated = true ;
2010-04-22 16:06:43 +00:00
switch ( $this -> signature_format ) {
2019-03-30 18:35:16 +00:00
case 'ssh-ed25519' :
case 'ecdsa-sha2-nistp256' :
2019-05-19 20:35:29 +00:00
case 'ecdsa-sha2-nistp384' :
case 'ecdsa-sha2-nistp521' :
2019-06-28 10:32:38 +00:00
$key = EC :: loadFormat ( 'OpenSSH' , $server_public_host_key )
2019-05-19 20:35:29 +00:00
-> withSignatureFormat ( 'SSH2' );
2019-03-30 18:35:16 +00:00
switch ( $this -> signature_format ) {
case 'ssh-ed25519' :
2019-05-19 20:35:29 +00:00
$hash = 'sha512' ;
2019-03-30 18:35:16 +00:00
break ;
case 'ecdsa-sha2-nistp256' :
2019-05-19 20:35:29 +00:00
$hash = 'sha256' ;
break ;
case 'ecdsa-sha2-nistp384' :
$hash = 'sha384' ;
break ;
case 'ecdsa-sha2-nistp521' :
$hash = 'sha512' ;
2010-04-22 16:06:43 +00:00
}
2019-05-19 20:35:29 +00:00
$key = $key -> withHash ( $hash );
2019-03-30 18:35:16 +00:00
break ;
case 'ssh-dss' :
2019-06-28 10:32:38 +00:00
$key = DSA :: loadFormat ( 'OpenSSH' , $server_public_host_key )
2019-05-19 20:35:29 +00:00
-> withSignatureFormat ( 'SSH2' )
-> withHash ( 'sha1' );
2010-04-22 16:06:43 +00:00
break ;
case 'ssh-rsa' :
2018-05-27 14:48:44 +00:00
case 'rsa-sha2-256' :
case 'rsa-sha2-512' :
2022-01-27 11:51:06 +00:00
// could be ssh-rsa, rsa-sha2-256, rsa-sha2-512
// we don't check here because we already checked in key_exchange
// some signatures have the type embedded within the message and some don't
2022-01-28 00:26:08 +00:00
list (, $signature ) = Strings :: unpackSSH2 ( 'ss' , $signature );
2010-04-22 16:06:43 +00:00
2019-06-28 10:32:38 +00:00
$key = RSA :: loadFormat ( 'OpenSSH' , $server_public_host_key )
2019-05-19 20:35:29 +00:00
-> withPadding ( RSA :: SIGNATURE_PKCS1 );
2018-05-27 14:48:44 +00:00
switch ( $this -> signature_format ) {
case 'rsa-sha2-512' :
$hash = 'sha512' ;
break ;
case 'rsa-sha2-256' :
$hash = 'sha256' ;
break ;
//case 'ssh-rsa':
default :
$hash = 'sha1' ;
}
2019-05-19 20:35:29 +00:00
$key = $key -> withHash ( $hash );
2012-07-04 18:36:26 +00:00
break ;
default :
2017-01-08 01:51:56 +00:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE );
2016-04-30 21:23:35 +00:00
throw new NoSupportedAlgorithmsException ( 'Unsupported signature format' );
2010-04-22 16:06:43 +00:00
}
2019-05-19 20:35:29 +00:00
if ( ! $key -> verify ( $this -> exchange_hash , $signature )) {
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE );
};
return $this -> signature_format . ' ' . $server_public_host_key ;
2007-07-23 05:21:39 +00:00
}
2013-01-17 18:47:42 +00:00
/**
* Returns the exit status of an SSH command or false .
*
2016-04-10 16:30:59 +00:00
* @ return false | int
2013-01-17 18:47:42 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getExitStatus ()
2013-01-17 18:47:42 +00:00
{
if ( is_null ( $this -> exit_status )) {
return false ;
}
return $this -> exit_status ;
}
2014-06-22 19:30:52 +00:00
2014-06-20 10:04:17 +00:00
/**
* Returns the number of columns for the terminal window size .
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return int
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getWindowColumns ()
2014-06-20 10:04:17 +00:00
{
return $this -> windowColumns ;
}
2014-06-22 19:30:52 +00:00
2014-06-20 10:04:17 +00:00
/**
* Returns the number of rows for the terminal window size .
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ return int
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
public function getWindowRows ()
2014-06-20 10:04:17 +00:00
{
return $this -> windowRows ;
}
2014-06-22 19:30:52 +00:00
2014-06-20 10:04:17 +00:00
/**
* Sets the number of columns for the terminal window size .
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param int $value
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
public function setWindowColumns ( $value )
2014-06-20 10:04:17 +00:00
{
$this -> windowColumns = $value ;
}
2014-06-22 19:30:52 +00:00
2014-06-20 10:04:17 +00:00
/**
* Sets the number of rows for the terminal window size .
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param int $value
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
public function setWindowRows ( $value )
2014-06-20 10:04:17 +00:00
{
$this -> windowRows = $value ;
}
2014-06-22 19:30:52 +00:00
2014-06-20 10:04:17 +00:00
/**
* Sets the number of columns and rows for the terminal window size .
2014-07-20 21:03:06 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param int $columns
* @ param int $rows
2014-06-20 10:04:17 +00:00
*/
2017-01-08 01:51:56 +00:00
public function setWindowSize ( $columns = 80 , $rows = 24 )
2014-06-20 10:04:17 +00:00
{
$this -> windowColumns = $columns ;
$this -> windowRows = $rows ;
}
2016-04-30 21:23:35 +00:00
/**
2017-01-08 01:51:56 +00:00
* To String Magic Method
*
2016-04-30 21:23:35 +00:00
* @ return string
*/
2022-01-30 17:20:45 +00:00
#[\ReturnTypeWillChange]
2017-01-08 01:51:56 +00:00
public function __toString ()
2016-04-30 21:23:35 +00:00
{
return $this -> getResourceId ();
}
/**
2017-01-08 01:51:56 +00:00
* Get Resource ID
*
2016-04-30 21:23:35 +00:00
* We use {} because that symbols should not be in URL according to
* { @ link http :// tools . ietf . org / html / rfc3986 #section-2 RFC}.
* It will safe us from any conflicts , because otherwise regexp will
* match all alphanumeric domains .
*
* @ return string
*/
2017-01-08 01:51:56 +00:00
public function getResourceId ()
2016-04-30 21:23:35 +00:00
{
return '{' . spl_object_hash ( $this ) . '}' ;
}
/**
* Return existing connection
*
* @ param string $id
*
* @ return bool | SSH2 will return false if no such connection
*/
2017-01-08 01:51:56 +00:00
public static function getConnectionByResourceId ( $id )
2016-04-30 21:23:35 +00:00
{
2021-06-19 15:14:45 +00:00
if ( isset ( self :: $connections [ $id ])) {
return self :: $connections [ $id ] instanceof \WeakReference ? self :: $connections [ $id ] -> get () : self :: $connections [ $id ];
}
return false ;
2016-04-30 21:23:35 +00:00
}
/**
* Return all excising connections
*
2022-01-28 19:14:38 +00:00
* @ return array < string , SSH2 >
2016-04-30 21:23:35 +00:00
*/
2017-01-08 01:51:56 +00:00
public static function getConnections ()
2016-04-30 21:23:35 +00:00
{
2021-06-19 15:14:45 +00:00
if ( ! class_exists ( 'WeakReference' )) {
2022-01-28 19:14:38 +00:00
/** @var array<string, SSH2> */
2021-06-19 15:14:45 +00:00
return self :: $connections ;
}
$temp = [];
2022-02-17 02:25:59 +00:00
foreach ( self :: $connections as $key => $ref ) {
2021-06-19 15:14:45 +00:00
$temp [ $key ] = $ref -> get ();
}
return $temp ;
2016-04-30 21:23:35 +00:00
}
2020-01-17 10:09:49 +00:00
2020-01-17 11:10:12 +00:00
/*
2020-01-17 09:37:25 +00:00
* Update packet types in log history
*
* @ param string $old
* @ param string $new
*/
2020-01-17 10:09:49 +00:00
private function updateLogHistory ( $old , $new )
2020-01-17 09:37:25 +00:00
{
2020-01-17 09:59:18 +00:00
if ( defined ( 'NET_SSH2_LOGGING' ) && NET_SSH2_LOGGING == self :: LOG_COMPLEX ) {
2020-01-17 09:37:25 +00:00
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ] = str_replace (
$old ,
$new ,
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ]
);
}
}
2021-04-29 21:22:24 +00:00
/**
* Return the list of authentication methods that may productively continue authentication .
2022-01-22 18:03:07 +00:00
*
2021-04-29 21:22:24 +00:00
* @ see https :// tools . ietf . org / html / rfc4252 #section-5.1
* @ return array | null
*/
2021-10-09 18:00:30 +00:00
public function getAuthMethodsToContinue ()
2021-04-29 21:22:24 +00:00
{
return $this -> auth_methods_to_continue ;
}
2021-11-04 03:16:14 +00:00
/**
* Enables " smart " multi - factor authentication ( MFA )
*/
2021-11-04 03:28:16 +00:00
public function enableSmartMFA ()
2021-11-04 03:16:14 +00:00
{
$this -> smartMFA = true ;
}
/**
* Disables " smart " multi - factor authentication ( MFA )
*/
2021-11-04 03:28:16 +00:00
public function disableSmartMFA ()
2021-11-04 03:16:14 +00:00
{
$this -> smartMFA = false ;
}
2013-05-08 06:52:18 +00:00
}