diff options
author | Martin Basti <mbasti@redhat.com> | 2014-05-16 12:21:04 +0200 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2014-06-03 15:55:32 +0200 |
commit | b964d2130a15d7b1561c66c721e3257ce0d24305 (patch) | |
tree | 6c67297f14b7b1a57e074a18d133d9c32cf27fe6 | |
parent | e70f88c696ef4efe405ef940f99f6955594cd0ab (diff) | |
download | freeipa-b964d2130a15d7b1561c66c721e3257ce0d24305.tar.gz freeipa-b964d2130a15d7b1561c66c721e3257ce0d24305.tar.xz freeipa-b964d2130a15d7b1561c66c721e3257ce0d24305.zip |
Modified dns related global functions
* Modified functions to use DNSName type
* Removed unused functions
Part of ticket:
IPA should allow internationalized domain names
https://fedorahosted.org/freeipa/ticket/3169
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
-rw-r--r-- | ipalib/plugins/dns.py | 121 | ||||
-rw-r--r-- | ipalib/plugins/host.py | 8 | ||||
-rw-r--r-- | ipalib/util.py | 58 | ||||
-rw-r--r-- | ipaserver/install/bindinstance.py | 4 |
4 files changed, 74 insertions, 117 deletions
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index bea8298d4..13f10ed65 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -24,6 +24,7 @@ import netaddr import time import re import dns.name +import dns.exception import dns.resolver from ipalib.request import context @@ -33,11 +34,14 @@ from ipalib.parameters import (Flag, Bool, Int, Decimal, Str, StrEnum, Any, DeprecatedParam) from ipalib.plugins.baseldap import * from ipalib import _, ngettext -from ipalib.util import (validate_zonemgr, normalize_zonemgr, normalize_zone, - validate_hostname, validate_dns_label, validate_domain_name, - get_dns_forward_zone_update_policy, get_dns_reverse_zone_update_policy, - get_reverse_zone_default, zone_is_reverse, REVERSE_DNS_ZONES) +from ipalib.util import (validate_zonemgr, normalize_zonemgr, + get_dns_forward_zone_update_policy, + get_dns_reverse_zone_update_policy, + normalize_zone, zone_is_reverse, + validate_domain_name, + get_reverse_zone_default, REVERSE_DNS_ZONES) from ipapython.ipautil import valid_ip, CheckedIPAddress, is_host_resolvable +from ipapython.dnsutil import DNSName __doc__ = _(""" Domain Name System (DNS) @@ -236,7 +240,7 @@ _record_types = ( ) # DNS zone record identificator -_dns_zone_record = u'@' +_dns_zone_record = DNSName.empty # most used record types, always ask for those in interactive prompt _top_record_types = ('A', 'AAAA', ) @@ -264,7 +268,7 @@ _output_permissions = ( def _rname_validator(ugettext, zonemgr): try: validate_zonemgr(zonemgr) - except ValueError, e: + except (ValueError, dns.exception.SyntaxError), e: return unicode(e) return None @@ -381,35 +385,6 @@ def _normalize_bind_aci(bind_acis): acis += u';' return acis -def _bind_hostname_validator(ugettext, value, allow_slash=False): - if value == _dns_zone_record: - return - try: - # Allow domain name which is not fully qualified. These are supported - # in bind and then translated as <non-fqdn-name>.<domain>. - validate_hostname(value, check_fqdn=False, allow_underscore=True, allow_slash=allow_slash) - except ValueError, e: - return _('invalid domain-name: %s') \ - % unicode(e) - - 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): - if value == _dns_zone_record: - return - - try: - map(lambda label:validate_dns_label(label, allow_underscore=True, allow_slash=True), \ - value.split(u'.')) - except ValueError, e: - return unicode(e) - def _validate_bind_forwarder(ugettext, forwarder): ip_address, sep, port = forwarder.partition(u' port ') @@ -438,21 +413,12 @@ def _domain_name_validator(ugettext, value): return unicode(e) def _hostname_validator(ugettext, value): - try: - validate_hostname(value) - except ValueError, e: - return _('invalid domain-name: %s') \ - % unicode(e) + assert isinstance(value, DNSName) + if len(value.make_absolute().labels) < 3: + return _('invalid domain-name: not fully qualified') return None -def _normalize_hostname(domain_name): - """Make it fully-qualified""" - if domain_name[-1] != '.': - return domain_name + '.' - else: - return domain_name - def is_forward_record(zone, str_address): addr = netaddr.IPAddress(str_address) if addr.version == 4: @@ -478,15 +444,16 @@ def add_forward_record(zone, name, str_address): def get_reverse_zone(ipaddr, prefixlen=None): ip = netaddr.IPAddress(str(ipaddr)) - revdns = unicode(ip.reverse_dns) + revdns = DNSName(unicode(ip.reverse_dns)) if prefixlen is None: - revzone = u'' + revzone = None result = api.Command['dnszone_find']()['result'] for zone in result: zonename = zone['idnsname'][0] - if revdns.endswith(zonename) and len(zonename) > len(revzone): + if (revdns.is_subdomain(zonename.make_absolute()) and + (revzone is None or zonename.is_subdomain(revzone))): revzone = zonename else: if ip.version == 4: @@ -494,23 +461,26 @@ def get_reverse_zone(ipaddr, prefixlen=None): elif ip.version == 6: pos = 32 - prefixlen / 4 items = ip.reverse_dns.split('.') - revzone = u'.'.join(items[pos:]) + revzone = DNSName(items[pos:]) try: api.Command['dnszone_show'](revzone) except errors.NotFound: - revzone = u'' + revzone = None - if len(revzone) == 0: + if revzone is None: raise errors.NotFound( reason=_('DNS reverse zone for IP address %(addr)s not found') % dict(addr=ipaddr) ) - revname = revdns[:-len(revzone)-1] + revname = revdns.relativize(revzone) return revzone, revname def add_records_for_host_validation(option_name, host, domain, ip_addresses, check_forward=True, check_reverse=True): + assert isinstance(host, DNSName) + assert isinstance(domain, DNSName) + try: api.Command['dnszone_show'](domain)['result'] except errors.NotFound: @@ -549,6 +519,9 @@ def add_records_for_host_validation(option_name, host, domain, ip_addresses, che def add_records_for_host(host, domain, ip_addresses, add_forward=True, add_reverse=True): + assert isinstance(host, DNSName) + assert isinstance(domain, DNSName) + if not isinstance(ip_addresses, (tuple, list)): ip_addresses = [ip_addresses] @@ -564,12 +537,25 @@ def add_records_for_host(host, domain, ip_addresses, add_forward=True, add_rever if not ip.defaultnet: prefixlen = ip.prefixlen revzone, revname = get_reverse_zone(ip, prefixlen) - addkw = { 'ptrrecord' : host + "." + domain } + addkw = {'ptrrecord': host.derelativize(domain).ToASCII()} api.Command['dnsrecord_add'](revzone, revname, **addkw) except errors.EmptyModlist: # the entry already exists and matches pass +def _dns_name_to_string(value, raw=False): + if isinstance(value, unicode): + try: + value = DNSName(value) + except Exception: + return value + + assert isinstance(value, DNSName) + if raw: + return value.ToASCII() + else: + return unicode(value) + class DNSRecord(Str): # a list of parts that create the actual raw DNS record parts = None @@ -1327,12 +1313,6 @@ class RPRecord(DNSRecord): rfc = 1183 supported = False -def _srv_target_validator(ugettext, value): - if value == u'.': - # service not available - return - return _bind_hostname_validator(ugettext, value) - class SRVRecord(DNSRecord): rrtype = 'SRV' rfc = 2782 @@ -1530,17 +1510,20 @@ _dns_supported_record_types = tuple(record.rrtype for record in _dns_records \ if record.supported) def check_ns_rec_resolvable(zone, name): - if name == _dns_zone_record: - name = normalize_zone(zone) - elif not name.endswith('.'): + assert isinstance(zone, DNSName) + assert isinstance(name, DNSName) + + if name.is_empty(): + name = zone.make_absolute() + elif not name.is_absolute(): # this is a DNS name relative to the zone - zone = dns.name.from_text(zone) - name = unicode(dns.name.from_text(name, origin=zone)) + name = name.derelativize(zone.make_absolute()) try: return api.Command['dns_resolve'](name) except errors.NotFound: raise errors.NotFound( - reason=_('Nameserver \'%(host)s\' does not have a corresponding A/AAAA record') % {'host': name} + reason=_('Nameserver \'%(host)s\' does not have a corresponding ' + 'A/AAAA record') % {'host': name} ) def dns_container_exists(ldap): @@ -1551,8 +1534,8 @@ def dns_container_exists(ldap): return True def default_zone_update_policy(zone): - if zone_is_reverse(zone): - return get_dns_reverse_zone_update_policy(api.env.realm, zone) + if zone.is_reverse(): + return get_dns_reverse_zone_update_policy(api.env.realm, zone.ToASCII()) else: return get_dns_forward_zone_update_policy(api.env.realm) diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index 8f770ff02..64d018532 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -42,6 +42,7 @@ from ipalib.request import context from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options, convert_sshpubkey_post) from ipapython.ipautil import ipa_generate_password, CheckedIPAddress +from ipapython.dnsutil import DNSName from ipapython.ssh import SSHPublicKey from ipapython.dn import DN @@ -442,7 +443,9 @@ class host_add(LDAPCreate): host = parts[0] domain = unicode('.'.join(parts[1:])) check_reverse = not options.get('no_reverse', False) - add_records_for_host_validation('ip_address', host, domain, + add_records_for_host_validation('ip_address', + DNSName(host), + DNSName(domain), options['ip_address'], check_forward=True, check_reverse=check_reverse) @@ -494,7 +497,8 @@ class host_add(LDAPCreate): if options.get('ip_address'): add_reverse = not options.get('no_reverse', False) - add_records_for_host(host, domain, options['ip_address'], + add_records_for_host(DNSName(host), DNSName(domain), + options['ip_address'], add_forward=True, add_reverse=add_reverse) del options['ip_address'] diff --git a/ipalib/util.py b/ipalib/util.py index 89c068008..7250fd9ce 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -37,6 +37,7 @@ from ipalib import errors from ipalib.text import _ from ipapython.ssh import SSHPublicKey from ipapython.dn import DN, RDN +from ipapython.dnsutil import DNSName def json_serialize(obj): @@ -198,8 +199,7 @@ def check_writable_file(filename): raise errors.FileError(reason=str(e)) def normalize_zonemgr(zonemgr): - if not zonemgr: - # do not normalize empty or None value + if not zonemgr or not isinstance(zonemgr, basestring): return zonemgr if '@' in zonemgr: # local-part needs to be normalized @@ -260,46 +260,20 @@ def validate_domain_name(domain_name, allow_underscore=False, allow_slash=False) def validate_zonemgr(zonemgr): + assert isinstance(zonemgr, DNSName) + assert zonemgr.is_absolute() """ See RFC 1033, 1035 """ - regex_local_part = re.compile(r'^[a-z0-9]([a-z0-9-_]?[a-z0-9])*$', - re.IGNORECASE) - local_part_errmsg = _('mail account may only include letters, numbers, -, _ and a dot. There may not be consecutive -, _ and . characters. Its parts may not start or end with - or _') - local_part_sep = '.' - local_part = None - domain = None - - if len(zonemgr) > 255: - raise ValueError(_('cannot be longer that 255 characters')) - - if zonemgr.endswith('.'): - zonemgr = zonemgr[:-1] - - if zonemgr.count('@') == 1: - local_part, dot, domain = zonemgr.partition('@') - elif zonemgr.count('@') > 1: + if any('@' in label for label in zonemgr.labels): raise ValueError(_('too many \'@\' characters')) - else: - last_fake_sep = zonemgr.rfind('\\.') - if last_fake_sep != -1: # there is a 'fake' local-part/domain separator - local_part_sep = '\\.' - sep = zonemgr.find('.', last_fake_sep+2) - if sep != -1: - local_part = zonemgr[:sep] - domain = zonemgr[sep+1:] - else: - local_part, dot, domain = zonemgr.partition('.') - - if not domain: + if len(zonemgr.labels) < 3: raise ValueError(_('missing address domain')) - - validate_domain_name(domain) - - if not local_part: + if not zonemgr.labels[0]: raise ValueError(_('missing mail account')) - if not all(regex_local_part.match(part) for part in \ - local_part.split(local_part_sep)): - raise ValueError(local_part_errmsg) +def validate_zonemgr_str(zonemgr): + zonemgr = normalize_zonemgr(zonemgr) + zonemgr = DNSName(zonemgr).make_absolute() + return validate_zonemgr(zonemgr) def validate_hostname(hostname, check_fqdn=True, allow_underscore=False, allow_slash=False): """ See RFC 952, 1123 @@ -546,16 +520,12 @@ def get_dns_reverse_zone_update_policy(realm, reverse_zone, rrtypes=('PTR',)): # dictionary of valid reverse zone -> number of address components REVERSE_DNS_ZONES = { - '.in-addr.arpa.' : 4, - '.ip6.arpa.' : 32, + DNSName.ip4_rev_zone : 4, + DNSName.ip6_rev_zone : 32, } def zone_is_reverse(zone_name): - zone_name = normalize_zone(zone_name) - if any(zone_name.endswith(name) for name in REVERSE_DNS_ZONES): - return True - - return False + return DNSName(zone_name).is_reverse() def get_reverse_zone_default(ip_address): ip = netaddr.IPAddress(str(ip_address)) diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index c5ff76726..af9ddbc9c 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -35,7 +35,7 @@ from ipapython.ipa_log_manager import * from ipapython.dn import DN import ipalib from ipalib import api, errors -from ipalib.util import (validate_zonemgr, normalize_zonemgr, +from ipalib.util import (validate_zonemgr_str, normalize_zonemgr, get_dns_forward_zone_update_policy, get_dns_reverse_zone_update_policy, normalize_zone, get_reverse_zone_default, zone_is_reverse) from ipalib.constants import CACERT @@ -383,7 +383,7 @@ def zonemgr_callback(option, opt_str, value, parser): """ # validate the value first try: - validate_zonemgr(value) + validate_zonemgr_str(value) except ValueError, e: parser.error("invalid zonemgr: " + unicode(e)) |