Ejemplo n.º 1
0
def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
    # What domains should we serve HTTP(S) for?
    domains = set()

    # Serve web for all mail domains so that we might at least
    # provide auto-discover of email settings, and also a static website
    # if the user wants to make one.
    domains |= get_mail_domains(env)

    if include_www_redirects:
        # Add 'www.' subdomains that we want to provide default redirects
        # to the main domain for. We'll add 'www.' to any DNS zones, i.e.
        # the topmost of each domain we serve.
        domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))

    if exclude_dns_elsewhere:
        # ...Unless the domain has an A/AAAA record that maps it to a different
        # IP address than this box. Remove those domains from our list.
        domains -= get_domains_with_a_records(env)

    # Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
    # as well as Z-Push for Exchange ActiveSync. This can't be removed
    # by a custom A/AAAA record and is never a 'www.' redirect.
    domains.add(env['PRIMARY_HOSTNAME'])

    # Sort the list so the nginx conf gets written in a stable order.
    domains = sort_domains(domains, env)

    return domains
Ejemplo n.º 2
0
def run_domain_checks(rounded_time, env, output, pool):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	domains_to_check = mail_domains | dns_domains | web_domains

	# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
	domains_with_a_records = get_domains_with_a_records(env)

	ssl_certificates = get_ssl_certificates(env)

	# Serial version:
	#for domain in sort_domains(domains_to_check, env):
	#	run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)

	# Parallelize the checks across a worker pool.
	args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records, ssl_certificates)
		for domain in domains_to_check)
	ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
	ret = dict(ret) # (domain, output) => { domain: output }
	for domain in sort_domains(ret, env):
		ret[domain].playback(output)
Ejemplo n.º 3
0
def get_web_domains(env, include_www_redirects=True):
	# What domains should we serve HTTP(S) for?
	domains = set()

	# Serve web for all mail domains so that we might at least
	# provide auto-discover of email settings, and also a static website
	# if the user wants to make one.
	domains |= get_mail_domains(env)

	if include_www_redirects:
		# Add 'www.' subdomains that we want to provide default redirects
		# to the main domain for. We'll add 'www.' to any DNS zones, i.e.
		# the topmost of each domain we serve.
		domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
	 
	# ...Unless the domain has an A/AAAA record that maps it to a different
	# IP address than this box. Remove those domains from our list.
	domains -= get_domains_with_a_records(env)

	# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
	# as well as Z-Push for Exchange ActiveSync. This can't be removed
	# by a custom A/AAAA record and is never a 'www.' redirect.
	domains.add(env['PRIMARY_HOSTNAME'])

	# Sort the list so the nginx conf gets written in a stable order.
	domains = sort_domains(domains, env)

	return domains
Ejemplo n.º 4
0
def get_default_www_redirects(env):
	# Returns a list of www subdomains that we want to provide default redirects
	# for, i.e. any www's that aren't domains the user has actually configured
	# to serve for real. Which would be unusual.
	web_domains = set(get_web_domains(env))
	www_domains = set('www.' + zone for zone, zonefile in get_dns_zones(env))
	return sort_domains(www_domains - web_domains - get_domains_with_a_records(env), env)
Ejemplo n.º 5
0
def run_domain_checks(env):
    # Get the list of domains we handle mail for.
    mail_domains = get_mail_domains(env)

    # Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
    dns_zonefiles = dict(get_dns_zones(env))
    dns_domains = set(dns_zonefiles)

    # Get the list of domains we serve HTTPS for.
    web_domains = set(get_web_domains(env))

    domains_to_check = mail_domains | dns_domains | web_domains

    # Serial version:
    #for domain in sort_domains(domains_to_check, env):
    #	run_domain_checks_on_domain(domain, env, dns_domains, dns_zonefiles, mail_domains, web_domains)

    # Parallelize the checks across a worker pool.
    args = ((domain, env, dns_domains, dns_zonefiles, mail_domains,
             web_domains) for domain in domains_to_check)
    pool = multiprocessing.pool.Pool(processes=10)
    ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
    ret = dict(ret)  # (domain, output) => { domain: output }
    output = BufferedOutput()
    for domain in sort_domains(ret, env):
        ret[domain].playback(output)
    return output
