r26520: More Python updates.
[ira/wip.git] / source / scripting / python / samba / provision.py
index df40c2fb7aa8e13c71a9f517c0ef26b2336f7371..0a3c183fcc9a95323b2b65b418f628e33f52d04b 100644 (file)
@@ -10,11 +10,14 @@ import os
 import pwd
 import grp
 import time
-import uuid, sid, misc
+import uuid, misc
 from socket import gethostname, gethostbyname
 import param
 import registry
+import samba
 from samba import Ldb, substitute_var, valid_netbios_name
+from samba.samdb import SamDB
+import security
 from ldb import Dn, SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
         LDB_ERR_NO_SUCH_OBJECT, timestring
 
@@ -54,6 +57,7 @@ class ProvisionSettings(object):
         self.schemedn_ldb = None
         self.s4_ldapi_path = None
         self.policyguid = None
+        self.extensibleobject = None
 
     def subst_vars(self):
         return {"SCHEMADN": self.schemadn,
@@ -61,20 +65,21 @@ class ProvisionSettings(object):
                 "SCHEMADN_MOD": "schema_fsmo",
                 "SCHEMADN_MOD2": ",objectguid",
                 "CONFIGDN": self.configdn,
-                "TDB_MODULES_LIST": ","+",".join(self.tdb_modules_list)
-                "MODULES_LIST2": ",".join(self.modules_list2)
+                "TDB_MODULES_LIST": ","+",".join(self.tdb_modules_list),
+                "MODULES_LIST2": ",".join(self.modules_list2),
                 "CONFIGDN_LDB": self.configdn_ldb,
                 "DOMAINDN": self.domaindn,
                 "DOMAINDN_LDB": self.domaindn_ldb,
                 "DOMAINDN_MOD": "pdc_fsmo,password_hash",
                 "DOMAINDN_MOD2": ",objectguid",
-                "DOMAINSID": self.domainsid,
+                "DOMAINSID": str(self.domainsid),
                 "MODULES_LIST": ",".join(self.modules_list),
                 "CONFIGDN_MOD": "naming_fsmo",
                 "CONFIGDN_MOD2": ",objectguid",
                 "NETBIOSNAME": self.netbiosname,
                 "DNSNAME": self.dnsname,
                 "ROOTDN": self.rootdn,
+                "DOMAIN": self.domain,
                 "DNSDOMAIN": self.dnsdomain,
                 "REALM": self.realm,
                 "DEFAULTSITE": self.defaultsite,
@@ -87,6 +92,9 @@ class ProvisionSettings(object):
                 "POLICYGUID": self.policyguid,
                 "RDN_DC": self.rdn_dc,
                 "DOMAINGUID_MOD": self.domainguid_mod,
+                "VERSION": samba.version(),
+                "ACI": "# no aci for local ldb",
+                "EXTENSIBLEOBJECT": self.extensibleobject,
                 }
 
     def fix(self, paths):
@@ -149,7 +157,7 @@ def install_ok(lp, session_info, credentials):
     if lp.get("realm") == "":
         return False
     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
-            credentials=credentials)
+            credentials=credentials, lp=lp)
     if len(ldb.search("(cn=Administrator)")) != 1:
         return False
     return True
@@ -164,33 +172,6 @@ def findnss(nssfn, *names):
             pass
     raise Exception("Unable to find user/group for %s" % arguments[1])
 
-def add_foreign(ldb, subobj, sid, desc):
-    """Add a foreign security principle."""
-    add = """
-dn: CN=%s,CN=ForeignSecurityPrincipals,%s
-objectClass: top
-objectClass: foreignSecurityPrincipal
-description: %s
-""" % (sid, subobj.domaindn, desc)
-    # deliberately ignore errors from this, as the records may
-    # already exist
-    for msg in ldb.parse_ldif(add):
-        ldb.add(msg[1])
-
-def setup_name_mapping(subobj, ldb, sid, unixname):
-    """Setup a mapping between a sam name and a unix name."""
-    res = ldb.search(Dn(ldb, subobj.domaindn), SCOPE_SUBTREE, 
-                     "objectSid=%s" % sid, ["dn"])
-    assert len(res) == 1, "Failed to find record for objectSid %s" % sid
-
-    mod = """
-dn: %s
-changetype: modify
-replace: unixName
-unixName: %s
-""" % (res[0].dn, unixname)
-    ldb.modify(ldb.parse_ldif(mod).next()[1])
-
 
 def hostip():
     """return first host IP."""
