mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
DNS classless support for reverse domains
Now users can add reverse zones in classless form: 0/25.1.168.192.in-addr.arpa. 0-25.1.168.192.in-addr.arpa. 128/25 NS ns.example.com. 10 CNAME 10.128/25.1.168.192.in-addr.arpa. Ticket: https://fedorahosted.org/freeipa/ticket/4143 Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
parent
11505d9bce
commit
8ede71fd84
@ -368,25 +368,31 @@ def _normalize_bind_aci(bind_acis):
|
|||||||
acis += u';'
|
acis += u';'
|
||||||
return acis
|
return acis
|
||||||
|
|
||||||
def _bind_hostname_validator(ugettext, value):
|
def _bind_hostname_validator(ugettext, value, allow_slash=False):
|
||||||
if value == _dns_zone_record:
|
if value == _dns_zone_record:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
# Allow domain name which is not fully qualified. These are supported
|
# Allow domain name which is not fully qualified. These are supported
|
||||||
# in bind and then translated as <non-fqdn-name>.<domain>.
|
# in bind and then translated as <non-fqdn-name>.<domain>.
|
||||||
validate_hostname(value, check_fqdn=False, allow_underscore=True)
|
validate_hostname(value, check_fqdn=False, allow_underscore=True, allow_slash=allow_slash)
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
return _('invalid domain-name: %s') \
|
return _('invalid domain-name: %s') \
|
||||||
% unicode(e)
|
% unicode(e)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _bind_cname_hostname_validator(ugettext, value):
|
||||||
|
"""
|
||||||
|
Validator for CNAME allows classless domain names (25/0.0.10.in-addr.arpa.)
|
||||||
|
"""
|
||||||
|
return _bind_hostname_validator(ugettext, value, allow_slash=True)
|
||||||
|
|
||||||
def _dns_record_name_validator(ugettext, value):
|
def _dns_record_name_validator(ugettext, value):
|
||||||
if value == _dns_zone_record:
|
if value == _dns_zone_record:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
map(lambda label:validate_dns_label(label, allow_underscore=True), \
|
map(lambda label:validate_dns_label(label, allow_underscore=True, allow_slash=True), \
|
||||||
value.split(u'.'))
|
value.split(u'.'))
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
return unicode(e)
|
return unicode(e)
|
||||||
@ -411,7 +417,10 @@ def _validate_bind_forwarder(ugettext, forwarder):
|
|||||||
|
|
||||||
def _domain_name_validator(ugettext, value):
|
def _domain_name_validator(ugettext, value):
|
||||||
try:
|
try:
|
||||||
validate_domain_name(value)
|
#classless reverse zones can contain slash '/'
|
||||||
|
normalized_zone = normalize_zone(value)
|
||||||
|
validate_domain_name(value, allow_slash=zone_is_reverse(normalized_zone))
|
||||||
|
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
return unicode(e)
|
return unicode(e)
|
||||||
|
|
||||||
@ -939,7 +948,7 @@ class CNAMERecord(DNSRecord):
|
|||||||
rfc = 1035
|
rfc = 1035
|
||||||
parts = (
|
parts = (
|
||||||
Str('hostname',
|
Str('hostname',
|
||||||
_bind_hostname_validator,
|
_bind_cname_hostname_validator,
|
||||||
label=_('Hostname'),
|
label=_('Hostname'),
|
||||||
doc=_('A hostname which this alias hostname points to'),
|
doc=_('A hostname which this alias hostname points to'),
|
||||||
),
|
),
|
||||||
@ -960,7 +969,7 @@ class DNAMERecord(DNSRecord):
|
|||||||
rfc = 2672
|
rfc = 2672
|
||||||
parts = (
|
parts = (
|
||||||
Str('target',
|
Str('target',
|
||||||
_bind_hostname_validator,
|
_bind_cname_hostname_validator,
|
||||||
label=_('Target'),
|
label=_('Target'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -2131,6 +2140,14 @@ class dnsrecord(LDAPObject):
|
|||||||
doc=_('Parse all raw DNS records and return them in a structured way'),
|
doc=_('Parse all raw DNS records and return them in a structured way'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _idnsname_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
|
if not self.is_pkey_zone_record(*keys):
|
||||||
|
zone, addr = normalize_zone(keys[-2]), keys[-1]
|
||||||
|
try:
|
||||||
|
validate_domain_name(addr, allow_underscore=True, allow_slash=zone_is_reverse(zone))
|
||||||
|
except ValueError, e:
|
||||||
|
raise errors.ValidationError(name='idnsname', error=unicode(e))
|
||||||
|
|
||||||
def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
assert isinstance(dn, DN)
|
assert isinstance(dn, DN)
|
||||||
nsrecords = entry_attrs.get('nsrecord')
|
nsrecords = entry_attrs.get('nsrecord')
|
||||||
@ -2144,6 +2161,7 @@ class dnsrecord(LDAPObject):
|
|||||||
ptrrecords = entry_attrs.get('ptrrecord')
|
ptrrecords = entry_attrs.get('ptrrecord')
|
||||||
if ptrrecords is None:
|
if ptrrecords is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
zone = keys[-2]
|
zone = keys[-2]
|
||||||
if self.is_pkey_zone_record(*keys):
|
if self.is_pkey_zone_record(*keys):
|
||||||
addr = u''
|
addr = u''
|
||||||
@ -2165,6 +2183,11 @@ class dnsrecord(LDAPObject):
|
|||||||
error=unicode(_('Reverse zone for PTR record should be a sub-zone of one the following fully qualified domains: %s') % allowed_zones))
|
error=unicode(_('Reverse zone for PTR record should be a sub-zone of one the following fully qualified domains: %s') % allowed_zones))
|
||||||
|
|
||||||
addr_len = len(addr.split('.')) if addr else 0
|
addr_len = len(addr.split('.')) if addr else 0
|
||||||
|
|
||||||
|
#Classless zones (0/25.0.0.10.in-addr.arpa.) -> skip check
|
||||||
|
#zone has to be checked without reverse domain suffix (in-addr.arpa.)
|
||||||
|
if ('/' not in addr and '/' not in zone and
|
||||||
|
'-' not in addr and '-' not in zone):
|
||||||
ip_addr_comp_count = addr_len + len(zone.split('.'))
|
ip_addr_comp_count = addr_len + len(zone.split('.'))
|
||||||
if ip_addr_comp_count != zone_len:
|
if ip_addr_comp_count != zone_len:
|
||||||
raise errors.ValidationError(name='ptrrecord',
|
raise errors.ValidationError(name='ptrrecord',
|
||||||
|
@ -215,13 +215,21 @@ def normalize_zone(zone):
|
|||||||
else:
|
else:
|
||||||
return zone
|
return zone
|
||||||
|
|
||||||
def validate_dns_label(dns_label, allow_underscore=False):
|
|
||||||
label_chars = r'a-z0-9'
|
def validate_dns_label(dns_label, allow_underscore=False, allow_slash=False):
|
||||||
underscore_err_msg = ''
|
base_chars = 'a-z0-9'
|
||||||
|
extra_chars = ''
|
||||||
|
middle_chars = ''
|
||||||
|
|
||||||
if allow_underscore:
|
if allow_underscore:
|
||||||
label_chars += "_"
|
extra_chars += '_'
|
||||||
underscore_err_msg = u' _,'
|
if allow_slash:
|
||||||
label_regex = r'^[%(chars)s]([%(chars)s-]?[%(chars)s])*$' % dict(chars=label_chars)
|
middle_chars += '/'
|
||||||
|
|
||||||
|
middle_chars = middle_chars + '-' #has to be always the last in the regex [....-]
|
||||||
|
|
||||||
|
label_regex = r'^[%(base)s%(extra)s]([%(base)s%(extra)s%(middle)s]?[%(base)s%(extra)s])*$' \
|
||||||
|
% dict(base=base_chars, extra=extra_chars, middle=middle_chars)
|
||||||
regex = re.compile(label_regex, re.IGNORECASE)
|
regex = re.compile(label_regex, re.IGNORECASE)
|
||||||
|
|
||||||
if not dns_label:
|
if not dns_label:
|
||||||
@ -231,18 +239,21 @@ def validate_dns_label(dns_label, allow_underscore=False):
|
|||||||
raise ValueError(_('DNS label cannot be longer that 63 characters'))
|
raise ValueError(_('DNS label cannot be longer that 63 characters'))
|
||||||
|
|
||||||
if not regex.match(dns_label):
|
if not regex.match(dns_label):
|
||||||
raise ValueError(_('only letters, numbers,%(underscore)s and - are allowed. ' \
|
chars = ', '.join("'%s'" % c for c in extra_chars + middle_chars)
|
||||||
'DNS label may not start or end with -') \
|
chars2 = ', '.join("'%s'" % c for c in middle_chars)
|
||||||
% dict(underscore=underscore_err_msg))
|
raise ValueError(_("only letters, numbers, %(chars)s are allowed. " \
|
||||||
|
"DNS label may not start or end with %(chars2)s") \
|
||||||
|
% dict(chars=chars, chars2=chars2))
|
||||||
|
|
||||||
def validate_domain_name(domain_name, allow_underscore=False):
|
|
||||||
|
def validate_domain_name(domain_name, allow_underscore=False, allow_slash=False):
|
||||||
if domain_name.endswith('.'):
|
if domain_name.endswith('.'):
|
||||||
domain_name = domain_name[:-1]
|
domain_name = domain_name[:-1]
|
||||||
|
|
||||||
domain_name = domain_name.split(".")
|
domain_name = domain_name.split(".")
|
||||||
|
|
||||||
# apply DNS name validator to every name part
|
# apply DNS name validator to every name part
|
||||||
map(lambda label:validate_dns_label(label,allow_underscore), domain_name)
|
map(lambda label:validate_dns_label(label, allow_underscore, allow_slash), domain_name)
|
||||||
|
|
||||||
|
|
||||||
def validate_zonemgr(zonemgr):
|
def validate_zonemgr(zonemgr):
|
||||||
@ -287,7 +298,7 @@ def validate_zonemgr(zonemgr):
|
|||||||
local_part.split(local_part_sep)):
|
local_part.split(local_part_sep)):
|
||||||
raise ValueError(local_part_errmsg)
|
raise ValueError(local_part_errmsg)
|
||||||
|
|
||||||
def validate_hostname(hostname, check_fqdn=True, allow_underscore=False):
|
def validate_hostname(hostname, check_fqdn=True, allow_underscore=False, allow_slash=False):
|
||||||
""" See RFC 952, 1123
|
""" See RFC 952, 1123
|
||||||
|
|
||||||
:param hostname Checked value
|
:param hostname Checked value
|
||||||
@ -305,9 +316,9 @@ def validate_hostname(hostname, check_fqdn=True, allow_underscore=False):
|
|||||||
if '.' not in hostname:
|
if '.' not in hostname:
|
||||||
if check_fqdn:
|
if check_fqdn:
|
||||||
raise ValueError(_('not fully qualified'))
|
raise ValueError(_('not fully qualified'))
|
||||||
validate_dns_label(hostname,allow_underscore)
|
validate_dns_label(hostname, allow_underscore, allow_slash)
|
||||||
else:
|
else:
|
||||||
validate_domain_name(hostname,allow_underscore)
|
validate_domain_name(hostname, allow_underscore, allow_slash)
|
||||||
|
|
||||||
def normalize_sshpubkey(value):
|
def normalize_sshpubkey(value):
|
||||||
return SSHPublicKey(value).openssh()
|
return SSHPublicKey(value).openssh()
|
||||||
|
Loading…
Reference in New Issue
Block a user