#!/usr/bin/python2.7
#
# Univention Admin Modules
"""synchronise attributes uniqueMember to memberUID of group objects.

Update the UIDs in memberUid of all groups to match the uid of the objects
referenced by uniqueMember."""
#
# Copyright 2007-2014 Univention GmbH
#
# http://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
# <http://www.gnu.org/licenses/>.

import ldap
import sys
from optparse import OptionParser

import univention.config_registry

import univention.debug as ud

class ConsistencyError(Exception):
	"""Inconsistence detected."""
	pass

def main():
	"""synchronise attributes uniqueMember to memberUID of group objects."""
	configRegistry = univention.config_registry.ConfigRegistry()
	configRegistry.load()

	parser = OptionParser(usage="usage: %prog [options]")
	parser.add_option('-t', '--test', action='store_true',
			dest='test', default=False, help='just test the modification')
	parser.add_option('-d', action='store', default=2, type='int',
			dest='debug', help='set debug level')
	parser.add_option('-c', '--continue', action='store_true',
			dest='cont', default=False, help='continue on error')

	(options, _arguments) = parser.parse_args()

	ud.init('/var/log/univention/sync-memberuid.log', ud.FLUSH, ud.NO_FUNCTION)
	ud.set_level( ud.ADMIN, options.debug)

	base_dn = configRegistry['ldap/base']

	lo = ldap.open('localhost', 7389)
	bindpw = open('/etc/ldap.secret' ).read()
	if bindpw[-1] == '\n':
		bindpw = bindpw[:-1]
	lo.simple_bind_s("cn=admin," + base_dn, bindpw)

	try:
		process_groups(lo, base_dn, options.test, options.cont)
	except ConsistencyError:
		sys.exit(1)

def process_groups(lo, base_dn, test=False, cont=False):
	groups = lo.search_s(base_dn, ldap.SCOPE_SUBTREE,
			'(&(objectClass=posixGroup)(objectClass=univentionGroup))',
			('uniqueMember', 'memberUid'))

	if test:
		print 'Test Mode: The following groups have to be modified:'
	for grp_dn, grp_attrs in groups:
		old = set(grp_attrs.get('memberUid', ()))

		ud.debug(ud.ADMIN, ud.PROCESS, 'Group: %s' % grp_dn)
		new = set()
		member_dns = grp_attrs.get('uniqueMember', ())
		for uniqueMember in member_dns:
			try:
				result = lo.search_s(uniqueMember, ldap.SCOPE_BASE,
						'(objectclass=*)')
			except ldap.NO_SUCH_OBJECT, ex:
				ud.debug(ud.ADMIN, ud.WARN,
						'searching %s failed: %s' % (uniqueMember, ex))
				print >> sys.stderr, 'WARNING: DN %s not found' % uniqueMember
				continue
			if not result:
				ud.debug(ud.ADMIN, ud.WARN,
						'empty result for uniqueMember %s' % uniqueMember)
				print >> sys.stderr, \
						'WARNING: empty result for uniqueMember %s' % \
						uniqueMember
				continue
			_, uniqueMemberAttrs = result[0]
			uniqueMemberUid = uniqueMemberAttrs.get('uid')
			if uniqueMemberUid:
				new.add(uniqueMemberUid[0])

		if old != new:
			ud.debug(ud.ADMIN, ud.INFO, '  members: %s' % member_dns)
			ud.debug(ud.ADMIN, ud.INFO, '  old memberUid: %s' % old)
			ud.debug(ud.ADMIN, ud.INFO, '  new memberUid: %s' % new)
			if test:
				print 'Group:', grp_dn
				continue
			add = list(new - old)
			if add:
				try:
					lo.modify_s(grp_dn, [(ldap.MOD_ADD, 'memberUid', add)])
				except ldap.LDAPError, ex:
					ud.debug(ud.ADMIN, ud.ERROR,
							'adding memberUid entries failed: %s' % ex)
					if not cont:
						raise ConsistencyError()
			remove = list(old - new)
			if remove:
				try:
					lo.modify_s(grp_dn,
							[(ldap.MOD_DELETE, 'memberUid', remove)])
				except ldap.LDAPError, ex:
					ud.debug(ud.ADMIN, ud.ERROR,
							'removing memberUid entries failed: %s' % ex)
					if not cont:
						raise ConsistencyError()

if __name__ == '__main__':
	main()
