mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
parent
e70f88c696
commit
b964d2130a
@ -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)
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user