372 lines
11 KiB
PHP
372 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* HexaDNS - WHOIS Lookup API
|
|
*
|
|
* Ruft WHOIS-Informationen für eine Domain ab
|
|
*
|
|
* Verwendung: GET /api/whois-lookup.php?domain=example.com
|
|
*/
|
|
|
|
require_once __DIR__ . '/../includes/api-helpers.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(200);
|
|
exit;
|
|
}
|
|
|
|
if (!checkApiRateLimit('whois-lookup')) {
|
|
rejectApiRateLimit();
|
|
}
|
|
|
|
$domain = isset($_GET['domain']) ? trim((string) $_GET['domain']) : '';
|
|
|
|
if (empty($domain)) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Domain-Parameter fehlt']);
|
|
exit;
|
|
}
|
|
|
|
// Nur Root-Domain extrahieren
|
|
$domain = extractRootDomain($domain);
|
|
|
|
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}$/', $domain)) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Ungültiges Domain-Format']);
|
|
exit;
|
|
}
|
|
|
|
$startTime = microtime(true);
|
|
$whoisData = performWhoisLookup($domain);
|
|
$queryTime = round((microtime(true) - $startTime) * 1000, 2);
|
|
|
|
if ($whoisData === null) {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'WHOIS-Abfrage fehlgeschlagen']);
|
|
exit;
|
|
}
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'domain' => $domain,
|
|
'query_time_ms' => $queryTime,
|
|
'timestamp' => date('Y-m-d H:i:s'),
|
|
'whois' => $whoisData
|
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
|
|
/**
|
|
* Extrahiert die Root-Domain (ohne Subdomain)
|
|
*/
|
|
function extractRootDomain(string $domain): string {
|
|
$domain = strtolower($domain);
|
|
$domain = preg_replace('/^(https?:\/\/)?(www\.)?/', '', $domain);
|
|
$domain = explode('/', $domain)[0];
|
|
|
|
$parts = explode('.', $domain);
|
|
if (count($parts) > 2) {
|
|
// Einfache Logik: nimm die letzten 2 Teile
|
|
// (funktioniert nicht perfekt für .co.uk etc., aber gut genug)
|
|
return implode('.', array_slice($parts, -2));
|
|
}
|
|
|
|
return $domain;
|
|
}
|
|
|
|
/**
|
|
* Führt WHOIS-Lookup durch
|
|
*/
|
|
function performWhoisLookup(string $domain): ?array {
|
|
// Primär: Socket-basierte Abfrage (funktioniert ohne shell_exec)
|
|
$whoisRaw = whoisViaSocket($domain);
|
|
|
|
// Fallback: Shell-Kommando (sicher escaped)
|
|
if (empty($whoisRaw) && function_exists('shell_exec')) {
|
|
$escapedDomain = escapeshellarg($domain);
|
|
$whoisRaw = @shell_exec("whois {$escapedDomain} 2>/dev/null");
|
|
}
|
|
|
|
if (empty($whoisRaw)) {
|
|
return null;
|
|
}
|
|
|
|
// Parse WHOIS-Daten
|
|
return parseWhoisData($whoisRaw, $domain);
|
|
}
|
|
|
|
/**
|
|
* WHOIS-Abfrage über Socket (unabhängig von shell_exec)
|
|
*/
|
|
function whoisViaSocket(string $domain): ?string {
|
|
$whoisServer = getWhoisServer($domain);
|
|
|
|
if (!$whoisServer) {
|
|
return null;
|
|
}
|
|
|
|
$result = queryWhoisServer($whoisServer, $domain);
|
|
|
|
// Prüfe auf Weiterleitungen zu anderen WHOIS-Servern
|
|
if ($result && preg_match('/Registrar WHOIS Server:\s*(\S+)/i', $result, $matches)) {
|
|
$referralServer = trim($matches[1]);
|
|
if ($referralServer && $referralServer !== $whoisServer) {
|
|
$referralResult = queryWhoisServer($referralServer, $domain);
|
|
if ($referralResult) {
|
|
$result = $referralResult;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Abfrage an einen spezifischen WHOIS-Server
|
|
*/
|
|
function queryWhoisServer(string $server, string $domain): ?string {
|
|
$port = 43;
|
|
$timeout = 10;
|
|
|
|
$socket = @fsockopen($server, $port, $errno, $errstr, $timeout);
|
|
|
|
if (!$socket) {
|
|
return null;
|
|
}
|
|
|
|
// Setze Stream-Timeout
|
|
stream_set_timeout($socket, $timeout);
|
|
|
|
// Sende Anfrage
|
|
fwrite($socket, $domain . "\r\n");
|
|
|
|
// Lese Antwort
|
|
$response = '';
|
|
while (!feof($socket)) {
|
|
$response .= fread($socket, 8192);
|
|
}
|
|
|
|
fclose($socket);
|
|
|
|
return !empty($response) ? $response : null;
|
|
}
|
|
|
|
/**
|
|
* Ermittelt den zuständigen WHOIS-Server für eine TLD
|
|
*/
|
|
function getWhoisServer(string $domain): ?string {
|
|
$parts = explode('.', $domain);
|
|
$tld = strtolower(end($parts));
|
|
|
|
// Bekannte WHOIS-Server nach TLD
|
|
$whoisServers = [
|
|
// Generische TLDs
|
|
'com' => 'whois.verisign-grs.com',
|
|
'net' => 'whois.verisign-grs.com',
|
|
'org' => 'whois.pir.org',
|
|
'info' => 'whois.afilias.net',
|
|
'biz' => 'whois.biz',
|
|
'name' => 'whois.nic.name',
|
|
'mobi' => 'whois.dotmobiregistry.net',
|
|
'pro' => 'whois.registrypro.pro',
|
|
'aero' => 'whois.aero',
|
|
'asia' => 'whois.nic.asia',
|
|
'cat' => 'whois.nic.cat',
|
|
'coop' => 'whois.nic.coop',
|
|
'edu' => 'whois.educause.edu',
|
|
'gov' => 'whois.dotgov.gov',
|
|
'int' => 'whois.iana.org',
|
|
'jobs' => 'whois.nic.jobs',
|
|
'mil' => 'whois.nic.mil',
|
|
'museum' => 'whois.museum',
|
|
'post' => 'whois.dotpostregistry.net',
|
|
'tel' => 'whois.nic.tel',
|
|
'travel' => 'whois.nic.travel',
|
|
'xxx' => 'whois.nic.xxx',
|
|
|
|
// Neue gTLDs
|
|
'app' => 'whois.nic.google',
|
|
'dev' => 'whois.nic.google',
|
|
'page' => 'whois.nic.google',
|
|
'blog' => 'whois.nic.blog',
|
|
'cloud' => 'whois.nic.cloud',
|
|
'shop' => 'whois.nic.shop',
|
|
'store' => 'whois.nic.store',
|
|
'online' => 'whois.nic.online',
|
|
'site' => 'whois.nic.site',
|
|
'website' => 'whois.nic.website',
|
|
'tech' => 'whois.nic.tech',
|
|
'io' => 'whois.nic.io',
|
|
'co' => 'whois.nic.co',
|
|
'me' => 'whois.nic.me',
|
|
'tv' => 'whois.nic.tv',
|
|
'cc' => 'ccwhois.verisign-grs.com',
|
|
'ws' => 'whois.website.ws',
|
|
|
|
// Europäische ccTLDs
|
|
'de' => 'whois.denic.de',
|
|
'at' => 'whois.nic.at',
|
|
'ch' => 'whois.nic.ch',
|
|
'li' => 'whois.nic.li',
|
|
'uk' => 'whois.nic.uk',
|
|
'fr' => 'whois.nic.fr',
|
|
'it' => 'whois.nic.it',
|
|
'es' => 'whois.nic.es',
|
|
'pt' => 'whois.dns.pt',
|
|
'nl' => 'whois.domain-registry.nl',
|
|
'be' => 'whois.dns.be',
|
|
'pl' => 'whois.dns.pl',
|
|
'cz' => 'whois.nic.cz',
|
|
'sk' => 'whois.sk-nic.sk',
|
|
'hu' => 'whois.nic.hu',
|
|
'ro' => 'whois.rotld.ro',
|
|
'bg' => 'whois.register.bg',
|
|
'hr' => 'whois.dns.hr',
|
|
'si' => 'whois.register.si',
|
|
'rs' => 'whois.rnids.rs',
|
|
'gr' => 'grwhois.ics.forth.gr',
|
|
'dk' => 'whois.punktum.dk',
|
|
'se' => 'whois.iis.se',
|
|
'no' => 'whois.norid.no',
|
|
'fi' => 'whois.fi',
|
|
'ie' => 'whois.iedr.ie',
|
|
'eu' => 'whois.eu',
|
|
'lu' => 'whois.dns.lu',
|
|
|
|
// Andere ccTLDs
|
|
'ru' => 'whois.tcinet.ru',
|
|
'ua' => 'whois.ua',
|
|
'us' => 'whois.nic.us',
|
|
'ca' => 'whois.cira.ca',
|
|
'mx' => 'whois.mx',
|
|
'br' => 'whois.registro.br',
|
|
'ar' => 'whois.nic.ar',
|
|
'au' => 'whois.auda.org.au',
|
|
'nz' => 'whois.srs.net.nz',
|
|
'jp' => 'whois.jprs.jp',
|
|
'kr' => 'whois.kr',
|
|
'cn' => 'whois.cnnic.cn',
|
|
'in' => 'whois.registry.in',
|
|
'sg' => 'whois.sgnic.sg',
|
|
'hk' => 'whois.hkirc.hk',
|
|
'tw' => 'whois.twnic.net.tw',
|
|
'za' => 'whois.registry.net.za',
|
|
];
|
|
|
|
// Spezielle Behandlung für .co.uk, .com.au etc.
|
|
if (count($parts) >= 2) {
|
|
$sld = $parts[count($parts) - 2];
|
|
$combinedTld = $sld . '.' . $tld;
|
|
|
|
$secondLevelTlds = [
|
|
'co.uk' => 'whois.nic.uk',
|
|
'org.uk' => 'whois.nic.uk',
|
|
'me.uk' => 'whois.nic.uk',
|
|
'com.au' => 'whois.auda.org.au',
|
|
'net.au' => 'whois.auda.org.au',
|
|
'org.au' => 'whois.auda.org.au',
|
|
'co.nz' => 'whois.srs.net.nz',
|
|
'com.br' => 'whois.registro.br',
|
|
];
|
|
|
|
if (isset($secondLevelTlds[$combinedTld])) {
|
|
return $secondLevelTlds[$combinedTld];
|
|
}
|
|
}
|
|
|
|
return $whoisServers[$tld] ?? 'whois.iana.org';
|
|
}
|
|
|
|
/**
|
|
* Parsed WHOIS-Rohdaten in strukturiertes Format
|
|
*/
|
|
function parseWhoisData(string $raw, string $domain): array {
|
|
$data = [
|
|
'raw' => $raw,
|
|
'parsed' => [
|
|
'domain_name' => $domain,
|
|
'registrar' => null,
|
|
'registrar_url' => null,
|
|
'creation_date' => null,
|
|
'expiration_date' => null,
|
|
'updated_date' => null,
|
|
'status' => [],
|
|
'nameservers' => [],
|
|
'dnssec' => null,
|
|
]
|
|
];
|
|
|
|
$lines = explode("\n", $raw);
|
|
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if (empty($line) || strpos($line, '%') === 0 || strpos($line, '#') === 0) {
|
|
continue;
|
|
}
|
|
|
|
// Key: Value Format
|
|
if (strpos($line, ':') !== false) {
|
|
list($key, $value) = array_map('trim', explode(':', $line, 2));
|
|
$keyLower = strtolower($key);
|
|
|
|
// Registrar
|
|
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'abuse') === false && strpos($keyLower, 'url') === false) {
|
|
if (empty($data['parsed']['registrar'])) {
|
|
$data['parsed']['registrar'] = $value;
|
|
}
|
|
}
|
|
|
|
// Registrar URL
|
|
if (strpos($keyLower, 'registrar') !== false && strpos($keyLower, 'url') !== false) {
|
|
$data['parsed']['registrar_url'] = $value;
|
|
}
|
|
|
|
// Erstellungsdatum
|
|
if (preg_match('/(creation|created|registered)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
|
if (empty($data['parsed']['creation_date'])) {
|
|
$data['parsed']['creation_date'] = $value;
|
|
}
|
|
}
|
|
|
|
// Ablaufdatum
|
|
if (preg_match('/(expir|paid-till)/i', $keyLower)) {
|
|
if (empty($data['parsed']['expiration_date'])) {
|
|
$data['parsed']['expiration_date'] = $value;
|
|
}
|
|
}
|
|
|
|
// Aktualisierungsdatum
|
|
if (preg_match('/(updated|modified|changed)/i', $keyLower) && strpos($keyLower, 'registrar') === false) {
|
|
if (empty($data['parsed']['updated_date'])) {
|
|
$data['parsed']['updated_date'] = $value;
|
|
}
|
|
}
|
|
|
|
// Status
|
|
if (preg_match('/(status|state)/i', $keyLower) && !empty($value)) {
|
|
$data['parsed']['status'][] = $value;
|
|
}
|
|
|
|
// Nameserver
|
|
if (preg_match('/^(name.?server|nserver)/i', $keyLower) && !empty($value)) {
|
|
$ns = strtolower(explode(' ', $value)[0]);
|
|
if (!in_array($ns, $data['parsed']['nameservers'])) {
|
|
$data['parsed']['nameservers'][] = $ns;
|
|
}
|
|
}
|
|
|
|
// DNSSEC
|
|
if (strpos($keyLower, 'dnssec') !== false) {
|
|
$data['parsed']['dnssec'] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Status einzigartig machen
|
|
$data['parsed']['status'] = array_unique($data['parsed']['status']);
|
|
|
|
return $data;
|
|
}
|