mirror of
https://github.com/samba-team/samba.git
synced 2025-03-11 16:58:40 +03:00
s4 upgradeprovision: add function to backup the provision before updating
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
parent
6c51b3a432
commit
2afc2f20b6
@ -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()
|
||||
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))
|
||||
# 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)
|
||||
# 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)
|
||||
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)
|
||||
# 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)
|
||||
# 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()
|
||||
# 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
|
||||
# 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)
|
||||
# 12)
|
||||
schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
|
||||
serverdn=str(names.serverdn))
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
|
||||
# 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)):
|
||||
# 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)
|
||||
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)
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user