1088,6 → 1088,21 |
private $smartMFA = true; |
|
/** |
* How many channels are currently opened |
* |
* @var int |
*/ |
private $channelCount = 0; |
|
/** |
* Does the server support multiple channels? If not then error out |
* when multiple channels are attempted to be opened |
* |
* @var bool |
*/ |
private $errorOnMultipleChannels; |
|
/** |
* Default Constructor. |
* |
* $host can either be a string, representing the host, or a stream resource. |
1384,6 → 1399,18 |
throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); |
} |
|
// Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see |
// https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info. |
// https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses |
// when consolekit was incorporated. |
// https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the |
// issues with how Ubuntu incorporated consolekit |
$pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#'; |
$match = preg_match($pattern, $this->server_identifier, $matches); |
$match = $match && version_compare('5.8', $matches[1], '<='); |
$match = $match && version_compare('6.9', $matches[1], '>='); |
$this->errorOnMultipleChannels = $match; |
|
if (!$this->send_id_string_first) { |
fputs($this->fsock, $this->identifier . "\r\n"); |
} |
2725,33 → 2752,12 |
return false; |
} |
|
if ($this->isPTYOpen()) { |
throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); |
} |
//if ($this->isPTYOpen()) { |
// throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); |
//} |
|
// RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to |
// be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, |
// honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. |
// see http://tools.ietf.org/html/rfc4254#section-5.2 for more info |
$this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size; |
// 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy |
// uses 0x4000, that's what will be used here, as well. |
$packet_size = 0x4000; |
$this->openChannel(self::CHANNEL_EXEC); |
|
$packet = Strings::packSSH2( |
'CsN3', |
NET_SSH2_MSG_CHANNEL_OPEN, |
'session', |
self::CHANNEL_EXEC, |
$this->window_size_server_to_client[self::CHANNEL_EXEC], |
$packet_size |
); |
$this->send_binary_packet($packet); |
|
$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', NET_SSH2_TTY_OP_END); |
$packet = Strings::packSSH2( |
2831,30 → 2837,41 |
} |
|
/** |
* Creates an interactive shell |
* How many channels are currently open? |
* |
* Returns bool(true) if the shell was opened. |
* Returns bool(false) if the shell was already open. |
* @return int |
*/ |
public function getOpenChannelCount() |
{ |
return $this->channelCount; |
} |
|
/** |
* Opens a channel |
* |
* @see self::isShellOpen() |
* @see self::read() |
* @see self::write() |
* @param string $channel |
* @param bool $skip_extended |
* @return bool |
* @throws InsufficientSetupException if not authenticated |
* @throws \UnexpectedValueException on receipt of unexpected packets |
* @throws \RuntimeException on other errors |
*/ |
public function openShell() |
protected function openChannel($channel, $skip_extended = false) |
{ |
if ($this->isShellOpen()) { |
return false; |
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) { |
throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again'); |
} |
|
if (!$this->isAuthenticated()) { |
throw new InsufficientSetupException('Operation disallowed prior to login()'); |
$this->channelCount++; |
|
if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { |
throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels"); |
} |
|
$this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; |
// RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to |
// be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, |
// honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. |
// see http://tools.ietf.org/html/rfc4254#section-5.2 for more info |
$this->window_size_server_to_client[$channel] = $this->window_size; |
// 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy |
// uses 0x4000, that's what will be used here, as well. |
$packet_size = 0x4000; |
|
$packet = Strings::packSSH2( |
2861,17 → 2878,40 |
'CsN3', |
NET_SSH2_MSG_CHANNEL_OPEN, |
'session', |
self::CHANNEL_SHELL, |
$this->window_size_server_to_client[self::CHANNEL_SHELL], |
$channel, |
$this->window_size_server_to_client[$channel], |
$packet_size |
); |
|
$this->send_binary_packet($packet); |
|
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; |
$this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN; |
|
$this->get_channel_packet(self::CHANNEL_SHELL); |
return $this->get_channel_packet($channel, $skip_extended); |
} |
|
/** |
* Creates an interactive shell |
* |
* Returns bool(true) if the shell was opened. |
* Returns bool(false) if the shell was already open. |
* |
* @see self::isShellOpen() |
* @see self::read() |
* @see self::write() |
* @return bool |
* @throws InsufficientSetupException if not authenticated |
* @throws \UnexpectedValueException on receipt of unexpected packets |
* @throws \RuntimeException on other errors |
*/ |
public function openShell() |
{ |
if (!$this->isAuthenticated()) { |
throw new InsufficientSetupException('Operation disallowed prior to login()'); |
} |
|
$this->openChannel(self::CHANNEL_SHELL); |
|
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END); |
$packet = Strings::packSSH2( |
'CNsbsN4s', |
3023,6 → 3063,10 |
*/ |
public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null) |
{ |
if (!$this->isAuthenticated()) { |
throw new InsufficientSetupException('Operation disallowed prior to login()'); |
} |
|
$this->curTimeout = $this->timeout; |
$this->is_timeout = false; |
|
3030,7 → 3074,7 |
$channel = $this->get_interactive_channel(); |
} |
|
if (!$this->isInteractiveChannelOpen($channel)) { |
if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) { |
if ($channel != self::CHANNEL_SHELL) { |
throw new InsufficientSetupException('Data is not available on channel'); |
} elseif (!$this->openShell()) { |
3080,11 → 3124,15 |
*/ |
public function write($cmd, $channel = null) |
{ |
if (!$this->isAuthenticated()) { |
throw new InsufficientSetupException('Operation disallowed prior to login()'); |
} |
|
if ($channel === null) { |
$channel = $this->get_interactive_channel(); |
} |
|
if (!$this->isInteractiveChannelOpen($channel)) { |
if (!$this->is_channel_status_data($channel)) { |
if ($channel != self::CHANNEL_SHELL) { |
throw new InsufficientSetupException('Data is not available on channel'); |
} elseif (!$this->openShell()) { |
3110,24 → 3158,9 |
*/ |
public function startSubsystem($subsystem) |
{ |
$this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; |
$this->openChannel(self::CHANNEL_SUBSYSTEM); |
|
$packet = Strings::packSSH2( |
'CsN3', |
NET_SSH2_MSG_CHANNEL_OPEN, |
'session', |
self::CHANNEL_SUBSYSTEM, |
$this->window_size, |
0x4000 |
); |
|
$this->send_binary_packet($packet); |
|
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN; |
|
$this->get_channel_packet(self::CHANNEL_SUBSYSTEM); |
|
$packet = Strings::packSSH2( |
'CNsCs', |
NET_SSH2_MSG_CHANNEL_REQUEST, |
$this->server_channels[self::CHANNEL_SUBSYSTEM], |
3303,23 → 3336,8 |
return false; |
} |
|
$this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; |
$packet_size = 0x4000; |
$packet = Strings::packSSH2( |
'CsN3', |
NET_SSH2_MSG_CHANNEL_OPEN, |
'session', |
self::CHANNEL_KEEP_ALIVE, |
$this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], |
$packet_size |
); |
|
try { |
$this->send_binary_packet($packet); |
|
$this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; |
|
$response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE); |
$this->openChannel(self::CHANNEL_KEEP_ALIVE); |
} catch (\RuntimeException $e) { |
return $this->reconnect(); |
} |
4111,6 → 4129,8 |
} |
|
$this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
$this->channelCount--; |
|
if ($client_channel == $channel) { |
return true; |
} |
4445,6 → 4465,7 |
} |
|
$this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; |
$this->channelCount--; |
|
$this->curTimeout = 5; |
|
4917,6 → 4938,14 |
} |
|
/** |
* Force multiple channels (even if phpseclib has decided to disable them) |
*/ |
public function forceMultipleChannels() |
{ |
$this->errorOnMultipleChannels = false; |
} |
|
/** |
* Allows you to set the terminal |
* |
* @param string $term |