diff --git a/management/dns_update.py b/management/dns_update.py index abba5bc1..b51287b6 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -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(): - if rtype == "TXT": value2 = "\"" + value2 + "\"" - records.append((qname, rtype, value2)) + values = value.items() + else: + raise ValueError() + for rtype, value2 in values: + if rtype == "TXT": value2 = "\"" + 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