#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention S4 Connector
# dns helper functions
#
# Copyright 2004-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
from __future__ import absolute_import
import ldap
import univention.debug2 as ud
import univention.s4connector.s4
import univention.admin.uldap
from univention.s4connector.s4.dc import _unixTimeInverval2seconds
from univention.s4connector.s4 import format_escaped, str2dn
from univention.admin.mapping import unmapUNIX_TimeInterval
from samba.dcerpc import dnsp
from samba.ndr import ndr_pack, ndr_unpack
import copy
import time
from dns.rdtypes.ANY.TXT import TXT
from dns import rdatatype
from dns import rdataclass
from dns.tokenizer import Tokenizer
from samba.provision.sambadns import ARecord
# def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import AAAARecord
# def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import NSRecord
# def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import SOARecord
# def __init__(self, mname, rname, serial=1, refresh=900, retry=600, expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import SRVRecord
# def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import CNameRecord
# def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
from samba.provision.sambadns import TXTRecord
# def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
import univention.admin.handlers
import univention.admin.handlers.dns.forward_zone
import univention.admin.handlers.dns.alias
import univention.admin.handlers.dns.host_record
import univention.admin.handlers.dns.srv_record
import univention.admin.handlers.dns.reverse_zone
import univention.admin.handlers.dns.ptr_record
[docs]class PTRRecord(dnsp.DnssrvRpcRecord):
def __init__(self, ptr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
super(PTRRecord, self).__init__()
self.wType = dnsp.DNS_TYPE_PTR
self.rank = rank
self.dwSerial = serial
self.dwTtlSeconds = ttl
self.data = ptr
[docs]class MXRecord(dnsp.DnssrvRpcRecord):
def __init__(self, name, priority, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
super(MXRecord, self).__init__()
self.wType = dnsp.DNS_TYPE_MX
self.rank = rank
self.dwSerial = serial
self.dwTtlSeconds = ttl
self.data.wPriority = priority
self.data.nameTarget = name
# mapping functions
[docs]def dns_dn_mapping(s4connector, given_object, dn_mapping_stored, isUCSobject):
'''
map dn of given object (which must have an s4_RR_attr in S4)
ol_oc_filter and s4_RR_filter are objectclass filters in UCS and S4
Code is based on univention.s4connector.s4.samaccountname_dn_mapping
'''
obj = copy.deepcopy(given_object)
propertyname = 'dns'
propertyattrib = u'relativeDomainName' # using LDAP name here, for simplicity
ol_oc_filter = '(objectClass=dNSZone)' # all OpenLDAP DNS records match
ol_RR_attr = 'relativeDomainName'
s4_RR_filter = u'(objectClass=dnsNode)' # This also matches the DC=@ SOA object
s4_RR_attr = 'dc' # Note: the S4 attribute itself is lowercase
if obj['dn'] is not None:
try:
s4_RR_val = [_value for _key, _value in obj['attributes'].items() if s4_RR_attr.lower() == _key.lower()][0][0].decode('UTf-8')
except (KeyError, IndexError):
s4_RR_val = u''
def dn_premapped(given_object, dn_key, dn_mapping_stored):
if (dn_key not in dn_mapping_stored) or (not given_object[dn_key]):
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: not premapped (in first instance)")
return False
else: # check if DN exists
if isUCSobject:
premapped_dn = s4connector.get_object_dn(given_object[dn_key])
if premapped_dn is not None:
# ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped S4 object found")
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped S4 object: %s" % premapped_dn)
return True
else:
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped S4 object not found")
return False
else:
premapped_dn = s4connector.get_ucs_ldap_object_dn(given_object[dn_key])
if premapped_dn is not None:
# ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped UCS object found")
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped UCS object: %s" % premapped_dn)
return True
else:
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: premapped UCS object not found")
return False
for dn_key in ['dn', 'olddn']:
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: check newdn for key '%s'" % dn_key)
if dn_key in obj and not dn_premapped(obj, dn_key, dn_mapping_stored):
dn = obj[dn_key]
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: dn: %s" % dn)
# Skip Configuration objects with empty DNs
if dn is None:
break
exploded_dn = str2dn(dn)
(fst_rdn_attribute_utf8, fst_rdn_value_utf8, _flags) = exploded_dn[0][0]
if isUCSobject:
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: got an UCS-Object")
# lookup the relativeDomainName as DC/dnsNode in S4 to get corresponding DN, if not found create new
# Case move with rename
if dn_key == 'olddn' and fst_rdn_attribute_utf8 == 'relativeDomainName':
relativeDomainName = fst_rdn_value_utf8
else:
try:
relativeDomainName = obj['attributes'][ol_RR_attr][0].decode('UTF-8')
except (KeyError, IndexError):
# Safety fallback for the unexpected case, where relativeDomainName would not be set
if 'zoneName' == fst_rdn_attribute_utf8:
relativeDomainName = '@'
else:
raise # can't determine relativeDomainName
for ucsval, conval in s4connector.property[propertyname].mapping_table.get(propertyattrib, []):
if relativeDomainName.lower() == ucsval.lower():
relativeDomainName = conval
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: map relativeDomainName according to mapping-table")
continue
try:
ol_zone_name = obj['attributes']['zoneName'][0].decode('UTF-8')
except (KeyError, IndexError):
# Safety fallback for the unexpected case, where zoneName would not be set
if ol_RR_attr == fst_rdn_attribute_utf8:
(snd_rdn_attribute_utf8, snd_rdn_value_utf8, _flags) = exploded_dn[1][0]
if 'zoneName' == snd_rdn_attribute_utf8:
ol_zone_name = snd_rdn_value_utf8
else:
raise # can't determine zoneName for this relativeDomainName
target_RR_val = relativeDomainName
target_zone_name = ol_zone_name
s4dn_utf16_le = None
s4_zone_dn = None
if '@' == relativeDomainName: # or dn starts with 'zoneName='
s4_filter = format_escaped('(&(objectClass=dnsZone)({0}={1!e}))', s4_RR_attr, ol_zone_name)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: search in S4")
for base in s4connector.s4_ldap_partitions:
result = s4connector._s4__search_s4(
base,
ldap.SCOPE_SUBTREE,
s4_filter,
attrlist=(s4_RR_attr,),
show_deleted=False)
if result:
# We only need the SOA dn here
s4dn_utf16_le = ldap.dn.dn2str([[('DC', '@', ldap.AVA_STRING)]] + str2dn(result[0][0]))
break
else:
# identify position by parent zone name
target_zone_dn = s4connector.lo.parentDn(dn)
if s4connector.configRegistry.get('connector/s4/mapping/dns/position') != 'legacy':
if relativeDomainName.endswith('._msdcs'):
target_zone_name = '_msdcs.' + ol_zone_name
target_RR_val = relativeDomainName[:-7]
target_zone_dn = ldap.dn.dn2str([[(s4_RR_attr.upper(), target_zone_name, ldap.AVA_STRING)]] + exploded_dn[2:])
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: get dns_dn_mapping for target zone %s" % target_zone_dn)
fake_ol_zone_object = {
'dn': target_zone_dn,
'attributes': {
'objectClass': [b'top', b'dNSZone'],
'relativeDomainName': [b'@'],
'zoneName': [target_zone_name.encode('UTF-8')],
},
}
s4_soa_object = dns_dn_mapping(s4connector, fake_ol_zone_object, dn_mapping_stored, isUCSobject)
# and use its parent as the search base
if s4_soa_object['dn'].startswith('DC=@,'):
s4_zone_dn = s4connector.lo_s4.parentDn(s4_soa_object['dn'])
else:
# There is the corner case, where con2ucsc
# syncs the objectClass=dnsZone container and
# stores it's DN in the premapping.
# After that, we don't get the DC=@ dnsNode
# object DN here, but directly the parent.
# So, actually it's not the SOA object DN:
s4_zone_dn = s4_soa_object['dn']
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: search in S4 base %s" % (s4_zone_dn,))
s4_filter = format_escaped('(&{0}({1}={2!e}))', s4_RR_filter, s4_RR_attr, target_RR_val)
result = s4connector._s4__search_s4(
s4_zone_dn,
ldap.SCOPE_SUBTREE,
s4_filter,
attrlist=('dn',),
show_deleted=False)
if result:
s4dn_utf16_le = result[0][0]
if s4dn_utf16_le: # no referral, so we've got a valid result
s4dn = s4dn_utf16_le
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: got s4dn %s" % (s4dn,))
if dn_key == 'olddn' or (dn_key == 'dn' and 'olddn' not in obj):
# Cases: ("delete") or ("add" but exists already)
newdn = s4dn
else:
# Case: "moved" (?)
raw_new_dn = ldap.dn.dn2str([str2dn(s4dn)[0]] + exploded_dn[1:])
# The next line looks wrong to me: the source DN is a UCS dn here..
# But this is just like samaccountname_dn_mapping does it:
newdn = raw_new_dn.lower().replace(s4connector.lo_s4.base.lower(), s4connector.lo.base.lower())
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: move case newdn=%s" % newdn)
else:
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: target object not found")
if s4_zone_dn:
# At least we found the zone
zone_dn = s4_zone_dn
relativeDomainName = target_RR_val
else:
# Ok, it's a new object without existing parent zone in S4 (probably this object itself is a soa/zone), so propose an S4 DN for it:
default_dn = s4connector.property['dns'].con_default_dn
zone_dn = ldap.dn.dn2str([[('DC', ol_zone_name, ldap.AVA_STRING)]] + str2dn(default_dn))
newdn = ldap.dn.dn2str([[('DC', relativeDomainName, ldap.AVA_STRING)]] + str2dn(zone_dn))
else:
# get the object to read the s4_RR_attr in S4 and use it as name
# we have no fallback here, the given dn must be found in S4 or we've got an error
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: got an S4-Object")
i = 0
while not s4_RR_val: # in case of olddn this is already set
i = i + 1
search_base_dn = obj.get('deleted_dn', dn)
try:
search_result = s4connector.lo_s4.search(filter=s4_RR_filter, base=search_base_dn, scope='base', attr=[s4_RR_attr], required=True)
except ldap.NO_SUCH_OBJECT: # S4 may need time
if i > 5:
raise
time.sleep(1) # S4 may need some time...
else:
(_search_result_dn, search_result_attributes) = search_result[0]
search_result_attributes = dict((k.lower(), v) for k, v in search_result_attributes)
s4_RR_val = search_result_attributes[s4_RR_attr.lower()][0].decode('UTF-8')
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: got %s from S4" % s4_RR_attr)
for ucsval, conval in s4connector.property[propertyname].mapping_table.get(propertyattrib, []):
if s4_RR_val.lower() == conval.lower():
s4_RR_val = ucsval
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: map %s according to mapping-table" % s4_RR_attr)
continue
# search for object with this dn in ucs, needed if it is located in a different container
try:
s4_ocs = obj['attributes']['objectClass']
except (KeyError, TypeError):
s4_ocs = []
target_RR_val = s4_RR_val
ol_zone_dn = None
if b'dnsZone' in s4_ocs:
if s4connector.configRegistry.get('connector/s4/mapping/dns/position') != 'legacy':
if s4_RR_val.startswith('_msdcs.'):
target_RR_val = s4_RR_val[7:]
target_zone_name = target_RR_val
base = s4connector.lo.base
ol_search_attr = 'zoneName'
# could use a specific LDAP filter here, but not necessary:
# ol_oc_filter = '(&(objectClass=dNSZone)(|(univentionObjectType=dns/forward_zone)(univentionObjectType=dns/reverse_zone)))'
elif b'dnsNode' in s4_ocs:
# identify position of the parent zone
(snd_rdn_attribute_utf8, snd_rdn_value_utf8, _flags) = exploded_dn[1][0]
target_zone_name = snd_rdn_value_utf8
target_zone_dn = s4connector.lo_s4.parentDn(dn)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: get dns_dn_mapping for %s" % target_zone_dn)
if s4connector.configRegistry.get('connector/s4/mapping/dns/position') != 'legacy':
if target_zone_name.startswith('_msdcs.'):
target_zone_name = target_zone_name[7:]
target_RR_val += '._msdcs'
target_zone_dn = ldap.dn.dn2str([[(snd_rdn_attribute_utf8, target_zone_name, ldap.AVA_STRING)]] + exploded_dn[2:])
fake_s4_zone_object = {
'dn': target_zone_dn,
'attributes': {
'objectClass': [b'top', b'dnsZone'],
'dc': [target_zone_name.encode('UTF-8')],
},
}
ol_zone_object = dns_dn_mapping(s4connector, fake_s4_zone_object, dn_mapping_stored, isUCSobject)
# and use that as the search base
ol_zone_dn = ol_zone_object['dn']
base = ol_zone_dn
ol_search_attr = ol_RR_attr
# could use a specific LDAP filter here, but not necessary:
# ol_oc_filter = '(&(objectClass=dNSZone)(!(|(univentionObjectType=dns/forward_zone)(univentionObjectType=dns/reverse_zone))))'
s4_filter = format_escaped('(&{0}({1}={2!e}))', ol_oc_filter, ol_search_attr, target_RR_val)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: UCS filter: %s" % s4_filter)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: UCS base: %s" % (base,))
try:
ucsdn_result = s4connector.search_ucs(filter=s4_filter, base=base, scope='sub', attr=('1.1',))
except univention.admin.uexceptions.noObject:
ucsdn_result = None
try:
ucsdn = ucsdn_result[0][0]
except (IndexError, TypeError):
ucsdn = None
ud.debug(ud.LDAP, ud.ALL, "dns_dn_mapping: Found ucsdn: %s" % ucsdn)
if ucsdn and (dn_key == 'olddn' or (dn_key == 'dn' and 'olddn' not in obj)):
# Cases: ("delete") or ("add" but exists already)
newdn = ucsdn
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: newdn is ucsdn")
else:
# Cases: (Target not found) or/and ("moved" (?))
# Ok, it's a new object, so propose a S4 DN for it:
if ol_zone_dn:
# At least we found the zone
zone_dn = ol_zone_dn
s4_RR_val = target_RR_val
else:
# Fallback, e.g. for new zones
zone_dn = __get_zone_dn(s4connector, target_zone_name)
if '@' == s4_RR_val:
newdn = zone_dn
elif b'dnsZone' in s4_ocs:
# Hmm, is it ok to map it to the same as '@'?
newdn = zone_dn
else:
newdn = ldap.dn.dn2str([[('relativeDomainName', s4_RR_val, ldap.AVA_STRING)]] + str2dn(zone_dn))
if not (dn_key == 'olddn' or (dn_key == 'dn' and 'olddn' not in obj)):
# Case: "moved" (?)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: move case newdn=%s" % newdn)
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: mapping for key %r:" % (dn_key,))
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: source DN: %r" % (dn,))
ud.debug(ud.LDAP, ud.INFO, "dns_dn_mapping: mapped DN: %r" % (newdn,))
obj[dn_key] = newdn
return obj
''' HELPER functions '''
def __get_zone_dn(s4connector, zone_name):
default_dn = s4connector.property['dns'].ucs_default_dn
return ldap.dn.dn2str([[('zoneName', zone_name, ldap.AVA_STRING)]] + str2dn(default_dn))
def __append_dot(string):
if not string.endswith('.'):
string += '.'
return string
def __remove_dot(string):
if string.endswith(b'.'):
string = string[:-1]
return string
def __split_s4_dnsNode_dn(dn):
exploded_dn = str2dn(dn) # TODO: fix encoding
# split the DC= from the zoneName
(_, zoneName, _) = exploded_dn[1][0]
(_, relativeDomainName, _) = exploded_dn[0][0]
return (zoneName, relativeDomainName)
def __split_ol_dNSZone_dn(dn, objectclasses):
exploded_dn = str2dn(dn)
(fst_rdn_attribute_utf8, fst_rdn_value_utf8, _flags) = exploded_dn[0][0]
(snd_rdn_attribute_utf8, snd_rdn_value_utf8, _flags) = exploded_dn[1][0]
if fst_rdn_attribute_utf8.lower() == 'zonename':
zoneName = fst_rdn_value_utf8
if b'dnsNode' in objectclasses:
relativeDomainName = '@'
elif b'dnsZone' in objectclasses:
# make S4 dnsZone containers distinguishable from SOA records
relativeDomainName = zoneName
else:
relativeDomainName = None
elif snd_rdn_attribute_utf8.lower() == 'zonename':
zoneName = snd_rdn_value_utf8
relativeDomainName = fst_rdn_value_utf8
else:
zoneName = None
relativeDomainName = None
ud.debug(ud.LDAP, ud.WARN, 'Failed to get zone name for object %r' % (dn,))
return (zoneName, relativeDomainName)
def __create_s4_forward_zone(s4connector, zone_dn):
al = []
al.append(('objectClass', [b'top', b'dnsZone']))
ud.debug(ud.LDAP, ud.INFO, '_dns_zone_forward_con_create: dn: %s' % zone_dn)
ud.debug(ud.LDAP, ud.INFO, '_dns_zone_forward_con_create: al: %s' % al)
s4connector.lo_s4.lo.add_s(zone_dn, al)
def __create_s4_forward_zone_soa(s4connector, soa_dn):
al = []
al.append(('objectClass', [b'top', b'dnsNode']))
al.append(('dc', [b'@']))
s4connector.lo_s4.lo.add_s(soa_dn, al)
def __create_s4_dns_node(s4connector, dnsNodeDn, relativeDomainNames, dnsRecords):
al = []
al.append(('objectClass', [b'top', b'dnsNode']))
al.append(('dc', relativeDomainNames))
if dnsRecords:
al.append(('dnsRecord', dnsRecords))
ud.debug(ud.LDAP, ud.INFO, '__create_s4_dns_node: dn: %s' % dnsNodeDn)
ud.debug(ud.LDAP, ud.INFO, '__create_s4_dns_node: al: %s' % al)
s4connector.lo_s4.lo.add_s(dnsNodeDn, al)
''' Pack and unpack DNS records by using the
Samba NDR functions
'''
def __pack_aRecord(object, dnsRecords):
# add aRecords
# IPv4
for a in object['attributes'].get('aRecord', []):
a_record = ARecord(a)
dnsRecords.append(ndr_pack(a_record))
# IPv6
for a in object['attributes'].get('aAAARecord', []):
a_record = AAAARecord(a)
dnsRecords.append(ndr_pack(a_record))
def __unpack_aRecord(object):
a = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_A or ndrRecord.wType == dnsp.DNS_TYPE_AAAA:
a.append(ndrRecord.data)
return a
def __pack_soaRecord(object, dnsRecords):
soaRecord = object['attributes'].get('sOARecord', [None])[0]
if soaRecord:
soa = soaRecord.split(b' ')
mname = soa[0]
rname = soa[1]
serial = int(soa[2])
refresh = int(soa[3])
retry = int(soa[4])
expire = int(soa[5])
ttl = int(soa[6])
soa_record = SOARecord(mname=mname, rname=rname, serial=serial, refresh=refresh, retry=retry, expire=expire, minimum=3600, ttl=ttl)
dnsRecords.append(ndr_pack(soa_record))
def __unpack_soaRecord(object):
soa = {}
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_SOA:
soa['mname'] = ndrRecord.data.mname
soa['rname'] = ndrRecord.data.rname
soa['serial'] = str(ndrRecord.data.serial)
soa['refresh'] = str(ndrRecord.data.refresh)
soa['retry'] = str(ndrRecord.data.retry)
soa['expire'] = str(ndrRecord.data.expire)
soa['minimum'] = str(ndrRecord.data.minimum)
soa['ttl'] = str(ndrRecord.dwTtlSeconds)
return soa
def __pack_nsRecord(object, dnsRecords):
for nSRecord in object['attributes'].get('nSRecord', []):
a_record = NSRecord(nSRecord)
dnsRecords.append(ndr_pack(a_record))
def __unpack_nsRecord(object):
ns = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_NS:
ns.append(__append_dot(ndrRecord.data))
return ns
def __pack_mxRecord(object, dnsRecords):
for mXRecord in object['attributes'].get('mXRecord', []):
if mXRecord:
ud.debug(ud.LDAP, ud.INFO, '__pack_mxRecord: %s' % mXRecord)
mx = mXRecord.split(b' ')
priority = mx[0]
name = mx[1]
mx_record = MXRecord(name, int(priority))
dnsRecords.append(ndr_pack(mx_record))
ud.debug(ud.LDAP, ud.INFO, '__pack_mxRecord: %s' % ndr_pack(mx_record))
def __unpack_mxRecord(object):
mx = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_MX:
mx.append([str(ndrRecord.data.wPriority), __append_dot(ndrRecord.data.nameTarget)])
return mx
def __pack_txtRecord(object, dnsRecords):
for txtRecord in object['attributes'].get('tXTRecord', []):
if txtRecord:
ud.debug(ud.LDAP, ud.INFO, '__pack_txtRecord: %s' % txtRecord)
token_list = TXT.from_text(rdataclass.IN, rdatatype.TXT, Tokenizer(txtRecord)).strings
ndr_txt_record = ndr_pack(TXTRecord(token_list))
dnsRecords.append(ndr_txt_record)
ud.debug(ud.LDAP, ud.INFO, '__pack_txtRecord: %s' % ndr_txt_record)
def __unpack_txtRecord(object):
txt = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_TXT:
txt.append(str(TXT(rdataclass.IN, rdatatype.TXT, ndrRecord.data.str)))
# or: txt.append(' '.join(['"%s"' % token for token in ndrRecord.data.str]))
return txt
def __pack_cName(object, dnsRecords):
for c in object['attributes'].get('cNAMERecord', []):
c = __remove_dot(c)
c_record = CNameRecord(c)
dnsRecords.append(ndr_pack(c_record))
def __unpack_cName(object):
c = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_CNAME:
if "." in ndrRecord.data:
c.append(__append_dot(ndrRecord.data))
else:
c.append(ndrRecord.data)
return c
def __pack_sRVrecord(object, dnsRecords):
for srvRecord in object['attributes'].get('sRVRecord', []):
srv = srvRecord.split(b' ')
priority = int(srv[0])
weight = int(srv[1])
port = int(srv[2])
target = __remove_dot(srv[3])
s = SRVRecord(target, port, priority, weight)
dnsRecords.append(ndr_pack(s))
def __unpack_sRVrecord(object):
srv = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_SRV:
srv.append([str(ndrRecord.data.wPriority), str(ndrRecord.data.wWeight), str(ndrRecord.data.wPort), __append_dot(ndrRecord.data.nameTarget)])
return srv
def __pack_ptrRecord(object, dnsRecords):
for ptr in object['attributes'].get('pTRRecord', []):
ptr = __remove_dot(ptr)
ptr_record = PTRRecord(ptr)
dnsRecords.append(ndr_pack(ptr_record))
def __unpack_ptrRecord(object):
ptr = []
dnsRecords = object['attributes'].get('dnsRecord', [])
for dnsRecord in dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_PTR:
ptr.append(__append_dot(ndrRecord.data))
return ptr
def __get_s4_msdcs_soa(s4connector, zoneName):
''' Required to keep the SOA serial numbers in sync
'''
msdcs_obj = {}
msdcs_zonename = '_msdcs.%s' % (zoneName,)
s4_filter = format_escaped('(&(objectClass=dnsZone)(DC={0!e}))', msdcs_zonename)
ud.debug(ud.LDAP, ud.INFO, "__get_s4_msdcs_soa: search _msdcs in S4")
msdcs_obj = {}
for base in s4connector.s4_ldap_partitions:
resultlist = s4connector._s4__search_s4(
base,
ldap.SCOPE_SUBTREE,
s4_filter,
show_deleted=False)
if resultlist:
break
else:
ud.debug(ud.LDAP, ud.WARN, "__get_s4_msdcs_soa: _msdcs sub-zone for %s not found in S4" % (zoneName,))
return
# We need the SOA here
msdcs_soa_dn = ldap.dn.dn2str([[('DC', '@', ldap.AVA_STRING)]] + str2dn(resultlist[0][0]))
ud.debug(ud.LDAP, ud.INFO, "__get_s4_msdcs_soa: search DC=@ for _msdcs in S4")
resultlist = s4connector._s4__search_s4(
msdcs_soa_dn,
ldap.SCOPE_BASE,
'(objectClass=dnsNode)',
show_deleted=False)
if resultlist:
# __object_from_element not required here
msdcs_obj = s4connector._s4__object_from_element(resultlist[0])
return msdcs_obj
''' Create/modify a DNS zone in Samba 4 '''
[docs]def s4_zone_create(s4connector, object):
soa_dn = object['dn']
zone_dn = s4connector.lo.parentDn(soa_dn)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
# Create the forward zone in S4 if it does not exist
try:
s4connector.lo_s4.get(zone_dn, attr=[''], required=True)
except ldap.NO_SUCH_OBJECT:
__create_s4_forward_zone(s4connector, zone_dn)
# Create SOA DC=@ object
old_dnsRecords = []
try:
old_dnsRecords = s4connector.lo_s4.get(soa_dn, attr=['dnsRecord'], required=True).get('dnsRecord')
except ldap.NO_SUCH_OBJECT:
__create_s4_forward_zone_soa(s4connector, soa_dn)
dnsRecords = []
__pack_nsRecord(object, dnsRecords)
__pack_soaRecord(object, dnsRecords)
# The IP address of the DNS forward zone will be used to determine the
# sysvol share. On a selective replicated DC only a short list of DCs
# should be returned
aRecords = s4connector.configRegistry.get('connector/s4/mapping/dns/forward_zone/%s/static/ipv4' % zoneName.lower())
aAAARecords = s4connector.configRegistry.get('connector/s4/mapping/dns/forward_zone/%s/static/ipv6' % zoneName.lower())
if aRecords or aAAARecords:
# IPv4
if aRecords:
for a in aRecords.split(' '):
a_record = ARecord(a)
dnsRecords.append(ndr_pack(a_record))
# IPv6
if aAAARecords:
for a in aAAARecords.split(' '):
a_record = AAAARecord(a)
dnsRecords.append(ndr_pack(a_record))
else:
__pack_aRecord(object, dnsRecords)
__pack_mxRecord(object, dnsRecords)
__pack_txtRecord(object, dnsRecords)
s4connector.lo_s4.modify(soa_dn, [('dnsRecord', old_dnsRecords, dnsRecords)])
return True
[docs]def s4_zone_msdcs_sync(s4connector, object):
# Get the current serial number of the OpenLDAP domainname zone
domainZoneName = object['attributes']['zoneName'][0].decode('UTF-8')
soaRecord = object['attributes'].get('sOARecord', [None])[0]
if not soaRecord:
ud.debug(ud.LDAP, ud.WARN, 's4_zone_msdcs_sync: OL zone %s has no SOA info' % domainZoneName)
return
soa = soaRecord.split(b' ')
serial = int(soa[2])
# lookup the SOA record of the _msdcs sub-zone for the domainname zone
msdcs_soa_obj = __get_s4_msdcs_soa(s4connector, domainZoneName)
if not msdcs_soa_obj:
return
msdcs_soa_dn = msdcs_soa_obj['dn']
dnsRecords = []
old_dnsRecords = msdcs_soa_obj['attributes'].get('dnsRecord', [])
found = False
for dnsRecord in old_dnsRecords:
ndrRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
if ndrRecord.wType == dnsp.DNS_TYPE_SOA:
if ndrRecord.data.serial >= serial:
ud.debug(ud.LDAP, ud.WARN, 's4_zone_msdcs_sync: SOA serial OpenLDAP zone %s is higher than corresponding value of %s' % (domainZoneName, msdcs_soa_dn))
return
ndrRecord.data.serial = serial
dnsRecords.append(ndr_pack(ndrRecord))
found = True
else:
dnsRecords.append(dnsRecord)
if not found:
ud.debug(ud.LDAP, ud.WARN, 's4_zone_msdcs_sync: object %s has no SOA info' % msdcs_soa_dn)
return
s4connector.lo_s4.modify(msdcs_soa_dn, [('dnsRecord', old_dnsRecords, dnsRecords)])
return True
''' Create/modify a DNS zone and possibly _msdcs in Samba 4 '''
[docs]def s4_zone_create_wrapper(s4connector, object):
''' Handle s4_zone_create to additionally sync to _msdcs.$domainname
Required to keep the SOA serial numbers in sync
'''
result = s4_zone_create(s4connector, object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
if zoneName == s4connector.configRegistry.get('domainname') and s4connector.configRegistry.get('connector/s4/mapping/dns/position') != 'legacy' and object['modtype'] == 'modify':
# Additionally sync serialNumber to _msdcs zone
result = result and s4_zone_msdcs_sync(s4connector, object)
return result
''' Delete a forward zone in Samaba 4 '''
[docs]def s4_zone_delete(s4connector, object):
soa_dn = object['dn']
zone_dn = s4connector.lo.parentDn(soa_dn)
try:
s4connector.lo_s4.lo.delete_s(soa_dn)
except ldap.NO_SUCH_OBJECT:
pass # the object was already removed
try:
s4connector.lo_s4.lo.delete_s(zone_dn)
except ldap.NO_SUCH_OBJECT:
pass # the object was already removed
return True
[docs]def s4_dns_node_base_create(s4connector, object, dnsRecords):
relativeDomainNames = object['attributes'].get('relativeDomainName')
old_dnsRecords = []
# Create dnsNode object
dnsNodeDn = object['dn']
try:
old_dnsRecords = s4connector.lo_s4.get(dnsNodeDn, attr=['dnsRecord'], required=True).get('dnsRecord')
except ldap.NO_SUCH_OBJECT:
__create_s4_dns_node(s4connector, dnsNodeDn, relativeDomainNames, dnsRecords)
else:
s4connector.lo_s4.modify(dnsNodeDn, [('dnsRecord', old_dnsRecords, dnsRecords)])
return dnsNodeDn
[docs]def s4_dns_node_base_delete(s4connector, object):
dnsNodeDn = object['dn']
try:
s4connector.lo_s4.lo.delete_s(dnsNodeDn)
except ldap.NO_SUCH_OBJECT:
pass # the object was already removed
return True
''' Create a host record in Samaba 4 '''
[docs]def s4_host_record_create(s4connector, object):
dnsRecords = []
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
aRecords = s4connector.configRegistry.get('connector/s4/mapping/dns/host_record/%s.%s/static/ipv4' % (relativeDomainName.lower(), zoneName.lower()))
aAAARecords = s4connector.configRegistry.get('connector/s4/mapping/dns/host_record/%s.%s/static/ipv6' % (relativeDomainName.lower(), zoneName.lower()))
if aRecords or aAAARecords:
# IPv4
if aRecords:
for a in aRecords.split(' '):
a_record = ARecord(a)
dnsRecords.append(ndr_pack(a_record))
# IPv6
if aAAARecords:
for a in aAAARecords.split(' '):
a_record = AAAARecord(a)
dnsRecords.append(ndr_pack(a_record))
else:
__pack_aRecord(object, dnsRecords)
__pack_mxRecord(object, dnsRecords)
__pack_txtRecord(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
return True
[docs]def ucs_host_record_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_create: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
aRecords = s4connector.configRegistry.get('connector/s4/mapping/dns/host_record/%s.%s/static/ipv4' % (relativeDomainName.lower(), zoneName.lower()))
if aRecords:
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_create: do not write host record back from S4 to UCS because location of A record has been overwritten by UCR')
return
aAAARecords = s4connector.configRegistry.get('connector/s4/mapping/dns/host_record/%s.%s/static/ipv6' % (relativeDomainName.lower(), zoneName.lower()))
if aAAARecords:
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_create: do not write host record back from S4 to UCS because location of AAAA record has been overwritten by UCR')
return
# unpack the host record
a = __unpack_aRecord(object)
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.host_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
if set(newRecord['a']) != set(a):
newRecord['a'] = a
newRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_create: do not modify host record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_create: zoneDN: %s' % zoneDN)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.host_record.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
newRecord['name'] = relativeDomainName
newRecord['a'] = a
newRecord.create()
[docs]def ucs_host_record_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.host_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_host_record_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def s4_ptr_record_create(s4connector, object):
dnsRecords = []
__pack_ptrRecord(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
return True
[docs]def ucs_ptr_record_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_ptr_record_create: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# unpack the host record
ptr = __unpack_ptrRecord(object)
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.ptr_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
if set(newRecord['ptr_record']) != set(ptr):
newRecord['ptr_record'] = ptr[0]
newRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_ptr_record_create: do not modify ptr record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.ptr_record.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
newRecord['address'] = relativeDomainName
newRecord['ptr_record'] = ptr[0]
newRecord.create()
[docs]def ucs_ptr_record_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_ptr_record_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.ptr_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_ptr_record_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def ucs_cname_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_cname_create: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# unpack the host record
c = __unpack_cName(object)
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.alias.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
if set(newRecord['cname']) != set(c):
newRecord['cname'] = c[0]
newRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_cname_create: do not modify cname record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.alias.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
newRecord['name'] = relativeDomainName
newRecord['cname'] = c[0]
newRecord.create()
[docs]def ucs_cname_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_cname_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.alias.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_cname_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def s4_cname_create(s4connector, object):
dnsRecords = []
__pack_cName(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
[docs]def ucs_srv_record_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# unpack the host record
srv = __unpack_sRVrecord(object)
# ucr set connector/s4/mapping/dns/srv_record/_ldap._tcp.test.local/location='100 0 389 foobar.test.local. 100 0 389 foobar2.test.local.'
ucr_locations = s4connector.configRegistry.get('connector/s4/mapping/dns/srv_record/%s.%s/location' % (relativeDomainName.lower(), zoneName.lower()))
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: ucr_locations for connector/s4/mapping/dns/srv_record/%s.%s/location: %s' % (relativeDomainName.lower(), zoneName.lower(), ucr_locations))
if ucr_locations and ucr_locations.lower() == 'ignore':
return
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.srv_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
if ucr_locations:
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: do not write SRV record back from S4 to UCS because location of SRV record have been overwritten by UCR')
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: location: %s' % newRecord['location'])
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: srv : %s' % srv)
srv.sort()
newRecord['location'].sort()
if srv != newRecord['location']:
newRecord['location'] = srv
newRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_create: do not modify srv record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.srv_record.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
# Make syntax UDM compatible
parts = univention.admin.handlers.dns.srv_record.unmapName([relativeDomainName.encode('UTF-8')])
if len(parts) == 3 and parts[2]:
msg = 'SRV create: service="%s" protocol="%s" extension="%s"' % (parts[0], parts[1], parts[2])
if len(parts) == 2:
msg = 'SRV create: service="%s" protocol="%s"' % (parts[0], parts[1])
else:
msg = 'SRV create: unexpected format, parts: %s' % (parts,)
ud.debug(ud.LDAP, ud.INFO, msg)
newRecord['name'] = parts
newRecord['location'] = srv
newRecord.create()
[docs]def ucs_srv_record_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.srv_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_srv_record_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def s4_srv_record_create(s4connector, object):
dnsRecords = []
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# ucr set connector/s4/mapping/dns/srv_record/_ldap._tcp.test.local/location='100 0 389 foobar.test.local.'
# ucr set connector/s4/mapping/dns/srv_record/_ldap._tcp.test.local/location='100 0 389 foobar.test.local. 100 0 389 foobar2.test.local.'
ucr_locations = s4connector.configRegistry.get('connector/s4/mapping/dns/srv_record/%s.%s/location' % (relativeDomainName.lower(), zoneName.lower()))
ud.debug(ud.LDAP, ud.INFO, 's4_srv_record_create: ucr_locations for connector/s4/mapping/dns/srv_record/%s.%s/location: %s' % (relativeDomainName.lower(), zoneName.lower(), ucr_locations))
if ucr_locations:
if ucr_locations.lower() == 'ignore':
return
# Convert ucr variable
priority = None
weight = None
port = None
target = None
for v in ucr_locations.split(' '):
# Check explicit for None, because the int values may be 0
if priority is None:
priority = int(v)
elif weight is None:
weight = int(v)
elif port is None:
port = int(v)
elif not target:
target = __remove_dot(v.encode('UTF-8')).decode('UTF-8')
if priority is not None and weight is not None and port is not None and target:
ud.debug(ud.LDAP, ud.INFO, 'priority=%d weight=%d port=%d target=%s' % (priority, weight, port, target))
s = SRVRecord(target, port, priority, weight)
dnsRecords.append(ndr_pack(s))
priority = None
weight = None
port = None
target = None
else:
__pack_sRVrecord(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
[docs]def ucs_txt_record_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_txt_record_create: object: %s' % object)
udm_property = 'txt'
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# unpack the record
c = __unpack_txtRecord(object)
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
foundRecord = univention.admin.handlers.dns.txt_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
foundRecord.open()
# use normalized TXT records for comparison
normalized_txtRecord_list = []
for txtRecord in foundRecord['txt']:
normalized_txtRecord = str(TXT.from_text(rdataclass.IN, rdatatype.TXT, Tokenizer(txtRecord)))
normalized_txtRecord_list.append(normalized_txtRecord)
if set(normalized_txtRecord_list) != set(c):
foundRecord[udm_property] = c
foundRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_txt_record_create: do not modify txt record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.txt_record.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
newRecord['name'] = relativeDomainName
newRecord[udm_property] = c
newRecord.create()
[docs]def ucs_txt_record_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_txt_record_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.txt_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_txt_record_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def s4_txt_record_create(s4connector, object):
dnsRecords = []
__pack_txtRecord(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
[docs]def ucs_ns_record_create(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_ns_record_create: object: %s' % object)
udm_property = 'nameserver'
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# unpack the record
c = __unpack_nsRecord(object)
# Does a host record for this zone already exist?
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
foundRecord = univention.admin.handlers.dns.ns_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
foundRecord.open()
if set(foundRecord[udm_property]) != set(c):
foundRecord[udm_property] = c
foundRecord.modify()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_ns_record_create: do not modify ns record')
else:
zoneDN = __get_zone_dn(s4connector, zoneName)
position = univention.admin.uldap.position(zoneDN)
newRecord = univention.admin.handlers.dns.ns_record.object(None, s4connector.lo, position, dn=None, update_zone=False)
newRecord.open()
newRecord['zone'] = relativeDomainName
newRecord[udm_property] = c
newRecord.create()
[docs]def ucs_ns_record_delete(s4connector, object):
ud.debug(ud.LDAP, ud.INFO, 'ucs_ns_record_delete: object: %s' % object)
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
newRecord = univention.admin.handlers.dns.ns_record.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
newRecord.open()
newRecord.delete()
else:
ud.debug(ud.LDAP, ud.INFO, 'ucs_ns_record_delete: Object was not found, filter was: %s' % ol_filter)
return True
[docs]def s4_ns_record_create(s4connector, object):
dnsRecords = []
__pack_nsRecord(object, dnsRecords)
s4_dns_node_base_create(s4connector, object, dnsRecords)
[docs]def ucs_zone_create(s4connector, object, dns_type):
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
# create the zone when the dc=@ object has been created
if relativeDomainName != '@':
ud.debug(ud.LDAP, ud.INFO, "ucs_zone_create: ignoring DC=%s object" % (relativeDomainName,))
return
ns = __unpack_nsRecord(object)
soa = __unpack_soaRecord(object)
a = __unpack_aRecord(object)
mx = __unpack_mxRecord(object)
if zoneName == s4connector.configRegistry.get('domainname') and s4connector.configRegistry.get('connector/s4/mapping/dns/position') != 'legacy' and object['modtype'] == 'modify':
# Determine max of serialNumber from _msdcs zone
msdcs_soa_obj = __get_s4_msdcs_soa(s4connector, zoneName)
if msdcs_soa_obj:
msdcs_soa = __unpack_soaRecord(msdcs_soa_obj)
soa['serial'] = str(max(int(soa['serial']), int(msdcs_soa['serial'])))
mname = soa['mname']
if mname and not mname.endswith("."):
mname = "%s." % mname
ns_lower = [x.lower() for x in ns]
mname_lower = mname.lower()
if mname_lower not in ns_lower:
ns.insert(0, mname)
ns_lower.insert(0, mname_lower)
# Does a zone already exist?
modify = False
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
if dns_type == 'forward_zone':
zone = univention.admin.handlers.dns.forward_zone.object(None, s4connector.lo, position=None, dn=searchResult[0][0])
elif dns_type == 'reverse_zone':
zone = univention.admin.handlers.dns.reverse_zone.object(None, s4connector.lo, position=None, dn=searchResult[0][0])
zone.open()
udm_zone_nameservers_lower = [x.lower() for x in zone['nameserver']]
if set(ns_lower) != set(udm_zone_nameservers_lower):
zone['nameserver'] = ns
modify = True
if soa['rname'].replace('.', '@', 1) != zone['contact'].rstrip('.'):
zone['contact'] = soa['rname'].replace('.', '@', 1)
modify = True
if int(soa['serial']) != int(zone['serial']):
zone['serial'] = soa['serial']
modify = True
for k in ['refresh', 'retry', 'expire', 'ttl']:
if int(soa[k]) != _unixTimeInverval2seconds(zone[k]):
zone[k] = unmapUNIX_TimeInterval(soa[k])
modify = True
if dns_type == 'forward_zone':
# The IP address of the DNS forward zone will be used to determine the
# sysvol share. On a selective replicated DC only a short list of DCs
# should be returned
aRecords = s4connector.configRegistry.get('connector/s4/mapping/dns/forward_zone/%s/static/ipv4' % zoneName.lower())
aAAARecords = s4connector.configRegistry.get('connector/s4/mapping/dns/forward_zone/%s/static/ipv6' % zoneName.lower())
if not aRecords and not aAAARecords:
if set(a) != set(zone['a']):
zone['a'] = a
modify = True
if mx:
def mapMX(m):
return '%s %s' % (m[0], m[1])
if set(map(mapMX, mx)) != set(map(mapMX, zone['mx'])):
zone['mx'] = mx
modify = True
if modify:
zone.modify()
else:
position = univention.admin.uldap.position(s4connector.property['dns'].ucs_default_dn)
if dns_type == 'forward_zone':
zone = univention.admin.handlers.dns.forward_zone.object(None, s4connector.lo, position, dn=None)
name_key = 'zone'
elif dns_type == 'reverse_zone':
zone = univention.admin.handlers.dns.reverse_zone.object(None, s4connector.lo, position, dn=None)
name_key = 'subnet'
zoneName = univention.admin.handlers.dns.reverse_zone.unmapSubnet(zoneName.encode('ASCII'))
zone.open()
zone[name_key] = zoneName
zone['nameserver'] = ns
zone['contact'] = soa['rname'].replace('.', '@', 1)
zone['serial'] = soa['serial']
zone['refresh'] = [soa['refresh']] # complex UDM syntax
zone['retry'] = [soa['retry']] # complex UDM syntax
zone['expire'] = [soa['expire']] # complex UDM syntax
zone['ttl'] = [soa['ttl']] # complex UDM syntax
if dns_type == 'forward_zone':
zone['a'] = a
zone['mx'] = mx
zone.create()
[docs]def ucs_zone_delete(s4connector, object, dns_type):
zoneName = object['attributes']['zoneName'][0].decode('UTF-8')
relativeDomainName = object['attributes']['relativeDomainName'][0].decode('UTF-8')
if relativeDomainName != '@':
ud.debug(ud.LDAP, ud.INFO, "ucs_zone_delete: ignoring DC=%s object" % (relativeDomainName,))
return
ol_filter = format_escaped('(&(relativeDomainName={0!e})(zoneName={1!e}))', relativeDomainName, zoneName)
searchResult = s4connector.lo.search(filter=ol_filter, unique=True)
if len(searchResult) > 0:
if dns_type == 'forward_zone':
zone = univention.admin.handlers.dns.forward_zone.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
elif dns_type == 'reverse_zone':
zone = univention.admin.handlers.dns.reverse_zone.object(None, s4connector.lo, position=None, dn=searchResult[0][0], update_zone=False)
zone.open()
zone.delete()
def _identify_dns_ucs_object(s4connector, object):
# At this point dn_mapping_function already has converted object['dn'] from ucs to con
# But since there is no attribute mapping defined for DNS, the object attributes still
# are the ones from UCS. Passing the Samba4 object['dn'] is irrelevant here:
if object.get('attributes'):
if univention.admin.handlers.dns.forward_zone.identify(object['dn'], object['attributes']):
return 'forward_zone'
if univention.admin.handlers.dns.reverse_zone.identify(object['dn'], object['attributes']):
return 'reverse_zone'
if univention.admin.handlers.dns.alias.identify(object['dn'], object['attributes']):
return 'alias'
if univention.admin.handlers.dns.host_record.identify(object['dn'], object['attributes']):
return 'host_record'
if univention.admin.handlers.dns.srv_record.identify(object['dn'], object['attributes']):
return 'srv_record'
if univention.admin.handlers.dns.ptr_record.identify(object['dn'], object['attributes']):
return 'ptr_record'
if univention.admin.handlers.dns.txt_record.identify(object['dn'], object['attributes']):
return 'txt_record'
if univention.admin.handlers.dns.ns_record.identify(object['dn'], object['attributes']):
return 'ns_record'
return None
def _identify_dns_con_object(s4connector, object):
# At this point dn_mapping_function already has converted object['dn'] from con to ucs
# But since there is no attribute mapping defined for DNS, the object attributes still
# are the ones from Samba.
if object.get('attributes'):
oc = object['attributes'].get('objectClass')
dc = object['attributes'].get('dc') or object['attributes'].get('DC')
if oc and b'dnsZone' in oc:
# forward or reverse zone
if dc and (dc[0].lower().endswith(b'.in-addr.arpa') or dc[0].lower().endswith(b'.ip6.arpa')):
return 'reverse_zone'
else:
return 'forward_zone'
if oc and b'dnsNode' in oc:
if dc and dc[0] == b'@':
zone_type = 'forward_zone'
exploded_dn = str2dn(object['dn'])
for multi_rdn in exploded_dn:
(attribute, value, _flags) = multi_rdn[0]
if attribute.lower() == 'zonename' and (value.lower().endswith('.in-addr.arpa') or value.lower().endswith('.ip6.arpa')):
zone_type = 'reverse_zone'
break
return zone_type
else:
dnsRecords = object['attributes'].get('dnsRecord')
if not dnsRecords:
return None
dns_types = set()
for dnsRecord in dnsRecords:
dnsRecord_DnssrvRpcRecord = ndr_unpack(dnsp.DnssrvRpcRecord, dnsRecord)
dns_types.add(dnsRecord_DnssrvRpcRecord.wType)
if dnsp.DNS_TYPE_PTR in dns_types:
return 'ptr_record'
elif dnsp.DNS_TYPE_CNAME in dns_types:
return 'alias'
elif dnsp.DNS_TYPE_SRV in dns_types:
return 'srv_record'
elif set((dnsp.DNS_TYPE_A, dnsp.DNS_TYPE_AAAA)) & dns_types:
return 'host_record'
elif dnsp.DNS_TYPE_TXT in dns_types:
return 'txt_record'
elif dnsp.DNS_TYPE_NS in dns_types:
return 'ns_record'
return None
[docs]def ucs2con(s4connector, key, object):
# At this point dn_mapping_function already has converted object['dn'] from ucs to con
# But since there is no attribute mapping defined for DNS, the object attributes still
# are the ones from UCS.
dns_type = _identify_dns_ucs_object(s4connector, object)
if not dns_type:
# unknown object -> ignore
ud.debug(ud.LDAP, ud.INFO, 'dns ucs2con: Ignore unknown dns object: %s' % object['dn'])
return True
ud.debug(ud.LDAP, ud.INFO, 'dns ucs2con: Object (%s) is of type %s' % (object['dn'], dns_type))
# We can only get the mapped zone_name from the DN here (see comment above):
# (In the case of _msdcs the zoneName would be wrong here otherwise)
(zoneName, relativeDomainName) = __split_s4_dnsNode_dn(object['dn'])
object['attributes']['zoneName'] = [zoneName.encode('UTF-8')]
object['attributes']['relativeDomainName'] = [relativeDomainName.encode('UTF-8')]
if dns_type == 'forward_zone' or dns_type == 'reverse_zone':
if object['modtype'] in ['add', 'modify']:
s4_zone_create_wrapper(s4connector, object)
elif object['modtype'] in ['delete']:
s4_zone_delete(s4connector, object)
# ignore move
elif dns_type == 'host_record':
if object['modtype'] in ['add', 'modify']:
s4_host_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
elif dns_type == 'alias':
if object['modtype'] in ['add', 'modify']:
s4_cname_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
elif dns_type == 'srv_record':
if object['modtype'] in ['add', 'modify']:
s4_srv_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
elif dns_type == 'ptr_record':
if object['modtype'] in ['add', 'modify']:
s4_ptr_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
elif dns_type == 'txt_record':
if object['modtype'] in ['add', 'modify']:
s4_txt_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
elif dns_type == 'ns_record':
if object['modtype'] in ['add', 'modify']:
s4_ns_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
s4_dns_node_base_delete(s4connector, object)
# ignore move
return True
[docs]def con2ucs(s4connector, key, object):
ud.debug(ud.LDAP, ud.INFO, 'dns con2ucs: Object (%s): %s' % (object['dn'], object))
# At this point dn_mapping_function already has converted object['dn'] from con to ucs
# But since there is no attribute mapping defined for DNS, the object attributes still
# are the ones from Samba.
dns_type = _identify_dns_con_object(s4connector, object)
if not dns_type:
# unknown object -> ignore
ud.debug(ud.LDAP, ud.INFO, 'dns con2ucs: Ignore unknown dns object: %s' % object['dn'])
return True
ud.debug(ud.LDAP, ud.INFO, 'dns con2ucs: Object (%s) is of type %s' % (object['dn'], dns_type))
# We can only get the mapped zone_name from the DN here (see comment above):
(zoneName, relativeDomainName) = __split_ol_dNSZone_dn(object['dn'], object['attributes']['objectClass'])
# Inject the zoneName and relativeDomainName to simplify things below
object['attributes']['zoneName'] = [zoneName.encode('UTF-8')]
object['attributes']['relativeDomainName'] = [relativeDomainName.encode('UTF-8')]
if dns_type == 'host_record':
if object['modtype'] in ['add', 'modify']:
ucs_host_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_host_record_delete(s4connector, object)
# ignore move
elif dns_type == 'ptr_record':
if object['modtype'] in ['add', 'modify']:
ucs_ptr_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_ptr_record_delete(s4connector, object)
# ignore move
elif dns_type == 'alias':
if object['modtype'] in ['add', 'modify']:
ucs_cname_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_cname_delete(s4connector, object)
# ignore move
elif dns_type == 'srv_record':
if object['modtype'] in ['add', 'modify']:
ucs_srv_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_srv_record_delete(s4connector, object)
# ignore move
elif dns_type == 'txt_record':
if object['modtype'] in ['add', 'modify']:
ucs_txt_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_txt_record_delete(s4connector, object)
# ignore move
elif dns_type == 'ns_record':
if object['modtype'] in ['add', 'modify']:
ucs_ns_record_create(s4connector, object)
elif object['modtype'] in ['delete']:
ucs_ns_record_delete(s4connector, object)
# ignore move
if dns_type in ['forward_zone', 'reverse_zone']:
if object['modtype'] in ['add', 'modify']:
ucs_zone_create(s4connector, object, dns_type)
elif object['modtype'] in ['delete']:
ucs_zone_delete(s4connector, object, dns_type)
# ignore move
return True