@@ -214,66 +195,16 @@ def ldb_delete(ldb):
     ldb.connect(ldb.filename)
 
 
-def ldb_erase(ldb):
-    """Erase an ldb, removing all records."""
-    # delete the specials
-    for attr in ["@INDEXLIST", "@ATTRIBUTES", "@SUBCLASSES", "@MODULES", 
-                 "@OPTIONS", "@PARTITION", "@KLUDGEACL"]:
-        try:
-            ldb.delete(Dn(ldb, attr))
-        except LdbError, (LDB_ERR_NO_SUCH_OBJECT, _):
-            # Ignore missing dn errors
-            pass
-
-    basedn = Dn(ldb, "")
-    # and the rest
-    for msg in ldb.search(basedn, SCOPE_SUBTREE, 
-            "(&(|(objectclass=*)(dn=*))(!(dn=@BASEINFO)))", 
-            ["dn"]):
-        ldb.delete(msg.dn)
-
-    res = ldb.search(basedn, SCOPE_SUBTREE, "(&(|(objectclass=*)(dn=*))(!(dn=@BASEINFO)))", ["dn"])
-    assert len(res) == 0
-
-
-def ldb_erase_partitions(subobj, message, ldb, ldapbackend):
-    """Erase an ldb, removing all records."""
-    assert ldb is not None
-    res = ldb.search(Dn(ldb, ""), SCOPE_BASE, "(objectClass=*)", 
-                     ["namingContexts"])
-    assert len(res) == 1
-    if not "namingContexts" in res[0]:
-        return
-    for basedn in res[0]["namingContexts"]:
-        anything = "(|(objectclass=*)(dn=*))"
-        previous_remaining = 1
-        current_remaining = 0
-
-        if ldapbackend and (basedn == subobj.domaindn):
-            # Only delete objects that were created by provision
-            anything = "(objectcategory=*)"
-
-        k = 0
-        while ++k < 10 and (previous_remaining != current_remaining):
-            # and the rest
-            res2 = ldb.search(Dn(ldb, basedn), SCOPE_SUBTREE, anything, ["dn"])
-            previous_remaining = current_remaining
-            current_remaining = len(res2)
-            for msg in res2:
-                try:
-                    ldb.delete(msg.dn)
-                except LdbError, (_, text):
-                    message("Unable to delete %s: %s" % (msg.dn, text))
-
-
-def open_ldb(session_info, credentials, dbname):
+def open_ldb(session_info, credentials, lp, dbname):
     assert session_info is not None
     try:
-        return Ldb(dbname, session_info=session_info, credentials=credentials)
+        return Ldb(dbname, session_info=session_info, credentials=credentials, 
+                   lp=lp)
     except LdbError, e:
         print e
         os.unlink(dbname)
-        return Ldb(dbname, session_info=session_info, credentials=credentials)
+        return Ldb(dbname, session_info=session_info, credentials=credentials,
+                   lp=lp)
 
 
 def setup_add_ldif(setup_dir, ldif, subobj, ldb):
@@ -299,15 +230,15 @@ def setup_modify_ldif(setup_dir, ldif, subobj, ldb):
         ldb.modify(msg)
 
 
