- better debugging for DNS queries

- don't try to guess IP addresses but insist on A and AAAA records
- try to allow ipv4 or ipv6 only zones
- move chatmail.zone generation to jinja so we can have conditionals
This commit is contained in:
holger krekel
2024-07-10 15:45:39 +02:00
parent be5b25b0ab
commit 2e5a1a3a67
4 changed files with 48 additions and 47 deletions

View File

@@ -1,15 +0,0 @@
{chatmail_domain}. A {ipv4}
{chatmail_domain}. AAAA {ipv6}
{chatmail_domain}. MX 10 {chatmail_domain}.
_submission._tcp.{chatmail_domain}. SRV 0 1 587 {chatmail_domain}.
_submissions._tcp.{chatmail_domain}. SRV 0 1 465 {chatmail_domain}.
_imap._tcp.{chatmail_domain}. SRV 0 1 143 {chatmail_domain}.
_imaps._tcp.{chatmail_domain}. SRV 0 1 993 {chatmail_domain}.
{chatmail_domain}. CAA 128 issue "letsencrypt.org;accounturi={acme_account_url}"
{chatmail_domain}. TXT "v=spf1 a:{chatmail_domain} ~all"
_dmarc.{chatmail_domain}. TXT "v=DMARC1;p=reject;adkim=s;aspf=s"
_mta-sts.{chatmail_domain}. TXT "v=STSv1; id={sts_id}"
mta-sts.{chatmail_domain}. CNAME {chatmail_domain}.
www.{chatmail_domain}. CNAME {chatmail_domain}.
{dkim_entry}
_adsp._domainkey.{chatmail_domain}. TXT "dkim=discardable"

View File

@@ -0,0 +1,21 @@
{% if ipv4 %}
{{ chatmail_domain }}. A {{ ' '.join(ipv4) }}
{% endif %}
{% if ipv6 %}
{{ chatmail_domain }}. AAAA {{ ' '.join(ipv6) }}
{% endif %}
{{ chatmail_domain }}. MX 10 {{ chatmail_domain }}.
_submission._tcp.{{ chatmail_domain }}. SRV 0 1 587 {{ chatmail_domain }}.
_submissions._tcp.{{ chatmail_domain }}. SRV 0 1 465 {{ chatmail_domain }}.
_imap._tcp.{{ chatmail_domain }}. SRV 0 1 143 {{ chatmail_domain }}.
_imaps._tcp.{{ chatmail_domain }}. SRV 0 1 993 {{ chatmail_domain }}.
{% if acme_account_url %}
{{ chatmail_domain }}. CAA 128 issue "letsencrypt.org;accounturi={{ acme_account_url }}"
{% endif %}
{{ chatmail_domain }}. TXT "v=spf1 a:{{ chatmail_domain }} ~all"
_dmarc.{{ chatmail_domain }}. TXT "v=DMARC1;p=reject;adkim=s;aspf=s"
_mta-sts.{{ chatmail_domain }}. TXT "v=STSv1; id={{ sts_id }}"
mta-sts.{{ chatmail_domain }}. CNAME {{ chatmail_domain }}.
www.{{ chatmail_domain }}. CNAME {{ chatmail_domain }}.
{{ dkim_entry }}
_adsp._domainkey.{{ chatmail_domain }}. TXT "dkim=discardable"

View File

@@ -2,13 +2,15 @@ import datetime
import importlib
import sys
from jinja2 import Template
from . import remote_funcs
def show_dns(args, out) -> int:
def show_dns(args, out, fullcheck=True) -> int:
"""Check existing DNS records, optionally write them to zone file
and return (exitcode, remote_data) tuple."""
template = importlib.resources.files(__package__).joinpath("chatmail.zone.f")
template = importlib.resources.files(__package__).joinpath("chatmail.zone.j2")
mail_domain = args.config.mail_domain
def log_progress(data):
@@ -20,19 +22,25 @@ def show_dns(args, out) -> int:
remote_data = sshexec(remote_funcs.perform_initial_checks, mail_domain=mail_domain)
assert remote_data["ipv4"] or remote_data["ipv6"]
if not remote_data["ipv4"] and not remote_data["ipv6"]:
print()
print(f"no A and also no AAAA record set for domain: {mail_domain}")
raise SystemExit(1)
with open(template, "r") as f:
zonefile = f.read().format(
acme_account_url=remote_data["acme_account_url"],
dkim_entry=remote_data["dkim_entry"],
ipv6=remote_data["ipv6"],
ipv4=remote_data["ipv4"],
sts_id=datetime.datetime.now().strftime("%Y%m%d%H%M"),
chatmail_domain=args.config.mail_domain,
)
content = template.read_text()
zonefile = Template(content).render(
acme_account_url=remote_data.get("acme_account_url"),
dkim_entry=remote_data.get("dkim_entry"),
ipv4=remote_data["ipv4"],
ipv6=remote_data["ipv6"],
sts_id=datetime.datetime.now().strftime("%Y%m%d%H%M"),
chatmail_domain=args.config.mail_domain,
)
zonefile = "\n".join([x.strip() for x in zonefile.split("\n") if x.strip()])
to_print = sshexec(remote_funcs.check_zonefile, zonefile=zonefile)
to_print = sshexec(
remote_funcs.check_zonefile, zonefile=zonefile, fullcheck=fullcheck
)
if not args.verbose:
print()

View File

@@ -10,7 +10,6 @@ without any installed dependencies.
"""
import re
import socket
from subprocess import CalledProcessError, check_output
@@ -36,10 +35,9 @@ def perform_initial_checks(mail_domain):
if not shell("dig", fail_ok=True):
shell("apt-get install -y dnsutils")
shell(f"unbound-control flush_zone {mail_domain}", fail_ok=True)
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")
res["ipv4"] = get_ip_address(socket.AF_INET)
res["ipv6"] = get_ip_address(socket.AF_INET6)
res["ipv4"] = query_dns("A", mail_domain)
res["ipv6"] = query_dns("AAAA", mail_domain)
return res
@@ -53,29 +51,18 @@ def get_dkim_entry(mail_domain, dkim_selector):
return f'{dkim_selector}._domainkey.{mail_domain}. TXT "{dkim_value}"'
def get_ip_address(typ):
sock = socket.socket(typ, socket.SOCK_DGRAM)
sock.settimeout(0)
host_port = "notifications.delta.chat", 443
try:
sock.connect(host_port)
except OSError:
print(f"failed to connect to: {host_port}")
return None
else:
print(f"successfully connected to: {host_port}")
return sock.getsockname()[0]
def query_dns(typ, domain):
res = shell(f"dig -r -q {domain} -t {typ} +short")
print(res)
return set(filter(None, res.split("\n")))
def check_zonefile(zonefile):
def check_zonefile(zonefile, fullcheck):
diff = []
for zf_line in zonefile.splitlines():
print("")
print(f"dns-checking {zf_line!r}")
zf_domain, zf_typ, zf_value = zf_line.split(maxsplit=2)
zf_domain = zf_domain.rstrip(".")
zf_value = zf_value.strip()