"""Live DNS checks for company domain onboarding.""" from __future__ import annotations import dns.exception import dns.resolver def _normalize_txt(value: str) -> str: return " ".join(value.split()).strip().lower() def _lookup_txt(name: str) -> list[str]: try: answers = dns.resolver.resolve(name, "TXT") except ( dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.exception.Timeout, dns.resolver.LifetimeTimeout, ): return [] records: list[str] = [] for answer in answers: parts = [] for chunk in answer.strings: if isinstance(chunk, bytes): parts.append(chunk.decode("utf-8", errors="replace")) else: parts.append(str(chunk)) records.append("".join(parts)) return records def _lookup_mx(domain_name: str) -> list[tuple[int, str]]: try: answers = dns.resolver.resolve(domain_name, "MX") except ( dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.exception.Timeout, dns.resolver.LifetimeTimeout, ): return [] hosts: list[tuple[int, str]] = [] for answer in answers: host = str(answer.exchange).rstrip(".").lower() hosts.append((int(answer.preference), host)) return hosts def verify_mx(domain_name: str, expected_host: str) -> tuple[bool, str]: expected = expected_host.rstrip(".").lower() hosts = _lookup_mx(domain_name) if not hosts: return False, f"No MX records found for {domain_name}." hostnames = [host for _, host in hosts] if expected in hostnames: return True, f"MX points to {expected_host}." return False, f"Expected MX host {expected_host}; found {', '.join(hostnames)}." def verify_spf(domain_name: str, expected_include: str) -> tuple[bool, str]: include_token = expected_include.rstrip(".").lower() records = [_normalize_txt(record) for record in _lookup_txt(domain_name)] spf_records = [record for record in records if record.startswith("v=spf1")] if not spf_records: return False, f"No SPF TXT record found at {domain_name}." needle = f"include:{include_token}" for record in spf_records: if needle in record: return True, f"SPF includes {expected_include}." return False, f"SPF record must include {expected_include}." def _lookup_a(name: str) -> list[str]: try: answers = dns.resolver.resolve(name, "A") except ( dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.exception.Timeout, dns.resolver.LifetimeTimeout, ): return [] return [str(answer).strip() for answer in answers] def verify_platform_spf(platform_spf_host: str, mail_host: str) -> tuple[bool, str]: """Ensure the shared SPF include target exists and authorizes the mail host.""" records = [_normalize_txt(record) for record in _lookup_txt(platform_spf_host)] spf_records = [record for record in records if record.startswith("v=spf1")] if not spf_records: return ( False, f"No SPF TXT at {platform_spf_host}. On getall.help add Host _spf with " f"v=spf1 ip4: ~all.", ) spf_value = spf_records[0] mail_ips = _lookup_a(mail_host.rstrip(".")) for ip in mail_ips: if f"ip4:{ip}" in spf_value or f"ip6:{ip}" in spf_value: return True, f"{platform_spf_host} authorizes {mail_host} ({ip})." if "ip4:" in spf_value or "ip6:" in spf_value or " a " in f" {spf_value} ": return True, f"{platform_spf_host} SPF record is present." return ( False, f"{platform_spf_host} exists but does not appear to authorize the mail server.", ) def verify_dkim( domain_name: str, selector: str, expected_public_key: str, ) -> tuple[bool, str]: name = f"{selector}._domainkey.{domain_name}" records = [_normalize_txt(record) for record in _lookup_txt(name)] if not records: return False, f"No DKIM TXT record found at {name}." key_token = expected_public_key.strip().lower() for record in records: if key_token in record.replace(" ", ""): return True, "DKIM public key matches." return False, f"DKIM TXT at {selector}._domainkey is missing the expected public key." def verify_dmarc(domain_name: str, expected_value: str) -> tuple[bool, str]: name = f"_dmarc.{domain_name}" records = [_normalize_txt(record) for record in _lookup_txt(name)] if not records: return False, f"No DMARC TXT record found at {name}." expected = _normalize_txt(expected_value) for record in records: if record == expected or record.startswith("v=dmarc1"): if "v=dmarc1" in record: return True, "DMARC record is present." return False, "DMARC TXT record is missing or incomplete." def verify_company_dns( *, domain_name: str, shared_mail_host: str, dkim_selector: str, dkim_public_key: str, platform_spf_include: str, ) -> list[dict]: dmarc_expected = f"v=DMARC1; p=quarantine; rua=mailto:dmarc@{domain_name}" checks = [ ("mx", *verify_mx(domain_name, shared_mail_host)), ("spf", *verify_spf(domain_name, platform_spf_include)), ( "platform_spf", *verify_platform_spf(platform_spf_include, shared_mail_host), ), ( "dkim", *verify_dkim(domain_name, dkim_selector, dkim_public_key), ), ("dmarc", *verify_dmarc(domain_name, dmarc_expected)), ] return [ { "purpose": purpose, "verified": verified, "message": message, } for purpose, verified, message in checks ]