#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# Univention Salt Kerberos Keys
#  Tool that adds a Kerberos salt to all keys found in the LDAP Backend
#
# Copyright 2010-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 univention.config_registry
import ldap
import heimdal
import sys

def salt_krb5Keys(principal, keys):
	context = heimdal.context()
	new_keys = []
	for k in keys:
		(keyblock, salt, kvno) = heimdal.asn1_decode_key(k)
		if salt.saltvalue():
			return []
		krb5_principal = heimdal.principal(context, principal)
		krb5_salt = heimdal.salt(context, krb5_principal)
		new_keys.append(heimdal.asn1_encode_key(keyblock, krb5_salt, kvno))
	return new_keys

class ldapconnection:
	def __init__(self, location, port, binddn , bindpw, protocol = 'ldaps'):
		if protocol.lower() == 'ldapi':
			import urllib
			location = urllib.quote(location)
		uri = '%s://%s:%s' % ( protocol, location, port )

		try:
			self.lo = ldap.initialize(uri)
			self.lo.simple_bind_s(binddn, bindpw)
		except ldap.LDAPError, error_message:
			print error_message
			raise

	def ldapsearch_async( self, base, scope = ldap.SCOPE_SUBTREE, ldapfilter = '(objectClass=*)', attrlist = None):
		timeout = 0
		try:
			result_id = self.lo.search(base, scope, ldapfilter, attrlist)
			while 1:
				result_type, result_data = self.lo.result(result_id, timeout)
				if (result_data == []):
					break
				else:
					if result_type == ldap.RES_SEARCH_ENTRY:
						yield result_data[0]
		except ldap.LDAPError, error_message:
			print error_message
			raise

	def ldapmodify_object( self, dn, object_dict ):
		try:
			modlist = []
			for attr, value in object_dict.items():
				modlist.append((ldap.MOD_REPLACE, attr, value))
			res = self.lo.modify_s(dn, modlist)
		except ldap.LDAPError, error_message:
			print error_message
			raise

def main():
	from optparse import OptionParser
	parser = OptionParser(usage="%prog [-h|--help] [--binddn <binddn>] [--bindpwd <bindpwd>]")
	parser.add_option("--binddn", action="store", dest="binddn",
		help="binddn")
	parser.add_option("--bindpwd", action="store", dest="bindpwd",
		help="bindpwd")
	(options, args) = parser.parse_args()

	ucr = univention.config_registry.ConfigRegistry()
	ucr.load()
	ldap_base = ucr['ldap/base']

	if not options.binddn:
		if not ucr['server/role'] in ('domaincontroller_master', 'domaincontroller_backup'):
			print "salt_krb5Keys: Without explicit credentials this only works on a Master or Backup server."
			return 0
		else:
			options.binddn = "cn=admin,%s" % ldap_base
			options.bindpwd = open('/etc/ldap.secret').read().strip()

	ldapfilter = "(objectClass=krb5Principal)"
	attrlist = ['krb5PrincipalName', 'krb5Key']

	ldaps_master_port = ucr.get('ldap/master/port', "7636")
	if ldaps_master_port == "7389":
		ldaps_master_port = "7636"

	lc = ldapconnection(ucr['ldap/master'], ldaps_master_port, options.binddn, options.bindpwd, 'ldaps')
	for dn, object_dict in lc.ldapsearch_async(ldap_base, ldapfilter = ldapfilter, attrlist = attrlist):
		if 'krb5Key' in object_dict:
			mod_krb5Keys = salt_krb5Keys(object_dict['krb5PrincipalName'][0], object_dict['krb5Key'])
			if mod_krb5Keys:
				lc.ldapmodify_object(dn, { 'krb5Key' : mod_krb5Keys } )

	return 0

if __name__=='__main__':
    sys.exit(main())