-def setup_ldb(setup_dir, ldif, session_info, credentials, subobj, dbname, 
+def setup_ldb(setup_dir, ldif, session_info, credentials, subobj, lp, dbname, 
               erase=True):
     assert dbname is not None
-    ldb = open_ldb(session_info, credentials, dbname)
+    ldb = open_ldb(session_info, credentials, lp, dbname)
     assert ldb is not None
     ldb.transaction_start()
     try:
         if erase:
-            ldb_erase(ldb);    
+            ldb.erase();    
         setup_add_ldif(setup_dir, ldif, subobj, ldb)
     except:
         ldb.transaction_cancel()
@@ -356,11 +287,15 @@ def provision_default_paths(lp, subobj):
     paths.keytab = os.path.join(private_dir, "secrets.keytab")
     paths.dns = os.path.join(private_dir, subobj.dnsdomain + ".zone")
     paths.winsdb = os.path.join(private_dir, "wins.ldb")
-    paths.ldap_basedn_ldif = os.path.join(private_dir, subobj.dnsdomain + ".ldif")
-    paths.ldap_config_basedn_ldif = os.path.join(private_dir, subobj.dnsdomain + "-config.ldif")
-    paths.ldap_schema_basedn_ldif = os.path.join(private_dir, subobj.dnsdomain + "-schema.ldif")
+    paths.ldap_basedn_ldif = os.path.join(private_dir, 
+                                          subobj.dnsdomain + ".ldif")
+    paths.ldap_config_basedn_ldif = os.path.join(private_dir, 
+                                             subobj.dnsdomain + "-config.ldif")
+    paths.ldap_schema_basedn_ldif = os.path.join(private_dir, 
+                                              subobj.dnsdomain + "-schema.ldif")
     paths.s4_ldapi_path = os.path.join(private_dir, "ldapi")
-    paths.phpldapadminconfig = os.path.join(private_dir, "phpldapadmin-config.php")
+    paths.phpldapadminconfig = os.path.join(private_dir, 
+                                            "phpldapadmin-config.php")
     paths.hklm = os.path.join(private_dir, "hklm.ldb")
     return paths
 
@@ -371,50 +306,51 @@ def setup_name_mappings(subobj, ldb):
                      ["objectSid"])
     assert len(res) == 1
     assert "objectSid" in res[0]
-    sid = list(res[0]["objectSid"])[0]
+    sid = str(list(res[0]["objectSid"])[0])
 
     # add some foreign sids if they are not present already
-    add_foreign(ldb, subobj, "S-1-5-7", "Anonymous")
-    add_foreign(ldb, subobj, "S-1-1-0", "World")
-    add_foreign(ldb, subobj, "S-1-5-2", "Network")
-    add_foreign(ldb, subobj, "S-1-5-18", "System")
-    add_foreign(ldb, subobj, "S-1-5-11", "Authenticated Users")
+    ldb.add_foreign(subobj.domaindn, "S-1-5-7", "Anonymous")
+    ldb.add_foreign(subobj.domaindn, "S-1-1-0", "World")
+    ldb.add_foreign(subobj.domaindn, "S-1-5-2", "Network")
+    ldb.add_foreign(subobj.domaindn, "S-1-5-18", "System")
+    ldb.add_foreign(subobj.domaindn, "S-1-5-11", "Authenticated Users")
 
     # some well known sids
-    setup_name_mapping(subobj, ldb, "S-1-5-7", subobj.nobody)
-    setup_name_mapping(subobj, ldb, "S-1-1-0", subobj.nogroup)
-    setup_name_mapping(subobj, ldb, "S-1-5-2", subobj.nogroup)
-    setup_name_mapping(subobj, ldb, "S-1-5-18", subobj.root)
-    setup_name_mapping(subobj, ldb, "S-1-5-11", subobj.users)
-    setup_name_mapping(subobj, ldb, "S-1-5-32-544", subobj.wheel)
-    setup_name_mapping(subobj, ldb, "S-1-5-32-545", subobj.users)
-    setup_name_mapping(subobj, ldb, "S-1-5-32-546", subobj.nogroup)
-    setup_name_mapping(subobj, ldb, "S-1-5-32-551", subobj.backup)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-7", subobj.nobody)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-1-0", subobj.nogroup)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-2", subobj.nogroup)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-18", subobj.root)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-11", subobj.users)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-32-544", subobj.wheel)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-32-545", subobj.users)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-32-546", subobj.nogroup)
+    ldb.setup_name_mapping(subobj.domaindn, "S-1-5-32-551", subobj.backup)
 
     # and some well known domain rids
