4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
38 import samba.getopt as options
39 from samba.samdb import get_default_backend_store
41 from base64 import b64encode
42 from samba.credentials import DONT_USE_KERBEROS
43 from samba.auth import system_session, admin_session
44 from samba import tdb_util
45 from samba import mdb_util
46 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
47 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
48 MessageElement, Message, Dn, LdbError)
49 from samba import param, dsdb, Ldb
50 from samba.common import confirm
51 from samba.descriptor import get_wellknown_sds, get_empty_descriptor, get_diff_sds
52 from samba.provision import (find_provision_key_parameters,
53 ProvisioningError, get_last_provision_usn,
54 get_max_usn, update_provision_usn, setup_path)
55 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
56 from samba.dcerpc import security, drsblobs
57 from samba.dcerpc.security import (
58 SECINFO_OWNER, SECINFO_GROUP, SECINFO_DACL, SECINFO_SACL)
59 from samba.ndr import ndr_unpack
60 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
61 get_ldbs, findprovisionrange,
62 usn_in_range, identic_rename,
63 update_secrets, CHANGE, ERROR, SIMPLE,
64 CHANGEALL, GUESS, CHANGESD, PROVISION,
65 updateOEMInfo, getOEMInfo, update_gpo,
66 delta_update_basesamdb, update_policyids,
67 update_machine_account_password,
68 search_constructed_attrs_stored,
69 int64range2str, update_dns_account_password,
70 increment_calculated_keyversion_number,
71 print_provision_ranges)
72 from samba.xattr import copytree_with_xattrs
73 from functools import cmp_to_key
75 # make sure the script dies immediately when hitting control-C,
76 # rather than raising KeyboardInterrupt. As we do all database
77 # operations using transactions, this is safe.
79 signal.signal(signal.SIGINT, signal.SIG_DFL)
81 replace=2**FLAG_MOD_REPLACE
83 delete=2**FLAG_MOD_DELETE
87 # Will be modified during provision to tell if default sd has been modified
90 #Errors are always logged
92 __docformat__ = "restructuredText"
94 # Attributes that are never copied from the reference provision (even if they
95 # do not exist in the destination object).
96 # This is most probably because they are populated automatcally when object is
98 # This also apply to imported object from reference provision
99 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
100 "parentGUID", "distinguishedName",
101 "instanceType", "cn",
102 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
103 "unicodePwd", "dBCSPwd", "supplementalCredentials",
104 "gPCUserExtensionNames", "gPCMachineExtensionNames",
105 "maxPwdAge", "secret", "possibleInferiors", "privilege",
106 "sAMAccountType", "oEMInformation", "creationTime" ]
108 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
109 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
111 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
114 attrNotCopied = replAttrNotCopied
115 attrNotCopied.extend(nonreplAttrNotCopied)
116 attrNotCopied.extend(nonDSDBAttrNotCopied)
117 # Usually for an object that already exists we do not overwrite attributes as
118 # they might have been changed for good reasons. Anyway for a few of them it's
119 # mandatory to replace them otherwise the provision will be broken somehow.
120 # But for attribute that are just missing we do not have to specify them as the default
121 # behavior is to add missing attribute
122 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
123 "systemOnly":replace, "searchFlags":replace,
124 "mayContain":replace, "systemFlags":replace+add,
125 "description":replace, "operatingSystemVersion":replace,
126 "adminPropertyPages":replace, "groupType":replace,
127 "wellKnownObjects":replace, "privilege":never,
128 "rIDAvailablePool": never,
129 "rIDNextRID": add, "rIDUsedPool": never,
130 "defaultSecurityDescriptor": replace + add,
131 "isMemberOfPartialAttributeSet": delete,
132 "attributeDisplayNames": replace + add,
133 "versionNumber": add}
135 dnNotToRecalculateFound = False
138 forwardlinked = set()
141 def define_what_to_log(opts):
145 if opts.debugchangesd:
146 what = what | CHANGESD
149 if opts.debugprovision:
150 what = what | PROVISION
152 what = what | CHANGEALL
156 parser = optparse.OptionParser("samba_upgradeprovision [options]")
157 sambaopts = options.SambaOptions(parser)
158 parser.add_option_group(sambaopts)
159 parser.add_option_group(options.VersionOptions(parser))
160 credopts = options.CredentialsOptions(parser)
161 parser.add_option_group(credopts)
162 parser.add_option("--setupdir", type="string", metavar="DIR",
163 help="directory with setup files")
164 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
165 parser.add_option("--debugguess", action="store_true",
166 help="Print information on which values are guessed")
167 parser.add_option("--debugchange", action="store_true",
168 help="Print information on what is different but won't be changed")
169 parser.add_option("--debugchangesd", action="store_true",
170 help="Print security descriptor differences")
171 parser.add_option("--debugall", action="store_true",
172 help="Print all available information (very verbose)")
173 parser.add_option("--db_backup_only", action="store_true",
174 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
175 parser.add_option("--full", action="store_true",
176 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
177 parser.add_option("--very-old-pre-alpha9", action="store_true",
178 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
180 opts = parser.parse_args()[0]
182 handler = logging.StreamHandler(sys.stdout)
183 upgrade_logger = logging.getLogger("upgradeprovision")
184 upgrade_logger.setLevel(logging.INFO)
186 upgrade_logger.addHandler(handler)
188 provision_logger = logging.getLogger("provision")
189 provision_logger.addHandler(handler)
191 whatToLog = define_what_to_log(opts)
193 def message(what, text):
194 """Print a message if this message type has been selected to be printed
196 :param what: Category of the message
197 :param text: Message to print """
198 if (whatToLog & what) or what <= 0:
199 upgrade_logger.info("%s", text)
201 if len(sys.argv) == 1:
202 opts.interactive = True
203 lp = sambaopts.get_loadparm()
204 smbconf = lp.configfile
206 creds = credopts.get_credentials(lp)
207 creds.set_kerberos_state(DONT_USE_KERBEROS)
211 def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
212 """Check if the provision has already the requirement for dynamic dns
214 :param refprivate: The path to the private directory of the reference
216 :param private: The path to the private directory of the upgraded
219 spnfile = "%s/spn_update_list" % private
220 dnsfile = "%s/dns_update_list" % private
222 if not os.path.exists(spnfile):
223 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
225 if not os.path.exists(dnsfile):
226 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
228 if not os.path.exists(binddns_dir):
229 os.mkdir(binddns_dir)
231 if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
234 namedfile = lp.get("dnsupdate:path")
236 namedfile = "%s/named.conf.update" % binddns_dir
237 if not os.path.exists(namedfile):
238 destdir = "%s/new_dns" % binddns_dir
239 dnsdir = "%s/dns" % binddns_dir
241 if not os.path.exists(destdir):
243 if not os.path.exists(dnsdir):
245 shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
246 shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
247 message(SIMPLE, "It seems that your provision did not integrate "
248 "new rules for dynamic dns update of domain related entries")
249 message(SIMPLE, "A copy of the new bind configuration files and "
250 "template has been put in %s, you should read them and "
251 "configure dynamic dns updates" % destdir)
254 def populate_links(samdb, schemadn):
255 """Populate an array with all the back linked attributes
257 This attributes that are modified automatically when
258 front attibutes are changed
260 :param samdb: A LDB object for sam.ldb file
261 :param schemadn: DN of the schema for the partition"""
262 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
263 backlinked.extend(linkedAttHash.values())
264 for t in linkedAttHash.keys():
267 def isReplicated(att):
268 """ Indicate if the attribute is replicated or not
270 :param att: Name of the attribute to be tested
271 :return: True is the attribute is replicated, False otherwise
274 return (att not in not_replicated)
276 def populateNotReplicated(samdb, schemadn):
277 """Populate an array with all the attributes that are not replicated
279 :param samdb: A LDB object for sam.ldb file
280 :param schemadn: DN of the schema for the partition"""
281 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
282 str(schemadn)), scope=SCOPE_SUBTREE,
283 attrs=["lDAPDisplayName"])
285 not_replicated.append(str(elem["lDAPDisplayName"]))
288 def populate_dnsyntax(samdb, schemadn):
289 """Populate an array with all the attributes that have DN synthax
292 :param samdb: A LDB object for sam.ldb file
293 :param schemadn: DN of the schema for the partition"""
294 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
295 str(schemadn)), scope=SCOPE_SUBTREE,
296 attrs=["lDAPDisplayName"])
298 dn_syntax_att.append(elem["lDAPDisplayName"])
301 def sanitychecks(samdb, names):
302 """Make some checks before trying to update
304 :param samdb: An LDB object opened on sam.ldb
305 :param names: list of key provision parameters
306 :return: Status of check (1 for Ok, 0 for not Ok) """
307 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
308 scope=SCOPE_SUBTREE, attrs=["dn"],
309 controls=["search_options:1:2"])
311 print("No DC found. Your provision is most probably broken!")
314 print("Found %d domain controllers. For the moment " \
315 "upgradeprovision is not able to handle an upgrade on a " \
316 "domain with more than one DC. Please demote the other " \
317 "DC(s) before upgrading") % len(res)
323 def print_provision_key_parameters(names):
324 """Do a a pretty print of provision parameters
326 :param names: list of key provision parameters """
327 message(GUESS, "rootdn :" + str(names.rootdn))
328 message(GUESS, "configdn :" + str(names.configdn))
329 message(GUESS, "schemadn :" + str(names.schemadn))
330 message(GUESS, "serverdn :" + str(names.serverdn))
331 message(GUESS, "netbiosname :" + names.netbiosname)
332 message(GUESS, "defaultsite :" + names.sitename)
333 message(GUESS, "dnsdomain :" + names.dnsdomain)
334 message(GUESS, "hostname :" + names.hostname)
335 message(GUESS, "domain :" + names.domain)
336 message(GUESS, "realm :" + names.realm)
337 message(GUESS, "invocationid:" + names.invocation)
338 message(GUESS, "policyguid :" + names.policyid)
339 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
340 message(GUESS, "domainsid :" + str(names.domainsid))
341 message(GUESS, "domainguid :" + names.domainguid)
342 message(GUESS, "ntdsguid :" + names.ntdsguid)
343 message(GUESS, "domainlevel :" + str(names.domainlevel))
346 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
347 """Define more complicate update rules for some attributes
349 :param att: The attribute to be updated
350 :param delta: A messageElement object that correspond to the difference
351 between the updated object and the reference one
352 :param new: The reference object
353 :param old: The Updated object
354 :param useReplMetadata: A boolean that indicate if the update process
355 use replPropertyMetaData to decide what has to be updated.
356 :param basedn: The base DN of the provision
357 :param aldb: An ldb object used to build DN
358 :return: True to indicate that the attribute should be kept, False for
361 # We do most of the special case handle if we do not have the
362 # highest usn as otherwise the replPropertyMetaData will guide us more
364 if not useReplMetadata:
365 flag = delta.get(att).flags()
366 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
367 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
368 "CN=Services,CN=Configuration,%s" % basedn)
371 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
372 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
374 message(SIMPLE, "We suggest that you change the userAccountControl"
375 " for user Administrator from value %d to %d" %
376 (int(str(old[0][att])), int(str(new[0][att]))))
378 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
379 if (int(str(old[0][att])) == 0):
380 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
383 if (att == "member" and flag == FLAG_MOD_REPLACE):
387 for elem in old[0][att]:
388 hash[str(elem).lower()]=1
389 newval.append(str(elem))
391 for elem in new[0][att]:
392 if not str(elem).lower() in hash:
394 newval.append(str(elem))
396 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
401 if (att in ("gPLink", "gPCFileSysPath") and
402 flag == FLAG_MOD_REPLACE and
403 str(new[0].dn).lower() == str(old[0].dn).lower()):
407 if att == "forceLogoff":
408 ref=0x8000000000000000
409 oldval=int(old[0][att][0])
410 newval=int(new[0][att][0])
411 ref == old and ref == abs(new)
414 if att in ("adminDisplayName", "adminDescription"):
417 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
418 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
421 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
422 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
425 if (str(old[0].dn) == "%s" % (str(names.rootdn))
426 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
428 #Allow to change revision of ForestUpdates objects
429 if (att == "revision" or att == "objectVersion"):
430 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
432 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
435 # This is a bit of special animal as we might have added
436 # already SPN entries to the list that has to be modified
437 # So we go in detail to try to find out what has to be added ...
438 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
442 for elem in old[0][att]:
444 newval.append(str(elem))
446 for elem in new[0][att]:
447 if not str(elem) in hash:
449 newval.append(str(elem))
451 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
458 def dump_denied_change(dn, att, flagtxt, current, reference):
459 """Print detailed information about why a change is denied
461 :param dn: DN of the object which attribute is denied
462 :param att: Attribute that was supposed to be upgraded
463 :param flagtxt: Type of the update that should be performed
464 (add, change, remove, ...)
465 :param current: Value(s) of the current attribute
466 :param reference: Value(s) of the reference attribute"""
468 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
469 + " must not be changed/removed. Discarding the change")
470 if att == "objectSid" :
471 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
472 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
473 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
474 message(CHANGE, "old : %s" % int64range2str(current[0]))
475 message(CHANGE, "new : %s" % int64range2str(reference[0]))
478 for e in range(0, len(current)):
479 message(CHANGE, "old %d : %s" % (i, str(current[e])))
481 if reference is not None:
483 for e in range(0, len(reference)):
484 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
487 def handle_special_add(samdb, dn, names):
488 """Handle special operation (like remove) on some object needed during
491 This is mostly due to wrong creation of the object in previous provision.
492 :param samdb: An Ldb object representing the SAM database
493 :param dn: DN of the object to inspect
494 :param names: list of key provision parameters
498 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
500 #This entry was misplaced lets remove it if it exists
501 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
504 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
506 #This entry was misplaced lets remove it if it exists
507 dntoremove = "CN=Certificate Service DCOM Access,"\
508 "CN=Users, %s" % names.rootdn
510 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
512 #This entry was misplaced lets remove it if it exists
513 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
515 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
517 #This entry was misplaced lets remove it if it exists
518 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
520 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
521 "CN=Configuration,%s" % names.rootdn)
523 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
524 "CN=WellKnown Security Principals,"
525 "CN=Configuration,%s" % names.rootdn)
527 res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
528 base=str(names.rootdn),
529 scope=SCOPE_SUBTREE, attrs=["dn"],
530 controls=["search_options:1:2"])
532 res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
533 base=str(names.rootdn),
534 scope=SCOPE_SUBTREE, attrs=["dn"],
535 controls=["search_options:1:2"])
537 if len(res) > 0 and len(res2) == 0:
538 message(CHANGE, "Existing object %s must be replaced by %s. "
539 "Renaming old object" % (str(oldDn), str(dn)))
540 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
544 if dntoremove is not None:
545 res = samdb.search(expression="(cn=RID Set)",
546 base=str(names.rootdn),
547 scope=SCOPE_SUBTREE, attrs=["dn"],
548 controls=["search_options:1:2"])
552 res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
553 base=str(names.rootdn),
554 scope=SCOPE_SUBTREE, attrs=["dn"],
555 controls=["search_options:1:2"])
557 message(CHANGE, "Existing object %s must be replaced by %s. "
558 "Removing old object" % (dntoremove, str(dn)))
559 samdb.delete(res[0]["dn"])
565 def check_dn_nottobecreated(hash, index, listdn):
566 """Check if one of the DN present in the list has a creation order
567 greater than the current.
569 Hash is indexed by dn to be created, with each key
570 is associated the creation order.
572 First dn to be created has the creation order 0, second has 1, ...
573 Index contain the current creation order
575 :param hash: Hash holding the different DN of the object to be
577 :param index: Current creation order
578 :param listdn: List of DNs on which the current DN depends on
579 :return: None if the current object do not depend on other
580 object or if all object have been created before."""
584 key = str(dn).lower()
585 if key in hash and hash[key] > index:
591 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
592 """Add a new object if the dependencies are satisfied
594 The function add the object if the object on which it depends are already
597 :param ref_samdb: Ldb object representing the SAM db of the reference
599 :param samdb: Ldb object representing the SAM db of the upgraded
601 :param dn: DN of the object to be added
602 :param names: List of key provision parameters
603 :param basedn: DN of the partition to be updated
604 :param hash: Hash holding the different DN of the object to be
606 :param index: Current creation order
607 :return: True if the object was created False otherwise"""
609 ret = handle_special_add(samdb, dn, names)
618 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
619 base=basedn, scope=SCOPE_SUBTREE,
620 controls=["search_options:1:2"])
622 delta = samdb.msg_diff(empty, reference[0])
626 if str(reference[0].get("cn")) == "RID Set":
627 for klass in reference[0].get("objectClass"):
628 if str(klass).lower() == "ridset":
631 if delta.get("objectSid"):
632 sid = str(ndr_unpack(security.dom_sid, reference[0]["objectSid"][0]))
633 m = re.match(r".*-(\d+)$", sid)
634 if m and int(m.group(1))>999:
635 delta.remove("objectSid")
636 for att in attrNotCopied:
638 for att in backlinked:
640 for att in dn_syntax_att:
641 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
643 if depend_on_yet_tobecreated is not None:
644 message(CHANGE, "Object %s depends on %s in attribute %s. "
645 "Delaying the creation" % (dn,
646 depend_on_yet_tobecreated, att))
651 message(CHANGE,"Object %s will be added" % dn)
652 samdb.add(delta, ["relax:0", "provision:0"])
654 message(CHANGE,"Object %s was skipped" % dn)
658 def gen_dn_index_hash(listMissing):
659 """Generate a hash associating the DN to its creation order
661 :param listMissing: List of DN
662 :return: Hash with DN as keys and creation order as values"""
664 for i in range(0, len(listMissing)):
665 hash[str(listMissing[i]).lower()] = i
668 def add_deletedobj_containers(ref_samdb, samdb, names):
669 """Add the object container: CN=Deleted Objects
671 This function create the container for each partition that need one and
672 then reference the object into the root of the partition
674 :param ref_samdb: Ldb object representing the SAM db of the reference
676 :param samdb: Ldb object representing the SAM db of the upgraded provision
677 :param names: List of key provision parameters"""
680 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
681 partitions = [str(names.rootdn), str(names.configdn)]
682 for part in partitions:
683 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
684 base=part, scope=SCOPE_SUBTREE,
686 controls=["show_deleted:0",
688 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
689 base=part, scope=SCOPE_SUBTREE,
691 controls=["show_deleted:0",
693 if len(ref_delObjCnt) > len(delObjCnt):
694 reference = ref_samdb.search(expression="cn=Deleted Objects",
695 base=part, scope=SCOPE_SUBTREE,
696 controls=["show_deleted:0",
699 delta = samdb.msg_diff(empty, reference[0])
701 delta.dn = Dn(samdb, str(reference[0]["dn"]))
702 for att in attrNotCopied:
705 modcontrols = ["relax:0", "provision:0"]
706 samdb.add(delta, modcontrols)
709 res = samdb.search(expression="(objectClass=*)", base=part,
711 attrs=["dn", "wellKnownObjects"])
713 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
717 wko = res[0]["wellKnownObjects"]
719 # The wellKnownObject that we want to add.
721 if str(o) == targetWKO:
723 listwko.append(str(o))
726 listwko.append(targetWKO)
729 delta.dn = Dn(samdb, str(res[0]["dn"]))
730 delta["wellKnownObjects"] = MessageElement(listwko,
735 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
736 """Add the missing object whose DN is the list
738 The function add the object if the objects on which it depends are
741 :param ref_samdb: Ldb object representing the SAM db of the reference
743 :param samdb: Ldb object representing the SAM db of the upgraded
745 :param dn: DN of the object to be added
746 :param names: List of key provision parameters
747 :param basedn: DN of the partition to be updated
748 :param list: List of DN to be added in the upgraded provision"""
753 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
755 listMissing = listDefered
757 hashMissing = gen_dn_index_hash(listMissing)
758 for dn in listMissing:
759 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
763 # DN can't be created because it depends on some
764 # other DN in the list
765 listDefered.append(dn)
767 if len(listDefered) != 0:
768 raise ProvisioningError("Unable to insert missing elements: "
769 "circular references")
771 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
772 """This function handle updates on links
774 :param samdb: An LDB object pointing to the updated provision
775 :param att: Attribute to update
776 :param basedn: The root DN of the provision
777 :param dn: The DN of the inspected object
778 :param value: The value of the attribute
779 :param ref_value: The value of this attribute in the reference provision
780 :param delta: The MessageElement object that will be applied for
781 transforming the current provision"""
783 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
792 newlinklist.append(str(v))
796 # for w2k domain level the reveal won't reveal anything ...
797 # it means that we can readd links that were removed on purpose ...
798 # Also this function in fact just accept add not removal
800 for e in res[0][att]:
802 # We put in the blacklist all the element that are in the "revealed"
803 # result and not in the "standard" result
804 # This element are links that were removed before and so that
805 # we don't wan't to readd
809 if not e in blacklist and not e in hash:
810 newlinklist.append(str(e))
813 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
820 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
821 hash_attr_usn, basedn, usns, samdb):
822 """ Check if we should keep the attribute modification or not
824 :param delta: A message diff object
825 :param att: An attribute
826 :param message: A function to print messages
827 :param reference: A message object for the current entry comming from
828 the reference provision.
829 :param current: A message object for the current entry commin from
830 the current provision.
831 :param hash_attr_usn: A dictionary with attribute name as keys,
832 USN and invocation id as values.
833 :param basedn: The DN of the partition
834 :param usns: A dictionary with invocation ID as keys and USN ranges
836 :param samdb: A ldb object pointing to the sam DB
838 :return: The modified message diff.
845 for att in list(delta):
846 if att in ["dn", "objectSid"]:
850 # We have updated by provision usn information so let's exploit
851 # replMetadataProperties
852 if att in forwardlinked:
853 curval = current[0].get(att, ())
854 refval = reference[0].get(att, ())
855 delta = handle_links(samdb, att, basedn, current[0]["dn"],
856 curval, refval, delta)
860 if isFirst and len(list(delta)) > 1:
862 txt = "%s\n" % (str(dn))
864 if handle_special_case(att, delta, reference, current, True, None, None):
865 # This attribute is "complicated" to handle and handling
866 # was done in handle_special_case
870 if hash_attr_usn.get(att):
871 [attrUSN, attInvId] = hash_attr_usn.get(att)
874 # If it's a replicated attribute and we don't have any USN
875 # information about it. It means that we never saw it before
877 # If it is a replicated attribute but we are not master on it
878 # (ie. not initially added in the provision we masterize).
880 if isReplicated(att):
883 message(CHANGE, "Non replicated attribute %s changed" % att)
886 if att == "nTSecurityDescriptor":
887 cursd = ndr_unpack(security.descriptor,
888 current[0]["nTSecurityDescriptor"][0])
889 refsd = ndr_unpack(security.descriptor,
890 reference[0]["nTSecurityDescriptor"][0])
892 diff = get_diff_sds(refsd, cursd, names.domainsid)
894 # FIXME find a way to have it only with huge huge verbose mode
895 # message(CHANGE, "%ssd are identical" % txt)
901 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
904 message(CHANGESD, "But the SD has been changed by someonelse "
905 "so it's impossible to know if the difference"
906 " cames from the modification or from a previous bug")
907 global dnNotToRecalculateFound
908 dnNotToRecalculateFound = True
910 dnToRecalculate.append(dn)
914 # This attribute was last modified by another DC forget
916 message(CHANGE, "%sAttribute: %s has been "
917 "created/modified/deleted by another DC. "
918 "Doing nothing" % (txt, att))
922 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
923 message(CHANGE, "%sAttribute: %s was not "
924 "created/modified/deleted during a "
925 "provision or upgradeprovision. Current "
926 "usn: %d. Doing nothing" % (txt, att,
932 if att == "defaultSecurityDescriptor":
935 message(CHANGE, "%sAttribute: %s will be modified"
936 "/deleted it was last modified "
937 "during a provision. Current usn: "
938 "%d" % (txt, att, attrUSN))
941 message(CHANGE, "%sAttribute: %s will be added because "
942 "it did not exist before" % (txt, att))
948 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
949 """ This function updates the object that are already present in the
952 :param ref_samdb: An LDB object pointing to the reference provision
953 :param samdb: An LDB object pointing to the updated provision
954 :param basedn: A string with the value of the base DN for the provision
956 :param listPresent: A list of object that is present in the provision
957 :param usns: A list of USN range modified by previous provision and
958 upgradeprovision grouped by invocation ID
961 # This hash is meant to speedup lookup of attribute name from an oid,
962 # it's for the replPropertyMetaData handling
964 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
965 controls=["search_options:1:2"], attrs=["attributeID",
969 strDisplay = str(e.get("lDAPDisplayName"))
970 hash_oid_name[str(e.get("attributeID"))] = strDisplay
972 msg = "Unable to insert missing elements: circular references"
973 raise ProvisioningError(msg)
976 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
977 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
978 message(CHANGE, "Using replPropertyMetadata for change selection")
979 for dn in listPresent:
980 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
983 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
984 scope=SCOPE_SUBTREE, controls=controls)
987 (str(current[0].dn) != str(reference[0].dn)) and
988 (str(current[0].dn).upper() == str(reference[0].dn).upper())
990 message(CHANGE, "Names are the same except for the case. "
991 "Renaming %s to %s" % (str(current[0].dn),
992 str(reference[0].dn)))
993 identic_rename(samdb, reference[0].dn)
994 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
998 delta = samdb.msg_diff(current[0], reference[0])
1000 for att in backlinked:
1003 for att in attrNotCopied:
1006 delta.remove("name")
1008 nb_items = len(list(delta))
1014 # Fetch the replPropertyMetaData
1015 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1016 scope=SCOPE_SUBTREE, controls=controls,
1017 attrs=["replPropertyMetaData"])
1018 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1019 res[0]["replPropertyMetaData"][0]).ctr
1023 # We put in this hash only modification
1024 # made on the current host
1025 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1026 if str(o.originating_invocation_id) in usns.keys():
1027 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1029 hash_attr_usn[att] = [-1, None]
1031 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1032 current, hash_attr_usn,
1033 basedn, usns, samdb)
1039 # Skip dn as the value is not really changed ...
1040 attributes=", ".join(delta.keys()[1:])
1042 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1043 # Let's try to reduce as much as possible the use of relax control
1044 for attr in delta.keys():
1045 if attr.lower() in relaxedatt:
1046 modcontrols = ["relax:0", "provision:0"]
1047 message(CHANGE, "%s is different from the reference one, changed"
1048 " attributes: %s\n" % (dn, attributes))
1050 samdb.modify(delta, modcontrols)
1053 def reload_full_schema(samdb, names):
1054 """Load the updated schema with all the new and existing classes
1057 :param samdb: An LDB object connected to the sam.ldb of the update
1059 :param names: List of key provision parameters
1062 schemadn = str(names.schemadn)
1063 current = samdb.search(expression="objectClass=*", base=schemadn,
1064 scope=SCOPE_SUBTREE)
1066 schema_ldif = "".join(samdb.write_ldif(ent, ldb.CHANGETYPE_NONE) for ent in current)
1068 prefixmap_data = b64encode(open(setup_path("prefixMap.txt"), 'rb').read()).decode('utf8')
1070 # We don't actually add this ldif, just parse it
1071 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1073 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1076 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1077 """Check differences between the reference provision and the upgraded one.
1079 It looks for all objects which base DN is name.
1081 This function will also add the missing object and update existing object
1082 to add or remove attributes that were missing.
1084 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1086 :param samdb: An LDB object connected to the sam.ldb of the update
1088 :param basedn: String value of the DN of the partition
1089 :param names: List of key provision parameters
1090 :param schema: A Schema object
1091 :param provisionUSNs: A dictionary with range of USN modified during provision
1092 or upgradeprovision. Ranges are grouped by invocationID.
1093 :param prereloadfunc: A function that must be executed just before the reload
1104 # Connect to the reference provision and get all the attribute in the
1105 # partition referred by name
1106 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1107 scope=SCOPE_SUBTREE, attrs=["dn"],
1108 controls=["search_options:1:2"])
1110 current = samdb.search(expression="objectClass=*", base=basedn,
1111 scope=SCOPE_SUBTREE, attrs=["dn"],
1112 controls=["search_options:1:2"])
1113 # Create a hash for speeding the search of new object
1114 for i in range(0, len(reference)):
1115 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1117 # Create a hash for speeding the search of existing object in the
1119 for i in range(0, len(current)):
1120 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1123 for k in hash_new.keys():
1125 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1126 listMissing.append(hash_new[k])
1128 listPresent.append(hash_new[k])
1130 # Sort the missing object in order to have object of the lowest level
1131 # first (which can be containers for higher level objects)
1132 listMissing.sort(key=cmp_to_key(dn_sort))
1133 listPresent.sort(key=cmp_to_key(dn_sort))
1135 # The following lines is to load the up to
1136 # date schema into our current LDB
1137 # a complete schema is needed as the insertion of attributes
1138 # and class is done against it
1139 # and the schema is self validated
1140 samdb.set_schema(schema)
1142 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1143 add_deletedobj_containers(ref_samdb, samdb, names)
1145 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1148 message(SIMPLE, "Reloading a merged schema, which might trigger "
1149 "reindexing so please be patient")
1150 reload_full_schema(samdb, names)
1151 message(SIMPLE, "Schema reloaded!")
1153 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1155 message(SIMPLE, "There are %d changed objects" % (changed))
1158 except Exception as err:
1159 message(ERROR, "Exception during upgrade of samdb:")
1160 (typ, val, tb) = sys.exc_info()
1161 traceback.print_exception(typ, val, tb)
1165 def check_updated_sd(ref_sam, cur_sam, names):
1166 """Check if the security descriptor in the upgraded provision are the same
1169 :param ref_sam: A LDB object connected to the sam.ldb file used as
1170 the reference provision
1171 :param cur_sam: A LDB object connected to the sam.ldb file used as
1173 :param names: List of key provision parameters"""
1174 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1175 scope=SCOPE_SUBTREE,
1176 attrs=["dn", "nTSecurityDescriptor"],
1177 controls=["search_options:1:2"])
1178 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1179 scope=SCOPE_SUBTREE,
1180 attrs=["dn", "nTSecurityDescriptor"],
1181 controls=["search_options:1:2"])
1183 for i in range(0, len(reference)):
1184 refsd_blob = reference[i]["nTSecurityDescriptor"][0]
1185 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1188 for i in range(0, len(current)):
1189 key = str(current[i]["dn"]).lower()
1191 cursd_blob = current[i]["nTSecurityDescriptor"][0]
1192 cursd = ndr_unpack(security.descriptor,
1194 if cursd_blob != hash[key]:
1195 refsd = ndr_unpack(security.descriptor,
1197 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1199 message(CHANGESD, "On object %s ACL is different"
1200 " \n%s" % (current[i]["dn"], txt))
1204 def fix_wellknown_sd(samdb, names):
1205 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1206 This is needed because some provision use to have broken SD on containers
1208 :param samdb: An LDB object pointing to the sam of the current provision
1209 :param names: A list of key provision parameters
1212 list_wellknown_dns = []
1214 subcontainers = get_wellknown_sds(samdb)
1216 for [dn, descriptor_fn] in subcontainers:
1217 list_wellknown_dns.append(dn)
1218 if dn in dnToRecalculate:
1221 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1222 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1223 "nTSecurityDescriptor" )
1225 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1227 return list_wellknown_dns
1229 def rebuild_sd(samdb, names):
1230 """Rebuild security descriptor of the current provision from scratch
1232 During the different pre release of samba4 security descriptors
1233 (SD) were notarly broken (up to alpha11 included)
1235 This function allows one to get them back in order, this function works
1236 only after the database comparison that --full mode uses and which
1237 populates the dnToRecalculate and dnNotToRecalculate lists.
1239 The idea is that the SD can be safely recalculated from scratch to get it right.
1241 :param names: List of key provision parameters"""
1243 listWellknown = fix_wellknown_sd(samdb, names)
1245 if len(dnToRecalculate) != 0:
1246 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1247 % (len(dnToRecalculate)))
1249 for dn in dnToRecalculate:
1250 # well known SDs have already been reset
1251 if dn in listWellknown:
1255 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1257 descr = get_empty_descriptor(names.domainsid)
1258 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1259 "nTSecurityDescriptor")
1260 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1261 except LdbError as e:
1262 samdb.transaction_cancel()
1263 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1265 attrs=["nTSecurityDescriptor"],
1266 controls=["sd_flags:1:%d" % sd_flags])
1267 badsd = ndr_unpack(security.descriptor,
1268 res[0]["nTSecurityDescriptor"][0])
1269 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1272 def hasATProvision(samdb):
1273 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1277 if entry is not None and len(entry) == 1:
1282 def removeProvisionUSN(samdb):
1283 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1284 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1288 empty.dn = entry[0].dn
1289 delta = samdb.msg_diff(entry[0], empty)
1291 delta.dn = entry[0].dn
1294 def remove_stored_generated_attrs(paths, creds, session, lp):
1295 """Remove previously stored constructed attributes
1297 :param paths: List of paths for different provision objects
1298 from the upgraded provision
1299 :param creds: A credential object
1300 :param session: A session object
1301 :param lp: A line parser object
1302 :return: An associative array whose key are the different constructed
1303 attributes and the value the dn where this attributes were found.
1307 def simple_update_basesamdb(newpaths, paths, names):
1308 """Update the provision container db: sam.ldb
1309 This function is aimed at very old provision (before alpha9)
1311 :param newpaths: List of paths for different provision objects
1312 from the reference provision
1313 :param paths: List of paths for different provision objects
1314 from the upgraded provision
1315 :param names: List of key provision parameters"""
1317 message(SIMPLE, "Copy samdb")
1318 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1320 message(SIMPLE, "Update partitions filename if needed")
1321 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1322 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1323 usersldb = os.path.join(paths.private_dir, "users.ldb")
1324 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1326 if not os.path.isdir(samldbdir):
1328 os.chmod(samldbdir, 0o700)
1329 if os.path.isfile(schemaldb):
1330 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1331 "%s.ldb"%str(names.schemadn).upper()))
1332 os.remove(schemaldb)
1333 if os.path.isfile(usersldb):
1334 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1335 "%s.ldb"%str(names.rootdn).upper()))
1337 if os.path.isfile(configldb):
1338 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1339 "%s.ldb"%str(names.configdn).upper()))
1340 os.remove(configldb)
1343 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1344 """Upgrade the SAM DB contents for all the provision partitions
1346 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1348 :param samdb: An LDB object connected to the sam.ldb of the update
1350 :param names: List of key provision parameters
1351 :param provisionUSNs: A dictionary with range of USN modified during provision
1352 or upgradeprovision. Ranges are grouped by invocationID.
1353 :param schema: A Schema object that represent the schema of the provision
1354 :param prereloadfunc: A function that must be executed just before the reload
1358 message(SIMPLE, "Starting update of samdb")
1359 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1360 schema, provisionUSNs, prereloadfunc)
1362 message(SIMPLE, "Update of samdb finished")
1365 message(SIMPLE, "Update failed")
1369 def backup_provision(samdb, paths, dir, only_db):
1370 """This function backup the provision files so that a rollback
1373 :param paths: Paths to different objects
1374 :param dir: Directory where to store the backup
1375 :param only_db: Skip sysvol for users with big sysvol
1378 # Currently we default to tdb for the backend store type
1380 backend_store = "tdb"
1381 res = samdb.search(base="@PARTITION",
1382 scope=ldb.SCOPE_BASE,
1383 attrs=["backendStore"])
1384 if "backendStore" in res[0]:
1385 backend_store = str(res[0]["backendStore"][0])
1388 if paths.sysvol and not only_db:
1389 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1391 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1392 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1393 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1394 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1395 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1396 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1397 shutil.copy2(paths.smbconf, dir)
1398 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1400 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1401 if not os.path.isdir(samldbdir):
1402 samldbdir = paths.private_dir
1403 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1404 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1405 usersldb = os.path.join(paths.private_dir, "users.ldb")
1406 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1407 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1408 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1410 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
1412 for ldb_name in os.listdir(samldbdir):
1413 if not ldb_name.endswith("-lock"):
1414 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1415 mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1416 os.path.join(dir, "sam.ldb.d", ldb_name))
1418 tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1419 os.path.join(dir, "sam.ldb.d", ldb_name))
1422 def sync_calculated_attributes(samdb, names):
1423 """Synchronize attributes used for constructed ones, with the
1424 old constructed that were stored in the database.
1426 This apply for instance to msds-keyversionnumber that was
1427 stored and that is now constructed from replpropertymetadata.
1429 :param samdb: An LDB object attached to the currently upgraded samdb
1430 :param names: Various key parameter about current provision.
1432 listAttrs = ["msDs-KeyVersionNumber"]
1433 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1434 if "msDs-KeyVersionNumber" in hash:
1435 increment_calculated_keyversion_number(samdb, names.rootdn,
1436 hash["msDs-KeyVersionNumber"])
1438 # Synopsis for updateprovision
1439 # 1) get path related to provision to be update (called current)
1440 # 2) open current provision ldbs
1441 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1443 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1444 # by either upgradeprovision or provision
1445 # 5) creation of a new provision the latest version of provision script
1446 # (called reference)
1447 # 6) get reference provision paths
1448 # 7) open reference provision ldbs
1449 # 8) setup helpers data that will help the update process
1450 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1451 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1452 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1453 # 10)get the oemInfo field, this field contains information about the different
1454 # provision that have been done
1455 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1456 # A) When alpha9 or alphaxx not specified (default)
1457 # The base sam.ldb file is updated by looking at the difference between
1458 # referrence one and the current one. Everything is copied with the
1459 # exception of lastProvisionUSN attributes.
1460 # B) Other case (it reflect that that provision was done before alpha9)
1461 # The base sam.ldb of the reference provision is copied over
1462 # the current one, if necessary ldb related to partitions are moved
1464 # The highest used USN is fetched so that changed by upgradeprovision
1465 # usn can be tracked
1466 # 12)A Schema object is created, it will be used to provide a complete
1467 # schema to current provision during update (as the schema of the
1468 # current provision might not be complete and so won't allow some
1469 # object to be created)
1470 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1471 # 14)The secrets db is updated by pull all the difference from the reference
1472 # provision into the current provision
1473 # 15)As the previous step has most probably modified the password stored in
1474 # in secret for the current DC, a new password is generated,
1475 # the kvno is bumped and the entry in samdb is also updated
1476 # 16)For current provision older than alpha9, we must fix the SD a little bit
1477 # administrator to update them because SD used to be generated with the
1478 # system account before alpha9.
1479 # 17)The highest usn modified so far is searched in the database it will be
1480 # the upper limit for usn modified during provision.
1481 # This is done before potential SD recalculation because we do not want
1482 # SD modified during recalculation to be marked as modified during provision
1483 # (and so possibly remplaced at next upgradeprovision)
1484 # 18)Rebuilt SD if the flag indicate to do so
1485 # 19)Check difference between SD of reference provision and those of the
1486 # current provision. The check is done by getting the sddl representation
1487 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1488 # Each part is verified separetly, for dacl and sacl ACL is splited into
1489 # ACEs and each ACE is verified separately (so that a permutation in ACE
1490 # didn't raise as an error).
1491 # 20)The oemInfo field is updated to add information about the fact that the
1492 # provision has been updated by the upgradeprovision version xxx
1493 # (the version is the one obtained when starting samba with the --version
1495 # 21)Check if the current provision has all the settings needed for dynamic
1496 # DNS update to work (that is to say the provision is newer than
1497 # january 2010). If not dns configuration file from reference provision
1498 # are copied in a sub folder and the administrator is invited to
1499 # do what is needed.
1500 # 22)If the lastProvisionUSN attribute was present it is updated to add
1501 # the range of usns modified by the current upgradeprovision
1504 # About updating the sam DB
1505 # The update takes place in update_partition function
1506 # This function read both current and reference provision and list all
1507 # the available DN of objects
1508 # If the string representation of a DN in reference provision is
1509 # equal to the string representation of a DN in current provision
1510 # (without taking care of case) then the object is flaged as being
1511 # present. If the object is not present in current provision the object
1512 # is being flaged as missing in current provision. Object present in current
1513 # provision but not in reference provision are ignored.
1514 # Once the list of objects present and missing is done, the deleted object
1515 # containers are created in the differents partitions (if missing)
1517 # Then the function add_missing_entries is called
1518 # This function will go through the list of missing entries by calling
1519 # add_missing_object for the given object. If this function returns 0
1520 # it means that the object needs some other object in order to be created
1521 # The object is reappended at the end of the list to be created later
1522 # (and preferably after all the needed object have been created)
1523 # The function keeps on looping on the list of object to be created until
1524 # it's empty or that the number of deferred creation is equal to the number
1525 # of object that still needs to be created.
1527 # The function add_missing_object will first check if the object can be created.
1528 # That is to say that it didn't depends other not yet created objects
1529 # If requisit can't be fullfilled it exists with 0
1530 # Then it will try to create the missing entry by creating doing
1531 # an ldb_message_diff between the object in the reference provision and
1533 # This resulting object is filtered to remove all the back link attribute
1534 # (ie. memberOf) as they will be created by the other linked object (ie.
1535 # the one with the member attribute)
1536 # All attributes specified in the attrNotCopied array are
1537 # also removed it's most of the time generated attributes
1539 # After missing entries have been added the update_partition function will
1540 # take care of object that exist but that need some update.
1541 # In order to do so the function update_present is called with the list
1542 # of object that are present in both provision and that might need an update.
1544 # This function handle first case mismatch so that the DN in the current
1545 # provision have the same case as in reference provision
1547 # It will then construct an associative array consiting of attributes as
1548 # key and invocationid as value( if the originating invocation id is
1549 # different from the invocation id of the current DC the value is -1 instead).
1551 # If the range of provision modified attributes is present, the function will
1552 # use the replMetadataProperty update method which is the following:
1553 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1554 # creationTime, msDs-KeyVersionNumber, oEMInformation
1555 # Check for each attribute if its usn is within one of the modified by
1556 # provision range and if its originating id is the invocation id of the
1557 # current DC, then validate the update from reference to current.
1558 # If not or if there is no replMetatdataProperty for this attribute then we
1560 # Otherwise (case the range of provision modified attribute is not present) it
1561 # use the following process:
1562 # All attributes that need to be added are accepted at the exeption of those
1563 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1564 # correct flags specified.
1565 # For attributes that need to be modified or removed, a check is performed
1566 # in OverwrittenAtt, if the attribute is present and the modification flag
1567 # (remove, delete) is one of those listed for this attribute then modification
1568 # is accepted. For complicated handling of attribute update, the control is passed
1569 # to handle_special_case
1573 if __name__ == '__main__':
1574 defSDmodified = False
1576 # From here start the big steps of the program
1577 # 1) First get files paths
1578 paths = get_paths(param, smbconf=smbconf)
1579 # Get ldbs with the system session, it is needed for searching
1580 # provision parameters
1581 session = system_session()
1583 # This variable will hold the last provision USN once if it exists.
1586 ldbs = get_ldbs(paths, creds, session, lp)
1587 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1588 prefix="backupprovision")
1589 backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1591 ldbs.startTransactions()
1593 # 3) Guess all the needed names (variables in fact) from the current
1595 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1598 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1599 if lastProvisionUSNs is not None:
1601 for k in lastProvisionUSNs.keys():
1602 for r in lastProvisionUSNs[k]:
1606 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1607 (len(lastProvisionUSNs.keys()), v /2 ))
1609 if lastProvisionUSNs.get("default") is not None:
1610 message(CHANGE, "Old style for usn ranges used")
1611 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1612 del lastProvisionUSNs["default"]
1614 message(SIMPLE, "Your provision lacks provision range information")
1615 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1616 ldbs.groupedRollback()
1618 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1619 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1620 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1621 " of hundred of objects or more")
1622 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1625 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1627 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1630 # Objects will be created with the admin session
1631 # (not anymore system session)
1632 adm_session = admin_session(lp, str(names.domainsid))
1633 # So we reget handle on objects
1634 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1636 if not sanitychecks(ldbs.sam, names):
1637 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1638 "Check the messages and correct the errors "
1639 "before rerunning upgradeprovision")
1640 ldbs.groupedRollback()
1643 # Let's see provision parameters
1644 print_provision_key_parameters(names)
1646 # 5) With all this information let's create a fresh new provision used as
1648 message(SIMPLE, "Creating a reference provision")
1649 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1650 prefix="referenceprovision")
1651 result = newprovision(names, session, smbconf, provisiondir,
1652 provision_logger, base_schema="2008_R2")
1653 result.report_logger(provision_logger)
1657 # We need to get a list of object which SD is directly computed from
1658 # defaultSecurityDescriptor.
1659 # This will allow us to know which object we can rebuild the SD in case
1660 # of change of the parent's SD or of the defaultSD.
1661 # Get file paths of this new provision
1662 newpaths = get_paths(param, targetdir=provisiondir)
1663 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1664 new_ldbs.startTransactions()
1666 populateNotReplicated(new_ldbs.sam, names.schemadn)
1667 # 8) Populate some associative array to ease the update process
1668 # List of attribute which are link and backlink
1669 populate_links(new_ldbs.sam, names.schemadn)
1670 # List of attribute with ASN DN synthax)
1671 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1672 # 9) (now skipped, was copy of privileges.ldb)
1674 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1675 # Do some modification on sam.ldb
1676 ldbs.groupedCommit()
1677 new_ldbs.groupedCommit()
1681 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1683 # Starting from alpha9 we can consider that the structure is quite ok
1684 # and that we should do only dela
1685 deltaattr = delta_update_basesamdb(newpaths.samdb,
1693 simple_update_basesamdb(newpaths, paths, names)
1694 ldbs = get_ldbs(paths, creds, session, lp)
1695 removeProvisionUSN(ldbs.sam)
1697 ldbs.startTransactions()
1698 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1699 new_ldbs.startTransactions()
1702 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1703 # We create a closure that will be invoked just before schema reload
1704 def schemareloadclosure():
1705 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1706 options=["modules:"])
1708 if deltaattr is not None and len(deltaattr) > 1:
1711 deltaattr.remove("dn")
1712 for att in deltaattr:
1713 if att.lower() == "dn":
1715 if (deltaattr.get(att) is not None
1716 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1718 elif deltaattr.get(att) is None:
1721 message(CHANGE, "Applying delta to @ATTRIBUTES")
1722 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1723 basesam.modify(deltaattr)
1725 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1726 "there is not only add")
1729 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1730 schema, schemareloadclosure):
1731 message(SIMPLE, "Rolling back all changes. Check the cause"
1733 message(SIMPLE, "Your system is as it was before the upgrade")
1734 ldbs.groupedRollback()
1735 new_ldbs.groupedRollback()
1736 shutil.rmtree(provisiondir)
1739 # Try to reapply the change also when we do not change the sam
1740 # as the delta_upgrade
1741 schemareloadclosure()
1742 sync_calculated_attributes(ldbs.sam, names)
1743 res = ldbs.sam.search(expression="(samaccountname=dns)",
1744 scope=SCOPE_SUBTREE, attrs=["dn"],
1745 controls=["search_options:1:2"])
1747 message(SIMPLE, "You still have the old DNS object for managing "
1748 "dynamic DNS, but you didn't supply --full so "
1749 "a correct update can't be done")
1750 ldbs.groupedRollback()
1751 new_ldbs.groupedRollback()
1752 shutil.rmtree(provisiondir)
1755 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1757 res = ldbs.sam.search(expression="(samaccountname=dns)",
1758 scope=SCOPE_SUBTREE, attrs=["dn"],
1759 controls=["search_options:1:2"])
1762 ldbs.sam.delete(res[0]["dn"])
1763 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1764 scope=SCOPE_SUBTREE, attrs=["dn"])
1765 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1766 message(SIMPLE, "IMPORTANT!!! "
1767 "If you were using Dynamic DNS before you need "
1768 "to update your configuration, so that the "
1769 "tkey-gssapi-credential has the following value: "
1770 "DNS/%s.%s" % (names.netbiosname.lower(),
1771 names.realm.lower()))
1773 message(SIMPLE, "Update machine account")
1774 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1776 # 16) SD should be created with admin but as some previous acl were so wrong
1777 # that admin can't modify them we have first to recreate them with the good
1778 # form but with system account and then give the ownership to admin ...
1779 if opts.very_old_pre_alpha9:
1780 message(SIMPLE, "Fixing very old provision SD")
1781 rebuild_sd(ldbs.sam, names)
1783 # We calculate the max USN before recalculating the SD because we might
1784 # touch object that have been modified after a provision and we do not
1785 # want that the next upgradeprovision thinks that it has a green light
1789 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1791 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1792 # defSDmodified is set.
1793 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1794 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1795 "changed, recalculating them")
1796 ldbs.sam.set_session_info(adm_session)
1797 rebuild_sd(ldbs.sam, names)
1800 # Now we are quite confident in the recalculate process of the SD, we make
1801 # it optional. And we don't do it if there is DN that we must touch
1802 # as we are assured that on this DNs we will have differences !
1803 # Also the check must be done in a clever way as for the moment we just
1805 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1806 message(CHANGESD, "Checking recalculated SDs")
1807 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1810 updateOEMInfo(ldbs.sam, str(names.rootdn))
1812 check_for_DNS(newpaths.private_dir, paths.private_dir,
1813 newpaths.binddns_dir, paths.binddns_dir,
1816 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1817 if opts.full and (names.policyid is None or names.policyid_dc is None):
1818 update_policyids(names, ldbs.sam)
1822 update_gpo(paths, ldbs.sam, names, lp, message)
1823 except ProvisioningError as e:
1824 message(ERROR, "The policy for domain controller is missing. "
1825 "You should restart upgradeprovision with --full")
1827 ldbs.groupedCommit()
1828 new_ldbs.groupedCommit()
1829 message(SIMPLE, "Upgrade finished!")
1830 # remove reference provision now that everything is done !
1831 # So we have reindexed first if need when the merged schema was reloaded
1832 # (as new attributes could have quick in)
1833 # But the second part of the update (when we update existing objects
1834 # can also have an influence on indexing as some attribute might have their
1835 # searchflag modificated
1836 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1837 "after modification")
1838 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1839 message(SIMPLE, "Reindexing finished")
1841 shutil.rmtree(provisiondir)
1842 except Exception as err:
1843 message(ERROR, "A problem occurred while trying to upgrade your "
1844 "provision. A full backup is located at %s" % backupdir)
1845 if opts.debugall or opts.debugchange:
1846 (typ, val, tb) = sys.exc_info()
1847 traceback.print_exception(typ, val, tb)