Ejemplo n.º 6
0
def run_domain_checks(env):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	# Check the domains.
	for domain in sort_domains(mail_domains | dns_domains | web_domains, env):
		env["out"].add_heading(domain)

		if domain == env["PRIMARY_HOSTNAME"]:
			check_primary_hostname_dns(domain, env, dns_domains, dns_zonefiles)
		
		if domain in dns_domains:
			check_dns_zone(domain, env, dns_zonefiles)
		
		if domain in mail_domains:
			check_mail_domain(domain, env)

		if domain in web_domains:
			check_web_domain(domain, env)

		if domain in dns_domains:
			check_dns_zone_suggestions(domain, env, dns_zonefiles)
Ejemplo n.º 7
0
def run_domain_checks(env):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	# Check the domains.
	for domain in sort_domains(mail_domains | dns_domains | web_domains, env):
		print(domain)
		print("=" * len(domain))

		if domain == env["PRIMARY_HOSTNAME"]:
			check_primary_hostname_dns(domain, env)
			check_alias_exists("administrator@" + domain, env)
		
		if domain in dns_domains:
			check_dns_zone(domain, env, dns_zonefiles)
		
		if domain in mail_domains:
			check_mail_domain(domain, env)

		if domain == env["PRIMARY_HOSTNAME"] or domain in web_domains: 
			# We need a SSL certificate for PRIMARY_HOSTNAME because that's where the
			# user will log in with IMAP or webmail. Any other domain we serve a
			# website for also needs a signed certificate.
			check_ssl_cert(domain, env)

		print()
Ejemplo n.º 8
0
def run_domain_checks(rounded_time, env, output, pool):
    # Get the list of domains we handle mail for.
    mail_domains = get_mail_domains(env)

    # Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
    dns_zonefiles = dict(get_dns_zones(env))
    dns_domains = set(dns_zonefiles)

    # Get the list of domains we serve HTTPS for.
    web_domains = set(get_web_domains(env) + get_default_www_redirects(env))

    domains_to_check = mail_domains | dns_domains | web_domains

    # Serial version:
    # for domain in sort_domains(domains_to_check, env):
    # 	run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)

    # Parallelize the checks across a worker pool.
    args = (
        (domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)
        for domain in domains_to_check
    )
    ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
    ret = dict(ret)  # (domain, output) => { domain: output }
    for domain in sort_domains(ret, env):
        ret[domain].playback(output)
Ejemplo n.º 9
0
def get_web_domain_flags(env):
	flags = dict()
	zones = get_dns_zones(env)
	email_domains = get_mail_domains(env)
	user_domains = get_mail_domains(env, users_only=True)
	external = get_domains_with_a_records(env)
	redirects = get_web_domains_with_root_overrides(env)

	for d in email_domains:
		flags[d] = flags.get(d, 0)
		flags[f"mta-sts.{d}"] = flags.get(d, 0)
		flags[f"openpgpkey.{d}"] = flags.get(d, 0) | DOMAIN_WKD

	for d in user_domains:
		flags[f"autoconfig.{d}"] = flags.get(d, 0)
		flags[f"autodiscover.{d}"] = flags.get(d, 0)

	for d, _ in zones:
		flags[f"www.{d}"] = flags.get(d, 0) | DOMAIN_WWW

	for d in redirects:
		flags[d] = flags.get(d, 0) | DOMAIN_REDIRECT

	flags[env["PRIMARY_HOSTNAME"]] |= DOMAIN_PRIMARY

	# Last check for websites hosted elsewhere
	for d in flags.keys():
		if d in external:
			flags[d] = DOMAIN_EXTERNAL # -1 = All bits set to 1, assuming twos-complement
	return flags
Ejemplo n.º 10
0
def run_domain_checks(env):
    # Get the list of domains we handle mail for.
    mail_domains = get_mail_domains(env)

    # Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
    dns_zonefiles = dict(get_dns_zones(env))
    dns_domains = set(dns_zonefiles)

    # Get the list of domains we serve HTTPS for.
    web_domains = set(get_web_domains(env))

    # Check the domains.
    for domain in sort_domains(mail_domains | dns_domains | web_domains, env):
        env["out"].add_heading(domain)

        if domain == env["PRIMARY_HOSTNAME"]:
            check_primary_hostname_dns(domain, env)

        if domain in dns_domains:
            check_dns_zone(domain, env, dns_zonefiles)

        if domain in mail_domains:
            check_mail_domain(domain, env)

        if domain in web_domains:
            check_web_domain(domain, env)
