#!/usr/bin/python2.7
#
'''Univention Admin Modules
migration tool for univentionObjectType'''
#
# Copyright 2005-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 optparse
import sys
import univention.uldap

def buildModuleIdentifyMapping():
	import univention.admin.modules
	univention.admin.modules.update()
	return dict([(name, module.identify, ) for (name, module, ) in univention.admin.modules.modules.items() if 'identify' in dir(module)])

def parseOptions():
	parser = optparse.OptionParser(usage='Usage: %prog (-n|-a) [options]\nSet the attribute univentionObjectType for each directory object')
	parser.add_option('-n', '--no-action',
	                  dest='action', action='store_false',
	                  help='do not modify the directory, show what would have been done')
	parser.add_option('-a', '--action',
	                  dest='action', action='store_true',
	                  help='do modify the directory')
	parser.add_option('--verbose',
	                  dest='verbose', action='store_true', default=False,
	                  help='do not hide warnings for unmatched component objects')
	parser.add_option('-v', '--verify',
	                  dest='verify', action='store_true', default=False,
	                  help='check objects with already set univentionObjectType')
	parser.add_option('-b', '--base',
	                  dest='base', default='',
	                  help='only modify objects at or below SEARCHBASE', metavar='SEARCHBASE')
	(options, args, ) = parser.parse_args()
	if options.action is None:
		print 'Neither --no-action nor --action given!'
		parser.print_help()
		sys.exit(3)
	if args:
		print >>sys.stderr, 'Unknown arguments %r!' % (args, )
		parser.print_help()
		sys.exit(3)
	return options

def warningSupressed(dn, attributes):
	# cn=admin,$ldap_base cn=backup,$ldap_base
	if set(attributes.keys()) == set(('objectClass', 'userPassword', 'cn', 'sn', )):
		if set(attributes['objectClass']) == set(('top', 'person', )):
			if attributes['sn'] == attributes['cn'] and len(attributes['cn']) == 1 and attributes['cn'][0] in ('admin', 'backup', ):
				return True
	# Kerberos principal objects
	if set(attributes['objectClass']) == set(('top', 'account', 'krb5Principal', 'krb5KDCEntry', )):
		return True
	if set(attributes['objectClass']) == set(('top', 'person' , 'krb5Principal', 'krb5KDCEntry', )):
		return True
	# samba idmap objects
	if set(attributes['objectClass']) == set(('sambaIdmapEntry', 'sambaSidEntry', )):
		return True
	# old (2.4) Kolab objects
	if 'kolabSharedFolder' in set(attributes['objectClass']):
		return True
	if 'univentionKolabGroup' in set(attributes['objectClass']):
		return True
	# old (2.4) UMC ACLs
	if 'univentionConsoleACL' in set(attributes['objectClass']):
		return True
	if 'univentionConsoleOperations' in set(attributes['objectClass']):
		return True
	if 'univentionPolicyConsoleAccess' in set(attributes['objectClass']):
		return True
	# old (2.4) UDM visibility settings
	if 'univentionPolicyAdminSettings' in set(attributes['objectClass']):
		return True
	if 'univentionAdminUserSettings' in set(attributes['objectClass']):
		return True
	return warningHidden(dn, attributes)

def warningHidden(dn, attributes):
	if warningHidden.verbose:
		return False
	if 'univentionCitrixUserSessionsClass' in set(attributes['objectClass']):
		return True
	if 'univentionPolicyThinClientUser' in set(attributes['objectClass']):
		return True
	if 'univentionThinClientSession' in set(attributes['objectClass']):
		return True
	if 'univentionThinClientAutostart' in set(attributes['objectClass']):
		return True
	return False
warningHidden.verbose = False

def main(options):
	errorsOccurred = False
	if options.verify:
		searchFilter='(objectClass=*)'
	else:
		searchFilter='(!(objectClass=univentionObject))'
	moduleIdentify = buildModuleIdentifyMapping()
	uldap = univention.uldap.getAdminConnection()
	if options.action is not True:
		uldap.modify = lambda dn, changes: sys.stdout.write('Would modify %r\n' % (dn, ))
	warningHidden.verbose = options.verbose
	for (dn, attributes, ) in uldap.search(filter=searchFilter, base=options.base):
		matches = [module for (module, identify, ) in moduleIdentify.items() if identify(dn, attributes)]
		if 'container/dc' in matches and ('container/ou' in matches or 'container/cn' in matches):
			# container/dc has priority (ldapbase ou=/cn= has multiple matches)
			matches = ['container/dc']
		if len(matches) == 1:
			if 'univentionObject' not in attributes['objectClass']:
				try:
					changes = [
						('objectClass', attributes['objectClass'], attributes ['objectClass'] + ['univentionObject'], ),
						('univentionObjectType', [], [matches[0], ], ),
						]
					uldap.modify(dn, changes)
				except univention.uldap.ldap.INVALID_SYNTAX as e:
					# this error should not happen, in case it does, it is an indicator that
					# LDAP schema extensions are missing (Bug #26304)
					print >>sys.stderr, 'ERROR: Could not set univentionObjectType! (%s)\nIt seems that the corresponding LDAP schema extensions are not installed correctly.' % e
					return False
			elif attributes['univentionObjectType'][0] != matches[0]:
				errorsOccurred = True
				print >>sys.stderr, 'Mismatch for %r: univentionObjectType is %r but should be %r!' % (dn, attributes['univentionObjectType'], matches, )
		elif len(matches) > 1:
			raise ValueError('Multiple matches for %r: %r!' % (dn, matches, ))
		else:
			if not warningSupressed(dn, attributes):
				print >>sys.stderr, 'Warning: No match for %r' % (dn, )
	return not errorsOccurred

if __name__ == "__main__":
	options = parseOptions()
	if not main(options):
		sys.exit(1)
