e31bdeb59d
- inwx_common.py: Login mit Session-Cache, KP_PW-Option - inwx_list/add: Domain als Argument, gemeinsamer Helfer - inwx_domains(_owner).py: Domains alphabetisch, NS, Gruppen via citeq-TXT (DNS) - hosting.kdbx aus Tracking genommen und in .gitignore Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
157 lines
5.1 KiB
Python
157 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
"""Listet alle für den Benutzer sichtbaren Domains alphabetisch sortiert auf."""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
from inwx_common import get_client, call_api
|
||
|
||
PAGE_LIMIT = 100 # Domains pro API-Seite
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
OWNER_FILE = os.path.join(BASE_DIR, "domain_owner")
|
||
TXT_PREFIX = "citeq:" # Präfix der Gruppen-TXT-Einträge
|
||
|
||
|
||
def fetch_all_domains(api):
|
||
"""Holt alle Domains über alle Seiten hinweg (domain.list ist paginiert)."""
|
||
domains = []
|
||
page = 1
|
||
while True:
|
||
res = call_api(api, 'domain.list', {'page': page, 'pageLimit': PAGE_LIMIT})
|
||
if res['code'] != 1000:
|
||
print(f"❌ API Fehler: {res['msg']} (Code: {res['code']})")
|
||
sys.exit(1)
|
||
|
||
data = res['resData']
|
||
domains.extend(data.get('domain', []))
|
||
|
||
# Solange wir noch nicht alle laut 'count' geladen haben, weiterblättern.
|
||
if len(domains) >= data.get('count', 0) or not data.get('domain'):
|
||
break
|
||
page += 1
|
||
|
||
return domains
|
||
|
||
|
||
def get_nameservers(api, domain):
|
||
"""Ermittelt die zuständigen Nameserver (Registry-Delegation) einer Domain.
|
||
|
||
Nutzt domain.info -> Feld 'ns', das die delegierten Nameserver enthält und
|
||
auch dann funktioniert, wenn die DNS-Zone nicht bei INWX gehostet wird.
|
||
Liefert eine sortierte Liste der NS-Hostnamen oder None bei Fehler.
|
||
"""
|
||
res = call_api(api, 'domain.info', {'domain': domain})
|
||
if res['code'] != 1000:
|
||
return None
|
||
|
||
ns = res['resData'].get('ns') or []
|
||
return sorted(n.rstrip('.') for n in ns)
|
||
|
||
|
||
def load_owners():
|
||
"""Liest die Zuordnung Ziffer -> Klarname aus der Datei domain_owner.
|
||
|
||
Format pro Zeile: <ziffer><Whitespace><klarname>, wobei als Trenner
|
||
beliebiger Whitespace dient (ein oder mehrere Tabs/Leerzeichen). Der
|
||
Klarname darf selbst Leerzeichen enthalten.
|
||
Leere Zeilen und Kommentarzeilen (#) werden ignoriert.
|
||
"""
|
||
owners = {}
|
||
if not os.path.exists(OWNER_FILE):
|
||
print(f"⚠️ Datei {OWNER_FILE} nicht gefunden – Gruppen werden ohne Klarnamen angezeigt.")
|
||
return owners
|
||
try:
|
||
with open(OWNER_FILE, "r") as f:
|
||
for line in f:
|
||
line = line.strip()
|
||
if not line or line.startswith("#"):
|
||
continue
|
||
parts = line.split(None, 1) # an erstem Whitespace (Tab/Space) trennen
|
||
if len(parts) == 2:
|
||
ziffer, klarname = parts
|
||
owners[ziffer.strip()] = klarname.strip()
|
||
except Exception as e:
|
||
print(f"⚠️ Konnte {OWNER_FILE} nicht lesen: {e}")
|
||
return owners
|
||
|
||
|
||
def get_group(domain):
|
||
"""Ermittelt die Gruppen-Ziffer aus dem citeq:-TXT-Eintrag einer Domain.
|
||
|
||
Nutzt eine echte DNS-Abfrage (dig +short TXT) und funktioniert daher
|
||
unabhängig davon, wo die DNS-Zone gehostet wird. Liefert die Ziffer als
|
||
String oder None, wenn kein passender TXT-Eintrag gefunden wird.
|
||
"""
|
||
try:
|
||
out = subprocess.run(
|
||
["dig", "+short", "TXT", domain],
|
||
capture_output=True, text=True, timeout=10,
|
||
)
|
||
except FileNotFoundError:
|
||
print("⚠️ 'dig' nicht gefunden – Gruppen können nicht ermittelt werden.")
|
||
return None
|
||
except subprocess.TimeoutExpired:
|
||
return None
|
||
|
||
if out.returncode != 0:
|
||
return None
|
||
|
||
for line in out.stdout.splitlines():
|
||
# dig liefert TXT-Werte in Anführungszeichen, lange Werte ggf. in
|
||
# mehreren Teilen ("teil1" "teil2") -> zusammenfügen und entkleiden.
|
||
content = line.replace('" "', '').strip().strip('"')
|
||
if content.startswith(TXT_PREFIX):
|
||
return content[len(TXT_PREFIX):].strip()
|
||
return None
|
||
|
||
|
||
def main():
|
||
api = get_client()
|
||
|
||
print("Rufe Domain-Liste ab...")
|
||
domains = fetch_all_domains(api)
|
||
|
||
if not domains:
|
||
print("ℹ️ Keine Domains gefunden.")
|
||
return
|
||
|
||
# Zuordnung Ziffer -> Klarname laden
|
||
owners = load_owners()
|
||
|
||
# Alphabetisch sortieren (Groß-/Kleinschreibung ignorieren)
|
||
domains.sort(key=lambda d: d['domain'].lower())
|
||
|
||
print(f"\n{'DOMAIN':<35} {'STATUS':<8} {'GRUPPE':<18} {'NAMESERVER'}")
|
||
print("-" * 100)
|
||
for d in domains:
|
||
name = d.get('domain', '')
|
||
status = d.get('status', '')
|
||
|
||
# Gruppen-Ziffer aus citeq:-TXT (per DNS) ermitteln und Klarnamen zuordnen
|
||
ziffer = get_group(name)
|
||
if ziffer is None:
|
||
group = "-"
|
||
elif ziffer in owners:
|
||
group = f"{ziffer} ({owners[ziffer]})"
|
||
else:
|
||
group = f"{ziffer} (?)"
|
||
|
||
# Zuständige Nameserver pro Domain ermitteln
|
||
ns_list = get_nameservers(api, name)
|
||
if ns_list is None:
|
||
ns = "(nicht abrufbar)"
|
||
elif not ns_list:
|
||
ns = "(keine NS-Einträge gefunden)"
|
||
else:
|
||
ns = ", ".join(ns_list)
|
||
|
||
print(f"{name:<35} {status:<8} {group:<18} {ns}")
|
||
|
||
print(f"\nGesamt: {len(domains)} Domain(s)")
|
||
|
||
# Kein logout() -> die Session bleibt gültig und kann wiederverwendet werden.
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|