django-orchestra/orchestra/contrib/domains/validators.py
2024-02-26 14:04:16 +01:00

160 lines
5.5 KiB
Python

import logging
import os
import re
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from orchestra.core.validators import validate_hostname
from orchestra.utils import paths
from orchestra.utils.sys import run
from .. import domains
logger = logging.getLogger(__name__)
def validate_allowed_domain(value):
context = {
'site_dir': paths.get_site_dir()
}
fname = domains.settings.DOMAINS_FORBIDDEN
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"))
def validate_domain_name(value):
# SRV, CNAME and TXT records may use '_' in the domain name
value = value.lstrip('*.').replace('_', '')
try:
validate_hostname(value)
except ValidationError:
raise ValidationError(_("Not a valid domain name."))
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):
"""
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].
"""
if not re.match(r'^[a-z0-9][\.\-0-9a-z_]*[\.0-9a-z]$', value):
msg = _("Labels must start and end with a letter or digit, "
"and have as interior characters only letters, digits, and hyphen.")
raise ValidationError(msg)
if not value.endswith('.'):
msg = _("Use a fully expanded domain name ending with a dot.")
raise ValidationError(msg)
if len(value) > 254:
raise ValidationError(_("Labels must be 63 characters or less."))
def validate_mx_record(value):
msg = _("MX record format is 'priority domain.' tuple, with priority being a number.")
value = value.split()
if len(value) != 2:
raise ValidationError(msg)
else:
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)
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)
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.")
)
def validate_zone(zone):
""" Ultimate zone file validation using named-checkzone """
zone_name = zone.split()[0][:-1]
zone_path = os.path.join(domains.settings.DOMAINS_ZONE_VALIDATION_TMP_DIR, zone_name)
checkzone = domains.settings.DOMAINS_CHECKZONE_BIN_PATH
try:
with open(zone_path, 'wb') as f:
f.write(zone.encode('ascii'))
# Don't use /dev/stdin becuase the 'argument list is too long' error
check = run(' '.join([checkzone, zone_name, zone_path]), valid_codes=(0,1,127), display=False)
finally:
try:
os.unlink(zone_path)
except FileNotFoundError:
pass
if check.exit_code == 127:
logger.error("Cannot validate domain zone: %s not installed." % checkzone)
elif check.exit_code == 1:
errors = re.compile(r'zone.*: (.*)').findall(check.stdout.decode('utf8'))[:-1]
raise ValidationError(', '.join(errors))