Ejemplo n.º 11
0
def run_domain_checks(rounded_time, env, output, pool):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	domains_to_check = mail_domains | dns_domains | web_domains

	# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
	domains_with_a_records = get_domains_with_a_records(env)

	# Serial version:
	#for domain in sort_domains(domains_to_check, env):
	#	run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)

	# Parallelize the checks across a worker pool.
	args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records)
		for domain in domains_to_check)
	ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
	ret = dict(ret) # (domain, output) => { domain: output }
	for domain in sort_domains(ret, env):
		ret[domain].playback(output)
Ejemplo n.º 12
0
def get_default_www_redirects(env):
    # Returns a list of www subdomains that we want to provide default redirects
    # for, i.e. any www's that aren't domains the user has actually configured
    # to serve for real. Which would be unusual.
    web_domains = set(get_web_domains(env))
    www_domains = set('www.' + zone for zone, zonefile in get_dns_zones(env))
    return sort_domains(
        www_domains - web_domains - get_domains_with_a_records(env), env)
Ejemplo n.º 13
0
def get_web_domains(env,
                    include_www_redirects=True,
                    include_auto=True,
                    exclude_dns_elsewhere=True,
                    categories=['mail', 'ssl']):
    # What domains should we serve HTTP(S) for?
    domains = set()

    # Serve web for all mail domains so that we might at least
    # provide auto-discover of email settings, and also a static website
    # if the user wants to make one.
    for category in categories:
        domains |= get_mail_domains(env, category=category)

    if include_www_redirects and include_auto:
        # Add 'www.' subdomains that we want to provide default redirects
        # to the main domain for. We'll add 'www.' to any DNS zones, i.e.
        # the topmost of each domain we serve.
        domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))

    if 'mail' in categories and include_auto:
        # Add Autoconfiguration domains for domains that there are user accounts at:
        # 'autoconfig.' for Mozilla Thunderbird auto setup.
        # 'autodiscover.' for Activesync autodiscovery.
        domains |= set(
            'autoconfig.' + maildomain
            for maildomain in get_mail_domains(env, users_only=True))
        domains |= set(
            'autodiscover.' + maildomain
            for maildomain in get_mail_domains(env, users_only=True))
        # 'mta-sts.' for MTA-STS support for all domains that have email addresses.
        domains |= set('mta-sts.' + maildomain
                       for maildomain in get_mail_domains(env))

    if exclude_dns_elsewhere:
        # ...Unless the domain has an A/AAAA record that maps it to a different
        # IP address than this box. Remove those domains from our list.
        domains -= get_domains_with_a_records(env)

    # Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
    # as well as Z-Push for Exchange ActiveSync. This can't be removed
    # by a custom A/AAAA record and is never a 'www.' redirect.
    domains.add(env['PRIMARY_HOSTNAME'])

    # Sort the list so the nginx conf gets written in a stable order.
    domains = sort_domains(domains, env)

    return domains
Ejemplo n.º 14
0
def dns_get_records(qname=None, rtype=None):
    # Get the current set of custom DNS records.
    from dns_update import get_custom_dns_config, get_dns_zones
    records = get_custom_dns_config(env, only_real_records=True)

    # Filter per the arguments for the more complex GET routes below.
    records = [
        r for r in records
        if (not qname or r[0] == qname) and (not rtype or r[1] == rtype)
    ]

    # Make a better data structure.
    records = [{
        "qname": r[0],
        "rtype": r[1],
        "value": r[2],
        "ttl": r[3],
        "sort-order": {},
    } for r in records]

    # To help with grouping by zone in qname sorting, label each record with which zone it is in.
    # There's an inconsistency in how we handle zones in get_dns_zones and in sort_domains, so
    # do this first before sorting the domains within the zones.
    zones = utils.sort_domains([z[0] for z in get_dns_zones(env)], env)
    for r in records:
        for z in zones:
            if r["qname"] == z or r["qname"].endswith("." + z):
                r["zone"] = z
                break

    # Add sorting information. The 'created' order follows the order in the YAML file on disk,
    # which tracs the order entries were added in the control panel since we append to the end.
    # The 'qname' sort order sorts by our standard domain name sort (by zone then by qname),
    # then by rtype, and last by the original order in the YAML file (since sorting by value
    # may not make sense, unless we parse IP addresses, for example).
    for i, r in enumerate(records):
        r["sort-order"]["created"] = i
    domain_sort_order = utils.sort_domains([r["qname"] for r in records], env)
    for i, r in enumerate(
            sorted(records,
                   key=lambda r:
                   (zones.index(r["zone"]), domain_sort_order.index(r[
                       "qname"]), r["rtype"]))):
        r["sort-order"]["qname"] = i

    # Return.
    return json_response(records)
