mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-30 18:50:53 +00:00 
			
		
		
		
	Merge f389603f01 into 0b7f477b96
				
					
				
			This commit is contained in:
		
						commit
						02469f1d78
					
				| @ -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. | 		# Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. | ||||||
| 		# User may provide one or more additional nameservers | 		# User may provide one or more additional nameservers | ||||||
| 		secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \ | 		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: | 		for secondary_ns in secondary_ns_list: | ||||||
| 			records.append((None,  "NS", secondary_ns+'.', False)) | 			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. | 	# Append the DKIM TXT record to the zone as generated by OpenDKIM. | ||||||
| 	# Skip if the user has set a DKIM record already. | 	# Skip if the user has set a DKIM record already. | ||||||
| 	opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt') | 	m, val = retrieve_dkim_record(env) | ||||||
| 	with open(opendkim_record_file) as orf: | 	if not has_rec(m.group(1), "TXT", prefix="v=DKIM1; "): | ||||||
| 		m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S) | 		records.append((m.group(1), "TXT", val, "Recommended. Provides a way for recipients to verify that this machine sent @%s mail." % domain)) | ||||||
| 		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)) |  | ||||||
| 
 | 
 | ||||||
| 	# Append a DMARC record. | 	# Append a DMARC record. | ||||||
| 	# Skip if the user has set a DMARC record already. | 	# 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 | 	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): | def build_tlsa_record(env): | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ import dateutil.parser, dateutil.tz | |||||||
| import idna | import idna | ||||||
| import psutil | 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 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 ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate | ||||||
| from mailconfig import get_mail_domains, get_mail_aliases | from mailconfig import get_mail_domains, get_mail_aliases | ||||||
| @ -325,6 +325,20 @@ def run_domain_checks(rounded_time, env, output, pool): | |||||||
| 	for domain in sort_domains(ret, env): | 	for domain in sort_domains(ret, env): | ||||||
| 		ret[domain].playback(output) | 		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): | def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records): | ||||||
| 	output = BufferedOutput() | 	output = BufferedOutput() | ||||||
| 
 | 
 | ||||||
| @ -355,6 +369,9 @@ def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zone | |||||||
| 	if domain in dns_domains: | 	if domain in dns_domains: | ||||||
| 		check_dns_zone_suggestions(domain, env, output, dns_zonefiles, domains_with_a_records) | 		check_dns_zone_suggestions(domain, env, output, dns_zonefiles, domains_with_a_records) | ||||||
| 
 | 
 | ||||||
|  | 	if check_deliverability_domain(domain, domain in mail_domains, env, output): | ||||||
|  | 		output.print_ok("Domain's SPF and DMARC records are set up correctly.") | ||||||
|  | 
 | ||||||
| 	return (domain, output) | 	return (domain, output) | ||||||
| 
 | 
 | ||||||
| def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): | def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): | ||||||
| @ -415,6 +432,10 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): | |||||||
| 		output.print_error("""Your box's reverse DNS is currently %s (IPv4) and %s (IPv6), but it should be %s. Your ISP or cloud provider will have instructions | 		output.print_error("""Your box's reverse DNS is currently %s (IPv4) and %s (IPv6), but it should be %s. Your ISP or cloud provider will have instructions | ||||||
| 			on setting up reverse DNS for your box.""" % (existing_rdns_v4, existing_rdns_v6, domain) ) | 			on setting up reverse DNS for your box.""" % (existing_rdns_v4, existing_rdns_v6, domain) ) | ||||||
| 
 | 
 | ||||||
|  | 	# ensure our nameservers aren't deliverable | ||||||
|  | 	if check_deliverability_domain('ns1.' + domain, False, env, output) and check_deliverability_domain('ns2.' + domain, False, env, output): | ||||||
|  | 		output.print_ok("Root nameservers prevent delivery with SPF and DMARC records.") | ||||||
|  | 
 | ||||||
