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->is_timeout = true;
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->is_timeout = true;
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 ($client_channel == -1 && $response === true) {
return true;
}
if (!strlen($response)) {
return '';
}
extract(unpack('Ctype', $this->_string_shift($response, 1)));
if ($type == NET_SSH2_MSG_CHANNEL_OPEN) {
extract(unpack('Nlength', $this->_string_shift($response, 4)));
} else {
extract(unpack('Nchannel', $this->_string_shift($response, 4)));
}
// will not be setup yet on incoming channel open request
if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
$this->window_size_server_to_client[$channel] -= strlen($response);
// resize the window, if appropriate
if ($this->window_size_server_to_client[$channel] < 0) {
$packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size);
if (!$this->_send_binary_packet($packet)) {
return false;
}
$this->window_size_server_to_client[$channel] += $this->window_size;
}
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;
extract(unpack('Nwindow_size', $this->_string_shift($response, 4)));
if ($window_size < 0) {
$window_size &= 0x7fffffff;
$window_size += 2147483648.0;
}
$this->window_size_client_to_server[$channel] = $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'];
$result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
$this->_on_channel_open();
return $result;
//case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
//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);
}
}
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
switch ($type) {
case NET_SSH2_MSG_CHANNEL_DATA:
/*
if ($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($channel, chr(0));
}
*/
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$data = $this->_string_shift($response, $length);
if ($channel == NET_SSH2_CHANNEL_AGENT_FORWARD) {
$agent_response = $this->agent->_forward_data($data);
if (!is_bool($agent_response)) {
$this->_send_channel_packet($channel, $agent_response);
}
break;
}
if ($client_channel == $channel) {
return $data;
}
if (!isset($this->channel_buffers[$channel])) {
$this->channel_buffers[$channel] = array();
}
$this->channel_buffers[$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[$channel])) {
$this->channel_buffers[$channel] = array();
}
$this->channel_buffers[$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);
}
$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;
break;
case 'exit-status':
extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5)));
$this->exit_status = $exit_status;
// "The client MAY ignore these messages."
// -- http://tools.ietf.org/html/rfc4254#section-6.10
break;
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);
}
}
}