예제 #1
0
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
    # Check that SSL certificate is signed.

    # Skip the check if the A record is not pointed here.
    if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return

    # Where is the SSL stored?
    x = get_domain_ssl_files(domain,
                             ssl_certificates,
                             env,
                             allow_missing_cert=True)

    if x is None:
        output.print_warning(
            """No SSL certificate is installed for this domain. Visitors to a website on
			this domain will get a security warning. If you are not serving a website on this domain, you do
			not need to take any action. Use the SSL Certificates page in the control panel to install a
			SSL certificate.""")
        return

    ssl_key, ssl_certificate, ssl_via = x

    # Check that the certificate is good.

    cert_status, cert_status_details = check_certificate(
        domain, ssl_certificate, ssl_key, rounded_time=rounded_time)

    if cert_status == "OK":
        # The certificate is ok. The details has expiry info.
        output.print_ok("SSL certificate is signed & valid. %s %s" %
                        (ssl_via if ssl_via else "", cert_status_details))

    elif cert_status == "SELF-SIGNED":
        # Offer instructions for purchasing a signed certificate.

        fingerprint = shell('check_output', [
            "openssl", "x509", "-in", ssl_certificate, "-noout", "-fingerprint"
        ])
        fingerprint = re.sub(".*Fingerprint=", "", fingerprint).strip()

        if domain == env['PRIMARY_HOSTNAME']:
            output.print_error(
                """The SSL certificate for this domain is currently self-signed. You will get a security
			warning when you check or send email and when visiting this domain in a web browser (for webmail or
			static site hosting). Use the SSL Certificates page in the control panel to install a signed SSL certificate.
			You may choose to leave the self-signed certificate in place and confirm the security exception, but check that
			the certificate fingerprint matches the following:""")
            output.print_line("")
            output.print_line("   " + fingerprint, monospace=True)
        else:
            output.print_error(
                """The SSL certificate for this domain is self-signed.""")

    else:
        output.print_error("The SSL certificate has a problem: " + cert_status)
        if cert_status_details:
            output.print_line("")
            output.print_line(cert_status_details)
            output.print_line("")
예제 #2
0
 def check_cert(domain):
     tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
     if tls_cert is None: return ("danger", "No Certificate Installed")
     cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
     if cert_status == "OK":
         return ("success", "Signed & valid. " + cert_status_details)
     elif cert_status == "SELF-SIGNED":
         return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
     else:
         return ("danger", "Certificate has a problem: " + cert_status)
예제 #3
0
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
	# Check that SSL certificate is signed.

	# Skip the check if the A record is not pointed here.
	if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return

	# Where is the SSL stored?
	x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)

	if x is None:
		output.print_warning("""No SSL certificate is installed for this domain. Visitors to a website on
			this domain will get a security warning. If you are not serving a website on this domain, you do
			not need to take any action. Use the SSL Certificates page in the control panel to install a
			SSL certificate.""")
		return

	ssl_key, ssl_certificate, ssl_via = x

	# Check that the certificate is good.

	cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, rounded_time=rounded_time)

	if cert_status == "OK":
		# The certificate is ok. The details has expiry info.
		output.print_ok("SSL certificate is signed & valid. %s %s" % (ssl_via if ssl_via else "", cert_status_details))

	elif cert_status == "SELF-SIGNED":
		# Offer instructions for purchasing a signed certificate.

		fingerprint = shell('check_output', [
			"openssl",
			"x509",
			"-in", ssl_certificate,
			"-noout",
			"-fingerprint"
			])
		fingerprint = re.sub(".*Fingerprint=", "", fingerprint).strip()

		if domain == env['PRIMARY_HOSTNAME']:
			output.print_error("""The SSL certificate for this domain is currently self-signed. You will get a security
			warning when you check or send email and when visiting this domain in a web browser (for webmail or
			static site hosting). Use the SSL Certificates page in the control panel to install a signed SSL certificate.
			You may choose to leave the self-signed certificate in place and confirm the security exception, but check that
			the certificate fingerprint matches the following:""")
			output.print_line("")
			output.print_line("   " + fingerprint, monospace=True)
		else:
			output.print_error("""The SSL certificate for this domain is self-signed.""")

	else:
		output.print_error("The SSL certificate has a problem: " + cert_status)
		if cert_status_details:
			output.print_line("")
			output.print_line(cert_status_details)
			output.print_line("")
