From 2afc2f20b65b28140274828249160f1483090b5e Mon Sep 17 00:00:00 2001 From: Matthieu Patou Date: Sat, 3 Jul 2010 16:26:24 +0400 Subject: s4 upgradeprovision: add function to backup the provision before updating Signed-off-by: Andrew Bartlett --- source4/scripting/bin/upgradeprovision | 420 +++++++++++++++++++++------------ 1 file changed, 268 insertions(+), 152 deletions(-) (limited to 'source4/scripting') diff --git a/source4/scripting/bin/upgradeprovision b/source4/scripting/bin/upgradeprovision index 400d629602..c22f3ce43b 100755 --- a/source4/scripting/bin/upgradeprovision +++ b/source4/scripting/bin/upgradeprovision @@ -47,7 +47,7 @@ from samba.provision import (find_setup_dir, get_domain_descriptor, ProvisioningError, get_last_provision_usn, get_max_usn, update_provision_usn) from samba.schema import get_linked_attributes, Schema, get_schema_descriptor -from samba.dcerpc import security, drsblobs +from samba.dcerpc import security, drsblobs, xattr from samba.ndr import ndr_unpack from samba.upgradehelpers import (dn_sort, get_paths, newprovision, find_provision_key_parameters, get_ldbs, @@ -1002,6 +1002,7 @@ def check_updated_sd(ref_sam, cur_sam, names): str(reference[i]["nTSecurityDescriptor"])) hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid) + for i in range(0, len(current)): key = str(current[i]["dn"]).lower() if hash.has_key(key): @@ -1113,6 +1114,18 @@ def removeProvisionUSN(samdb): delta.dn = entry[0].dn samdb.modify(delta) +def remove_stored_generated_attrs(paths, creds, session, lp): + """Remove previously stored constructed attributes + + :param paths: List of paths for different provision objects + from the upgraded provision + :param creds: A credential object + :param session: A session object + :param lp: A line parser object + :return: An associative array whose key are the different constructed + attributes and the value the dn where this attributes were found. + """ + def simple_update_basesamdb(newpaths, paths, names): """Update the provision container db: sam.ldb @@ -1185,6 +1198,96 @@ def update_samdb(ref_samdb, samdb, names, highestUSN, schema): return 0 +def copyxattrs(dir, refdir): + """ Copy owner, groups, extended ACL and NT acls from + a reference dir to a destination dir + + Both dir are supposed to hold the same files + :param dir: Destination dir + :param refdir: Reference directory""" + + noxattr = 0 + for root, dirs, files in os.walk(dir, topdown=True): + for name in files: + subdir=root[len(dir):] + ref = os.path.join("%s%s" % (refdir, subdir), name) + statsinfo = os.stat(ref) + tgt = os.path.join(root, name) + try: + + os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid) + # Get the xattr attributes if any + try: + attribute = samba.xattr_native.wrap_getxattr(ref, + xattr.XATTR_NTACL_NAME) + samba.xattr_native.wrap_setxattr(tgt, + xattr.XATTR_NTACL_NAME, + attribute) + except: + noxattr = 1 + attribute = samba.xattr_native.wrap_getxattr(ref, + "system.posix_acl_access") + samba.xattr_native.wrap_setxattr(tgt, + "system.posix_acl_access", + attribute) + except: + continue + for name in dirs: + subdir=root[len(dir):] + ref = os.path.join("%s%s" % (refdir, subdir), name) + statsinfo = os.stat(ref) + tgt = os.path.join(root, name) + try: + os.chown(os.path.join(root, name), statsinfo.st_uid, + statsinfo.st_gid) + try: + attribute = samba.xattr_native.wrap_getxattr(ref, + xattr.XATTR_NTACL_NAME) + samba.xattr_native.wrap_setxattr(tgt, + xattr.XATTR_NTACL_NAME, + attribute) + except: + noxattr = 1 + attribute = samba.xattr_native.wrap_getxattr(ref, + "system.posix_acl_access") + samba.xattr_native.wrap_setxattr(tgt, + "system.posix_acl_access", + attribute) + + except: + continue + + +def backup_provision(paths, dir): + """This function backup the provision files so that a rollback + is possible + + :param paths: Paths to different objects + :param dir: Directory where to store the backup + """ + + shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol")) + copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol) + shutil.copy2(paths.samdb, dir) + shutil.copy2(paths.secrets, dir) + shutil.copy2(paths.idmapdb, dir) + shutil.copy2(paths.privilege, dir) + if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")): + shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir) + shutil.copy2(paths.smbconf, dir) + shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir) + + samldbdir = os.path.join(paths.private_dir, "sam.ldb.d") + if not os.path.isdir(samldbdir): + samldbdir = paths.private_dir + schemaldb = os.path.join(paths.private_dir, "schema.ldb") + configldb = os.path.join(paths.private_dir, "configuration.ldb") + usersldb = os.path.join(paths.private_dir, "users.ldb") + shutil.copy2(schemaldb, dir) + shutil.copy2(usersldb, dir) + shutil.copy2(configldb, dir) + else: + shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d")) def setup_path(file): return os.path.join(setup_dir, file) @@ -1210,12 +1313,13 @@ def setup_path(file): # A) When alpha9 or alphaxx is present # The base sam.ldb file is updated by looking at the difference between # referrence one and the current one. Everything is copied with the -# exception of lastProvisionUSN attributes. The highest used USN -# is fetched so that changed by upgradeprovision usn can be tracked +# exception of lastProvisionUSN attributes. # B) Other case (it reflect that that provision was done before alpha9) # The base sam.ldb of the reference provision is copied over # the current one, if necessary ldb related to partitions are moved # and renamed +# The highest used USN is fetched so that changed by upgradeprovision +# usn can be tracked # 12)A Schema object is created, it will be used to provide a complete # schema to current provision during update (as the schema of the # current provision might not be complete and so won't allow some @@ -1338,158 +1442,170 @@ if __name__ == '__main__': minUSN = 0 # 2) ldbs = get_ldbs(paths, creds, session, lp) - ldbs.startTransactions() - - # 3) Guess all the needed names (variables in fact) from the current - # provision. - names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap, - paths, smbconf, lp) - # 4) - lastProvisionUSNs = get_last_provision_usn(ldbs.sam) - if lastProvisionUSNs is not None: - message(CHANGE, - "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs)) - - # Objects will be created with the admin session - # (not anymore system session) - adm_session = admin_session(lp, str(names.domainsid)) - # So we reget handle on objects - # ldbs = get_ldbs(paths, creds, adm_session, lp) - - if not sanitychecks(ldbs.sam, names): - message(SIMPLE, "Sanity checks for the upgrade fails, checks messages" - " and correct them before rerunning upgradeprovision") - sys.exit(1) - - # Let's see provision parameters - print_provision_key_parameters(names) - - # 5) With all this information let's create a fresh new provision used as - # reference - message(SIMPLE, "Creating a reference provision") - provisiondir = tempfile.mkdtemp(dir=paths.private_dir, - prefix="referenceprovision") - newprovision(names, setup_dir, creds, session, smbconf, provisiondir, - provision_logger) - - # TODO - # 6) and 7) - # We need to get a list of object which SD is directly computed from - # defaultSecurityDescriptor. - # This will allow us to know which object we can rebuild the SD in case - # of change of the parent's SD or of the defaultSD. - # Get file paths of this new provision - newpaths = get_paths(param, targetdir=provisiondir) - new_ldbs = get_ldbs(newpaths, creds, session, lp) - new_ldbs.startTransactions() - - # 8) Populate some associative array to ease the update process - # List of attribute which are link and backlink - populate_links(new_ldbs.sam, names.schemadn) - # List of attribute with ASN DN synthax) - populate_dnsyntax(new_ldbs.sam, names.schemadn) - # 9) - update_privilege(newpaths.private_dir, paths.private_dir) - # 10) - oem = getOEMInfo(ldbs.sam, str(names.rootdn)) - # Do some modification on sam.ldb - ldbs.groupedCommit() - # 11) - if re.match(".*alpha((9)|(\d\d+)).*", str(oem)): - # 11) A - # Starting from alpha9 we can consider that the structure is quite ok - # and that we should do only dela + backupdir = tempfile.mkdtemp(dir=paths.private_dir, + prefix="backupprovision") + backup_provision(paths, backupdir) + try: + ldbs.startTransactions() + + # 3) Guess all the needed names (variables in fact) from the current + # provision. + names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap, + paths, smbconf, lp) + # 4) + lastProvisionUSNs = get_last_provision_usn(ldbs.sam) + if lastProvisionUSNs is not None: + message(CHANGE, + "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs)) + + # Objects will be created with the admin session + # (not anymore system session) + adm_session = admin_session(lp, str(names.domainsid)) + # So we reget handle on objects + # ldbs = get_ldbs(paths, creds, adm_session, lp) + + if not sanitychecks(ldbs.sam, names): + message(SIMPLE, "Sanity checks for the upgrade fails, checks messages" + " and correct them before rerunning upgradeprovision") + sys.exit(1) + + # Let's see provision parameters + print_provision_key_parameters(names) + + # 5) With all this information let's create a fresh new provision used as + # reference + message(SIMPLE, "Creating a reference provision") + provisiondir = tempfile.mkdtemp(dir=paths.private_dir, + prefix="referenceprovision") + newprovision(names, setup_dir, creds, session, smbconf, provisiondir, + provision_logger) + + # TODO + # 6) and 7) + # We need to get a list of object which SD is directly computed from + # defaultSecurityDescriptor. + # This will allow us to know which object we can rebuild the SD in case + # of change of the parent's SD or of the defaultSD. + # Get file paths of this new provision + newpaths = get_paths(param, targetdir=provisiondir) + new_ldbs = get_ldbs(newpaths, creds, session, lp) + new_ldbs.startTransactions() + + # 8) Populate some associative array to ease the update process + # List of attribute which are link and backlink + populate_links(new_ldbs.sam, names.schemadn) + # List of attribute with ASN DN synthax) + populate_dnsyntax(new_ldbs.sam, names.schemadn) + # 9) + update_privilege(newpaths.private_dir, paths.private_dir) + # 10) + oem = getOEMInfo(ldbs.sam, str(names.rootdn)) + # Do some modification on sam.ldb + ldbs.groupedCommit() new_ldbs.groupedCommit() - delta_update_basesamdb(newpaths.samdb, paths.samdb, creds, session, lp, message) + + # 11) + if re.match(".*alpha((9)|(\d\d+)).*", str(oem)): + # 11) A + # Starting from alpha9 we can consider that the structure is quite ok + # and that we should do only dela + delta_update_basesamdb(newpaths.samdb, paths.samdb, creds, session, lp, message) + else: + # 11) B + simple_update_basesamdb(newpaths, paths, names) + ldbs = get_ldbs(paths, creds, session, lp) + removeProvisionUSN(ldbs.sam) + ldbs.startTransactions() minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1 new_ldbs.startTransactions() - else: - # 11) B - simple_update_basesamdb(newpaths, paths, names) - ldbs = get_ldbs(paths, creds, session, lp) - removeProvisionUSN(ldbs.sam) - ldbs.startTransactions() - # 12) - schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn), - serverdn=str(names.serverdn)) - # 13) - if opts.full: - if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs, - schema): - message(SIMPLE, "Rollbacking every changes. Check the reason" - " of the problem") - message(SIMPLE, "In any case your system as it was before" - " the upgrade") - ldbs.groupedRollback() - new_ldbs.groupedRollback() - shutil.rmtree(provisiondir) - sys.exit(1) - # 14) - update_secrets(new_ldbs.secrets, ldbs.secrets, message) - # 15) - message(SIMPLE, "Update machine account") - update_machine_account_password(ldbs.sam, ldbs.secrets, names) - - # 16) SD should be created with admin but as some previous acl were so wrong - # that admin can't modify them we have first to recreate them with the good - # form but with system account and then give the ownership to admin ... - if not re.match(r'.*alpha(9|\d\d+)', str(oem)): - message(SIMPLE, "Fixing old povision SD") - fix_partition_sd(ldbs.sam, names) - rebuild_sd(ldbs.sam, names) - - # We calculate the max USN before recalculating the SD because we might - # touch object that have been modified after a provision and we do not - # want that the next upgradeprovision thinks that it has a green light - # to modify them - - # 17) - maxUSN = get_max_usn(ldbs.sam, str(names.rootdn)) - - # 18) We rebuild SD only if defaultSecurityDescriptor is modified - # But in fact we should do it also if one object has its SD modified as - # child might need rebuild - if defSDmodified: - message(SIMPLE, "Updating SD") - ldbs.sam.set_session_info(adm_session) - # Alpha10 was a bit broken still - if re.match(r'.*alpha(\d|10)', str(oem)): + # 12) + schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn), + serverdn=str(names.serverdn)) + + # 13) + if opts.full: + if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs, + schema): + message(SIMPLE, "Rollbacking every changes. Check the reason" + " of the problem") + message(SIMPLE, "In any case your system as it was before" + " the upgrade") + ldbs.groupedRollback() + new_ldbs.groupedRollback() + shutil.rmtree(provisiondir) + sys.exit(1) + # 14) + update_secrets(new_ldbs.secrets, ldbs.secrets, message) + # 15) + message(SIMPLE, "Update machine account") + update_machine_account_password(ldbs.sam, ldbs.secrets, names) + + # 16) SD should be created with admin but as some previous acl were so wrong + # that admin can't modify them we have first to recreate them with the good + # form but with system account and then give the ownership to admin ... + if not re.match(r'.*alpha(9|\d\d+)', str(oem)): + message(SIMPLE, "Fixing old povision SD") fix_partition_sd(ldbs.sam, names) - rebuild_sd(ldbs.sam, names) - - # 19) - # Now we are quite confident in the recalculate process of the SD, we make - # it optional. - # Also the check must be done in a clever way as for the moment we just - # compare SDDL - if opts.debugchangesd: - check_updated_sd(new_ldbs.sam, ldbs.sam, names) - - # 20) - updateOEMInfo(ldbs.sam, str(names.rootdn)) - # 21) - check_for_DNS(newpaths.private_dir, paths.private_dir) - # 22) - if lastProvisionUSNs is not None: - update_provision_usn(ldbs.sam, minUSN, maxUSN) - if opts.full and (names.policyid is None or names.policyid_dc is None): - update_policyids(names, ldbs.sam) - if opts.full or opts.resetfileacl: - try: - update_gpo(paths, ldbs.sam, names, lp, message, 1) - except ProvisioningError, e: - message(ERROR, "The policy for domain controller is missing," - " you should restart upgradeprovision with --full") - else: - try: - update_gpo(paths, ldbs.sam, names, lp, message, 0) - except ProvisioningError, e: - message(ERROR, "The policy for domain controller is missing," - " you should restart upgradeprovision with --full") - ldbs.groupedCommit() - new_ldbs.groupedCommit() - message(SIMPLE, "Upgrade finished !") - # remove reference provision now that everything is done ! - shutil.rmtree(provisiondir) + rebuild_sd(ldbs.sam, names) + + # We calculate the max USN before recalculating the SD because we might + # touch object that have been modified after a provision and we do not + # want that the next upgradeprovision thinks that it has a green light + # to modify them + + # 17) + maxUSN = get_max_usn(ldbs.sam, str(names.rootdn)) + + # 18) We rebuild SD only if defaultSecurityDescriptor is modified + # But in fact we should do it also if one object has its SD modified as + # child might need rebuild + if defSDmodified: + message(SIMPLE, "Updating SD") + ldbs.sam.set_session_info(adm_session) + # Alpha10 was a bit broken still + if re.match(r'.*alpha(\d|10)', str(oem)): + fix_partition_sd(ldbs.sam, names) + rebuild_sd(ldbs.sam, names) + + # 19) + # Now we are quite confident in the recalculate process of the SD, we make + # it optional. + # Also the check must be done in a clever way as for the moment we just + # compare SDDL + if opts.debugchangesd: + check_updated_sd(new_ldbs.sam, ldbs.sam, names) + + # 20) + updateOEMInfo(ldbs.sam, str(names.rootdn)) + # 21) + check_for_DNS(newpaths.private_dir, paths.private_dir) + # 22) + if lastProvisionUSNs is not None: + update_provision_usn(ldbs.sam, minUSN, maxUSN) + if opts.full and (names.policyid is None or names.policyid_dc is None): + update_policyids(names, ldbs.sam) + if opts.full or opts.resetfileacl: + try: + update_gpo(paths, ldbs.sam, names, lp, message, 1) + except ProvisioningError, e: + message(ERROR, "The policy for domain controller is missing," + " you should restart upgradeprovision with --full") + else: + try: + update_gpo(paths, ldbs.sam, names, lp, message, 0) + except ProvisioningError, e: + message(ERROR, "The policy for domain controller is missing," + " you should restart upgradeprovision with --full") + ldbs.groupedCommit() + new_ldbs.groupedCommit() + message(SIMPLE, "Upgrade finished !") + # remove reference provision now that everything is done ! + shutil.rmtree(provisiondir) + except StandardError, err: + message(ERROR,"A problem has occured when trying to upgrade your provision," + " a full backup is located at %s" % backupdir) + if opts.changeall: + (typ, val, tb) = sys.exc_info() + traceback.print_exception(typ, val, tb) -- cgit