| 	# Check the TLSA record. | 	# Check the TLSA record. | ||||||
| 	tlsa_qname = "_25._tcp." + domain | 	tlsa_qname = "_25._tcp." + domain | ||||||
| 	tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None) | 	tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None) | ||||||
| @ -631,6 +652,34 @@ def check_mail_domain(domain, env, output): | |||||||
| 			which may prevent recipients from receiving your mail. | 			which may prevent recipients from receiving your mail. | ||||||
| 			See http://www.spamhaus.org/dbl/ and http://www.spamhaus.org/query/domain/%s.""" % (dbl, domain)) | 			See http://www.spamhaus.org/dbl/ and http://www.spamhaus.org/query/domain/%s.""" % (dbl, domain)) | ||||||
| 
 | 
 | ||||||
|  | 	if domain == env["PRIMARY_HOSTNAME"]: | ||||||
|  | 		if check_dkim_domain(domain, env, output): | ||||||
|  | 			output.print_ok("Domain's DKIM record is set correctly.") | ||||||
|  | 	else: | ||||||
|  | 		if check_dkim_domain(domain, env, output) and check_dav_domain("cal", domain, env, output) and check_dav_domain("card", domain, env, output): | ||||||
|  | 			output.print_ok("Domain's DKIM, caldav, and carddav records are set correctly.") | ||||||
|  | 
 | ||||||
|  | def check_dkim_domain(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 + '"': | ||||||
|  | 		return True | ||||||
|  | 	else: | ||||||
|  | 		output.print_warning("Domain's DKIM record is not set to [%s ↦ %s]" % (dkim_domain, val)) | ||||||
|  | 
 | ||||||
|  | def check_dav_domain(dav, domain, env, output): | ||||||
|  | 	dav_domain = "_" + dav + "davs._tcp." + domain | ||||||
|  | 	expected = "0 0 443 " + env["PRIMARY_HOSTNAME"] | ||||||
|  | 	values = query_dns(dav_domain, "SRV") | ||||||
|  | 	if expected == values: | ||||||
|  | 		return True | ||||||
|  | 	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): | 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 | 	# 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 | 	# for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and | ||||||
| @ -657,6 +706,27 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output): | |||||||
| 	# website for also needs a signed certificate. | 	# website for also needs a signed certificate. | ||||||
| 	check_ssl_cert(domain, rounded_time, ssl_certificates, env, output) | 	check_ssl_cert(domain, rounded_time, ssl_certificates, env, output) | ||||||
| 
 | 
 | ||||||
|  | def check_deliverability_domain(domain, deliverable, env, output): | ||||||
|  | 	result = True  # returns True if everything is set up OK | ||||||
|  | 	action = 'allow' if deliverable else 'prevent' | ||||||
|  | 
 | ||||||
|  | 	# Ensure the SPF record for this domain either allows or prevents email | ||||||
|  | 	expected = "\"v=spf1 %s-all\"" % ('mx ' if deliverable else '') | ||||||
|  | 	values = query_dns(domain, "TXT").split('; ') | ||||||
|  | 	if expected not in values: | ||||||
|  | 		output.print_warning("This domain should %s mail delivery by setting a TXT record. [%s ↦ %s]" % (action, domain, expected)) | ||||||
|  | 		result = False | ||||||
|  | 
 | ||||||
|  | 	# ensure the DMARC record specifies the correct action | ||||||
|  | 	dmarc_domain = '_dmarc.' + domain | ||||||
|  | 	values = query_dns(dmarc_domain, "TXT") | ||||||
|  | 	expected = "\"v=DMARC1; p=%s\"" % ('quarantine' if deliverable else 'reject') | ||||||
|  | 	if expected != values: | ||||||
|  | 		output.print_warning("This domain should %s mail delivery by setting a DMARC record. [%s ↦ %s]" % (action, dmarc_domain, expected)) | ||||||
|  | 		result = False | ||||||
|  | 
 | ||||||
|  | 	return result | ||||||
|  | 
 | ||||||
| def query_dns(qname, rtype, nxdomain='[Not Set]', at=None): | def query_dns(qname, rtype, nxdomain='[Not Set]', at=None): | ||||||
| 	# Make the qname absolute by appending a period. Without this, dns.resolver.query | 	# Make the qname absolute by appending a period. Without this, dns.resolver.query | ||||||
| 	# will fall back a failed lookup to a second query with this machine's hostname | 	# will fall back a failed lookup to a second query with this machine's hostname | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user