added avm scanner module to radius
This commit is contained in:
@@ -657,47 +657,302 @@ class RadiusController extends mfBaseController {
|
||||
|
||||
|
||||
private function getVendor($mac) {
|
||||
|
||||
|
||||
$mac = strtoupper(str_replace([':', '-', '.'], '', $mac));
|
||||
|
||||
|
||||
if (strlen($mac) < 6) return null;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$path = TEMP_DIR . '/mac-vendors.csv';
|
||||
|
||||
|
||||
if (!file_exists($path)) return null;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Format as XX:XX:XX
|
||||
|
||||
|
||||
$prefix = substr($mac, 0, 2) . ':' . substr($mac, 2, 2) . ':' . substr($mac, 4, 2);
|
||||
|
||||
|
||||
|
||||
// Use grep for speed if available, else fallback to basic search?
|
||||
|
||||
|
||||
|
||||
|
||||
// Use grep for speed if available, else fallback to basic search?
|
||||
|
||||
// Assuming Linux env as per docker context.
|
||||
|
||||
|
||||
$cmd = "grep -m 1 \"^" . $prefix . "\" " . escapeshellarg($path);
|
||||
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if ($output) {
|
||||
|
||||
|
||||
$parts = str_getcsv($output);
|
||||
|
||||
|
||||
if (isset($parts[1])) return $parts[1];
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ========== AVM Scanner Methods ==========
|
||||
|
||||
private static $avmScannerStateFile = null;
|
||||
|
||||
private static $avmPrefixes = [
|
||||
'00:04:0E', '00:15:0C', '00:1A:4F', '00:1C:4A', '00:1F:3F', '00:24:FE',
|
||||
'04:B4:FE', '08:96:D7', '08:B6:57', '0C:72:74',
|
||||
'1C:ED:6F',
|
||||
'24:65:11', '2C:3A:FD', '2C:91:AB',
|
||||
'34:31:C4', '34:81:C4', '34:E1:A9', '38:10:D5', '3C:37:12', '3C:A6:2F',
|
||||
'44:4E:6D', '48:5D:35',
|
||||
'50:E6:36', '5C:49:79',
|
||||
'60:B5:8D',
|
||||
'74:42:7F', '7C:FF:4D',
|
||||
'80:23:95',
|
||||
'98:9B:CB', '98:A9:65', '9C:C7:A6',
|
||||
'B0:F2:08', 'B4:FC:7D', 'BC:05:43',
|
||||
'C0:25:06', 'C8:0E:14', 'CC:CE:1E',
|
||||
'D0:12:CB', 'D4:24:DD', 'DC:15:C8', 'DC:39:6F',
|
||||
'E0:08:55', 'E0:28:6D', 'E8:DF:70',
|
||||
'F0:B0:14'
|
||||
];
|
||||
|
||||
private function getAvmScannerStatePath(): string {
|
||||
return BASEDIR . '/files/avm_scanner.json';
|
||||
}
|
||||
|
||||
private function loadAvmScannerState(): array {
|
||||
$path = $this->getAvmScannerStatePath();
|
||||
if (file_exists($path)) {
|
||||
$content = file_get_contents($path);
|
||||
$state = json_decode($content, true);
|
||||
if (is_array($state)) return $state;
|
||||
}
|
||||
return [
|
||||
'scanning' => false,
|
||||
'stopRequested' => false,
|
||||
'progress' => ['current' => 0, 'total' => 0],
|
||||
'currentDevice' => null,
|
||||
'startedAt' => null,
|
||||
'startedBy' => null,
|
||||
'lastUpdated' => date('c'),
|
||||
'devices' => []
|
||||
];
|
||||
}
|
||||
|
||||
private function saveAvmScannerState(array $state): void {
|
||||
$dir = dirname($this->getAvmScannerStatePath());
|
||||
if (!is_dir($dir)) @mkdir($dir, 0777, true);
|
||||
$state['lastUpdated'] = date('c');
|
||||
file_put_contents($this->getAvmScannerStatePath(), json_encode($state, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private function isAvmMac(string $mac): bool {
|
||||
$mac = strtoupper(trim($mac));
|
||||
$prefix = substr($mac, 0, 8);
|
||||
return in_array($prefix, self::$avmPrefixes);
|
||||
}
|
||||
|
||||
private function fetchRadiusUsersFromApi(): array {
|
||||
$url = "http://radius.xinon.at/api.php";
|
||||
$opts = [
|
||||
"http" => [
|
||||
"method" => "GET",
|
||||
"header" => "Authorization: Basic " . base64_encode("admin:saveman"),
|
||||
"timeout" => 120
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($opts);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
if ($response === false) return [];
|
||||
$data = json_decode($response, true);
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
private function fetchRadacctForUser(string $username): ?array {
|
||||
$url = "http://radius.xinon.at/api.php?action2=fetchRadacct&username=" . urlencode($username);
|
||||
$opts = [
|
||||
"http" => [
|
||||
"method" => "GET",
|
||||
"header" => "Authorization: Basic " . base64_encode("admin:saveman"),
|
||||
"timeout" => 10
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($opts);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
if ($response === false) return null;
|
||||
$data = json_decode($response, true);
|
||||
return is_array($data) ? $data : null;
|
||||
}
|
||||
|
||||
protected function avmScannerGetStateAction() {
|
||||
$state = $this->loadAvmScannerState();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($state);
|
||||
die();
|
||||
}
|
||||
|
||||
protected function avmScannerGetUsersAction() {
|
||||
$users = $this->fetchRadiusUsersFromApi();
|
||||
$debug = $_GET['debug'] ?? false;
|
||||
|
||||
$macUsers = 0;
|
||||
$avmMacUsers = 0;
|
||||
$notInAcs = 0;
|
||||
$macPrefixCounts = [];
|
||||
|
||||
$avmUsers = [];
|
||||
foreach ($users as $user) {
|
||||
$username = trim($user['username'] ?? '');
|
||||
if (!preg_match('/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/i', $username)) continue;
|
||||
$macUsers++;
|
||||
|
||||
$prefix = strtoupper(substr($username, 0, 8));
|
||||
$macPrefixCounts[$prefix] = ($macPrefixCounts[$prefix] ?? 0) + 1;
|
||||
|
||||
if (!$this->isAvmMac($username)) continue;
|
||||
$avmMacUsers++;
|
||||
|
||||
$info = $user['info'] ?? '';
|
||||
if (stripos($info, 'ACS') !== false) continue;
|
||||
$notInAcs++;
|
||||
|
||||
$user['username'] = $username;
|
||||
$avmUsers[] = $user;
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
arsort($macPrefixCounts);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'totalFromApi' => count($users),
|
||||
'macAddressUsers' => $macUsers,
|
||||
'avmMacUsers' => $avmMacUsers,
|
||||
'notInAcs' => $notInAcs,
|
||||
'topMacPrefixes' => array_slice($macPrefixCounts, 0, 30, true),
|
||||
'avmPrefixes' => self::$avmPrefixes
|
||||
]);
|
||||
die();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['users' => $avmUsers, 'count' => count($avmUsers)]);
|
||||
die();
|
||||
}
|
||||
|
||||
protected function avmScannerStartAction() {
|
||||
$state = $this->loadAvmScannerState();
|
||||
if ($state['scanning']) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Scan already running']);
|
||||
die();
|
||||
}
|
||||
|
||||
// Count how many users will be scanned (for immediate feedback)
|
||||
$users = $this->fetchRadiusUsersFromApi();
|
||||
$count = 0;
|
||||
foreach ($users as $user) {
|
||||
$username = trim($user['username'] ?? '');
|
||||
if (!preg_match('/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/i', $username)) continue;
|
||||
if (!$this->isAvmMac($username)) continue;
|
||||
$info = $user['info'] ?? '';
|
||||
if (stripos($info, 'ACS') !== false) continue;
|
||||
$count++;
|
||||
}
|
||||
|
||||
// Spawn background script using nohup to ensure it runs independently
|
||||
$scriptPath = BASEDIR . '/scripts/avm_scanner.php';
|
||||
$logPath = BASEDIR . '/files/avm_scanner.log';
|
||||
$cmd = "nohup php " . escapeshellarg($scriptPath) . " >> " . escapeshellarg($logPath) . " 2>&1 &";
|
||||
shell_exec($cmd);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Scan started', 'total' => $count]);
|
||||
die();
|
||||
}
|
||||
|
||||
protected function avmScannerStopAction() {
|
||||
$state = $this->loadAvmScannerState();
|
||||
$state['stopRequested'] = true;
|
||||
$this->saveAvmScannerState($state);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Stop requested']);
|
||||
die();
|
||||
}
|
||||
|
||||
protected function avmScannerToggleErledigtAction() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$mac = $input['mac'] ?? null;
|
||||
if (!$mac) {
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'MAC address required']);
|
||||
die();
|
||||
}
|
||||
|
||||
$state = $this->loadAvmScannerState();
|
||||
foreach ($state['devices'] as &$device) {
|
||||
if ($device['mac'] === $mac) {
|
||||
$device['erledigt'] = !($device['erledigt'] ?? false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->saveAvmScannerState($state);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true]);
|
||||
die();
|
||||
}
|
||||
|
||||
private function detectFritzPort(string $ip): ?int {
|
||||
$url = "https://acs.xinon.at/detect-port";
|
||||
$data = json_encode(['fritz_ip' => $ip, 'timeout' => 3]);
|
||||
$opts = [
|
||||
"http" => [
|
||||
"method" => "POST",
|
||||
"header" => "Content-Type: application/json\r\nContent-Length: " . strlen($data) . "\r\n",
|
||||
"content" => $data,
|
||||
"timeout" => 15
|
||||
],
|
||||
"ssl" => ["verify_peer" => false, "verify_peer_name" => false]
|
||||
];
|
||||
$context = stream_context_create($opts);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
if ($response) {
|
||||
$json = json_decode($response, true);
|
||||
if ($json && $json['success'] && isset($json['port'])) {
|
||||
return (int)$json['port'];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function detectFritzDevice(string $ip, int $port): ?array {
|
||||
$url = "https://acs.xinon.at/detect-device";
|
||||
$data = json_encode(['fritz_ip' => $ip, 'fritz_port' => (string)$port]);
|
||||
$opts = [
|
||||
"http" => [
|
||||
"method" => "POST",
|
||||
"header" => "Content-Type: application/json\r\nContent-Length: " . strlen($data) . "\r\n",
|
||||
"content" => $data,
|
||||
"timeout" => 15
|
||||
],
|
||||
"ssl" => ["verify_peer" => false, "verify_peer_name" => false]
|
||||
];
|
||||
$context = stream_context_create($opts);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
if ($response) {
|
||||
$json = json_decode($response, true);
|
||||
if ($json && $json['success'] && isset($json['device'])) {
|
||||
return $json['device'];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user