#!/usr/bin/env php 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";