예제 #4
0
	def check_cert(domain):
		try:
			tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
		except OSError: # PRIMARY_HOSTNAME cert is missing
			tls_cert = None
		if tls_cert is None: return ("danger", "No certificate installed.")
		cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
		if cert_status == "OK":
			return ("success", "Signed & valid. " + cert_status_details)
		elif cert_status == "SELF-SIGNED":
			return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
		else:
			return ("danger", "Certificate has a problem: " + cert_status)
예제 #5
0
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
    # Check that TLS certificate is signed.

    # Skip the check if the A record is not pointed here.
    if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return

    # Where is the certificate file stored?
    tls_cert = get_domain_ssl_files(domain,
                                    ssl_certificates,
                                    env,
                                    allow_missing_cert=True)
    if tls_cert is None:
        output.print_warning(
            """No TLS (SSL) certificate is installed for this domain. Visitors to a website on
			this domain will get a security warning. If you are not serving a website on this domain, you do
			not need to take any action. Use the TLS Certificates page in the control panel to install a
			TLS certificate.""")
        return

    # Check that the certificate is good.

    cert_status, cert_status_details = check_certificate(
        domain,
        tls_cert["certificate"],
        tls_cert["private-key"],
        rounded_time=rounded_time)

    if cert_status == "OK":
        # The certificate is ok. The details has expiry info.
        output.print_ok("TLS (SSL) certificate is signed & valid. " +
                        cert_status_details)

    elif cert_status == "SELF-SIGNED":
        # Offer instructions for purchasing a signed certificate.
        if domain == env['PRIMARY_HOSTNAME']:
            output.print_error(
                """The TLS (SSL) certificate for this domain is currently self-signed. You will get a security
			warning when you check or send email and when visiting this domain in a web browser (for webmail or
			static site hosting).""")
        else:
            output.print_error(
                """The TLS (SSL) certificate for this domain is self-signed."""
            )

    else:
        output.print_error("The TLS (SSL) certificate has a problem: " +
                           cert_status)
        if cert_status_details:
            output.print_line("")
            output.print_line(cert_status_details)
            output.print_line("")
예제 #6
0
	def check_cert(domain):
		ssl_certificates = get_ssl_certificates(env)
		x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
		if x is None: return ("danger", "No Certificate Installed")
		ssl_key, ssl_certificate, ssl_via = x
		cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
		if cert_status == "OK":
			if not ssl_via:
				return ("success", "Signed & valid. " + cert_status_details)
			else:
				# This is an alternate domain but using the same cert as the primary domain.
				return ("success", "Signed & valid. " + ssl_via)
		elif cert_status == "SELF-SIGNED":
			return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
		else:
			return ("danger", "Certificate has a problem: " + cert_status)
예제 #7
0
 def check_cert(domain):
     ssl_certificates = get_ssl_certificates(env)
     x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
     if x is None:
         return ("danger", "No Certificate Installed")
     ssl_key, ssl_certificate, ssl_via = x
     cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
     if cert_status == "OK":
         if not ssl_via:
             return ("success", "Signed & valid. " + cert_status_details)
         else:
             # This is an alternate domain but using the same cert as the primary domain.
             return ("success", "Signed & valid. " + ssl_via)
     elif cert_status == "SELF-SIGNED":
         return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
     else:
         return ("danger", "Certificate has a problem: " + cert_status)
