,
// "implementations SHOULD check that the packet length is reasonable"
// PuTTY uses 0x9000 as the actual max packet size and so to shall we
if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
user_error('Invalid size');
return false;
}
$buffer = '';
while ($remaining_length > 0) {
$temp = fread($this->fsock, $remaining_length);
$buffer.= $temp;
$remaining_length-= strlen($temp);
}
$stop = strtok(microtime(), ' ') + strtok('');
if (strlen($buffer)) {
$raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
}
$payload = $this->_string_shift($raw, $packet_length - $padding_length - 1);
$padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty
if ($this->hmac_check !== false) {
$hmac = fread($this->fsock, $this->hmac_size);
if ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
user_error('Invalid HMAC');
return false;
}
}
//if ($this->decompress) {
// $payload = gzinflate(substr($payload, 2));
//}
$this->get_seq_no++;
if (defined('NET_SSH2_LOGGING')) {
$current = strtok(microtime(), ' ') + strtok('');
$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;
}
return $this->_filter($payload);
}
/**
* Filter Binary Packets
*
* Because some binary packets need to be ignored...
*
* @see Net_SSH2::_get_binary_packet()
* @return String
* @access private
*/
function _filter($payload)
{
switch (ord($payload[0])) {
case NET_SSH2_MSG_DISCONNECT:
$this->_string_shift($payload, 1);
extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length));
$this->bitmask = 0;
return false;
case NET_SSH2_MSG_IGNORE:
$payload = $this->_get_binary_packet();
break;
case NET_SSH2_MSG_DEBUG:
$this->_string_shift($payload, 2);
extract(unpack('Nlength', $this->_string_shift($payload, 4)));
$this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length));
$payload = $this->_get_binary_packet();
break;
case NET_SSH2_MSG_UNIMPLEMENTED:
return false;
case NET_SSH2_MSG_KEXINIT:
if ($this->session_id !== false) {
if (!$this->_key_exchange($payload)) {
$this->bitmask = 0;
return false;
}
$payload = $this->_get_binary_packet();
}
}
// 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 & NET_SSH2_MASK_CONSTRUCTOR) && !($this->bitmap & NET_SSH2_MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
$this->_string_shift($payload, 1);
extract(unpack('Nlength', $this->_string_shift($payload, 4)));
$this->banner_message = utf8_decode($this->_string_shift($payload, $length));
$payload = $this->_get_binary_packet();
}
// only called when we've already logged in
if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && ($this->bitmap & NET_SSH2_MASK_LOGIN)) {
switch (ord($payload[0])) {
case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
$this->_string_shift($payload, 1);
extract(unpack('Nlength', $this->_string_shift($payload)));
$this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . utf8_decode($this->_string_shift($payload, $length));
if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) {
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
$payload = $this->_get_binary_packet();
break;
case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
$this->_string_shift($payload, 1);
extract(unpack('N', $this->_string_shift($payload, 4)));
$this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length));
$this->_string_shift($payload, 4); // skip over client channel
extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
$packet = pack('CN3a*Na*',
NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, '');
if (!$this->_send_binary_packet($packet)) {
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
$payload = $this->_get_binary_packet();
break;
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
$payload = $this->_get_binary_packet();
}
}
return $payload;
}
/**
* Enable Quiet Mode
*
* Suppress stderr from output
*
* @access public
*/
function enableQuietMode()
{
$this->quiet_mode = true;
}
/**
* Disable Quiet Mode
*
* Show stderr in output
*
* @access public
*/
function disableQuietMode()
{
$this->quiet_mode = false;
}
/**
* Enable request-pty when using exec()
*
* @access public
*/
function enablePTY()
{
$this->request_pty = true;
}
/**
* Disable request-pty when using exec()
*
* @access public
*/
function disablePTY()
{
$this->request_pty = false;
}
/**
* Gets channel data
*
* Returns the data as a string if it's available and false if not.
*
* @param $client_channel
* @return Mixed
* @access private
*/
function _get_channel_packet($client_channel, $skip_extended = false)
{
if (!empty($this->channel_buffers[$client_channel])) {
return array_shift($this->channel_buffers[$client_channel]);
}
while (true) {
if ($this->curTimeout) {
if ($this->curTimeout < 0) {
$this->_close_channel($client_channel);
return true;
}
$read = array($this->fsock);
$write = $except = NULL;
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
$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->_close_channel($client_channel);
return true;
}
$elapsed = strtok(microtime(), ' ') + strtok('') - $start;
$this->curTimeout-= $elapsed;
}
$response = $this->_get_binary_packet();
if ($response === false) {
user_error('Connection closed by server');
return false;
}
if (!strlen($response)) {
return '';
}
// resize the window, if appropriate
$this->window_size_server_to_client[$client_channel]-= strlen($response);
if ($this->window_size_server_to_client[$client_channel] < 0) {
$packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$client_channel], $this->window_size);
if (!$this->_send_binary_packet($packet)) {
return false;
}
$this->window_size_server_to_client[$client_channel]+= $this->window_size;
}
extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5)));
switch ($this->channel_status[$channel]) {
case NET_SSH2_MSG_CHANNEL_OPEN:
switch ($type) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
extract(unpack('Nserver_channel', $this->_string_shift($response, 4)));
$this->server_channels[$channel] = $server_channel;
$this->_string_shift($response, 4); // skip over (server) window size
$temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
$this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
//case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
default:
user_error('Unable to open channel');
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
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:
user_error('Unable to fulfill channel request');
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
case NET_SSH2_MSG_CHANNEL_CLOSE:
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
}
switch ($type) {
case NET_SSH2_MSG_CHANNEL_DATA:
/*
if ($client_channel == NET_SSH2_CHANNEL_EXEC) {
// SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
// this actually seems to make things twice as fast. more to the point, the message right after
// 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.
$this->_send_channel_packet($client_channel, chr(0));
}
*/
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$data = $this->_string_shift($response, $length);
if ($client_channel == $channel) {
return $data;
}
if (!isset($this->channel_buffers[$client_channel])) {
$this->channel_buffers[$client_channel] = array();
}
$this->channel_buffers[$client_channel][] = $data;
break;
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
/*
if ($client_channel == NET_SSH2_CHANNEL_EXEC) {
$this->_send_channel_packet($client_channel, chr(0));
}
*/
// currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8)));
$data = $this->_string_shift($response, $length);
$this->stdErrorLog .= $data;
if ($skip_extended || $this->quiet_mode) {
break;
}
if ($client_channel == $channel) {
return $data;
}
if (!isset($this->channel_buffers[$client_channel])) {
$this->channel_buffers[$client_channel] = array();
}
$this->channel_buffers[$client_channel][] = $data;
break;
case NET_SSH2_MSG_CHANNEL_REQUEST:
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$value = $this->_string_shift($response, $length);
switch ($value) {
case 'exit-signal':
$this->_string_shift($response, 1);
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length);
$this->_string_shift($response, 1);
extract(unpack('Nlength', $this->_string_shift($response, 4)));
if ($length) {
$this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length);
}
case 'exit-status':
extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5)));
$this->exit_status = $exit_status;
// "The channel needs to be closed with SSH_MSG_CHANNEL_CLOSE after this message."
// -- http://tools.ietf.org/html/rfc4254#section-6.10
$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] = NET_SSH2_MSG_CHANNEL_EOF;
default:
// "Some systems may not implement signals, in which case they SHOULD ignore this message."
// -- http://tools.ietf.org/html/rfc4254#section-6.9
break;
}
break;
case NET_SSH2_MSG_CHANNEL_CLOSE:
$this->curTimeout = 0;
if ($this->bitmap & NET_SSH2_MASK_SHELL) {
$this->bitmap&= ~NET_SSH2_MASK_SHELL;
}
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] = NET_SSH2_MSG_CHANNEL_CLOSE;
return true;
case NET_SSH2_MSG_CHANNEL_EOF:
break;
default:
user_error('Error reading channel data');
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
}
}
/**
* Sends Binary Packets
*
* See '6. Binary Packet Protocol' of rfc4253 for more info.
*
* @param String $data
* @see Net_SSH2::_get_binary_packet()
* @return Boolean
* @access private
*/
function _send_binary_packet($data)
{
if (!is_resource($this->fsock) || feof($this->fsock)) {
user_error('Connection closed prematurely');
$this->bitmask = 0;
return false;
}
//if ($this->compress) {
// // the -4 removes the checksum:
// // http://php.net/function.gzcompress#57710
// $data = substr(gzcompress($data), 0, -4);
//}
// 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
$packet_length = strlen($data) + 9;
// round up to the nearest $this->encrypt_block_size
$packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen($data) - 5;
$padding = crypt_random_string($padding_length);
// 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);
$hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';
$this->send_seq_no++;
if ($this->encrypt !== false) {
$packet = $this->encrypt->encrypt($packet);
}
$packet.= $hmac;
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
$result = strlen($packet) == fputs($this->fsock, $packet);
$stop = strtok(microtime(), ' ') + strtok('');
if (defined('NET_SSH2_LOGGING')) {
$current = strtok(microtime(), ' ') + strtok('');
$message_number = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN (' . ord($data[0]) . ')';
$message_number = '-> ' . $message_number .
' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
$this->_append_log($message_number, $data);
$this->last_packet = $current;
}
return $result;
}
/**
* Logs data packets
*
* Makes sure that only the last 1MB worth of packets will be logged
*
* @param String $data
* @access private
*/
function _append_log($message_number, $message)
{
switch (NET_SSH2_LOGGING) {
// useful for benchmarks
case NET_SSH2_LOG_SIMPLE:
$this->message_number_log[] = $message_number;
break;
// the most useful log for SSH2
case NET_SSH2_LOG_COMPLEX:
$this->message_number_log[] = $message_number;
$this->_string_shift($message);
$this->log_size+= strlen($message);
$this->message_log[] = $message;
while ($this->log_size > NET_SSH2_LOG_MAX_SIZE) {
$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
case NET_SSH2_LOG_REALTIME:
echo "\r\n" . $this->_format_log(array($message), array($message_number)) . "\r\n
\r\n";
@flush();
@ob_flush();
break;
// basically the same thing as NET_SSH2_LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILE
// needs to be defined and that the resultant log file will be capped out at NET_SSH2_LOG_MAX_SIZE.
// 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
case NET_SSH2_LOG_REALTIME_FILE:
if (!isset($this->realtime_log_file)) {
// PHP doesn't seem to like using constants in fopen()
$filename = NET_SSH2_LOG_REALTIME_FILENAME;
$fp = fopen($filename, 'w');
$this->realtime_log_file = $fp;
}
if (!is_resource($this->realtime_log_file)) {
break;
}
$entry = $this->_format_log(array($message), array($message_number));
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);
if ($this->realtime_log_size > NET_SSH2_LOG_MAX_SIZE) {
fseek($this->realtime_log_file, 0);
$this->realtime_log_size = strlen($entry);
$this->realtime_log_wrap = true;
}
fputs($this->realtime_log_file, $entry);
}
}
/**
* Sends channel data
*
* Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
*
* @param Integer $client_channel
* @param String $data
* @return Boolean
* @access private
*/
function _send_channel_packet($client_channel, $data)
{
while (strlen($data) > $this->packet_size_client_to_server[$client_channel]) {
$packet = pack('CN2a*',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$client_channel],
$this->packet_size_client_to_server[$client_channel],
$this->_string_shift($data, $this->packet_size_client_to_server[$client_channel])
);
if (!$this->_send_binary_packet($packet)) {
return false;
}
}
return $this->_send_binary_packet(pack('CN2a*',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$client_channel],
strlen($data),
$data));
}
/**
* Closes and flushes a channel
*
* Net_SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server
* and for SFTP channels are presumably closed when the client disconnects. This functions is intended
* for SCP more than anything.
*
* @param Integer $client_channel
* @return Boolean
* @access private
*/
function _close_channel($client_channel)
{
// see http://tools.ietf.org/html/rfc4254#section-5.3
$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[$client_channel]));
$this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
$this->curTimeout = 0;
while (!is_bool($this->_get_channel_packet($client_channel)));
if ($this->bitmap & NET_SSH2_MASK_SHELL) {
$this->bitmap&= ~NET_SSH2_MASK_SHELL;
}
}
/**
* Disconnect
*
* @param Integer $reason
* @return Boolean
* @access private
*/
function _disconnect($reason)
{
if ($this->bitmap) {
$data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
$this->_send_binary_packet($data);
$this->bitmap = 0;
fclose($this->fsock);
return false;
}
}
/**
* String Shift
*
* Inspired by array_shift
*
* @param String $string
* @param optional Integer $index
* @return String
* @access private
*/
function _string_shift(&$string, $index = 1)
{
$substr = substr($string, 0, $index);
$string = substr($string, $index);
return $substr;
}
/**
* 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 Array $array
* @access private
*/
function _define_array()
{
$args = func_get_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 == NET_SSH2_LOG_COMPLEX, an array if NET_SSH2_LOGGING == NET_SSH2_LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')
*
* @access public
* @return String or Array
*/
function getLog()
{
if (!defined('NET_SSH2_LOGGING')) {
return false;
}
switch (NET_SSH2_LOGGING) {
case NET_SSH2_LOG_SIMPLE:
return $this->message_number_log;
break;
case NET_SSH2_LOG_COMPLEX:
return $this->_format_log($this->message_log, $this->message_number_log);
break;
default:
return false;
}
}
/**
* Formats a log for printing
*
* @param Array $message_log
* @param Array $message_number_log
* @access private
* @return String
*/
function _format_log($message_log, $message_number_log)
{
static $boundary = ':', $long_width = 65, $short_width = 16;
$output = '';
for ($i = 0; $i < count($message_log); $i++) {
$output.= $message_number_log[$i] . "\r\n";
$current_log = $message_log[$i];
$j = 0;
do {
if (strlen($current_log)) {
$output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
}
$fragment = $this->_string_shift($current_log, $short_width);
$hex = substr(
preg_replace(
'#(.)#es',
'"' . $boundary . '" . str_pad(dechex(ord(substr("\\1", -1))), 2, "0", STR_PAD_LEFT)',
$fragment),
strlen($boundary)
);
// replace non ASCII printable characters with dots
// http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
// also replace < with a . since < messes up the output on web browsers
$raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
$output.= str_pad($hex, $long_width - $short_width, ' ') . $raw . "\r\n";
$j++;
} while (strlen($current_log));
$output.= "\r\n";
}
return $output;
}
/**
* Returns all errors
*
* @return String
* @access public
*/
function getErrors()
{
return $this->errors;
}
/**
* Returns the last error
*
* @return String
* @access public
*/
function getLastError()
{
return $this->errors[count($this->errors) - 1];
}
/**
* Return the server identification.
*
* @return String
* @access public
*/
function getServerIdentification()
{
return $this->server_identifier;
}
/**
* Return a list of the key exchange algorithms the server supports.
*
* @return Array
* @access public
*/
function getKexAlgorithms()
{
return $this->kex_algorithms;
}
/**
* Return a list of the host key (public key) algorithms the server supports.
*
* @return Array
* @access public
*/
function getServerHostKeyAlgorithms()
{
return $this->server_host_key_algorithms;
}
/**
* Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client.
*
* @return Array
* @access public
*/
function getEncryptionAlgorithmsClient2Server()
{
return $this->encryption_algorithms_client_to_server;
}
/**
* Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client.
*
* @return Array
* @access public
*/
function getEncryptionAlgorithmsServer2Client()
{
return $this->encryption_algorithms_server_to_client;
}
/**
* Return a list of the MAC algorithms the server supports, when receiving stuff from the client.
*
* @return Array
* @access public
*/
function getMACAlgorithmsClient2Server()
{
return $this->mac_algorithms_client_to_server;
}
/**
* Return a list of the MAC algorithms the server supports, when sending stuff to the client.
*
* @return Array
* @access public
*/
function getMACAlgorithmsServer2Client()
{
return $this->mac_algorithms_server_to_client;
}
/**
* Return a list of the compression algorithms the server supports, when receiving stuff from the client.
*
* @return Array
* @access public
*/
function getCompressionAlgorithmsClient2Server()
{
return $this->compression_algorithms_client_to_server;
}
/**
* Return a list of the compression algorithms the server supports, when sending stuff to the client.
*
* @return Array
* @access public
*/
function getCompressionAlgorithmsServer2Client()
{
return $this->compression_algorithms_server_to_client;
}
/**
* Return a list of the languages the server supports, when sending stuff to the client.
*
* @return Array
* @access public
*/
function getLanguagesServer2Client()
{
return $this->languages_server_to_client;
}
/**
* Return a list of the languages the server supports, when receiving stuff from the client.
*
* @return Array
* @access public
*/
function getLanguagesClient2Server()
{
return $this->languages_client_to_server;
}
/**
* Returns the banner message.
*
* Quoting from the RFC, "in some jurisdictions, sending a warning message before
* authentication may be relevant for getting legal protection."
*
* @return String
* @access public
*/
function getBannerMessage()
{
return $this->banner_message;
}
/**
* Returns the server public host key.
*
* Caching this the first time you connect to a server and checking the result on subsequent connections
* is recommended. Returns false if the server signature is not signed correctly with the public host key.
*
* @return Mixed
* @access public
*/
function getServerPublicHostKey()
{
$signature = $this->signature;
$server_public_host_key = $this->server_public_host_key;
extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4)));
$this->_string_shift($server_public_host_key, $length);
if ($this->signature_validated) {
return $this->bitmap ?
$this->signature_format . ' ' . base64_encode($this->server_public_host_key) :
false;
}
$this->signature_validated = true;
switch ($this->signature_format) {
case 'ssh-dss':
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$p = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$q = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$g = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$y = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
/* The value for 'dss_signature_blob' is encoded as a string containing
r, followed by s (which are 160-bit integers, without lengths or
padding, unsigned, and in network byte order). */
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
if ($temp['length'] != 40) {
user_error('Invalid signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
}
$r = new Math_BigInteger($this->_string_shift($signature, 20), 256);
$s = new Math_BigInteger($this->_string_shift($signature, 20), 256);
if ($r->compare($q) >= 0 || $s->compare($q) >= 0) {
user_error('Invalid signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
}
$w = $s->modInverse($q);
$u1 = $w->multiply(new Math_BigInteger(sha1($this->exchange_hash), 16));
list(, $u1) = $u1->divide($q);
$u2 = $w->multiply($r);
list(, $u2) = $u2->divide($q);
$g = $g->modPow($u1, $p);
$y = $y->modPow($u2, $p);
$v = $g->multiply($y);
list(, $v) = $v->divide($p);
list(, $v) = $v->divide($q);
if (!$v->equals($r)) {
user_error('Bad server signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
}
break;
case 'ssh-rsa':
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$e = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$nLength = $temp['length'];
/*
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
$signature = $this->_string_shift($signature, $temp['length']);
if (!class_exists('Crypt_RSA')) {
require_once('Crypt/RSA.php');
}
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->loadKey(array('e' => $e, 'n' => $n), CRYPT_RSA_PUBLIC_FORMAT_RAW);
if (!$rsa->verify($this->exchange_hash, $signature)) {
user_error('Bad server signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
}
*/
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
$s = new Math_BigInteger($this->_string_shift($signature, $temp['length']), 256);
// validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the
// following URL:
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
// also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.
if ($s->compare(new Math_BigInteger()) < 0 || $s->compare($n->subtract(new Math_BigInteger(1))) > 0) {
user_error('Invalid signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
}
$s = $s->modPow($e, $n);
$s = $s->toBytes();
$h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash));
$h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 3 - strlen($h)) . $h;
if ($s != $h) {
user_error('Bad server signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
}
break;
default:
user_error('Unsupported signature format');
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
}
return $this->signature_format . ' ' . base64_encode($this->server_public_host_key);
}
/**
* Returns the exit status of an SSH command or false.
*
* @return Integer or false
* @access public
*/
function getExitStatus()
{
if (is_null($this->exit_status)) {
return false;
}
return $this->exit_status;
}
}