Commit 7803ac9c authored by Joshua Tauberer's avatar Joshua Tauberer

write explanatory text as we build DNS zones so we can help the user manage DNS off of the box

parent 91cf45c8
......@@ -129,36 +129,41 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
records = []
# For top-level zones, define ourselves as the authoritative name server.
# 'False' in the tuple indicates these records would not be used if the zone
# is managed outside of the box.
if with_ns:
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"]))
records.append((None, "NS", "ns2.%s." % env["PRIMARY_HOSTNAME"]))
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False))
records.append((None, "NS", "ns2.%s." % env["PRIMARY_HOSTNAME"], False))
# The MX record says where email for the domain should be delivered: Here!
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"]))
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname of the machine that handles @%s mail." % domain))
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
# the domain, and no one else.
records.append((None, "TXT", '"v=spf1 mx -all"'))
records.append((None, "TXT", '"v=spf1 mx -all"', "Recomended. Specifies that only the box is permitted to send @%s mail." % domain))
# If we need to define DNS for any subdomains of this domain, include it
# in the zone.
for subdomain in subdomains:
subdomain_qname = subdomain[0:-len("." + domain)]
for child_qname, child_rtype, child_value in build_zone(subdomain, [], {}, env, with_ns=False):
subzone = build_zone(subdomain, [], {}, env, with_ns=False)
for child_qname, child_rtype, child_value, child_explanation in subzone:
if child_qname == None:
child_qname = subdomain_qname
else:
child_qname += "." + subdomain_qname
records.append((child_qname, child_rtype, child_value))
records.append((child_qname, child_rtype, child_value, child_explanation))
# In PRIMARY_HOSTNAME...
if domain == env["PRIMARY_HOSTNAME"]:
# Define ns1 and ns2.
records.append(("ns1", "A", env["PUBLIC_IP"]))
records.append(("ns2", "A", env["PUBLIC_IP"]))
# 'False' in the tuple indicates these records would not be used if the zone
# is managed outside of the box.
records.append(("ns1", "A", env["PUBLIC_IP"], False))
records.append(("ns2", "A", env["PUBLIC_IP"], False))
# Add a DANE TLSA record for SMTP.
records.append(("_25._tcp", "TLSA", build_tlsa_record(env)))
records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used."))
def has_rec(qname, rtype):
for rec in records:
......@@ -175,17 +180,26 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
qname = qname[0:len(qname)-len("." + domain)]
if has_rec(qname, value): continue
if isinstance(value, str):
records.append((qname, "A", value))
values = [("A", value)]
elif isinstance(value, dict):
for rtype, value2 in value.items():
values = value.items()
else:
raise ValueError()
for rtype, value2 in values:
if rtype == "TXT": value2 = "\"" + value2 + "\""
records.append((qname, rtype, value2))
records.append((qname, rtype, value2, "(Set by user.)"))
# Add defaults if not overridden by the user's custom settings.
if not has_rec(None, "A"): records.append((None, "A", env["PUBLIC_IP"]))
if env.get('PUBLIC_IPV6') and not has_rec(None, "AAAA"): records.append((None, "AAAA", env["PUBLIC_IPV6"]))
if not has_rec("www", "A"): records.append(("www", "A", env["PUBLIC_IP"]))
if env.get('PUBLIC_IPV6') and not has_rec("www", "AAAA"): records.append(("www", "AAAA", env["PUBLIC_IPV6"]))
defaults = [
(None, "A", env["PUBLIC_IP"], "Optional. Sets the IP address that %s resolves to, e.g. for web hosting." % domain),
("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to, e.g. for web hosting." % domain),
(None, "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that %s resolves to, e.g. for web hosting." % domain),
("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to, e.g. for web hosting." % domain),
]
for qname, rtype, value, explanation in defaults:
if value is None or value.strip() == "": continue # skip IPV6 if not set
if not has_rec(qname, rtype):
records.append((qname, rtype, value, explanation))
# If OpenDKIM is in use..
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt')
......@@ -193,12 +207,12 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
# Append the DKIM TXT record to the zone as generated by OpenDKIM, after string formatting above.
with open(opendkim_record_file) as orf:
m = re.match(r"(\S+)\s+IN\s+TXT\s+(\(.*\))\s*;", orf.read(), re.S)
records.append((m.group(1), "TXT", m.group(2)))
records.append((m.group(1), "TXT", m.group(2), "Recommended. Specifies that only the box is permitted to send mail at this domain."))
# Append a DMARC record.
records.append(("_dmarc", "TXT", '"v=DMARC1; p=quarantine"'))
records.append(("_dmarc", "TXT", '"v=DMARC1; p=quarantine"', "Optional. Specifies that mail that does not originate from the box but claims to be from @%s is suspect and should be quarantined by the recipient's mail system." % domain))
# Sort the records. The None records *must* go first. Otherwise it doesn't matter.
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
return records
......@@ -255,7 +269,7 @@ $TTL 86400 ; default time to live
zone = zone.format(domain=domain, primary_domain=env["PRIMARY_HOSTNAME"])
# Add records.
for subdomain, querytype, value in records:
for subdomain, querytype, value, explanation in records:
if subdomain:
zone += subdomain
zone += "\tIN\t" + querytype + "\t"
......@@ -490,7 +504,7 @@ def justtestingdotemail(domain, records):
if not domain.endswith(".justtesting.email"):
return
for subdomain, querytype, value in records:
for subdomain, querytype, value, explanation in records:
if querytype in ("NS",): continue
if subdomain in ("www", "ns1", "ns2"): continue # don't do unnecessary things
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment