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 @@
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
.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
.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!