array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
// sanitize + remove single quotes
$host = str_replace('\'', '', filter_var($host, FILTER_SANITIZE_URL));
// execute command
$process = proc_open("{$cmd} '{$host}'", $spec, $pipes, null);
// check pipe exists
if (!is_resource($process)) {
return false;
}
// check for mtr/traceroute
if (strpos($cmd, 'mtr') !== false) {
$type = 'mtr';
} elseif (strpos($cmd, 'traceroute') !== false) {
$type = 'traceroute';
} else {
$type = '';
}
$fail = 0;
$match = 0;
$traceCount = 0;
$lastFail = 'start';
// iterate stdout
while (($str = fgets($pipes[1], 4096)) != null) {
// check for output buffer
if (ob_get_level() == 0) {
ob_start();
}
// fix RDNS XSS (outputs non-breakble space correctly)
$str = htmlspecialchars(trim($str));
// correct output for mtr
if ($type === 'mtr') {
if ($match < 10 && preg_match('/^[0-9]\. /', $str, $string)) {
$str = preg_replace('/^[0-9]\. /', ' ' . $string[0], $str);
$match++;
} else {
$str = preg_replace('/^[0-9]{2}\. /', ' ' . substr($str, 0, 4), $str);
}
}
// correct output for traceroute
elseif ($type === 'traceroute') {
if ($match < 10 && preg_match('/^[0-9] /', $str, $string)) {
$str = preg_replace('/^[0-9] /', ' ' . $string[0], $str);
$match++;
}
// check for consecutive failed hops
if (strpos($str, '* * *') !== false) {
$fail++;
if ($lastFail !== 'start'
&& ($traceCount - 1) === $lastFail
&& $fail >= $failCount
) {
echo str_pad($str . '
-- Traceroute timed out --
', 4096, ' ', STR_PAD_RIGHT);
break;
}
$lastFail = $traceCount;
}
$traceCount++;
}
// pad string for live output
echo str_pad($str . '
', 4096, ' ', STR_PAD_RIGHT);
// flush output buffering
@ob_flush();
flush();
}
// iterate stderr
while (($err = fgets($pipes[2], 4096)) != null) {
// check for IPv6 hostname passed to IPv4 command, and vice versa
if (strpos($err, 'Name or service not known') !== false || strpos($err, 'unknown host') !== false) {
echo 'Unauthorized request';
break;
}
}
$status = proc_get_status($process);
if ($status['running'] == true) {
// close pipes that are still open
foreach ($pipes as $pipe) {
fclose($pipe);
}
if ($status['pid']) {
// retrieve parent pid
//$ppid = $status['pid'];
// use ps to get all the children of this process
$pids = preg_split('/\s+/', 'ps -o pid --no-heading --ppid '.$status['pid']);
// kill remaining processes
foreach ($pids as $pid) {
if (is_numeric($pid)) {
posix_kill($pid, 9);
}
}
}
proc_close($process);
}
return true;
}
}