RemoteCommandFile: Replace exec with proc_open
This commit is contained in:
parent
de61e0c703
commit
3a00923116
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue