2015-05-04 14:19:58 +00:00
|
|
|
import logging
|
2014-05-08 16:59:35 +00:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
|
|
|
|
from django.core.exceptions import ValidationError
|
2023-10-24 16:59:02 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-30 16:34:02 +00:00
|
|
|
from orchestra.core.validators import validate_hostname
|
2014-05-08 16:59:35 +00:00
|
|
|
from orchestra.utils import paths
|
2015-04-05 10:46:24 +00:00
|
|
|
from orchestra.utils.sys import run
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2015-04-27 14:54:17 +00:00
|
|
|
from .. import domains
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
|
2015-05-04 14:19:58 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def validate_allowed_domain(value):
|
|
|
|
context = {
|
2015-03-31 12:39:08 +00:00
|
|
|
'site_dir': paths.get_site_dir()
|
2014-05-08 16:59:35 +00:00
|
|
|
}
|
2015-04-27 14:54:17 +00:00
|
|
|
fname = domains.settings.DOMAINS_FORBIDDEN
|
2014-05-08 16:59:35 +00:00
|
|
|
if fname:
|
|
|
|
fname = fname % context
|
|
|
|
with open(fname, 'r') as forbidden:
|
|
|
|
for domain in forbidden.readlines():
|
|
|
|
if re.match(r'^(.*\.)*%s$' % domain.strip(), value):
|
|
|
|
raise ValidationError(_("This domain name is not allowed"))
|
|
|
|
|
|
|
|
|
2014-10-30 16:34:02 +00:00
|
|
|
def validate_domain_name(value):
|
2016-10-22 07:23:45 +00:00
|
|
|
# SRV, CNAME and TXT records may use '_' in the domain name
|
2014-10-30 16:34:02 +00:00
|
|
|
value = value.lstrip('*.').replace('_', '')
|
|
|
|
try:
|
|
|
|
validate_hostname(value)
|
|
|
|
except ValidationError:
|
|
|
|
raise ValidationError(_("Not a valid domain name."))
|
|
|
|
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def validate_zone_interval(value):
|
|
|
|
try:
|
|
|
|
int(value)
|
|
|
|
except ValueError:
|
|
|
|
value, magnitude = value[:-1], value[-1]
|
|
|
|
if magnitude not in ('s', 'm', 'h', 'd', 'w') or not value.isdigit():
|
|
|
|
msg = _("%s is not an appropiate zone interval value") % value
|
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_zone_label(value):
|
|
|
|
"""
|
2015-06-02 12:59:49 +00:00
|
|
|
Allowable characters in a label for a host name are only ASCII letters, digits, and the `-' character.
|
|
|
|
Labels may not be all numbers, but may have a leading digit (e.g., 3com.com).
|
|
|
|
Labels must end and begin only with a letter or digit. See [RFC 1035] and [RFC 1123].
|
2014-05-08 16:59:35 +00:00
|
|
|
"""
|
2024-02-26 13:04:16 +00:00
|
|
|
if not re.match(r'^[a-z0-9][\.\-0-9a-z_]*[\.0-9a-z]$', value):
|
2015-06-02 12:59:49 +00:00
|
|
|
msg = _("Labels must start and end with a letter or digit, "
|
|
|
|
"and have as interior characters only letters, digits, and hyphen.")
|
2014-05-08 16:59:35 +00:00
|
|
|
raise ValidationError(msg)
|
|
|
|
if not value.endswith('.'):
|
2015-06-02 12:59:49 +00:00
|
|
|
msg = _("Use a fully expanded domain name ending with a dot.")
|
2014-05-08 16:59:35 +00:00
|
|
|
raise ValidationError(msg)
|
2021-01-13 15:41:36 +00:00
|
|
|
if len(value) > 254:
|
2015-06-02 12:59:49 +00:00
|
|
|
raise ValidationError(_("Labels must be 63 characters or less."))
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def validate_mx_record(value):
|
2015-10-29 18:19:00 +00:00
|
|
|
msg = _("MX record format is 'priority domain.' tuple, with priority being a number.")
|
2014-05-08 16:59:35 +00:00
|
|
|
value = value.split()
|
2015-10-29 18:19:00 +00:00
|
|
|
if len(value) != 2:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
else:
|
2014-05-08 16:59:35 +00:00
|
|
|
try:
|
|
|
|
int(value[0])
|
|
|
|
except ValueError:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
value = value[1]
|
|
|
|
validate_zone_label(value)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_srv_record(value):
|
|
|
|
# 1 0 9 server.example.com.
|
|
|
|
msg = _("%s is not an appropiate SRV record value") % value
|
|
|
|
value = value.split()
|
|
|
|
for i in [0,1,2]:
|
|
|
|
try:
|
|
|
|
int(value[i])
|
|
|
|
except ValueError:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
validate_zone_label(value[-1])
|
|
|
|
|
|
|
|
|
|
|
|
def validate_soa_record(value):
|
|
|
|
# ns1.pangea.ORG. hostmaster.pangea.ORG. 2012010401 28800 7200 604800 86400
|
|
|
|
msg = _("%s is not an appropiate SRV record value") % value
|
|
|
|
values = value.split()
|
|
|
|
if len(values) != 7:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
validate_zone_label(values[0])
|
|
|
|
validate_zone_label(values[1])
|
|
|
|
for value in values[2:]:
|
|
|
|
try:
|
|
|
|
int(value)
|
|
|
|
except ValueError:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
2024-02-26 12:53:01 +00:00
|
|
|
def validate_caa_record(value):
|
|
|
|
# 0-255 issue|issuewild|iodef "domain|mailto:email"
|
|
|
|
# 0 issue "letsewncript.org"
|
|
|
|
msg = _("%s is not an appropiate CAA record value, sintax: 0-255 issue|issuewild|iodef \"domain|mailto:email\"") % value
|
|
|
|
values = value.split()
|
|
|
|
if len(values) != 3:
|
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
patron_flag = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
|
|
|
|
patron_tag = r'^(issue|issuewild|iodef)$'
|
|
|
|
patron_value_domain = r'^"[a-zA-Z0-9-.]+\.[a-zA-Z]+\.?"$'
|
|
|
|
patron_value_mailto = r'^"mailto:[a-zA-Z0-9.]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"$'
|
|
|
|
flag = re.match(patron_flag, values[0])
|
|
|
|
tag = re.match(patron_tag, values[1])
|
|
|
|
if values[1] == 'iodef':
|
|
|
|
valor = re.match(patron_value_mailto, values[2])
|
|
|
|
else:
|
|
|
|
valor = re.match(patron_value_domain, values[2])
|
|
|
|
if not (flag and tag and valor):
|
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
2015-07-21 10:44:32 +00:00
|
|
|
def validate_quoted_record(value):
|
|
|
|
value = value.strip()
|
|
|
|
if ' ' in value and (value[0] != '"' or value[-1] != '"'):
|
|
|
|
raise ValidationError(
|
|
|
|
_("This record value contains spaces, you must enclose the string in double quotes; "
|
|
|
|
"otherwise, individual words will be separately quoted and break up the record "
|
|
|
|
"into multiple parts.")
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def validate_zone(zone):
|
|
|
|
""" Ultimate zone file validation using named-checkzone """
|
|
|
|
zone_name = zone.split()[0][:-1]
|
2015-04-27 14:54:17 +00:00
|
|
|
zone_path = os.path.join(domains.settings.DOMAINS_ZONE_VALIDATION_TMP_DIR, zone_name)
|
|
|
|
checkzone = domains.settings.DOMAINS_CHECKZONE_BIN_PATH
|
2015-03-31 12:39:08 +00:00
|
|
|
try:
|
|
|
|
with open(zone_path, 'wb') as f:
|
2015-04-05 18:02:36 +00:00
|
|
|
f.write(zone.encode('ascii'))
|
2015-03-31 12:39:08 +00:00
|
|
|
# Don't use /dev/stdin becuase the 'argument list is too long' error
|
2015-05-09 17:08:45 +00:00
|
|
|
check = run(' '.join([checkzone, zone_name, zone_path]), valid_codes=(0,1,127), display=False)
|
2015-03-31 12:39:08 +00:00
|
|
|
finally:
|
2015-05-28 10:55:48 +00:00
|
|
|
try:
|
|
|
|
os.unlink(zone_path)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
2015-05-09 17:08:45 +00:00
|
|
|
if check.exit_code == 127:
|
2015-05-04 14:19:58 +00:00
|
|
|
logger.error("Cannot validate domain zone: %s not installed." % checkzone)
|
2015-05-09 17:08:45 +00:00
|
|
|
elif check.exit_code == 1:
|
2015-05-12 12:38:40 +00:00
|
|
|
errors = re.compile(r'zone.*: (.*)').findall(check.stdout.decode('utf8'))[:-1]
|
2014-05-08 16:59:35 +00:00
|
|
|
raise ValidationError(', '.join(errors))
|