mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 04:18:09 +00:00
- 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:
@@ -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"
|
|
||||||
21
cmdeploy/src/cmdeploy/chatmail.zone.j2
Normal file
21
cmdeploy/src/cmdeploy/chatmail.zone.j2
Normal 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"
|
||||||
@@ -2,13 +2,15 @@ import datetime
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
from . import remote_funcs
|
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
|
"""Check existing DNS records, optionally write them to zone file
|
||||||
and return (exitcode, remote_data) tuple."""
|
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
|
mail_domain = args.config.mail_domain
|
||||||
|
|
||||||
def log_progress(data):
|
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)
|
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:
|
content = template.read_text()
|
||||||
zonefile = f.read().format(
|
zonefile = Template(content).render(
|
||||||
acme_account_url=remote_data["acme_account_url"],
|
acme_account_url=remote_data.get("acme_account_url"),
|
||||||
dkim_entry=remote_data["dkim_entry"],
|
dkim_entry=remote_data.get("dkim_entry"),
|
||||||
ipv6=remote_data["ipv6"],
|
ipv4=remote_data["ipv4"],
|
||||||
ipv4=remote_data["ipv4"],
|
ipv6=remote_data["ipv6"],
|
||||||
sts_id=datetime.datetime.now().strftime("%Y%m%d%H%M"),
|
sts_id=datetime.datetime.now().strftime("%Y%m%d%H%M"),
|
||||||
chatmail_domain=args.config.mail_domain,
|
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:
|
if not args.verbose:
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ without any installed dependencies.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
from subprocess import CalledProcessError, check_output
|
from subprocess import CalledProcessError, check_output
|
||||||
|
|
||||||
|
|
||||||
@@ -36,10 +35,9 @@ def perform_initial_checks(mail_domain):
|
|||||||
if not shell("dig", fail_ok=True):
|
if not shell("dig", fail_ok=True):
|
||||||
shell("apt-get install -y dnsutils")
|
shell("apt-get install -y dnsutils")
|
||||||
shell(f"unbound-control flush_zone {mail_domain}", fail_ok=True)
|
shell(f"unbound-control flush_zone {mail_domain}", fail_ok=True)
|
||||||
|
|
||||||
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")
|
res["dkim_entry"] = get_dkim_entry(mail_domain, dkim_selector="opendkim")
|
||||||
res["ipv4"] = get_ip_address(socket.AF_INET)
|
res["ipv4"] = query_dns("A", mail_domain)
|
||||||
res["ipv6"] = get_ip_address(socket.AF_INET6)
|
res["ipv6"] = query_dns("AAAA", mail_domain)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@@ -53,29 +51,18 @@ def get_dkim_entry(mail_domain, dkim_selector):
|
|||||||
return f'{dkim_selector}._domainkey.{mail_domain}. TXT "{dkim_value}"'
|
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):
|
def query_dns(typ, domain):
|
||||||
res = shell(f"dig -r -q {domain} -t {typ} +short")
|
res = shell(f"dig -r -q {domain} -t {typ} +short")
|
||||||
|
print(res)
|
||||||
return set(filter(None, res.split("\n")))
|
return set(filter(None, res.split("\n")))
|
||||||
|
|
||||||
|
|
||||||
def check_zonefile(zonefile):
|
def check_zonefile(zonefile, fullcheck):
|
||||||
diff = []
|
diff = []
|
||||||
|
|
||||||
for zf_line in zonefile.splitlines():
|
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_typ, zf_value = zf_line.split(maxsplit=2)
|
||||||
zf_domain = zf_domain.rstrip(".")
|
zf_domain = zf_domain.rstrip(".")
|
||||||
zf_value = zf_value.strip()
|
zf_value = zf_value.strip()
|
||||||
|
|||||||
Reference in New Issue
Block a user