-    setup_name_mapping(subobj, ldb, sid + "-500", subobj.root)
-    setup_name_mapping(subobj, ldb, sid + "-518", subobj.wheel)
-    setup_name_mapping(subobj, ldb, sid + "-519", subobj.wheel)
-    setup_name_mapping(subobj, ldb, sid + "-512", subobj.wheel)
-    setup_name_mapping(subobj, ldb, sid + "-513", subobj.users)
-    setup_name_mapping(subobj, ldb, sid + "-520", subobj.wheel)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-500", subobj.root)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-518", subobj.wheel)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-519", subobj.wheel)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-512", subobj.wheel)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-513", subobj.users)
+    ldb.setup_name_mapping(subobj.domaindn, sid + "-520", subobj.wheel)
 
 
-def provision_become_dc(setup_dir, subobj, message, paths, session_info, 
+def provision_become_dc(setup_dir, subobj, message, paths, lp, session_info, 
                         credentials):
     assert session_info is not None
     subobj.fix(paths)
 
     message("Setting up templates into %s" % paths.templates)
     setup_ldb(setup_dir, "provision_templates.ldif", session_info,
-              credentials, subobj, paths.templates)
+              credentials, subobj, lp, paths.templates)
 
     # Also wipes the database
     message("Setting up %s partitions" % paths.samdb)
     setup_ldb(setup_dir, "provision_partitions.ldif", session_info, 
-              credentials, subobj, paths.samdb)
+              credentials, subobj, lp, paths.samdb)
 
-    samdb = open_ldb(session_info, credentials, paths.samdb)
+    samdb = SamDB(paths.samdb, session_info=session_info, 
+                  credentials=credentials, lp=lp)
     ldb.transaction_start()
     try:
         message("Setting up %s attributes" % paths.samdb)
