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))