Subversion Repositories oidplus

Compare Revisions

Regard whitespace Rev 873 → Rev 874

/trunk/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php
37,6 → 37,8
* ?>
* </code>
*
* @category Net
* @package SSH2
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
60,7 → 62,7
use phpseclib3\Crypt\RC4;
use phpseclib3\Crypt\Rijndael;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Crypt\TripleDES; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
use phpseclib3\Crypt\Twofish;
use phpseclib3\Exception\ConnectionClosedException;
use phpseclib3\Exception\InsufficientSetupException;
69,17 → 71,14
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\Exception\UnsupportedCurveException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Net\SSH2\ChannelConnectionFailureReason;
use phpseclib3\Net\SSH2\DisconnectReason;
use phpseclib3\Net\SSH2\MessageType;
use phpseclib3\Net\SSH2\MessageTypeExtra;
use phpseclib3\Net\SSH2\TerminalMode;
use phpseclib3\System\SSH\Agent;
 
/**
* Pure-PHP implementation of SSHv2.
*
* @package SSH2
* @author Jim Wigginton <terrafrost@php.net>
* @access public
*/
class SSH2
{
86,6 → 85,7
/**#@+
* Compression Types
*
* @access private
*/
/**
* No compression
123,6 → 123,7
*
* @see \phpseclib3\Net\SSH2::send_channel_packet()
* @see \phpseclib3\Net\SSH2::get_channel_packet()
* @access private
*/
const CHANNEL_EXEC = 1; // PuTTy uses 0x100
const CHANNEL_SHELL = 2;
133,6 → 134,7
/**
* Returns the message numbers
*
* @access public
* @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_SIMPLE = 1;
139,6 → 141,7
/**
* Returns the message content
*
* @access public
* @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_COMPLEX = 2;
145,6 → 148,7
/**
* Outputs the content real-time
*
* @access public
* @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_REALTIME = 3;
151,6 → 155,7
/**
* Dumps the content real-time to a file
*
* @access public
* @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_REALTIME_FILE = 4;
157,6 → 162,7
/**
* Make sure that the log never gets larger than this
*
* @access public
* @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_MAX_SIZE = 1048576; // 1024 * 1024
164,6 → 170,7
/**
* Returns when a string matching $expect exactly is found
*
* @access public
* @see \phpseclib3\Net\SSH2::read()
*/
const READ_SIMPLE = 1;
170,6 → 177,7
/**
* Returns when a string matching the regular expression $expect is found
*
* @access public
* @see \phpseclib3\Net\SSH2::read()
*/
const READ_REGEX = 2;
179,6 → 187,7
* Some data packets may only contain a single character so it may be necessary
* to call read() multiple times when using this option
*
* @access public
* @see \phpseclib3\Net\SSH2::read()
*/
const READ_NEXT = 3;
187,6 → 196,7
* The SSH identifier
*
* @var string
* @access private
*/
private $identifier;
 
194,6 → 204,7
* The Socket Object
*
* @var resource|closed-resource|null
* @access private
*/
public $fsock;
 
204,6 → 215,7
* if a requisite function has been successfully executed. If not, an error should be thrown.
*
* @var int
* @access private
*/
protected $bitmap = 0;
 
213,6 → 225,7
* @see self::getErrors()
* @see self::getLastError()
* @var array
* @access private
*/
private $errors = [];
 
221,6 → 234,7
*
* @see self::getServerIdentification()
* @var string|false
* @access private
*/
protected $server_identifier = false;
 
229,6 → 243,7
*
* @see self::getKexAlgorithims()
* @var array|false
* @access private
*/
private $kex_algorithms = false;
 
237,6 → 252,7
*
* @see self::getMethodsNegotiated()
* @var string|false
* @access private
*/
private $kex_algorithm = false;
 
245,6 → 261,7
*
* @see self::_key_exchange()
* @var int
* @access private
*/
private $kex_dh_group_size_min = 1536;
 
253,6 → 270,7
*
* @see self::_key_exchange()
* @var int
* @access private
*/
private $kex_dh_group_size_preferred = 2048;
 
261,6 → 279,7
*
* @see self::_key_exchange()
* @var int
* @access private
*/
private $kex_dh_group_size_max = 4096;
 
269,6 → 288,7
*
* @see self::getServerHostKeyAlgorithms()
* @var array|false
* @access private
*/
private $server_host_key_algorithms = false;
 
277,6 → 297,7
*
* @see self::getEncryptionAlgorithmsClient2Server()
* @var array|false
* @access private
*/
private $encryption_algorithms_client_to_server = false;
 
285,6 → 306,7
*
* @see self::getEncryptionAlgorithmsServer2Client()
* @var array|false
* @access private
*/
private $encryption_algorithms_server_to_client = false;
 
293,6 → 315,7
*
* @see self::getMACAlgorithmsClient2Server()
* @var array|false
* @access private
*/
private $mac_algorithms_client_to_server = false;
 
301,6 → 324,7
*
* @see self::getMACAlgorithmsServer2Client()
* @var array|false
* @access private
*/
private $mac_algorithms_server_to_client = false;
 
309,6 → 333,7
*
* @see self::getCompressionAlgorithmsClient2Server()
* @var array|false
* @access private
*/
private $compression_algorithms_client_to_server = false;
 
317,6 → 342,7
*
* @see self::getCompressionAlgorithmsServer2Client()
* @var array|false
* @access private
*/
private $compression_algorithms_server_to_client = false;
 
325,6 → 351,7
*
* @see self::getLanguagesServer2Client()
* @var array|false
* @access private
*/
private $languages_server_to_client = false;
 
333,6 → 360,7
*
* @see self::getLanguagesClient2Server()
* @var array|false
* @access private
*/
private $languages_client_to_server = false;
 
341,6 → 369,7
*
* @see self::setPreferredAlgorithms()
* @var array
* @access private
*/
private $preferred = [];
 
357,6 → 386,7
* @see self::__construct()
* @see self::_send_binary_packet()
* @var int
* @access private
*/
private $encrypt_block_size = 8;
 
366,6 → 396,7
* @see self::__construct()
* @see self::_get_binary_packet()
* @var int
* @access private
*/
private $decrypt_block_size = 8;
 
374,6 → 405,7
*
* @see self::_get_binary_packet()
* @var SymmetricKey|false
* @access private
*/
private $decrypt = false;
 
381,6 → 413,7
* Decryption Algorithm Name
*
* @var string|null
* @access private
*/
private $decryptName;
 
390,6 → 423,7
* Used by GCM
*
* @var string|null
* @access private
*/
private $decryptInvocationCounter;
 
399,6 → 433,7
* Used by GCM
*
* @var string|null
* @access private
*/
private $decryptFixedPart;
 
407,6 → 442,7
*
* @see self::_get_binary_packet()
* @var object
* @access private
*/
private $lengthDecrypt = false;
 
415,6 → 451,7
*
* @see self::_send_binary_packet()
* @var SymmetricKey|false
* @access private
*/
private $encrypt = false;
 
422,6 → 459,7
* Encryption Algorithm Name
*
* @var string|null
* @access private
*/
private $encryptName;
 
431,6 → 469,7
* Used by GCM
*
* @var string|null
* @access private
*/
private $encryptInvocationCounter;
 
440,6 → 479,7
* Used by GCM
*
* @var string|null
* @access private
*/
private $encryptFixedPart;
 
448,6 → 488,7
*
* @see self::_send_binary_packet()
* @var object
* @access private
*/
private $lengthEncrypt = false;
 
456,6 → 497,7
*
* @see self::_send_binary_packet()
* @var object
* @access private
*/
private $hmac_create = false;
 
463,6 → 505,7
* Client to Server HMAC Name
*
* @var string|false
* @access private
*/
private $hmac_create_name;
 
470,6 → 513,7
* Client to Server ETM
*
* @var int|false
* @access private
*/
private $hmac_create_etm;
 
478,6 → 522,7
*
* @see self::_get_binary_packet()
* @var object
* @access private
*/
private $hmac_check = false;
 
485,6 → 530,7
* Server to Client HMAC Name
*
* @var string|false
* @access private
*/
private $hmac_check_name;
 
492,6 → 538,7
* Server to Client ETM
*
* @var int|false
* @access private
*/
private $hmac_check_etm;
 
504,6 → 551,7
*
* @see self::_get_binary_packet()
* @var int
* @access private
*/
private $hmac_size = false;
 
512,6 → 560,7
*
* @see self::getServerPublicHostKey()
* @var string
* @access private
*/
private $server_public_host_key;
 
526,6 → 575,7
*
* @see self::_key_exchange()
* @var string
* @access private
*/
private $session_id = false;
 
536,10 → 586,58
*
* @see self::_key_exchange()
* @var string
* @access private
*/
private $exchange_hash = false;
 
/**
* Message Numbers
*
* @see self::__construct()
* @var array
* @access private
*/
private $message_numbers = [];
 
/**
* Disconnection Message 'reason codes' defined in RFC4253
*
* @see self::__construct()
* @var array
* @access private
*/
private $disconnect_reasons = [];
 
/**
* SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
*
* @see self::__construct()
* @var array
* @access private
*/
private $channel_open_failure_reasons = [];
 
/**
* Terminal Modes
*
* @link http://tools.ietf.org/html/rfc4254#section-8
* @see self::__construct()
* @var array
* @access private
*/
private $terminal_modes = [];
 
/**
* SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
*
* @link http://tools.ietf.org/html/rfc4254#section-5.2
* @see self::__construct()
* @var array
* @access private
*/
private $channel_extended_data_type_codes = [];
 
/**
* Send Sequence Number
*
* See 'Section 6.4. Data Integrity' of rfc4253 for more info.
546,6 → 644,7
*
* @see self::_send_binary_packet()
* @var int
* @access private
*/
private $send_seq_no = 0;
 
556,6 → 655,7
*
* @see self::_get_binary_packet()
* @var int
* @access private
*/
private $get_seq_no = 0;
 
567,6 → 667,7
* @see self::get_channel_packet()
* @see self::exec()
* @var array
* @access private
*/
protected $server_channels = [];
 
579,6 → 680,7
* @see self::get_channel_packet()
* @see self::exec()
* @var array
* @access private
*/
private $channel_buffers = [];
 
589,6 → 691,7
*
* @see self::get_channel_packet()
* @var array
* @access private
*/
protected $channel_status = [];
 
599,6 → 702,7
*
* @see self::send_channel_packet()
* @var array
* @access private
*/
private $packet_size_client_to_server = [];
 
607,6 → 711,7
*
* @see self::getLog()
* @var array
* @access private
*/
private $message_number_log = [];
 
615,6 → 720,7
*
* @see self::getLog()
* @var array
* @access private
*/
private $message_log = [];
 
626,6 → 732,7
* @var int
* @see self::send_channel_packet()
* @see self::exec()
* @access private
*/
protected $window_size = 0x7FFFFFFF;
 
639,6 → 746,7
* @var int
* @see self::_send_channel_packet()
* @see self::exec()
* @access private
*/
private $window_resize = 0x40000000;
 
649,6 → 757,7
*
* @see self::send_channel_packet()
* @var array
* @access private
*/
protected $window_size_server_to_client = [];
 
659,6 → 768,7
*
* @see self::get_channel_packet()
* @var array
* @access private
*/
private $window_size_client_to_server = [];
 
669,6 → 779,7
*
* @see self::getServerPublicHostKey()
* @var string
* @access private
*/
private $signature = '';
 
679,6 → 790,7
*
* @see self::getServerPublicHostKey()
* @var string
* @access private
*/
private $signature_format = '';
 
687,6 → 799,7
*
* @see self::read()
* @var string
* @access private
*/
private $interactiveBuffer = '';
 
698,6 → 811,7
* @see self::_send_binary_packet()
* @see self::_get_binary_packet()
* @var int
* @access private
*/
private $log_size;
 
705,6 → 819,7
* Timeout
*
* @see self::setTimeout()
* @access private
*/
protected $timeout;
 
712,6 → 827,7
* Current Timeout
*
* @see self::get_channel_packet()
* @access private
*/
protected $curTimeout;
 
719,6 → 835,7
* Keep Alive Interval
*
* @see self::setKeepAlive()
* @access private
*/
private $keepAlive;
 
727,6 → 844,7
*
* @see self::_append_log()
* @var resource|closed-resource
* @access private
*/
private $realtime_log_file;
 
735,6 → 853,7
*
* @see self::_append_log()
* @var int
* @access private
*/
private $realtime_log_size;
 
743,6 → 862,7
*
* @see self::getServerPublicHostKey()
* @var bool
* @access private
*/
private $signature_validated = false;
 
750,6 → 870,7
* Real-time log file wrap boolean
*
* @see self::_append_log()
* @access private
*/
private $realtime_log_wrap;
 
757,6 → 878,7
* Flag to suppress stderr from output
*
* @see self::enableQuietMode()
* @access private
*/
private $quiet_mode = false;
 
764,6 → 886,7
* Time of first network activity
*
* @var float
* @access private
*/
private $last_packet;
 
771,6 → 894,7
* Exit status returned from ssh if any
*
* @var int
* @access private
*/
private $exit_status;
 
779,6 → 903,7
*
* @var bool
* @see self::enablePTY()
* @access private
*/
private $request_pty = false;
 
786,6 → 911,7
* Flag set while exec() is running when using enablePTY()
*
* @var bool
* @access private
*/
private $in_request_pty_exec = false;
 
793,6 → 919,7
* Flag set after startSubsystem() is called
*
* @var bool
* @access private
*/
private $in_subsystem;
 
800,6 → 927,7
* Contents of stdError
*
* @var string
* @access private
*/
private $stdErrorLog;
 
808,6 → 936,7
*
* @see self::_keyboard_interactive_process()
* @var string
* @access private
*/
private $last_interactive_response = '';
 
816,6 → 945,7
*
* @see self::_keyboard_interactive_process()
* @var array
* @access private
*/
private $keyboard_requests_responses = [];
 
828,6 → 958,7
* @see self::_filter()
* @see self::getBannerMessage()
* @var string
* @access private
*/
private $banner_message = '';
 
836,6 → 967,7
*
* @see self::isTimeout()
* @var bool
* @access private
*/
private $is_timeout = false;
 
844,6 → 976,7
*
* @see self::_format_log()
* @var string
* @access private
*/
private $log_boundary = ':';
 
852,6 → 985,7
*
* @see self::_format_log()
* @var int
* @access private
*/
private $log_long_width = 65;
 
860,6 → 994,7
*
* @see self::_format_log()
* @var int
* @access private
*/
private $log_short_width = 16;
 
869,6 → 1004,7
* @see self::__construct()
* @see self::_connect()
* @var string
* @access private
*/
private $host;
 
878,6 → 1014,7
* @see self::__construct()
* @see self::_connect()
* @var int
* @access private
*/
private $port;
 
888,6 → 1025,7
* @see self::setWindowColumns()
* @see self::setWindowSize()
* @var int
* @access private
*/
private $windowColumns = 80;
 
898,6 → 1036,7
* @see self::setWindowRows()
* @see self::setWindowSize()
* @var int
* @access private
*/
private $windowRows = 24;
 
907,6 → 1046,7
* @see self::setCryptoEngine()
* @see self::_key_exchange()
* @var int
* @access private
*/
private static $crypto_engine = false;
 
914,6 → 1054,7
* A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
*
* @var Agent
* @access private
*/
private $agent;
 
929,6 → 1070,7
* Send the identification string first?
*
* @var bool
* @access private
*/
private $send_id_string_first = true;
 
936,6 → 1078,7
* Send the key exchange initiation packet first?
*
* @var bool
* @access private
*/
private $send_kex_first = true;
 
943,6 → 1086,7
* Some versions of OpenSSH incorrectly calculate the key size
*
* @var bool
* @access private
*/
private $bad_key_size_fix = false;
 
950,6 → 1094,7
* Should we try to re-connect to re-establish keys?
*
* @var bool
* @access private
*/
private $retry_connect = false;
 
957,6 → 1102,7
* Binary Packet Buffer
*
* @var string|false
* @access private
*/
private $binary_packet_buffer = false;
 
964,6 → 1110,7
* Preferred Signature Format
*
* @var string|false
* @access private
*/
protected $preferred_signature_format = false;
 
971,6 → 1118,7
* Authentication Credentials
*
* @var array
* @access private
*/
protected $auth = [];
 
978,6 → 1126,7
* Terminal
*
* @var string
* @access private
*/
private $term = 'vt100';
 
986,6 → 1135,7
*
* @see https://tools.ietf.org/html/rfc4252#section-5.1
* @var array|null
* @access private
*/
private $auth_methods_to_continue = null;
 
993,6 → 1143,7
* Compression method
*
* @var int
* @access private
*/
private $compress = self::NET_SSH2_COMPRESSION_NONE;
 
1000,6 → 1151,7
* Decompression method
*
* @var int
* @access private
*/
private $decompress = self::NET_SSH2_COMPRESSION_NONE;
 
1007,6 → 1159,7
* Compression context
*
* @var resource|false|null
* @access private
*/
private $compress_context;
 
1014,6 → 1167,7
* Decompression context
*
* @var resource|object
* @access private
*/
private $decompress_context;
 
1021,6 → 1175,7
* Regenerate Compression Context
*
* @var bool
* @access private
*/
private $regenerate_compression_context = false;
 
1028,6 → 1183,7
* Regenerate Decompression Context
*
* @var bool
* @access private
*/
private $regenerate_decompression_context = false;
 
1035,6 → 1191,7
* Smart multi-factor authentication flag
*
* @var bool
* @access private
*/
private $smartMFA = true;
 
1047,9 → 1204,89
* @param int $port
* @param int $timeout
* @see self::login()
* @access public
*/
public function __construct($host, $port = 22, $timeout = 10)
{
$this->message_numbers = [
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'
];
$this->disconnect_reasons = [
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'
];
$this->channel_open_failure_reasons = [
1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
];
$this->terminal_modes = [
0 => 'NET_SSH2_TTY_OP_END'
];
$this->channel_extended_data_type_codes = [
1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
];
 
$this->define_array(
$this->message_numbers,
$this->disconnect_reasons,
$this->channel_open_failure_reasons,
$this->terminal_modes,
$this->channel_extended_data_type_codes,
[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'],
// RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
[30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
// RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
[30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
);
 
/**
* Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508
* @var \WeakReference<SSH2>|SSH2
1077,6 → 1314,7
* OpenSSL, mcrypt, Eval, PHP
*
* @param int $engine
* @access public
*/
public static function setCryptoEngine($engine)
{
1090,6 → 1328,7
* 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
*/
public function sendIdentificationStringFirst()
{
1103,6 → 1342,7
* 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
*/
public function sendIdentificationStringLast()
{
1116,6 → 1356,7
* 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
*/
public function sendKEXINITFirst()
{
1129,6 → 1370,7
* 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
*/
public function sendKEXINITLast()
{
1140,6 → 1382,7
*
* @throws \UnexpectedValueException on receipt of unexpected packets
* @throws \RuntimeException on other errors
* @access private
*/
private function connect()
{
1261,7 → 1504,7
if (!$this->send_kex_first) {
$response = $this->get_binary_packet();
 
if (is_bool($response) || !strlen($response) || ord($response[0]) != MessageType::KEXINIT) {
if (is_bool($response) || !strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
$this->bitmap = 0;
throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT');
}
1283,6 → 1526,7
*
* You should overwrite this method in your own class if you want to use another identifier
*
* @access protected
* @return string
*/
private function generate_identifier()
1321,6 → 1565,7
* @throws \UnexpectedValueException on receipt of unexpected packets
* @throws \RuntimeException on other errors
* @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
* @access private
*/
private function key_exchange($kexinit_payload_server = false)
{
1372,7 → 1617,7
 
$client_cookie = Random::string(16);
 
$kexinit_payload_client = pack('Ca*', MessageType::KEXINIT, $client_cookie);
$kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie);
$kexinit_payload_client .= Strings::packSSH2(
'L10bN',
$kex_algorithms,
1397,9 → 1642,9
if (
is_bool($kexinit_payload_server)
|| !strlen($kexinit_payload_server)
|| ord($kexinit_payload_server[0]) != MessageType::KEXINIT
|| ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT
) {
$this->disconnect_helper(DisconnectReason::PROTOCOL_ERROR);
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT');
}
 
1435,7 → 1680,7
$decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
$decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt);
if ($decryptKeyLength === null) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
}
 
1442,7 → 1687,7
$encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
$encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt);
if ($encryptKeyLength === null) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
}
 
1449,25 → 1694,25
// through diffie-hellman key exchange a symmetric key is obtained
$this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
if ($this->kex_algorithm === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
}
 
$server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
if ($server_host_key_algorithm === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
}
 
$mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
if ($mac_algorithm_out === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
}
 
$mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
if ($mac_algorithm_in === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
}
 
1479,7 → 1724,7
 
$compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
if ($compression_algorithm_in === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
}
$this->decompress = $compression_map[$compression_algorithm_in];
1486,7 → 1731,7
 
$compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
if ($compression_algorithm_out === false) {
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
}
$this->compress = $compression_map[$compression_algorithm_out];
1523,8 → 1768,8
substr($this->kex_algorithm, 10);
$ourPrivate = EC::createKey($curve);
$ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates();
$clientKexInitMessage = MessageTypeExtra::KEX_ECDH_INIT;
$serverKexReplyMessage = MessageTypeExtra::KEX_ECDH_REPLY;
$clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY';
} else {
if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
$dh_group_sizes_packed = pack(
1535,20 → 1780,20
);
$packet = pack(
'Ca*',
MessageTypeExtra::KEXDH_GEX_REQUEST,
NET_SSH2_MSG_KEXDH_GEX_REQUEST,
$dh_group_sizes_packed
);
$this->send_binary_packet($packet);
$this->updateLogHistory('UNKNOWN (34)', 'SSH_MSG_KEXDH_GEX_REQUEST');
$this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
 
$response = $this->get_binary_packet();
 
list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response);
if ($type != MessageTypeExtra::KEXDH_GEX_GROUP) {
$this->disconnect_helper(DisconnectReason::PROTOCOL_ERROR);
if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new \UnexpectedValueException('Expected SSH_MSG_KEX_DH_GEX_GROUP');
}
$this->updateLogHistory('UNKNOWN (31)', 'SSH_MSG_KEXDH_GEX_GROUP');
$this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
$prime = new BigInteger($primeBytes, -256);
$g = new BigInteger($gBytes, -256);
 
1559,12 → 1804,12
);
 
$params = DH::createParameters($prime, $g);
$clientKexInitMessage = MessageTypeExtra::KEXDH_GEX_INIT;
$serverKexReplyMessage = MessageTypeExtra::KEXDH_GEX_REPLY;
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY';
} else {
$params = DH::createParameters($this->kex_algorithm);
$clientKexInitMessage = MessageType::KEXDH_INIT;
$serverKexReplyMessage = MessageType::KEXDH_REPLY;
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY';
}
 
$keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength));
1574,16 → 1819,16
$ourPublicBytes = $ourPublic->toBytes(true);
}
 
$data = pack('CNa*', $clientKexInitMessage, strlen($ourPublicBytes), $ourPublicBytes);
$data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes);
 
$this->send_binary_packet($data);
 
switch ($clientKexInitMessage) {
case MessageTypeExtra::KEX_ECDH_INIT:
$this->updateLogHistory('SSH_MSG_KEXDH_INIT', 'SSH_MSG_KEX_ECDH_INIT');
case 'NET_SSH2_MSG_KEX_ECDH_INIT':
$this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
break;
case MessageTypeExtra::KEXDH_GEX_INIT:
$this->updateLogHistory('UNKNOWN (32)', 'SSH_MSG_KEXDH_GEX_INIT');
case 'NET_SSH2_MSG_KEXDH_GEX_INIT':
$this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
}
 
$response = $this->get_binary_packet();
1595,16 → 1840,16
$this->signature
) = Strings::unpackSSH2('Csss', $response);
 
if ($type != $serverKexReplyMessage) {
$this->disconnect_helper(DisconnectReason::PROTOCOL_ERROR);
if ($type != constant($serverKexReplyMessage)) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new \UnexpectedValueException("Expected $serverKexReplyMessage");
}
switch ($serverKexReplyMessage) {
case MessageTypeExtra::KEX_ECDH_REPLY:
$this->updateLogHistory('SSH_MSG_KEXDH_REPLY', 'SSH_MSG_KEX_ECDH_REPLY');
case 'NET_SSH2_MSG_KEX_ECDH_REPLY':
$this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
break;
case MessageTypeExtra::KEXDH_GEX_REPLY:
$this->updateLogHistory('UNKNOWN (33)', 'SSH_MSG_KEXDH_GEX_REPLY');
case 'NET_SSH2_MSG_KEXDH_GEX_REPLY':
$this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
}
 
$this->server_public_host_key = $server_public_host_key;
1658,24 → 1903,24
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':
$this->disconnect_helper(DisconnectReason::HOST_KEY_NOT_VERIFIABLE);
$this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')');
}
}
 