Ejemplo n.º 15
0
def run_domain_checks(rounded_time, env, output, pool):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	domains_to_check = mail_domains | dns_domains | web_domains

	# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
	domains_with_a_records = get_domains_with_a_records(env)

	# Serial version: Other one currently dies...
	for domain in sort_domains(domains_to_check, env):
		run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records)
Ejemplo n.º 16
0
def run_domain_checks(rounded_time, env, output, pool):
	# Get the list of domains we handle mail for.
	mail_domains = get_mail_domains(env)

	# Get the list of domains we serve DNS zones for (i.e. does not include subdomains).
	dns_zonefiles = dict(get_dns_zones(env))
	dns_domains = set(dns_zonefiles)

	# Get the list of domains we serve HTTPS for.
	web_domains = set(get_web_domains(env))

	domains_to_check = mail_domains | dns_domains | web_domains

	# Remove "www", "autoconfig", "autodiscover", and "mta-sts" subdomains, which we group with their parent,
	# if their parent is in the domains to check list.
	domains_to_check = [
		d for d in domains_to_check
		if not (
		   d.split(".", 1)[0] in ("www", "autoconfig", "autodiscover", "mta-sts")
		   and len(d.split(".", 1)) == 2
		   and d.split(".", 1)[1] in domains_to_check
		)
	]

	# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
	domains_with_a_records = get_domains_with_a_records(env)

	# Serial version:
	#for domain in sort_domains(domains_to_check, env):
	#	run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)

	# Parallelize the checks across a worker pool.
	args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records)
		for domain in domains_to_check)
	ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
	ret = dict(ret) # (domain, output) => { domain: output }
	for domain in sort_domains(ret, env):
		ret[domain].playback(output)
Ejemplo n.º 17
0
def dns_zones():
	from dns_update import get_dns_zones
	return json_response([z[0] for z in get_dns_zones(env)])
Ejemplo n.º 18
0
def dns_zones():
    from dns_update import get_dns_zones
    return json_response([z[0] for z in get_dns_zones(env)])
