#!/usr/bin/python2.4 # -*- coding: utf-8 -*- """Remove objectClass from object.""" # Copyright 2012 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 # . import sys import ldap import ldap.schema from ldap.filter import filter_format from optparse import OptionParser from univention.config_registry import ConfigRegistry def parse(): """Parse command line.""" ucr = ConfigRegistry() ucr.load() usage = '\n'.join(_[4:] for _ in """ %prog [options] [-c objectClass] dn... Remove "objectClass" and all its attributes from LDAP object "dn". """.splitlines()[1:-1]) parser = OptionParser(usage=usage) parser.add_option('-y', dest='passwdfile', help='Use complete contents of passwdfile as the password for ' + \ 'simple authentication. [%default]', default='/etc/ldap.secret') parser.add_option('-D', dest='binddn', help='Use the Distinguished Name binddn to bind to the LDAP ' + \ 'directory. [%default]', default='cn=Admin,%(ldap/base)s' % ucr) parser.add_option('-w', dest='passwd', help='Use passwd as the password for simple authentication.') parser.add_option('-c', dest='objectclass', help='Use the objectclass as the class to remove. [%default]', default='sambaSamAccount') (options, args) = parser.parse_args() return options, args def open_ldap(options): """Open ldap connection.""" if options.passwd: password = options.passwd else: try: pwfile = open(options.passwdfile, 'r') except IOError, ex: print >> sys.stderr, 'Could not read passowrd: %s' % (ex,) sys.exit(1) try: password = pwfile.read().rstrip('\n') finally: pwfile.close() con_str = 'ldap://' try: ldap_con = ldap.initialize(con_str) except ldap.LDAPError, ex: print >> sys.stderr, 'Could not connect LDAP server: %s' % (ex,) sys.exit(1) try: ldap_con.bind_s(options.binddn, password) except ldap.LDAPError, ex: print >> sys.stderr, 'Could not bind to LDAP server: %s' % (ex,) sys.exit(1) return ldap_con def get_schema(ldap_con): """Find and get subschemas.""" raw_res = ldap_con.search_s('', ldap.SCOPE_BASE, '(objectClass=*)', ['subschemaSubentry']) assert len(raw_res) == 1 _dn, attrs = raw_res[0] try: schema_dn = attrs['subschemaSubentry'][0] except LookupError: print >> sys.stderr, 'Could not find subschemas' sys.exit(1) raw_res = ldap_con.search_s(schema_dn, ldap.SCOPE_BASE, 'objectClass=subentry', ['objectClasses']) assert len(raw_res) == 1 _dn, attrs = raw_res[0] schema = ldap.schema.subentry.SubSchema(attrs) return schema class UpdateError(Exception): """Top level class for update errors.""" pass class PreconditionError(UpdateError): """Not modified because pre-condtion is unmatched.""" pass class ModificationError(UpdateError): """Not modified because of modification was refused.""" pass def drop(ldap_con, schema, drop_class, dname): """Remove objectClass from object including all dependent attributes.""" raw_res = ldap_con.search_s(dname, ldap.SCOPE_BASE, filter_format('(objectClass=%s)', (drop_class,)), None) if len(raw_res) != 1: raise PreconditionError(dname, 'filtered by objectClass') assert len(raw_res) == 1 _dn, attrs = raw_res[0] drop_attr = set(attrs) for cls in attrs['objectClass']: if cls == drop_class: continue oclass = schema.get_obj(ldap.schema.ObjectClass, cls) drop_attr -= set(oclass.must) drop_attr -= set(oclass.may) parts = ldap.dn.str2dn(dname) if parts[0][0][0] in drop_attr: raise PreconditionError(dname, 'refused removing RDN') modlist = [(ldap.MOD_DELETE, _, None) for _ in drop_attr] modlist.append((ldap.MOD_DELETE, 'objectClass', drop_class)) try: ldap_con.modify_s(dname, modlist) except ldap.LDAPError, ex: raise ModificationError(dname, modlist, ex) def main(): """Remove objectClass from object.""" options, dnames = parse() ldap_con = open_ldap(options) try: schema = get_schema(ldap_con) for dname in dnames: try: drop(ldap_con, schema, options.objectclass, dname) print >> sys.stdout, '"%s": success.' % (dname,) except UpdateError, ex: print >> sys.stderr, '"%s": %s' % ex.args[:2] finally: try: ldap_con.unbind() except ldap.LDAPError, ex: print >> sys.stderr, 'Could not unbind: %s' % (ex,) if __name__ == '__main__': main()