Session-Caching, Domain-/Gruppen-Skripte; KeePass-DB aus Repo entfernen
- 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>
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user