278 lines
8.2 KiB
PHP
278 lines
8.2 KiB
PHP
#!/usr/bin/env php
|
|
<?php
|
|
/**
|
|
* AVM Scanner Background Script
|
|
* Run with: php scripts/avm_scanner.php
|
|
*/
|
|
|
|
// Bootstrap the application
|
|
require_once(__DIR__ . "/../config/config.php");
|
|
|
|
define('FRONKDB_SQLDEBUG', false);
|
|
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
|
|
|
require_once(LIBDIR . "/mvcfronk/mfRouter/mfRouter.php");
|
|
require_once(LIBDIR . "/mvcfronk/mfBase/mfBaseModel.php");
|
|
require_once(LIBDIR . "/mvcfronk/mfBase/mfBaseController.php");
|
|
|
|
$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'
|
|
];
|
|
|
|
function getStatePath() {
|
|
return dirname(__DIR__) . '/files/avm_scanner.json';
|
|
}
|
|
|
|
function loadState() {
|
|
$path = getStatePath();
|
|
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' => []
|
|
];
|
|
}
|
|
|
|
function saveState($state) {
|
|
$dir = dirname(getStatePath());
|
|
if (!is_dir($dir)) @mkdir($dir, 0777, true);
|
|
$state['lastUpdated'] = date('c');
|
|
file_put_contents(getStatePath(), json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
function isAvmMac($mac) {
|
|
global $avmPrefixes;
|
|
$mac = strtoupper(trim($mac));
|
|
$prefix = substr($mac, 0, 8);
|
|
return in_array($prefix, $avmPrefixes);
|
|
}
|
|
|
|
function fetchRadiusUsers() {
|
|
$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 : [];
|
|
}
|
|
|
|
function fetchRadacct($username) {
|
|
$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;
|
|
}
|
|
|
|
function detectFritzPort($ip) {
|
|
$url = "http://acs.xinon.at:5000/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" => 20
|
|
]
|
|
];
|
|
$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;
|
|
}
|
|
|
|
function detectFritzDevice($ip, $port) {
|
|
$url = "http://acs.xinon.at:5000/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" => 20
|
|
]
|
|
];
|
|
$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;
|
|
}
|
|
|
|
// Main execution
|
|
echo "AVM Scanner starting...\n";
|
|
|
|
$state = loadState();
|
|
if ($state['scanning']) {
|
|
echo "Scan already running, exiting.\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Fetch all users
|
|
echo "Fetching users from RADIUS API...\n";
|
|
$users = fetchRadiusUsers();
|
|
echo "Got " . count($users) . " users from API\n";
|
|
|
|
// Filter AVM users
|
|
$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;
|
|
if (!isAvmMac($username)) continue;
|
|
$info = $user['info'] ?? '';
|
|
if (stripos($info, 'ACS') !== false) continue;
|
|
$user['username'] = $username;
|
|
$avmUsers[] = $user;
|
|
}
|
|
echo "Found " . count($avmUsers) . " AVM users to scan\n";
|
|
|
|
if (count($avmUsers) === 0) {
|
|
echo "No users to scan, exiting.\n";
|
|
exit(0);
|
|
}
|
|
|
|
// Preserve existing device data (for erledigt status)
|
|
$existingDevices = [];
|
|
foreach ($state['devices'] ?? [] as $dev) {
|
|
$existingDevices[$dev['mac']] = $dev;
|
|
}
|
|
|
|
// Initialize scan state
|
|
$state['scanning'] = true;
|
|
$state['stopRequested'] = false;
|
|
$state['progress'] = ['current' => 0, 'total' => count($avmUsers)];
|
|
$state['startedAt'] = date('c');
|
|
$state['startedBy'] = 'script';
|
|
saveState($state);
|
|
|
|
// Process each user
|
|
foreach ($avmUsers as $idx => $user) {
|
|
// Check for stop request
|
|
$state = loadState();
|
|
if ($state['stopRequested']) {
|
|
echo "Stop requested, terminating.\n";
|
|
$state['scanning'] = false;
|
|
$state['stopRequested'] = false;
|
|
$state['currentDevice'] = null;
|
|
saveState($state);
|
|
exit(0);
|
|
}
|
|
|
|
$mac = $user['username'];
|
|
$customerId = $user['customerNumber'] ?? '';
|
|
$customerName = $user['info'] ?? '';
|
|
|
|
echo "[" . ($idx + 1) . "/" . count($avmUsers) . "] Scanning $mac...\n";
|
|
|
|
$state['progress']['current'] = $idx + 1;
|
|
$state['currentDevice'] = ['mac' => $mac, 'ip' => null];
|
|
saveState($state);
|
|
|
|
// Fetch IP from radacct
|
|
$radacct = fetchRadacct($mac);
|
|
$ip = $radacct['ip'] ?? null;
|
|
|
|
$deviceResult = [
|
|
'mac' => $mac,
|
|
'ip' => $ip,
|
|
'customerId' => $customerId,
|
|
'customerName' => $customerName,
|
|
'deviceType' => null,
|
|
'oem' => null,
|
|
'port' => null,
|
|
'isRepeater' => false,
|
|
'isGateway' => false,
|
|
'erledigt' => $existingDevices[$mac]['erledigt'] ?? false,
|
|
'scannedAt' => date('c'),
|
|
'error' => null
|
|
];
|
|
|
|
if ($ip) {
|
|
echo " IP: $ip - detecting port...\n";
|
|
$port = detectFritzPort($ip);
|
|
if ($port) {
|
|
echo " Port: $port - detecting device...\n";
|
|
$deviceResult['port'] = $port;
|
|
$deviceInfo = detectFritzDevice($ip, $port);
|
|
if ($deviceInfo) {
|
|
$deviceResult['deviceType'] = $deviceInfo['product_name'] ?? null;
|
|
$deviceResult['oem'] = $deviceInfo['oem'] ?? null;
|
|
$deviceResult['isRepeater'] = $deviceInfo['is_repeater'] ?? false;
|
|
$deviceResult['isGateway'] = $deviceInfo['is_gateway'] ?? false;
|
|
echo " Device: " . ($deviceResult['deviceType'] ?? 'unknown') . "\n";
|
|
} else {
|
|
echo " Could not detect device info\n";
|
|
}
|
|
} else {
|
|
$deviceResult['error'] = 'No port detected';
|
|
echo " No port detected\n";
|
|
}
|
|
} else {
|
|
$deviceResult['error'] = 'No IP address';
|
|
echo " No IP address\n";
|
|
}
|
|
|
|
$existingDevices[$mac] = $deviceResult;
|
|
$state['devices'] = array_values($existingDevices);
|
|
saveState($state);
|
|
|
|
// Small delay between requests
|
|
usleep(50000);
|
|
}
|
|
|
|
// Done
|
|
$state = loadState();
|
|
$state['scanning'] = false;
|
|
$state['currentDevice'] = null;
|
|
saveState($state);
|
|
|
|
echo "Scan complete!\n";
|