Ejemplo n.º 19
0
def provision_certificates(env, limit_domains):
    # What domains should we provision certificates for? And what
    # errors prevent provisioning for other domains.
    domains, domains_cant_provision = get_certificates_to_provision(
        env, limit_domains=limit_domains)

    # Build a list of what happened on each domain or domain-set.
    ret = []
    for domain, error in domains_cant_provision.items():
        ret.append({
            "domains": [domain],
            "log": [error],
            "result": "skipped",
        })

    # Break into groups by DNS zone: Group every domain with its parent domain, if
    # its parent domain is in the list of domains to request a certificate for.
    # Start with the zones so that if the zone doesn't need a certificate itself,
    # its children will still be grouped together. Sort the provision domains to
    # put parents ahead of children.
    # Since Let's Encrypt requests are limited to 100 domains at a time,
    # we'll create a list of lists of domains where the inner lists have
    # at most 100 items. By sorting we also get the DNS zone domain as the first
    # entry in each list (unless we overflow beyond 100) which ends up as the
    # primary domain listed in each certificate.
    from dns_update import get_dns_zones
    certs = {}
    for zone, zonefile in get_dns_zones(env):
        certs[zone] = [[]]
    for domain in sort_domains(domains, env):
        # Does the domain end with any domain we've seen so far.
        for parent in certs.keys():
            if domain.endswith("." + parent):
                # Add this to the parent's list of domains.
                # Start a new group if the list already has
                # 100 items.
                if len(certs[parent][-1]) == 100:
                    certs[parent].append([])
                certs[parent][-1].append(domain)
                break
        else:
            # This domain is not a child of any domain we've seen yet, so
            # start a new group. This shouldn't happen since every zone
            # was already added.
            certs[domain] = [[domain]]

    # Flatten to a list of lists of domains (from a mapping). Remove empty
    # lists (zones with no domains that need certs).
    certs = sum(certs.values(), [])
    certs = [_ for _ in certs if len(_) > 0]

    # Prepare to provision.

    # Where should we put our Let's Encrypt account info and state cache.
    account_path = os.path.join(env['STORAGE_ROOT'], 'ssl/lets_encrypt')
    if not os.path.exists(account_path):
        os.mkdir(account_path)

    # Provision certificates.
    for domain_list in certs:
        ret.append({
            "domains": domain_list,
            "log": [],
        })
        try:
            # Create a CSR file for our master private key so that certbot
            # uses our private key.
            key_file = os.path.join(env['STORAGE_ROOT'], 'ssl',
                                    'ssl_private_key.pem')
            with tempfile.NamedTemporaryFile() as csr_file:
                # We could use openssl, but certbot requires
                # that the CN domain and SAN domains match
                # the domain list passed to certbot, and adding
                # SAN domains openssl req is ridiculously complicated.
                # subprocess.check_output([
                # 	"openssl", "req", "-new",
                # 	"-key", key_file,
                # 	"-out", csr_file.name,
                # 	"-subj", "/CN=" + domain_list[0],
                # 	"-sha256" ])
                from cryptography import x509
                from cryptography.hazmat.backends import default_backend
                from cryptography.hazmat.primitives.serialization import Encoding
                from cryptography.hazmat.primitives import hashes
                from cryptography.x509.oid import NameOID
                builder = x509.CertificateSigningRequestBuilder()
                builder = builder.subject_name(
                    x509.Name([
                        x509.NameAttribute(NameOID.COMMON_NAME, domain_list[0])
                    ]))
                builder = builder.add_extension(x509.BasicConstraints(
                    ca=False, path_length=None),
                                                critical=True)
                builder = builder.add_extension(x509.SubjectAlternativeName(
                    [x509.DNSName(d) for d in domain_list]),
                                                critical=False)
                request = builder.sign(load_pem(load_cert_chain(key_file)[0]),
                                       hashes.SHA256(), default_backend())
                with open(csr_file.name, "wb") as f:
                    f.write(request.public_bytes(Encoding.PEM))

                # Provision, writing to a temporary file.
                webroot = os.path.join(account_path, 'webroot')
                os.makedirs(webroot, exist_ok=True)
                with tempfile.TemporaryDirectory() as d:
                    cert_file = os.path.join(d, 'cert_and_chain.pem')
                    print("Provisioning TLS certificates for " +
                          ", ".join(domain_list) + ".")
                    certbotret = subprocess.check_output(
                        [
                            "certbot",
                            "certonly",
                            #"-v", # just enough to see ACME errors
                            "--non-interactive",  # will fail if user hasn't registered during Mail-in-a-Box setup
                            "-d",
                            ",".join(domain_list),  # first will be main domain
                            "--csr",
                            csr_file.
                            name,  # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
                            "--cert-path",
                            os.path.join(d,
                                         'cert'),  # we only use the full chain
                            "--chain-path",
                            os.path.join(
                                d, 'chain'),  # we only use the full chain
                            "--fullchain-path",
                            cert_file,
                            "--webroot",
                            "--webroot-path",
                            webroot,
                            "--config-dir",
                            account_path,
                            #"--staging",
                        ],
                        stderr=subprocess.STDOUT).decode("utf8")
                    install_cert_copy_file(cert_file, env)

            ret[-1]["log"].append(certbotret)
            ret[-1]["result"] = "installed"
        except subprocess.CalledProcessError as e:
            ret[-1]["log"].append(e.output.decode("utf8"))
            ret[-1]["result"] = "error"
        except Exception as e:
            ret[-1]["log"].append(str(e))
            ret[-1]["result"] = "error"

    # Run post-install steps.
    ret.extend(post_install_func(env))

    # Return what happened with each certificate request.
    return ret