2010-03-24 08:50:50 +03:00
#!/usr/bin/env python
2010-03-01 05:41:52 +03:00
# vim: expandtab
2009-10-27 15:31:40 +03:00
#
2010-06-10 01:00:43 +04:00
# Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
2009-10-27 15:31:40 +03:00
#
# Based on provision a Samba4 server by
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
#
2009-11-25 11:42:16 +03:00
#
2009-10-27 15:31:40 +03:00
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
2009-11-25 11:42:16 +03:00
#
2009-10-27 15:31:40 +03:00
# This program is distributed 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 General Public License for more details.
2009-11-25 11:42:16 +03:00
#
2009-10-27 15:31:40 +03:00
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2010-06-13 17:32:41 +04:00
import logging
2009-10-27 15:31:40 +03:00
import optparse
import os
2010-06-13 17:32:41 +04:00
import shutil
2009-10-27 15:31:40 +03:00
import sys
2009-11-26 13:52:40 +03:00
import tempfile
2010-05-02 19:56:03 +04:00
import re
2010-06-07 16:27:48 +04:00
import traceback
2010-01-31 22:06:01 +03:00
# Allow to run from s4 source directory (without installing samba)
2009-10-27 15:31:40 +03:00
sys.path.insert(0, "bin/python")
2010-06-24 11:06:49 +04:00
import ldb
2009-10-27 15:31:40 +03:00
import samba
2010-01-31 22:06:01 +03:00
import samba.getopt as options
2018-03-22 02:50:45 +03:00
from samba.samdb import get_default_backend_store
2010-07-11 15:36:32 +04:00
from base64 import b64encode
2009-10-27 15:31:40 +03:00
from samba.credentials import DONT_USE_KERBEROS
from samba.auth import system_session, admin_session
2013-02-17 11:14:06 +04:00
from samba import tdb_util
2018-03-22 02:50:45 +03:00
from samba import mdb_util
2010-06-10 01:00:43 +04:00
from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
2011-06-14 01:42:59 +04:00
MessageElement, Message, Dn, LdbError)
2010-08-12 12:22:08 +04:00
from samba import param, dsdb, Ldb
2011-06-20 01:05:04 +04:00
from samba.common import confirm
2013-03-22 09:19:27 +04:00
from samba.descriptor import get_wellknown_sds, get_empty_descriptor, get_diff_sds
2012-12-13 15:56:37 +04:00
from samba.provision import (find_provision_key_parameters,
2010-06-20 03:56:52 +04:00
ProvisioningError, get_last_provision_usn,
2011-02-07 03:54:31 +03:00
get_max_usn, update_provision_usn, setup_path)
2010-03-01 05:25:07 +03:00
from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
2012-02-27 03:50:00 +04:00
from samba.dcerpc import security, drsblobs
2012-11-22 19:22:30 +04:00
from samba.dcerpc.security import (
SECINFO_OWNER, SECINFO_GROUP, SECINFO_DACL, SECINFO_SACL)
2010-03-01 05:25:07 +03:00
from samba.ndr import ndr_unpack
2010-06-10 01:00:43 +04:00
from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
2012-03-17 11:19:40 +04:00
get_ldbs, findprovisionrange,
2013-03-22 09:19:27 +04:00
usn_in_range, identic_rename,
2010-06-10 01:00:43 +04:00
update_secrets, CHANGE, ERROR, SIMPLE,
CHANGEALL, GUESS, CHANGESD, PROVISION,
updateOEMInfo, getOEMInfo, update_gpo,
2010-07-04 16:38:54 +04:00
delta_update_basesamdb, update_policyids,
2010-07-05 01:00:13 +04:00
update_machine_account_password,
search_constructed_attrs_stored,
2010-10-26 16:37:50 +04:00
int64range2str, update_dns_account_password,
2012-03-17 11:19:40 +04:00
increment_calculated_keyversion_number,
print_provision_ranges)
2012-02-27 03:50:00 +04:00
from samba.xattr import copytree_with_xattrs
2018-10-15 12:36:19 +03:00
from samba.compat import cmp_to_key_fn
2009-10-27 15:31:40 +03:00
2012-09-25 22:49:22 +04:00
# make sure the script dies immediately when hitting control-C,
# rather than raising KeyboardInterrupt. As we do all database
# operations using transactions, this is safe.
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
2010-05-02 19:56:03 +04:00
replace=2**FLAG_MOD_REPLACE
add=2**FLAG_MOD_ADD
delete=2**FLAG_MOD_DELETE
2010-01-19 01:53:01 +03:00
never=0
2010-05-02 19:56:03 +04:00
2010-06-07 16:27:48 +04:00
# Will be modified during provision to tell if default sd has been modified
# somehow ...
2009-10-27 15:31:40 +03:00
2009-11-25 16:26:35 +03:00
#Errors are always logged
2009-10-27 15:31:40 +03:00
2010-02-21 21:29:36 +03:00
__docformat__ = "restructuredText"
2010-01-10 22:08:50 +03:00
# Attributes that are never copied from the reference provision (even if they
# do not exist in the destination object).
# This is most probably because they are populated automatcally when object is
# created
2010-01-12 15:43:39 +03:00
# This also apply to imported object from reference provision
2011-06-13 18:49:23 +04:00
replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
2016-11-21 05:06:22 +03:00
"parentGUID", "distinguishedName",
2011-07-05 21:56:30 +04:00
"instanceType", "cn",
2011-06-13 18:49:23 +04:00
"lmPwdHistory", "pwdLastSet", "ntPwdHistory",
"unicodePwd", "dBCSPwd", "supplementalCredentials",
"gPCUserExtensionNames", "gPCMachineExtensionNames",
"maxPwdAge", "secret", "possibleInferiors", "privilege",
"sAMAccountType", "oEMInformation", "creationTime" ]
nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
2011-06-21 13:37:26 +04:00
"nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
2011-06-13 18:49:23 +04:00
2011-06-14 01:39:41 +04:00
nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
2011-06-13 18:49:23 +04:00
attrNotCopied = replAttrNotCopied
attrNotCopied.extend(nonreplAttrNotCopied)
2011-06-14 01:39:41 +04:00
attrNotCopied.extend(nonDSDBAttrNotCopied)
2010-01-10 22:08:50 +03:00
# Usually for an object that already exists we do not overwrite attributes as
# they might have been changed for good reasons. Anyway for a few of them it's
# mandatory to replace them otherwise the provision will be broken somehow.
2010-06-07 16:27:48 +04:00
# But for attribute that are just missing we do not have to specify them as the default
# behavior is to add missing attribute
2010-06-07 23:47:43 +04:00
hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
"systemOnly":replace, "searchFlags":replace,
"mayContain":replace, "systemFlags":replace+add,
"description":replace, "operatingSystemVersion":replace,
"adminPropertyPages":replace, "groupType":replace,
"wellKnownObjects":replace, "privilege":never,
"rIDAvailablePool": never,
2011-06-19 01:17:27 +04:00
"versionNumber" : add,
2010-08-12 17:28:28 +04:00
"rIDNextRID": add, "rIDUsedPool": never,
2010-07-11 17:27:13 +04:00
"defaultSecurityDescriptor": replace + add,
"isMemberOfPartialAttributeSet": delete,
2010-09-07 17:50:39 +04:00
"attributeDisplayNames": replace + add,
"versionNumber": add}
2010-01-12 15:43:39 +03:00
2013-02-18 08:15:52 +04:00
dnNotToRecalculateFound = False
2011-06-14 01:42:59 +04:00
dnToRecalculate = []
2009-10-27 15:31:40 +03:00
backlinked = []
2010-06-20 04:32:23 +04:00
forwardlinked = set()
2010-01-19 01:53:01 +03:00
dn_syntax_att = []
2011-06-13 18:34:49 +04:00
not_replicated = []
2009-10-27 15:31:40 +03:00
def define_what_to_log(opts):
2010-03-01 05:29:47 +03:00
what = 0
if opts.debugchange:
what = what | CHANGE
if opts.debugchangesd:
what = what | CHANGESD
if opts.debugguess:
what = what | GUESS
if opts.debugprovision:
what = what | PROVISION
if opts.debugall:
what = what | CHANGEALL
return what
2009-10-27 15:31:40 +03:00
parser = optparse.OptionParser("provision [options]")
sambaopts = options.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(options.VersionOptions(parser))
credopts = options.CredentialsOptions(parser)
parser.add_option_group(credopts)
2009-11-25 11:42:16 +03:00
parser.add_option("--setupdir", type="string", metavar="DIR",
2010-06-07 23:47:43 +04:00
help="directory with setup files")
2009-10-27 15:31:40 +03:00
parser.add_option("--debugprovision", help="Debug provision", action="store_true")
2010-06-07 23:47:43 +04:00
parser.add_option("--debugguess", action="store_true",
2011-01-08 16:09:57 +03:00
help="Print information on which values are guessed")
2010-06-07 23:47:43 +04:00
parser.add_option("--debugchange", action="store_true",
help="Print information on what is different but won't be changed")
parser.add_option("--debugchangesd", action="store_true",
2011-01-05 04:36:16 +03:00
help="Print security descriptor differences")
2010-06-07 23:47:43 +04:00
parser.add_option("--debugall", action="store_true",
help="Print all available information (very verbose)")
2012-03-07 09:44:45 +04:00
parser.add_option("--db_backup_only", action="store_true",
help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
2010-06-07 23:47:43 +04:00
parser.add_option("--full", action="store_true",
help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
2013-02-16 14:58:57 +04:00
parser.add_option("--very-old-pre-alpha9", action="store_true",
help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
2009-10-27 15:31:40 +03:00
opts = parser.parse_args()[0]
2010-06-13 17:32:41 +04:00
handler = logging.StreamHandler(sys.stdout)
upgrade_logger = logging.getLogger("upgradeprovision")
2010-06-21 11:24:18 +04:00
upgrade_logger.setLevel(logging.INFO)
2010-06-13 17:32:41 +04:00
upgrade_logger.addHandler(handler)
2009-10-27 15:31:40 +03:00
2010-06-13 17:32:41 +04:00
provision_logger = logging.getLogger("provision")
provision_logger.addHandler(handler)
2010-02-21 21:29:36 +03:00
2010-06-13 17:32:41 +04:00
whatToLog = define_what_to_log(opts)
2009-10-27 15:31:40 +03:00
2010-06-13 17:32:41 +04:00
def message(what, text):
2010-03-01 05:29:47 +03:00
"""Print a message if this message type has been selected to be printed
2010-02-21 21:29:36 +03:00
2010-03-01 05:29:47 +03:00
:param what: Category of the message
:param text: Message to print """
2010-03-01 05:41:52 +03:00
if (whatToLog & what) or what <= 0:
2010-06-13 17:32:41 +04:00
upgrade_logger.info("%s", text)
2009-10-27 15:31:40 +03:00
if len(sys.argv) == 1:
2010-03-01 05:29:47 +03:00
opts.interactive = True
2009-10-27 15:31:40 +03:00
lp = sambaopts.get_loadparm()
smbconf = lp.configfile
creds = credopts.get_credentials(lp)
creds.set_kerberos_state(DONT_USE_KERBEROS)
2010-06-07 16:27:48 +04:00
2017-08-10 16:37:54 +03:00
def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
2010-04-09 02:55:38 +04:00
"""Check if the provision has already the requirement for dynamic dns
:param refprivate: The path to the private directory of the reference
provision
:param private: The path to the private directory of the upgraded
provision"""
spnfile = "%s/spn_update_list" % private
2010-08-14 20:44:35 +04:00
dnsfile = "%s/dns_update_list" % private
2010-04-09 02:55:38 +04:00
if not os.path.exists(spnfile):
shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
2010-08-14 20:44:35 +04:00
if not os.path.exists(dnsfile):
shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
2018-06-25 05:00:59 +03:00
if not os.path.exists(binddns_dir):
os.mkdir(binddns_dir)
2013-01-25 12:36:47 +04:00
if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
return
2010-04-09 02:55:38 +04:00
2013-01-25 12:36:47 +04:00
namedfile = lp.get("dnsupdate:path")
if not namedfile:
2017-08-10 16:37:54 +03:00
namedfile = "%s/named.conf.update" % binddns_dir
2010-04-09 02:55:38 +04:00
if not os.path.exists(namedfile):
2017-08-10 16:37:54 +03:00
destdir = "%s/new_dns" % binddns_dir
dnsdir = "%s/dns" % binddns_dir
2013-01-25 12:36:47 +04:00
2010-04-09 02:55:38 +04:00
if not os.path.exists(destdir):
os.mkdir(destdir)
if not os.path.exists(dnsdir):
os.mkdir(dnsdir)
2017-08-10 16:37:54 +03:00
shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
2011-01-05 04:36:16 +03:00
message(SIMPLE, "It seems that your provision did not integrate "
"new rules for dynamic dns update of domain related entries")
2010-04-09 02:55:38 +04:00
message(SIMPLE, "A copy of the new bind configuration files and "
2011-01-05 04:36:16 +03:00
"template has been put in %s, you should read them and "
"configure dynamic dns updates" % destdir)
2010-04-09 02:55:38 +04:00
2010-06-07 16:27:48 +04:00
def populate_links(samdb, schemadn):
2010-03-01 05:29:47 +03:00
"""Populate an array with all the back linked attributes
2010-02-21 21:29:36 +03:00
2010-03-01 05:29:47 +03:00
This attributes that are modified automaticaly when
front attibutes are changed
2010-02-21 21:29:36 +03:00
2010-05-02 19:56:03 +04:00
:param samdb: A LDB object for sam.ldb file
2010-03-01 05:29:47 +03:00
:param schemadn: DN of the schema for the partition"""
2010-06-07 16:27:48 +04:00
linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
2010-03-01 05:29:47 +03:00
backlinked.extend(linkedAttHash.values())
2010-06-07 16:27:48 +04:00
for t in linkedAttHash.keys():
2010-06-20 04:32:23 +04:00
forwardlinked.add(t)
2011-06-13 18:34:49 +04:00
def isReplicated(att):
""" Indicate if the attribute is replicated or not
:param att: Name of the attribute to be tested
:return: True is the attribute is replicated, False otherwise
"""
return (att not in not_replicated)
def populateNotReplicated(samdb, schemadn):
"""Populate an array with all the attributes that are not replicated
:param samdb: A LDB object for sam.ldb file
:param schemadn: DN of the schema for the partition"""
res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
str(schemadn)), scope=SCOPE_SUBTREE,
attrs=["lDAPDisplayName"])
for elem in res:
2011-06-21 13:37:26 +04:00
not_replicated.append(str(elem["lDAPDisplayName"]))
2010-01-19 01:53:01 +03:00
2010-05-02 19:56:03 +04:00
def populate_dnsyntax(samdb, schemadn):
"""Populate an array with all the attributes that have DN synthax
(oid 2.5.5.1)
2010-03-01 05:29:47 +03:00
2010-05-02 19:56:03 +04:00
:param samdb: A LDB object for sam.ldb file
2010-03-01 05:29:47 +03:00
:param schemadn: DN of the schema for the partition"""
2010-05-02 19:56:03 +04:00
res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
str(schemadn)), scope=SCOPE_SUBTREE,
attrs=["lDAPDisplayName"])
2010-03-01 05:29:47 +03:00
for elem in res:
dn_syntax_att.append(elem["lDAPDisplayName"])
2009-10-27 15:31:40 +03:00
2010-03-01 05:41:52 +03:00
2010-05-02 19:56:03 +04:00
def sanitychecks(samdb, names):
"""Make some checks before trying to update
2010-03-01 05:29:47 +03:00
2010-05-02 19:56:03 +04:00
:param samdb: An LDB object opened on sam.ldb
2010-03-01 05:29:47 +03:00
:param names: list of key provision parameters
:return: Status of check (1 for Ok, 0 for not Ok) """
2010-05-02 19:56:03 +04:00
res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
2010-03-01 05:29:47 +03:00
if len(res) == 0:
2018-09-27 20:15:49 +03:00
print("No DC found. Your provision is most probably broken!")
2010-03-01 05:41:52 +03:00
return False
2010-03-01 05:29:47 +03:00
elif len(res) != 1:
2018-09-27 20:15:49 +03:00
print("Found %d domain controllers. For the moment " \
2011-01-05 04:36:16 +03:00
"upgradeprovision is not able to handle an upgrade on a " \
2011-01-08 16:08:47 +03:00
"domain with more than one DC. Please demote the other " \
2018-09-27 20:15:49 +03:00
"DC(s) before upgrading") % len(res)
2010-03-01 05:41:52 +03:00
return False
2010-03-01 05:29:47 +03:00
else:
2010-03-01 05:41:52 +03:00
return True
2010-01-15 14:09:06 +03:00
2010-01-31 22:06:01 +03:00
def print_provision_key_parameters(names):
2010-03-01 05:29:47 +03:00
"""Do a a pretty print of provision parameters
:param names: list of key provision parameters """
2010-06-08 00:21:48 +04:00
message(GUESS, "rootdn :" + str(names.rootdn))
message(GUESS, "configdn :" + str(names.configdn))
message(GUESS, "schemadn :" + str(names.schemadn))
message(GUESS, "serverdn :" + str(names.serverdn))
message(GUESS, "netbiosname :" + names.netbiosname)
message(GUESS, "defaultsite :" + names.sitename)
message(GUESS, "dnsdomain :" + names.dnsdomain)
message(GUESS, "hostname :" + names.hostname)
message(GUESS, "domain :" + names.domain)
message(GUESS, "realm :" + names.realm)
message(GUESS, "invocationid:" + names.invocation)
message(GUESS, "policyguid :" + names.policyid)
message(GUESS, "policyguiddc:" + str(names.policyid_dc))
message(GUESS, "domainsid :" + str(names.domainsid))
message(GUESS, "domainguid :" + names.domainguid)
message(GUESS, "ntdsguid :" + names.ntdsguid)
message(GUESS, "domainlevel :" + str(names.domainlevel))
2009-10-27 15:31:40 +03:00
2010-03-01 05:41:52 +03:00
2011-06-13 17:13:26 +04:00
def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
2010-03-01 05:29:47 +03:00
"""Define more complicate update rules for some attributes
:param att: The attribute to be updated
2010-06-07 16:27:48 +04:00
:param delta: A messageElement object that correspond to the difference
between the updated object and the reference one
2010-03-01 05:29:47 +03:00
:param new: The reference object
:param old: The Updated object
2011-06-13 17:13:26 +04:00
:param useReplMetadata: A boolean that indicate if the update process
use replPropertyMetaData to decide what has to be updated.
2010-07-11 17:27:13 +04:00
:param basedn: The base DN of the provision
:param aldb: An ldb object used to build DN
2010-06-07 16:27:48 +04:00
:return: True to indicate that the attribute should be kept, False for
discarding it"""
# We do most of the special case handle if we do not have the
# highest usn as otherwise the replPropertyMetaData will guide us more
# correctly
2011-06-13 17:13:26 +04:00
if not useReplMetadata:
2012-01-03 07:27:48 +04:00
flag = delta.get(att).flags()
2010-07-11 17:27:13 +04:00
if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
"CN=Services,CN=Configuration,%s" % basedn)
== old[0].dn):
return True
if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
== old[0].dn):
message(SIMPLE, "We suggest that you change the userAccountControl"
" for user Administrator from value %d to %d" %
(int(str(old[0][att])), int(str(new[0][att]))))
return False
if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
2018-10-10 07:36:50 +03:00
if (int(str(old[0][att])) == 0):
2010-07-11 17:27:13 +04:00
delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
return True
2010-06-07 16:27:48 +04:00
if (att == "member" and flag == FLAG_MOD_REPLACE):
hash = {}
newval = []
changeDelta=0
for elem in old[0][att]:
2010-06-08 00:01:16 +04:00
hash[str(elem).lower()]=1
2010-06-07 16:27:48 +04:00
newval.append(str(elem))
2010-03-01 05:29:47 +03:00
2010-06-07 16:27:48 +04:00
for elem in new[0][att]:
2018-08-27 15:08:26 +03:00
if not str(elem).lower() in hash:
2010-06-07 16:27:48 +04:00
changeDelta=1
newval.append(str(elem))
if changeDelta == 1:
delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
else:
delta.remove(att)
return True
2010-03-01 05:29:47 +03:00
2010-07-03 16:53:44 +04:00
if (att in ("gPLink", "gPCFileSysPath") and
2010-06-20 03:56:52 +04:00
flag == FLAG_MOD_REPLACE and
str(new[0].dn).lower() == str(old[0].dn).lower()):
2010-06-07 16:27:48 +04:00
delta.remove(att)
return True
if att == "forceLogoff":
ref=0x8000000000000000
oldval=int(old[0][att][0])
newval=int(new[0][att][0])
ref == old and ref == abs(new)
return True
2010-06-20 03:56:52 +04:00
if att in ("adminDisplayName", "adminDescription"):
2010-06-07 16:27:48 +04:00
return True
2010-03-01 05:29:47 +03:00
2010-06-20 03:56:52 +04:00
if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
2010-06-07 16:27:48 +04:00
and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
return True
if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
return True
if (str(old[0].dn) == "%s" % (str(names.rootdn))
and att == "subRefs" and flag == FLAG_MOD_REPLACE):
return True
2010-10-23 21:57:16 +04:00
#Allow to change revision of ForestUpdates objects
if (att == "revision" or att == "objectVersion"):
if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
return True
2010-06-07 16:27:48 +04:00
if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
return True
# This is a bit of special animal as we might have added
# already SPN entries to the list that has to be modified
# So we go in detail to try to find out what has to be added ...
2012-01-03 07:27:48 +04:00
if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
2010-03-01 05:29:47 +03:00
hash = {}
newval = []
2011-06-13 17:56:17 +04:00
changeDelta = 0
2010-03-01 05:29:47 +03:00
for elem in old[0][att]:
hash[str(elem)]=1
newval.append(str(elem))
for elem in new[0][att]:
2018-08-27 15:08:26 +03:00
if not str(elem) in hash:
2011-06-13 17:56:17 +04:00
changeDelta = 1
2010-03-01 05:29:47 +03:00
newval.append(str(elem))
if changeDelta == 1:
delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
else:
delta.remove(att)
2010-03-01 05:41:52 +03:00
return True
2010-03-01 05:29:47 +03:00
2010-03-01 05:41:52 +03:00
return False
2009-10-27 15:31:40 +03:00
2010-06-08 00:21:48 +04:00
def dump_denied_change(dn, att, flagtxt, current, reference):
2011-01-05 04:36:16 +03:00
"""Print detailed information about why a change is denied
2010-03-01 05:29:47 +03:00
:param dn: DN of the object which attribute is denied
:param att: Attribute that was supposed to be upgraded
2010-06-08 00:21:48 +04:00
:param flagtxt: Type of the update that should be performed
(add, change, remove, ...)
2010-03-01 05:29:47 +03:00
:param current: Value(s) of the current attribute
:param reference: Value(s) of the reference attribute"""
2010-06-08 00:21:48 +04:00
message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
2011-01-05 04:36:16 +03:00
+ " must not be changed/removed. Discarding the change")
2010-08-12 17:28:28 +04:00
if att == "objectSid" :
message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
message(CHANGE, "old : %s" % int64range2str(current[0]))
message(CHANGE, "new : %s" % int64range2str(reference[0]))
else:
2010-03-01 05:29:47 +03:00
i = 0
2010-06-08 00:21:48 +04:00
for e in range(0, len(current)):
message(CHANGE, "old %d : %s" % (i, str(current[e])))
2010-03-01 05:41:52 +03:00
i+=1
2010-06-20 03:56:52 +04:00
if reference is not None:
2010-03-01 05:29:47 +03:00
i = 0
2010-06-08 00:21:48 +04:00
for e in range(0, len(reference)):
message(CHANGE, "new %d : %s" % (i, str(reference[e])))
2010-03-01 05:41:52 +03:00
i+=1
2010-06-08 00:21:48 +04:00
def handle_special_add(samdb, dn, names):
"""Handle special operation (like remove) on some object needed during
2010-06-20 03:56:52 +04:00
upgrade
2010-03-01 05:29:47 +03:00
This is mostly due to wrong creation of the object in previous provision.
2010-05-02 19:56:03 +04:00
:param samdb: An Ldb object representing the SAM database
2010-03-01 05:29:47 +03:00
:param dn: DN of the object to inspect
2010-06-20 03:56:52 +04:00
:param names: list of key provision parameters
"""
2010-06-08 00:21:48 +04:00
2010-03-01 05:41:52 +03:00
dntoremove = None
2010-06-08 00:01:16 +04:00
objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
if dn == objDn :
2010-05-02 19:56:03 +04:00
#This entry was misplaced lets remove it if it exists
2010-06-08 00:21:48 +04:00
dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
2010-05-02 19:56:03 +04:00
2010-06-08 00:01:16 +04:00
objDn = Dn(samdb,
"CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
if dn == objDn:
2010-03-01 05:29:47 +03:00
#This entry was misplaced lets remove it if it exists
2010-06-08 00:21:48 +04:00
dntoremove = "CN=Certificate Service DCOM Access,"\
"CN=Users, %s" % names.rootdn
2010-03-01 05:29:47 +03:00
2010-06-08 00:01:16 +04:00
objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
if dn == objDn:
2010-03-01 05:29:47 +03:00
#This entry was misplaced lets remove it if it exists
2010-06-08 00:21:48 +04:00
dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
2010-03-01 05:29:47 +03:00
2010-06-08 00:01:16 +04:00
objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
if dn == objDn:
2010-03-01 05:29:47 +03:00
#This entry was misplaced lets remove it if it exists
2010-06-08 00:21:48 +04:00
dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
2010-03-01 05:29:47 +03:00
2010-06-20 03:56:52 +04:00
objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
2010-06-08 00:01:16 +04:00
"CN=Configuration,%s" % names.rootdn)
if dn == objDn:
2010-06-20 03:56:52 +04:00
oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
"CN=WellKnown Security Principals,"
2010-06-08 00:01:16 +04:00
"CN=Configuration,%s" % names.rootdn)
2011-10-25 22:10:30 +04:00
res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
2010-06-08 00:01:16 +04:00
base=str(names.rootdn),
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
2010-09-05 02:59:20 +04:00
2011-10-25 22:10:30 +04:00
res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
2010-09-05 02:59:20 +04:00
base=str(names.rootdn),
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
if len(res) > 0 and len(res2) == 0:
2011-01-05 04:36:16 +03:00
message(CHANGE, "Existing object %s must be replaced by %s. "
2010-06-08 00:01:16 +04:00
"Renaming old object" % (str(oldDn), str(dn)))
2010-11-13 14:33:26 +03:00
samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
2010-06-08 00:01:16 +04:00
2010-09-05 02:59:20 +04:00
return 0
2010-06-08 00:01:16 +04:00
2010-06-20 03:56:52 +04:00
if dntoremove is not None:
2010-09-05 02:59:20 +04:00
res = samdb.search(expression="(cn=RID Set)",
base=str(names.rootdn),
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
if len(res) == 0:
return 2
2011-10-25 22:10:30 +04:00
res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
2010-06-08 00:21:48 +04:00
base=str(names.rootdn),
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
2010-03-01 05:29:47 +03:00
if len(res) > 0:
2011-01-05 04:36:16 +03:00
message(CHANGE, "Existing object %s must be replaced by %s. "
"Removing old object" % (dntoremove, str(dn)))
2010-05-02 19:56:03 +04:00
samdb.delete(res[0]["dn"])
2010-09-05 02:59:20 +04:00
return 0
return 1
2010-04-08 20:57:09 +04:00
2010-06-20 03:56:52 +04:00
2010-04-08 20:57:09 +04:00
def check_dn_nottobecreated(hash, index, listdn):
2010-06-08 00:21:48 +04:00
"""Check if one of the DN present in the list has a creation order
greater than the current.
Hash is indexed by dn to be created, with each key
is associated the creation order.
2010-03-01 05:29:47 +03:00
First dn to be created has the creation order 0, second has 1, ...
Index contain the current creation order
2010-03-01 05:41:52 +03:00
2010-06-07 16:27:48 +04:00
:param hash: Hash holding the different DN of the object to be
created as key
2010-03-01 05:29:47 +03:00
:param index: Current creation order
:param listdn: List of DNs on which the current DN depends on
2010-06-08 00:21:48 +04:00
:return: None if the current object do not depend on other
object or if all object have been created before."""
2010-06-20 03:56:52 +04:00
if listdn is None:
2010-03-01 05:29:47 +03:00
return None
for dn in listdn:
key = str(dn).lower()
2018-08-27 15:08:26 +03:00
if key in hash and hash[key] > index:
2010-03-01 05:29:47 +03:00
return str(dn)
return None
2010-01-19 01:53:01 +03:00
2010-03-01 05:41:52 +03:00
2010-06-08 00:21:48 +04:00
2010-05-02 19:56:03 +04:00
def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
2010-03-01 05:29:47 +03:00
"""Add a new object if the dependencies are satisfied
2010-06-08 00:21:48 +04:00
The function add the object if the object on which it depends are already
created
:param ref_samdb: Ldb object representing the SAM db of the reference
provision
:param samdb: Ldb object representing the SAM db of the upgraded
provision
2010-03-01 05:29:47 +03:00
:param dn: DN of the object to be added
:param names: List of key provision parameters
:param basedn: DN of the partition to be updated
2010-06-08 00:21:48 +04:00
:param hash: Hash holding the different DN of the object to be
created as key
2010-03-01 05:29:47 +03:00
:param index: Current creation order
2010-03-01 05:41:52 +03:00
:return: True if the object was created False otherwise"""
2010-06-08 00:21:48 +04:00
2010-09-05 02:59:20 +04:00
ret = handle_special_add(samdb, dn, names)
if ret == 2:
return False
if ret == 0:
return True
2011-10-25 22:10:30 +04:00
reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
base=basedn, scope=SCOPE_SUBTREE,
controls=["search_options:1:2"])
2010-03-01 05:29:47 +03:00
empty = Message()
2010-06-08 00:21:48 +04:00
delta = samdb.msg_diff(empty, reference[0])
2010-05-02 19:56:03 +04:00
delta.dn
2010-07-05 23:46:46 +04:00
skip = False
try:
if str(reference[0].get("cn")) == "RID Set":
for klass in reference[0].get("objectClass"):
2010-08-10 00:54:50 +04:00
if str(klass).lower() == "ridset":
2010-07-05 23:46:46 +04:00
skip = True
finally:
if delta.get("objectSid"):
2018-08-27 15:08:26 +03:00
sid = str(ndr_unpack(security.dom_sid, reference[0]["objectSid"][0]))
2010-07-05 23:46:46 +04:00
m = re.match(r".*-(\d+)$", sid)
if m and int(m.group(1))>999:
delta.remove("objectSid")
2011-06-13 18:49:23 +04:00
for att in attrNotCopied:
2010-07-05 23:46:46 +04:00
delta.remove(att)
for att in backlinked:
delta.remove(att)
for att in dn_syntax_att:
depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
delta.get(str(att)))
if depend_on_yet_tobecreated is not None:
2011-01-05 04:36:16 +03:00
message(CHANGE, "Object %s depends on %s in attribute %s. "
"Delaying the creation" % (dn,
2010-07-05 23:46:46 +04:00
depend_on_yet_tobecreated, att))
return False
delta.dn = dn
if not skip:
message(CHANGE,"Object %s will be added" % dn)
2010-11-13 14:33:26 +03:00
samdb.add(delta, ["relax:0", "provision:0"])
2010-07-05 23:46:46 +04:00
else:
message(CHANGE,"Object %s was skipped" % dn)
return True
2010-03-01 05:41:52 +03:00
2010-01-19 01:53:01 +03:00
def gen_dn_index_hash(listMissing):
2010-03-01 05:29:47 +03:00
"""Generate a hash associating the DN to its creation order
2010-02-21 21:29:36 +03:00
2010-03-01 05:29:47 +03:00
:param listMissing: List of DN
:return: Hash with DN as keys and creation order as values"""
hash = {}
2010-06-08 00:21:00 +04:00
for i in range(0, len(listMissing)):
2010-03-01 05:29:47 +03:00
hash[str(listMissing[i]).lower()] = i
return hash
2010-01-19 01:53:01 +03:00
2010-05-02 19:56:03 +04:00
def add_deletedobj_containers(ref_samdb, samdb, names):
"""Add the object containter: CN=Deleted Objects
2010-06-08 00:21:48 +04:00
This function create the container for each partition that need one and
then reference the object into the root of the partition
:param ref_samdb: Ldb object representing the SAM db of the reference
provision
2010-05-02 19:56:03 +04:00
:param samdb: Ldb object representing the SAM db of the upgraded provision
:param names: List of key provision parameters"""
2010-06-08 00:21:48 +04:00
wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
partitions = [str(names.rootdn), str(names.configdn)]
2010-05-02 19:56:03 +04:00
for part in partitions:
2010-06-08 00:21:48 +04:00
ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
base=part, scope=SCOPE_SUBTREE,
attrs=["dn"],
2010-09-20 00:36:12 +04:00
controls=["show_deleted:0",
"show_recycled:0"])
2010-06-08 00:21:48 +04:00
delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
base=part, scope=SCOPE_SUBTREE,
attrs=["dn"],
2010-09-20 00:36:12 +04:00
controls=["show_deleted:0",
"show_recycled:0"])
2010-05-02 19:56:03 +04:00
if len(ref_delObjCnt) > len(delObjCnt):
2010-06-08 00:21:48 +04:00
reference = ref_samdb.search(expression="cn=Deleted Objects",
base=part, scope=SCOPE_SUBTREE,
2010-09-20 00:36:12 +04:00
controls=["show_deleted:0",
"show_recycled:0"])
2010-05-02 19:56:03 +04:00
empty = Message()
2010-06-08 00:21:48 +04:00
delta = samdb.msg_diff(empty, reference[0])
2010-05-02 19:56:03 +04:00
2010-06-08 00:21:48 +04:00
delta.dn = Dn(samdb, str(reference[0]["dn"]))
2011-06-13 18:49:23 +04:00
for att in attrNotCopied:
2010-05-02 19:56:03 +04:00
delta.remove(att)
2010-11-12 20:00:57 +03:00
2010-11-13 14:33:26 +03:00
modcontrols = ["relax:0", "provision:0"]
2010-11-12 20:00:57 +03:00
samdb.add(delta, modcontrols)
2010-05-02 19:56:03 +04:00
listwko = []
2010-06-08 00:21:48 +04:00
res = samdb.search(expression="(objectClass=*)", base=part,
scope=SCOPE_BASE,
attrs=["dn", "wellKnownObjects"])
2010-05-02 19:56:03 +04:00
2010-06-08 00:21:48 +04:00
targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
2010-06-20 03:56:52 +04:00
found = False
2010-05-02 19:56:03 +04:00
if len(res[0]) > 0:
wko = res[0]["wellKnownObjects"]
# The wellKnownObject that we want to add.
for o in wko:
if str(o) == targetWKO:
2010-06-20 03:56:52 +04:00
found = True
2010-05-02 19:56:03 +04:00
listwko.append(str(o))
2010-06-08 00:21:48 +04:00
2010-05-02 19:56:03 +04:00
if not found:
listwko.append(targetWKO)
delta = Message()
2010-06-08 00:21:48 +04:00
delta.dn = Dn(samdb, str(res[0]["dn"]))
delta["wellKnownObjects"] = MessageElement(listwko,
FLAG_MOD_REPLACE,
"wellKnownObjects" )
2010-05-02 19:56:03 +04:00
samdb.modify(delta)
2010-03-01 05:41:52 +03:00
2010-05-02 19:56:03 +04:00
def add_missing_entries(ref_samdb, samdb, names, basedn, list):
2010-03-01 05:29:47 +03:00
"""Add the missing object whose DN is the list
2010-06-08 00:21:48 +04:00
The function add the object if the objects on which it depends are
already created.
:param ref_samdb: Ldb object representing the SAM db of the reference
provision
:param samdb: Ldb object representing the SAM db of the upgraded
provision
2010-03-01 05:29:47 +03:00
:param dn: DN of the object to be added
:param names: List of key provision parameters
:param basedn: DN of the partition to be updated
:param list: List of DN to be added in the upgraded provision"""
2010-06-08 00:21:48 +04:00
2010-03-01 05:29:47 +03:00
listMissing = []
listDefered = list
while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
index = 0
listMissing = listDefered
listDefered = []
hashMissing = gen_dn_index_hash(listMissing)
for dn in listMissing:
2010-06-08 00:21:48 +04:00
ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
hashMissing, index)
2010-03-01 05:29:47 +03:00
index = index + 1
if ret == 0:
2010-06-08 00:21:48 +04:00
# DN can't be created because it depends on some
# other DN in the list
2010-03-01 05:29:47 +03:00
listDefered.append(dn)
2010-09-05 02:58:31 +04:00
2010-03-01 05:29:47 +03:00
if len(listDefered) != 0:
2011-01-05 04:36:16 +03:00
raise ProvisioningError("Unable to insert missing elements: "
2010-06-08 00:21:48 +04:00
"circular references")
2010-01-19 01:53:01 +03:00
2010-06-07 16:27:48 +04:00
def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
"""This function handle updates on links
:param samdb: An LDB object pointing to the updated provision
:param att: Attribute to update
:param basedn: The root DN of the provision
:param dn: The DN of the inspected object
:param value: The value of the attribute
:param ref_value: The value of this attribute in the reference provision
:param delta: The MessageElement object that will be applied for
transforming the current provision"""
2012-01-03 07:27:48 +04:00
res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
2010-06-07 16:27:48 +04:00
attrs=[att])
blacklist = {}
hash = {}
newlinklist = []
2010-06-20 03:56:52 +04:00
changed = False
2010-06-07 16:27:48 +04:00
2012-01-03 07:27:48 +04:00
for v in value:
newlinklist.append(str(v))
2010-06-07 16:27:48 +04:00
for e in value:
hash[e] = 1
# for w2k domain level the reveal won't reveal anything ...
# it means that we can readd links that were removed on purpose ...
# Also this function in fact just accept add not removal
for e in res[0][att]:
2018-08-27 15:08:26 +03:00
if not e in hash:
2010-06-07 16:27:48 +04:00
# We put in the blacklist all the element that are in the "revealed"
# result and not in the "standard" result
# This element are links that were removed before and so that
# we don't wan't to readd
blacklist[e] = 1
for e in ref_value:
2018-08-27 15:08:26 +03:00
if not e in blacklist and not e in hash:
2010-06-07 16:27:48 +04:00
newlinklist.append(str(e))
2010-06-20 03:56:52 +04:00
changed = True
2010-06-07 16:27:48 +04:00
if changed:
delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
else:
delta.remove(att)
2012-01-03 07:27:48 +04:00
return delta
2010-06-20 03:26:23 +04:00
2011-06-13 17:50:00 +04:00
def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
hash_attr_usn, basedn, usns, samdb):
""" Check if we should keep the attribute modification or not
:param delta: A message diff object
:param att: An attribute
:param message: A function to print messages
:param reference: A message object for the current entry comming from
the reference provision.
:param current: A message object for the current entry commin from
the current provision.
:param hash_attr_usn: A dictionnary with attribute name as keys,
USN and invocation id as values.
:param basedn: The DN of the partition
:param usns: A dictionnary with invocation ID as keys and USN ranges
as values.
:param samdb: A ldb object pointing to the sam DB
:return: The modified message diff.
"""
global defSDmodified
2011-06-19 00:02:03 +04:00
isFirst = True
2011-06-13 17:50:00 +04:00
txt = ""
dn = current[0].dn
for att in list(delta):
2011-06-14 01:41:56 +04:00
if att in ["dn", "objectSid"]:
delta.remove(att)
2011-06-13 22:59:35 +04:00
continue
2011-06-14 01:41:56 +04:00
2011-06-13 17:50:00 +04:00
# We have updated by provision usn information so let's exploit
# replMetadataProperties
if att in forwardlinked:
curval = current[0].get(att, ())
refval = reference[0].get(att, ())
2012-01-03 07:27:48 +04:00
delta = handle_links(samdb, att, basedn, current[0]["dn"],
2011-06-13 17:50:00 +04:00
curval, refval, delta)
continue
2012-01-03 07:27:48 +04:00
if isFirst and len(list(delta)) > 1:
2011-06-19 00:02:03 +04:00
isFirst = False
2011-06-13 17:50:00 +04:00
txt = "%s\n" % (str(dn))
if handle_special_case(att, delta, reference, current, True, None, None):
# This attribute is "complicated" to handle and handling
# was done in handle_special_case
continue
attrUSN = None
if hash_attr_usn.get(att):
2011-06-13 17:39:06 +04:00
[attrUSN, attInvId] = hash_attr_usn.get(att)
if attrUSN is None:
# If it's a replicated attribute and we don't have any USN
# information about it. It means that we never saw it before
# so let's add it !
# If it is a replicated attribute but we are not master on it
# (ie. not initially added in the provision we masterize).
# attrUSN will be -1
if isReplicated(att):
continue
else:
2011-06-13 18:49:23 +04:00
message(CHANGE, "Non replicated attribute %s changed" % att)
2011-06-13 17:39:06 +04:00
continue
2011-06-13 17:50:00 +04:00
if att == "nTSecurityDescriptor":
cursd = ndr_unpack(security.descriptor,
2018-08-27 15:08:26 +03:00
current[0]["nTSecurityDescriptor"][0])
2011-06-13 17:50:00 +04:00
refsd = ndr_unpack(security.descriptor,
2018-08-27 15:08:26 +03:00
reference[0]["nTSecurityDescriptor"][0])
2011-06-13 17:50:00 +04:00
2013-02-17 15:03:18 +04:00
diff = get_diff_sds(refsd, cursd, names.domainsid)
2011-06-14 01:42:59 +04:00
if diff == "":
# FIXME find a way to have it only with huge huge verbose mode
# message(CHANGE, "%ssd are identical" % txt)
# txt = ""
delta.remove(att)
continue
2011-06-13 17:50:00 +04:00
else:
2011-06-14 01:42:59 +04:00
delta.remove(att)
message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
txt = ""
if attrUSN == -1:
2012-09-27 20:30:47 +04:00
message(CHANGESD, "But the SD has been changed by someonelse "
"so it's impossible to know if the difference"
2011-06-14 01:42:59 +04:00
" cames from the modification or from a previous bug")
2018-10-10 07:51:54 +03:00
global dnNotToRecalculateFound
2013-02-18 08:15:52 +04:00
dnNotToRecalculateFound = True
2011-06-14 01:42:59 +04:00
else:
2013-02-18 08:56:18 +04:00
dnToRecalculate.append(dn)
2011-06-14 01:42:59 +04:00
continue
2011-06-19 00:02:03 +04:00
2011-06-13 17:50:00 +04:00
if attrUSN == -1:
# This attribute was last modified by another DC forget
# about it
message(CHANGE, "%sAttribute: %s has been "
"created/modified/deleted by another DC. "
"Doing nothing" % (txt, att))
txt = ""
delta.remove(att)
continue
elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
message(CHANGE, "%sAttribute: %s was not "
"created/modified/deleted during a "
"provision or upgradeprovision. Current "
"usn: %d. Doing nothing" % (txt, att,
attrUSN))
txt = ""
delta.remove(att)
continue
else:
if att == "defaultSecurityDescriptor":
defSDmodified = True
if attrUSN:
message(CHANGE, "%sAttribute: %s will be modified"
"/deleted it was last modified "
"during a provision. Current usn: "
"%d" % (txt, att, attrUSN))
txt = ""
else:
message(CHANGE, "%sAttribute: %s will be added because "
"it did not exist before" % (txt, att))
txt = ""
continue
return delta
2010-06-20 03:26:23 +04:00
2011-06-13 17:39:06 +04:00
def update_present(ref_samdb, samdb, basedn, listPresent, usns):
2010-06-07 16:27:48 +04:00
""" This function updates the object that are already present in the
provision
:param ref_samdb: An LDB object pointing to the reference provision
:param samdb: An LDB object pointing to the updated provision
:param basedn: A string with the value of the base DN for the provision
(ie. DC=foo, DC=bar)
:param listPresent: A list of object that is present in the provision
:param usns: A list of USN range modified by previous provision and
2011-06-13 17:39:06 +04:00
upgradeprovision grouped by invocation ID
"""
2010-06-07 16:27:48 +04:00
# This hash is meant to speedup lookup of attribute name from an oid,
# it's for the replPropertyMetaData handling
hash_oid_name = {}
res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
controls=["search_options:1:2"], attrs=["attributeID",
"lDAPDisplayName"])
if len(res) > 0:
for e in res:
strDisplay = str(e.get("lDAPDisplayName"))
hash_oid_name[str(e.get("attributeID"))] = strDisplay
else:
msg = "Unable to insert missing elements: circular references"
raise ProvisioningError(msg)
changed = 0
2012-11-22 19:22:30 +04:00
sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
2013-02-18 05:28:23 +04:00
message(CHANGE, "Using replPropertyMetadata for change selection")
2010-06-07 16:27:48 +04:00
for dn in listPresent:
2011-10-25 22:10:30 +04:00
reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
2010-06-07 16:27:48 +04:00
scope=SCOPE_SUBTREE,
controls=controls)
2011-10-25 22:10:30 +04:00
current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
2010-06-07 16:27:48 +04:00
scope=SCOPE_SUBTREE, controls=controls)
if (
(str(current[0].dn) != str(reference[0].dn)) and
(str(current[0].dn).upper() == str(reference[0].dn).upper())
):
2011-01-05 04:36:16 +03:00
message(CHANGE, "Names are the same except for the case. "
"Renaming %s to %s" % (str(current[0].dn),
str(reference[0].dn)))
2010-06-07 16:27:48 +04:00
identic_rename(samdb, reference[0].dn)
2011-10-25 22:10:30 +04:00
current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
2010-06-07 16:27:48 +04:00
scope=SCOPE_SUBTREE,
2010-09-26 04:57:04 +04:00
controls=controls)
2010-06-07 16:27:48 +04:00
delta = samdb.msg_diff(current[0], reference[0])
for att in backlinked:
delta.remove(att)
2011-06-13 18:49:23 +04:00
for att in attrNotCopied:
delta.remove(att)
2010-06-07 16:27:48 +04:00
delta.remove("name")
2012-01-03 07:27:48 +04:00
nb_items = len(list(delta))
if nb_items == 1:
2011-06-13 23:23:05 +04:00
continue
2013-02-18 05:28:23 +04:00
if nb_items > 1:
2010-06-07 16:27:48 +04:00
# Fetch the replPropertyMetaData
2011-10-25 22:10:30 +04:00
res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
2010-06-07 16:27:48 +04:00
scope=SCOPE_SUBTREE, controls=controls,
attrs=["replPropertyMetaData"])
ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2018-08-27 15:08:26 +03:00
res[0]["replPropertyMetaData"][0]).ctr
2010-06-07 16:27:48 +04:00
hash_attr_usn = {}
for o in ctr.array:
# We put in this hash only modification
# made on the current host
att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
2011-06-13 17:39:06 +04:00
if str(o.originating_invocation_id) in usns.keys():
hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
2010-06-07 16:27:48 +04:00
else:
2011-06-13 17:39:06 +04:00
hash_attr_usn[att] = [-1, None]
2010-06-07 16:27:48 +04:00
2013-02-18 05:28:23 +04:00
delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
current, hash_attr_usn,
basedn, usns, samdb)
2010-06-07 16:27:48 +04:00
delta.dn = dn
2012-01-03 07:27:48 +04:00
if len(delta) >1:
2011-06-13 17:15:37 +04:00
# Skip dn as the value is not really changed ...
attributes=", ".join(delta.keys()[1:])
2010-10-23 22:01:30 +04:00
modcontrols = []
relaxedatt = ['iscriticalsystemobject', 'grouptype']
# Let's try to reduce as much as possible the use of relax control
for attr in delta.keys():
if attr.lower() in relaxedatt:
2010-11-13 14:33:26 +03:00
modcontrols = ["relax:0", "provision:0"]
2010-07-03 16:53:44 +04:00
message(CHANGE, "%s is different from the reference one, changed"
2010-06-07 16:27:48 +04:00
" attributes: %s\n" % (dn, attributes))
2010-06-20 03:56:52 +04:00
changed += 1
2010-10-23 22:01:30 +04:00
samdb.modify(delta, modcontrols)
2010-06-07 16:27:48 +04:00
return changed
2010-07-11 15:36:32 +04:00
def reload_full_schema(samdb, names):
"""Load the updated schema with all the new and existing classes
and attributes.
:param samdb: An LDB object connected to the sam.ldb of the update
provision
:param names: List of key provision parameters
"""
2011-11-11 19:34:48 +04:00
schemadn = str(names.schemadn)
current = samdb.search(expression="objectClass=*", base=schemadn,
2010-07-11 15:36:32 +04:00
scope=SCOPE_SUBTREE)
schema_ldif = ""
prefixmap_data = ""
for ent in current:
schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
2018-08-27 15:08:26 +03:00
prefixmap_data = open(setup_path("prefixMap.txt"), 'rb').read()
2018-05-04 17:25:22 +03:00
prefixmap_data = b64encode(prefixmap_data).decode('utf8')
2010-07-11 15:36:32 +04:00
# We don't actually add this ldif, just parse it
2011-11-11 19:34:48 +04:00
prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
2010-07-11 15:36:32 +04:00
2011-11-11 19:34:48 +04:00
dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
2010-07-11 15:36:32 +04:00
2010-06-20 03:26:23 +04:00
2010-08-12 12:22:08 +04:00
def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
2010-03-01 05:29:47 +03:00
"""Check differences between the reference provision and the upgraded one.
2010-06-07 16:27:48 +04:00
It looks for all objects which base DN is name.
2010-03-01 05:41:52 +03:00
2010-06-07 16:27:48 +04:00
This function will also add the missing object and update existing object
to add or remove attributes that were missing.
:param ref_sambdb: An LDB object conntected to the sam.ldb of the
reference provision
:param samdb: An LDB object connected to the sam.ldb of the update
provision
2010-05-02 19:56:03 +04:00
:param basedn: String value of the DN of the partition
2010-03-01 05:29:47 +03:00
:param names: List of key provision parameters
2010-06-07 16:27:48 +04:00
:param schema: A Schema object
2011-06-13 17:56:17 +04:00
:param provisionUSNs: A dictionnary with range of USN modified during provision
or upgradeprovision. Ranges are grouped by invocationID.
2010-08-12 12:22:08 +04:00
:param prereloadfunc: A function that must be executed just before the reload
of the schema
"""
2010-03-01 05:29:47 +03:00
hash_new = {}
hash = {}
listMissing = []
listPresent = []
reference = []
current = []
2010-05-02 19:56:03 +04:00
2010-03-01 05:29:47 +03:00
# Connect to the reference provision and get all the attribute in the
# partition referred by name
2010-06-07 16:27:48 +04:00
reference = ref_samdb.search(expression="objectClass=*", base=basedn,
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
2010-05-02 19:56:03 +04:00
2010-06-07 16:27:48 +04:00
current = samdb.search(expression="objectClass=*", base=basedn,
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
2010-03-01 05:29:47 +03:00
# Create a hash for speeding the search of new object
2010-06-07 16:27:48 +04:00
for i in range(0, len(reference)):
2010-03-01 05:29:47 +03:00
hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
# Create a hash for speeding the search of existing object in the
# current provision
2010-06-07 16:27:48 +04:00
for i in range(0, len(current)):
2010-03-01 05:29:47 +03:00
hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
2010-05-02 19:56:03 +04:00
2010-03-01 05:29:47 +03:00
for k in hash_new.keys():
2018-08-27 15:08:26 +03:00
if not k in hash:
2010-06-07 16:27:48 +04:00
if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
2010-05-02 19:56:03 +04:00
listMissing.append(hash_new[k])
2010-03-01 05:29:47 +03:00
else:
listPresent.append(hash_new[k])
# Sort the missing object in order to have object of the lowest level
# first (which can be containers for higher level objects)
2018-10-15 12:36:19 +03:00
listMissing.sort(key=cmp_to_key_fn(dn_sort))
listPresent.sort(key=cmp_to_key_fn(dn_sort))
2010-03-01 05:29:47 +03:00
2010-06-07 16:27:48 +04:00
# The following lines is to load the up to
# date schema into our current LDB
# a complete schema is needed as the insertion of attributes
# and class is done against it
# and the schema is self validated
2010-06-20 04:32:23 +04:00
samdb.set_schema(schema)
2010-06-13 17:13:12 +04:00
try:
2010-06-07 16:27:48 +04:00
message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
2010-05-02 19:56:03 +04:00
add_deletedobj_containers(ref_samdb, samdb, names)
2010-06-07 16:27:48 +04:00
add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
2010-07-11 15:36:32 +04:00
2010-08-12 12:22:08 +04:00
prereloadfunc()
2011-01-05 04:36:16 +03:00
message(SIMPLE, "Reloading a merged schema, which might trigger "
"reindexing so please be patient")
2010-07-11 15:36:32 +04:00
reload_full_schema(samdb, names)
2011-01-05 04:36:16 +03:00
message(SIMPLE, "Schema reloaded!")
2010-07-11 15:36:32 +04:00
2010-06-07 16:27:48 +04:00
changed = update_present(ref_samdb, samdb, basedn, listPresent,
2011-06-13 17:39:06 +04:00
provisionUSNs)
2010-06-07 16:27:48 +04:00
message(SIMPLE, "There are %d changed objects" % (changed))
return 1
2010-05-02 19:56:03 +04:00
2018-08-27 15:08:26 +03:00
except Exception as err:
2010-06-07 16:27:48 +04:00
message(ERROR, "Exception during upgrade of samdb:")
(typ, val, tb) = sys.exc_info()
traceback.print_exception(typ, val, tb)
return 0
2010-03-01 05:29:47 +03:00
2010-06-07 16:27:48 +04:00
def check_updated_sd(ref_sam, cur_sam, names):
"""Check if the security descriptor in the upgraded provision are the same
as the reference
:param ref_sam: A LDB object connected to the sam.ldb file used as
the reference provision
:param cur_sam: A LDB object connected to the sam.ldb file used as
upgraded provision
2010-03-01 05:29:47 +03:00
:param names: List of key provision parameters"""
2010-06-07 16:27:48 +04:00
reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
scope=SCOPE_SUBTREE,
attrs=["dn", "nTSecurityDescriptor"],
controls=["search_options:1:2"])
current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
scope=SCOPE_SUBTREE,
attrs=["dn", "nTSecurityDescriptor"],
controls=["search_options:1:2"])
hash = {}
2010-06-08 00:52:25 +04:00
for i in range(0, len(reference)):
2018-10-04 20:29:37 +03:00
refsd_blob = reference[i]["nTSecurityDescriptor"][0]
2013-02-17 15:03:18 +04:00
hash[str(reference[i]["dn"]).lower()] = refsd_blob
2010-03-01 05:29:47 +03:00
2010-07-03 16:26:24 +04:00
2010-06-08 00:52:25 +04:00
for i in range(0, len(current)):
2010-03-01 05:29:47 +03:00
key = str(current[i]["dn"]).lower()
2018-08-27 15:08:26 +03:00
if key in hash:
cursd_blob = current[i]["nTSecurityDescriptor"][0]
2010-06-08 00:52:25 +04:00
cursd = ndr_unpack(security.descriptor,
2013-02-17 15:03:18 +04:00
cursd_blob)
if cursd_blob != hash[key]:
refsd = ndr_unpack(security.descriptor,
hash[key])
txt = get_diff_sds(refsd, cursd, names.domainsid, False)
2010-06-07 16:27:48 +04:00
if txt != "":
2010-06-20 03:56:52 +04:00
message(CHANGESD, "On object %s ACL is different"
2010-06-08 00:52:25 +04:00
" \n%s" % (current[i]["dn"], txt))
2010-06-07 16:27:48 +04:00
2012-12-13 15:56:37 +04:00
def fix_wellknown_sd(samdb, names):
"""This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
2010-06-07 16:27:48 +04:00
This is needed because some provision use to have broken SD on containers
:param samdb: An LDB object pointing to the sam of the current provision
:param names: A list of key provision parameters
"""
2011-06-14 01:42:59 +04:00
2012-12-13 15:56:37 +04:00
list_wellknown_dns = []
2013-03-22 04:15:38 +04:00
subcontainers = get_wellknown_sds(samdb)
2012-12-13 15:56:37 +04:00
2013-03-21 05:49:46 +04:00
for [dn, descriptor_fn] in subcontainers:
2012-12-13 15:56:37 +04:00
list_wellknown_dns.append(dn)
2013-02-18 06:00:31 +04:00
if dn in dnToRecalculate:
2012-12-13 15:56:37 +04:00
delta = Message()
2013-02-18 08:56:18 +04:00
delta.dn = dn
2012-12-13 15:56:37 +04:00
descr = descriptor_fn(names.domainsid, name_map=names.name_map)
delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
"nTSecurityDescriptor" )
samdb.modify(delta)
message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
2011-06-14 01:42:59 +04:00
2012-12-13 15:56:37 +04:00
return list_wellknown_dns
2010-05-02 19:56:03 +04:00
2010-06-07 16:27:48 +04:00
def rebuild_sd(samdb, names):
"""Rebuild security descriptor of the current provision from scratch
2013-02-18 08:05:00 +04:00
During the different pre release of samba4 security descriptors
(SD) were notarly broken (up to alpha11 included)
2015-07-27 00:02:57 +03:00
This function allows one to get them back in order, this function works
2013-02-18 08:05:00 +04:00
only after the database comparison that --full mode uses and which
populates the dnToRecalculate and dnNotToRecalculate lists.
The idea is that the SD can be safely recalculated from scratch to get it right.
2010-06-07 16:27:48 +04:00
:param names: List of key provision parameters"""
2012-12-13 15:56:37 +04:00
listWellknown = fix_wellknown_sd(samdb, names)
2010-06-07 16:27:48 +04:00
2011-06-14 01:42:59 +04:00
if len(dnToRecalculate) != 0:
2012-09-27 20:30:47 +04:00
message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
2013-02-18 08:56:18 +04:00
% (len(dnToRecalculate)))
2011-06-14 01:42:59 +04:00
2013-02-18 08:56:18 +04:00
for dn in dnToRecalculate:
2013-02-18 08:05:00 +04:00
# well known SDs have already been reset
2013-02-18 08:56:18 +04:00
if dn in listWellknown:
2012-12-13 15:56:37 +04:00
continue
2011-06-14 01:42:59 +04:00
delta = Message()
2013-02-18 08:56:18 +04:00
delta.dn = dn
2012-11-22 19:22:30 +04:00
sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
2010-05-02 19:56:03 +04:00
try:
2012-11-22 19:22:30 +04:00
descr = get_empty_descriptor(names.domainsid)
delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
"nTSecurityDescriptor")
2013-01-23 18:24:11 +04:00
samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
2018-02-14 00:33:06 +03:00
except LdbError as e:
2010-05-02 19:56:03 +04:00
samdb.transaction_cancel()
2012-11-22 19:22:30 +04:00
res = samdb.search(expression="objectClass=*", base=str(delta.dn),
scope=SCOPE_BASE,
attrs=["nTSecurityDescriptor"],
controls=["sd_flags:1:%d" % sd_flags])
2010-06-08 01:13:45 +04:00
badsd = ndr_unpack(security.descriptor,
2018-08-27 15:08:26 +03:00
res[0]["nTSecurityDescriptor"][0])
2011-06-14 01:42:59 +04:00
message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
2010-05-02 19:56:03 +04:00
return
2011-07-21 00:50:38 +04:00
def hasATProvision(samdb):
2011-10-25 22:10:30 +04:00
entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
2011-07-31 00:46:58 +04:00
scope=SCOPE_BASE,
2011-07-21 00:50:38 +04:00
attrs=["dn"])
2012-09-27 20:30:47 +04:00
if entry is not None and len(entry) == 1:
2011-07-21 00:50:38 +04:00
return True
else:
return False
2010-06-07 16:27:48 +04:00
def removeProvisionUSN(samdb):
attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
2011-10-25 22:10:30 +04:00
entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
2011-07-13 07:05:19 +04:00
scope=SCOPE_BASE,
2010-06-07 16:27:48 +04:00
attrs=attrs)
empty = Message()
empty.dn = entry[0].dn
delta = samdb.msg_diff(entry[0], empty)
delta.remove("dn")
delta.dn = entry[0].dn
samdb.modify(delta)
2010-05-02 19:56:03 +04:00
2010-07-03 16:26:24 +04:00
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.
"""
2010-05-02 19:56:03 +04:00
def simple_update_basesamdb(newpaths, paths, names):
2010-03-01 05:29:47 +03:00
"""Update the provision container db: sam.ldb
2010-05-02 19:56:03 +04:00
This function is aimed at very old provision (before alpha9)
2010-03-01 05:29:47 +03:00
2010-06-08 01:13:45 +04:00
:param newpaths: List of paths for different provision objects
from the reference provision
:param paths: List of paths for different provision objects
from the upgraded provision
2010-03-01 05:29:47 +03:00
:param names: List of key provision parameters"""
2010-06-13 17:13:12 +04:00
message(SIMPLE, "Copy samdb")
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
2010-03-01 05:29:47 +03:00
2010-06-13 17:13:12 +04:00
message(SIMPLE, "Update partitions filename if needed")
2010-04-08 20:57:09 +04:00
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")
samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
2010-03-01 05:29:47 +03:00
if not os.path.isdir(samldbdir):
os.mkdir(samldbdir)
2018-08-27 15:08:26 +03:00
os.chmod(samldbdir, 0o700)
2010-03-01 05:29:47 +03:00
if os.path.isfile(schemaldb):
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
2010-06-08 01:13:45 +04:00
"%s.ldb"%str(names.schemadn).upper()))
2010-03-01 05:29:47 +03:00
os.remove(schemaldb)
if os.path.isfile(usersldb):
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
2010-06-08 01:13:45 +04:00
"%s.ldb"%str(names.rootdn).upper()))
2010-03-01 05:29:47 +03:00
os.remove(usersldb)
if os.path.isfile(configldb):
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
2010-06-08 01:13:45 +04:00
"%s.ldb"%str(names.configdn).upper()))
2010-03-01 05:29:47 +03:00
os.remove(configldb)
2009-11-25 16:26:35 +03:00
2010-03-01 05:41:52 +03:00
2011-06-13 17:56:17 +04:00
def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
2010-05-02 19:56:03 +04:00
"""Upgrade the SAM DB contents for all the provision partitions
2010-02-21 21:29:36 +03:00
2010-06-08 01:13:45 +04:00
:param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
provision
:param samdb: An LDB object connected to the sam.ldb of the update
provision
2010-05-02 19:56:03 +04:00
:param names: List of key provision parameters
2011-06-13 17:56:17 +04:00
:param provisionUSNs: A dictionnary with range of USN modified during provision
or upgradeprovision. Ranges are grouped by invocationID.
2010-08-12 12:22:08 +04:00
:param schema: A Schema object that represent the schema of the provision
:param prereloadfunc: A function that must be executed just before the reload
of the schema
"""
2009-11-25 16:26:35 +03:00
2010-05-02 19:56:03 +04:00
message(SIMPLE, "Starting update of samdb")
2010-06-08 01:13:45 +04:00
ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
2011-06-13 17:56:17 +04:00
schema, provisionUSNs, prereloadfunc)
2010-05-02 19:56:03 +04:00
if ret:
2010-06-08 01:13:45 +04:00
message(SIMPLE, "Update of samdb finished")
2010-05-02 19:56:03 +04:00
return 1
else:
2010-06-08 01:13:45 +04:00
message(SIMPLE, "Update failed")
2010-05-02 19:56:03 +04:00
return 0
2009-10-27 15:31:40 +03:00
2010-03-01 05:41:52 +03:00
2018-03-22 02:50:45 +03:00
def backup_provision(samdb, paths, dir, only_db):
2010-07-03 16:26:24 +04:00
"""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
2012-03-07 09:44:45 +04:00
:param only_db: Skip sysvol for users with big sysvol
2010-07-03 16:26:24 +04:00
"""
2018-03-22 02:50:45 +03:00
# Currently we default to tdb for the backend store type
#
backend_store = "tdb"
res = samdb.search(base="@PARTITION",
scope=ldb.SCOPE_BASE,
attrs=["backendStore"])
if "backendStore" in res[0]:
2018-10-05 17:10:52 +03:00
backend_store = str(res[0]["backendStore"][0])
2018-03-22 02:50:45 +03:00
2012-03-07 09:44:45 +04:00
if paths.sysvol and not only_db:
2012-02-27 03:53:19 +04:00
copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
2018-03-22 02:50:45 +03:00
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
2010-07-03 16:26:24 +04:00
if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
2010-07-03 16:26:24 +04:00
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")
2013-02-17 11:15:52 +04:00
tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
2010-07-03 16:26:24 +04:00
else:
2018-08-27 15:08:26 +03:00
os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
2013-02-17 11:15:52 +04:00
2018-01-17 04:02:09 +03:00
for ldb_name in os.listdir(samldbdir):
if not ldb_name.endswith("-lock"):
2018-03-22 02:50:45 +03:00
if backend_store == "mdb" and ldb_name != "metadata.tdb":
mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
os.path.join(dir, "sam.ldb.d", ldb_name))
else:
tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
os.path.join(dir, "sam.ldb.d", ldb_name))
2010-03-28 22:48:55 +04:00
2010-07-05 01:00:13 +04:00
def sync_calculated_attributes(samdb, names):
"""Synchronize attributes used for constructed ones, with the
old constructed that were stored in the database.
This apply for instance to msds-keyversionnumber that was
stored and that is now constructed from replpropertymetadata.
:param samdb: An LDB object attached to the currently upgraded samdb
:param names: Various key parameter about current provision.
"""
2010-08-10 17:39:29 +04:00
listAttrs = ["msDs-KeyVersionNumber"]
2010-07-05 01:00:13 +04:00
hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
2018-08-27 15:08:26 +03:00
if "msDs-KeyVersionNumber" in hash:
2010-08-10 18:19:40 +04:00
increment_calculated_keyversion_number(samdb, names.rootdn,
2010-08-10 17:39:29 +04:00
hash["msDs-KeyVersionNumber"])
2010-07-05 01:00:13 +04:00
2010-05-07 16:26:26 +04:00
# Synopsis for updateprovision
# 1) get path related to provision to be update (called current)
# 2) open current provision ldbs
# 3) fetch the key provision parameter (domain sid, domain guid, invocationid
# of the DC ....)
# 4) research of lastProvisionUSN in order to get ranges of USN modified
# by either upgradeprovision or provision
# 5) creation of a new provision the latest version of provision script
# (called reference)
# 6) get reference provision paths
# 7) open reference provision ldbs
# 8) setup helpers data that will help the update process
2013-02-17 11:41:00 +04:00
# 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
# the current provision, because a shutil.copy would break the transaction locks both databases are under
# and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
2010-05-07 16:26:26 +04:00
# 10)get the oemInfo field, this field contains information about the different
# provision that have been done
2013-02-16 14:58:57 +04:00
# 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
# A) When alpha9 or alphaxx not specified (default)
2010-05-07 16:26:26 +04:00
# The base sam.ldb file is updated by looking at the difference between
# referrence one and the current one. Everything is copied with the
2010-07-03 16:26:24 +04:00
# exception of lastProvisionUSN attributes.
2010-05-07 16:26:26 +04:00
# 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
2010-07-03 16:26:24 +04:00
# The highest used USN is fetched so that changed by upgradeprovision
# usn can be tracked
2010-05-07 16:26:26 +04:00
# 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
# object to be created)
# 13)Proceed to full update of sam DB (see the separate paragraph about i)
# 14)The secrets db is updated by pull all the difference from the reference
# provision into the current provision
# 15)As the previous step has most probably modified the password stored in
# in secret for the current DC, a new password is generated,
# the kvno is bumped and the entry in samdb is also updated
# 16)For current provision older than alpha9, we must fix the SD a little bit
# administrator to update them because SD used to be generated with the
# system account before alpha9.
# 17)The highest usn modified so far is searched in the database it will be
# the upper limit for usn modified during provision.
# This is done before potential SD recalculation because we do not want
# SD modified during recalculation to be marked as modified during provision
# (and so possibly remplaced at next upgradeprovision)
# 18)Rebuilt SD if the flag indicate to do so
# 19)Check difference between SD of reference provision and those of the
# current provision. The check is done by getting the sddl representation
# of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
# Each part is verified separetly, for dacl and sacl ACL is splited into
# ACEs and each ACE is verified separately (so that a permutation in ACE
# didn't raise as an error).
# 20)The oemInfo field is updated to add information about the fact that the
# provision has been updated by the upgradeprovision version xxx
# (the version is the one obtained when starting samba with the --version
# parameter)
# 21)Check if the current provision has all the settings needed for dynamic
# DNS update to work (that is to say the provision is newer than
# january 2010). If not dns configuration file from reference provision
# are copied in a sub folder and the administrator is invited to
# do what is needed.
# 22)If the lastProvisionUSN attribute was present it is updated to add
# the range of usns modified by the current upgradeprovision
# About updating the sam DB
# The update takes place in update_partition function
# This function read both current and reference provision and list all
# the available DN of objects
# If the string representation of a DN in reference provision is
# equal to the string representation of a DN in current provision
# (without taking care of case) then the object is flaged as being
# present. If the object is not present in current provision the object
# is being flaged as missing in current provision. Object present in current
# provision but not in reference provision are ignored.
# Once the list of objects present and missing is done, the deleted object
# containers are created in the differents partitions (if missing)
#
# Then the function add_missing_entries is called
# This function will go through the list of missing entries by calling
# add_missing_object for the given object. If this function returns 0
# it means that the object needs some other object in order to be created
# The object is reappended at the end of the list to be created later
# (and preferably after all the needed object have been created)
# The function keeps on looping on the list of object to be created until
2017-02-17 22:59:36 +03:00
# it's empty or that the number of deferred creation is equal to the number
2010-05-07 16:26:26 +04:00
# of object that still needs to be created.
# The function add_missing_object will first check if the object can be created.
# That is to say that it didn't depends other not yet created objects
# If requisit can't be fullfilled it exists with 0
# Then it will try to create the missing entry by creating doing
# an ldb_message_diff between the object in the reference provision and
# an empty object.
# This resulting object is filtered to remove all the back link attribute
# (ie. memberOf) as they will be created by the other linked object (ie.
# the one with the member attribute)
2011-06-13 18:49:23 +04:00
# All attributes specified in the attrNotCopied array are
2010-05-07 16:26:26 +04:00
# also removed it's most of the time generated attributes
# After missing entries have been added the update_partition function will
# take care of object that exist but that need some update.
# In order to do so the function update_present is called with the list
# of object that are present in both provision and that might need an update.
# This function handle first case mismatch so that the DN in the current
# provision have the same case as in reference provision
# It will then construct an associative array consiting of attributes as
# key and invocationid as value( if the originating invocation id is
# different from the invocation id of the current DC the value is -1 instead).
# If the range of provision modified attributes is present, the function will
# use the replMetadataProperty update method which is the following:
# Removing attributes that should not be updated: rIDAvailablePool, objectSid,
# creationTime, msDs-KeyVersionNumber, oEMInformation
# Check for each attribute if its usn is within one of the modified by
# provision range and if its originating id is the invocation id of the
# current DC, then validate the update from reference to current.
# If not or if there is no replMetatdataProperty for this attribute then we
# do not update it.
# Otherwise (case the range of provision modified attribute is not present) it
# use the following process:
# All attributes that need to be added are accepted at the exeption of those
# listed in hashOverwrittenAtt, in this case the attribute needs to have the
# correct flags specified.
# For attributes that need to be modified or removed, a check is performed
# in OverwrittenAtt, if the attribute is present and the modification flag
# (remove, delete) is one of those listed for this attribute then modification
# is accepted. For complicated handling of attribute update, the control is passed
# to handle_special_case
2010-03-01 05:41:52 +03:00
if __name__ == '__main__':
2010-06-07 16:27:48 +04:00
global defSDmodified
2010-06-20 03:56:52 +04:00
defSDmodified = False
2012-03-07 09:44:45 +04:00
2010-03-01 05:29:47 +03:00
# From here start the big steps of the program
2010-05-07 16:26:26 +04:00
# 1) First get files paths
2010-05-02 19:56:03 +04:00
paths = get_paths(param, smbconf=smbconf)
2010-06-08 01:13:45 +04:00
# Get ldbs with the system session, it is needed for searching
# provision parameters
2010-05-02 19:56:03 +04:00
session = system_session()
# This variable will hold the last provision USN once if it exists.
2010-06-07 16:27:48 +04:00
minUSN = 0
2010-05-07 16:26:26 +04:00
# 2)
2010-05-02 19:56:03 +04:00
ldbs = get_ldbs(paths, creds, session, lp)
2010-07-03 16:26:24 +04:00
backupdir = tempfile.mkdtemp(dir=paths.private_dir,
prefix="backupprovision")
2018-03-22 02:50:45 +03:00
backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
2010-07-03 16:26:24 +04:00
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:
2011-06-13 17:39:06 +04:00
v = 0
for k in lastProvisionUSNs.keys():
for r in lastProvisionUSNs[k]:
v = v + 1
2010-07-03 16:26:24 +04:00
message(CHANGE,
2012-09-27 20:30:47 +04:00
"Find last provision USN, %d invocation(s) for a total of %d ranges" %
2011-06-13 17:39:06 +04:00
(len(lastProvisionUSNs.keys()), v /2 ))
2010-07-03 16:26:24 +04:00
2012-09-27 20:30:47 +04:00
if lastProvisionUSNs.get("default") is not None:
2011-06-13 17:39:06 +04:00
message(CHANGE, "Old style for usn ranges used")
lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
del lastProvisionUSNs["default"]
2011-06-20 01:05:04 +04:00
else:
message(SIMPLE, "Your provision lacks provision range information")
if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
ldbs.groupedRollback()
2012-03-17 11:19:40 +04:00
minobj = 5
(hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
2012-09-27 20:30:47 +04:00
message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
2012-03-17 11:19:40 +04:00
" of hundred of objects or more")
message(SIMPLE, "Total number of objects: %d" % nb_obj)
message(SIMPLE, "")
print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
2011-06-20 01:05:04 +04:00
message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
sys.exit(0)
2010-07-03 16:26:24 +04:00
# 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)
2013-02-16 01:51:51 +04:00
if not sanitychecks(ldbs.sam, names):
message(SIMPLE, "Sanity checks for the upgrade have failed. "
"Check the messages and correct the errors "
"before rerunning upgradeprovision")
ldbs.groupedRollback()
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")
2013-09-26 21:19:18 +04:00
result = newprovision(names, session, smbconf, provisiondir,
2017-10-05 00:01:27 +03:00
provision_logger, base_schema="2008_R2")
2013-02-16 01:51:51 +04:00
result.report_logger(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()
populateNotReplicated(new_ldbs.sam, names.schemadn)
# 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)
2013-02-17 11:41:00 +04:00
# 9) (now skipped, was copy of privileges.ldb)
2013-02-16 01:51:51 +04:00
# 10)
oem = getOEMInfo(ldbs.sam, str(names.rootdn))
# Do some modification on sam.ldb
ldbs.groupedCommit()
new_ldbs.groupedCommit()
deltaattr = None
# 11)
message(GUESS, oem)
2013-02-16 14:58:57 +04:00
if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
2013-02-16 01:51:51 +04:00
# 11) A
# Starting from alpha9 we can consider that the structure is quite ok
# and that we should do only dela
deltaattr = 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)
2011-05-15 16:06:18 +04:00
2013-02-16 01:51:51 +04:00
ldbs.startTransactions()
minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
new_ldbs.startTransactions()
# 12)
schema = Schema(names.domainsid, schemadn=str(names.schemadn))
# We create a closure that will be invoked just before schema reload
def schemareloadclosure():
basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
options=["modules:"])
doit = False
if deltaattr is not None and len(deltaattr) > 1:
doit = True
if doit:
deltaattr.remove("dn")
for att in deltaattr:
if att.lower() == "dn":
continue
if (deltaattr.get(att) is not None
and deltaattr.get(att).flags() != FLAG_MOD_ADD):
doit = False
elif deltaattr.get(att) is None:
doit = False
if doit:
message(CHANGE, "Applying delta to @ATTRIBUTES")
deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
basesam.modify(deltaattr)
2012-03-07 09:44:45 +04:00
else:
2013-02-16 01:51:51 +04:00
message(CHANGE, "Not applying delta to @ATTRIBUTES because "
"there is not only add")
# 13)
if opts.full:
if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
schema, schemareloadclosure):
message(SIMPLE, "Rolling back all changes. Check the cause"
" of the problem")
message(SIMPLE, "Your system is as it was before the upgrade")
ldbs.groupedRollback()
new_ldbs.groupedRollback()
shutil.rmtree(provisiondir)
sys.exit(1)
2011-05-15 16:06:18 +04:00
else:
2013-02-16 01:51:51 +04:00
# Try to reapply the change also when we do not change the sam
# as the delta_upgrade
schemareloadclosure()
sync_calculated_attributes(ldbs.sam, names)
res = ldbs.sam.search(expression="(samaccountname=dns)",
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
if len(res) > 0:
message(SIMPLE, "You still have the old DNS object for managing "
"dynamic DNS, but you didn't supply --full so "
"a correct update can't be done")
ldbs.groupedRollback()
new_ldbs.groupedRollback()
shutil.rmtree(provisiondir)
sys.exit(1)
# 14)
update_secrets(new_ldbs.secrets, ldbs.secrets, message)
# 14bis)
res = ldbs.sam.search(expression="(samaccountname=dns)",
scope=SCOPE_SUBTREE, attrs=["dn"],
controls=["search_options:1:2"])
if (len(res) == 1):
ldbs.sam.delete(res[0]["dn"])
res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
scope=SCOPE_SUBTREE, attrs=["dn"])
update_dns_account_password(ldbs.sam, ldbs.secrets, names)
message(SIMPLE, "IMPORTANT!!! "
"If you were using Dynamic DNS before you need "
"to update your configuration, so that the "
"tkey-gssapi-credential has the following value: "
"DNS/%s.%s" % (names.netbiosname.lower(),
names.realm.lower()))
# 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 ...
2013-02-16 14:58:57 +04:00
if opts.very_old_pre_alpha9:
2013-02-16 01:51:51 +04:00
message(SIMPLE, "Fixing very old provision SD")
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 if a we have a list of DN to recalculate or if the
# defSDmodified is set.
2013-02-18 08:05:00 +04:00
if opts.full and (defSDmodified or len(dnToRecalculate) >0):
2013-02-16 01:51:51 +04:00
message(SIMPLE, "Some (default) security descriptors (SDs) have "
"changed, recalculating them")
ldbs.sam.set_session_info(adm_session)
rebuild_sd(ldbs.sam, names)
# 19)
# Now we are quite confident in the recalculate process of the SD, we make
# it optional. And we don't do it if there is DN that we must touch
# as we are assured that on this DNs we will have differences !
# Also the check must be done in a clever way as for the moment we just
# compare SDDL
2018-10-10 07:51:54 +03:00
if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
2013-02-16 01:51:51 +04:00
message(CHANGESD, "Checking recalculated SDs")
check_updated_sd(new_ldbs.sam, ldbs.sam, names)
# 20)
updateOEMInfo(ldbs.sam, str(names.rootdn))
# 21)
2017-08-10 16:37:54 +03:00
check_for_DNS(newpaths.private_dir, paths.private_dir,
newpaths.binddns_dir, paths.binddns_dir,
names.dns_backend)
2013-02-16 01:51:51 +04:00
# 22)
2013-02-18 05:28:23 +04:00
update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
2013-02-16 01:51:51 +04:00
if opts.full and (names.policyid is None or names.policyid_dc is None):
update_policyids(names, ldbs.sam)
if opts.full:
try:
update_gpo(paths, ldbs.sam, names, lp, message)
2018-02-14 00:33:06 +03:00
except ProvisioningError as e:
2013-02-16 01:51:51 +04:00
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 !
# So we have reindexed first if need when the merged schema was reloaded
# (as new attributes could have quick in)
# But the second part of the update (when we update existing objects
# can also have an influence on indexing as some attribute might have their
# searchflag modificated
message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
"after modification")
samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
message(SIMPLE, "Reindexing finished")
shutil.rmtree(provisiondir)
2018-08-27 15:08:26 +03:00
except Exception as err:
2011-01-08 16:08:47 +03:00
message(ERROR, "A problem occurred while trying to upgrade your "
2011-05-15 16:06:18 +04:00
"provision. A full backup is located at %s" % backupdir)
2010-08-10 17:39:29 +04:00
if opts.debugall or opts.debugchange:
2010-07-03 16:26:24 +04:00
(typ, val, tb) = sys.exc_info()
traceback.print_exception(typ, val, tb)
2010-08-14 16:57:49 +04:00
sys.exit(1)