예제 #8
0
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
    # Check that TLS certificate is signed.

    # Skip the check if the A record is not pointed here.
    if query_dns(domain, "A", None) not in (env["PUBLIC_IP"], None):
        return

    # Where is the certificate file stored?
    tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
    if tls_cert is None:
        output.print_warning(
            """No TLS (SSL) certificate is installed for this domain. Visitors to a website on
			this domain will get a security warning. If you are not serving a website on this domain, you do
			not need to take any action. Use the TLS Certificates page in the control panel to install a
			TLS certificate."""
        )
        return

        # Check that the certificate is good.

    cert_status, cert_status_details = check_certificate(
        domain, tls_cert["certificate"], tls_cert["private-key"], rounded_time=rounded_time
    )

    if cert_status == "OK":
        # The certificate is ok. The details has expiry info.
        output.print_ok("TLS (SSL) certificate is signed & valid. " + cert_status_details)

    elif cert_status == "SELF-SIGNED":
        # Offer instructions for purchasing a signed certificate.
        if domain == env["PRIMARY_HOSTNAME"]:
            output.print_error(
                """The TLS (SSL) certificate for this domain is currently self-signed. You will get a security
			warning when you check or send email and when visiting this domain in a web browser (for webmail or
			static site hosting)."""
            )
        else:
            output.print_error("""The TLS (SSL) certificate for this domain is self-signed.""")

    else:
        output.print_error("The TLS (SSL) certificate has a problem: " + cert_status)
        if cert_status_details:
            output.print_line("")
            output.print_line(cert_status_details)
            output.print_line("")
예제 #9
0

if __name__ == "__main__":
	from utils import load_environment

	env = load_environment()
	pool = multiprocessing.pool.Pool(processes=10)

	if len(sys.argv) == 1:
		run_checks(False, env, ConsoleOutput(), pool)

	elif sys.argv[1] == "--show-changes":
		run_and_output_changes(env, pool)

	elif sys.argv[1] == "--check-primary-hostname":
		# See if the primary hostname appears resolvable and has a signed certificate.
		domain = env['PRIMARY_HOSTNAME']
		if query_dns(domain, "A") != env['PUBLIC_IP']:
			sys.exit(1)
		ssl_certificates = get_ssl_certificates(env)
		ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env)
		if not os.path.exists(ssl_certificate):
			sys.exit(1)
		cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, warn_if_expiring_soon=False)
		if cert_status != "OK":
			sys.exit(1)
		sys.exit(0)

	elif sys.argv[1] == "--version":
		print(what_version_is_this(env))
예제 #10
0
if __name__ == "__main__":
    from utils import load_environment

    env = load_environment()
    pool = multiprocessing.pool.Pool(processes=10)

    if len(sys.argv) == 1:
        run_checks(False, env, ConsoleOutput(), pool)

    elif sys.argv[1] == "--show-changes":
        run_and_output_changes(env, pool)

    elif sys.argv[1] == "--check-primary-hostname":
        # See if the primary hostname appears resolvable and has a signed certificate.
        domain = env['PRIMARY_HOSTNAME']
        if query_dns(domain, "A") != env['PUBLIC_IP']:
            sys.exit(1)
        ssl_certificates = get_ssl_certificates(env)
        tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
        if not os.path.exists(tls_cert["certificate"]):
            sys.exit(1)
        cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"],
                                                             tls_cert["private-key"],
                                                             warn_if_expiring_soon=False)
        if cert_status != "OK":
            sys.exit(1)
        sys.exit(0)

    elif sys.argv[1] == "--version":
        print(what_version_is_this(env))
예제 #11
0

if __name__ == "__main__":
	from utils import load_environment

	env = load_environment()
	pool = multiprocessing.pool.Pool(processes=10)

	if len(sys.argv) == 1:
		run_checks(False, env, ConsoleOutput(), pool)

	elif sys.argv[1] == "--show-changes":
		run_and_output_changes(env, pool)

	elif sys.argv[1] == "--check-primary-hostname":
		# See if the primary hostname appears resolvable and has a signed certificate.
		domain = env['PRIMARY_HOSTNAME']
		if query_dns(domain, "A") != env['PUBLIC_IP']:
			sys.exit(1)
		ssl_certificates = get_ssl_certificates(env)
		tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
		if not os.path.exists(tls_cert["certificate"]):
			sys.exit(1)
		cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"], warn_if_expiring_soon=False)
		if cert_status != "OK":
			sys.exit(1)
		sys.exit(0)

	elif sys.argv[1] == "--version":
		print(what_version_is_this(env))
