From 46ba62b7b12b049071b8b5cd7c7ba41624d8c256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Deppierraz?= Date: Sun, 11 Jun 2017 13:56:30 +0200 Subject: [PATCH] Add support for NS records in custom domains (#1177) --- management/dns_update.py | 20 +++++++++++++++++++- management/templates/custom-dns.html | 3 ++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index b3764f7f..76daa40b 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -12,6 +12,11 @@ import dns.resolver from mailconfig import get_mail_domains from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains +# From https://stackoverflow.com/questions/3026957/how-to-validate-a-domain-name-using-regex-php/16491074#16491074 +# Thanks to Onur Yıldırım +# This regular expression matches domain names according to RFCs, it also accepts fqdn with an leading dot +DOMAIN_RE = "^(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}(\.?)$" + def get_dns_domains(env): # Add all domain names in use by email users and mail aliases and ensure # PRIMARY_HOSTNAME is in the list. @@ -144,7 +149,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)) @@ -759,6 +764,9 @@ def set_custom_dns_record(qname, rtype, value, action, env): if qname != "_secondary_nameserver": raise ValueError("%s is not a domain name or a subdomain of a domain name managed by this box." % qname) + if not re.search(DOMAIN_RE, qname): + raise ValueError("Invalid name.") + # validate rtype rtype = rtype.upper() if value is not None and qname != "_secondary_nameserver": @@ -767,6 +775,16 @@ def set_custom_dns_record(qname, rtype, value, action, env): v = ipaddress.ip_address(value) # raises a ValueError if there's a problem if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.") if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.") + elif rtype in ("CNAME", "NS"): + if rtype == "NS" and qname == zone: + raise ValueError("NS records can only be set for subdomains.") + + # ensure value has a trailing dot + if not value.endswith("."): + value = value + "." + + if not re.search(DOMAIN_RE, value): + raise ValueError("Invalid value.") elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"): # anything goes pass diff --git a/management/templates/custom-dns.html b/management/templates/custom-dns.html index ac530cd2..bd51d151 100644 --- a/management/templates/custom-dns.html +++ b/management/templates/custom-dns.html @@ -39,6 +39,7 @@ + @@ -126,7 +127,7 @@ email The email address of any administrative user here. password That user’s password. qname The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.) -rtype The resource type. Defaults to A if omitted. Possible values: A (an IPv4 address), AAAA (an IPv6 address), TXT (a text string), CNAME (an alias, which is a fully qualified domain name — don’t forget the final period), MX, SRV, SSHFP or CAA. +rtype The resource type. Defaults to A if omitted. Possible values: A (an IPv4 address), AAAA (an IPv6 address), TXT (a text string), CNAME (an alias, which is a fully qualified domain name — don’t forget the final period), MX, SRV, SSHFP, CAA or NS. value For PUT, POST, and DELETE, the record’s value. If the rtype is A or AAAA and value is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the -4 or -6 options to curl). This is handy for dynamic DNS!