Use dnspython for domain TXT verification

This commit is contained in:
A R R R Associates
2026-07-09 11:08:58 +05:30
parent 94aa8292e2
commit b7c772f6e1
2 changed files with 35 additions and 52 deletions
+35 -52
View File
@@ -4,7 +4,6 @@ import re
import secrets
import socket
import ssl
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
@@ -598,64 +597,53 @@ def _normalise_txt_value(value: str) -> str:
def _resolve_txt_with_dnspython(txt_name: str) -> tuple[list[str], str | None]:
"""Resolve TXT records using the Python DNS resolver.
Production containers should not depend on OS tools such as nslookup/dig.
The package dependency is declared in requirements.txt as dnspython.
"""
try:
import dns.exception # type: ignore
import dns.resolver # type: ignore
except Exception:
return [], "dnspython is not installed"
except Exception as exc:
return [], f"dnspython package is not installed or not importable: {exc}"
resolver = dns.resolver.Resolver(configure=True)
resolver.lifetime = 12.0
resolver.timeout = 4.0
try:
answers = dns.resolver.resolve(txt_name, "TXT")
answers = resolver.resolve(txt_name, "TXT")
except dns.resolver.NXDOMAIN:
return [], f"TXT name does not exist: {txt_name}"
except dns.resolver.NoAnswer:
return [], f"No TXT records found for: {txt_name}"
except dns.resolver.NoNameservers as exc:
return [], f"DNS nameserver failed for {txt_name}: {exc}"
except dns.exception.Timeout:
return [], f"DNS lookup timed out for: {txt_name}"
except Exception as exc:
return [], f"DNS lookup failed for {txt_name}: {exc}"
values: list[str] = []
for answer in answers:
try:
chunks = [part.decode("utf-8", errors="ignore") if isinstance(part, bytes) else str(part) for part in answer.strings]
values.append("".join(chunks))
# dnspython TXT records may be split into multiple byte chunks.
chunks = [
part.decode("utf-8", errors="ignore") if isinstance(part, bytes) else str(part)
for part in getattr(answer, "strings", [])
]
values.append("".join(chunks) if chunks else answer.to_text().strip())
except Exception:
values.append(str(answer).strip())
return values, None
except Exception as exc:
return [], str(exc)
def _resolve_txt_with_nslookup(txt_name: str) -> tuple[list[str], str | None]:
try:
completed = subprocess.run(
["nslookup", "-type=TXT", txt_name],
capture_output=True,
text=True,
timeout=12,
check=False,
)
except FileNotFoundError:
return [], "nslookup command is not available on this machine"
except Exception as exc:
return [], str(exc)
output = "\n".join([completed.stdout or "", completed.stderr or ""])
if completed.returncode != 0 and not output.strip():
return [], "DNS lookup failed"
values: list[str] = []
for line in output.splitlines():
line = line.strip()
if not line:
continue
# Windows/Linux nslookup generally prints TXT values inside quotes.
quoted = re.findall(r'"([^"]+)"', line)
if quoted:
values.append("".join(quoted))
continue
if "text =" in line.lower():
values.append(line.split("=", 1)[1].strip())
if not values and output.strip():
# Keep a short diagnostic without storing full command noise.
return [], output.strip().splitlines()[-1][:240]
return values, None
def lookup_dns_txt_values(txt_name: str) -> tuple[list[str], str | None]:
"""Return TXT records for a DNS name using dnspython when available, else nslookup.
"""Return TXT records for a DNS name using dnspython only.
No new dependency is required. On Windows, nslookup is normally available by default.
This avoids failures in minimal Docker/Coolify containers where the nslookup
command is not installed. Install dependencies with `pip install -r requirements.txt`.
"""
txt_name = normalize_domain(txt_name)
if not txt_name:
@@ -664,12 +652,7 @@ def lookup_dns_txt_values(txt_name: str) -> tuple[list[str], str | None]:
values, error = _resolve_txt_with_dnspython(txt_name)
if values:
return [_normalise_txt_value(v) for v in values], None
ns_values, ns_error = _resolve_txt_with_nslookup(txt_name)
if ns_values:
return [_normalise_txt_value(v) for v in ns_values], None
return [], ns_error or error or "No TXT record found"
return [], error or "No TXT record found"
def verify_domain_dns_txt(db: Session, mapping: DomainMapping, *, user_id: int | None) -> DomainDnsVerificationResult:
+1 -1
View File
@@ -12,6 +12,6 @@ alembic==1.14.0
psycopg[binary]==3.2.3
pydantic[email]
email-validator
dnspython==2.7.0
openpyxl
dnspython
itsdangerous