예제 #12
0
def build_zone(domain, all_domains, additional_records, www_redirect_domains, env, is_zone=True):
	records = []

	# For top-level zones, define the authoritative name servers.
	#
	# Normally we are our own nameservers. Some TLDs require two distinct IP addresses,
	# so we allow the user to override the second nameserver definition so that
	# secondary DNS can be set up elsewhere.
	#
	# 'False' in the tuple indicates these records would not be used if the zone
	# is managed outside of the box.
	if is_zone:
		# Obligatory definition of ns1.PRIMARY_HOSTNAME.
		records.append((None,  "NS",  "ns1.%s." % env["PRIMARY_HOSTNAME"], False))

		# 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"]]
		for secondary_ns in secondary_ns_list:
			records.append((None,  "NS", secondary_ns+'.', False))


	# In PRIMARY_HOSTNAME...
	if domain == env["PRIMARY_HOSTNAME"]:
		# Define ns1 and ns2.
		# '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))
		if env.get('PUBLIC_IPV6'):
			records.append(("ns1", "AAAA", env["PUBLIC_IPV6"], False))
			records.append(("ns2", "AAAA", env["PUBLIC_IPV6"], False))

		# Set the A/AAAA records. Do this early for the PRIMARY_HOSTNAME so that the user cannot override them
		# and we can provide different explanatory text.
		records.append((None, "A", env["PUBLIC_IP"], "Required. Sets the IP address of the box."))
		if env.get("PUBLIC_IPV6"): records.append((None, "AAAA", env["PUBLIC_IPV6"], "Required. Sets the IPv6 address of the box."))

		# Add a DANE TLSA record for SMTP.
		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."))

		# Add a DANE TLSA record for HTTPS, which some browser extensions might make use of.
		records.append(("_443._tcp", "TLSA", build_tlsa_record(env), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it."))

		# Add a SSHFP records to help SSH key validation. One per available SSH key on this system.
		for value in build_sshfp_records():
			records.append((None, "SSHFP", value, "Optional. Provides an out-of-band method for verifying an SSH key before connecting. Use 'VerifyHostKeyDNS yes' (or 'VerifyHostKeyDNS ask') when connecting with ssh."))

	# Add DNS records for any subdomains of this domain. We should not have a zone for
	# both a domain and one of its subdomains.
	subdomains = [d for d in all_domains if d.endswith("." + domain)]
	for subdomain in subdomains:
		subdomain_qname = subdomain[0:-len("." + domain)]
		subzone = build_zone(subdomain, [], additional_records, www_redirect_domains, env, is_zone=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, child_explanation))

	has_rec_base = list(records) # clone current state
	def has_rec(qname, rtype, prefix=None):
		for rec in has_rec_base:
			if rec[0] == qname and rec[1] == rtype and (prefix is None or rec[2].startswith(prefix)):
				return True
		return False

	# The user may set other records that don't conflict with our settings.
	# Don't put any TXT records above this line, or it'll prevent any custom TXT records.
	for qname, rtype, value in filter_custom_records(domain, additional_records):
		# Don't allow custom records for record types that override anything above.
		# But allow multiple custom records for the same rtype --- see how has_rec_base is used.
		if has_rec(qname, rtype): continue

		# The "local" keyword on A/AAAA records are short-hand for our own IP.
		# This also flags for web configuration that the user wants a website here.
		if rtype == "A" and value == "local":
			value = env["PUBLIC_IP"]
		if rtype == "AAAA" and value == "local":
			if "PUBLIC_IPV6" in env:
				value = env["PUBLIC_IPV6"]
			else:
				continue
		records.append((qname, rtype, value, "(Set by user.)"))

	# Add defaults if not overridden by the user's custom settings (and not otherwise configured).
	# Any CNAME or A record on the qname overrides A and AAAA. But when we set the default A record,
	# we should not cause the default AAAA record to be skipped because it thinks a custom A record
	# was set. So set has_rec_base to a clone of the current set of DNS settings, and don't update
	# during this process.
	has_rec_base = list(records)
	defaults = [
		(None,  "A",    env["PUBLIC_IP"],       "Required. May have a different value. Sets the IP address that %s resolves to for web hosting and other services besides mail. The A record must be present but its value does not affect mail delivery." % domain),
		(None,  "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that %s resolves to, e.g. for web hosting. (It is not necessary for receiving mail on this domain.)" % domain),
	]
	if "www." + domain in www_redirect_domains:
		defaults += [
			("www", "A",    env["PUBLIC_IP"],       "Optional. Sets the IP address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain),
			("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain),
		]
	for qname, rtype, value, explanation in defaults:
		if value is None or value.strip() == "": continue # skip IPV6 if not set
		if not is_zone and qname == "www": continue # don't create any default 'www' subdomains on what are themselves subdomains
		# Set the default record, but not if:
		# (1) there is not a user-set record of the same type already
		# (2) there is not a CNAME record already, since you can't set both and who knows what takes precedence
		# (2) there is not an A record already (if this is an A record this is a dup of (1), and if this is an AAAA record then don't set a default AAAA record if the user sets a custom A record, since the default wouldn't make sense and it should not resolve if the user doesn't provide a new AAAA record)
		if not has_rec(qname, rtype) and not has_rec(qname, "CNAME") and not has_rec(qname, "A"):
			records.append((qname, rtype, value, explanation))

	# Don't pin the list of records that has_rec checks against anymore.
	has_rec_base = records

	# The MX record says where email for the domain should be delivered: Here!
	if not has_rec(None, "MX", prefix="10 "):
		records.append((None,  "MX",  "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) 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.
	# Skip if the user has set a custom SPF record.
	if not has_rec(None, "TXT", prefix="v=spf1 "):
		records.append((None,  "TXT", 'v=spf1 mx -all', "Recommended. Specifies that only the box is permitted to send @%s mail." % domain))

	# 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))

	# Append a DMARC record.
	# Skip if the user has set a DMARC record already.
	if not has_rec("_dmarc", "TXT", prefix="v=DMARC1; "):
		records.append(("_dmarc", "TXT", 'v=DMARC1; p=quarantine', "Recommended. Specifies that mail that does not originate from the box but claims to be from @%s or which does not have a valid DKIM signature is suspect and should be quarantined by the recipient's mail system." % domain))

	# For any subdomain with an A record but no SPF or DMARC record, add strict policy records.
	all_resolvable_qnames = set(r[0] for r in records if r[1] in ("A", "AAAA"))
	for qname in all_resolvable_qnames:
		if not has_rec(qname, "TXT", prefix="v=spf1 "):
			records.append((qname,  "TXT", 'v=spf1 -all', "Recommended. Prevents use of this domain name for outbound mail by specifying that no servers are valid sources for mail from @%s. If you do send email from this domain name you should either override this record such that the SPF rule does allow the originating server, or, take the recommended approach and have the box handle mail for this domain (simply add any receiving alias at this domain name to make this machine treat the domain name as one of its mail domains)." % (qname + "." + domain)))
		dmarc_qname = "_dmarc" + ("" if qname is None else "." + qname)
		if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "):
			records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Recommended. Prevents use of this domain name for outbound mail by specifying that the SPF rule should be honoured for mail from @%s." % (qname + "." + domain)))

	# Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname
	# for autoconfiguration of mail clients (so only domains hosting user accounts need it).
	# The SRV record format is priority (0, whatever), weight (0, whatever), port, service provider hostname (w/ trailing dot).
	if domain != env["PRIMARY_HOSTNAME"] and domain in get_mail_domains(env, users_only=True):
		for dav in ("card", "cal"):
			qname = "_" + dav + "davs._tcp"
			if not has_rec(qname, "SRV"):
				records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))

	# Adds autoconfiguration A records for all domains that there are user accounts at.
	# This allows the following clients to automatically configure email addresses in the respective applications.
	# autodiscover.* - Z-Push ActiveSync Autodiscover
	# autoconfig.* - Thunderbird Autoconfig
	if domain in get_mail_domains(env, users_only=True):
		autodiscover_records = [
			("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
			("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
			("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."),
			("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.")
		]
		for qname, rtype, value, explanation in autodiscover_records:
			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 this is a domain name that there are email addresses configured for, i.e. "something@"
	# this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461)
	# Policy Domain.
	#
	# A "_mta-sts" TXT record signals the presence of a MTA-STS policy. The id field helps clients
	# cache the policy. It should be stable so we don't update DNS unnecessarily but change when
	# the policy changes. It must be at most 32 letters and numbers, so we compute a hash of the
	# policy file.
	#
	# The policy itself is served at the "mta-sts" (no underscore) subdomain over HTTPS. Therefore
	# the TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
	# domain name (PRIMARY_HOSTNAME) *and* the TLS certificate used by nginx for HTTPS on the mta-sts
	# subdomain must be valid certificate for that domain. Do not set an MTA-STS policy if either
	# certificate in use is not valid (e.g. because it is self-signed and a valid certificate has not
	# yet been provisioned). Since we cannot provision a certificate without A/AAAA records, we
	# always set them --- only the TXT records depend on there being valid certificates.
	mta_sts_enabled = False
	mta_sts_records = [
		("mta-sts", "A", env["PUBLIC_IP"], "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
		("mta-sts", "AAAA", env.get('PUBLIC_IPV6'), "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
	]
	if domain in get_mail_domains(env):
		# Check that PRIMARY_HOSTNAME and the mta_sts domain both have valid certificates.
		for d in (env['PRIMARY_HOSTNAME'], "mta-sts." + domain):
			cert = get_ssl_certificates(env).get(d)
			if not cert:
				break # no certificate provisioned for this domain
			cert_status = check_certificate(d, cert['certificate'], cert['private-key'])
			if cert_status[0] != 'OK':
				break # certificate is not valid
		else:
			# 'break' was not encountered above, so both domains are good
			mta_sts_enabled = True
	if mta_sts_enabled:
		# Compute an up-to-32-character hash of the policy file. We'll take a SHA-1 hash of the policy
		# file (20 bytes) and encode it as base-64 (28 bytes, using alphanumeric alternate characters
		# instead of '+' and '/' which are not allowed in an MTA-STS policy id) but then just take its
		# first 20 characters, which is more than sufficient to change whenever the policy file changes
		# (and ensures any '=' padding at the end of the base64 encoding is dropped).
		with open("/var/lib/mailinabox/mta-sts.txt", "rb") as f:
			mta_sts_policy_id = base64.b64encode(hashlib.sha1(f.read()).digest(), altchars=b"AA").decode("ascii")[0:20]
		mta_sts_records.extend([
			("_mta-sts", "TXT", "v=STSv1; id=" + mta_sts_policy_id, "Optional. Part of the MTA-STS policy for incoming mail. If set, a MTA-STS policy must also be published.")
		])

		# Rules can be custom configured accoring to https://tools.ietf.org/html/rfc8460.
		# Skip if the rules below if the user has set a custom _smtp._tls record.
		if not has_rec("_smtp._tls", "TXT", prefix="v=TLSRPTv1;"):
			tls_rpt_string = ""
			tls_rpt_email = env.get("MTA_STS_TLSRPT_EMAIL", "postmaster@%s" % env['PRIMARY_HOSTNAME'])
			if tls_rpt_email: # if a reporting address is not cleared
				tls_rpt_string = " rua=mailto:%s" % tls_rpt_email
			mta_sts_records.append(("_smtp._tls", "TXT", "v=TLSRPTv1;%s" % tls_rpt_string, "Optional. Enables MTA-STS reporting."))
	for qname, rtype, value, explanation in mta_sts_records:
		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))

	# 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
예제 #13
0

if __name__ == "__main__":
	from utils import load_environment

	env = load_environment()
	pool = multiprocessing.pool.Pool(processes=10)

	if len(sys.argv) == 1:
		run_checks(False, env, ConsoleOutput(), pool)

	elif sys.argv[1] == "--show-changes":
		run_and_output_changes(env, pool)

	elif sys.argv[1] == "--check-primary-hostname":
		# See if the primary hostname appears resolvable and has a signed certificate.
		domain = env['PRIMARY_HOSTNAME']
		if query_dns(domain, "A") != env['PUBLIC_IP']:
			sys.exit(1)
		ssl_certificates = get_ssl_certificates(env)
		ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env)
		if not os.path.exists(ssl_certificate):
			sys.exit(1)
		cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, warn_if_expiring_soon=False)
		if cert_status != "OK":
			sys.exit(1)
		sys.exit(0)

	elif sys.argv[1] == "--version":
		print(what_version_is_this(env))
def is_domain_cert_signed_and_valid(domain, env):
	cert = get_ssl_certificates(env).get(domain)
	if not cert: return False # no certificate provisioned
	cert_status = check_certificate(domain, cert['certificate'], cert['private-key'])
	print(domain, cert_status)
	return cert_status[0] == 'OK'