RemoteCommandFile: Replace exec with proc_open

This commit is contained in:
Thomas Gelf 2016-05-26 12:01:25 +02:00 committed by Eric Lippmann
parent de61e0c703
commit 3a00923116
1 changed files with 196 additions and 10 deletions

View File

@ -73,6 +73,20 @@ class RemoteCommandFile implements CommandTransportInterface
*/
protected $renderer;
/**
* SSH subprocess pipes
*
* @var array
*/
protected $sshPipes;
/**
* SSH subprocess
*
* @var resource
*/
protected $sshProcess;
/**
* Create a new remote command file command transport
*/
@ -274,26 +288,198 @@ class RemoteCommandFile implements CommandTransportInterface
$this->port,
$this->path
);
$ssh = sprintf('ssh -o BatchMode=yes -p %u', $this->port);
return $this->sendCommandString($commandString);
}
/**
* Get the SSH command
*
* @return string
*/
protected function sshCommand()
{
$cmd = sprintf(
'exec ssh -o BatchMode=yes -p %u',
$this->port
);
// -o BatchMode=yes for disabling interactive authentication methods
if (isset($this->user)) {
$ssh .= sprintf(' -l %s', escapeshellarg($this->user));
$cmd .= ' -l ' . escapeshellarg($this->user);
}
if (isset($this->privateKey)) {
$ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->privateKey));
// TODO: StrictHostKeyChecking=no for compat only, must be removed
$cmd .= ' -o StrictHostKeyChecking=no'
. ' -i ' . escapeshellarg($this->privateKey);
}
$ssh .= sprintf(
' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout
$cmd .= sprintf(
' %s "cat > %s"',
escapeshellarg($this->host),
escapeshellarg($commandString),
escapeshellarg($this->path)
);
exec($ssh, $output, $status);
if ($status !== 0) {
return $cmd;
}
/**
* Send the command over SSH
*
* @param string $commandString
*
* @throws CommandTransportException
*/
protected function sendCommandString($commandString)
{
if ($this->isSshAlive()) {
$ret = fwrite($this->sshPipes[0], $commandString . "\n");
if ($ret === false) {
$this->throwSshFailure('Cannot write to the remote command pipe');
} elseif ($ret !== strlen($commandString) + 1) {
$this->throwSshFailure(
'Failed to write the whole command to the remote command pipe'
);
}
fclose($this->sshPipes[0]);
$readBuffer = $read = array($this->sshPipes[2]);
$write = null;
$except = null;
$stderr = '';
while (
false !== (stream_select($readBuffer, $write, $exceptBuffer, 0, 20000))
&& $this->getSshProcessStatus('running')
) {
if (! empty($readBuffer)) {
$stderr .= stream_get_contents($readBuffer[0]);
}
// Reset buffer
$readBuffer = $read;
}
if (! empty($stderr)) {
throw new CommandTransportException('Can\'t send external Icinga command: %s', trim($stderr));
}
} else {
$this->throwSshFailure();
}
}
/**
* Get the pipes of the SSH subprocess
*
* @return array
*/
protected function getSshPipes()
{
if ($this->sshPipes === null) {
$this->forkSsh();
}
return $this->sshPipes;
}
/**
* Get the SSH subprocess
*
* @return resource
*/
protected function getSshProcess()
{
if ($this->sshProcess === null) {
$this->forkSsh();
}
return $this->sshProcess;
}
/**
* Get the status of the SSH subprocess
*
* @param string $what
*
* @return mixed
*/
protected function getSshProcessStatus($what = null)
{
$status = proc_get_status($this->getSshProcess());
if ($what === null) {
return $status;
} else {
return $status[$what];
}
}
/**
* Get whether the SSH subprocess is alive
*
* @return bool
*/
protected function isSshAlive()
{
return $this->getSshProcessStatus('running');
}
/**
* Fork SSH subprocess
*
* @throws CommandTransportException If fork fails
*/
protected function forkSsh()
{
$descriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
);
$this->sshProcess = proc_open($this->sshCommand(), $descriptors, $this->sshPipes);
if (! is_resource($this->sshProcess)) {
throw new CommandTransportException(
'Can\'t send external Icinga command: %s',
implode(' ', $output)
'Can\'t send external Icinga command, failed to fork SSH'
);
}
}
/**
* Read from STDERR
*
* @return string
*/
protected function readStderr()
{
return stream_get_contents($this->sshPipes[2]);
}
/**
* Throw SSH failure
*
* @param string $msg
*
* @throws CommandTransportException
*/
protected function throwSshFailure($msg = 'Can\'t send external Icinga command')
{
throw new CommandTransportException(
'%s: %s',
$msg,
$this->readStderr() . var_export($this->getSshProcessStatus(), true)
);
}
/**
* Close SSH pipes and SSH subprocess
*/
public function __destruct()
{
if (is_resource($this->sshProcess)) {
if (is_resource($this->sshPipes[0])) {
fclose($this->sshPipes[0]);
}
fclose($this->sshPipes[1]);
fclose($this->sshPipes[2]);
proc_close($this->sshProcess);
}
}
}