From 6150f91461576ca331acc0d1b915e167b2841b97 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Fri, 30 Sep 2016 18:11:34 -0700 Subject: [PATCH 1/4] Ensure DKIM records are set properly --- management/status_checks.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/management/status_checks.py b/management/status_checks.py index 4077066a..dab310c7 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -620,6 +620,20 @@ def check_mail_domain(domain, env, output): which may prevent recipients from receiving your mail. See http://www.spamhaus.org/dbl/ and http://www.spamhaus.org/query/domain/%s.""" % (dbl, domain)) + # ensure the DKIM keys are correct for this domain + dkim_domain = 'mail._domainkey.' + domain + opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt') + with open(opendkim_record_file) as orf: + m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S) + expected = '"' + "".join(re.findall(r'"([^"]+)"', m.group(2))) + '"' + # it appears dnspython doesn't join long lines so we'll do it with a replace statement + # https://github.com/rthalley/dnspython/blob/master/dns/rdtypes/txtbase.py#L42 + dkim = query_dns(dkim_domain, "TXT").replace('" "', '') + if dkim == expected: + output.print_ok("Domain's DKIM record is set correctly. [%s]" % (dkim_domain)) + else: + output.print_warning("Domain's DKIM record is not set to [%s ↦ %s]" % (dkim_domain, expected)) + def check_web_domain(domain, rounded_time, ssl_certificates, env, output): # See if the domain's A record resolves to our PUBLIC_IP. This is already checked # for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and From 1cab5a6d4be7817bbb79b3cfa93da69cebe9b3e8 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Fri, 30 Sep 2016 18:51:09 -0700 Subject: [PATCH 2/4] add caldav and carddav records to DNS status checks --- management/status_checks.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/management/status_checks.py b/management/status_checks.py index 4077066a..0bcf9d9f 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -620,6 +620,16 @@ def check_mail_domain(domain, env, output): which may prevent recipients from receiving your mail. See http://www.spamhaus.org/dbl/ and http://www.spamhaus.org/query/domain/%s.""" % (dbl, domain)) + if domain != env["PRIMARY_HOSTNAME"]: + for dav in ("card", "cal"): + dav_domain = "_" + dav + "davs._tcp." + domain + expected = "0 0 443 " + env["PRIMARY_HOSTNAME"] + values = query_dns(dav_domain, "SRV") + if expected == values: + output.print_ok("Domain's %sdav is set properly. [%s ↦ %s]" % (dav, dav_domain, expected)) + else: + output.print_warning("This domain should set a %sdav record: %s ↦ %s" % (dav, dav_domain, expected)) + def check_web_domain(domain, rounded_time, ssl_certificates, env, output): # See if the domain's A record resolves to our PUBLIC_IP. This is already checked # for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and From 00a3709b11423e2d811c4810bdd12b5f2ae9abb1 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Fri, 30 Sep 2016 20:29:32 -0700 Subject: [PATCH 3/4] add custom DNS records to DNS status checks --- management/status_checks.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/management/status_checks.py b/management/status_checks.py index 4077066a..19c98ec9 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -314,6 +314,20 @@ def run_domain_checks(rounded_time, env, output, pool): for domain in sort_domains(ret, env): ret[domain].playback(output) + run_custom_domain_checks(env, output) + +def run_custom_domain_checks(env, output): + header_pending = True + for qname, rtype, value in get_custom_dns_config(env): + if header_pending: + output.add_heading("Custom") + header_pending = False + result = query_dns(qname, rtype).replace('" "', '') + if value == result or '"' + value + '"' in result: + output.print_ok("Custom %s record is set correctly. [%s ↦ %s]" % (rtype, qname, value)) + else: + output.print_warning("Custom %s record should be set to [%s ↦ %s]" % (rtype, qname, value)) + def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records): output = BufferedOutput() From 3375ede0349185ccd000aee00d28ffe2d4466a58 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Fri, 30 Sep 2016 21:18:49 -0700 Subject: [PATCH 4/4] factor out retrieve_dkim_record to reduce code duplication --- management/dns_update.py | 18 +++++++++++------- management/status_checks.py | 27 ++++++++++++--------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index d7bbdfd0..46210f67 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -144,7 +144,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \ - or ["ns2." + env["PRIMARY_HOSTNAME"]] + or ["ns2." + env["PRIMARY_HOSTNAME"]] for secondary_ns in secondary_ns_list: records.append((None, "NS", secondary_ns+'.', False)) @@ -253,12 +253,9 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Append the DKIM TXT record to the zone as generated by OpenDKIM. # Skip if the user has set a DKIM record already. - opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt') - with open(opendkim_record_file) as orf: - m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S) - val = "".join(re.findall(r'"([^"]+)"', m.group(2))) - if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "): - records.append((m.group(1), "TXT", val, "Recommended. Provides a way for recipients to verify that this machine sent @%s mail." % domain)) + m, val = retrieve_dkim_record(env) + if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "): + records.append((m.group(1), "TXT", val, "Recommended. Provides a way for recipients to verify that this machine sent @%s mail." % domain)) # Append a DMARC record. # Skip if the user has set a DMARC record already. @@ -287,6 +284,13 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en return records +def retrieve_dkim_record(env): + opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt') + with open(opendkim_record_file) as orf: + m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S) + val = "".join(re.findall(r'"([^"]+)"', m.group(2))) + return m, val + ######################################################################## def build_tlsa_record(env): diff --git a/management/status_checks.py b/management/status_checks.py index dab310c7..768e0221 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -11,7 +11,7 @@ import dateutil.parser, dateutil.tz import idna import psutil -from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record +from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record, retrieve_dkim_record from web_update import get_web_domains, get_domains_with_a_records from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate from mailconfig import get_mail_domains, get_mail_aliases @@ -609,6 +609,17 @@ def check_mail_domain(domain, env, output): if "@" + domain not in [address for address, *_ in get_mail_aliases(env)]: check_alias_exists("Postmaster contact address", "postmaster@" + domain, env, output) + # ensure the DKIM keys are correct for this domain + dkim_domain = 'mail._domainkey.' + domain + m, val = retrieve_dkim_record(env) + # it appears dnspython doesn't join long lines so we'll do it with a replace statement + # https://github.com/rthalley/dnspython/blob/master/dns/rdtypes/txtbase.py#L42 + dkim = query_dns(dkim_domain, "TXT").replace('" "', '') + if dkim == '"' + val + '"': + output.print_ok("Domain's DKIM record is set correctly. [%s]" % (dkim_domain)) + else: + output.print_warning("Domain's DKIM record is not set to [%s ↦ %s]" % (dkim_domain, val)) + # Stop if the domain is listed in the Spamhaus Domain Block List. # The user might have chosen a domain that was previously in use by a spammer # and will not be able to reliably send mail. @@ -620,20 +631,6 @@ def check_mail_domain(domain, env, output): which may prevent recipients from receiving your mail. See http://www.spamhaus.org/dbl/ and http://www.spamhaus.org/query/domain/%s.""" % (dbl, domain)) - # ensure the DKIM keys are correct for this domain - dkim_domain = 'mail._domainkey.' + domain - opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt') - with open(opendkim_record_file) as orf: - m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S) - expected = '"' + "".join(re.findall(r'"([^"]+)"', m.group(2))) + '"' - # it appears dnspython doesn't join long lines so we'll do it with a replace statement - # https://github.com/rthalley/dnspython/blob/master/dns/rdtypes/txtbase.py#L42 - dkim = query_dns(dkim_domain, "TXT").replace('" "', '') - if dkim == expected: - output.print_ok("Domain's DKIM record is set correctly. [%s]" % (dkim_domain)) - else: - output.print_warning("Domain's DKIM record is not set to [%s ↦ %s]" % (dkim_domain, expected)) - def check_web_domain(domain, rounded_time, ssl_certificates, env, output): # See if the domain's A record resolves to our PUBLIC_IP. This is already checked # for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and