# -*- coding: utf-8 -*-
#
# 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/>.
"""
|UDM| allocators to allocate and lock resources for |LDAP| object creation.
"""
import ldap
from ldap.filter import filter_format
import univention.debug as ud
import univention.admin.locking
import univention.admin.uexceptions
from univention.admin import localization
from univention.admin import configRegistry
translation = localization.translation('univention/admin')
_ = translation.translate
_type2attr = {
'uidNumber': 'uidNumber',
'gidNumber': 'gidNumber',
'uid': 'uid',
'gid': 'gid',
'sid': 'sambaSID',
'domainSid': 'sambaSID',
'mailPrimaryAddress': 'mailPrimaryAddress',
'aRecord': 'aRecord',
'mac': 'macAddress',
'groupName': 'cn',
'cn-uid-position': 'cn', # ['cn', 'uid', 'ou']
}
_type2scope = {
'uidNumber': 'base',
'gidNumber': 'base',
'uid': 'domain',
'gid': 'domain',
'sid': 'base',
'domainSid': 'base',
'mailPrimaryAddress': 'domain',
'aRecord': 'domain',
'mac': 'domain',
'groupName': 'domain',
'cn-uid-position': 'one',
}
[docs]def requestUserSid(lo, position, uid_s):
uid = int(uid_s)
algorithmical_rid_base = 1000
rid = str(uid * 2 + algorithmical_rid_base)
searchResult = lo.search(filter='objectClass=sambaDomain', attr=['sambaSID'])
domainsid = searchResult[0][1]['sambaSID'][0].decode('ASCII')
sid = domainsid + '-' + rid
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: request user sid. SID = %s-%s' % (domainsid, rid))
return request(lo, position, 'sid', sid)
[docs]def requestGroupSid(lo, position, gid_s, generateDomainLocalSid=False):
gid = int(gid_s)
algorithmical_rid_base = 1000
rid = str(gid * 2 + algorithmical_rid_base + 1)
if generateDomainLocalSid:
sid = 'S-1-5-32-' + rid
else:
searchResult = lo.search(filter='objectClass=sambaDomain', attr=['sambaSID'])
domainsid = searchResult[0][1]['sambaSID'][0].decode('ASCII')
sid = domainsid + '-' + rid
return request(lo, position, 'sid', sid)
[docs]def acquireRange(lo, position, atype, attr, ranges, scope='base'):
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Start allocation for type = %r' % atype)
startID = lo.getAttr('cn=%s,cn=temporary,cn=univention,%s' % (ldap.dn.escape_dn_chars(atype), position.getBase()), 'univentionLastUsedValue')
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Start ID = %r' % startID)
if not startID:
startID = ranges[0]['first']
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Set Start ID to first %r' % startID)
else:
startID = int(startID[0])
for _range in ranges:
if startID < _range['first']:
startID = _range['first']
last = _range['last'] + 1
other = None
while startID < last:
startID += 1
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Set Start ID %r' % startID)
try:
if other:
# exception occurred while locking other, so atype was successfully locked and must be released
univention.admin.locking.unlock(lo, position, atype, str(startID - 1).encode('utf-8'), scope=scope)
other = None
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Lock ID %r for %r' % (startID, atype))
univention.admin.locking.lock(lo, position, atype, str(startID).encode('utf-8'), scope=scope)
if atype in ('uidNumber', 'gidNumber'):
# reserve the same ID for both
other = 'uidNumber' if atype == 'gidNumber' else 'gidNumber'
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Lock ID %r for %r' % (startID, other))
univention.admin.locking.lock(lo, position, other, str(startID).encode('utf-8'), scope=scope)
except univention.admin.uexceptions.noLock:
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Cannot Lock ID %r' % startID)
continue
except univention.admin.uexceptions.objectExists:
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Cannot Lock existing ID %r' % startID)
continue
if atype in ('uidNumber', 'gidNumber'):
_filter = filter_format('(|(uidNumber=%s)(gidNumber=%s))', (str(startID), str(startID)))
else:
_filter = '(%s=%d)' % (attr, startID)
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: searchfor %r' % _filter)
if lo.searchDn(base=position.getBase(), filter=_filter):
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Already used ID %r' % startID)
univention.admin.locking.unlock(lo, position, atype, str(startID).encode('utf-8'), scope=scope)
if other:
univention.admin.locking.unlock(lo, position, other, str(startID).encode('utf-8'), scope=scope)
other = None
continue
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE: Return ID %r' % startID)
if other:
univention.admin.locking.unlock(lo, position, other, str(startID).encode('utf-8'), scope=scope)
return str(startID)
raise univention.admin.uexceptions.noLock(_('The attribute %r could not get locked.') % (atype,))
[docs]def acquireUnique(lo, position, type, value, attr, scope='base'):
ud.debug(ud.ADMIN, ud.INFO, 'LOCK acquireUnique scope = %s' % scope)
if scope == 'domain':
searchBase = position.getDomain()
else:
searchBase = position.getBase()
if type == "aRecord": # uniqueness is only relevant among hosts (one or more dns entries having the same aRecord as a host are allowed)
univention.admin.locking.lock(lo, position, type, value.encode('utf-8'), scope=scope)
if not lo.searchDn(base=searchBase, filter=filter_format('(&(objectClass=univentionHost)(%s=%s))', (attr, value))):
return value
elif type in ['groupName', 'uid'] and configRegistry.is_true('directory/manager/user_group/uniqueness', True):
univention.admin.locking.lock(lo, position, type, value.encode('utf-8'), scope=scope)
if not lo.searchDn(base=searchBase, filter=filter_format('(|(&(cn=%s)(|(objectClass=univentionGroup)(objectClass=sambaGroupMapping)(objectClass=posixGroup)))(uid=%s))', (value, value))):
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE return %s' % value)
return value
elif type == "groupName": # search filter is more complex then in general case
univention.admin.locking.lock(lo, position, type, value.encode('utf-8'), scope=scope)
if not lo.searchDn(base=searchBase, filter=filter_format('(&(%s=%s)(|(objectClass=univentionGroup)(objectClass=sambaGroupMapping)(objectClass=posixGroup)))', (attr, value))):
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE return %s' % value)
return value
elif type == 'cn-uid-position':
base = lo.parentDn(value)
attr, value, __ = ldap.dn.str2dn(value)[0][0]
try:
attrs = {'cn': ['uid'], 'uid': ['cn', 'ou'], 'ou': ['uid']}[attr]
except KeyError:
return value
if all(ldap.dn.str2dn(x)[0][0][0] not in attrs for x in lo.searchDn(base=base, filter='(|%s)' % ''.join(filter_format('(%s=%s)', (attr, value)) for attr in attrs), scope=scope)):
return value
raise univention.admin.uexceptions.alreadyUsedInSubtree('name=%r position=%r' % (value, base))
else:
ud.debug(ud.ADMIN, ud.INFO, 'LOCK univention.admin.locking.lock scope = %s' % scope)
univention.admin.locking.lock(lo, position, type, value.encode('utf-8'), scope=scope)
if not lo.searchDn(base=searchBase, filter=filter_format('%s=%s', (attr, value))):
ud.debug(ud.ADMIN, ud.INFO, 'ALLOCATE return %s' % value)
return value
raise univention.admin.uexceptions.noLock(_('The attribute %r could not get locked.') % (type,))
[docs]def request(lo, position, type, value=None):
if type in ('uidNumber', 'gidNumber'):
return acquireRange(lo, position, type, _type2attr[type], [{'first': 1000, 'last': 55000}, {'first': 65536, 'last': 1000000}], scope=_type2scope[type])
return acquireUnique(lo, position, type, value, _type2attr[type], scope=_type2scope[type])
[docs]def confirm(lo, position, type, value, updateLastUsedValue=True):
if type in ('uidNumber', 'gidNumber') and updateLastUsedValue:
lo.modify('cn=%s,cn=temporary,cn=univention,%s' % (ldap.dn.escape_dn_chars(type), position.getBase()), [('univentionLastUsedValue', b'1', value.encode('utf-8'))])
elif type == 'cn-uid-position':
return
univention.admin.locking.unlock(lo, position, type, value.encode('utf-8'), _type2scope[type])
[docs]def release(lo, position, type, value):
univention.admin.locking.unlock(lo, position, type, value.encode('utf-8'), _type2scope[type])