<?php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/NetworkToolsQueued.php';
use Colors\Color;
use function Laravel\Prompts\spin;
function isWSL(): bool {
return str_contains(strtolower(php_uname('r')), 'microsoft') ||
file_exists('/proc/sys/fs/binfmt_misc/WSLInterop');
}
/**
* Open an URL in system browser
*
*
* @param string $url
* @return void
*/
function openBrowser(string $url) : void {
if (isWSL()) {
if (strpos($url, 'file://') === 0) {
$filePath = substr($url, 7); // Remove 'file://'
// Copy to accessible location and open with cmd
$windowsTempFile = '/mnt/c/Windows/Temp/' . basename($filePath);
shell_exec("cp " . escapeshellarg($filePath) . " " . escapeshellarg($windowsTempFile));
$windowsPath = 'C:\\Windows\\Temp\\' . basename($filePath);
shell_exec("cmd.exe /c start \"\" \"" . $windowsPath . "\" 2>/dev/null");
} else {
shell_exec("wslview \"$url\"");
}
} else {
shell_exec("xdg-open \"$url\"");
}
}
class StdinHelper {
/**
* Check if there is any data available on STDIN using select()
* This is the most reliable method for real-time checking
*
* @param int $timeout Timeout in seconds (0 for immediate return)
* @return bool True if data is available, false otherwise
*/
public static function hasDataSelect(float $timeout = 0): bool {
$read = array(STDIN);
$write = null;
$except = null;
return stream_select($read, $write, $except, $timeout) > 0;
}
/**
* Forward stdin to a subprocess
*
*
* @param string $cmd
* @return void
*/
public static function forwardStdInTo(string $cmd) : void {
// Make STDIN non-blocking
stream_set_blocking(STDIN, false);
// Create a subprocess (example using 'cat', replace with your desired command)
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
// Make subprocess input/output streams non-blocking
stream_set_blocking($pipes[0], false);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
while (true) {
// Read from STDIN
$input = fgets(STDIN);
if ($input !== false) {
// Write to subprocess stdin
fwrite($pipes[0], $input);
// Flush the pipe to ensure immediate writing
fflush($pipes[0]);
}
// Read from subprocess stdout
$output = fgets($pipes[1]);
if ($output !== false) {
echo $output;
}
// Read from subprocess stderr
$error = fgets($pipes[2]);
if ($error !== false) {
fwrite(STDERR, $error);
}
// Small delay to prevent CPU overload
usleep(10000); // 10ms delay
// Check if STDIN has closed
if (feof(STDIN)) {
break;
}
}
// Clean up
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
// Close the process
proc_close($process);
}
}
/**
* Check if STDIN is connected to a pipe or redirected file
* Useful for detecting if script is running interactively
*
* @return bool True if STDIN is a pipe/file, false if it's a terminal
*/
public static function isPiped(): bool {
return !posix_isatty(STDIN);
}
/**
* Check if there is any data using non-blocking read
* This method might be less reliable but doesn't require stream_select
*
* @return bool True if data is available, false otherwise
*/
public static function hasDataNonBlocking(): bool {
static $buffer = null;
// If we previously read a byte, return true
if ($buffer !== null) {
return true;
}
// Store current blocking state
$meta = stream_get_meta_data(STDIN);
$blocking = $meta['blocked'] ?? true;
// Temporarily set to non-blocking
stream_set_blocking(STDIN, false);
// Try to read one byte
$char = fgetc(STDIN);
// Restore original blocking state
stream_set_blocking(STDIN, $blocking);
if ($char !== false) {
// Store the character for later retrieval
$buffer = $char;
return true;
}
return false;
}
public static function getStdInData(): string {
static $buffer = null;
$data = '';
// If we previously read a byte, prepend it to the read data
if ($buffer !== null) {
$data .= $buffer;
$buffer = null;
}
// Read the rest of the input
$data .= stream_get_contents(STDIN);
return $data;
}
/**
* Comprehensive check that combines multiple methods
*
* @return array Array with detailed information about STDIN state
*/
public static function getStdinInfo(): array {
return [
'has_data' => self::hasDataSelect(),
'is_piped' => self::isPiped(),
'is_readable' => is_readable("php://stdin"),
'stream_type' => stream_get_meta_data(STDIN)['stream_type'],
'blocked' => stream_get_meta_data(STDIN)['blocked'],
'eof' => feof(STDIN)
];
}
}
//============================================================
// MAIN EXAMPLE CODE
//============================================================
try {
define('OPS',[
'a',
'mx',
'txt',
'cert',
'cname',
'soa',
'spf',
'ptr',
'tcp',
'smtp',
'http',
'https',
'ping',
'simulatedping',
'trace',
'simulatedtrace',
'fulldomain',
'fullip',
'securityscan',
'emailanalysis',
'quickcheck',
'serverscan',
'whois'
]);
$output = NULL;
if ($idx = array_search('--output', $_SERVER['argv'])) {
$output = $_SERVER['argv'][$idx+1];
}
$c = new Color(); // See: https://github.com/kevinlebrun/colors.php
$operation = $_SERVER['argv'][1];
$operation = strtolower($operation);
// Validation
if (!in_array($operation, OPS)) {
throw new Exception(sprintf('Invalid operation: "%s" - Valid operations: %s', $operation, implode(', ', OPS)));
}
$input = $_SERVER['argv'][2];
$stdInData = '';
if (StdInHelper::hasDataSelect()) {
$stdInData = StdinHelper::getStdInData();
}
$inputs = match(TRUE) {
(!empty($input) && is_file($input)) => file($input, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES),
(!empty($stdInData)) => explode(PHP_EOL, trim($stdInData)),
(!empty($input) && !is_file($input)) => [$input],
default => []
};
// Abort if no inputs
if (empty($inputs)) {
throw new Exception(sprintf('No inputs for "%s" operation - Use an IP or domain as argument or send a list on STDIN', $operation));
}
$tools = new NetworkToolsQueued();
// Enable auto-PTR if flag set
//if ($this->enableAutoPtr) $tools->enableAutoPTR();
// Enable problem summary
//if ($this->enableProblemSummary) $tools->enableProblemSummary();
// Determine if operation requires IP or domain
$requiresIP = in_array($operation, ['ping', 'simulatedping', 'ptr']);
$requiresDomain = !$requiresIP;
// Validate and queue all targets
foreach($inputs as $target) {
$target = trim($target);
if (empty($target)) continue;
// Handle special chained operations
if (in_array($operation, ['fulldomain', 'securityscan', 'emailanalysis', 'quickcheck', 'whois'])) {
// Domain operations
if (filter_var($target, FILTER_VALIDATE_DOMAIN) === FALSE) {
throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires a domain', $operation, $target));
}
if ($operation == 'fulldomain') {
$tools->fulldomain($target, in_array('--portscan', $_SERVER['argv']));
} else {
$tools->$operation($target);
}
} elseif ($operation === 'fullip') {
// IP operation
if (filter_var($target, FILTER_VALIDATE_IP) === FALSE) {
throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires an IP', $operation, $target));
}
$tools->$operation($target, TRUE, TRUE);
} elseif ($operation === 'serverscan') {
// Works with both IP and domain
$tools->$operation($target);
} else {
// Original single operations
if ($requiresIP) {
if (filter_var($target, FILTER_VALIDATE_IP) === FALSE) {
throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires an IP', $operation, $target));
}
} elseif ($requiresDomain) {
if (empty($target) || preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $target) === 0) {
throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires a domain', $operation, $target));
}
}
$tools->$operation($target);
}
}
// JSON output mode
if (in_array('--json', $_SERVER['argv'])) {
if (posix_isatty(STDOUT)) {
$m = ($output)
? '<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan> - <white>Will write</white> <light_green>'.$this->output.'</light_green> <white>momentarily...</white>'
: '<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan>';
$array = spin(function() use($tools) {
$tools->exec();
return $tools->getResults();
}, $c->colorize($m));
// Group by target
$byTarget = [];
foreach ($array as $r) {
$byTarget[$r['target']][] = $r;
}
ksort($byTarget);
// Calculate and add scores to output
$scores = $tools->getTargetScores();
$output = [
'targets' => $byTarget,
'scores' => $scores
];
if ($output) {
file_put_contents($output, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
echo self::TAB.$c->colorize("<light_green>✓</light_green> Results written to: <light_cyan>{$output}</light_cyan>\n");
} else {
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
} else {
$tools->exec();
$array = $tools->getResults();
// Group by target
$byTarget = [];
foreach ($array as $r) {
$byTarget[$r['target']][] = $r;
}
ksort($byTarget);
// Calculate and add scores to output
$scores = $tools->getTargetScores();
$output = [
'targets' => $byTarget,
'scores' => $scores
];
if ($output) {
file_put_contents($output, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
} else {
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
}
return;
}
// HTML output mode (default)
if (posix_isatty(STDOUT)) {
$html = spin(function() use($tools) { // Spin is part of Laravel\Prompts\ package
$tools->exec();
// Enable grouping by target for better organization
return $tools->getReport(TRUE);
}, $c->colorize('<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan> - <white>Will open in</white> <light_green>browser</light_green> <white>momentarily...</white>'));
} else {
$tools->exec();
$html = $tools->getReport(TRUE); // Group by target
}
// Save and open HTML report
$file = tempnam(sys_get_temp_dir(), 'nettool_') . '.html';
file_put_contents($file, $html);
openBrowser('file://'.$file);
} catch(Exception $ex) {
echo('ERROR :: ' . $ex->getMessage());
exit(1);
}
|