$packet = pack('C', MessageType::NEWKEYS);
$packet = pack('C', NET_SSH2_MSG_NEWKEYS);
$this->send_binary_packet($packet);
 
$response = $this->get_binary_packet();
 
if ($response === false) {
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
}
 
list($type) = Strings::unpackSSH2('C', $response);
if ($type != MessageType::NEWKEYS) {
$this->disconnect_helper(DisconnectReason::PROTOCOL_ERROR);
if ($type != NET_SSH2_MSG_NEWKEYS) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS');
}
 
1836,6 → 2081,7
*
* @param string $algorithm Name of the encryption algorithm
* @return int|null Number of bytes as an integer or null for unknown
* @access private
*/
private function encryption_algorithm_to_key_size($algorithm)
{
1883,6 → 2129,7
*
* @param string $algorithm Name of the encryption algorithm
* @return SymmetricKey|null
* @access private
*/
private static function encryption_algorithm_to_crypt_instance($algorithm)
{
1931,6 → 2178,7
*
* @param string $algorithm Name of the encryption algorithm
* @return array{Hash, int}|null
* @access private
*/
private static function mac_algorithm_to_hash_instance($algorithm)
{
1966,6 → 2214,7
* @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291
* @param string $algorithm Name of the encryption algorithm
* @return bool
* @access private
*/
private static function bad_algorithm_candidate($algorithm)
{
1988,6 → 2237,7
* @param string|AsymmetricKey|array[]|Agent|null ...$args
* @return bool
* @see self::_login()
* @access public
*/
public function login($username, ...$args)
{
2013,6 → 2263,7
* @param string ...$args
* @return bool
* @see self::_login_helper()
* @access private
*/
protected function sublogin($username, ...$args)
{
2110,6 → 2361,7
* @return bool
* @throws \UnexpectedValueException on receipt of unexpected packets
* @throws \RuntimeException on other errors
* @access private
*/
private function login_helper($username, $password = null)
{
2118,7 → 2370,7
}
 
if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
$packet = Strings::packSSH2('Cs', MessageType::SERVICE_REQUEST, 'ssh-userauth');
$packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth');
$this->send_binary_packet($packet);
 
try {
2129,13 → 2381,13
$this->connect();
return $this->login_helper($username, $password);
}
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
}
 
list($type, $service) = Strings::unpackSSH2('Cs', $response);
if ($type != MessageType::SERVICE_ACCEPT || $service != 'ssh-userauth') {
$this->disconnect_helper(DisconnectReason::PROTOCOL_ERROR);
if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') {
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
}
$this->bitmap |= self::MASK_LOGIN_REQ;
2164,7 → 2416,7
if (!isset($password)) {
$packet = Strings::packSSH2(
'Cs3',
MessageType::USERAUTH_REQUEST,
NET_SSH2_MSG_USERAUTH_REQUEST,
$username,
'ssh-connection',
'none'
2176,10 → 2428,10
 
list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case MessageType::USERAUTH_SUCCESS:
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
case MessageType::USERAUTH_FAILURE:
case NET_SSH2_MSG_USERAUTH_FAILURE:
list($auth_methods) = Strings::unpackSSH2('L', $response);
$this->auth_methods_to_continue = $auth_methods;
// fall-through
2190,7 → 2442,7
 
$packet = Strings::packSSH2(
'Cs3bs',
MessageType::USERAUTH_REQUEST,
NET_SSH2_MSG_USERAUTH_REQUEST,
$username,
'ssh-connection',
'password',
2204,7 → 2456,7
} else {
$logged = Strings::packSSH2(
'Cs3bs',
MessageType::USERAUTH_REQUEST,
NET_SSH2_MSG_USERAUTH_REQUEST,
$username,
'ssh-connection',
'password',
2216,19 → 2468,17
$this->send_binary_packet($packet, $logged);
 
$response = $this->get_binary_packet();
if ($response === false) {
return false;
}
 
list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case MessageTypeExtra::USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
$this->updateLogHistory('SSH_MSG_USERAUTH_INFO_REQUEST', 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ');
case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
$this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
 
list($message) = Strings::unpackSSH2('s', $response);
$this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message;
 
return $this->disconnect_helper(DisconnectReason::AUTH_CANCELLED_BY_USER);
case MessageType::USERAUTH_FAILURE:
return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
case NET_SSH2_MSG_USERAUTH_FAILURE:
// can we use keyboard-interactive authentication? if not then either the login is bad or the server employees
// multi-factor authentication
list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response);
2241,7 → 2491,7
return false;
}
return false;
case MessageType::USERAUTH_SUCCESS:
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
}
2257,12 → 2507,13
* @param string $username
* @param string|array $password
* @return bool
* @access private
*/
private function keyboard_interactive_login($username, $password)
{
$packet = Strings::packSSH2(
'Cs5',
MessageType::USERAUTH_REQUEST,
NET_SSH2_MSG_USERAUTH_REQUEST,
$username,
'ssh-connection',
'keyboard-interactive',
2280,6 → 2531,7
* @param string|array ...$responses
* @return bool
* @throws \RuntimeException on connection error
* @access private
*/
private function keyboard_interactive_process(...$responses)
{
2291,7 → 2543,7
 
list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case MessageType::USERAUTH_INFO_REQUEST:
case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
list(
, // name; may be empty
, // instruction; may be empty
2328,7 → 2580,7
if (strlen($this->last_interactive_response)) {
$this->last_interactive_response = '';
} else {
$this->updateLogHistory('UNKNOWN (60)', 'SSH_MSG_USERAUTH_INFO_REQUEST');
$this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
}
 
if (!count($responses) && $num_prompts) {
2341,7 → 2593,7
MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
*/
// see http://tools.ietf.org/html/rfc4256#section-3.4
$packet = $logged = pack('CN', MessageType::USERAUTH_INFO_RESPONSE, count($responses));
$packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
for ($i = 0; $i < count($responses); $i++) {
$packet .= Strings::packSSH2('s', $responses[$i]);
$logged .= Strings::packSSH2('s', 'dummy-answer');
2349,7 → 2601,7
 
$this->send_binary_packet($packet, $logged);
 
$this->updateLogHistory('UNKNOWN (61)', 'SSH_MSG_USERAUTH_INFO_RESPONSE');
$this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
 
/*
After receiving the response, the server MUST send either an
2359,9 → 2611,9
// 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.
return $this->keyboard_interactive_process();
case MessageType::USERAUTH_SUCCESS:
case NET_SSH2_MSG_USERAUTH_SUCCESS:
return true;
case MessageType::USERAUTH_FAILURE:
case NET_SSH2_MSG_USERAUTH_FAILURE:
list($auth_methods) = Strings::unpackSSH2('L', $response);
$this->auth_methods_to_continue = $auth_methods;
return false;
2376,6 → 2628,7
* @param string $username
* @param \phpseclib3\System\SSH\Agent $agent
* @return bool
* @access private
*/
private function ssh_agent_login($username, Agent $agent)
{
2400,6 → 2653,7
* @param \phpseclib3\Crypt\Common\PrivateKey $privatekey
* @return bool
* @throws \RuntimeException on connection error
* @access private
*/
private function privatekey_login($username, PrivateKey $privatekey)
{
2464,7 → 2718,7
 
$part1 = Strings::packSSH2(
'Csss',
MessageType::USERAUTH_REQUEST,
NET_SSH2_MSG_USERAUTH_REQUEST,
$username,
'ssh-connection',
'publickey'
2478,21 → 2732,21
 
list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case MessageType::USERAUTH_FAILURE:
case NET_SSH2_MSG_USERAUTH_FAILURE:
list($auth_methods) = Strings::unpackSSH2('L', $response);
$this->auth_methods_to_continue = $auth_methods;
$this->errors[] = 'SSH_MSG_USERAUTH_FAILURE';
return false;
case MessageTypeExtra::USERAUTH_PK_OK:
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
$this->updateLogHistory('SSH_MSG_USERAUTH_INFO_REQUEST', 'SSH_MSG_USERAUTH_PK_OK');
$this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
break;
case MessageType::USERAUTH_SUCCESS:
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
default:
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new ConnectionClosedException('Unexpected response to publickey authentication pt 1');
}
 
2510,17 → 2764,17
 
list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case MessageType::USERAUTH_FAILURE:
case NET_SSH2_MSG_USERAUTH_FAILURE:
// either the login is bad or the server employs multi-factor authentication
list($auth_methods) = Strings::unpackSSH2('L', $response);
$this->auth_methods_to_continue = $auth_methods;
return false;
case MessageType::USERAUTH_SUCCESS:
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
}
 
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new ConnectionClosedException('Unexpected response to publickey authentication pt 2');
}
 
2531,6 → 2785,7
* Setting $timeout to false or 0 will mean there is no timeout.
*
* @param mixed $timeout
* @access public
*/
public function setTimeout($timeout)
{
2543,6 → 2798,7
* Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number.
*
* @param int $interval
* @access public
*/
public function setKeepAlive($interval)
{
2552,6 → 2808,7
/**
* Get the output from stdError
*
* @access public
*/
public function getStdError()
{
2568,6 → 2825,7
* @return string|bool
* @psalm-return ($callback is callable ? bool : string|bool)
* @throws \RuntimeException on connection error
* @access public
*/
public function exec($command, callable $callback = null)
{
2594,7 → 2852,7
 
$packet = Strings::packSSH2(
'CsN3',
MessageType::CHANNEL_OPEN,
NET_SSH2_MSG_CHANNEL_OPEN,
'session',
self::CHANNEL_EXEC,
$this->window_size_server_to_client[self::CHANNEL_EXEC],
2602,15 → 2860,15
);
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_EXEC] = MessageType::CHANNEL_OPEN;
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
 
$this->get_channel_packet(self::CHANNEL_EXEC);
 
if ($this->request_pty === true) {
$terminal_modes = pack('C', TerminalMode::TTY_OP_END);
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
$packet = Strings::packSSH2(
'CNsCsN4s',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_EXEC],
'pty-req',
1,
2624,9 → 2882,9
 
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_EXEC] = MessageType::CHANNEL_REQUEST;
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new \RuntimeException('Unable to request pseudo-terminal');
}
 
2644,7 → 2902,7
// "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
$packet = Strings::packSSH2(
'CNsCs',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_EXEC],
'exec',
1,
2652,13 → 2910,13
);
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_EXEC] = MessageType::CHANNEL_REQUEST;
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
 
if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
return false;
}
 
$this->channel_status[self::CHANNEL_EXEC] = MessageType::CHANNEL_DATA;
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
 
if ($callback === false || $this->in_request_pty_exec) {
return true;
2693,6 → 2951,7
* @return bool
* @throws \UnexpectedValueException on receipt of unexpected packets
* @throws \RuntimeException on other errors
* @access private
*/
private function initShell()
{
2705,7 → 2964,7
 
$packet = Strings::packSSH2(
'CsN3',
MessageType::CHANNEL_OPEN,
NET_SSH2_MSG_CHANNEL_OPEN,
'session',
self::CHANNEL_SHELL,
$this->window_size_server_to_client[self::CHANNEL_SHELL],
2714,14 → 2973,14
 
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_SHELL] = MessageType::CHANNEL_OPEN;
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
 
$this->get_channel_packet(self::CHANNEL_SHELL);
 
$terminal_modes = pack('C', TerminalMode::TTY_OP_END);
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
$packet = Strings::packSSH2(
'CNsbsN4s',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SHELL],
'pty-req',
true, // want reply
2735,7 → 2994,7
 
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_SHELL] = MessageType::CHANNEL_REQUEST;
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
 
if (!$this->get_channel_packet(self::CHANNEL_SHELL)) {
throw new \RuntimeException('Unable to request pty');
2743,7 → 3002,7
 
$packet = Strings::packSSH2(
'CNsb',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SHELL],
'shell',
true // want reply
2755,7 → 3014,7
throw new \RuntimeException('Unable to request shell');
}
 
$this->channel_status[self::CHANNEL_SHELL] = MessageType::CHANNEL_DATA;
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
 
$this->bitmap |= self::MASK_SHELL;
 
2768,6 → 3027,7
* @see self::read()
* @see self::write()
* @return int
* @access public
*/
private function get_interactive_channel()
{
2785,12 → 3045,13
* Return an available open channel
*
* @return int
* @access public
*/
private function get_open_channel()
{
$channel = self::CHANNEL_EXEC;
do {
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == MessageType::CHANNEL_OPEN) {
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
return $channel;
}
} while ($channel++ < self::CHANNEL_SUBSYSTEM);
2802,6 → 3063,7
* Request agent forwarding of remote server
*
* @return bool
* @access public
*/
public function requestAgentForwarding()
{
2812,13 → 3074,13
 
$packet = Strings::packSSH2(
'CNsC',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[$request_channel],
'auth-agent-req@openssh.com',
1
);
 
$this->channel_status[$request_channel] = MessageType::CHANNEL_REQUEST;
$this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
 
$this->send_binary_packet($packet);
 
2826,7 → 3088,7
return false;
}
 
$this->channel_status[$request_channel] = MessageType::CHANNEL_OPEN;
$this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
 
return true;
}
2842,6 → 3104,7
* @param int $mode
* @return string|bool|null
* @throws \RuntimeException on connection error
* @access public
*/
public function read($expect = '', $mode = self::READ_SIMPLE)
{
2915,6 → 3178,7
* @see self::stopSubsystem()
* @param string $subsystem
* @return bool
* @access public
*/
public function startSubsystem($subsystem)
{
2922,7 → 3186,7
 
$packet = Strings::packSSH2(
'CsN3',
MessageType::CHANNEL_OPEN,
NET_SSH2_MSG_CHANNEL_OPEN,
'session',
self::CHANNEL_SUBSYSTEM,
$this->window_size,
2931,13 → 3195,13
 
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_SUBSYSTEM] = MessageType::CHANNEL_OPEN;
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
 
$this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
 
$packet = Strings::packSSH2(
'CNsCs',
MessageType::CHANNEL_REQUEST,
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SUBSYSTEM],
'subsystem',
1,
2945,13 → 3209,13
);
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_SUBSYSTEM] = MessageType::CHANNEL_REQUEST;
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
 
if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
return false;
}
 
$this->channel_status[self::CHANNEL_SUBSYSTEM] = MessageType::CHANNEL_DATA;
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
 
$this->bitmap |= self::MASK_SHELL;
$this->in_subsystem = true;
2964,6 → 3228,7
*
* @see self::startSubsystem()
* @return bool
* @access public
*/
public function stopSubsystem()
{
2977,6 → 3242,7
*
* If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
*
* @access public
*/
public function reset()
{
2988,6 → 3254,7
*
* Did exec() or read() return because they timed out or because they encountered the end?
*
* @access public
*/
public function isTimeout()
{
2997,10 → 3264,11
/**
* Disconnect
*
* @access public
*/
public function disconnect()
{
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
fclose($this->realtime_log_file);
}
3013,6 → 3281,7
* Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
* disconnect().
*
* @access public
*/
public function __destruct()
{
3023,6 → 3292,7
* Is the connection still active?
*
* @return bool
* @access public
*/
public function isConnected()
{
3033,6 → 3303,7
* Have you successfully been logged in?
*
* @return bool
* @access public
*/
public function isAuthenticated()
{
3059,7 → 3330,7
$packet_size = 0x4000;
$packet = Strings::packSSH2(
'CsN3',
MessageType::CHANNEL_OPEN,
NET_SSH2_MSG_CHANNEL_OPEN,
'session',
self::CHANNEL_KEEP_ALIVE,
$this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE],
3069,7 → 3340,7
try {
$this->send_binary_packet($packet);
 
$this->channel_status[self::CHANNEL_KEEP_ALIVE] = MessageType::CHANNEL_OPEN;
$this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN;
 
$response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE);
} catch (\RuntimeException $e) {
3087,7 → 3358,7
*/
private function reconnect()
{
$this->reset_connection(DisconnectReason::CONNECTION_LOST);
$this->reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST);
$this->retry_connect = true;
$this->connect();
foreach ($this->auth as $auth) {
3100,6 → 3371,7
* Resets a connection for re-use
*
* @param int $reason
* @access private
*/
protected function reset_connection($reason)
{
3121,6 → 3393,7
* @see self::_send_binary_packet()
* @param bool $skip_channel_filter
* @return bool|string
* @access private
*/
private function get_binary_packet($skip_channel_filter = false)
{
3136,7 → 3409,7
@stream_select($read, $write, $except, null);
} else {
if (!@stream_select($read, $write, $except, $this->keepAlive)) {
$this->send_binary_packet(pack('CN', MessageType::IGNORE, 0));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
return $this->get_binary_packet(true);
}
}
3150,7 → 3423,7
 
if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) {
if (!@stream_select($read, $write, $except, $this->keepAlive)) {
$this->send_binary_packet(pack('CN', MessageType::IGNORE, 0));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
$elapsed = microtime(true) - $start;
$this->curTimeout -= $elapsed;
return $this->get_binary_packet(true);
3288,7 → 3561,7
if ($this->hmac_check instanceof Hash) {
$hmac = stream_get_contents($this->fsock, $this->hmac_size);
if ($hmac === false || strlen($hmac) != $this->hmac_size) {
$this->disconnect_helper(DisconnectReason::MAC_ERROR);
$this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
throw new \RuntimeException('Error reading socket');
}
 
3298,12 → 3571,12
if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
$this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
if ($hmac != $this->hmac_check->hash($reconstructed)) {
$this->disconnect_helper(DisconnectReason::MAC_ERROR);
$this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
throw new \RuntimeException('Invalid UMAC');
}
} else {
if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
$this->disconnect_helper(DisconnectReason::MAC_ERROR);
$this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
throw new \RuntimeException('Invalid HMAC');
}
}
3350,14 → 3623,9
 
if (defined('NET_SSH2_LOGGING')) {
$current = microtime(true);
$message_number = sprintf(
'<- %s (since last: %s, network: %ss)',
($constantName = MessageType::findConstantNameByValue($value = ord($payload[0])))
? "SSH_MSG_$constantName"
: "UNKNOWN ($value)",
round($current - $this->last_packet, 4),
round($stop - $start, 4)
);
$message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
$message_number = '<- ' . $message_number .
' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
$this->append_log($message_number, $payload);
$this->last_packet = $current;
}
3371,6 → 3639,7
* @see self::get_binary_packet()
* @param int $remaining_length
* @return string
* @access private
*/
private function read_remaining_bytes($remaining_length)
{
3397,7 → 3666,7
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->decryptName : '') && !($this->bitmap & SSH2::MASK_LOGIN)) {
$this->bad_key_size_fix = true;
$this->reset_connection(DisconnectReason::KEY_EXCHANGE_FAILED);
$this->reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
return false;
}
throw new \RuntimeException('Invalid size');
3411,7 → 3680,7
while ($remaining_length > 0) {
$temp = stream_get_contents($this->fsock, $remaining_length);
if ($temp === false || feof($this->fsock)) {
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new \RuntimeException('Error reading from socket');
}
$buffer .= $temp;
3430,28 → 3699,29
* @param string $payload
* @param bool $skip_channel_filter
* @return string|bool
* @access private
*/
private function filter($payload, $skip_channel_filter)
{
switch (ord($payload[0])) {
case MessageType::DISCONNECT:
case NET_SSH2_MSG_DISCONNECT:
Strings::shift($payload, 1);
list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
$this->errors[] = 'SSH_MSG_DISCONNECT: SSH_DISCONNECT_' . DisconnectReason::getConstantNameByValue($reason_code) . "\r\n$message";
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message";
$this->bitmap = 0;
return false;
case MessageType::IGNORE:
case NET_SSH2_MSG_IGNORE:
$payload = $this->get_binary_packet($skip_channel_filter);
break;
case MessageType::DEBUG:
case NET_SSH2_MSG_DEBUG:
Strings::shift($payload, 2); // second byte is "always_display"
list($message) = Strings::unpackSSH2('s', $payload);
$this->errors[] = "SSH_MSG_DEBUG: $message";
$payload = $this->get_binary_packet($skip_channel_filter);
break;
case MessageType::UNIMPLEMENTED:
case NET_SSH2_MSG_UNIMPLEMENTED:
return false;
case MessageType::KEXINIT:
case NET_SSH2_MSG_KEXINIT:
if ($this->session_id !== false) {
if (!$this->key_exchange($payload)) {
$this->bitmap = 0;
3462,7 → 3732,7
}
 
// 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
if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && !is_bool($payload) && ord($payload[0]) == MessageType::USERAUTH_BANNER) {
if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && !is_bool($payload) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
Strings::shift($payload, 1);
list($this->banner_message) = Strings::unpackSSH2('s', $payload);
$payload = $this->get_binary_packet();
3475,21 → 3745,21
}
 
switch (ord($payload[0])) {
case MessageType::CHANNEL_REQUEST:
case NET_SSH2_MSG_CHANNEL_REQUEST:
if (strlen($payload) == 31) {
extract(unpack('cpacket_type/Nchannel/Nlength', $payload));
if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) {
if (ord(substr($payload, 9 + $length))) { // want reply
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_SUCCESS, $this->server_channels[$channel]));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
}
$payload = $this->get_binary_packet($skip_channel_filter);
}
}
break;
case MessageType::CHANNEL_DATA:
case MessageType::CHANNEL_EXTENDED_DATA:
case MessageType::CHANNEL_CLOSE:
case MessageType::CHANNEL_EOF:
case NET_SSH2_MSG_CHANNEL_DATA:
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
case NET_SSH2_MSG_CHANNEL_CLOSE:
case NET_SSH2_MSG_CHANNEL_EOF:
if (!$skip_channel_filter && !empty($this->server_channels)) {
$this->binary_packet_buffer = $payload;
$this->get_channel_packet(true);
3496,20 → 3766,20
$payload = $this->get_binary_packet();
}
break;
case MessageType::GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
Strings::shift($payload, 1);
list($request_name) = Strings::unpackSSH2('s', $payload);
$this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name";
 
try {
$this->send_binary_packet(pack('C', MessageType::REQUEST_FAILURE));
$this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE));
} catch (\RuntimeException $e) {
return $this->disconnect_helper(DisconnectReason::BY_APPLICATION);
return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
 
$payload = $this->get_binary_packet($skip_channel_filter);
break;
case MessageType::CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
Strings::shift($payload, 1);
list($data, $server_channel) = Strings::unpackSSH2('sN', $payload);
switch ($data) {
3531,7 → 3801,7
 
$packet = pack(
'CN4',
MessageType::CHANNEL_OPEN_CONFIRMATION,
NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
$server_channel,
$new_channel,
$packet_size,
3539,7 → 3809,7
);
 
$this->server_channels[$new_channel] = $server_channel;
$this->channel_status[$new_channel] = MessageType::CHANNEL_OPEN_CONFIRMATION;
$this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
$this->send_binary_packet($packet);
}
break;
3546,9 → 3816,9
default:
$packet = Strings::packSSH2(
'CN2ss',
MessageType::CHANNEL_OPEN_FAILURE,
NET_SSH2_MSG_CHANNEL_OPEN_FAILURE,
$server_channel,
ChannelConnectionFailureReason::ADMINISTRATIVELY_PROHIBITED,
NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
'', // description
'' // language tag
);
3556,13 → 3826,13
try {
$this->send_binary_packet($packet);
} catch (\RuntimeException $e) {
return $this->disconnect_helper(DisconnectReason::BY_APPLICATION);
return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
}
 
$payload = $this->get_binary_packet($skip_channel_filter);
break;
case MessageType::CHANNEL_WINDOW_ADJUST:
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
Strings::shift($payload, 1);
list($channel, $window_size) = Strings::unpackSSH2('NN', $payload);
 
3580,6 → 3850,7
*
* Suppress stderr from output
*
* @access public
*/
public function enableQuietMode()
{
3591,6 → 3862,7
*
* Show stderr in output
*
* @access public
*/
public function disableQuietMode()
{
3602,6 → 3874,7
*
* @see self::enableQuietMode()
* @see self::disableQuietMode()
* @access public
* @return bool
*/
public function isQuietModeEnabled()
3612,6 → 3885,7
/**
* Enable request-pty when using exec()
*
* @access public
*/
public function enablePTY()
{
3621,6 → 3895,7
/**
* Disable request-pty when using exec()
*
* @access public
*/
public function disablePTY()
{
3636,6 → 3911,7
*
* @see self::enablePTY()
* @see self::disablePTY()
* @access public
* @return bool
*/
public function isPTYEnabled()
3661,16 → 3937,17
* @param bool $skip_extended
* @return mixed
* @throws \RuntimeException on connection error
* @access private
*/
protected function get_channel_packet($client_channel, $skip_extended = false)
{
if (!empty($this->channel_buffers[$client_channel])) {
switch ($this->channel_status[$client_channel]) {
case MessageType::CHANNEL_REQUEST:
case NET_SSH2_MSG_CHANNEL_REQUEST:
foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
switch (ord($packet[0])) {
case MessageType::CHANNEL_SUCCESS:
case MessageType::CHANNEL_FAILURE:
case NET_SSH2_MSG_CHANNEL_SUCCESS:
case NET_SSH2_MSG_CHANNEL_FAILURE:
unset($this->channel_buffers[$client_channel][$i]);
return substr($packet, 1);
}
3694,7 → 3971,7
return true;
}
if ($response === false) {
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
}
}
3712,13 → 3989,13
if ($this->window_size_server_to_client[$channel] < 0) {
// PuTTY does something more analogous to the following:
//if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
$packet = pack('CNN', MessageType::CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
$packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
$this->send_binary_packet($packet);
$this->window_size_server_to_client[$channel] += $this->window_resize;
}
 
switch ($type) {
case MessageType::CHANNEL_EXTENDED_DATA:
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
/*
if ($client_channel == self::CHANNEL_EXEC) {
$this->send_channel_packet($client_channel, chr(0));
3730,14 → 4007,14
if ($skip_extended || $this->quiet_mode) {
continue 2;
}
if ($client_channel == $channel && $this->channel_status[$channel] == MessageType::CHANNEL_DATA) {
if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) {
return $data;
}
$this->channel_buffers[$channel][] = chr($type) . $data;
 
continue 2;
case MessageType::CHANNEL_REQUEST:
if ($this->channel_status[$channel] == MessageType::CHANNEL_CLOSE) {
case NET_SSH2_MSG_CHANNEL_REQUEST:
if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) {
continue 2;
}
list($value) = Strings::unpackSSH2('s', $response);
3755,10 → 4032,10
$this->errors[count($this->errors) - 1] .= "\r\n$error_message";
}
 
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$client_channel]));
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$channel]));
$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]));
 
$this->channel_status[$channel] = MessageType::CHANNEL_EOF;
$this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
 
continue 3;
case 'exit-status':
3776,9 → 4053,9
}
 
switch ($this->channel_status[$channel]) {
case MessageType::CHANNEL_OPEN:
case NET_SSH2_MSG_CHANNEL_OPEN:
switch ($type) {
case MessageType::CHANNEL_OPEN_CONFIRMATION:
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
list(
$this->server_channels[$channel],
$window_size,
3793,40 → 4070,40
$result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended);
$this->on_channel_open();
return $result;
case MessageType::CHANNEL_OPEN_FAILURE:
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new \RuntimeException('Unable to open channel');
default:
if ($client_channel == $channel) {
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new \RuntimeException('Unexpected response to open request');
}
return $this->get_channel_packet($client_channel, $skip_extended);
}
break;
case MessageType::CHANNEL_REQUEST:
case NET_SSH2_MSG_CHANNEL_REQUEST:
switch ($type) {
case MessageType::CHANNEL_SUCCESS:
case NET_SSH2_MSG_CHANNEL_SUCCESS:
return true;
case MessageType::CHANNEL_FAILURE:
case NET_SSH2_MSG_CHANNEL_FAILURE:
return false;
case MessageType::CHANNEL_DATA:
case NET_SSH2_MSG_CHANNEL_DATA:
list($data) = Strings::unpackSSH2('s', $response);
$this->channel_buffers[$channel][] = chr($type) . $data;
return $this->get_channel_packet($client_channel, $skip_extended);
default:
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new \RuntimeException('Unable to fulfill channel request');
}
case MessageType::CHANNEL_CLOSE:
return $type == MessageType::CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
case NET_SSH2_MSG_CHANNEL_CLOSE:
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
}
}
 
// ie. $this->channel_status[$channel] == SSHMsg::CHANNEL_DATA
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
 
switch ($type) {
case MessageType::CHANNEL_DATA:
case NET_SSH2_MSG_CHANNEL_DATA:
/*
if ($channel == self::CHANNEL_EXEC) {
// SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
3851,25 → 4128,25
}
$this->channel_buffers[$channel][] = chr($type) . $data;
break;
case MessageType::CHANNEL_CLOSE:
case NET_SSH2_MSG_CHANNEL_CLOSE:
$this->curTimeout = 5;
 
if ($this->bitmap & self::MASK_SHELL) {
$this->bitmap &= ~self::MASK_SHELL;
}
if ($this->channel_status[$channel] != MessageType::CHANNEL_EOF) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$channel]));
if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
}
 
$this->channel_status[$channel] = MessageType::CHANNEL_CLOSE;
$this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
if ($client_channel == $channel) {
return true;
}
// fall-through
case MessageType::CHANNEL_EOF:
case NET_SSH2_MSG_CHANNEL_EOF:
break;
default:
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
throw new \RuntimeException("Error reading channel data ($type)");
}
}
3884,6 → 4161,7
* @param string $logged
* @see self::_get_binary_packet()
* @return void
* @access private
*/
protected function send_binary_packet($data, $logged = null)
{
4008,14 → 4286,9
 
if (defined('NET_SSH2_LOGGING')) {
$current = microtime(true);
$message_number = sprintf(
'-> %s (since last: %s, network: %ss)',
($constantName = MessageType::findConstantNameByValue($value = ord($logged[0]), false))
? "SSH_MSG_$constantName"
: "UNKNOWN ($value)",
round($current - $this->last_packet, 4),
round($stop - $start, 4)
);
$message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
$message_number = '-> ' . $message_number .
' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
$this->append_log($message_number, $logged);
$this->last_packet = $current;
}
4033,13 → 4306,10
*
* @param string $message_number
* @param string $message
* @access private
*/
private function append_log($message_number, $message)
{
if (!defined('NET_SSH2_LOGGING')) {
return;
}
 
// remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
if (strlen($message_number) > 2) {
Strings::shift($message);
4137,7 → 4407,7
$temp = Strings::shift($data, $max_size);
$packet = Strings::packSSH2(
'CNs',
MessageType::CHANNEL_DATA,
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$client_channel],
$temp
);
4156,18 → 4426,19
* @param int $client_channel
* @param bool $want_reply
* @return void
* @access private
*/
private function close_channel($client_channel, $want_reply = false)
{
// see http://tools.ietf.org/html/rfc4254#section-5.3
 
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$client_channel]));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
 
if (!$want_reply) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$client_channel]));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
}
 
$this->channel_status[$client_channel] = MessageType::CHANNEL_CLOSE;
$this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
 
$this->curTimeout = 5;
 
4179,7 → 4450,7
}
 
if ($want_reply) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$client_channel]));
$this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
}
 
if ($this->bitmap & self::MASK_SHELL) {
4192,11 → 4463,12
*
* @param int $reason
* @return false
* @access protected
*/
protected function disconnect_helper($reason)
{
if ($this->bitmap & self::MASK_CONNECTED) {
$data = Strings::packSSH2('CNss', MessageType::DISCONNECT, $reason, '', '');
$data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
try {
$this->send_binary_packet($data);
} catch (\Exception $e) {
4212,10 → 4484,34
}
 
/**
* 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.
*
* @param mixed[] ...$args
* @access protected
*/
protected function define_array(...$args)
{
foreach ($args as $arg) {
foreach ($arg as $key => $value) {
if (!defined($value)) {
define($value, $key);
} else {
break 2;
}
}
}
}
 
/**
* Returns a log of the packets that have been sent and received.
*
* 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')
*
* @access public
* @return array|false|string
*/
public function getLog()
4240,6 → 4536,7
*
* @param array $message_log
* @param array $message_number_log
* @access private
* @return string
*/
protected function format_log($message_log, $message_number_log)
4277,6 → 4574,7
* of said channel opening. Must be called after
* channel open confirmation received
*
* @access private
*/
private function on_channel_open()
{
4292,6 → 4590,7
* @param array $array1
* @param array $array2
* @return mixed False if intersection is empty, else intersected value.
* @access private
*/
private static function array_intersect_first($array1, $array2)
{
4307,6 → 4606,7
* Returns all errors
*
* @return string[]
* @access public
*/
public function getErrors()
{
4317,6 → 4617,7
* Returns the last error
*
* @return string
* @access public
*/
public function getLastError()
{
4331,6 → 4632,7
* Return the server identification.
*
* @return string|false
* @access public
*/
public function getServerIdentification()
{
4343,6 → 4645,7
* Returns a list of algorithms the server supports
*
* @return array
* @access public
*/
public function getServerAlgorithms()
{
4370,6 → 4673,7
* Returns a list of KEX algorithms that phpseclib supports
*
* @return array
* @access public
*/
public static function getSupportedKEXAlgorithms()
{
4406,6 → 4710,7
* Returns a list of host key algorithms that phpseclib supports
*
* @return array
* @access public
*/
public static function getSupportedHostKeyAlgorithms()
{
4425,6 → 4730,7
* Returns a list of symmetric key algorithms that phpseclib supports
*
* @return array
* @access public
*/
public static function getSupportedEncryptionAlgorithms()
{
4444,7 → 4750,7
'aes192-ctr', // RECOMMENDED AES with 192-bit key
'aes256-ctr', // RECOMMENDED AES with 256-bit key
 
// from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>:
// 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
4529,6 → 4835,7
* Returns a list of MAC algorithms that phpseclib supports
*
* @return array
* @access public
*/
public static function getSupportedMACAlgorithms()
{
4559,6 → 4866,7
* Returns a list of compression algorithms that phpseclib supports
*
* @return array
* @access public
*/
public static function getSupportedCompressionAlgorithms()
{
4576,6 → 4884,7
* Uses the same format as https://www.php.net/ssh2-methods-negotiated
*
* @return array
* @access public
*/
public function getAlgorithmsNegotiated()
{
4607,6 → 4916,7
* Allows you to set the terminal
*
* @param string $term
* @access public
*/
public function setTerminal($term)
{
4618,6 → 4928,7
* <https://www.php.net/manual/en/function.ssh2-connect.php>
*
* @param array $methods
* @access public
*/
public function setPreferredAlgorithms(array $methods)
{
4704,6 → 5015,7
* authentication may be relevant for getting legal protection."
*
* @return string
* @access public
*/
public function getBannerMessage()
{
4719,6 → 5031,7
* @return string|false
* @throws \RuntimeException on badly formatted keys
* @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format
* @access public
*/
public function getServerPublicHostKey()
{
4788,12 → 5101,12
$key = $key->withHash($hash);
break;
default:
$this->disconnect_helper(DisconnectReason::HOST_KEY_NOT_VERIFIABLE);
$this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
throw new NoSupportedAlgorithmsException('Unsupported signature format');
}
 
if (!$key->verify($this->exchange_hash, $signature)) {
return $this->disconnect_helper(DisconnectReason::HOST_KEY_NOT_VERIFIABLE);
return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
};
 
return $this->signature_format . ' ' . $server_public_host_key;
4803,6 → 5116,7
* Returns the exit status of an SSH command or false.
*
* @return false|int
* @access public
*/
public function getExitStatus()
{
4816,6 → 5130,7
* Returns the number of columns for the terminal window size.
*
* @return int
* @access public
*/
public function getWindowColumns()
{
4826,6 → 5141,7
* Returns the number of rows for the terminal window size.
*
* @return int
* @access public
*/
public function getWindowRows()
{
4836,6 → 5152,7
* Sets the number of columns for the terminal window size.
*
* @param int $value
* @access public
*/
public function setWindowColumns($value)
{
4846,6 → 5163,7
* Sets the number of rows for the terminal window size.
*
* @param int $value
* @access public
*/
public function setWindowRows($value)
{
4857,6 → 5175,7
*
* @param int $columns
* @param int $rows
* @access public
*/
public function setWindowSize($columns = 80, $rows = 24)
{
4868,6 → 5187,7
* To String Magic Method
*
* @return string
* @access public
*/
#[\ReturnTypeWillChange]
public function __toString()
4923,11 → 5243,12
return $temp;
}
 
/**
/*
* Update packet types in log history
*
* @param string $old
* @param string $new
* @access private
*/
private function updateLogHistory($old, $new)
{