From 682cc5402a424650dc01695728b6886ec6b2554b Mon Sep 17 00:00:00 2001 From: Thomas Ciesla Date: Fri, 9 Jan 2026 14:08:17 +0100 Subject: [PATCH] ip resolve local over whois --- jtl-wafi-agent.py | 154 ++++++++++++++++++++++++++++++++++++++++++ jtl-wafi-dashboard.py | 35 ++++++++-- 2 files changed, 184 insertions(+), 5 deletions(-) diff --git a/jtl-wafi-agent.py b/jtl-wafi-agent.py index f9151e9..65faa43 100644 --- a/jtl-wafi-agent.py +++ b/jtl-wafi-agent.py @@ -1045,6 +1045,10 @@ _ip_info_cache: Dict[str, Dict[str, Any]] = {} _ip_api_last_request = 0.0 _ip_api_request_count = 0 +# WHOIS Cache (1 Stunde TTL) +WHOIS_CACHE_TTL = 3600 # 1 Stunde +_whois_cache: Dict[str, Dict[str, Any]] = {} + # Global Country Ranges Cache (loaded once from ipdeny.com files) _country_ranges_cache: Dict[str, List[tuple]] = {} # country -> [(network_int, mask_int), ...] _country_cache_loaded = False @@ -1202,6 +1206,112 @@ def get_cached_ip_info(ip: str) -> Optional[Dict[str, Any]]: return _ip_info_cache.get(ip) +def whois_lookup(ip: str) -> Dict[str, Any]: + """ + Führt WHOIS-Lookup für eine IP durch (lokales whois-Tool). + Cached Ergebnisse für 1 Stunde. + + Returns: + Dict mit: netname, org, asn, country, abuse, range, raw + """ + global _whois_cache + + # Cache prüfen + if ip in _whois_cache: + cached = _whois_cache[ip] + if time.time() - cached.get('_cached_at', 0) < WHOIS_CACHE_TTL: + return cached + + result = { + 'ip': ip, + 'netname': '', + 'org': '', + 'asn': '', + 'country': '', + 'abuse': '', + 'range': '', + 'descr': '', + 'raw': '', + '_cached_at': time.time() + } + + try: + # WHOIS-Kommando ausführen + proc = subprocess.run( + ['whois', ip], + capture_output=True, + text=True, + timeout=10 + ) + + if proc.returncode != 0: + logger.debug(f"WHOIS fehlgeschlagen für {ip}: {proc.stderr}") + _whois_cache[ip] = result + return result + + raw_output = proc.stdout + result['raw'] = raw_output + + # Parsing - verschiedene WHOIS-Formate unterstützen (RIPE, ARIN, APNIC, etc.) + lines = raw_output.split('\n') + + for line in lines: + line_lower = line.lower().strip() + + # Netname + if line_lower.startswith('netname:'): + result['netname'] = line.split(':', 1)[1].strip() + + # Organisation + elif line_lower.startswith('org-name:') or line_lower.startswith('orgname:'): + result['org'] = line.split(':', 1)[1].strip() + elif line_lower.startswith('organization:') and not result['org']: + result['org'] = line.split(':', 1)[1].strip() + elif line_lower.startswith('descr:') and not result['org']: + # Fallback zu descr wenn keine org gefunden + val = line.split(':', 1)[1].strip() + if val and not result['descr']: + result['descr'] = val + + # ASN + elif line_lower.startswith('origin:') or line_lower.startswith('originas:'): + result['asn'] = line.split(':', 1)[1].strip() + + # Country + elif line_lower.startswith('country:') and not result['country']: + result['country'] = line.split(':', 1)[1].strip().upper() + + # Abuse Contact + elif 'abuse' in line_lower and '@' in line: + # E-Mail aus der Zeile extrahieren + import re + email_match = re.search(r'[\w\.-]+@[\w\.-]+\.\w+', line) + if email_match and not result['abuse']: + result['abuse'] = email_match.group(0) + + # IP Range (inetnum/NetRange) + elif line_lower.startswith('inetnum:') or line_lower.startswith('netrange:'): + result['range'] = line.split(':', 1)[1].strip() + elif line_lower.startswith('cidr:') and not result['range']: + result['range'] = line.split(':', 1)[1].strip() + + # Fallback: descr als org wenn org leer + if not result['org'] and result['descr']: + result['org'] = result['descr'] + + logger.debug(f"WHOIS für {ip}: netname={result['netname']}, org={result['org']}, asn={result['asn']}") + + except subprocess.TimeoutExpired: + logger.warning(f"WHOIS Timeout für {ip}") + except FileNotFoundError: + logger.error("WHOIS-Tool nicht installiert! Bitte 'whois' installieren.") + except Exception as e: + logger.error(f"WHOIS Fehler für {ip}: {e}") + + _whois_cache[ip] = result + return result + + # ============================================================================= # LIVE STATISTICS TRACKER # ============================================================================= @@ -3889,6 +3999,9 @@ class JTLWAFiAgent: elif event_type == 'command.autoban_config': await self._handle_autoban_config_command(event_data) + elif event_type == 'command.whois': + await self._handle_whois_command(event_data) + elif event_type == 'log.subscribe': shop = event_data.get('shop') if shop: @@ -4376,6 +4489,47 @@ class JTLWAFiAgent: 'message': str(e) }) + async def _handle_whois_command(self, data: Dict[str, Any]): + """Führt WHOIS-Lookup für eine IP durch und sendet Ergebnis.""" + command_id = data.get('command_id', 'unknown') + ip = data.get('ip') + + if not ip: + await self._send_event('whois_result', { + 'command_id': command_id, + 'success': False, + 'error': 'Keine IP angegeben' + }) + return + + try: + # WHOIS-Lookup durchführen (wird gecacht) + result = whois_lookup(ip) + + # Ergebnis senden (ohne raw für kleinere Payload) + await self._send_event('whois_result', { + 'command_id': command_id, + 'success': True, + 'ip': ip, + 'netname': result.get('netname', ''), + 'org': result.get('org', ''), + 'asn': result.get('asn', ''), + 'country': result.get('country', ''), + 'abuse': result.get('abuse', ''), + 'range': result.get('range', '') + }) + + logger.info(f"WHOIS für {ip}: {result.get('org', 'Unknown')} ({result.get('asn', 'N/A')})") + + except Exception as e: + logger.error(f"WHOIS Fehler für {ip}: {e}") + await self._send_event('whois_result', { + 'command_id': command_id, + 'success': False, + 'ip': ip, + 'error': str(e) + }) + async def _periodic_tasks(self): """Führt periodische Tasks aus.""" while self.running: diff --git a/jtl-wafi-dashboard.py b/jtl-wafi-dashboard.py index c140bf2..7f2a91e 100644 --- a/jtl-wafi-dashboard.py +++ b/jtl-wafi-dashboard.py @@ -798,6 +798,12 @@ async def agent_websocket(websocket: WebSocket): 'data': event_data }) + elif event_type == 'whois_result': + await manager.broadcast_to_browsers({ + 'type': 'whois_result', + 'data': event_data + }) + except json.JSONDecodeError: pass except Exception as e: @@ -900,6 +906,19 @@ async def dashboard_websocket(websocket: WebSocket): 'data': event_data }) + elif event_type == 'command.whois': + # WHOIS ist Shop-unabhängig - nutze einen beliebigen Online-Agent + agent_id = None + for aid, ws in manager.agents.items(): + if ws: + agent_id = aid + break + if agent_id: + await manager.send_to_agent(agent_id, { + 'type': 'command.whois', + 'data': event_data + }) + elif event_type == 'get_shop_history': domain = event_data.get('domain') data = store.get_shop_history(domain) @@ -1691,6 +1710,7 @@ def get_dashboard_html() -> str: +