diff --git a/management/univention-management-console-module-ipchange/umc/python/ipchange/__init__.py b/management/univention-management-console-module-ipchange/umc/python/ipchange/__init__.py index d40b906fef..280cdf8ab9 100644 --- a/management/univention-management-console-module-ipchange/umc/python/ipchange/__init__.py +++ b/management/univention-management-console-module-ipchange/umc/python/ipchange/__init__.py @@ -59,79 +59,114 @@ class Instance(Base): '''Return a dict with all necessary values for ipchange read from the current status of the system.''' - # ignore link local addresses (no DHCP address received) - network = ipaddress.IPv4Network(f'{ip}/{netmask}', False) - if network.is_link_local: - MODULE.error('Ignore link local address change.') + # ignore link local or loopback addresses (no DHCP address received) + network = ipaddress.ip_network(f'{ip}/{netmask}', False) + if network.is_link_local or network.is_loopback: + MODULE.error('Ignore link local or loopback address change.') return + # set attribute aRecord(.|0) or aAAARecord(:|0000) for IP version 4 or 6 + if network.version == 4: + attribute = 'aRecord' + sattribute = '.' + tattribute = '0' + if network.version == 6: + attribute = 'aAAARecord' + sattribute = ':' + tattribute = '0000' + lo, position = univention.admin.uldap.getAdminConnection() hmodule = univention.admin.modules.get('dns/host_record') + rmodule = univention.admin.modules.get('dns/reverse_zone') + fmodule = univention.admin.modules.get('dns/forward_zone') cmodule = univention.admin.modules.get(f'computers/{role}') + server = cmodule.object(None, lo, position, self.user_dn) + server.open() + + # get the current server status filtert by IP version and init fqdns to change the host records too + saddresses = [entry for entry in server.get('ip') if ipaddress.ip_address(entry).version == network.version] + fqdns = [server.get('fqdn')] + + if ip: + naddress = ipaddress.ip_address(ip).exploded + if oldip: + oaddress = ipaddress.ip_address(oldip).exploded + if oldip and oaddress != naddress: + for address in [oaddress, oldip]: + if address not in saddresses: + saddresses.append(address) + + # check if already used by host record ( raise by first match ) + res = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', filter=filter_format(f'(|({attribute}=%s)({attribute}=%s))', (naddress,ip))) + for name in [entry['name'] for entry in res if 'name' in entry]: + raise BadRequest(f'The IP address is already in use by host record for : {name}') + + # change host records first ( we need to prepare this for a new pTRRecord ) + ucr.load() + + # FIXME: This should be done for UCS-in-AD domains as well! + # FIXED? Works, but it is't stable! + # ( Sometimes the S4 connector resync old adresses. Do we have to wait for a replication? ) + if 'samba' in ucr.get('dns/backend'): + for host in ['gc._msdcs','DomainDnsZones','ForestDnsZones']: + fqdns.append(f'{host}.%s' % ucr.get('domainname')) + + if ucr.is_true('ucs/server/sso/autoregistraton', True): + fqdns.append(ucr.get('ucs/server/sso/fqdn')) - # check if already used - res = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', filter=filter_format('aRecord=%s', (ip,))) - used_by = [entry['name'] for entry in res if 'name' in entry] - if used_by: - raise BadRequest(f'The IP address is already in use by host record(s) for: {", ".join(used_by)}') + for fqdn in fqdns: + forwardobjects = univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=None) + for forwardobject in forwardobjects: + zone = forwardobject.get('zone') + if not fqdn.endswith(zone): + continue + name = fqdn[:-(len(zone) + 1)] + for address in saddresses: + records = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', superordinate=forwardobject, filter=filter_format(f'(&(relativeDomainName=%s)({attribute}=%s))', (name, address))) + for record in records: + record.open() + record['a'].remove(address) + record['a'].append(naddress) + record.modify() - # do we have a forward zone for this IP address? - if oldip and oldip != ip: - fmodule = univention.admin.modules.get('dns/forward_zone') - for forwardobject in univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=filter_format('(aRecord=%s)', (oldip,))): + # do we have a forward zone for this IP addresses? + for address in saddresses: + for forwardobject in univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=filter_format(f'({attribute}=%s)', (address,))): forwardobject.open() - forwardobject['a'].remove(oldip) - forwardobject['a'].append(ip) + forwardobject['a'].remove(address) + forwardobject['a'].append(naddress) forwardobject.modify() - # remove old DNS reverse entries with old IP - server = cmodule.object(None, lo, position, self.user_dn) - server.open() - current_ips = server['ip'] - for entry in server['dnsEntryZoneReverse']: - if entry[1] in current_ips: - server['dnsEntryZoneReverse'].remove(entry) - - # change IP - server['ip'] = ip - MODULE.info(f'Change IP to {ip}') - server.modify() - # do we have a new reverse zone for this IP address? - rmodule = univention.admin.modules.get('dns/reverse_zone') - parts = network.network_address.exploded.split('.') - while parts[-1] == '0': + parts = network.network_address.exploded.split(sattribute) + while parts[-1] == tattribute: parts.pop() while parts: - subnet = '.'.join(parts) + subnet = sattribute.join(parts) parts.pop() - filter = filter_format('(subnet=%s)', (subnet,)) - reverseobject = univention.admin.modules.lookup(rmodule, None, lo, scope='sub', superordinate=None, filter=filter) + reverseobject = univention.admin.modules.lookup(rmodule, None, lo, scope='sub', superordinate=None, filter=filter_format('(subnet=%s)', (subnet,))) if reverseobject: - server = cmodule.object(None, lo, position, self.user_dn) server.open() - server['dnsEntryZoneReverse'].append([reverseobject[0].dn, ip]) + server['dnsEntryZoneReverse'].append([reverseobject[0].dn, naddress]) server.modify() break - # Change ucs-sso entry - # FIXME: this should be done for UCS-in-AD domains as well! - ucr.load() - sso_fqdn = ucr.get('ucs/server/sso/fqdn') - if ucr.is_true('ucs/server/sso/autoregistraton', True): - fmodule = univention.admin.modules.get('dns/forward_zone') - forwardobjects = univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=None) - for forwardobject in forwardobjects: - zone = forwardobject.get('zone') - if not sso_fqdn.endswith(zone): - continue - sso_name = sso_fqdn[:-(len(zone) + 1)] - for current_ip in current_ips: - records = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', superordinate=forwardobject, filter=filter_format('(&(relativeDomainName=%s)(aRecord=%s))', (sso_name, current_ip))) - for record in records: - record.open() - if oldip in record['a']: - record['a'].remove(oldip) - record['a'].append(ip) - record.modify() + # remove old server entries with old server addresses + server.open() + for zone in ['dnsEntryZoneReverse', 'dnsEntryZoneForward']: + for entry in server[zone]: + if entry[1] in saddresses: + server[zone].remove(entry) + + # change/append server address + server['ip'].append(naddress) + MODULE.info(f'Change IP address {ip} for {fqdns[0]}') + server.modify() + + # cleanup any old ip addresses + server.open() + for entry in server['ip']: + if entry in saddresses: + server['ip'].remove(entry) + server.modify()