trim($l) !== ''); $lines = array_values($lines); $scanner = ''; $subnet = ''; $headers = []; $rows = []; $splitPattern = null; // wird beim ersten Header-Treffer gesetzt foreach ($lines as $line) { $trimmed = trim($line); if (str_starts_with($trimmed, 'Erstellt von') || str_starts_with($trimmed, 'Created by')) { $scanner = trim(str_replace(['Erstellt von ', 'Created by '], '', $trimmed)); continue; } if (str_starts_with($trimmed, 'Gescannt') || str_starts_with($trimmed, 'Scanned')) { preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/', $trimmed, $m); $subnet = $m[1] ?? '0.0.0.0'; continue; } if (str_starts_with($trimmed, 'http')) { continue; } // Header-Zeile erkennen (beginnt mit "IP") if (str_starts_with($trimmed, 'IP') && empty($headers)) { // Trennzeichen auto-detektieren if (substr_count($trimmed, "\t") >= 2) { $splitPattern = '/\t/'; } elseif (substr_count($trimmed, ';') >= 2) { $splitPattern = '/;/'; } elseif (substr_count($trimmed, ',') >= 2) { $splitPattern = '/,/'; } else { // Angry IP Scanner: mehrere normale Leerzeichen als Trennzeichen // Non-Breaking Spaces (0xC2 0xA0) bleiben innerhalb von Werten erhalten $splitPattern = '/ {2,}/'; } $headers = array_map('trim', preg_split($splitPattern, $trimmed)); continue; } // Datenzeile: muss mit IP-Adresse beginnen if (!empty($headers) && $splitPattern !== null && preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $trimmed)) { $cols = array_map( fn($v) => trim(str_replace("\xc2\xa0", ' ', $v)), // Non-Breaking Spaces → normale Spaces preg_split($splitPattern, $line) ); $row = []; foreach ($headers as $i => $h) { $row[$h] = $cols[$i] ?? ''; } $rows[] = $row; } } // DEBUG: Log parse results Log::info('NetworkScanImporter: scanner=' . $scanner . ', subnet=' . $subnet); Log::info('NetworkScanImporter: separator hex=' . bin2hex($separator)); Log::info('NetworkScanImporter: headers=' . json_encode($headers)); Log::info('NetworkScanImporter: rows found=' . count($rows)); if (!empty($rows)) { Log::info('NetworkScanImporter: first row=' . json_encode($rows[0])); } return DB::transaction(function () use ($rows, $scanner, $subnet, $createdBy, $segmentId) { $this->scan = NetworkScan::create([ 'segment_id' => $segmentId, 'subnet' => $subnet, 'source' => 'import', 'scanner' => $scanner, 'created_by' => $createdBy, ]); foreach ($rows as $row) { $this->processRow($row); } $this->scan->update([ 'total_hosts' => count($rows), 'online_hosts' => $this->onlineHosts, 'new_devices' => $this->newDevices, 'changed_devices'=> $this->changedDevices, ]); return $this->scan; }); } private function processRow(array $row): void { $ip = trim($this->extractColumn($row, ['IP', 'IP-Adresse', 'IP Address', 'IP Adresse'])); $ping = trim($this->extractColumn($row, ['Ping', 'Ping (ms)'])); $host = $this->cleanValue($this->extractColumn($row, ['Hostname', 'Host Name', 'DNS-Hostname'])); $mac = $this->normalizeMAC($this->extractColumn($row, ['MAC Adresse', 'MAC Addresse', 'MAC Address', 'MAC-Adresse', 'MAC'])); $vendor = $this->cleanValue($this->extractColumn($row, ['MAC Hersteller', 'MAC Vendor', 'Hersteller', 'Vendor'])); $ttl = (int) $this->cleanValue($this->extractColumn($row, ['TTL'])) ?: null; $ports = $this->cleanValue($this->extractColumn($row, ['Ports', 'Gefilterte Ports', 'Offene Ports'])); $netbios= $this->cleanValue($this->extractColumn($row, ['NetBIOS Info', 'NetBIOS-Info', 'NetBIOS'])); $http = $this->cleanValue($this->extractColumn($row, ['HTTP Sender', 'HTTP'])); $web = $this->cleanValue($this->extractColumn($row, ['Web Erkennung', 'Web Detection', 'Webserver'])); $pingMs = null; $status = 'offline'; // Online-Erkennung: Ping enthält eine Zahl gefolgt von "ms" if (preg_match('/(\d+)\s*ms/i', $ping, $m)) { $pingMs = (int) $m[1]; $status = 'online'; $this->onlineHosts++; } // Host-Eintrag speichern $host_record = NetworkHost::create([ 'scan_id' => $this->scan->id, 'ip_address' => $ip, 'mac_address' => $mac ?: null, 'hostname' => $host ?: null, 'mac_vendor' => $vendor ?: null, 'status' => $status, 'ping_ms' => $pingMs, 'netbios_info' => $netbios ?: null, 'ttl' => $ttl, 'ports' => $ports ?: null, 'http_sender' => $http ?: null, 'web_detection' => $web ?: null, ]); // Geräte-Master nur wenn MAC bekannt if (!empty($mac)) { $this->processDevice($host_record, $ip, $mac, $host, $vendor, $netbios, $ttl, $ports, $status); } } private function processDevice( NetworkHost $hostRecord, string $ip, string $mac, ?string $hostname, ?string $vendor, ?string $netbios, ?int $ttl, ?string $ports, string $status ): void { $device = NetworkDevice::firstOrNew(['mac_address' => $mac]); $isNew = !$device->exists; if ($isNew) { $device->fill([ 'current_ip' => $ip, 'hostname' => $hostname, 'mac_vendor' => $vendor, 'status' => $status, 'netbios_name' => $netbios, 'ttl' => $ttl, 'ports' => $ports, 'first_seen_at'=> now(), 'last_seen_at' => now(), ])->save(); NetworkDeviceEvent::create([ 'device_id' => $device->id, 'scan_id' => $this->scan->id, 'event_type' => 'new_device', 'new_value' => $ip, 'description'=> "Erstes Erscheinen: {$ip} ({$vendor})", ]); $this->newDevices++; } else { $events = []; // IP-Änderung erkennen if ($device->current_ip !== $ip) { $events[] = [ 'event_type' => 'ip_changed', 'old_value' => $device->current_ip, 'new_value' => $ip, 'description'=> "IP geändert von {$device->current_ip} zu {$ip}", ]; $this->changedDevices++; } // Online/Offline-Status if ($device->status !== $status) { $events[] = [ 'event_type' => $status === 'online' ? 'came_online' : 'went_offline', 'old_value' => $device->status, 'new_value' => $status, 'description'=> $status === 'online' ? "Gerät wieder online ({$ip})" : "Gerät offline ({$device->current_ip})", ]; } $device->update([ 'current_ip' => $ip, 'hostname' => $hostname ?? $device->hostname, 'status' => $status, 'last_seen_at' => now(), ]); foreach ($events as $event) { NetworkDeviceEvent::create(array_merge($event, [ 'device_id' => $device->id, 'scan_id' => $this->scan->id, ])); } } $hostRecord->update(['device_id' => $device->id]); } private function extractColumn(array $row, array $keys): string { foreach ($keys as $key) { if (isset($row[$key]) && $row[$key] !== '') { return $row[$key]; } } return ''; } /** * Bereinigt Angry IP Scanner Platzhalterwerte wie [n/a], [n/s], [n/d] zu leerem String. */ private function cleanValue(string $value): string { if (preg_match('/^\[n\/[asd]\]$/i', trim($value))) { return ''; } return trim($value); } private function normalizeMAC(string $mac): string { // Verschiedene MAC-Formate normalisieren zu XX:XX:XX:XX:XX:XX $clean = preg_replace('/[^a-fA-F0-9]/', '', $mac); if (strlen($clean) !== 12) { return ''; } return strtoupper(implode(':', str_split($clean, 2))); } }