2007-07-23 07:21:39 +02:00
< ? php
/**
2009-06-09 06:00:38 +02:00
* Pure - PHP implementation of SSHv2 .
2007-07-23 07:21:39 +02:00
*
2015-04-02 12:57:52 +02:00
* PHP version 5
2007-07-23 07:21:39 +02:00
*
2009-12-03 20:04:10 +01:00
* Here are some examples of how to use this library :
2007-07-25 23:49:33 +02:00
* < code >
* < ? php
2014-12-10 02:31:41 +01:00
* include 'vendor/autoload.php' ;
2007-07-25 23:49:33 +02:00
*
2014-12-10 02:31:41 +01:00
* $ssh = new \phpseclib\Net\SSH2 ( 'www.domain.tld' );
2007-07-25 23:49:33 +02:00
* if ( ! $ssh -> login ( 'username' , 'password' )) {
* exit ( 'Login Failed' );
* }
*
* echo $ssh -> exec ( 'pwd' );
* echo $ssh -> exec ( 'ls -la' );
* ?>
* </ code >
*
2009-12-03 09:19:00 +01:00
* < code >
* < ? php
2014-12-10 02:31:41 +01:00
* include 'vendor/autoload.php' ;
2009-12-03 09:19:00 +01:00
*
2019-05-19 22:35:29 +02:00
* \phpseclib\Crypt\PublicKeyLoader :: load ( '...' );
2009-12-03 09:19:00 +01:00
* //$key->setPassword('whatever');
2016-04-30 23:23:35 +02:00
* $key -> load ( file_get_contents ( 'privatekey' ));
2009-12-03 09:19:00 +01:00
*
2014-12-10 02:31:41 +01:00
* $ssh = new \phpseclib\Net\SSH2 ( 'www.domain.tld' );
2009-12-03 09:19:00 +01:00
* if ( ! $ssh -> login ( 'username' , $key )) {
* exit ( 'Login Failed' );
* }
*
2011-05-09 01:53:30 +02:00
* echo $ssh -> read ( 'username@username:~$' );
* $ssh -> write ( " ls -la \n " );
* echo $ssh -> read ( 'username@username:~$' );
2009-12-03 09:19:00 +01:00
* ?>
* </ code >
*
2013-12-10 20:10:37 +01:00
* @ category Net
2014-12-10 02:31:41 +01:00
* @ package SSH2
2013-12-10 20:10:37 +01:00
* @ author Jim Wigginton < terrafrost @ php . net >
2014-12-10 00:02:44 +01:00
* @ copyright 2007 Jim Wigginton
2013-12-10 20:10:37 +01:00
* @ license http :// www . opensource . org / licenses / mit - license . html MIT License
* @ link http :// phpseclib . sourceforge . net
2007-07-23 07:21:39 +02:00
*/
2014-12-10 02:31:41 +01:00
namespace phpseclib\Net ;
2014-12-17 01:16:54 +01:00
use phpseclib\Crypt\Blowfish ;
use phpseclib\Crypt\Hash ;
2014-12-10 02:31:41 +01:00
use phpseclib\Crypt\Random ;
2014-12-17 01:16:54 +01:00
use phpseclib\Crypt\RC4 ;
use phpseclib\Crypt\Rijndael ;
2019-05-19 22:35:29 +02:00
use phpseclib\Crypt\Common\PrivateKey ;
2014-12-17 01:16:54 +01:00
use phpseclib\Crypt\RSA ;
2019-03-30 19:35:16 +01:00
use phpseclib\Crypt\DSA ;
use phpseclib\Crypt\ECDSA ;
2014-12-17 01:16:54 +01:00
use phpseclib\Crypt\TripleDES ;
use phpseclib\Crypt\Twofish ;
2019-03-18 12:59:00 +01:00
use phpseclib\Crypt\ChaCha20 ;
2014-12-17 01:16:54 +01:00
use phpseclib\Math\BigInteger ; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
2014-12-24 22:05:56 +01:00
use phpseclib\System\SSH\Agent ;
2018-05-27 18:03:16 +02:00
use phpseclib\System\SSH\Agent\Identity as AgentIdentity ;
2016-04-30 23:23:35 +02:00
use phpseclib\Exception\NoSupportedAlgorithmsException ;
2019-03-30 00:44:31 +01:00
use phpseclib\Exception\UnsupportedAlgorithmException ;
2019-05-19 22:35:29 +02:00
use phpseclib\Exception\UnsupportedCurveException ;
2016-07-31 05:18:06 +02:00
use phpseclib\Common\Functions\Strings ;
2019-05-21 16:10:18 +02:00
use phpseclib\Crypt\Common\Keys\OpenSSH ;
2014-06-02 20:09:47 +02:00
2007-07-23 07:21:39 +02:00
/**
* Pure - PHP implementation of SSHv2 .
*
2014-12-10 02:31:41 +01:00
* @ package SSH2
2007-07-23 07:21:39 +02:00
* @ author Jim Wigginton < terrafrost @ php . net >
* @ access public
*/
2014-12-10 02:31:41 +01:00
class SSH2
2013-12-03 19:34:41 +01:00
{
2014-12-04 22:45:13 +01:00
/** #@+
* Execution Bitmap Masks
*
2014-12-10 02:31:41 +01:00
* @ see \phpseclib\Net\SSH2 :: bitmap
2014-12-04 22:45:13 +01:00
* @ access private
*/
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 ;
/**#@-*/
/** #@+
* 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 13:07:14 +01: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 22:45:13 +01: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 .
*
2018-09-10 17:17:20 +02:00
* @ see \phpseclib\Net\SSH2 :: send_channel_packet ()
* @ see \phpseclib\Net\SSH2 :: get_channel_packet ()
2014-12-04 22:45:13 +01:00
* @ access private
*/
2017-09-06 06:22:09 +02:00
const CHANNEL_EXEC = 1 ; // PuTTy uses 0x100
const CHANNEL_SHELL = 2 ;
const CHANNEL_SUBSYSTEM = 3 ;
const CHANNEL_AGENT_FORWARD = 4 ;
2019-01-20 16:15:53 +01:00
const CHANNEL_KEEP_ALIVE = 5 ;
2014-12-04 22:45:13 +01:00
/**#@-*/
/** #@+
* @ access public
2014-12-10 02:31:41 +01:00
* @ see \phpseclib\Net\SSH2 :: getLog ()
2014-12-04 22:45:13 +01:00
*/
/**
* Returns the message numbers
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const LOG_SIMPLE = 1 ;
/**
* Returns the message content
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const LOG_COMPLEX = 2 ;
/**
* Outputs the content real - time
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const LOG_REALTIME = 3 ;
/**
* Dumps the content real - time to a file
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const LOG_REALTIME_FILE = 4 ;
2017-06-20 05:35:22 +02:00
/**
* Make sure that the log never gets larger than this
*/
const LOG_MAX_SIZE = 1048576 ; // 1024 * 1024
2014-12-04 22:45:13 +01:00
/**#@-*/
/** #@+
* @ access public
2014-12-10 02:31:41 +01:00
* @ see \phpseclib\Net\SSH2 :: read ()
2014-12-04 22:45:13 +01:00
*/
/**
* Returns when a string matching $expect exactly is found
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const READ_SIMPLE = 1 ;
/**
* Returns when a string matching the regular expression $expect is found
2016-04-10 18:30:59 +02:00
*/
2014-12-04 22:45:13 +01:00
const READ_REGEX = 2 ;
2017-06-20 05:34:20 +02:00
/**
2019-04-12 04:56:17 +02: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
2017-06-20 05:34:20 +02:00
*/
const READ_NEXT = 3 ;
2014-12-04 22:45:13 +01:00
/**#@-*/
2007-07-23 07:21:39 +02:00
/**
* The SSH identifier
*
2016-04-10 18:30:59 +02:00
* @ var string
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $identifier ;
2007-07-23 07:21:39 +02:00
/**
* The Socket Object
*
2016-04-10 18:30:59 +02:00
* @ var object
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $fsock ;
2007-07-23 07:21:39 +02:00
/**
* Execution Bitmap
*
2012-08-14 19:07:18 +02:00
* The bits that are set represent functions that have been called already . This is used to determine
2007-07-23 07:21:39 +02:00
* if a requisite function has been successfully executed . If not , an error should be thrown .
*
2016-04-10 18:30:59 +02:00
* @ var int
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected $bitmap = 0 ;
2007-07-23 07:21:39 +02:00
/**
2010-02-13 00:02:13 +01:00
* Error information
2007-07-23 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: getErrors ()
* @ see self :: getLastError ()
2017-01-08 02:51:56 +01:00
* @ var array
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $errors = [];
2007-07-23 07:21:39 +02:00
/**
* Server Identifier
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerIdentification ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $server_identifier = false ;
2007-07-23 07:21:39 +02:00
/**
* Key Exchange Algorithms
*
2016-04-10 18:30:59 +02:00
* @ see self :: getKexAlgorithims ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $kex_algorithms = false ;
2007-07-23 07:21:39 +02:00
2019-03-28 19:57:26 +01:00
/**
* Key Exchange Algorithm
*
* @ see self :: getMethodsNegotiated ()
* @ var string | false
* @ access private
*/
private $kex_algorithm = false ;
2015-06-25 00:50:00 +02:00
/**
* Minimum Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 18:30:59 +02:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-25 00:50:00 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $kex_dh_group_size_min = 1536 ;
2015-06-25 00:50:00 +02:00
/**
* Preferred Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 18:30:59 +02:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-25 00:50:00 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $kex_dh_group_size_preferred = 2048 ;
2015-06-25 00:50:00 +02:00
/**
* Maximum Diffie - Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
2016-04-10 18:30:59 +02:00
* @ see self :: _key_exchange ()
* @ var int
2015-06-25 00:50:00 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $kex_dh_group_size_max = 4096 ;
2015-06-25 00:50:00 +02:00
2007-07-23 07:21:39 +02:00
/**
* Server Host Key Algorithms
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerHostKeyAlgorithms ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $server_host_key_algorithms = false ;
2007-07-23 07:21:39 +02:00
/**
* Encryption Algorithms : Client to Server
*
2016-04-10 18:30:59 +02:00
* @ see self :: getEncryptionAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $encryption_algorithms_client_to_server = false ;
2007-07-23 07:21:39 +02:00
/**
* Encryption Algorithms : Server to Client
*
2016-04-10 18:30:59 +02:00
* @ see self :: getEncryptionAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $encryption_algorithms_server_to_client = false ;
2007-07-23 07:21:39 +02:00
/**
* MAC Algorithms : Client to Server
*
2016-04-10 18:30:59 +02:00
* @ see self :: getMACAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $mac_algorithms_client_to_server = false ;
2007-07-23 07:21:39 +02:00
/**
* MAC Algorithms : Server to Client
*
2016-04-10 18:30:59 +02:00
* @ see self :: getMACAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $mac_algorithms_server_to_client = false ;
2007-07-23 07:21:39 +02:00
/**
* Compression Algorithms : Client to Server
*
2016-04-10 18:30:59 +02:00
* @ see self :: getCompressionAlgorithmsClient2Server ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $compression_algorithms_client_to_server = false ;
2007-07-23 07:21:39 +02:00
/**
* Compression Algorithms : Server to Client
*
2016-04-10 18:30:59 +02:00
* @ see self :: getCompressionAlgorithmsServer2Client ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $compression_algorithms_server_to_client = false ;
2007-07-23 07:21:39 +02:00
/**
* Languages : Server to Client
*
2016-04-10 18:30:59 +02:00
* @ see self :: getLanguagesServer2Client ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $languages_server_to_client = false ;
2007-07-23 07:21:39 +02:00
/**
* Languages : Client to Server
*
2016-04-10 18:30:59 +02:00
* @ see self :: getLanguagesClient2Server ()
* @ var array | false
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $languages_client_to_server = false ;
2007-07-23 07:21:39 +02:00
2019-03-30 00:44:31 +01:00
/**
* Preferred Algorithms
*
* @ see self :: setPreferredAlgorithms ()
* @ var array
* @ access private
*/
private $preferred = [];
2007-07-23 07:21:39 +02:00
/**
2009-02-16 23:22:13 +01: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 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ see self :: _send_binary_packet ()
* @ var int
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $encrypt_block_size = 8 ;
2009-02-16 23:22:13 +01:00
/**
* Block Size for Client to Server Encryption
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ see self :: _get_binary_packet ()
* @ var int
2009-02-16 23:22:13 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $decrypt_block_size = 8 ;
2007-07-23 07:21:39 +02:00
/**
* Server to Client Encryption Object
*
2016-04-10 18:30:59 +02:00
* @ see self :: _get_binary_packet ()
* @ var object
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $decrypt = false ;
2007-07-23 07:21:39 +02:00
2019-03-18 12:59:00 +01:00
/**
* Server to Client Length Encryption Object
*
* @ see self :: _get_binary_packet ()
* @ var object
* @ access private
*/
private $lengthDecrypt = false ;
2007-07-23 07:21:39 +02:00
/**
* Client to Server Encryption Object
*
2016-04-10 18:30:59 +02:00
* @ see self :: _send_binary_packet ()
* @ var object
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $encrypt = false ;
2007-07-23 07:21:39 +02:00
2019-03-18 12:59:00 +01:00
/**
* Client to Server Length Encryption Object
*
* @ see self :: _send_binary_packet ()
* @ var object
* @ access private
*/
private $lengthEncrypt = false ;
2007-07-23 07:21:39 +02:00
/**
* Client to Server HMAC Object
*
2016-04-10 18:30:59 +02:00
* @ see self :: _send_binary_packet ()
* @ var object
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $hmac_create = false ;
2007-07-23 07:21:39 +02:00
/**
* Server to Client HMAC Object
*
2016-04-10 18:30:59 +02:00
* @ see self :: _get_binary_packet ()
* @ var object
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $hmac_check = false ;
2007-07-23 07:21:39 +02: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 18:30:59 +02:00
* @ see self :: _get_binary_packet ()
* @ var int
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $hmac_size = false ;
2007-07-23 07:21:39 +02:00
/**
* Server Public Host Key
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $server_public_host_key ;
2007-07-23 07:21:39 +02:00
/**
2016-07-31 14:41:23 +02:00
* Session identifier
2007-07-23 07:21:39 +02: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 18:30:59 +02:00
* @ see self :: _key_exchange ()
* @ var string
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $session_id = false ;
2007-07-23 07:21:39 +02:00
2010-10-24 03:24:30 +02:00
/**
* Exchange hash
*
* The current exchange hash
*
2016-04-10 18:30:59 +02:00
* @ see self :: _key_exchange ()
* @ var string
2010-10-24 03:24:30 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $exchange_hash = false ;
2010-10-24 03:24:30 +02:00
2008-05-26 21:42:01 +02:00
/**
* Message Numbers
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ var array
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $message_numbers = [];
2008-05-26 21:42:01 +02:00
/**
* Disconnection Message 'reason codes' defined in RFC4253
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ var array
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $disconnect_reasons = [];
2008-05-26 21:42:01 +02:00
/**
* SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes' , defined in RFC4254
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ var array
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $channel_open_failure_reasons = [];
2008-05-26 21:42:01 +02:00
/**
* Terminal Modes
*
* @ link http :// tools . ietf . org / html / rfc4254 #section-8
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ var array
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $terminal_modes = [];
2008-05-26 21:42:01 +02:00
/**
* SSH_MSG_CHANNEL_EXTENDED_DATA ' s data_type_codes
*
* @ link http :// tools . ietf . org / html / rfc4254 #section-5.2
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ var array
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $channel_extended_data_type_codes = [];
2008-05-26 21:42:01 +02:00
/**
* Send Sequence Number
*
* See 'Section 6.4. Data Integrity' of rfc4253 for more info .
*
2016-04-10 18:30:59 +02:00
* @ see self :: _send_binary_packet ()
* @ var int
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $send_seq_no = 0 ;
2008-05-26 21:42:01 +02:00
/**
* Get Sequence Number
*
* See 'Section 6.4. Data Integrity' of rfc4253 for more info .
*
2016-04-10 18:30:59 +02:00
* @ see self :: _get_binary_packet ()
* @ var int
2008-05-26 21:42:01 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $get_seq_no = 0 ;
2008-05-26 21:42:01 +02:00
2009-10-16 05:37:24 +02:00
/**
2009-12-14 19:14:54 +01:00
* Server Channels
2009-10-16 05:37:24 +02:00
*
2009-12-14 19:14:54 +01:00
* Maps client channels to server channels
2009-10-16 05:37:24 +02:00
*
2018-09-10 17:17:20 +02:00
* @ see self :: get_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ see self :: exec ()
* @ var array
2009-10-16 05:37:24 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected $server_channels = [];
2009-10-16 05:37:24 +02:00
2009-08-23 05:40:50 +02:00
/**
2009-12-14 19:14:54 +01:00
* Channel Buffers
2009-08-23 05:40:50 +02:00
*
2009-12-14 19:14:54 +01: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 05:40:50 +02:00
*
2018-09-10 17:17:20 +02:00
* @ see self :: get_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ see self :: exec ()
* @ var array
2009-08-23 05:40:50 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $channel_buffers = [];
2009-12-14 19:14:54 +01:00
/**
* Channel Status
*
* Contains the type of the last sent message
*
2018-09-10 17:17:20 +02:00
* @ see self :: get_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ var array
2009-12-14 19:14:54 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected $channel_status = [];
2009-08-23 05:40:50 +02:00
2009-09-17 05:19:20 +02:00
/**
* Packet Size
*
2009-12-14 19:14:54 +01:00
* Maximum packet size indexed by channel
2009-09-17 05:19:20 +02:00
*
2018-09-10 17:17:20 +02:00
* @ see self :: send_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ var array
2009-09-17 05:19:20 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $packet_size_client_to_server = [];
2009-09-17 05:19:20 +02:00
2009-03-25 23:29:42 +01:00
/**
* Message Number Log
*
2016-04-10 18:30:59 +02:00
* @ see self :: getLog ()
* @ var array
2009-03-25 23:29:42 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $message_number_log = [];
2009-03-25 23:29:42 +01:00
/**
* Message Log
*
2016-04-10 18:30:59 +02:00
* @ see self :: getLog ()
* @ var array
2009-03-25 23:29:42 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $message_log = [];
2009-03-25 23:29:42 +01:00
2010-02-11 08:02:51 +01:00
/**
* The Window Size
*
2013-05-10 23:48:10 +02:00
* Bytes the other party can send before it must wait for the window to be adjusted ( 0x7FFFFFFF = 2 GB )
2010-02-11 08:02:51 +01:00
*
2016-04-10 18:30:59 +02:00
* @ var int
2018-09-10 17:17:20 +02:00
* @ see self :: send_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ see self :: exec ()
2010-02-11 08:02:51 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected $window_size = 0x7FFFFFFF ;
2010-02-11 08:02:51 +01:00
/**
2013-07-07 22:57:15 +02:00
* Window size , server to client
2010-02-11 08:02:51 +01:00
*
* Window size indexed by channel
*
2018-09-10 17:17:20 +02:00
* @ see self :: send_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ var array
2010-02-11 08:02:51 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected $window_size_server_to_client = [];
2010-02-11 08:02:51 +01:00
2013-07-07 22:57:15 +02:00
/**
* Window size , client to server
*
* Window size indexed by channel
*
2018-09-10 17:17:20 +02:00
* @ see self :: get_channel_packet ()
2016-04-10 18:30:59 +02:00
* @ var array
2013-07-07 22:57:15 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $window_size_client_to_server = [];
2013-07-07 22:57:15 +02:00
2010-04-22 18:06:43 +02:00
/**
* Server signature
*
* Verified against $this -> session_id
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2010-04-22 18:06:43 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $signature = '' ;
2010-04-22 18:06:43 +02:00
/**
* Server signature format
*
* ssh - rsa or ssh - dss .
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerPublicHostKey ()
* @ var string
2010-04-22 18:06:43 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $signature_format = '' ;
2010-04-22 18:06:43 +02:00
2011-02-28 06:24:09 +01:00
/**
* Interactive Buffer
*
2016-04-10 18:30:59 +02:00
* @ see self :: read ()
* @ var array
2011-02-28 06:24:09 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $interactiveBuffer = '' ;
2011-02-28 06:24:09 +01:00
2011-06-04 19:06:53 +02:00
/**
* Current log size
*
2014-12-04 22:45:13 +01:00
* Should never exceed self :: LOG_MAX_SIZE
2011-06-04 19:06:53 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: _send_binary_packet ()
* @ see self :: _get_binary_packet ()
* @ var int
2011-06-04 19:06:53 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $log_size ;
2011-06-04 19:06:53 +02:00
2012-03-03 18:49:16 +01:00
/**
* Timeout
*
2016-04-10 18:30:59 +02:00
* @ see self :: setTimeout ()
2012-03-03 18:49:16 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $timeout ;
2012-03-03 18:49:16 +01:00
/**
* Current Timeout
*
2018-09-10 17:17:20 +02:00
* @ see self :: get_channel_packet ()
2012-03-03 18:49:16 +01:00
* @ access private
*/
2017-08-03 09:19:11 +02:00
protected $curTimeout ;
2012-03-03 18:49:16 +01:00
2012-04-30 18:25:37 +02:00
/**
* Real - time log file pointer
*
2016-04-10 18:30:59 +02:00
* @ see self :: _append_log ()
* @ var resource
2012-04-30 18:25:37 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $realtime_log_file ;
2012-04-30 18:25:37 +02:00
/**
* Real - time log file size
*
2016-04-10 18:30:59 +02:00
* @ see self :: _append_log ()
* @ var int
2012-04-30 18:25:37 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $realtime_log_size ;
2012-04-30 18:25:37 +02:00
2012-07-04 20:36:26 +02:00
/**
* Has the signature been validated ?
*
2016-04-10 18:30:59 +02:00
* @ see self :: getServerPublicHostKey ()
* @ var bool
2012-07-04 20:36:26 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $signature_validated = false ;
2012-07-04 20:36:26 +02:00
2012-04-30 18:25:37 +02:00
/**
* Real - time log file wrap boolean
*
2016-04-10 18:30:59 +02:00
* @ see self :: _append_log ()
2012-04-30 18:25:37 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $realtime_log_wrap ;
2012-04-30 18:25:37 +02:00
2012-07-23 14:17:53 +02:00
/**
* Flag to suppress stderr from output
*
2016-04-10 18:30:59 +02:00
* @ see self :: enableQuietMode ()
2012-07-23 14:17:53 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $quiet_mode = false ;
2012-07-23 14:17:53 +02:00
2012-11-22 20:08:30 +01:00
/**
* Time of first network activity
*
2016-04-10 18:30:59 +02:00
* @ var int
2012-11-22 20:08:30 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $last_packet ;
2012-11-22 20:08:30 +01:00
2013-01-17 19:47:42 +01:00
/**
* Exit status returned from ssh if any
*
2016-04-10 18:30:59 +02:00
* @ var int
2013-01-17 19:47:42 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $exit_status ;
2013-01-17 19:47:42 +01:00
2013-02-08 23:04:52 +01:00
/**
* Flag to request a PTY when using exec ()
*
2016-04-10 18:30:59 +02:00
* @ var bool
* @ see self :: enablePTY ()
2013-02-08 23:04:52 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $request_pty = false ;
2013-02-08 23:04:52 +01:00
/**
* Flag set while exec () is running when using enablePTY ()
*
2016-04-10 18:30:59 +02:00
* @ var bool
2013-02-08 23:04:52 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $in_request_pty_exec = false ;
2013-02-08 23:04:52 +01:00
2013-10-25 19:35:30 +02:00
/**
* Flag set after startSubsystem () is called
*
2016-04-10 18:30:59 +02:00
* @ var bool
2013-10-25 19:35:30 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $in_subsystem ;
2013-10-25 19:35:30 +02:00
2013-03-21 23:18:31 +01:00
/**
* Contents of stdError
2013-04-20 05:23:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ var string
2013-03-21 23:18:31 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $stdErrorLog ;
2013-03-21 23:18:31 +01:00
2013-04-20 05:23:06 +02:00
/**
* The Last Interactive Response
*
2016-04-10 18:30:59 +02:00
* @ see self :: _keyboard_interactive_process ()
* @ var string
2013-04-20 05:23:06 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $last_interactive_response = '' ;
2013-04-20 05:23:06 +02:00
2013-04-20 21:35:08 +02:00
/**
* Keyboard Interactive Request / Responses
*
2016-04-10 18:30:59 +02:00
* @ see self :: _keyboard_interactive_process ()
* @ var array
2013-04-20 21:35:08 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $keyboard_requests_responses = [];
2013-04-20 21:35:08 +02:00
2013-04-28 02:58:24 +02: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 18:30:59 +02:00
* @ see self :: _filter ()
* @ see self :: getBannerMessage ()
* @ var string
2013-04-28 02:58:24 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $banner_message = '' ;
2013-04-28 02:58:24 +02:00
2013-06-08 00:21:11 +02:00
/**
* Did read () timeout or return normally ?
*
2016-04-10 18:30:59 +02:00
* @ see self :: isTimeout ()
* @ var bool
2013-06-08 00:21:11 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $is_timeout = false ;
2013-06-08 00:21:11 +02:00
2013-12-16 18:27:12 +01:00
/**
* Log Boundary
*
2016-04-10 18:30:59 +02:00
* @ see self :: _format_log ()
* @ var string
2013-12-16 18:27:12 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $log_boundary = ':' ;
2013-12-16 18:27:12 +01:00
/**
* Log Long Width
*
2016-04-10 18:30:59 +02:00
* @ see self :: _format_log ()
* @ var int
2013-12-16 18:27:12 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $log_long_width = 65 ;
2013-12-16 18:27:12 +01:00
/**
* Log Short Width
*
2016-04-10 18:30:59 +02:00
* @ see self :: _format_log ()
* @ var int
2013-12-16 18:27:12 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $log_short_width = 16 ;
2013-12-16 18:27:12 +01:00
2007-07-23 07:21:39 +02:00
/**
2014-04-07 06:45:25 +02:00
* Hostname
2007-07-23 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ see self :: _connect ()
* @ var string
2014-04-07 06:45:25 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $host ;
2014-04-07 06:45:25 +02:00
/**
* Port Number
*
2016-04-10 18:30:59 +02:00
* @ see self :: __construct ()
* @ see self :: _connect ()
* @ var int
2014-04-07 06:45:25 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $port ;
2014-04-07 06:45:25 +02:00
2014-06-20 12:04:17 +02:00
/**
* Number of columns for terminal window size
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: getWindowColumns ()
* @ see self :: setWindowColumns ()
* @ see self :: setWindowSize ()
* @ var int
2014-06-20 12:04:17 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $windowColumns = 80 ;
2014-06-20 12:04:17 +02:00
/**
* Number of columns for terminal window size
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: getWindowRows ()
* @ see self :: setWindowRows ()
* @ see self :: setWindowSize ()
* @ var int
2014-06-20 12:04:17 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $windowRows = 24 ;
2014-06-20 12:04:17 +02:00
2014-10-12 06:26:46 +02:00
/**
* Crypto Engine
*
2016-04-10 18:30:59 +02:00
* @ see self :: setCryptoEngine ()
* @ see self :: _key_exchange ()
* @ var int
2014-10-12 06:26:46 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $crypto_engine = false ;
2014-10-12 06:26:46 +02:00
2014-12-30 03:44:31 +01:00
/**
* A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
*
2017-08-04 12:06:25 +02:00
* @ var \phpseclib\System\Ssh\Agent
2014-12-30 03:44:31 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private $agent ;
2014-12-30 03:44:31 +01:00
2016-04-30 23:23:35 +02:00
/**
* Connection storage to replicates ssh2 extension functionality :
* { @ link http :// php . net / manual / en / wrappers . ssh2 . php #refsect1-wrappers.ssh2-examples}
*
* @ var SSH2 []
*/
2017-01-08 02:51:56 +01:00
private static $connections ;
2016-04-30 23:23:35 +02:00
2017-08-07 07:15:50 +02:00
/**
* Send the identification string first ?
*
* @ var bool
* @ access private
*/
2017-08-08 03:09:26 +02:00
private $send_id_string_first = true ;
2017-08-07 07:15:50 +02:00
/**
* Send the key exchange initiation packet first ?
*
* @ var bool
* @ access private
*/
2017-08-08 03:09:26 +02:00
private $send_kex_first = true ;
2017-08-07 07:15:50 +02:00
2017-08-27 02:50:17 +02:00
/**
* Some versions of OpenSSH incorrectly calculate the key size
*
* @ var bool
* @ access private
*/
2017-08-27 19:33:19 +02:00
private $bad_key_size_fix = false ;
2017-08-27 02:50:17 +02:00
/**
* Should we try to re - connect to re - establish keys ?
*
* @ var bool
* @ access private
*/
2017-08-27 19:33:19 +02:00
private $retry_connect = false ;
2017-08-27 02:50:17 +02:00
2017-08-29 05:37:35 +02:00
/**
* Binary Packet Buffer
*
* @ var string | false
* @ access private
*/
2017-09-06 06:42:05 +02:00
private $binary_packet_buffer = false ;
2017-08-29 05:37:35 +02:00
2018-05-27 16:48:44 +02:00
/**
* Preferred Signature Format
*
* @ var string | false
* @ access private
*/
var $preferred_signature_format = false ;
2018-10-06 08:19:14 +02:00
/**
* Authentication Credentials
*
* @ var array
* @ access private
*/
2019-03-18 12:59:00 +01:00
var $auth = [];
2018-10-06 08:19:14 +02:00
2007-07-23 07:21:39 +02:00
/**
* Default Constructor .
*
2015-07-17 19:30:44 +02:00
* $host can either be a string , representing the host , or a stream resource .
*
2016-04-10 18:30:59 +02:00
* @ param mixed $host
* @ param int $port
* @ param int $timeout
* @ see self :: login ()
2017-08-03 09:19:11 +02:00
* @ return SSH2 | void
2007-07-23 07:21:39 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function __construct ( $host , $port = 22 , $timeout = 10 )
2007-07-23 07:21:39 +02:00
{
2016-11-30 22:19:23 +01:00
$this -> message_numbers = [
2008-05-26 21:42:01 +02: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 22:19:23 +01:00
];
$this -> disconnect_reasons = [
2008-05-26 21:42:01 +02: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 22:19:23 +01:00
];
$this -> channel_open_failure_reasons = [
2008-05-26 21:42:01 +02:00
1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
2016-11-30 22:19:23 +01:00
];
$this -> terminal_modes = [
2008-05-26 21:42:01 +02:00
0 => 'NET_SSH2_TTY_OP_END'
2016-11-30 22:19:23 +01:00
];
$this -> channel_extended_data_type_codes = [
2008-05-26 21:42:01 +02:00
1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
2016-11-30 22:19:23 +01:00
];
2008-05-26 21:42:01 +02:00
2017-01-08 02:51:56 +01:00
$this -> define_array (
2008-05-26 21:42:01 +02:00
$this -> message_numbers ,
$this -> disconnect_reasons ,
$this -> channel_open_failure_reasons ,
$this -> terminal_modes ,
2009-12-03 09:19:00 +01:00
$this -> channel_extended_data_type_codes ,
2016-11-30 22:19:23 +01: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-25 00:50:00 +02:00
// RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
2016-11-30 22:19:23 +01:00
[ 30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD' ,
2015-06-25 00:50:00 +02: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 22:19:23 +01:00
34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST' ],
2015-07-23 03:02:28 +02:00
// RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
2016-11-30 22:19:23 +01:00
[ 30 => 'NET_SSH2_MSG_KEX_ECDH_INIT' ,
31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY' ]
2008-05-26 21:42:01 +02:00
);
2016-04-30 23:23:35 +02:00
self :: $connections [ $this -> getResourceId ()] = $this ;
2015-07-17 19:30:44 +02:00
if ( is_resource ( $host )) {
$this -> fsock = $host ;
return ;
}
if ( is_string ( $host )) {
$this -> host = $host ;
$this -> port = $port ;
$this -> timeout = $timeout ;
}
2014-04-07 06:45:25 +02:00
}
2014-10-12 06:26:46 +02:00
/**
* Set Crypto Engine Mode
*
* Possible $engine values :
2017-06-28 05:34:36 +02:00
* OpenSSL , mcrypt , Eval , PHP
2014-10-12 06:26:46 +02:00
*
2016-04-10 18:30:59 +02:00
* @ param int $engine
2017-08-07 07:15:50 +02:00
* @ access public
2014-10-12 06:26:46 +02:00
*/
2017-01-08 02:51:56 +01:00
public function setCryptoEngine ( $engine )
2014-10-12 06:26:46 +02:00
{
$this -> crypto_engine = $engine ;
}
2017-08-07 07:15:50 +02: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
*
* @ access public
*/
2017-08-27 19:33:19 +02:00
public function sendIdentificationStringFirst ()
2017-08-07 07:15:50 +02: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
*
* @ access public
*/
2017-08-27 19:33:19 +02:00
public function sendIdentificationStringLast ()
2017-08-07 07:15:50 +02: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
*
* @ access public
*/
2017-08-27 19:33:19 +02:00
public function sendKEXINITFirst ()
2017-08-07 07:15:50 +02: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
*
* @ access public
*/
2017-08-27 19:33:19 +02:00
public function sendKEXINITLast ()
2017-08-07 07:15:50 +02:00
{
$this -> send_kex_first = false ;
}
2014-04-07 06:56:21 +02:00
/**
* Connect to an SSHv2 server
*
2016-04-10 18:30:59 +02:00
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2014-04-07 06:56:21 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function connect ()
2014-04-07 06:45:25 +02:00
{
2014-12-04 22:45:13 +01:00
if ( $this -> bitmap & self :: MASK_CONSTRUCTOR ) {
2014-08-05 03:11:34 +02:00
return false ;
}
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_CONSTRUCTOR ;
2014-08-05 03:11:34 +02:00
2015-03-19 13:53:19 +01:00
$this -> curTimeout = $this -> timeout ;
2014-04-10 18:00:38 +02:00
$this -> last_packet = microtime ( true );
2014-04-07 06:45:25 +02:00
2015-07-17 19:30:44 +02:00
if ( ! is_resource ( $this -> fsock )) {
2015-07-17 19:43:50 +02:00
$start = microtime ( true );
2016-11-20 18:08:53 +01: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 19:30:44 +02:00
if ( ! $this -> fsock ) {
2015-07-17 20:08:24 +02:00
$host = $this -> host . ':' . $this -> port ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( rtrim ( " Cannot connect to $host . Error $errno . $errstr " ));
2015-07-17 19:30:44 +02:00
}
2015-07-17 19:43:50 +02:00
$elapsed = microtime ( true ) - $start ;
2015-07-17 19:30:44 +02:00
2018-05-19 13:26:22 +02:00
if ( $this -> curTimeout ) {
$this -> curTimeout -= $elapsed ;
if ( $this -> curTimeout < 0 ) {
$this -> is_timeout = true ;
return false ;
}
2015-07-17 19:30:44 +02:00
}
2012-06-24 00:16:42 +02:00
}
2017-01-08 02:51:56 +01:00
$this -> identifier = $this -> generate_identifier ();
2016-11-08 16:11:57 +01:00
2017-08-07 07:15:50 +02:00
if ( $this -> send_id_string_first ) {
fputs ( $this -> fsock , $this -> identifier . " \r \n " );
}
2016-11-08 16:11:57 +01:00
2007-07-23 07:21:39 +02: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 21:37:12 +02:00
$data = '' ;
while ( ! feof ( $this -> fsock ) && ! preg_match ( '#(.*)^(SSH-(\d\.\d+).*)#ms' , $data , $matches )) {
$line = '' ;
while ( true ) {
if ( $this -> curTimeout ) {
if ( $this -> curTimeout < 0 ) {
$this -> is_timeout = true ;
return false ;
}
2016-11-30 22:19:23 +01:00
$read = [ $this -> fsock ];
2016-05-22 21:37:12 +02:00
$write = $except = null ;
2016-06-07 06:05:15 +02:00
$start = microtime ( true );
2016-05-22 21:37:12 +02:00
$sec = floor ( $this -> curTimeout );
$usec = 1000000 * ( $this -> curTimeout - $sec );
// on windows this returns a "Warning: Invalid CRT parameters detected" error
// the !count() is done as a workaround for <https://bugs.php.net/42682>
if ( !@ stream_select ( $read , $write , $except , $sec , $usec ) && ! count ( $read )) {
$this -> is_timeout = true ;
return false ;
}
2016-06-07 06:05:15 +02:00
$elapsed = microtime ( true ) - $start ;
2016-05-22 21:37:12 +02:00
$this -> curTimeout -= $elapsed ;
}
2015-03-19 13:53:19 +01:00
2016-05-22 21:37:12 +02:00
$temp = stream_get_line ( $this -> fsock , 255 , " \n " );
if ( strlen ( $temp ) == 255 ) {
continue ;
2015-03-19 13:53:19 +01:00
}
2016-06-07 06:05:15 +02:00
2016-05-22 21:37:12 +02:00
$line .= " $temp\n " ;
2016-08-12 06:09:38 +02: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 13:53:19 +01:00
}
2016-08-12 06:09:38 +02:00
2016-05-22 21:37:12 +02:00
$data .= $line ;
2007-07-23 07:21:39 +02:00
}
2009-10-17 05:49:16 +02:00
2010-05-10 18:16:35 +02:00
if ( feof ( $this -> fsock )) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2010-05-10 18:16:35 +02:00
}
2016-05-22 21:37:12 +02:00
$extra = $matches [ 1 ];
2009-10-17 05:49:16 +02:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2017-01-08 02:51:56 +01:00
$this -> append_log ( '<-' , $matches [ 0 ]);
$this -> append_log ( '->' , $this -> identifier . " \r \n " );
2009-10-17 05:49:16 +02:00
}
2010-04-27 23:29:36 +02:00
$this -> server_identifier = trim ( $temp , " \r \n " );
2013-01-12 17:46:19 +01:00
if ( strlen ( $extra )) {
2018-08-21 04:41:18 +02:00
$this -> errors [] = $data ;
2010-02-28 00:34:46 +01:00
}
2007-07-23 07:21:39 +02:00
2017-08-25 06:55:27 +02:00
if ( version_compare ( $matches [ 3 ], '1.99' , '<' )) {
2017-08-25 06:56:08 +02:00
throw new \RuntimeException ( " Cannot connect to SSH $matches[3] servers " );
2007-07-23 07:21:39 +02:00
}
2017-08-07 07:15:50 +02:00
if ( ! $this -> send_id_string_first ) {
fputs ( $this -> fsock , $this -> identifier . " \r \n " );
2007-07-23 07:21:39 +02:00
}
2017-08-07 07:15:50 +02:00
if ( ! $this -> send_kex_first ) {
2017-08-08 03:09:26 +02:00
$response = $this -> get_binary_packet ();
2017-08-07 07:15:50 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2017-08-08 03:09:26 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2017-08-07 07:15:50 +02:00
}
if ( ! strlen ( $response ) || ord ( $response [ 0 ]) != NET_SSH2_MSG_KEXINIT ) {
2017-08-08 03:09:26 +02:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEXINIT' );
2017-08-07 07:15:50 +02:00
}
2017-08-08 03:09:26 +02:00
if ( ! $this -> key_exchange ( $response )) {
2017-08-07 07:15:50 +02:00
return false ;
}
2007-07-23 07:21:39 +02:00
}
2017-08-08 03:09:26 +02:00
if ( $this -> send_kex_first && ! $this -> key_exchange ()) {
2014-04-07 06:45:25 +02:00
return false ;
2007-07-23 07:21:39 +02:00
}
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_CONNECTED ;
2014-04-07 06:45:25 +02:00
return true ;
2007-07-23 07:21:39 +02:00
}
2013-12-16 20:17:10 +01:00
/**
* Generates the SSH identifier
2013-12-28 20:47:24 +01:00
*
2013-12-28 18:16:09 +01:00
* You should overwrite this method in your own class if you want to use another identifier
2013-12-16 20:17:10 +01:00
*
* @ access protected
2016-04-10 18:30:59 +02:00
* @ return string
2013-12-16 20:17:10 +01:00
*/
2017-01-08 02:51:56 +01:00
private function generate_identifier ()
2013-12-16 20:17:10 +01:00
{
2015-07-23 03:02:28 +02:00
$identifier = 'SSH-2.0-phpseclib_2.0' ;
2013-12-16 20:17:10 +01:00
2016-11-30 22:19:23 +01:00
$ext = [];
2017-09-13 10:19:57 +02:00
if ( function_exists ( '\\Sodium\\library_version_major' )) {
2015-07-23 03:02:28 +02:00
$ext [] = 'libsodium' ;
}
2014-12-30 05:10:26 +01:00
if ( extension_loaded ( 'openssl' )) {
$ext [] = 'openssl' ;
} elseif ( extension_loaded ( 'mcrypt' )) {
2013-12-16 20:17:10 +01:00
$ext [] = 'mcrypt' ;
}
if ( extension_loaded ( 'gmp' )) {
$ext [] = 'gmp' ;
2013-12-17 18:44:37 +01:00
} elseif ( extension_loaded ( 'bcmath' )) {
$ext [] = 'bcmath' ;
2013-12-16 20:17:10 +01:00
}
if ( ! empty ( $ext )) {
$identifier .= ' (' . implode ( ', ' , $ext ) . ')' ;
}
return $identifier ;
}
2007-07-23 07:21:39 +02:00
/**
* Key Exchange
2017-11-05 21:35:27 +01:00
* @ return bool
* @ param string | bool $kexinit_payload_server optional
2016-04-30 23:23:35 +02:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
* @ throws \phpseclib\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-08-08 03:09:26 +02:00
private function key_exchange ( $kexinit_payload_server = false )
2007-07-23 07:21:39 +02:00
{
2019-03-30 00:44:31 +01:00
$preferred = $this -> preferred ;
$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 07:21:39 +02:00
2012-09-17 09:33:03 +02:00
// some SSH servers have buggy implementations of some of the above algorithms
2017-06-02 14:46:51 +02:00
switch ( true ) {
case $this -> server_identifier == 'SSH-2.0-SSHD' :
case substr ( $this -> server_identifier , 0 , 13 ) == 'SSH-2.0-DLINK' :
2019-03-30 00:44:31 +01: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 09:33:03 +02:00
}
2014-12-02 18:20:40 +01:00
$client_cookie = Random :: string ( 16 );
2007-07-23 07:21:39 +02:00
2019-04-02 07:09:19 +02:00
$kexinit_payload_client = pack ( 'Ca*' , NET_SSH2_MSG_KEXINIT , $client_cookie );
$kexinit_payload_client .= Strings :: packSSH2 (
'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 07:15:50 +02:00
);
if ( $this -> send_kex_first ) {
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $kexinit_payload_client );
2017-08-07 07:15:50 +02:00
2017-08-08 03:09:26 +02:00
$kexinit_payload_server = $this -> get_binary_packet ();
2017-08-07 07:15:50 +02:00
if ( $kexinit_payload_server === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2017-08-08 03:09:26 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2017-08-07 07:15:50 +02:00
}
if ( ! strlen ( $kexinit_payload_server ) || ord ( $kexinit_payload_server [ 0 ]) != NET_SSH2_MSG_KEXINIT ) {
2017-08-08 03:09:26 +02:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEXINIT' );
2017-08-07 07:15:50 +02:00
}
}
2007-07-23 07:21:39 +02:00
$response = $kexinit_payload_server ;
2016-07-31 05:18:06 +02: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 07:21:39 +02:00
2019-04-02 07:09:19 +02: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 07:21:39 +02:00
2019-03-30 20:12:52 +01:00
if ( ! $this -> send_kex_first ) {
$this -> send_binary_packet ( $kexinit_payload_client );
2007-07-23 07:21:39 +02:00
}
// we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
2016-04-30 23:23:35 +02:00
2007-07-23 07:21:39 +02: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-03-30 00:44:31 +01:00
$decrypt = $this -> array_intersect_first ( $s2c_encryption_algorithms , $this -> encryption_algorithms_server_to_client );
2017-01-08 02:51:56 +01:00
$decryptKeyLength = $this -> encryption_algorithm_to_key_size ( $decrypt );
2015-07-24 12:31:50 +02:00
if ( $decryptKeyLength === null ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible server to client encryption algorithms found' );
2007-07-23 07:21:39 +02:00
}
2019-03-30 00:44:31 +01:00
$encrypt = $this -> array_intersect_first ( $c2s_encryption_algorithms , $this -> encryption_algorithms_client_to_server );
2017-01-08 02:51:56 +01:00
$encryptKeyLength = $this -> encryption_algorithm_to_key_size ( $encrypt );
2015-07-24 12:31:50 +02:00
if ( $encryptKeyLength === null ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible client to server encryption algorithms found' );
2007-07-23 07:21:39 +02:00
}
// through diffie-hellman key exchange a symmetric key is obtained
2019-03-28 19:57:26 +01:00
$this -> kex_algorithm = $this -> array_intersect_first ( $kex_algorithms , $this -> kex_algorithms );
if ( $this -> kex_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible key exchange algorithms found' );
2007-07-23 07:21:39 +02:00
}
2015-06-25 00:50:00 +02:00
2015-07-23 03:02:28 +02:00
// Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
$exchange_hash_rfc4419 = '' ;
2015-06-25 00:50:00 +02:00
2019-03-28 19:57:26 +01:00
if ( $this -> kex_algorithm === 'curve25519-sha256@libssh.org' ) {
2015-07-23 03:02:28 +02:00
$x = Random :: string ( 32 );
2016-04-10 18:30:59 +02:00
$eBytes = \Sodium\crypto_box_publickey_from_secretkey ( $x );
2015-07-23 03:02:28 +02:00
$clientKexInitMessage = NET_SSH2_MSG_KEX_ECDH_INIT ;
$serverKexReplyMessage = NET_SSH2_MSG_KEX_ECDH_REPLY ;
$kexHash = new Hash ( 'sha256' );
2015-06-25 00:50:00 +02:00
} else {
2019-03-28 19:57:26 +01:00
if ( strpos ( $this -> kex_algorithm , 'diffie-hellman-group-exchange' ) === 0 ) {
2015-07-23 03:02:28 +02: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 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2015-07-23 03:02:28 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2015-07-23 03:02:28 +02:00
if ( $response === false ) {
2018-10-06 05:49:24 +02:00
$this -> bitmap = 0 ;
2017-12-23 21:32:22 +01:00
throw new \RuntimeException ( 'Connection closed by server' );
2015-07-23 03:02:28 +02:00
}
2019-04-02 07:09:19 +02:00
list ( $type , $primeBytes , $gBytes ) = unpack ( 'Css' , $response );
2015-07-23 03:02:28 +02:00
if ( $type != NET_SSH2_MSG_KEXDH_GEX_GROUP ) {
2018-12-31 16:06:12 +01:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEX_DH_GEX_GROUP' );
2015-07-23 03:02:28 +02:00
}
$prime = new BigInteger ( $primeBytes , - 256 );
$g = new BigInteger ( $gBytes , - 256 );
2019-04-02 07:09:19 +02:00
$exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings :: packSSH2 (
'ss' ,
2015-07-23 03:02:28 +02:00
$primeBytes ,
$gBytes
);
$clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT ;
$serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY ;
} else {
2019-03-28 19:57:26 +01:00
switch ( $this -> kex_algorithm ) {
2015-07-23 03:02:28 +02:00
// see http://tools.ietf.org/html/rfc2409#section-6.2 and
// http://tools.ietf.org/html/rfc2412, appendex E
case 'diffie-hellman-group1-sha1' :
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
2018-12-27 15:31:35 +01:00
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF' ;
2015-07-23 03:02:28 +02:00
break ;
// see http://tools.ietf.org/html/rfc3526#section-3
case 'diffie-hellman-group14-sha1' :
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
2018-12-27 15:31:35 +01:00
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF' ;
2015-07-23 03:02:28 +02:00
break ;
}
// For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
// the generator field element is 2 (decimal) and the hash function is sha1.
$g = new BigInteger ( 2 );
$prime = new BigInteger ( $prime , 16 );
$clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT ;
$serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY ;
}
2019-03-28 19:57:26 +01:00
switch ( $this -> kex_algorithm ) {
2015-07-23 03:02:28 +02:00
case 'diffie-hellman-group-exchange-sha256' :
$kexHash = new Hash ( 'sha256' );
2015-06-25 00:50:00 +02:00
break ;
2015-07-23 03:02:28 +02:00
default :
$kexHash = new Hash ( 'sha1' );
2015-06-25 00:50:00 +02:00
}
2007-07-23 07:21:39 +02:00
2015-07-23 03:02:28 +02:00
/* To increase the speed of the key exchange , both client and server may
reduce the size of their private exponents . It should be at least
twice as long as the key material that is generated from the shared
secret . For more details , see the paper by van Oorschot and Wiener
[ VAN - OORSCHOT ] .
2007-07-23 07:21:39 +02:00
2015-07-23 03:02:28 +02:00
-- http :// tools . ietf . org / html / rfc4419 #section-6.2 */
$one = new BigInteger ( 1 );
2016-10-19 14:45:42 +02:00
$keyLength = min ( $kexHash -> getLengthInBytes (), max ( $encryptKeyLength , $decryptKeyLength ));
2015-07-23 03:02:28 +02:00
$max = $one -> bitwise_leftShift ( 16 * $keyLength ); // 2 * 8 * $keyLength
$max = $max -> subtract ( $one );
2007-07-23 07:21:39 +02:00
2016-09-18 04:48:51 +02:00
$x = BigInteger :: randomRange ( $one , $max );
2015-07-23 03:02:28 +02:00
$e = $g -> modPow ( $x , $prime );
2007-07-23 07:21:39 +02:00
2015-07-23 03:02:28 +02:00
$eBytes = $e -> toBytes ( true );
}
2019-04-02 07:09:19 +02:00
2015-06-25 00:50:00 +02:00
$data = pack ( 'CNa*' , $clientKexInitMessage , strlen ( $eBytes ), $eBytes );
2007-07-23 07:21:39 +02:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $data );
2007-07-23 07:21:39 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2007-07-23 07:21:39 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2007-07-23 07:21:39 +02:00
}
2016-10-23 05:13:17 +02:00
if ( ! strlen ( $response )) {
return false ;
}
2007-07-23 07:21:39 +02:00
2019-04-02 07:09:19 +02:00
list (
$type ,
$server_public_host_key ,
$fBytes ,
$this -> signature
) = Strings :: unpackSSH2 ( 'Csss' , $response );
2007-07-23 07:21:39 +02:00
2019-04-02 07:09:19 +02:00
if ( $type != $serverKexReplyMessage ) {
throw new \UnexpectedValueException ( 'Expected SSH_MSG_KEXDH_REPLY' );
2016-10-23 05:13:17 +02:00
}
2007-07-23 07:21:39 +02:00
2019-04-02 07:09:19 +02:00
$this -> server_public_host_key = $server_public_host_key ;
list ( $public_key_format ) = Strings :: unpackSSH2 ( 's' , $server_public_host_key );
2007-07-23 07:21:39 +02:00
2016-10-23 05:13:17 +02:00
if ( strlen ( $this -> signature ) < 4 ) {
return false ;
}
2019-03-30 19:35:16 +01:00
$temp = unpack ( 'Nlength' , substr ( $this -> signature , 0 , 4 ));
$this -> signature_format = substr ( $this -> signature , 4 , $temp [ 'length' ]);
2019-03-28 19:57:26 +01:00
if ( $this -> kex_algorithm === 'curve25519-sha256@libssh.org' ) {
2015-07-23 03:02:28 +02:00
if ( strlen ( $fBytes ) !== 32 ) {
2017-12-23 21:32:22 +01:00
throw new \RuntimeException ( 'Received curve25519 public key of invalid length.' );
2015-07-23 03:02:28 +02:00
return false ;
}
2016-04-10 18:30:59 +02:00
$key = new BigInteger ( \Sodium\crypto_scalarmult ( $x , $fBytes ), 256 );
\Sodium\memzero ( $x );
2015-07-23 03:02:28 +02:00
} else {
$f = new BigInteger ( $fBytes , - 256 );
$key = $f -> modPow ( $x , $prime );
}
2007-07-23 07:21:39 +02:00
$keyBytes = $key -> toBytes ( true );
2019-04-02 07:09:19 +02:00
$this -> exchange_hash = Strings :: packSSH2 ( 's5' ,
2015-07-15 03:52:31 +02:00
$this -> identifier ,
$this -> server_identifier ,
$kexinit_payload_client ,
$kexinit_payload_server ,
2019-04-02 07:09:19 +02:00
$this -> server_public_host_key
);
$this -> exchange_hash .= $exchange_hash_rfc4419 ;
$this -> exchange_hash .= Strings :: packSSH2 ( 's3' ,
2015-07-15 03:52:31 +02:00
$eBytes ,
$fBytes ,
$keyBytes
2010-10-24 03:24:30 +02:00
);
2007-07-23 07:21:39 +02:00
2013-06-07 00:38:38 +02:00
$this -> exchange_hash = $kexHash -> hash ( $this -> exchange_hash );
2007-07-23 07:21:39 +02:00
2010-10-24 03:24:30 +02:00
if ( $this -> session_id === false ) {
$this -> session_id = $this -> exchange_hash ;
2007-07-23 07:21:39 +02:00
}
2017-01-08 02:51:56 +01:00
$server_host_key_algorithm = $this -> array_intersect_first ( $server_host_key_algorithms , $this -> server_host_key_algorithms );
2015-07-17 18:20:42 +02:00
if ( $server_host_key_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible server host key algorithms found' );
2007-07-23 07:21:39 +02:00
}
2018-05-27 16:48:44 +02:00
switch ( $server_host_key_algorithm ) {
2019-03-30 19:35:16 +01:00
case 'rsa-sha2-256' :
case 'rsa-sha2-512' :
2018-05-27 16:48:44 +02:00
//case 'ssh-rsa':
$expected_key_format = 'ssh-rsa' ;
2019-03-30 19:35:16 +01:00
break ;
default :
$expected_key_format = $server_host_key_algorithm ;
2018-05-27 16:48:44 +02:00
}
if ( $public_key_format != $expected_key_format || $this -> signature_format != $server_host_key_algorithm ) {
2019-01-17 04:15:11 +01: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' :
2019-01-17 04:17:18 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
throw new \RuntimeException ( 'Server Host Key Algorithm Mismatch' );
2019-01-17 04:15:11 +01:00
}
2007-07-23 07:21:39 +02:00
}
2019-04-02 07:09:19 +02:00
$packet = pack ( 'C' , NET_SSH2_MSG_NEWKEYS );
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2007-07-23 07:21:39 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2007-07-23 07:21:39 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2007-07-23 07:21:39 +02:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2008-05-25 09:28:57 +02:00
if ( $type != NET_SSH2_MSG_NEWKEYS ) {
2016-04-30 23:23:35 +02:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_NEWKEYS' );
2007-07-23 07:21:39 +02:00
}
2009-05-16 19:09:37 +02:00
$keyBytes = pack ( 'Na*' , strlen ( $keyBytes ), $keyBytes );
2019-03-29 03:45:28 +01:00
$this -> encrypt = self :: encryption_algorithm_to_crypt_instance ( $encrypt );
2009-05-16 19:09:37 +02:00
if ( $this -> encrypt ) {
2014-10-12 06:26:46 +02:00
if ( $this -> crypto_engine ) {
2017-06-28 05:34:36 +02:00
$this -> encrypt -> setPreferredEngine ( $this -> crypto_engine );
2014-10-12 06:26:46 +02:00
}
2016-12-03 21:39:11 +01:00
if ( $this -> encrypt -> getBlockLengthInBytes ()) {
$this -> encrypt_block_size = $this -> encrypt -> getBlockLengthInBytes ();
2015-07-28 11:22:17 +02:00
}
2009-05-16 19:09:37 +02:00
$this -> encrypt -> disablePadding ();
2016-04-30 23:23:35 +02:00
if ( $this -> encrypt -> usesIV ()) {
$iv = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'A' . $this -> session_id );
while ( $this -> encrypt_block_size > strlen ( $iv )) {
$iv .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $iv );
}
$this -> encrypt -> setIV ( substr ( $iv , 0 , $this -> encrypt_block_size ));
2009-05-16 19:09:37 +02:00
}
2019-03-18 12:59:00 +01:00
switch ( $encrypt ) {
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$nonce = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'A' . $this -> session_id );
$this -> encrypt -> fixed = substr ( $nonce , 0 , 4 );
$this -> encrypt -> invocation_counter = substr ( $nonce , 4 , 8 );
case 'chacha20-poly1305@openssh.com' :
break ;
default :
$this -> encrypt -> enableContinuousBuffer ();
2018-12-27 15:31:35 +01:00
}
2013-06-07 00:38:38 +02:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'C' . $this -> session_id );
2009-05-16 19:09:37 +02:00
while ( $encryptKeyLength > strlen ( $key )) {
2013-06-07 00:38:38 +02:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2009-05-16 19:09:37 +02:00
}
2019-03-18 12:59:00 +01:00
switch ( $encrypt ) {
case 'chacha20-poly1305@openssh.com' :
$encryptKeyLength = 32 ;
2019-03-29 03:45:28 +01:00
$this -> lengthEncrypt = self :: encryption_algorithm_to_crypt_instance ( $encrypt );
2019-03-18 12:59:00 +01:00
$this -> lengthEncrypt -> setKey ( substr ( $key , 32 , 32 ));
}
2009-05-16 19:09:37 +02:00
$this -> encrypt -> setKey ( substr ( $key , 0 , $encryptKeyLength ));
2018-12-27 15:31:35 +01:00
$this -> encrypt -> name = $encrypt ;
2009-05-16 19:09:37 +02:00
}
2019-03-29 03:45:28 +01:00
$this -> decrypt = self :: encryption_algorithm_to_crypt_instance ( $decrypt );
2009-05-16 19:09:37 +02:00
if ( $this -> decrypt ) {
2014-10-12 06:26:46 +02:00
if ( $this -> crypto_engine ) {
2017-06-28 05:34:36 +02:00
$this -> decrypt -> setPreferredEngine ( $this -> crypto_engine );
2014-10-12 06:26:46 +02:00
}
2016-12-03 21:39:11 +01:00
if ( $this -> decrypt -> getBlockLengthInBytes ()) {
$this -> decrypt_block_size = $this -> decrypt -> getBlockLengthInBytes ();
2015-07-28 11:22:17 +02:00
}
2009-05-16 19:09:37 +02:00
$this -> decrypt -> disablePadding ();
2016-04-30 23:23:35 +02:00
if ( $this -> decrypt -> usesIV ()) {
$iv = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'B' . $this -> session_id );
while ( $this -> decrypt_block_size > strlen ( $iv )) {
$iv .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $iv );
}
$this -> decrypt -> setIV ( substr ( $iv , 0 , $this -> decrypt_block_size ));
2009-05-16 19:09:37 +02:00
}
2007-07-23 07:21:39 +02:00
2019-03-30 00:44:31 +01:00
switch ( $decrypt ) {
2019-03-18 12:59:00 +01: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 );
$this -> decrypt -> fixed = substr ( $nonce , 0 , 4 );
$this -> decrypt -> invocation_counter = substr ( $nonce , 4 , 8 );
case 'chacha20-poly1305@openssh.com' :
break ;
default :
$this -> decrypt -> enableContinuousBuffer ();
2018-12-27 15:31:35 +01:00
}
2013-06-07 00:38:38 +02:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'D' . $this -> session_id );
2009-05-16 19:09:37 +02:00
while ( $decryptKeyLength > strlen ( $key )) {
2013-06-07 00:38:38 +02:00
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
2009-05-16 19:09:37 +02:00
}
2019-03-18 12:59:00 +01:00
switch ( $decrypt ) {
case 'chacha20-poly1305@openssh.com' :
$decryptKeyLength = 32 ;
2019-03-29 03:45:28 +01:00
$this -> lengthDecrypt = self :: encryption_algorithm_to_crypt_instance ( $decrypt );
2019-03-18 12:59:00 +01:00
$this -> lengthDecrypt -> setKey ( substr ( $key , 32 , 32 ));
}
2009-05-16 19:09:37 +02:00
$this -> decrypt -> setKey ( substr ( $key , 0 , $decryptKeyLength ));
2018-12-27 15:31:35 +01:00
$this -> decrypt -> name = $decrypt ;
2009-05-16 19:09:37 +02:00
}
2007-07-23 07:21:39 +02:00
2010-02-09 07:10:26 +01: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-03-30 00:44:31 +01:00
$mac_algorithm = $this -> array_intersect_first ( $c2s_mac_algorithms , $this -> mac_algorithms_client_to_server );
2015-07-17 18:20:42 +02:00
if ( $mac_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible client to server message authentication algorithms found' );
2007-07-23 07:21:39 +02:00
}
2018-12-27 15:31:35 +01:00
if ( $this -> encrypt -> usesNonce ()) {
2019-03-29 03:45:28 +01:00
$this -> hmac_create = new \stdClass ;
$this -> hmac_create -> name = $mac_algorithm ;
2018-12-27 15:31:35 +01:00
$mac_algorithm = 'none' ;
}
2015-07-17 18:20:42 +02:00
$createKeyLength = 0 ; // ie. $mac_algorithm == 'none'
switch ( $mac_algorithm ) {
2014-07-25 12:28:08 +02:00
case 'hmac-sha2-256' :
2014-12-17 01:16:54 +01:00
$this -> hmac_create = new Hash ( 'sha256' );
2014-07-25 12:28:08 +02:00
$createKeyLength = 32 ;
break ;
2007-07-23 07:21:39 +02:00
case 'hmac-sha1' :
2014-12-17 01:16:54 +01:00
$this -> hmac_create = new Hash ( 'sha1' );
2007-07-23 07:21:39 +02:00
$createKeyLength = 20 ;
break ;
case 'hmac-sha1-96' :
2014-12-17 01:16:54 +01:00
$this -> hmac_create = new Hash ( 'sha1-96' );
2007-07-23 07:21:39 +02:00
$createKeyLength = 20 ;
break ;
case 'hmac-md5' :
2014-12-17 01:16:54 +01:00
$this -> hmac_create = new Hash ( 'md5' );
2007-07-23 07:21:39 +02:00
$createKeyLength = 16 ;
break ;
case 'hmac-md5-96' :
2014-12-17 01:16:54 +01:00
$this -> hmac_create = new Hash ( 'md5-96' );
2007-07-23 07:21:39 +02:00
$createKeyLength = 16 ;
}
2019-03-29 03:45:28 +01:00
if ( $this -> hmac_create instanceof Hash ) {
2018-12-27 15:31:35 +01:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'E' . $this -> session_id );
while ( $createKeyLength > strlen ( $key )) {
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
}
$this -> hmac_create -> setKey ( substr ( $key , 0 , $createKeyLength ));
$this -> hmac_create -> name = $mac_algorithm ;
}
2019-03-30 00:44:31 +01:00
$mac_algorithm = $this -> array_intersect_first ( $s2c_mac_algorithms , $this -> mac_algorithms_server_to_client );
2015-07-17 18:20:42 +02:00
if ( $mac_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible server to client message authentication algorithms found' );
2007-07-23 07:21:39 +02:00
}
2018-12-27 15:31:35 +01:00
if ( $this -> decrypt -> usesNonce ()) {
2019-03-29 03:45:28 +01:00
$this -> hmac_check = new \stdClass ;
$this -> hmac_check -> name = $mac_algorithm ;
2018-12-27 15:31:35 +01:00
$mac_algorithm = 'none' ;
}
2009-02-16 23:22:13 +01:00
$checkKeyLength = 0 ;
$this -> hmac_size = 0 ;
2015-07-17 18:20:42 +02:00
switch ( $mac_algorithm ) {
2014-07-25 12:28:08 +02:00
case 'hmac-sha2-256' :
2014-12-17 01:16:54 +01:00
$this -> hmac_check = new Hash ( 'sha256' );
2014-07-25 12:28:08 +02:00
$checkKeyLength = 32 ;
$this -> hmac_size = 32 ;
break ;
2007-07-23 07:21:39 +02:00
case 'hmac-sha1' :
2014-12-17 01:16:54 +01:00
$this -> hmac_check = new Hash ( 'sha1' );
2007-07-23 07:21:39 +02:00
$checkKeyLength = 20 ;
$this -> hmac_size = 20 ;
break ;
case 'hmac-sha1-96' :
2014-12-17 01:16:54 +01:00
$this -> hmac_check = new Hash ( 'sha1-96' );
2007-07-23 07:21:39 +02:00
$checkKeyLength = 20 ;
$this -> hmac_size = 12 ;
break ;
case 'hmac-md5' :
2014-12-17 01:16:54 +01:00
$this -> hmac_check = new Hash ( 'md5' );
2007-07-23 07:21:39 +02:00
$checkKeyLength = 16 ;
$this -> hmac_size = 16 ;
break ;
case 'hmac-md5-96' :
2014-12-17 01:16:54 +01:00
$this -> hmac_check = new Hash ( 'md5-96' );
2007-07-23 07:21:39 +02:00
$checkKeyLength = 16 ;
$this -> hmac_size = 12 ;
}
2019-03-29 03:45:28 +01:00
if ( $this -> hmac_check instanceof Hash ) {
2018-12-27 15:31:35 +01:00
$key = $kexHash -> hash ( $keyBytes . $this -> exchange_hash . 'F' . $this -> session_id );
while ( $checkKeyLength > strlen ( $key )) {
$key .= $kexHash -> hash ( $keyBytes . $this -> exchange_hash . $key );
}
$this -> hmac_check -> setKey ( substr ( $key , 0 , $checkKeyLength ));
$this -> hmac_check -> name = $mac_algorithm ;
2007-07-23 07:21:39 +02:00
}
2008-05-26 21:42:01 +02:00
2019-03-30 00:44:31 +01:00
$compression_algorithm = $this -> array_intersect_first ( $s2c_compression_algorithms , $this -> compression_algorithms_server_to_client );
2015-07-17 18:20:42 +02:00
if ( $compression_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible server to client compression algorithms found' );
2009-05-16 19:09:37 +02:00
}
2015-07-17 18:20:42 +02:00
$this -> decompress = $compression_algorithm == 'zlib' ;
2009-05-16 19:09:37 +02:00
2019-03-30 00:44:31 +01:00
$compression_algorithm = $this -> array_intersect_first ( $c2s_compression_algorithms , $this -> compression_algorithms_client_to_server );
2015-07-17 18:20:42 +02:00
if ( $compression_algorithm === false ) {
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'No compatible client to server compression algorithms found' );
2009-05-16 19:09:37 +02:00
}
2015-07-17 18:20:42 +02:00
$this -> compress = $compression_algorithm == 'zlib' ;
2009-05-30 18:40:31 +02:00
return true ;
2007-07-23 07:21:39 +02:00
}
2015-07-24 12:31:50 +02:00
/**
* Maps an encryption algorithm name to the number of key bytes .
*
2016-04-10 18:30:59 +02: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 12:31:50 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function encryption_algorithm_to_key_size ( $algorithm )
2015-07-24 12:31:50 +02:00
{
2018-12-30 03:54:18 +01:00
if ( $this -> bad_key_size_fix && self :: bad_algorithm_candidate ( $algorithm )) {
2017-08-27 02:50:17 +02:00
return 16 ;
}
2015-07-24 12:31:50 +02:00
switch ( $algorithm ) {
case 'none' :
return 0 ;
2018-12-27 15:31:35 +01:00
case 'aes128-gcm@openssh.com' :
2015-07-24 12:31:50 +02: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 15:31:35 +01:00
case 'aes256-gcm@openssh.com' :
2015-07-24 12:31:50 +02:00
case 'aes256-cbc' :
case 'aes256-ctr' :
case 'arcfour256' :
case 'twofish-cbc' :
case 'twofish256-cbc' :
case 'twofish256-ctr' :
return 32 ;
2019-03-18 12:59:00 +01:00
case 'chacha20-poly1305@openssh.com' :
return 64 ;
2015-07-24 12:31:50 +02:00
}
return null ;
}
2015-07-28 11:22:17 +02:00
/**
* Maps an encryption algorithm name to an instance of a subclass of
* \phpseclib\Crypt\Base .
*
2016-04-10 18:30:59 +02:00
* @ param string $algorithm Name of the encryption algorithm
* @ return mixed Instance of \phpseclib\Crypt\Base or null for unknown
2015-07-28 11:22:17 +02:00
* @ access private
*/
2019-03-29 03:45:28 +01:00
private static function encryption_algorithm_to_crypt_instance ( $algorithm )
2015-07-28 11:22:17 +02:00
{
switch ( $algorithm ) {
case '3des-cbc' :
2017-06-28 05:34:36 +02:00
return new TripleDES ( 'cbc' );
2015-07-28 11:22:17 +02:00
case '3des-ctr' :
2017-06-28 05:34:36 +02:00
return new TripleDES ( 'ctr' );
2015-07-28 11:22:17 +02:00
case 'aes256-cbc' :
case 'aes192-cbc' :
case 'aes128-cbc' :
2017-06-28 05:34:36 +02:00
return new Rijndael ( 'cbc' );
2015-07-28 11:22:17 +02:00
case 'aes256-ctr' :
case 'aes192-ctr' :
case 'aes128-ctr' :
2017-06-28 05:34:36 +02:00
return new Rijndael ( 'ctr' );
2015-07-28 11:22:17 +02:00
case 'blowfish-cbc' :
2017-06-28 05:34:36 +02:00
return new Blowfish ( 'cbc' );
2015-07-28 11:22:17 +02:00
case 'blowfish-ctr' :
2017-06-28 05:34:36 +02:00
return new Blowfish ( 'ctr' );
2015-07-28 11:22:17 +02:00
case 'twofish128-cbc' :
case 'twofish192-cbc' :
case 'twofish256-cbc' :
case 'twofish-cbc' :
2017-06-28 05:34:36 +02:00
return new Twofish ( 'cbc' );
2015-07-28 11:22:17 +02:00
case 'twofish128-ctr' :
case 'twofish192-ctr' :
case 'twofish256-ctr' :
2017-06-28 05:34:36 +02:00
return new Twofish ( 'ctr' );
2015-07-28 11:22:17 +02:00
case 'arcfour' :
case 'arcfour128' :
case 'arcfour256' :
return new RC4 ();
2018-12-27 15:31:35 +01:00
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
2019-03-24 02:53:20 +01:00
return new Rijndael ( 'gcm' );
2019-03-18 12:59:00 +01:00
case 'chacha20-poly1305@openssh.com' :
return new ChaCha20 ();
2015-07-28 11:22:17 +02:00
}
return null ;
}
2017-08-27 09:43:31 +02:00
/*
2017-08-27 02:50:17 +02: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
* @ access private
*/
2018-12-30 03:54:18 +01:00
private static function bad_algorithm_candidate ( $algorithm )
2017-08-27 02:50:17 +02:00
{
switch ( $algorithm ) {
case 'arcfour256' :
case 'aes192-ctr' :
case 'aes256-ctr' :
return true ;
}
return false ;
}
2007-07-23 07:21:39 +02:00
/**
* Login
*
2014-12-17 01:16:54 +01:00
* The $password parameter can be a plaintext password , a \phpseclib\Crypt\RSA object or an array
2010-08-28 19:26:22 +02:00
*
2016-04-10 18:30:59 +02:00
* @ param string $username
2017-11-21 09:36:28 +01:00
* @ param $args [] param mixed $password
2016-04-10 18:30:59 +02:00
* @ return bool
* @ see self :: _login ()
2007-07-23 07:21:39 +02:00
* @ access public
2013-04-20 21:33:07 +02:00
*/
2017-11-21 09:36:28 +01:00
public function login ( $username , ... $args )
2013-12-23 01:14:49 +01:00
{
2018-10-06 15:02:59 +02:00
$this -> auth [] = array_merge ([ $username ], $args );
2017-11-21 09:36:28 +01:00
return $this -> sublogin ( $username , ... $args );
2013-12-23 01:14:49 +01:00
}
/**
* Login Helper
*
2016-04-10 18:30:59 +02:00
* @ param string $username
2017-11-21 09:36:28 +01:00
* @ param $args [] param mixed $password
2016-04-10 18:30:59 +02:00
* @ return bool
* @ see self :: _login_helper ()
2013-12-23 01:14:49 +01:00
* @ access private
*/
2017-11-21 09:36:28 +01:00
protected function sublogin ( $username , ... $args )
2013-04-20 21:33:07 +02:00
{
2014-12-04 22:45:13 +01:00
if ( ! ( $this -> bitmap & self :: MASK_CONSTRUCTOR )) {
2017-01-08 02:51:56 +01:00
if ( ! $this -> connect ()) {
2014-04-08 15:48:12 +02:00
return false ;
}
}
2013-04-20 21:33:07 +02:00
if ( empty ( $args )) {
2017-01-08 02:51:56 +01:00
return $this -> login_helper ( $username );
2013-04-20 21:33:07 +02:00
}
2013-04-20 21:35:08 +02:00
2013-04-20 21:33:07 +02:00
foreach ( $args as $arg ) {
2017-01-08 02:51:56 +01:00
if ( $this -> login_helper ( $username , $arg )) {
2013-04-20 21:33:07 +02:00
return true ;
}
}
return false ;
}
/**
* Login Helper
*
2016-04-10 18:30:59 +02:00
* @ param string $username
* @ param string $password
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2013-04-20 21:33:07 +02:00
* @ access private
2009-05-16 19:09:37 +02: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}
2009-12-03 09:19:00 +01:00
* by sending dummy SSH_MSG_IGNORE messages .
2007-07-23 07:21:39 +02:00
*/
2017-01-08 02:51:56 +01:00
private function login_helper ( $username , $password = null )
2007-07-23 07:21:39 +02:00
{
2014-12-04 22:45:13 +01:00
if ( ! ( $this -> bitmap & self :: MASK_CONNECTED )) {
2007-07-23 07:21:39 +02:00
return false ;
}
2014-12-04 22:45:13 +01:00
if ( ! ( $this -> bitmap & self :: MASK_LOGIN_REQ )) {
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 ( 'Cs' , NET_SSH2_MSG_SERVICE_REQUEST , 'ssh-userauth' );
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2007-07-23 07:21:39 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2013-04-20 05:23:06 +02:00
if ( $response === false ) {
2017-08-27 02:50:17 +02:00
if ( $this -> retry_connect ) {
$this -> retry_connect = false ;
2017-08-27 19:33:19 +02:00
if ( ! $this -> connect ()) {
2017-08-27 02:50:17 +02:00
return false ;
}
2017-08-27 19:33:19 +02:00
return $this -> login_helper ( $username , $password );
2017-08-27 02:50:17 +02:00
}
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2013-04-20 05:23:06 +02:00
}
2007-07-23 07:21:39 +02:00
2019-04-02 07:09:19 +02:00
list ( $type , $service ) = Strings :: unpackSSH2 ( 'Cs' , $response );
if ( $type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth' ) {
2016-04-30 23:23:35 +02:00
throw new \UnexpectedValueException ( 'Expected SSH_MSG_SERVICE_ACCEPT' );
2013-04-20 05:23:06 +02:00
}
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN_REQ ;
2013-04-20 05:23:06 +02:00
}
if ( strlen ( $this -> last_interactive_response )) {
2017-01-08 02:51:56 +01:00
return ! is_string ( $password ) && ! is_array ( $password ) ? false : $this -> keyboard_interactive_process ( $password );
2007-07-23 07:21:39 +02:00
}
2019-05-19 22:35:29 +02:00
if ( $password instanceof PrivateKey ) {
2017-01-08 02:51:56 +01:00
return $this -> privatekey_login ( $username , $password );
2019-05-19 22:35:29 +02:00
}
if ( $password instanceof Agent ) {
2017-01-08 02:51:56 +01:00
return $this -> ssh_agent_login ( $username , $password );
2009-12-03 09:19:00 +01:00
}
2013-04-20 21:35:08 +02:00
if ( is_array ( $password )) {
2017-01-08 02:51:56 +01:00
if ( $this -> keyboard_interactive_login ( $username , $password )) {
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN ;
2013-04-20 21:35:08 +02:00
return true ;
}
return false ;
}
2019-05-19 22:35:29 +02:00
if ( ! is_string ( $password )) {
throw new \UnexpectedValueException ( '$password needs to either be an instance of \phpseclib\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string' );
}
2012-11-22 21:25:57 +01:00
if ( ! isset ( $password )) {
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'Cs3' ,
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'none'
2012-11-22 21:25:57 +01:00
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2012-11-22 21:25:57 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2012-11-22 21:25:57 +01:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2012-11-22 21:25:57 +01:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2012-11-22 21:25:57 +01:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN ;
2012-11-22 21:25:57 +01:00
return true ;
//case NET_SSH2_MSG_USERAUTH_FAILURE:
default :
return false ;
}
}
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'Cs3bs' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'password' ,
2019-04-02 07:09:19 +02:00
false ,
2015-07-15 03:52:31 +02:00
$password
2007-07-23 07:21:39 +02:00
);
2013-09-12 15:45:13 +02:00
// remove the username and password from the logged packet
if ( ! defined ( 'NET_SSH2_LOGGING' )) {
2013-12-03 18:54:43 +01:00
$logged = null ;
2013-09-12 15:45:13 +02:00
} else {
2019-04-02 07:09:19 +02:00
$logged = Strings :: packSSH2 (
'Cs3bs' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
2019-04-02 07:09:19 +02:00
$username ,
2015-07-15 03:52:31 +02:00
'ssh-connection' ,
'password' ,
2019-04-02 07:09:19 +02:00
false ,
2015-07-15 03:52:31 +02:00
'password'
2009-05-23 16:42:17 +02:00
);
2013-09-12 15:45:13 +02:00
}
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet , $logged );
2009-05-23 16:42:17 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2007-07-23 07:21:39 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2007-07-23 07:21:39 +02:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2007-07-23 07:21:39 +02:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ : // in theory, the password can be changed
2009-12-03 09:19:00 +01:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' ;
}
2016-11-30 02:35:50 +01:00
2019-04-02 07:09:19 +02:00
list ( $message ) = Strings :: unpackSSH2 ( 's' , $response );
$this -> errors [] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message ;
2016-11-30 02:35:50 +01:00
2017-01-08 02:51:56 +01:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER );
2009-12-03 09:19:00 +01:00
case NET_SSH2_MSG_USERAUTH_FAILURE :
2010-08-28 19:26:22 +02: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 07:09:19 +02:00
list ( $auth_methods , $partial_success ) = Strings :: unpackSSH2 ( 'Lb' , $response );
2013-04-20 05:23:06 +02:00
if ( ! $partial_success && in_array ( 'keyboard-interactive' , $auth_methods )) {
2017-01-08 02:51:56 +01:00
if ( $this -> keyboard_interactive_login ( $username , $password )) {
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN ;
2010-09-13 00:12:45 +02:00
return true ;
}
return false ;
}
return false ;
2009-12-03 09:19:00 +01:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN ;
2009-12-03 09:19:00 +01:00
return true ;
}
return false ;
}
2010-08-28 19:26:22 +02: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 18:30:59 +02:00
* @ param string $username
* @ param string $password
* @ return bool
2010-08-28 19:26:22 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function keyboard_interactive_login ( $username , $password )
2010-08-28 19:26:22 +02:00
{
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'Cs5' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'keyboard-interactive' ,
2019-04-02 07:09:19 +02:00
'' , // language tag
'' // submethods
2010-08-28 19:26:22 +02:00
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2010-08-28 19:26:22 +02:00
2017-01-08 02:51:56 +01:00
return $this -> keyboard_interactive_process ( $password );
2010-08-28 19:26:22 +02:00
}
/**
* Handle the keyboard - interactive requests / responses .
*
2017-11-21 09:36:28 +01:00
* @ param $responses []
2016-04-10 18:30:59 +02:00
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2010-08-28 19:26:22 +02:00
* @ access private
*/
2017-11-21 09:36:28 +01:00
private function keyboard_interactive_process ( ... $responses )
2010-08-28 19:26:22 +02:00
{
2013-04-20 05:23:06 +02:00
if ( strlen ( $this -> last_interactive_response )) {
$response = $this -> last_interactive_response ;
} else {
2017-01-08 02:51:56 +01:00
$orig = $response = $this -> get_binary_packet ();
2013-04-20 05:23:06 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2013-04-20 05:23:06 +02:00
}
2010-08-28 19:26:22 +02:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2010-08-28 19:26:22 +02:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_INFO_REQUEST :
2019-04-02 07:09:19 +02:00
list (
, // name; may be empty
, // instruction; may be empty
, // language tag; may be empty
$num_prompts
) = Strings :: unpackSSH2 ( 's3N' , $response );
2013-04-20 21:35:08 +02: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 07:09:19 +02:00
list (
$prompt , // prompt - ie. "Password: "; must not be empty
// echo
) = Strings :: unpackSSH2 ( 'sC' , $response );
2013-04-20 21:35:08 +02:00
foreach ( $this -> keyboard_requests_responses as $key => $value ) {
if ( substr ( $prompt , 0 , strlen ( $key )) == $key ) {
$responses [] = $value ;
break ;
}
}
}
2010-08-28 19:26:22 +02:00
}
2013-04-20 05:23:06 +02:00
// see http://tools.ietf.org/html/rfc4256#section-3.2
if ( strlen ( $this -> last_interactive_response )) {
$this -> last_interactive_response = '' ;
2019-04-02 07:09:19 +02:00
} elseif ( defined ( 'NET_SSH2_LOGGING' ) && NET_SSH2_LOGGING == self :: LOG_COMPLEX ) {
2013-04-20 05:23:06 +02:00
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ] = str_replace (
'UNKNOWN' ,
'NET_SSH2_MSG_USERAUTH_INFO_REQUEST' ,
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ]
);
}
if ( ! count ( $responses ) && $num_prompts ) {
$this -> last_interactive_response = $orig ;
return false ;
}
2010-08-28 19:26:22 +02: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 05:27:02 +02:00
$packet = $logged = pack ( 'CN' , NET_SSH2_MSG_USERAUTH_INFO_RESPONSE , count ( $responses ));
2010-08-28 19:26:22 +02:00
for ( $i = 0 ; $i < count ( $responses ); $i ++ ) {
2019-04-02 07:09:19 +02:00
$packet .= Strings :: packSSH2 ( 's' , $responses [ $i ]);
$logged .= Strings :: packSSH2 ( 's' , 'dummy-answer' );
2010-08-28 19:26:22 +02:00
}
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet , $logged );
2010-08-28 19:26:22 +02:00
2014-12-04 22:45:13 +01:00
if ( defined ( 'NET_SSH2_LOGGING' ) && NET_SSH2_LOGGING == self :: LOG_COMPLEX ) {
2011-02-28 06:24:09 +01:00
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ] = str_replace (
'UNKNOWN' ,
'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE' ,
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ]
);
2010-08-28 19:26:22 +02: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 02:51:56 +01:00
return $this -> keyboard_interactive_process ();
2010-08-28 19:26:22 +02:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
return true ;
case NET_SSH2_MSG_USERAUTH_FAILURE :
return false ;
}
return false ;
}
2014-02-10 07:04:16 +01:00
/**
* Login with an ssh - agent provided key
*
2016-04-10 18:30:59 +02:00
* @ param string $username
2014-12-15 18:25:46 +01:00
* @ param \phpseclib\System\SSH\Agent $agent
2016-04-10 18:30:59 +02:00
* @ return bool
2014-02-10 07:04:16 +01:00
* @ access private
*/
2019-05-19 22:35:29 +02:00
private function ssh_agent_login ( $username , Agent $agent )
2014-02-10 07:04:16 +01:00
{
2014-12-30 03:44:31 +01:00
$this -> agent = $agent ;
2014-02-10 07:04:16 +01:00
$keys = $agent -> requestIdentities ();
foreach ( $keys as $key ) {
2017-01-08 02:51:56 +01:00
if ( $this -> privatekey_login ( $username , $key )) {
2014-02-10 07:04:16 +01:00
return true ;
}
}
return false ;
}
2009-12-03 09:19:00 +01:00
/**
* Login with an RSA private key
*
2016-04-10 18:30:59 +02:00
* @ param string $username
2019-05-19 22:35:29 +02:00
* @ param \phpseclib\Crypt\Common\PrivateKey $privatekey
2016-04-10 18:30:59 +02:00
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2009-12-03 09:19:00 +01:00
* @ access private
* @ 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 .
*/
2019-05-19 22:35:29 +02:00
private function privatekey_login ( $username , PrivateKey $privatekey )
2009-12-03 09:19:00 +01:00
{
2019-05-19 22:35:29 +02:00
$publickey = $privatekey -> getPublicKey ();
if ( $publickey instanceof RSA ) {
$privatekey = $privatekey -> withPadding ( RSA :: SIGNATURE_PKCS1 );
switch ( $this -> signature_format ) {
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' ;
}
} else if ( $publickey instanceof ECDSA ) {
$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' );
}
throw new UnsupportedCurveException ( 'Named Curve of ' . $curveName . ' is not supported by phpseclib\'s SSH2 implementation' );
}
} else if ( $publickey instanceof DSA ) {
$privatekey = $privatekey -> withSignatureFormat ( 'SSH2' );
$hash = 'sha1' ;
$signatureType = 'ssh-dss' ;
} else {
throw new UnsupportedAlgorithmException ( 'Please use either an RSA key, an ECDSA one or a DSA key' );
2009-12-06 08:26:52 +01:00
}
2019-06-01 20:23:11 +02:00
$publickeyStr = $publickey -> toString ( 'OpenSSH' , [ 'binary' => true ]);
2019-01-16 06:41:49 +01:00
2019-04-02 07:09:19 +02:00
$part1 = Strings :: packSSH2 (
'Csss' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_USERAUTH_REQUEST ,
$username ,
'ssh-connection' ,
'publickey'
2009-12-03 09:19:00 +01:00
);
2019-05-19 22:35:29 +02:00
$part2 = Strings :: packSSH2 ( 'ss' , $signatureType , $publickeyStr );
2009-12-03 09:19:00 +01:00
$packet = $part1 . chr ( 0 ) . $part2 ;
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2009-12-03 09:19:00 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2009-12-03 09:19:00 +01:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2009-12-03 09:19:00 +01:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2009-12-03 09:19:00 +01:00
switch ( $type ) {
2007-07-23 07:21:39 +02:00
case NET_SSH2_MSG_USERAUTH_FAILURE :
2019-04-02 07:09:19 +02:00
list ( $message ) = Strings :: unpackSSH2 ( 's' , $response );
$this -> errors [] = 'SSH_MSG_USERAUTH_FAILURE: ' . $message ;
2013-04-20 05:23:06 +02:00
return false ;
2009-12-03 09:19:00 +01: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
2014-12-04 22:45:13 +01:00
if ( defined ( 'NET_SSH2_LOGGING' ) && NET_SSH2_LOGGING == self :: LOG_COMPLEX ) {
2011-04-18 14:17:40 +02:00
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ] = str_replace (
'UNKNOWN' ,
'NET_SSH2_MSG_USERAUTH_PK_OK' ,
$this -> message_number_log [ count ( $this -> message_number_log ) - 1 ]
);
2009-12-03 09:19:00 +01:00
}
}
$packet = $part1 . chr ( 1 ) . $part2 ;
2019-05-19 22:35:29 +02: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 );
}
2019-04-02 07:09:19 +02:00
$packet .= Strings :: packSSH2 ( 's' , $signature );
2009-12-03 09:19:00 +01:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2009-12-03 09:19:00 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2009-12-03 09:19:00 +01:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2009-12-03 09:19:00 +01:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2009-12-03 09:19:00 +01:00
switch ( $type ) {
case NET_SSH2_MSG_USERAUTH_FAILURE :
2012-08-14 19:07:18 +02:00
// either the login is bad or the server employs multi-factor authentication
2009-12-03 09:19:00 +01:00
return false ;
2007-07-23 07:21:39 +02:00
case NET_SSH2_MSG_USERAUTH_SUCCESS :
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_LOGIN ;
2007-07-23 07:21:39 +02:00
return true ;
}
return false ;
}
2012-03-03 18:49:16 +01: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 18:30:59 +02:00
* @ param mixed $timeout
2013-04-27 21:10:36 +02:00
* @ access public
2012-03-03 18:49:16 +01:00
*/
2017-01-08 02:51:56 +01:00
public function setTimeout ( $timeout )
2012-03-03 18:49:16 +01:00
{
2012-03-03 20:56:22 +01:00
$this -> timeout = $this -> curTimeout = $timeout ;
2012-03-03 18:49:16 +01:00
}
2013-03-21 23:18:31 +01:00
/**
* Get the output from stdError
2013-12-26 00:33:08 +01:00
*
2013-04-27 21:12:59 +02:00
* @ access public
2013-03-21 23:18:31 +01:00
*/
2017-01-08 02:51:56 +01:00
public function getStdError ()
2013-04-27 21:10:36 +02:00
{
2013-03-21 23:18:31 +01:00
return $this -> stdErrorLog ;
}
2007-07-25 23:49:33 +02:00
/**
* Execute Command
*
2018-09-10 17:17:20 +02:00
* If $callback is set to false then \phpseclib\Net\SSH2 :: get_channel_packet ( self :: CHANNEL_EXEC ) will need to be called manually .
2010-11-30 05:34:31 +01:00
* In all likelihood , this is not a feature you want to be taking advantage of .
*
2016-04-10 18:30:59 +02:00
* @ param string $command
* @ param Callback $callback
* @ return string
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2007-07-25 23:49:33 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function exec ( $command , $callback = null )
2007-07-25 23:49:33 +02:00
{
2012-03-03 18:49:16 +01:00
$this -> curTimeout = $this -> timeout ;
2013-06-08 00:21:11 +02:00
$this -> is_timeout = false ;
2013-03-21 23:18:31 +01:00
$this -> stdErrorLog = '' ;
2012-03-03 18:49:16 +01:00
2016-12-16 17:27:56 +01:00
if ( ! $this -> isAuthenticated ()) {
2007-07-25 23:49:33 +02:00
return false ;
}
2016-12-17 23:09:48 +01:00
if ( $this -> in_request_pty_exec ) {
2017-12-23 21:32:22 +01: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 23:49:33 +02:00
}
2009-09-17 05:19:20 +02:00
// RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2013-12-26 00:33:08 +01:00
// be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
2016-07-31 14:41:23 +02:00
// honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
2009-05-16 19:09:37 +02:00
// see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
2014-12-04 22:45:13 +01:00
$this -> window_size_server_to_client [ self :: CHANNEL_EXEC ] = $this -> window_size ;
2009-05-16 19:09:37 +02:00
// 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
2009-09-17 05:19:20 +02:00
// uses 0x4000, that's what will be used here, as well.
2009-05-16 19:09:37 +02:00
$packet_size = 0x4000 ;
2007-07-25 23:49:33 +02:00
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 13:36:18 +02:00
self :: CHANNEL_EXEC ,
$this -> window_size_server_to_client [ self :: CHANNEL_EXEC ],
2015-07-15 03:52:31 +02:00
$packet_size
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2007-07-25 23:49:33 +02:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2007-07-25 23:49:33 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_EXEC );
2007-07-25 23:49:33 +02:00
if ( $response === false ) {
return false ;
}
2013-02-08 23:04:52 +01:00
if ( $this -> request_pty === true ) {
$terminal_modes = pack ( 'C' , NET_SSH2_TTY_OP_END );
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsCsN4s' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 13:36:18 +02:00
$this -> server_channels [ self :: CHANNEL_EXEC ],
2015-07-15 03:52:31 +02:00
'pty-req' ,
1 ,
'vt100' ,
$this -> windowColumns ,
$this -> windowRows ,
0 ,
0 ,
$terminal_modes
);
2013-02-08 23:04:52 +01:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2014-12-30 03:44:31 +01:00
2017-11-11 23:13:48 +01:00
$response = $this -> get_binary_packet ();
2013-02-08 23:04:52 +01:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2013-02-08 23:04:52 +01:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2013-02-08 23:04:52 +01:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_SUCCESS :
break ;
case NET_SSH2_MSG_CHANNEL_FAILURE :
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Unable to request pseudo-terminal' );
2013-02-08 23:04:52 +01:00
}
$this -> in_request_pty_exec = true ;
}
2010-03-22 23:01:38 +01:00
// sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2014-12-10 02:31:41 +01:00
// down. the one place where it might be desirable is if you're doing something like \phpseclib\Net\SSH2::exec('ping localhost &').
2010-05-16 18:10:50 +02:00
// with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2010-03-22 23:01:38 +01: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 23:49:33 +02:00
2009-09-17 05:19:20 +02:00
// although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
2013-12-26 00:33:08 +01:00
// SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
2009-09-17 05:19:20 +02:00
// "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsCs' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 13:36:18 +02:00
$this -> server_channels [ self :: CHANNEL_EXEC ],
2015-07-15 03:52:31 +02:00
'exec' ,
1 ,
$command
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2007-07-25 23:49:33 +02:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2009-12-14 19:14:54 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_EXEC );
2007-07-25 23:49:33 +02:00
if ( $response === false ) {
return false ;
}
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_EXEC ] = NET_SSH2_MSG_CHANNEL_DATA ;
2007-07-25 23:49:33 +02:00
2013-05-29 00:02:27 +02:00
if ( $callback === false || $this -> in_request_pty_exec ) {
2010-11-30 05:34:31 +01:00
return true ;
}
2007-07-25 23:49:33 +02:00
$output = '' ;
while ( true ) {
2017-01-08 02:51:56 +01:00
$temp = $this -> get_channel_packet ( self :: CHANNEL_EXEC );
2009-05-23 16:42:17 +02:00
switch ( true ) {
case $temp === true :
2013-05-29 00:02:27 +02:00
return is_callable ( $callback ) ? true : $output ;
2009-05-23 16:42:17 +02:00
case $temp === false :
return false ;
2007-07-25 23:49:33 +02:00
default :
2013-05-29 00:02:27 +02:00
if ( is_callable ( $callback )) {
2014-05-27 00:37:32 +02:00
if ( call_user_func ( $callback , $temp ) === true ) {
2017-01-08 02:51:56 +01:00
$this -> close_channel ( self :: CHANNEL_EXEC );
2014-05-27 00:37:32 +02:00
return true ;
}
2013-05-29 00:02:27 +02:00
} else {
$output .= $temp ;
}
2007-07-25 23:49:33 +02:00
}
}
}
2011-02-28 06:24:09 +01:00
/**
* Creates an interactive shell
*
2016-04-10 18:30:59 +02:00
* @ see self :: read ()
* @ see self :: write ()
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \UnexpectedValueException on receipt of unexpected packets
* @ throws \RuntimeException on other errors
2011-02-28 06:24:09 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function initShell ()
2011-02-28 06:24:09 +01:00
{
2013-02-08 23:04:52 +01:00
if ( $this -> in_request_pty_exec === true ) {
return true ;
}
2014-12-04 22:45:13 +01:00
$this -> window_size_server_to_client [ self :: CHANNEL_SHELL ] = $this -> window_size ;
2011-02-28 06:24:09 +01:00
$packet_size = 0x4000 ;
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 13:36:18 +02:00
self :: CHANNEL_SHELL ,
$this -> window_size_server_to_client [ self :: CHANNEL_SHELL ],
2015-07-15 03:52:31 +02:00
$packet_size
);
2011-02-28 06:24:09 +01:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2011-02-28 06:24:09 +01:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2011-02-28 06:24:09 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_SHELL );
2011-02-28 06:24:09 +01:00
if ( $response === false ) {
return false ;
}
$terminal_modes = pack ( 'C' , NET_SSH2_TTY_OP_END );
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsCsN4s' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 13:36:18 +02:00
$this -> server_channels [ self :: CHANNEL_SHELL ],
2015-07-15 03:52:31 +02:00
'pty-req' ,
1 ,
'vt100' ,
$this -> windowColumns ,
$this -> windowRows ,
0 ,
0 ,
$terminal_modes
);
2011-02-28 06:24:09 +01:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2011-02-28 06:24:09 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_binary_packet ();
2011-02-28 06:24:09 +01:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2011-02-28 06:24:09 +01:00
}
2019-04-02 07:09:19 +02:00
list ( $type ) = Strings :: unpackSSH2 ( 'C' , $response );
2011-02-28 06:24:09 +01:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_SUCCESS :
2013-08-21 06:52:15 +02:00
// if a pty can't be opened maybe commands can still be executed
2011-02-28 06:24:09 +01:00
case NET_SSH2_MSG_CHANNEL_FAILURE :
2013-08-21 06:52:15 +02:00
break ;
2011-02-28 06:24:09 +01:00
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 23:23:35 +02:00
throw new \UnexpectedValueException ( 'Unable to request pseudo-terminal' );
2011-02-28 06:24:09 +01:00
}
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsb' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 13:36:18 +02:00
$this -> server_channels [ self :: CHANNEL_SHELL ],
2015-07-15 03:52:31 +02:00
'shell' ,
2019-04-02 07:09:19 +02:00
true // want reply
2015-07-15 03:52:31 +02:00
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2011-02-28 06:24:09 +01:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2011-02-28 06:24:09 +01:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_SHELL );
2011-02-28 06:24:09 +01:00
if ( $response === false ) {
return false ;
}
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SHELL ] = NET_SSH2_MSG_CHANNEL_DATA ;
2011-02-28 06:24:09 +01:00
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_SHELL ;
2011-02-28 06:24:09 +01:00
return true ;
}
2013-10-25 19:35:30 +02:00
/**
* Return the channel to be used with read () / write ()
*
2016-04-10 18:30:59 +02:00
* @ see self :: read ()
* @ see self :: write ()
* @ return int
2013-10-25 19:35:30 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
private function get_interactive_channel ()
2013-10-25 19:35:30 +02:00
{
switch ( true ) {
case $this -> in_subsystem :
2014-12-04 22:45:13 +01:00
return self :: CHANNEL_SUBSYSTEM ;
2013-10-25 19:35:30 +02:00
case $this -> in_request_pty_exec :
2014-12-04 22:45:13 +01:00
return self :: CHANNEL_EXEC ;
2013-10-25 19:35:30 +02:00
default :
2014-12-04 22:45:13 +01:00
return self :: CHANNEL_SHELL ;
2013-10-25 19:35:30 +02:00
}
}
2015-02-06 04:28:23 +01:00
/**
* Return an available open channel
*
2016-04-10 18:30:59 +02:00
* @ return int
2015-02-06 04:28:23 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
private function get_open_channel ()
2015-02-06 04:28:23 +01:00
{
2015-03-29 04:54:51 +02:00
$channel = self :: CHANNEL_EXEC ;
2015-02-06 04:28:23 +01:00
do {
2015-03-24 06:38:56 +01:00
if ( isset ( $this -> channel_status [ $channel ]) && $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_OPEN ) {
2015-02-06 04:28:23 +01:00
return $channel ;
}
2015-03-29 04:54:51 +02:00
} while ( $channel ++ < self :: CHANNEL_SUBSYSTEM );
2015-02-06 04:28:23 +01:00
2015-03-24 06:38:56 +01:00
return false ;
2015-02-06 04:28:23 +01:00
}
2019-03-31 22:15:32 +02:00
/**
* Request agent forwarding of remote server
*
* @ return bool
* @ access public
*/
public function requestAgentForwarding ()
{
$request_channel = $this -> get_open_channel ();
if ( $request_channel === false ) {
return false ;
}
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsC' ,
2019-03-31 22:15:32 +02: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 );
$response = $this -> get_channel_packet ( $request_channel );
if ( $response === false ) {
return false ;
}
$this -> channel_status [ $request_channel ] = NET_SSH2_MSG_CHANNEL_OPEN ;
return true ;
}
2011-02-28 06:24:09 +01: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 22:45:13 +01:00
* if $mode == self :: READ_REGEX , a regular expression .
2011-02-28 06:24:09 +01:00
*
2016-04-10 18:30:59 +02:00
* @ see self :: write ()
* @ param string $expect
* @ param int $mode
2018-07-22 13:17:15 +02:00
* @ return string | bool | null
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2011-02-28 06:24:09 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function read ( $expect = '' , $mode = self :: READ_SIMPLE )
2011-02-28 06:24:09 +01:00
{
2012-03-03 18:49:16 +01:00
$this -> curTimeout = $this -> timeout ;
2013-06-08 00:21:11 +02:00
$this -> is_timeout = false ;
2012-03-03 18:49:16 +01:00
2016-12-16 17:27:56 +01:00
if ( ! $this -> isAuthenticated ()) {
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Operation disallowed prior to login()' );
2011-02-28 06:24:09 +01:00
}
2017-01-08 02:51:56 +01:00
if ( ! ( $this -> bitmap & self :: MASK_SHELL ) && ! $this -> initShell ()) {
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Unable to initiate an interactive shell session' );
2011-02-28 06:24:09 +01:00
}
2017-01-08 02:51:56 +01:00
$channel = $this -> get_interactive_channel ();
2013-02-08 23:04:52 +01:00
2017-06-20 05:34:20 +02:00
if ( $mode == self :: READ_NEXT ) {
2018-09-10 17:17:20 +02:00
return $this -> get_channel_packet ( $channel );
2017-06-14 01:56:50 +02:00
}
2011-05-08 04:34:36 +02:00
$match = $expect ;
2011-02-28 06:24:09 +01:00
while ( true ) {
2014-12-04 22:45:13 +01:00
if ( $mode == self :: READ_REGEX ) {
2015-07-15 02:05:52 +02:00
preg_match ( $expect , substr ( $this -> interactiveBuffer , - 1024 ), $matches );
2013-01-13 17:49:03 +01:00
$match = isset ( $matches [ 0 ]) ? $matches [ 0 ] : '' ;
2011-02-28 06:24:09 +01:00
}
2013-01-12 17:46:19 +01:00
$pos = strlen ( $match ) ? strpos ( $this -> interactiveBuffer , $match ) : false ;
2011-02-28 06:24:09 +01:00
if ( $pos !== false ) {
2016-07-31 05:18:06 +02:00
return Strings :: shift ( $this -> interactiveBuffer , $pos + strlen ( $match ));
2011-02-28 06:24:09 +01:00
}
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( $channel );
2012-03-03 18:49:16 +01:00
if ( is_bool ( $response )) {
2013-02-08 23:04:52 +01:00
$this -> in_request_pty_exec = false ;
2016-07-31 05:18:06 +02:00
return $response ? Strings :: shift ( $this -> interactiveBuffer , strlen ( $this -> interactiveBuffer )) : false ;
2012-03-03 18:49:16 +01:00
}
2011-02-28 06:24:09 +01:00
$this -> interactiveBuffer .= $response ;
}
}
/**
* Inputs a command into an interactive shell .
*
2016-04-10 18:30:59 +02:00
* @ see self :: read ()
* @ param string $cmd
* @ return bool
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2011-02-28 06:24:09 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function write ( $cmd )
2011-02-28 06:24:09 +01:00
{
2016-12-16 17:27:56 +01:00
if ( ! $this -> isAuthenticated ()) {
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Operation disallowed prior to login()' );
2011-02-28 06:24:09 +01:00
}
2017-01-08 02:51:56 +01:00
if ( ! ( $this -> bitmap & self :: MASK_SHELL ) && ! $this -> initShell ()) {
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Unable to initiate an interactive shell session' );
2011-02-28 06:24:09 +01:00
}
2017-01-08 02:51:56 +01:00
return $this -> send_channel_packet ( $this -> get_interactive_channel (), $cmd );
2013-10-25 19:35:30 +02: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 18:30:59 +02:00
* @ see self :: stopSubsystem ()
* @ param string $subsystem
* @ return bool
2013-10-25 19:35:30 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function startSubsystem ( $subsystem )
2013-10-25 19:35:30 +02:00
{
2014-12-04 22:45:13 +01:00
$this -> window_size_server_to_client [ self :: CHANNEL_SUBSYSTEM ] = $this -> window_size ;
2013-10-25 19:35:30 +02:00
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2015-07-17 13:36:18 +02:00
self :: CHANNEL_SUBSYSTEM ,
2015-07-15 03:52:31 +02:00
$this -> window_size ,
0x4000
);
2013-10-25 19:35:30 +02:00
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2013-10-25 19:35:30 +02:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2013-10-25 19:35:30 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_SUBSYSTEM );
2013-10-25 19:35:30 +02:00
if ( $response === false ) {
return false ;
}
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNsCs' ,
2015-07-15 03:52:31 +02:00
NET_SSH2_MSG_CHANNEL_REQUEST ,
2015-07-17 13:36:18 +02:00
$this -> server_channels [ self :: CHANNEL_SUBSYSTEM ],
2015-07-15 03:52:31 +02:00
'subsystem' ,
1 ,
$subsystem
);
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2013-10-25 19:35:30 +02:00
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_REQUEST ;
2013-10-25 19:35:30 +02:00
2017-01-08 02:51:56 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_SUBSYSTEM );
2013-10-25 19:35:30 +02:00
if ( $response === false ) {
2015-07-15 03:52:31 +02:00
return false ;
2013-10-25 19:35:30 +02:00
}
2014-12-04 22:45:13 +01:00
$this -> channel_status [ self :: CHANNEL_SUBSYSTEM ] = NET_SSH2_MSG_CHANNEL_DATA ;
2013-10-25 19:35:30 +02:00
2014-12-04 22:45:13 +01:00
$this -> bitmap |= self :: MASK_SHELL ;
2013-10-25 19:35:30 +02:00
$this -> in_subsystem = true ;
return true ;
}
/**
* Stops a subsystem .
*
2016-04-10 18:30:59 +02:00
* @ see self :: startSubsystem ()
* @ return bool
2013-10-25 19:35:30 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function stopSubsystem ()
2013-10-25 19:35:30 +02:00
{
$this -> in_subsystem = false ;
2017-01-08 02:51:56 +01:00
$this -> close_channel ( self :: CHANNEL_SUBSYSTEM );
2013-10-25 19:35:30 +02:00
return true ;
2011-02-28 06:24:09 +01:00
}
2013-06-08 00:21:11 +02: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
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function reset ()
2013-06-08 00:21:11 +02:00
{
2017-01-08 02:51:56 +01:00
$this -> close_channel ( $this -> get_interactive_channel ());
2013-06-08 00:21:11 +02:00
}
/**
* Is timeout ?
*
* Did exec () or read () return because they timed out or because they encountered the end ?
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function isTimeout ()
2013-06-08 00:21:11 +02:00
{
return $this -> is_timeout ;
}
2008-05-15 19:40:03 +02:00
/**
* Disconnect
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function disconnect ()
2008-05-15 19:40:03 +02:00
{
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2012-04-30 18:25:37 +02:00
if ( isset ( $this -> realtime_log_file ) && is_resource ( $this -> realtime_log_file )) {
fclose ( $this -> realtime_log_file );
}
2016-04-30 23:23:35 +02:00
unset ( self :: $connections [ $this -> getResourceId ()]);
2008-05-15 19:40:03 +02:00
}
2008-05-15 18:33:08 +02:00
/**
* Destructor .
*
2008-05-15 19:40:03 +02: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 18:33:08 +02:00
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function __destruct ()
2008-05-15 18:33:08 +02:00
{
2008-05-15 19:40:03 +02:00
$this -> disconnect ();
2008-05-15 18:33:08 +02:00
}
2013-02-16 21:58:12 +01:00
/**
* Is the connection still active ?
*
2016-04-10 18:30:59 +02:00
* @ return bool
2013-02-16 21:58:12 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function isConnected ()
2013-02-16 21:58:12 +01:00
{
2014-12-04 22:45:13 +01:00
return ( bool ) ( $this -> bitmap & self :: MASK_CONNECTED );
2013-02-16 21:58:12 +01:00
}
2016-04-10 18:30:59 +02:00
/**
* Have you successfully been logged in ?
*
* @ return bool
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function isAuthenticated ()
2016-04-10 18:30:59 +02:00
{
return ( bool ) ( $this -> bitmap & self :: MASK_LOGIN );
}
2017-08-27 02:50:17 +02:00
/**
2018-10-06 08:19:14 +02: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 15:02:59 +02:00
public function ping ()
2018-10-06 08:19:14 +02:00
{
if ( ! $this -> isAuthenticated ()) {
return false ;
}
2019-01-20 16:15:53 +01:00
$this -> window_size_server_to_client [ self :: CHANNEL_KEEP_ALIVE ] = $this -> window_size ;
2018-10-06 08:19:14 +02:00
$packet_size = 0x4000 ;
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CsN3' ,
2018-10-06 08:19:14 +02:00
NET_SSH2_MSG_CHANNEL_OPEN ,
'session' ,
2019-01-20 16:15:53 +01:00
self :: CHANNEL_KEEP_ALIVE ,
$this -> window_size_server_to_client [ self :: CHANNEL_KEEP_ALIVE ],
2018-10-06 08:19:14 +02:00
$packet_size
);
2018-10-06 18:41:15 +02:00
try {
$this -> send_binary_packet ( $packet );
2018-10-06 08:19:14 +02:00
2019-01-20 16:17:57 +01:00
$this -> channel_status [ self :: CHANNEL_KEEP_ALIVE ] = NET_SSH2_MSG_CHANNEL_OPEN ;
2018-10-06 08:19:14 +02:00
2019-01-20 16:17:57 +01:00
$response = $this -> get_channel_packet ( self :: CHANNEL_KEEP_ALIVE );
2018-10-06 18:41:15 +02:00
} catch ( \RuntimeException $e ) {
return $this -> reconnect ();
2018-10-06 08:19:14 +02:00
}
2018-10-06 18:41:15 +02:00
$this -> close_channel ( NET_SSH2_CHANNEL_KEEP_ALIVE );
return true ;
2018-10-06 08:19:14 +02:00
}
/**
* In situ reconnect method
*
* @ return boolean
*/
2018-10-06 15:02:59 +02:00
private function reconnect ()
2018-10-06 08:19:14 +02:00
{
2018-10-06 15:02:59 +02:00
$this -> reset_connection ( NET_SSH2_DISCONNECT_CONNECTION_LOST );
2018-10-06 08:19:14 +02:00
$this -> retry_connect = true ;
2018-10-06 15:02:59 +02:00
if ( ! $this -> connect ()) {
2018-10-06 08:19:14 +02:00
return false ;
}
foreach ( $this -> auth as $auth ) {
2019-03-24 01:20:06 +01:00
$result = $this -> login ( ... $auth );
2018-10-06 08:19:14 +02:00
}
return $result ;
}
2017-08-27 02:50:17 +02:00
/**
* Resets a connection for re - use
*
* @ param int $reason
* @ access private
*/
2017-08-27 19:33:19 +02:00
private function reset_connection ( $reason )
2017-08-27 02:50:17 +02:00
{
2017-08-27 19:33:19 +02:00
$this -> disconnect_helper ( $reason );
2017-08-27 02:50:17 +02: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 07:21:39 +02:00
/**
* Gets Binary Packets
*
* See '6. Binary Packet Protocol' of rfc4253 for more info .
*
2016-04-10 18:30:59 +02:00
* @ see self :: _send_binary_packet ()
2017-10-25 11:44:14 +02:00
* @ param bool $filter_channel_packets
2016-04-10 18:30:59 +02:00
* @ return string
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-11-11 23:13:48 +01:00
private function get_binary_packet ( $skip_channel_filter = false )
2007-07-23 07:21:39 +02:00
{
2012-04-24 07:01:55 +02:00
if ( ! is_resource ( $this -> fsock ) || feof ( $this -> fsock )) {
2013-11-15 19:34:31 +01:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed prematurely' );
2007-07-23 07:21:39 +02:00
}
2013-12-05 18:46:46 +01:00
$start = microtime ( true );
2016-05-22 21:37:12 +02:00
$raw = stream_get_contents ( $this -> fsock , $this -> decrypt_block_size );
2007-07-23 07:21:39 +02:00
2013-01-12 17:46:19 +01:00
if ( ! strlen ( $raw )) {
2011-04-22 09:50:24 +02:00
return '' ;
}
2018-12-31 21:14:43 +01:00
if ( $this -> decrypt ) {
2019-03-18 12:59:00 +01:00
switch ( $this -> decrypt -> name ) {
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$this -> decrypt -> setNonce (
$this -> decrypt -> fixed .
$this -> decrypt -> invocation_counter
);
Strings :: increment_str ( $this -> decrypt -> invocation_counter );
$this -> decrypt -> setAAD ( $temp = Strings :: shift ( $raw , 4 ));
extract ( unpack ( 'Npacket_length' , $temp ));
/**
* @ var integer $packet_length
*/
$raw .= $this -> read_remaining_bytes ( $packet_length - $this -> decrypt_block_size + 4 );
$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' :
$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
*/
$raw .= $this -> read_remaining_bytes ( $packet_length - $this -> decrypt_block_size + 4 );
$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 :
$raw = $this -> decrypt -> decrypt ( $raw );
2018-12-27 15:31:35 +01:00
}
2007-07-23 07:21:39 +02:00
}
2016-10-23 05:13:17 +02:00
if ( strlen ( $raw ) < 5 ) {
return false ;
}
2016-07-31 05:18:06 +02:00
extract ( unpack ( 'Npacket_length/Cpadding_length' , Strings :: shift ( $raw , 5 )));
2017-12-04 11:11:43 +01:00
/**
* @ var integer $packet_length
* @ var integer $padding_length
*/
2007-07-23 07:21:39 +02:00
2018-12-27 15:31:35 +01:00
if ( ! isset ( $remaining_length )) {
$remaining_length = $packet_length + 4 - $this -> decrypt_block_size ;
}
2013-01-26 07:17:23 +01:00
2018-12-27 15:31:35 +01:00
$buffer = $this -> read_remaining_bytes ( $remaining_length );
2017-08-29 05:37:35 +02:00
2018-12-27 15:31:35 +01:00
if ( ! isset ( $stop )) {
$stop = microtime ( true );
}
2013-01-12 17:46:19 +01:00
if ( strlen ( $buffer )) {
2018-12-31 21:14:43 +01:00
$raw .= $this -> decrypt ? $this -> decrypt -> decrypt ( $buffer ) : $buffer ;
2009-02-16 23:22:13 +01:00
}
2007-07-23 07:21:39 +02:00
2016-07-31 05:18:06 +02:00
$payload = Strings :: shift ( $raw , $packet_length - $padding_length - 1 );
$padding = Strings :: shift ( $raw , $padding_length ); // should leave $raw empty
2007-07-23 07:21:39 +02:00
2019-03-29 03:45:28 +01:00
if ( $this -> hmac_check instanceof Hash ) {
2016-05-22 21:37:12 +02:00
$hmac = stream_get_contents ( $this -> fsock , $this -> hmac_size );
2014-04-18 23:58:00 +02:00
if ( $hmac === false || strlen ( $hmac ) != $this -> hmac_size ) {
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Error reading socket' );
2014-04-18 23:58:00 +02:00
} elseif ( $hmac != $this -> hmac_check -> hash ( pack ( 'NNCa*' , $this -> get_seq_no , $packet_length , $padding_length , $payload . $padding ))) {
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Invalid HMAC' );
2007-07-23 07:21:39 +02:00
}
}
2009-05-16 19:09:37 +02:00
//if ($this->decompress) {
// $payload = gzinflate(substr($payload, 2));
//}
2008-05-26 21:42:01 +02:00
$this -> get_seq_no ++ ;
2007-07-23 07:21:39 +02:00
2009-03-25 23:29:42 +01:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2013-12-05 18:46:46 +01:00
$current = microtime ( true );
2012-04-30 18:25:37 +02: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 20:08:30 +01:00
' (since last: ' . round ( $current - $this -> last_packet , 4 ) . ', network: ' . round ( $stop - $start , 4 ) . 's)' ;
2017-01-08 02:51:56 +01:00
$this -> append_log ( $message_number , $payload );
2012-11-22 20:08:30 +01:00
$this -> last_packet = $current ;
2009-03-25 23:29:42 +01:00
}
2017-11-11 23:13:48 +01:00
return $this -> filter ( $payload , $skip_channel_filter );
2007-07-26 16:53:45 +02:00
}
2018-12-27 15:31:35 +01:00
/**
* Read Remaining Bytes
*
* @ see self :: get_binary_packet ()
* @ param int $remaining_length
* @ return string
* @ access private
*/
private function read_remaining_bytes ( $remaining_length )
{
2019-03-18 12:59:00 +01:00
if ( ! $remaining_length ) {
return '' ;
}
$adjustLength = false ;
if ( $this -> decrypt ) {
switch ( $this -> decrypt -> name ) {
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
case 'chacha20-poly1305@openssh.com' :
$remaining_length += $this -> decrypt_block_size - 4 ;
$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 ) {
if ( ! $this -> bad_key_size_fix && self :: bad_algorithm_candidate ( $this -> decrypt ? $this -> decrypt -> name : '' ) && ! ( $this -> bitmap & SSH2 :: MASK_LOGIN )) {
$this -> bad_key_size_fix = true ;
$this -> reset_connection ( NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED );
return false ;
}
throw new \RuntimeException ( 'Invalid size' );
}
if ( $adjustLength ) {
$remaining_length -= $this -> decrypt_block_size - 4 ;
}
2018-12-27 15:31:35 +01:00
$buffer = '' ;
while ( $remaining_length > 0 ) {
$temp = stream_get_contents ( $this -> fsock , $remaining_length );
if ( $temp === false || feof ( $this -> fsock )) {
$this -> bitmap = 0 ;
throw new \RuntimeException ( 'Error reading from socket' );
}
$buffer .= $temp ;
$remaining_length -= strlen ( $temp );
}
return $buffer ;
}
2007-07-26 16:53:45 +02:00
/**
* Filter Binary Packets
*
* Because some binary packets need to be ignored ...
*
2016-04-10 18:30:59 +02:00
* @ see self :: _get_binary_packet ()
2017-11-05 21:35:27 +01:00
* @ param string $payload
* @ param bool $filter_channel_packets
2016-04-10 18:30:59 +02:00
* @ return string
2007-07-26 16:53:45 +02:00
* @ access private
*/
2017-11-11 23:13:48 +01:00
private function filter ( $payload , $skip_channel_filter )
2007-07-26 16:53:45 +02:00
{
2007-07-23 07:21:39 +02:00
switch ( ord ( $payload [ 0 ])) {
case NET_SSH2_MSG_DISCONNECT :
2016-07-31 05:18:06 +02:00
Strings :: shift ( $payload , 1 );
2019-04-02 07:09:19 +02: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 19:34:31 +01:00
$this -> bitmap = 0 ;
2007-07-23 07:21:39 +02:00
return false ;
case NET_SSH2_MSG_IGNORE :
2017-11-12 06:16:23 +01:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 07:21:39 +02:00
break ;
case NET_SSH2_MSG_DEBUG :
2019-04-02 07:09:19 +02: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 06:16:23 +01:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 07:21:39 +02:00
break ;
case NET_SSH2_MSG_UNIMPLEMENTED :
return false ;
case NET_SSH2_MSG_KEXINIT :
if ( $this -> session_id !== false ) {
2017-01-08 02:51:56 +01:00
if ( ! $this -> key_exchange ( $payload )) {
2013-11-15 19:34:31 +01:00
$this -> bitmap = 0 ;
2007-07-23 07:21:39 +02:00
return false ;
}
2017-11-12 06:16:23 +01:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-23 07:21:39 +02:00
}
}
2007-07-25 23:49:33 +02: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
2016-12-18 00:03:08 +01:00
if (( $this -> bitmap & self :: MASK_CONNECTED ) && ! $this -> isAuthenticated () && ord ( $payload [ 0 ]) == NET_SSH2_MSG_USERAUTH_BANNER ) {
2016-07-31 05:18:06 +02:00
Strings :: shift ( $payload , 1 );
2019-04-02 07:09:19 +02:00
list ( $this -> banner_message ) = Strings :: unpackSSH2 ( 's' , $response );
2017-01-08 02:51:56 +01:00
$payload = $this -> get_binary_packet ();
2007-07-25 23:49:33 +02:00
}
// only called when we've already logged in
2016-12-18 00:03:08 +01:00
if (( $this -> bitmap & self :: MASK_CONNECTED ) && $this -> isAuthenticated ()) {
2007-07-25 23:49:33 +02:00
switch ( ord ( $payload [ 0 ])) {
2017-08-29 05:37:35 +02:00
case NET_SSH2_MSG_CHANNEL_DATA :
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA :
case NET_SSH2_MSG_CHANNEL_REQUEST :
case NET_SSH2_MSG_CHANNEL_CLOSE :
case NET_SSH2_MSG_CHANNEL_EOF :
2017-11-09 07:12:06 +01:00
if ( ! $skip_channel_filter && ! empty ( $this -> server_channels )) {
2017-08-29 05:37:35 +02:00
$this -> binary_packet_buffer = $payload ;
2017-09-06 07:44:38 +02:00
$this -> get_channel_packet ( true );
2017-11-11 23:13:48 +01:00
$payload = $this -> get_binary_packet ();
2017-08-29 05:37:35 +02:00
}
break ;
2007-07-25 23:49:33 +02:00
case NET_SSH2_MSG_GLOBAL_REQUEST : // see http://tools.ietf.org/html/rfc4254#section-4
2019-04-02 07:09:19 +02:00
Strings :: shift ( $payload , 1 );
list ( $request_name ) = Strings :: unpackSSH2 ( 's' , $payload );
$this -> errors [] = " SSH_MSG_GLOBAL_REQUEST: $request_name " ;
2007-07-25 23:49:33 +02:00
2019-03-30 20:12:52 +01:00
try {
$this -> send_binary_packet ( pack ( 'C' , NET_SSH2_MSG_REQUEST_FAILURE ));
} catch ( \RuntimeException $e ) {
2017-01-08 02:51:56 +01:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2007-07-25 23:49:33 +02:00
}
2017-11-11 23:13:48 +01:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 23:49:33 +02:00
break ;
case NET_SSH2_MSG_CHANNEL_OPEN : // see http://tools.ietf.org/html/rfc4254#section-5.1
2016-07-31 05:18:06 +02:00
Strings :: shift ( $payload , 1 );
2019-04-02 07:09:19 +02:00
list ( $data , $server_channel ) = Strings :: unpackSSH2 ( 'sN' , $payload );
2015-07-15 03:52:31 +02:00
switch ( $data ) {
2015-02-06 04:28:23 +01:00
case 'auth-agent' :
case 'auth-agent@openssh.com' :
if ( isset ( $this -> agent )) {
2015-07-17 13:36:18 +02:00
$new_channel = self :: CHANNEL_AGENT_FORWARD ;
2015-07-15 03:52:31 +02:00
2019-04-02 07:09:19 +02:00
list (
$remote_window_size ,
$remote_maximum_packet_size
) = Strings :: unpackSSH2 ( 'NN' , $payload );
2015-07-15 03:52:31 +02: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 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2015-02-06 04:28:23 +01:00
}
2015-03-24 06:38:56 +01:00
break ;
2015-02-06 04:28:23 +01:00
default :
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CN2ss' ,
NET_SSH2_MSG_CHANNEL_OPEN_FAILURE ,
2015-07-15 03:52:31 +02:00
$server_channel ,
NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED ,
2019-04-02 07:09:19 +02:00
'' , // description
'' // language tag
2015-07-15 03:52:31 +02:00
);
2007-07-25 23:49:33 +02:00
2019-03-30 20:12:52 +01:00
try {
$this -> send_binary_packet ( $packet );
} catch ( \RuntimeException $e ) {
2017-01-08 02:51:56 +01:00
return $this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2015-02-06 04:28:23 +01:00
}
}
2017-11-11 23:13:48 +01:00
$payload = $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 23:49:33 +02:00
break ;
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST :
2016-07-31 05:18:06 +02:00
Strings :: shift ( $payload , 1 );
2019-04-02 07:09:19 +02:00
list ( $channel , $window_size ) = Strings :: unpackSSH2 ( 'NN' , $payload );
2016-11-30 02:35:50 +01:00
2013-09-12 15:29:14 +02:00
$this -> window_size_client_to_server [ $channel ] += $window_size ;
2013-07-07 22:57:15 +02:00
2017-11-11 23:13:48 +01:00
$payload = ( $this -> bitmap & self :: MASK_WINDOW_ADJUST ) ? true : $this -> get_binary_packet ( $skip_channel_filter );
2007-07-25 23:49:33 +02:00
}
}
2007-07-23 07:21:39 +02:00
return $payload ;
}
2012-07-23 14:17:53 +02:00
/**
* Enable Quiet Mode
*
* Suppress stderr from output
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function enableQuietMode ()
2012-07-23 14:17:53 +02:00
{
$this -> quiet_mode = true ;
}
/**
* Disable Quiet Mode
*
* Show stderr in output
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function disableQuietMode ()
2012-07-23 14:17:53 +02:00
{
$this -> quiet_mode = false ;
}
2013-12-28 21:27:02 +01:00
/**
* Returns whether Quiet Mode is enabled or not
*
2016-04-10 18:30:59 +02:00
* @ see self :: enableQuietMode ()
* @ see self :: disableQuietMode ()
2013-12-28 21:27:02 +01:00
* @ access public
2016-04-10 18:30:59 +02:00
* @ return bool
2013-12-28 21:27:02 +01:00
*/
2017-01-08 02:51:56 +01:00
public function isQuietModeEnabled ()
2013-12-28 21:27:02 +01:00
{
return $this -> quiet_mode ;
}
2013-02-08 23:04:52 +01:00
/**
* Enable request - pty when using exec ()
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function enablePTY ()
2013-02-08 23:04:52 +01:00
{
$this -> request_pty = true ;
}
/**
* Disable request - pty when using exec ()
*
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function disablePTY ()
2013-02-08 23:04:52 +01:00
{
2016-12-17 23:09:48 +01:00
if ( $this -> in_request_pty_exec ) {
2017-05-28 16:44:38 +02:00
$this -> close_channel ( self :: CHANNEL_EXEC );
2016-12-17 23:09:48 +01:00
$this -> in_request_pty_exec = false ;
}
2013-02-08 23:04:52 +01:00
$this -> request_pty = false ;
}
2013-12-28 21:27:02 +01:00
/**
* Returns whether request - pty is enabled or not
*
2016-04-10 18:30:59 +02:00
* @ see self :: enablePTY ()
* @ see self :: disablePTY ()
2013-12-28 21:27:02 +01:00
* @ access public
2016-04-10 18:30:59 +02:00
* @ return bool
2013-12-28 21:27:02 +01:00
*/
2017-01-08 02:51:56 +01:00
public function isPTYEnabled ()
2013-12-28 21:27:02 +01:00
{
return $this -> request_pty ;
}
2009-05-23 16:42:17 +02:00
/**
* Gets channel data
*
* Returns the data as a string if it ' s available and false if not .
*
2017-11-05 21:35:27 +01:00
* @ param int $client_channel
* @ param bool $skip_extended
2016-04-10 18:30:59 +02:00
* @ return mixed
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on connection error
2009-05-23 16:42:17 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected function get_channel_packet ( $client_channel , $skip_extended = false )
2009-05-23 16:42:17 +02:00
{
2013-01-13 18:36:57 +01:00
if ( ! empty ( $this -> channel_buffers [ $client_channel ])) {
2009-12-14 19:14:54 +01:00
return array_shift ( $this -> channel_buffers [ $client_channel ]);
}
2009-05-23 16:42:17 +02:00
while ( true ) {
2017-08-29 05:37:35 +02:00
if ( $this -> binary_packet_buffer !== false ) {
$response = $this -> binary_packet_buffer ;
$this -> binary_packet_buffer = false ;
} else {
2019-03-24 01:20:06 +01:00
$read = [ $this -> fsock ];
2018-12-16 18:44:58 +01:00
$write = $except = null ;
if ( ! $this -> curTimeout ) {
@ stream_select ( $read , $write , $except , null );
} else {
2017-08-29 05:37:35 +02:00
if ( $this -> curTimeout < 0 ) {
$this -> is_timeout = true ;
return true ;
}
2013-04-21 03:43:05 +02:00
2017-09-06 06:42:05 +02:00
$read = [ $this -> fsock ];
2017-08-29 05:37:35 +02:00
$write = $except = null ;
2013-04-21 03:43:05 +02:00
2017-09-06 06:22:09 +02:00
$start = microtime ( true );
2017-08-29 05:37:35 +02:00
$sec = floor ( $this -> curTimeout );
$usec = 1000000 * ( $this -> curTimeout - $sec );
// on windows this returns a "Warning: Invalid CRT parameters detected" error
if ( !@ stream_select ( $read , $write , $except , $sec , $usec ) && ! count ( $read )) {
$this -> is_timeout = true ;
return true ;
}
2017-09-06 06:22:09 +02:00
$elapsed = microtime ( true ) - $start ;
2017-08-29 05:37:35 +02:00
$this -> curTimeout -= $elapsed ;
2012-03-03 18:49:16 +01:00
}
2017-11-11 23:13:48 +01:00
$response = $this -> get_binary_packet ( true );
2017-08-29 05:37:35 +02:00
if ( $response === false ) {
2018-10-06 04:27:59 +02:00
$this -> bitmap = 0 ;
2017-09-06 06:42:05 +02:00
throw new \RuntimeException ( 'Connection closed by server' );
2012-03-03 18:49:16 +01:00
}
}
2013-07-14 11:09:16 +02:00
if ( $client_channel == - 1 && $response === true ) {
return true ;
}
2019-04-02 07:09:19 +02:00
list ( $type , $channel ) = Strings :: unpackSSH2 ( 'CN' , $response );
2013-05-14 05:37:32 +02:00
2014-12-30 03:44:31 +01: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 ])) {
$this -> window_size_server_to_client [ $channel ] -= strlen ( $response );
// resize the window, if appropriate
if ( $this -> window_size_server_to_client [ $channel ] < 0 ) {
$packet = pack ( 'CNN' , NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST , $this -> server_channels [ $channel ], $this -> window_size );
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2014-12-30 03:44:31 +01:00
$this -> window_size_server_to_client [ $channel ] += $this -> window_size ;
}
2017-12-25 17:26:26 +01:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA :
/*
if ( $client_channel == NET_SSH2_CHANNEL_EXEC ) {
2018-09-10 17:17:20 +02:00
$this -> send_channel_packet ( $client_channel , chr ( 0 ));
2017-12-25 17:26:26 +01:00
}
*/
// currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
2019-04-02 07:09:19 +02:00
list ( $data_type_code , $data ) = Strings :: unpackSSH2 ( 'Ns' , $response );
2017-12-25 17:26:26 +01:00
$this -> stdErrorLog .= $data ;
if ( $skip_extended || $this -> quiet_mode ) {
continue 2 ;
}
if ( $client_channel == $channel && $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_DATA ) {
return $data ;
}
if ( ! isset ( $this -> channel_buffers [ $channel ])) {
2019-03-24 01:20:06 +01:00
$this -> channel_buffers [ $channel ] = [];
2017-12-25 17:26:26 +01:00
}
$this -> channel_buffers [ $channel ][] = $data ;
2017-11-09 07:12:06 +01:00
2017-12-25 17:26:26 +01:00
continue 2 ;
case NET_SSH2_MSG_CHANNEL_REQUEST :
if ( $this -> channel_status [ $channel ] == NET_SSH2_MSG_CHANNEL_CLOSE ) {
continue 2 ;
}
2019-04-02 07:09:19 +02:00
list ( $value ) = Strings :: unpackSSH2 ( 's' , $response );
2017-12-25 17:26:26 +01:00
switch ( $value ) {
case 'exit-signal' :
2019-04-02 07:09:19 +02: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 )) {
$this -> errors [ count ( $this -> errors ) - 1 ] .= " \r \n $error_message " ;
2017-12-25 17:26:26 +01:00
}
2017-12-26 02:12:57 +01: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 17:26:26 +01:00
$this -> channel_status [ $channel ] = NET_SSH2_MSG_CHANNEL_EOF ;
continue 3 ;
case 'exit-status' :
2019-04-02 07:09:19 +02:00
list (, $this -> exit_status ) = Strings :: unpackSSH2 ( 'CN' , $response );
2017-11-09 07:12:06 +01:00
2017-12-25 17:26:26 +01: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 07:12:06 +01:00
}
2014-12-30 03:44:31 +01:00
switch ( $this -> channel_status [ $channel ]) {
2015-01-12 10:13:33 +01:00
case NET_SSH2_MSG_CHANNEL_OPEN :
2014-12-30 03:44:31 +01:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION :
2019-04-02 07:09:19 +02:00
list (
$this -> server_channels [ $channel ],
$window_size ,
$this -> packet_size_client_to_server [ $channel ]
) = Strings :: unpackSSH2 ( 'NNN' , $response );
2016-11-30 02:35:50 +01:00
2015-06-25 06:45:14 +02:00
if ( $window_size < 0 ) {
$window_size &= 0x7FFFFFFF ;
$window_size += 0x80000000 ;
}
2014-12-30 03:44:31 +01:00
$this -> window_size_client_to_server [ $channel ] = $window_size ;
2017-01-08 02:51:56 +01:00
$result = $client_channel == $channel ? true : $this -> get_channel_packet ( $client_channel , $skip_extended );
$this -> on_channel_open ();
2015-02-06 04:28:23 +01:00
return $result ;
2014-12-30 03:44:31 +01:00
//case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Unable to open channel' );
2014-12-30 03:44:31 +01: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 ;
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Unable to fulfill channel request' );
2014-12-30 03:44:31 +01:00
}
case NET_SSH2_MSG_CHANNEL_CLOSE :
2017-01-08 02:51:56 +01:00
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this -> get_channel_packet ( $client_channel , $skip_extended );
2014-12-30 03:44:31 +01:00
}
2009-12-14 19:14:54 +01:00
}
2009-05-23 16:42:17 +02:00
2013-07-14 11:09:16 +02:00
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
2009-05-23 16:42:17 +02:00
switch ( $type ) {
case NET_SSH2_MSG_CHANNEL_DATA :
2011-02-13 08:46:01 +01:00
/*
2014-12-04 22:45:13 +01:00
if ( $channel == self :: CHANNEL_EXEC ) {
2009-12-14 19:14:54 +01:00
// SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
2013-12-26 00:33:08 +01:00
// this actually seems to make things twice as fast. more to the point, the message right after
2009-12-14 19:14:54 +01: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 02:51:56 +01:00
$this -> send_channel_packet ( $channel , chr ( 0 ));
2009-12-14 19:14:54 +01:00
}
2011-02-13 08:46:01 +01:00
*/
2019-04-02 07:09:19 +02:00
list ( $data ) = Strings :: unpackSSH2 ( 's' , $response );
2014-12-30 03:44:31 +01:00
2015-03-29 17:58:05 +02:00
if ( $channel == self :: CHANNEL_AGENT_FORWARD ) {
2019-03-31 22:15:32 +02:00
$agent_response = $this -> agent -> forwardData ( $data );
2014-12-30 03:44:31 +01:00
if ( ! is_bool ( $agent_response )) {
2017-01-08 02:51:56 +01:00
$this -> send_channel_packet ( $channel , $agent_response );
2014-12-30 03:44:31 +01:00
}
break ;
}
2010-02-15 23:24:08 +01:00
if ( $client_channel == $channel ) {
return $data ;
}
2013-07-14 11:09:16 +02:00
if ( ! isset ( $this -> channel_buffers [ $channel ])) {
2016-11-30 22:19:23 +01:00
$this -> channel_buffers [ $channel ] = [];
2009-05-23 16:42:17 +02:00
}
2013-07-14 11:09:16 +02:00
$this -> channel_buffers [ $channel ][] = $data ;
2009-05-23 16:42:17 +02:00
break ;
case NET_SSH2_MSG_CHANNEL_CLOSE :
2012-06-11 04:52:30 +02:00
$this -> curTimeout = 0 ;
2014-12-04 22:45:13 +01:00
if ( $this -> bitmap & self :: MASK_SHELL ) {
$this -> bitmap &= ~ self :: MASK_SHELL ;
2012-06-11 04:52:30 +02:00
}
if ( $this -> channel_status [ $channel ] != NET_SSH2_MSG_CHANNEL_EOF ) {
2017-01-08 02:51:56 +01:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $channel ]));
2012-06-11 04:52:30 +02:00
}
$this -> channel_status [ $channel ] = NET_SSH2_MSG_CHANNEL_CLOSE ;
2016-06-29 07:30:28 +02:00
if ( $client_channel == $channel ) {
return true ;
}
2009-05-23 16:42:17 +02:00
case NET_SSH2_MSG_CHANNEL_EOF :
break ;
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_BY_APPLICATION );
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Error reading channel data' );
2009-05-23 16:42:17 +02:00
}
}
}
2007-07-23 07:21:39 +02:00
/**
* Sends Binary Packets
*
* See '6. Binary Packet Protocol' of rfc4253 for more info .
*
2016-04-10 18:30:59 +02:00
* @ param string $data
* @ param string $logged
* @ see self :: _get_binary_packet ()
* @ return bool
2007-07-23 07:21:39 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected function send_binary_packet ( $data , $logged = null )
2007-07-23 07:21:39 +02:00
{
2012-04-24 07:01:55 +02:00
if ( ! is_resource ( $this -> fsock ) || feof ( $this -> fsock )) {
2013-11-15 19:34:31 +01:00
$this -> bitmap = 0 ;
2016-04-30 23:23:35 +02:00
throw new \RuntimeException ( 'Connection closed prematurely' );
2007-07-23 07:21:39 +02:00
}
2009-05-16 19:09:37 +02:00
//if ($this->compress) {
// // the -4 removes the checksum:
// // http://php.net/function.gzcompress#57710
// $data = substr(gzcompress($data), 0, -4);
//}
2009-02-16 23:22:13 +01:00
// 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
2007-07-23 07:21:39 +02:00
$packet_length = strlen ( $data ) + 9 ;
2018-12-31 21:14:43 +01:00
if ( $this -> encrypt && $this -> encrypt -> usesNonce ()) {
2018-12-27 15:31:35 +01:00
$packet_length -= 4 ;
}
2009-02-16 23:22:13 +01:00
// round up to the nearest $this->encrypt_block_size
$packet_length += (( $this -> encrypt_block_size - 1 ) * $packet_length ) % $this -> encrypt_block_size ;
2007-07-23 07:21:39 +02:00
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen ( $data ) - 5 ;
2018-12-31 21:14:43 +01:00
if ( $this -> encrypt && $this -> encrypt -> usesNonce ()) {
2018-12-27 15:31:35 +01:00
$padding_length += 4 ;
$packet_length += 4 ;
}
2014-12-02 18:20:40 +01:00
$padding = Random :: string ( $padding_length );
2007-07-23 07:21:39 +02: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-03-29 03:45:28 +01:00
$hmac = $this -> hmac_create instanceof Hash ? $this -> hmac_create -> hash ( pack ( 'Na*' , $this -> send_seq_no , $packet )) : '' ;
2008-05-26 21:42:01 +02:00
$this -> send_seq_no ++ ;
2007-07-23 07:21:39 +02:00
2018-12-31 21:14:43 +01:00
if ( $this -> encrypt ) {
2019-03-18 12:59:00 +01:00
switch ( $this -> encrypt -> name ) {
case 'aes128-gcm@openssh.com' :
case 'aes256-gcm@openssh.com' :
$this -> encrypt -> setNonce (
$this -> encrypt -> fixed .
$this -> encrypt -> invocation_counter
);
Strings :: increment_str ( $this -> encrypt -> invocation_counter );
2019-03-26 14:36:07 +01:00
$this -> encrypt -> setAAD ( $temp = ( $packet & " \xFF \xFF \xFF \xFF " ));
2019-03-18 12:59:00 +01:00
$packet = $temp . $this -> encrypt -> encrypt ( substr ( $packet , 4 ));
break ;
case 'chacha20-poly1305@openssh.com' :
$nonce = pack ( 'N2' , 0 , $this -> send_seq_no - 1 );
$this -> encrypt -> setNonce ( $nonce );
$this -> lengthEncrypt -> setNonce ( $nonce );
2019-03-26 14:36:07 +01:00
$length = $this -> lengthEncrypt -> encrypt ( $packet & " \xFF \xFF \xFF \xFF " );
2019-03-18 12:59:00 +01: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 :
$packet = $this -> encrypt -> encrypt ( $packet );
2018-12-27 15:31:35 +01:00
}
2007-07-23 07:21:39 +02:00
}
2018-12-31 21:14:43 +01:00
$packet .= $this -> encrypt && $this -> encrypt -> usesNonce () ? $this -> encrypt -> getTag () : $hmac ;
2007-07-23 07:21:39 +02:00
2013-12-05 18:46:46 +01:00
$start = microtime ( true );
2019-03-30 20:12:52 +01:00
$sent = fputs ( $this -> fsock , $packet );
2013-12-05 18:46:46 +01:00
$stop = microtime ( true );
2009-08-29 21:23:25 +02:00
if ( defined ( 'NET_SSH2_LOGGING' )) {
2013-12-05 18:46:46 +01:00
$current = microtime ( true );
2012-04-30 18:25:37 +02:00
$message_number = isset ( $this -> message_numbers [ ord ( $data [ 0 ])]) ? $this -> message_numbers [ ord ( $data [ 0 ])] : 'UNKNOWN (' . ord ( $data [ 0 ]) . ')' ;
$message_number = '-> ' . $message_number .
2012-11-22 20:08:30 +01:00
' (since last: ' . round ( $current - $this -> last_packet , 4 ) . ', network: ' . round ( $stop - $start , 4 ) . 's)' ;
2017-01-08 02:51:56 +01:00
$this -> append_log ( $message_number , isset ( $logged ) ? $logged : $data );
2012-11-22 20:08:30 +01:00
$this -> last_packet = $current ;
2009-08-29 21:23:25 +02:00
}
2019-03-30 20:12:52 +01:00
if ( strlen ( $packet ) != $sent ) {
$this -> bitmap = 0 ;
throw new \RuntimeException ( " Only $sent of " . strlen ( $packet ) . " bytes were sent " );
}
2007-07-23 07:21:39 +02:00
}
2011-06-04 19:06:53 +02:00
/**
* Logs data packets
*
* Makes sure that only the last 1 MB worth of packets will be logged
*
2017-11-05 21:35:27 +01:00
* @ param string $message_number
* @ param string $message
2011-06-04 19:06:53 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function append_log ( $message_number , $message )
2011-06-04 19:06:53 +02:00
{
2013-12-26 11:45:24 +01: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 05:18:06 +02:00
Strings :: shift ( $message );
2013-12-26 11:45:24 +01:00
}
2013-07-26 16:57:20 +02:00
2013-12-26 11:45:24 +01:00
switch ( NET_SSH2_LOGGING ) {
// useful for benchmarks
2014-12-04 22:45:13 +01:00
case self :: LOG_SIMPLE :
2013-12-26 11:45:24 +01:00
$this -> message_number_log [] = $message_number ;
break ;
// the most useful log for SSH2
2014-12-04 22:45:13 +01:00
case self :: LOG_COMPLEX :
2013-12-26 11:45:24 +01:00
$this -> message_number_log [] = $message_number ;
$this -> log_size += strlen ( $message );
$this -> message_log [] = $message ;
2014-12-04 22:45:13 +01:00
while ( $this -> log_size > self :: LOG_MAX_SIZE ) {
2013-12-26 11:45:24 +01:00
$this -> log_size -= strlen ( array_shift ( $this -> message_log ));
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 22:45:13 +01:00
case self :: LOG_REALTIME :
2013-12-26 11:45:24 +01:00
switch ( PHP_SAPI ) {
case 'cli' :
$start = $stop = " \r \n " ;
2012-04-30 18:25:37 +02:00
break ;
2013-12-26 11:45:24 +01:00
default :
$start = '<pre>' ;
$stop = '</pre>' ;
}
2017-01-08 02:51:56 +01:00
echo $start . $this -> format_log ([ $message ], [ $message_number ]) . $stop ;
2013-12-26 11:45:24 +01:00
@ flush ();
@ ob_flush ();
break ;
2017-08-29 14:42:51 +02:00
// basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
2014-12-04 22:45:13 +01:00
// needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
2013-12-26 11:45:24 +01: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 22:45:13 +01:00
case self :: LOG_REALTIME_FILE :
2013-12-26 11:45:24 +01:00
if ( ! isset ( $this -> realtime_log_file )) {
// PHP doesn't seem to like using constants in fopen()
2016-04-30 23:23:35 +02:00
$filename = NET_SSH2_LOG_REALTIME_FILENAME ;
2013-12-26 11:45:24 +01:00
$fp = fopen ( $filename , 'w' );
$this -> realtime_log_file = $fp ;
}
if ( ! is_resource ( $this -> realtime_log_file )) {
break ;
}
2017-01-08 02:51:56 +01:00
$entry = $this -> format_log ([ $message ], [ $message_number ]);
2013-12-26 11:45:24 +01:00
if ( $this -> realtime_log_wrap ) {
$temp = " <<< START >>> \r \n " ;
$entry .= $temp ;
fseek ( $this -> realtime_log_file , ftell ( $this -> realtime_log_file ) - strlen ( $temp ));
}
$this -> realtime_log_size += strlen ( $entry );
2014-12-04 22:45:13 +01:00
if ( $this -> realtime_log_size > self :: LOG_MAX_SIZE ) {
2013-12-26 11:45:24 +01: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 19:06:53 +02:00
}
2009-09-17 05:19:20 +02:00
/**
* Sends channel data
*
* Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
*
2016-04-10 18:30:59 +02:00
* @ param int $client_channel
* @ param string $data
* @ return bool
2009-09-17 05:19:20 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
protected function send_channel_packet ( $client_channel , $data )
2009-09-17 05:19:20 +02:00
{
2014-12-06 05:01:39 +01:00
while ( strlen ( $data )) {
2013-09-12 15:29:14 +02:00
if ( ! $this -> window_size_client_to_server [ $client_channel ]) {
2014-12-04 22:45:13 +01:00
$this -> bitmap ^= self :: MASK_WINDOW_ADJUST ;
2013-09-12 15:29:14 +02:00
// using an invalid channel will let the buffers be built up for the valid channels
2017-01-08 02:51:56 +01:00
$this -> get_channel_packet ( - 1 );
2015-04-11 00:48:58 +02:00
$this -> bitmap ^= self :: MASK_WINDOW_ADJUST ;
2013-09-12 15:29:14 +02:00
}
2014-12-06 05:01:39 +01: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 05:18:06 +02:00
$temp = Strings :: shift ( $data , $max_size );
2019-04-02 07:09:19 +02:00
$packet = Strings :: packSSH2 (
'CNs' ,
2009-09-17 05:19:20 +02:00
NET_SSH2_MSG_CHANNEL_DATA ,
2009-12-14 19:14:54 +01:00
$this -> server_channels [ $client_channel ],
2014-03-21 08:53:43 +01:00
$temp
2009-09-17 05:19:20 +02:00
);
2014-08-30 22:26:23 +02:00
$this -> window_size_client_to_server [ $client_channel ] -= strlen ( $temp );
2019-03-30 20:12:52 +01:00
$this -> send_binary_packet ( $packet );
2013-07-14 11:09:16 +02:00
}
2014-12-06 05:01:39 +01:00
return true ;
2009-09-17 05:19:20 +02:00
}
2011-02-13 08:46:01 +01:00
/**
* Closes and flushes a channel
*
2014-12-10 02:31:41 +01:00
* \phpseclib\Net\SSH2 doesn ' t properly close most channels . For exec () channels are normally closed by the server
2011-02-13 08:46:01 +01:00
* and for SFTP channels are presumably closed when the client disconnects . This functions is intended
* for SCP more than anything .
*
2016-04-10 18:30:59 +02:00
* @ param int $client_channel
* @ param bool $want_reply
* @ return bool
2011-02-13 08:46:01 +01:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function close_channel ( $client_channel , $want_reply = false )
2011-02-13 08:46:01 +01:00
{
// see http://tools.ietf.org/html/rfc4254#section-5.3
2017-01-08 02:51:56 +01:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_EOF , $this -> server_channels [ $client_channel ]));
2012-03-05 03:06:13 +01:00
2013-12-15 07:43:20 +01:00
if ( ! $want_reply ) {
2017-01-08 02:51:56 +01:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $client_channel ]));
2013-12-15 07:43:20 +01:00
}
2011-02-13 08:46:01 +01:00
2012-03-03 18:49:16 +01:00
$this -> channel_status [ $client_channel ] = NET_SSH2_MSG_CHANNEL_CLOSE ;
$this -> curTimeout = 0 ;
2017-01-08 02:51:56 +01:00
while ( ! is_bool ( $this -> get_channel_packet ( $client_channel ))) {
2015-07-15 03:52:31 +02:00
}
2012-03-03 18:49:16 +01:00
2013-12-15 07:43:20 +01:00
if ( $want_reply ) {
2017-01-08 02:51:56 +01:00
$this -> send_binary_packet ( pack ( 'CN' , NET_SSH2_MSG_CHANNEL_CLOSE , $this -> server_channels [ $client_channel ]));
2013-12-15 07:43:20 +01:00
}
2014-12-04 22:45:13 +01:00
if ( $this -> bitmap & self :: MASK_SHELL ) {
$this -> bitmap &= ~ self :: MASK_SHELL ;
2012-03-03 18:49:16 +01:00
}
2011-02-13 08:46:01 +01:00
}
2007-07-23 07:21:39 +02:00
/**
* Disconnect
*
2016-04-10 18:30:59 +02:00
* @ param int $reason
* @ return bool
2017-08-04 12:06:25 +02:00
* @ access protected
2007-07-23 07:21:39 +02:00
*/
2017-08-03 09:19:11 +02:00
protected function disconnect_helper ( $reason )
2007-07-23 07:21:39 +02:00
{
2015-01-04 11:38:03 +01:00
if ( $this -> bitmap & self :: MASK_CONNECTED ) {
2019-04-02 07:09:19 +02:00
$data = Strings :: packSSH2 ( 'CNss' , NET_SSH2_MSG_DISCONNECT , $reason , '' , '' );
2017-01-08 02:51:56 +01:00
$this -> send_binary_packet ( $data );
2008-05-26 21:42:01 +02:00
$this -> bitmap = 0 ;
2008-05-15 19:40:03 +02:00
fclose ( $this -> fsock );
return false ;
}
2007-07-23 07:21:39 +02:00
}
2008-05-26 21:42:01 +02: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 .
*
2017-11-21 09:36:28 +01:00
* @ param $args []
2017-08-04 12:06:25 +02:00
* @ access protected
2008-05-26 21:42:01 +02:00
*/
2017-11-21 09:36:28 +01:00
protected function define_array ( ... $args )
2008-05-26 21:42:01 +02:00
{
foreach ( $args as $arg ) {
2015-07-15 03:52:31 +02:00
foreach ( $arg as $key => $value ) {
2008-05-26 21:42:01 +02:00
if ( ! defined ( $value )) {
define ( $value , $key );
} else {
break 2 ;
}
}
}
}
2009-03-25 23:29:42 +01:00
/**
* Returns a log of the packets that have been sent and received .
*
2014-12-04 22:45:13 +01: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 23:29:42 +01:00
*
* @ access public
2016-04-10 18:30:59 +02:00
* @ return array | false | string
2009-03-25 23:29:42 +01:00
*/
2017-01-08 02:51:56 +01:00
public function getLog ()
2009-03-25 23:29:42 +01:00
{
2010-04-07 05:50:54 +02:00
if ( ! defined ( 'NET_SSH2_LOGGING' )) {
return false ;
}
switch ( NET_SSH2_LOGGING ) {
2014-12-04 22:45:13 +01:00
case self :: LOG_SIMPLE :
2010-04-07 05:50:54 +02:00
return $this -> message_number_log ;
2014-12-04 22:45:13 +01:00
case self :: LOG_COMPLEX :
2017-05-28 16:44:38 +02:00
$log = $this -> format_log ( $this -> message_log , $this -> message_number_log );
2017-03-02 05:14:58 +01:00
return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>' ;
2010-04-07 05:50:54 +02:00
default :
return false ;
2009-03-25 23:29:42 +01:00
}
2010-04-07 05:50:54 +02:00
}
2009-03-25 23:29:42 +01:00
2010-04-07 05:50:54 +02:00
/**
* Formats a log for printing
*
2016-04-10 18:30:59 +02:00
* @ param array $message_log
* @ param array $message_number_log
2010-04-07 05:50:54 +02:00
* @ access private
2016-04-10 18:30:59 +02:00
* @ return string
2010-04-07 05:50:54 +02:00
*/
2017-01-08 02:51:56 +01:00
protected function format_log ( $message_log , $message_number_log )
2010-04-07 05:50:54 +02:00
{
2009-03-25 23:29:42 +01:00
$output = '' ;
2010-04-07 05:50:54 +02:00
for ( $i = 0 ; $i < count ( $message_log ); $i ++ ) {
$output .= $message_number_log [ $i ] . " \r \n " ;
$current_log = $message_log [ $i ];
2010-04-24 08:40:49 +02:00
$j = 0 ;
2009-03-25 23:29:42 +01:00
do {
2013-01-12 17:46:19 +01:00
if ( strlen ( $current_log )) {
2010-04-24 08:40:49 +02:00
$output .= str_pad ( dechex ( $j ), 7 , '0' , STR_PAD_LEFT ) . '0 ' ;
}
2016-07-31 05:18:06 +02:00
$fragment = Strings :: shift ( $current_log , $this -> log_short_width );
2017-01-08 02:51:56 +01:00
$hex = substr ( preg_replace_callback ( '#.#s' , [ $this , 'format_log_helper' ], $fragment ), strlen ( $this -> log_boundary ));
2009-03-25 23:29:42 +01:00
// replace non ASCII printable characters with dots
// http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
2010-04-25 16:19:43 +02:00
// also replace < with a . since < messes up the output on web browsers
$raw = preg_replace ( '#[^\x20-\x7E]|<#' , '.' , $fragment );
2013-12-16 18:27:12 +01:00
$output .= str_pad ( $hex , $this -> log_long_width - $this -> log_short_width , ' ' ) . $raw . " \r \n " ;
2010-04-24 08:40:49 +02:00
$j ++ ;
2013-01-12 17:46:19 +01:00
} while ( strlen ( $current_log ));
2009-03-25 23:29:42 +01:00
$output .= " \r \n " ;
}
return $output ;
}
2013-12-16 18:27:12 +01:00
/**
* Helper function for _format_log
*
* For use with preg_replace_callback ()
*
2016-04-10 18:30:59 +02:00
* @ param array $matches
2013-12-16 18:27:12 +01:00
* @ access private
2016-04-10 18:30:59 +02:00
* @ return string
2013-12-16 18:27:12 +01:00
*/
2017-01-08 02:51:56 +01:00
private function format_log_helper ( $matches )
2013-12-16 18:27:12 +01:00
{
return $this -> log_boundary . str_pad ( dechex ( ord ( $matches [ 0 ])), 2 , '0' , STR_PAD_LEFT );
}
2015-02-06 04:28:23 +01:00
/**
2017-01-08 02:51:56 +01:00
* Helper function for agent -> on_channel_open ()
2015-02-06 04:28:23 +01:00
*
* Used when channels are created to inform agent
* of said channel opening . Must be called after
* channel open confirmation received
*
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function on_channel_open ()
2015-02-06 04:28:23 +01:00
{
if ( isset ( $this -> agent )) {
2019-03-31 22:15:32 +02:00
$this -> agent -> registerChannelOpen ( $this );
2015-02-06 04:28:23 +01:00
}
}
2015-07-17 18:20:42 +02: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 18:30:59 +02:00
* @ param array $array1
* @ param array $array2
* @ return mixed False if intersection is empty , else intersected value .
2015-07-17 18:20:42 +02:00
* @ access private
*/
2017-01-08 02:51:56 +01:00
private function array_intersect_first ( $array1 , $array2 )
2015-07-17 18:20:42 +02:00
{
foreach ( $array1 as $value ) {
if ( in_array ( $value , $array2 )) {
return $value ;
}
}
return false ;
}
2007-07-23 07:21:39 +02:00
/**
2010-02-13 00:02:13 +01:00
* Returns all errors
2007-07-23 07:21:39 +02:00
*
2016-04-13 11:59:55 +02:00
* @ return string []
2010-02-13 00:02:13 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getErrors ()
2010-02-13 00:02:13 +01:00
{
return $this -> errors ;
}
/**
* Returns the last error
2007-07-23 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ return string
2009-03-25 23:29:42 +01:00
* @ access public
2007-07-23 07:21:39 +02:00
*/
2017-01-08 02:51:56 +01:00
public function getLastError ()
2007-07-23 07:21:39 +02:00
{
2016-04-10 18:30:59 +02:00
$count = count ( $this -> errors );
if ( $count > 0 ) {
return $this -> errors [ $count - 1 ];
}
2007-07-23 07:21:39 +02:00
}
/**
* Return the server identification .
*
2016-04-10 18:30:59 +02:00
* @ return string
2007-07-23 07:21:39 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getServerIdentification ()
2007-07-23 07:21:39 +02:00
{
2017-01-08 02:51:56 +01:00
$this -> connect ();
2014-07-25 17:03:31 +02:00
2007-07-23 07:21:39 +02:00
return $this -> server_identifier ;
}
/**
2019-03-30 00:44:31 +01:00
* Returns a list of algorithms the server supports
2007-07-23 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ return array
2007-07-23 07:21:39 +02:00
* @ access public
*/
2019-03-30 00:44:31 +01:00
public function getServerAlgorithms ()
2007-07-23 07:21:39 +02:00
{
2017-01-08 02:51:56 +01:00
$this -> connect ();
2014-07-25 17:03:31 +02:00
2019-03-30 00:44:31 +01: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 07:21:39 +02:00
}
2019-03-29 03:45:28 +01:00
/**
* Returns a list of KEX algorithms that phpseclib supports
*
* @ return array
* @ access public
*/
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.
'curve25519-sha256@libssh.org' ,
// Diffie-Hellman Key Agreement (DH) using integer modulo prime
// groups.
'diffie-hellman-group1-sha1' , // REQUIRED
'diffie-hellman-group14-sha1' , // REQUIRED
'diffie-hellman-group-exchange-sha1' , // RFC 4419
'diffie-hellman-group-exchange-sha256' , // RFC 4419
];
if ( ! function_exists ( '\\Sodium\\library_version_major' )) {
$kex_algorithms = array_diff (
$kex_algorithms ,
[ 'curve25519-sha256@libssh.org' ]
);
}
return $kex_algorithms ;
}
/**
* Returns a list of host key algorithms that phpseclib supports
*
* @ return array
* @ access public
*/
public static function getSupportedHostKeyAlgorithms ()
{
return [
2019-03-30 19:35:16 +01:00
'ssh-ed25519' , // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
'ecdsa-sha2-nistp256' , // RFC 5656
2019-05-19 22:35:29 +02:00
'ecdsa-sha2-nistp384' , // RFC 5656
'ecdsa-sha2-nistp521' , // RFC 5656
2019-03-29 03:45:28 +01: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
* @ access public
*/
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
// from <https://git.io/fhxOl>:
// 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
];
$engines = [
'libsodium' ,
'OpenSSL (GCM)' ,
'OpenSSL' ,
'mcrypt' ,
'Eval' ,
'PHP'
];
$ciphers = [];
foreach ( $engines as $engine ) {
foreach ( $algos as $algo ) {
$obj = self :: encryption_algorithm_to_crypt_instance ( $algo );
if ( $obj instanceof Rijndael ) {
$obj -> setKeyLength ( preg_replace ( '#[^\d]#' , '' , $algo ));
}
switch ( $algo ) {
case 'chacha20-poly1305@openssh.com' :
case 'arcfour128' :
case 'arcfour256' :
if ( $engine == 'Eval' ) {
$algos = array_diff ( $algos , [ $algo ]);
$ciphers [] = $algo ;
} else {
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
* @ access public
*/
public static function getSupportedMACAlgorithms ()
{
return [
// from <http://www.ietf.org/rfc/rfc6668.txt>:
'hmac-sha2-256' , // RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
'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
* @ access public
*/
public static function getSupportedCompressionAlgorithms ()
{
return [
'none' // REQUIRED no compression
//'zlib' // OPTIONAL ZLIB (LZ77) compression
];
}
2019-03-28 19:57:26 +01:00
/**
2019-03-30 00:44:31 +01:00
* Return list of negotiated algorithms
2019-03-28 19:57:26 +01:00
*
* Uses the same format as https :// www . php . net / ssh2 - methods - negotiated
*
* @ return array
* @ access public
*/
2019-03-30 00:44:31 +01:00
public function getAlgorithmsNegotiated ()
2019-03-28 19:57:26 +01:00
{
$this -> connect ();
return [
'kex' => $this -> kex_algorithm ,
'hostkey' => $this -> signature_format ,
'client_to_server' => [
'crypt' => $this -> encrypt -> name ,
'mac' => $this -> hmac_create -> name ,
'comp' => 'none' ,
],
'server_to_client' => [
'crypt' => $this -> decrypt -> name ,
'mac' => $this -> hmac_check -> name ,
'comp' => 'none' ,
]
];
}
2019-03-30 00:44:31 +01: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
* @ access public
*/
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 02:58:24 +02: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 18:30:59 +02:00
* @ return string
2013-04-28 02:58:24 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getBannerMessage ()
2013-04-28 02:58:24 +02:00
{
return $this -> banner_message ;
}
2007-07-23 07:21:39 +02: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 18:06:43 +02:00
* is recommended . Returns false if the server signature is not signed correctly with the public host key .
2007-07-23 07:21:39 +02:00
*
2016-04-10 18:30:59 +02:00
* @ return mixed
2016-04-30 23:23:35 +02:00
* @ throws \RuntimeException on badly formatted keys
* @ throws \phpseclib\Exception\NoSupportedAlgorithmsException when the key isn ' t in a supported format
2007-07-23 07:21:39 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getServerPublicHostKey ()
2007-07-23 07:21:39 +02:00
{
2014-12-04 22:45:13 +01:00
if ( ! ( $this -> bitmap & self :: MASK_CONSTRUCTOR )) {
2017-01-08 02:51:56 +01:00
if ( ! $this -> connect ()) {
2014-06-16 17:19:34 +02:00
return false ;
}
}
2010-04-22 18:06:43 +02:00
$signature = $this -> signature ;
2019-03-30 19:35:16 +01:00
$server_public_host_key = base64_encode ( $this -> server_public_host_key );
2010-04-22 18:06:43 +02:00
2012-07-04 20:36:26 +02:00
if ( $this -> signature_validated ) {
return $this -> bitmap ?
2019-05-19 22:35:29 +02:00
$this -> signature_format . ' ' . $server_public_host_key :
2012-07-04 20:36:26 +02:00
false ;
}
$this -> signature_validated = true ;
2010-04-22 18:06:43 +02:00
switch ( $this -> signature_format ) {
2019-03-30 19:35:16 +01:00
case 'ssh-ed25519' :
case 'ecdsa-sha2-nistp256' :
2019-05-19 22:35:29 +02:00
case 'ecdsa-sha2-nistp384' :
case 'ecdsa-sha2-nistp521' :
$key = ECDSA :: load ( $server_public_host_key , 'OpenSSH' )
-> withSignatureFormat ( 'SSH2' );
2019-03-30 19:35:16 +01:00
switch ( $this -> signature_format ) {
case 'ssh-ed25519' :
Strings :: shift ( $signature , 4 + strlen ( 'ssh-ed25519' ) + 4 );
2019-05-19 22:35:29 +02:00
$hash = 'sha512' ;
2019-03-30 19:35:16 +01:00
break ;
case 'ecdsa-sha2-nistp256' :
2019-05-19 22:35:29 +02:00
$hash = 'sha256' ;
break ;
case 'ecdsa-sha2-nistp384' :
$hash = 'sha384' ;
break ;
case 'ecdsa-sha2-nistp521' :
$hash = 'sha512' ;
2010-04-22 18:06:43 +02:00
}
2019-05-19 22:35:29 +02:00
$key = $key -> withHash ( $hash );
2019-03-30 19:35:16 +01:00
break ;
case 'ssh-dss' :
2019-05-19 22:35:29 +02:00
$key = DSA :: load ( $server_public_host_key , 'OpenSSH' )
-> withSignatureFormat ( 'SSH2' )
-> withHash ( 'sha1' );
2010-04-22 18:06:43 +02:00
break ;
case 'ssh-rsa' :
2018-05-27 16:48:44 +02:00
case 'rsa-sha2-256' :
case 'rsa-sha2-512' :
2019-03-30 19:35:16 +01:00
if ( strlen ( $signature ) < 15 ) {
2016-10-23 05:13:17 +02:00
return false ;
}
2019-03-30 19:35:16 +01:00
Strings :: shift ( $signature , 11 );
2016-07-31 05:18:06 +02:00
$temp = unpack ( 'Nlength' , Strings :: shift ( $signature , 4 ));
$signature = Strings :: shift ( $signature , $temp [ 'length' ]);
2010-04-22 18:06:43 +02:00
2019-05-19 22:35:29 +02:00
$key = RSA :: load ( $server_public_host_key , 'OpenSSH' )
-> withPadding ( RSA :: SIGNATURE_PKCS1 );
2018-05-27 16:48:44 +02: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 22:35:29 +02:00
$key = $key -> withHash ( $hash );
2012-07-04 20:36:26 +02:00
break ;
default :
2017-01-08 02:51:56 +01:00
$this -> disconnect_helper ( NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE );
2016-04-30 23:23:35 +02:00
throw new NoSupportedAlgorithmsException ( 'Unsupported signature format' );
2010-04-22 18:06:43 +02:00
}
2019-05-19 22:35:29 +02: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 07:21:39 +02:00
}
2013-01-17 19:47:42 +01:00
/**
* Returns the exit status of an SSH command or false .
*
2016-04-10 18:30:59 +02:00
* @ return false | int
2013-01-17 19:47:42 +01:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getExitStatus ()
2013-01-17 19:47:42 +01:00
{
if ( is_null ( $this -> exit_status )) {
return false ;
}
return $this -> exit_status ;
}
2014-06-22 21:30:52 +02:00
2014-06-20 12:04:17 +02:00
/**
* Returns the number of columns for the terminal window size .
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ return int
2014-06-20 12:04:17 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getWindowColumns ()
2014-06-20 12:04:17 +02:00
{
return $this -> windowColumns ;
}
2014-06-22 21:30:52 +02:00
2014-06-20 12:04:17 +02:00
/**
* Returns the number of rows for the terminal window size .
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ return int
2014-06-20 12:04:17 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function getWindowRows ()
2014-06-20 12:04:17 +02:00
{
return $this -> windowRows ;
}
2014-06-22 21:30:52 +02:00
2014-06-20 12:04:17 +02:00
/**
* Sets the number of columns for the terminal window size .
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ param int $value
2014-06-20 12:04:17 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function setWindowColumns ( $value )
2014-06-20 12:04:17 +02:00
{
$this -> windowColumns = $value ;
}
2014-06-22 21:30:52 +02:00
2014-06-20 12:04:17 +02:00
/**
* Sets the number of rows for the terminal window size .
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ param int $value
2014-06-20 12:04:17 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function setWindowRows ( $value )
2014-06-20 12:04:17 +02:00
{
$this -> windowRows = $value ;
}
2014-06-22 21:30:52 +02:00
2014-06-20 12:04:17 +02:00
/**
* Sets the number of columns and rows for the terminal window size .
2014-07-20 23:03:06 +02:00
*
2016-04-10 18:30:59 +02:00
* @ param int $columns
* @ param int $rows
2014-06-20 12:04:17 +02:00
* @ access public
*/
2017-01-08 02:51:56 +01:00
public function setWindowSize ( $columns = 80 , $rows = 24 )
2014-06-20 12:04:17 +02:00
{
$this -> windowColumns = $columns ;
$this -> windowRows = $rows ;
}
2016-04-30 23:23:35 +02:00
/**
2017-01-08 02:51:56 +01:00
* To String Magic Method
*
2016-04-30 23:23:35 +02:00
* @ return string
2017-01-08 02:51:56 +01:00
* @ access public
2016-04-30 23:23:35 +02:00
*/
2017-01-08 02:51:56 +01:00
public function __toString ()
2016-04-30 23:23:35 +02:00
{
return $this -> getResourceId ();
}
/**
2017-01-08 02:51:56 +01:00
* Get Resource ID
*
2016-04-30 23:23:35 +02: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 02:51:56 +01:00
public function getResourceId ()
2016-04-30 23:23:35 +02: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 02:51:56 +01:00
public static function getConnectionByResourceId ( $id )
2016-04-30 23:23:35 +02:00
{
return isset ( self :: $connections [ $id ]) ? self :: $connections [ $id ] : false ;
}
/**
* Return all excising connections
*
* @ return SSH2 []
*/
2017-01-08 02:51:56 +01:00
public static function getConnections ()
2016-04-30 23:23:35 +02:00
{
return self :: $connections ;
}
2013-05-08 08:52:18 +02:00
}