@@ -424,7 +360,7 @@ def provision_become_dc(setup_dir, subobj, message, paths, session_info,
         setup_add_ldif(setup_dir, "provision_rootdse_add.ldif", subobj, samdb)
 
         message("Erasing data from partitions")
-        ldb_erase_partitions(subobj, message, samdb, undefined)
+        ldb_erase_partitions(subobj, message, samdb, None)
 
         message("Setting up %s indexes" % paths.samdb)
         setup_add_ldif(setup_dir, "provision_index.ldif", subobj, samdb)
@@ -436,10 +372,10 @@ def provision_become_dc(setup_dir, subobj, message, paths, session_info,
 
     message("Setting up %s" % paths.secrets)
     setup_ldb(setup_dir, "secrets_init.ldif", session_info, credentials, 
-              subobj, paths.secrets)
+              subobj, lp, paths.secrets)
 
     setup_ldb(setup_dir, "secrets.ldif", session_info, credentials, subobj, 
-              paths.secrets, False)
+              lp, paths.secrets, False)
 
 
 def provision(lp, setup_dir, subobj, message, blank, paths, session_info, 
@@ -465,32 +401,41 @@ def provision(lp, setup_dir, subobj, message, blank, paths, session_info,
     # only install a new smb.conf if there isn't one there already
     if not os.path.exists(paths.smbconf):
         message("Setting up smb.conf")
-        setup_file(setup_dir, "provision.smb.conf", message, paths.smbconf, subobj)
+        setup_file(setup_dir, "provision.smb.conf", message, paths.smbconf, 
+                   subobj)
         lp.reload()
 
     # only install a new shares config db if there is none
     if not os.path.exists(paths.shareconf):
         message("Setting up share.ldb")
-        setup_ldb(setup_dir, "share.ldif", session_info, credentials, subobj, paths.shareconf)
+        setup_ldb(setup_dir, "share.ldif", session_info, credentials, subobj, 
+                  lp, paths.shareconf)
 
     message("Setting up %s" % paths.secrets)
-    setup_ldb(setup_dir, "secrets_init.ldif", session_info, credentials, subobj, paths.secrets)
-    setup_ldb(setup_dir, "secrets.ldif", session_info, credentials, subobj, paths.secrets, False)
+    setup_ldb(setup_dir, "secrets_init.ldif", session_info, credentials, 
+              subobj, lp, paths.secrets)
+    setup_ldb(setup_dir, "secrets.ldif", session_info, credentials, subobj, 
+              lp, paths.secrets, False)
 
     message("Setting up registry")
     reg = registry.Registry()
-    # FIXME: Still fails for some reason:
-    #reg.mount(paths.hklm, registry.HKEY_LOCAL_MACHINE, [])
-    #reg.apply_patchfile(os.path.join(setup_dir, "provision.reg"))
+    #hive = registry.Hive(paths.hklm, session_info=session_info, 
+    #                     credentials=credentials, lp_ctx=lp)
+    #reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
+    provision_reg = os.path.join(setup_dir, "provision.reg")
+    assert os.path.exists(provision_reg)
+    #reg.apply_patchfile(provision_reg)
 
     message("Setting up templates into %s" % paths.templates)
-    setup_ldb(setup_dir, "provision_templates.ldif", session_info, credentials, subobj, paths.templates)
+    setup_ldb(setup_dir, "provision_templates.ldif", session_info, 
+              credentials, subobj, lp, paths.templates)
 
     message("Setting up sam.ldb partitions")
     setup_ldb(setup_dir, "provision_partitions.ldif", session_info, 
-              credentials, subobj, paths.samdb)
+              credentials, subobj, lp, paths.samdb)
 
-    samdb = open_ldb(session_info, credentials, paths.samdb)
+    samdb = SamDB(paths.samdb, session_info=session_info, 
+                  credentials=credentials, lp=lp)
     samdb.transaction_start()
     try:
         message("Setting up sam.ldb attributes")
@@ -509,10 +454,9 @@ def provision(lp, setup_dir, subobj, message, blank, paths, session_info,
 
     message("Pre-loading the Samba 4 and AD schema")
 
-    samdb = open_ldb(session_info, credentials, paths.samdb)
-
+    samdb = SamDB(paths.samdb, session_info=session_info, 
+                  credentials=credentials, lp=lp)
     samdb.set_domain_sid(subobj.domainsid)
-
     load_schema(setup_dir, subobj, samdb)
 
     samdb.transaction_start()
@@ -603,7 +547,8 @@ def provision_dns(setup_dir, subobj, message, paths, session_info, credentials):
     """Write out a DNS zone file, from the info in the current database."""
     message("Setting up DNS zone: %s" % subobj.dnsdomain)
     # connect to the sam
-    ldb = Ldb(paths.samdb, session_info=session_info, credentials=credentials)
+    ldb = SamDB(paths.samdb, session_info=session_info, credentials=credentials,
+                lp=lp)
 
     # These values may have changed, due to an incoming SamSync,
     # or may not have been specified, so fetch them from the database
@@ -614,7 +559,7 @@ def provision_dns(setup_dir, subobj, message, paths, session_info, credentials):
     assert(res[0]["objectGUID"] is not None)
     subobj.domainguid = res[0]["objectGUID"]
 
-    subobj.host_guid = searchone(ldb, subobj.domaindn, 
+    subobj.host_guid = ldb.searchone(subobj.domaindn, 
                                  "(&(objectClass=computer)(cn=%s))" % subobj.netbiosname, "objectGUID")
     assert subobj.host_guid is not None
 
@@ -657,7 +602,7 @@ def provision_guess(lp):
     assert subobj.domain is not None
     assert subobj.hostname is not None
     
-    subobj.domainsid    = sid.random()
+    subobj.domainsid    = security.random_sid()
     subobj.invocationid = uuid.random()
     subobj.policyguid   = uuid.random()
     subobj.krbtgtpass   = misc.random_password(12)
@@ -716,97 +661,17 @@ def provision_guess(lp):
     return subobj
 
 
-def searchone(ldb, basedn, expression, attribute):
-    """search for one attribute as a string."""
-    res = ldb.search(basedn, SCOPE_SUBTREE, expression, [attribute])
-    if len(res) != 1 or res[0][attribute] is None:
-        return None
-    return res[0][attribute]
-
-
 def load_schema(setup_dir, subobj, samdb):
     """Load schema."""
     src = os.path.join(setup_dir, "schema.ldif")
-
     schema_data = open(src, 'r').read()
-
     src = os.path.join(setup_dir, "schema_samba4.ldif")
-
     schema_data += open(src, 'r').read()
-
     schema_data = substitute_var(schema_data, subobj.subst_vars())
-
     src = os.path.join(setup_dir, "provision_schema_basedn_modify.ldif")
-
     head_data = open(src, 'r').read()
-
     head_data = substitute_var(head_data, subobj.subst_vars())
-
-    samdb.attach_dsdb_schema_from_ldif(head_data, schema_data)
-
-
-def enable_account(ldb, user_dn):
-    """enable the account."""
-    res = ldb.search(user_dn, SCOPE_ONELEVEL, None, ["userAccountControl"])
-    assert len(res) == 1
-    userAccountControl = res[0].userAccountControl
-    userAccountControl = userAccountControl - 2 # remove disabled bit
-    mod = """
-dn: %s
-changetype: modify
-replace: userAccountControl
-userAccountControl: %u
-""" % (user_dn, userAccountControl)
-    ldb.modify(mod)
-
-
-def newuser(sam, username, unixname, password, message, session_info, 
-            credentials):
-    """add a new user record"""
-    # connect to the sam 
-    ldb.transaction_start()
-
-    # find the DNs for the domain and the domain users group
-    res = ldb.search("", SCOPE_BASE, "defaultNamingContext=*", 
-                     ["defaultNamingContext"])
-    assert(len(res) == 1 and res[0].defaultNamingContext is not None)
-    domain_dn = res[0].defaultNamingContext
-    assert(domain_dn is not None)
-    dom_users = searchone(ldb, domain_dn, "name=Domain Users", "dn")
-    assert(dom_users is not None)
-
-    user_dn = "CN=%s,CN=Users,%s" % (username, domain_dn)
-
-    #
-    #  the new user record. note the reliance on the samdb module to fill
-    #  in a sid, guid etc
-    #
-    ldif = """
-dn: %s
-sAMAccountName: %s
-unixName: %s
-sambaPassword: %s
-objectClass: user
-""" % (user_dn, username, unixname, password)
-    #  add the user to the users group as well
-    modgroup = """
-dn: %s
-changetype: modify
-add: member
-member: %s
-""" % (dom_users, user_dn)
-
-
-    #  now the real work
-    message("Adding user %s" % user_dn)
-    ldb.add(ldif)
-
-    message("Modifying group %s" % dom_users)
-    ldb.modify(modgroup)
-
-    #  modify the userAccountControl to remove the disabled bit
-    enable_account(ldb, user_dn)
-    ldb.transaction_commit()
+    samdb.attach_schema_from_ldif(head_data, schema_data)
 
 
 def join_domain(domain, netbios_name, join_type, creds, message):
@@ -835,3 +700,35 @@ def vampire(domain, session_info, credentials, message):
     vampire_ctx.session_info = session_info
     if not ctx.SamSyncLdb(vampire_ctx):
         raise Exception("Migration of remote domain to Samba failed: %s " % vampire_ctx.error_string)
+
+
+def ldb_erase_partitions(subobj, message, ldb, ldapbackend):
+    """Erase an ldb, removing all records."""
+    assert ldb is not None
+    res = ldb.search(Dn(ldb, ""), SCOPE_BASE, "(objectClass=*)", 
+                     ["namingContexts"])
+    assert len(res) == 1
+    if not "namingContexts" in res[0]:
+        return
+    for basedn in res[0]["namingContexts"]:
+        anything = "(|(objectclass=*)(dn=*))"
+        previous_remaining = 1
+        current_remaining = 0
+
+        if ldapbackend and (basedn == subobj.domaindn):
+            # Only delete objects that were created by provision
+            anything = "(objectcategory=*)"
+
+        k = 0
+        while ++k < 10 and (previous_remaining != current_remaining):
+            # and the rest
+            res2 = ldb.search(Dn(ldb, basedn), SCOPE_SUBTREE, anything, ["dn"])
+            previous_remaining = current_remaining
+            current_remaining = len(res2)
+            for msg in res2:
+                try:
+                    ldb.delete(msg.dn)
+                except LdbError, (_, text):
+                    message("Unable to delete %s: %s" % (msg.dn, text))
+
+