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 automatically 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,
126 "operatingSystem": replace, "operatingSystemVersion":replace,
127 "adminPropertyPages":replace, "groupType":replace,
128 "wellKnownObjects":replace, "privilege":never,
129 "rIDAvailablePool": never,
130 "rIDNextRID": add, "rIDUsedPool": never,
131 "defaultSecurityDescriptor": replace + add,
132 "isMemberOfPartialAttributeSet": delete,
133 "attributeDisplayNames": replace + add,
134 "versionNumber": add}
136 dnNotToRecalculateFound = False
139 forwardlinked = set()
142 def define_what_to_log(opts):
146 if opts.debugchangesd:
147 what = what | CHANGESD
150 if opts.debugprovision:
151 what = what | PROVISION
153 what = what | CHANGEALL
157 parser = optparse.OptionParser("samba_upgradeprovision [options]")
158 sambaopts = options.SambaOptions(parser)
159 parser.add_option_group(sambaopts)
160 parser.add_option_group(options.VersionOptions(parser))
161 credopts = options.CredentialsOptions(parser)
162 parser.add_option_group(credopts)
163 parser.add_option("--setupdir", type="string", metavar="DIR",
164 help="directory with setup files")
165 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
166 parser.add_option("--debugguess", action="store_true",
167 help="Print information on which values are guessed")
168 parser.add_option("--debugchange", action="store_true",
169 help="Print information on what is different but won't be changed")
170 parser.add_option("--debugchangesd", action="store_true",
171 help="Print security descriptor differences")
172 parser.add_option("--debugall", action="store_true",
173 help="Print all available information (very verbose)")
174 parser.add_option("--db_backup_only", action="store_true",
175 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
176 parser.add_option("--full", action="store_true",
177 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
178 parser.add_option("--very-old-pre-alpha9", action="store_true",
179 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
181 opts = parser.parse_args()[0]
183 handler = logging.StreamHandler(sys.stdout)
184 upgrade_logger = logging.getLogger("upgradeprovision")
185 upgrade_logger.setLevel(logging.INFO)
187 upgrade_logger.addHandler(handler)
189 provision_logger = logging.getLogger("provision")
190 provision_logger.addHandler(handler)
192 whatToLog = define_what_to_log(opts)
194 def message(what, text):
195 """Print a message if this message type has been selected to be printed
197 :param what: Category of the message
198 :param text: Message to print """
199 if (whatToLog & what) or what <= 0:
200 upgrade_logger.info("%s", text)
202 if len(sys.argv) == 1:
203 opts.interactive = True
204 lp = sambaopts.get_loadparm()
205 smbconf = lp.configfile
207 creds = credopts.get_credentials(lp)
208 creds.set_kerberos_state(DONT_USE_KERBEROS)
212 def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
213 """Check if the provision has already the requirement for dynamic dns
215 :param refprivate: The path to the private directory of the reference
217 :param private: The path to the private directory of the upgraded
220 spnfile = "%s/spn_update_list" % private
221 dnsfile = "%s/dns_update_list" % private
223 if not os.path.exists(spnfile):
224 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
226 if not os.path.exists(dnsfile):
227 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
229 if not os.path.exists(binddns_dir):
230 os.mkdir(binddns_dir)
232 if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
235 namedfile = lp.get("dnsupdate:path")
237 namedfile = "%s/named.conf.update" % binddns_dir
238 if not os.path.exists(namedfile):
239 destdir = "%s/new_dns" % binddns_dir
240 dnsdir = "%s/dns" % binddns_dir
242 if not os.path.exists(destdir):
244 if not os.path.exists(dnsdir):
246 shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
247 shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
248 message(SIMPLE, "It seems that your provision did not integrate "
249 "new rules for dynamic dns update of domain related entries")
250 message(SIMPLE, "A copy of the new bind configuration files and "
251 "template has been put in %s, you should read them and "
252 "configure dynamic dns updates" % destdir)
255 def populate_links(samdb, schemadn):
256 """Populate an array with all the back linked attributes
258 This attributes that are modified automatically when
259 front attributes are changed
261 :param samdb: A LDB object for sam.ldb file
262 :param schemadn: DN of the schema for the partition"""
263 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
264 backlinked.extend(linkedAttHash.values())
265 for t in linkedAttHash.keys():
268 def isReplicated(att):
269 """ Indicate if the attribute is replicated or not
271 :param att: Name of the attribute to be tested
272 :return: True is the attribute is replicated, False otherwise
275 return (att not in not_replicated)
277 def populateNotReplicated(samdb, schemadn):
278 """Populate an array with all the attributes that are not replicated
280 :param samdb: A LDB object for sam.ldb file
281 :param schemadn: DN of the schema for the partition"""
282 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
283 str(schemadn)), scope=SCOPE_SUBTREE,
284 attrs=["lDAPDisplayName"])
286 not_replicated.append(str(elem["lDAPDisplayName"]))
289 def populate_dnsyntax(samdb, schemadn):
290 """Populate an array with all the attributes that have DN synthax
293 :param samdb: A LDB object for sam.ldb file
294 :param schemadn: DN of the schema for the partition"""
295 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
296 str(schemadn)), scope=SCOPE_SUBTREE,
297 attrs=["lDAPDisplayName"])
299 dn_syntax_att.append(elem["lDAPDisplayName"])
302 def sanitychecks(samdb, names):
303 """Make some checks before trying to update
305 :param samdb: An LDB object opened on sam.ldb
306 :param names: list of key provision parameters
307 :return: Status of check (1 for Ok, 0 for not Ok) """
308 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
309 scope=SCOPE_SUBTREE, attrs=["dn"],
310 controls=["search_options:1:2"])
312 print("No DC found. Your provision is most probably broken!")
315 print("Found %d domain controllers. For the moment "
316 "upgradeprovision is not able to handle an upgrade on a "
317 "domain with more than one DC. Please demote the other "
318 "DC(s) before upgrading" % len(res))
324 def print_provision_key_parameters(names):
325 """Do a a pretty print of provision parameters
327 :param names: list of key provision parameters """
328 message(GUESS, "rootdn :" + str(names.rootdn))
329 message(GUESS, "configdn :" + str(names.configdn))
330 message(GUESS, "schemadn :" + str(names.schemadn))
331 message(GUESS, "serverdn :" + str(names.serverdn))
332 message(GUESS, "netbiosname :" + names.netbiosname)
333 message(GUESS, "defaultsite :" + names.sitename)
334 message(GUESS, "dnsdomain :" + names.dnsdomain)
335 message(GUESS, "hostname :" + names.hostname)
336 message(GUESS, "domain :" + names.domain)
337 message(GUESS, "realm :" + names.realm)
338 message(GUESS, "invocationid:" + names.invocation)
339 message(GUESS, "policyguid :" + names.policyid)
340 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
341 message(GUESS, "domainsid :" + str(names.domainsid))
342 message(GUESS, "domainguid :" + names.domainguid)
343 message(GUESS, "ntdsguid :" + names.ntdsguid)
344 message(GUESS, "domainlevel :" + str(names.domainlevel))
347 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
348 """Define more complicate update rules for some attributes
350 :param att: The attribute to be updated
351 :param delta: A messageElement object that correspond to the difference
352 between the updated object and the reference one
353 :param new: The reference object
354 :param old: The Updated object
355 :param useReplMetadata: A boolean that indicate if the update process
356 use replPropertyMetaData to decide what has to be updated.
357 :param basedn: The base DN of the provision
358 :param aldb: An ldb object used to build DN
359 :return: True to indicate that the attribute should be kept, False for
362 # We do most of the special case handle if we do not have the
363 # highest usn as otherwise the replPropertyMetaData will guide us more
365 if not useReplMetadata:
366 flag = delta.get(att).flags()
367 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
368 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
369 "CN=Services,CN=Configuration,%s" % basedn)
372 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
373 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
375 message(SIMPLE, "We suggest that you change the userAccountControl"
376 " for user Administrator from value %d to %d" %
377 (int(str(old[0][att])), int(str(new[0][att]))))
379 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
380 if (int(str(old[0][att])) == 0):
381 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
384 if (att == "member" and flag == FLAG_MOD_REPLACE):
388 for elem in old[0][att]:
389 hash[str(elem).lower()]=1
390 newval.append(str(elem))
392 for elem in new[0][att]:
393 if not str(elem).lower() in hash:
395 newval.append(str(elem))
397 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
402 if (att in ("gPLink", "gPCFileSysPath") and
403 flag == FLAG_MOD_REPLACE and
404 str(new[0].dn).lower() == str(old[0].dn).lower()):
408 if att == "forceLogoff":
409 ref=0x8000000000000000
410 oldval=int(old[0][att][0])
411 newval=int(new[0][att][0])
412 ref == old and ref == abs(new)
415 if att in ("adminDisplayName", "adminDescription"):
418 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
419 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
422 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
423 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
426 if (str(old[0].dn) == "%s" % (str(names.rootdn))
427 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
429 #Allow to change revision of ForestUpdates objects
430 if (att == "revision" or att == "objectVersion"):
431 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
433 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
436 # This is a bit of special animal as we might have added
437 # already SPN entries to the list that has to be modified
438 # So we go in detail to try to find out what has to be added ...
439 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
443 for elem in old[0][att]:
445 newval.append(str(elem))
447 for elem in new[0][att]:
448 if not str(elem) in hash:
450 newval.append(str(elem))
452 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
459 def dump_denied_change(dn, att, flagtxt, current, reference):
460 """Print detailed information about why a change is denied
462 :param dn: DN of the object which attribute is denied
463 :param att: Attribute that was supposed to be upgraded
464 :param flagtxt: Type of the update that should be performed
465 (add, change, remove, ...)
466 :param current: Value(s) of the current attribute
467 :param reference: Value(s) of the reference attribute"""
469 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
470 + " must not be changed/removed. Discarding the change")
471 if att == "objectSid" :
472 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
473 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
474 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
475 message(CHANGE, "old : %s" % int64range2str(current[0]))
476 message(CHANGE, "new : %s" % int64range2str(reference[0]))
479 for e in range(0, len(current)):
480 message(CHANGE, "old %d : %s" % (i, str(current[e])))
482 if reference is not None:
484 for e in range(0, len(reference)):
485 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
488 def handle_special_add(samdb, dn, names):
489 """Handle special operation (like remove) on some object needed during
492 This is mostly due to wrong creation of the object in previous provision.
493 :param samdb: An Ldb object representing the SAM database
494 :param dn: DN of the object to inspect
495 :param names: list of key provision parameters
499 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
501 #This entry was misplaced lets remove it if it exists
502 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
505 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
507 #This entry was misplaced lets remove it if it exists
508 dntoremove = "CN=Certificate Service DCOM Access,"\
509 "CN=Users, %s" % names.rootdn
511 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
513 #This entry was misplaced lets remove it if it exists
514 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
516 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
518 #This entry was misplaced lets remove it if it exists
519 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
521 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
522 "CN=Configuration,%s" % names.rootdn)
524 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
525 "CN=WellKnown Security Principals,"
526 "CN=Configuration,%s" % names.rootdn)
528 res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
529 base=str(names.rootdn),
530 scope=SCOPE_SUBTREE, attrs=["dn"],
531 controls=["search_options:1:2"])
533 res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
534 base=str(names.rootdn),
535 scope=SCOPE_SUBTREE, attrs=["dn"],
536 controls=["search_options:1:2"])
538 if len(res) > 0 and len(res2) == 0:
539 message(CHANGE, "Existing object %s must be replaced by %s. "
540 "Renaming old object" % (str(oldDn), str(dn)))
541 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
545 if dntoremove is not None:
546 res = samdb.search(expression="(cn=RID Set)",
547 base=str(names.rootdn),
548 scope=SCOPE_SUBTREE, attrs=["dn"],
549 controls=["search_options:1:2"])
553 res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
554 base=str(names.rootdn),
555 scope=SCOPE_SUBTREE, attrs=["dn"],
556 controls=["search_options:1:2"])
558 message(CHANGE, "Existing object %s must be replaced by %s. "
559 "Removing old object" % (dntoremove, str(dn)))
560 samdb.delete(res[0]["dn"])
566 def check_dn_nottobecreated(hash, index, listdn):
567 """Check if one of the DN present in the list has a creation order
568 greater than the current.
570 Hash is indexed by dn to be created, with each key
571 is associated the creation order.
573 First dn to be created has the creation order 0, second has 1, ...
574 Index contain the current creation order
576 :param hash: Hash holding the different DN of the object to be
578 :param index: Current creation order
579 :param listdn: List of DNs on which the current DN depends on
580 :return: None if the current object do not depend on other
581 object or if all object have been created before."""
585 key = str(dn).lower()
586 if key in hash and hash[key] > index:
592 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
593 """Add a new object if the dependencies are satisfied
595 The function add the object if the object on which it depends are already
598 :param ref_samdb: Ldb object representing the SAM db of the reference
600 :param samdb: Ldb object representing the SAM db of the upgraded
602 :param dn: DN of the object to be added
603 :param names: List of key provision parameters
604 :param basedn: DN of the partition to be updated
605 :param hash: Hash holding the different DN of the object to be
607 :param index: Current creation order
608 :return: True if the object was created False otherwise"""
610 ret = handle_special_add(samdb, dn, names)
619 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
620 base=basedn, scope=SCOPE_SUBTREE,
621 controls=["search_options:1:2"])
623 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))
649 delta.dn = ldb.Dn(samdb, str(dn))
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 names: List of key provision parameters
746 :param basedn: DN of the partition to be updated
747 :param list: List of DN to be added in the upgraded provision"""
752 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
754 listMissing = listDefered
756 hashMissing = gen_dn_index_hash(listMissing)
757 for dn in listMissing:
758 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
762 # DN can't be created because it depends on some
763 # other DN in the list
764 listDefered.append(dn)
766 if len(listDefered) != 0:
767 raise ProvisioningError("Unable to insert missing elements: "
768 "circular references")
770 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
771 """This function handle updates on links
773 :param samdb: An LDB object pointing to the updated provision
774 :param att: Attribute to update
775 :param basedn: The root DN of the provision
776 :param dn: The DN of the inspected object
777 :param value: The value of the attribute
778 :param ref_value: The value of this attribute in the reference provision
779 :param delta: The MessageElement object that will be applied for
780 transforming the current provision"""
782 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
791 newlinklist.append(str(v))
795 # for w2k domain level the reveal won't reveal anything ...
796 # it means that we can readd links that were removed on purpose ...
797 # Also this function in fact just accept add not removal
799 for e in res[0][att]:
801 # We put in the blacklist all the element that are in the "revealed"
802 # result and not in the "standard" result
803 # These elements are links that were removed before. We don't want
808 if not e in blacklist and not e in hash:
809 newlinklist.append(str(e))
812 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
819 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
820 hash_attr_usn, basedn, usns, samdb):
821 """ Check if we should keep the attribute modification or not
823 :param delta: A message diff object
824 :param att: An attribute
825 :param message: A function to print messages
826 :param reference: A message object for the current entry coming from
827 the reference provision.
828 :param current: A message object for the current entry commin from
829 the current provision.
830 :param hash_attr_usn: A dictionary with attribute name as keys,
831 USN and invocation id as values.
832 :param basedn: The DN of the partition
833 :param usns: A dictionary with invocation ID as keys and USN ranges
835 :param samdb: A ldb object pointing to the sam DB
837 :return: The modified message diff.
844 for att in list(delta):
845 if att in ["dn", "objectSid"]:
849 # We have updated by provision usn information so let's exploit
850 # replMetadataProperties
851 if att in forwardlinked:
852 curval = current[0].get(att, ())
853 refval = reference[0].get(att, ())
854 delta = handle_links(samdb, att, basedn, current[0]["dn"],
855 curval, refval, delta)
859 if isFirst and len(list(delta)) > 1:
861 txt = "%s\n" % (str(dn))
863 if handle_special_case(att, delta, reference, current, True, None, None):
864 # This attribute is "complicated" to handle and handling
865 # was done in handle_special_case
869 if hash_attr_usn.get(att):
870 [attrUSN, attInvId] = hash_attr_usn.get(att)
873 # If it's a replicated attribute and we don't have any USN
874 # information about it. It means that we never saw it before
876 # If it is a replicated attribute but we are not master on it
877 # (ie. not initially added in the provision we masterize).
879 if isReplicated(att):
882 message(CHANGE, "Non replicated attribute %s changed" % att)
885 if att == "nTSecurityDescriptor":
886 cursd = ndr_unpack(security.descriptor,
887 current[0]["nTSecurityDescriptor"][0])
888 refsd = ndr_unpack(security.descriptor,
889 reference[0]["nTSecurityDescriptor"][0])
891 diff = get_diff_sds(refsd, cursd, names.domainsid)
893 # FIXME find a way to have it only with huge huge verbose mode
894 # message(CHANGE, "%ssd are identical" % txt)
900 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
903 message(CHANGESD, "But the SD has been changed by someonelse "
904 "so it's impossible to know if the difference"
905 " cames from the modification or from a previous bug")
906 global dnNotToRecalculateFound
907 dnNotToRecalculateFound = True
909 dnToRecalculate.append(dn)
913 # This attribute was last modified by another DC forget
915 message(CHANGE, "%sAttribute: %s has been "
916 "created/modified/deleted by another DC. "
917 "Doing nothing" % (txt, att))
921 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
922 message(CHANGE, "%sAttribute: %s was not "
923 "created/modified/deleted during a "
924 "provision or upgradeprovision. Current "
925 "usn: %d. Doing nothing" % (txt, att,
931 if att == "defaultSecurityDescriptor":
934 message(CHANGE, "%sAttribute: %s will be modified"
935 "/deleted it was last modified "
936 "during a provision. Current usn: "
937 "%d" % (txt, att, attrUSN))
940 message(CHANGE, "%sAttribute: %s will be added because "
941 "it did not exist before" % (txt, att))
947 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
948 """ This function updates the object that are already present in the
951 :param ref_samdb: An LDB object pointing to the reference provision
952 :param samdb: An LDB object pointing to the updated provision
953 :param basedn: A string with the value of the base DN for the provision
955 :param listPresent: A list of object that is present in the provision
956 :param usns: A list of USN range modified by previous provision and
957 upgradeprovision grouped by invocation ID
960 # This hash is meant to speedup lookup of attribute name from an oid,
961 # it's for the replPropertyMetaData handling
963 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
964 controls=["search_options:1:2"], attrs=["attributeID",
968 strDisplay = str(e.get("lDAPDisplayName"))
969 hash_oid_name[str(e.get("attributeID"))] = strDisplay
971 msg = "Unable to insert missing elements: circular references"
972 raise ProvisioningError(msg)
975 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
976 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
977 message(CHANGE, "Using replPropertyMetadata for change selection")
978 for dn in listPresent:
979 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
982 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
983 scope=SCOPE_SUBTREE, controls=controls)
986 (str(current[0].dn) != str(reference[0].dn)) and
987 (str(current[0].dn).upper() == str(reference[0].dn).upper())
989 message(CHANGE, "Names are the same except for the case. "
990 "Renaming %s to %s" % (str(current[0].dn),
991 str(reference[0].dn)))
992 identic_rename(samdb, reference[0].dn)
993 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
997 delta = samdb.msg_diff(current[0], reference[0])
999 for att in backlinked:
1002 for att in attrNotCopied:
1005 delta.remove("name")
1007 nb_items = len(list(delta))
1013 # Fetch the replPropertyMetaData
1014 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1015 scope=SCOPE_SUBTREE, controls=controls,
1016 attrs=["replPropertyMetaData"])
1017 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1018 res[0]["replPropertyMetaData"][0]).ctr
1022 # We put in this hash only modification
1023 # made on the current host
1024 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1025 if str(o.originating_invocation_id) in usns.keys():
1026 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1028 hash_attr_usn[att] = [-1, None]
1030 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1031 current, hash_attr_usn,
1032 basedn, usns, samdb)
1034 delta.dn = dn.copy(delta.ldb)
1038 # Skip dn as the value is not really changed ...
1039 attributes=", ".join(delta.keys()[1:])
1041 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1042 # Let's try to reduce as much as possible the use of relax control
1043 for attr in delta.keys():
1044 if attr.lower() in relaxedatt:
1045 modcontrols = ["relax:0", "provision:0"]
1046 message(CHANGE, "%s is different from the reference one, changed"
1047 " attributes: %s\n" % (dn, attributes))
1049 samdb.modify(delta, modcontrols)
1052 def reload_full_schema(samdb, names):
1053 """Load the updated schema with all the new and existing classes
1056 :param samdb: An LDB object connected to the sam.ldb of the update
1058 :param names: List of key provision parameters
1061 schemadn = str(names.schemadn)
1062 current = samdb.search(expression="objectClass=*", base=schemadn,
1063 scope=SCOPE_SUBTREE)
1065 schema_ldif = "".join(samdb.write_ldif(ent, ldb.CHANGETYPE_NONE) for ent in current)
1067 prefixmap_data = b64encode(open(setup_path("prefixMap.txt"), 'rb').read()).decode('utf8')
1069 # We don't actually add this ldif, just parse it
1070 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1072 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1075 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1076 """Check differences between the reference provision and the upgraded one.
1078 It looks for all objects which base DN is name.
1080 This function will also add the missing object and update existing object
1081 to add or remove attributes that were missing.
1083 :param ref_samdb: An LDB object connected to the sam.ldb of the
1085 :param samdb: An LDB object connected to the sam.ldb of the update
1087 :param basedn: String value of the DN of the partition
1088 :param names: List of key provision parameters
1089 :param schema: A Schema object
1090 :param provisionUSNs: A dictionary with range of USN modified during provision
1091 or upgradeprovision. Ranges are grouped by invocationID.
1092 :param prereloadfunc: A function that must be executed just before the reload
1103 # Connect to the reference provision and get all the attribute in the
1104 # partition referred by name
1105 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1106 scope=SCOPE_SUBTREE, attrs=["dn"],
1107 controls=["search_options:1:2"])
1109 current = samdb.search(expression="objectClass=*", base=basedn,
1110 scope=SCOPE_SUBTREE, attrs=["dn"],
1111 controls=["search_options:1:2"])
1112 # Create a hash for speeding the search of new object
1113 for i in range(0, len(reference)):
1114 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1116 # Create a hash for speeding the search of existing object in the
1118 for i in range(0, len(current)):
1119 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1122 for k in hash_new.keys():
1124 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1125 listMissing.append(hash_new[k])
1127 listPresent.append(hash_new[k])
1129 # Sort the missing object in order to have object of the lowest level
1130 # first (which can be containers for higher level objects)
1131 listMissing.sort(key=cmp_to_key(dn_sort))
1132 listPresent.sort(key=cmp_to_key(dn_sort))
1134 # The following lines is to load the up to
1135 # date schema into our current LDB
1136 # a complete schema is needed as the insertion of attributes
1137 # and class is done against it
1138 # and the schema is self validated
1139 samdb.set_schema(schema)
1141 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1142 add_deletedobj_containers(ref_samdb, samdb, names)
1144 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1147 message(SIMPLE, "Reloading a merged schema, which might trigger "
1148 "reindexing so please be patient")
1149 reload_full_schema(samdb, names)
1150 message(SIMPLE, "Schema reloaded!")
1152 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1154 message(SIMPLE, "There are %d changed objects" % (changed))
1157 except Exception as err:
1158 message(ERROR, "Exception during upgrade of samdb:")
1159 (typ, val, tb) = sys.exc_info()
1160 traceback.print_exception(typ, val, tb)
1164 def check_updated_sd(ref_sam, cur_sam, names):
1165 """Check if the security descriptor in the upgraded provision are the same
1168 :param ref_sam: A LDB object connected to the sam.ldb file used as
1169 the reference provision
1170 :param cur_sam: A LDB object connected to the sam.ldb file used as
1172 :param names: List of key provision parameters"""
1173 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1174 scope=SCOPE_SUBTREE,
1175 attrs=["dn", "nTSecurityDescriptor"],
1176 controls=["search_options:1:2"])
1177 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1178 scope=SCOPE_SUBTREE,
1179 attrs=["dn", "nTSecurityDescriptor"],
1180 controls=["search_options:1:2"])
1182 for i in range(0, len(reference)):
1183 refsd_blob = reference[i]["nTSecurityDescriptor"][0]
1184 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1187 for i in range(0, len(current)):
1188 key = str(current[i]["dn"]).lower()
1190 cursd_blob = current[i]["nTSecurityDescriptor"][0]
1191 cursd = ndr_unpack(security.descriptor,
1193 if cursd_blob != hash[key]:
1194 refsd = ndr_unpack(security.descriptor,
1196 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1198 message(CHANGESD, "On object %s ACL is different"
1199 " \n%s" % (current[i]["dn"], txt))
1203 def fix_wellknown_sd(samdb, names):
1204 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1205 This is needed because some provision use to have broken SD on containers
1207 :param samdb: An LDB object pointing to the sam of the current provision
1208 :param names: A list of key provision parameters
1211 list_wellknown_dns = []
1213 subcontainers = get_wellknown_sds(samdb)
1215 for [dn, descriptor_fn] in subcontainers:
1216 list_wellknown_dns.append(dn)
1217 if dn in dnToRecalculate:
1219 delta.dn = dn.copy(samdb)
1220 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1221 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1222 "nTSecurityDescriptor" )
1224 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1226 return list_wellknown_dns
1228 def rebuild_sd(samdb, names):
1229 """Rebuild security descriptor of the current provision from scratch
1231 During the different pre release of samba4 security descriptors
1232 (SD) were notarly broken (up to alpha11 included)
1234 This function allows one to get them back in order, this function works
1235 only after the database comparison that --full mode uses and which
1236 populates the dnToRecalculate and dnNotToRecalculate lists.
1238 The idea is that the SD can be safely recalculated from scratch to get it right.
1240 :param names: List of key provision parameters"""
1242 listWellknown = fix_wellknown_sd(samdb, names)
1244 if len(dnToRecalculate) != 0:
1245 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1246 % (len(dnToRecalculate)))
1248 for dn in dnToRecalculate:
1249 # well known SDs have already been reset
1250 if dn in listWellknown:
1253 delta.dn = dn.copy(samdb)
1254 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1256 descr = get_empty_descriptor(names.domainsid)
1257 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1258 "nTSecurityDescriptor")
1259 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1260 except LdbError as e:
1261 samdb.transaction_cancel()
1262 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1264 attrs=["nTSecurityDescriptor"],
1265 controls=["sd_flags:1:%d" % sd_flags])
1266 badsd = ndr_unpack(security.descriptor,
1267 res[0]["nTSecurityDescriptor"][0])
1268 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1271 def hasATProvision(samdb):
1272 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1276 if entry is not None and len(entry) == 1:
1281 def removeProvisionUSN(samdb):
1282 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1283 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1287 empty.dn = entry[0].dn
1288 delta = samdb.msg_diff(entry[0], empty)
1290 delta.dn = entry[0].dn
1293 def remove_stored_generated_attrs(paths, creds, session, lp):
1294 """Remove previously stored constructed attributes
1296 :param paths: List of paths for different provision objects
1297 from the upgraded provision
1298 :param creds: A credential object
1299 :param session: A session object
1300 :param lp: A line parser object
1301 :return: An associative array whose key are the different constructed
1302 attributes and the value the dn where this attributes were found.
1306 def simple_update_basesamdb(newpaths, paths, names):
1307 """Update the provision container db: sam.ldb
1308 This function is aimed at very old provision (before alpha9)
1310 :param newpaths: List of paths for different provision objects
1311 from the reference provision
1312 :param paths: List of paths for different provision objects
1313 from the upgraded provision
1314 :param names: List of key provision parameters"""
1316 message(SIMPLE, "Copy samdb")
1317 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1319 message(SIMPLE, "Update partitions filename if needed")
1320 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1321 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1322 usersldb = os.path.join(paths.private_dir, "users.ldb")
1323 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1325 if not os.path.isdir(samldbdir):
1327 os.chmod(samldbdir, 0o700)
1328 if os.path.isfile(schemaldb):
1329 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1330 "%s.ldb"%str(names.schemadn).upper()))
1331 os.remove(schemaldb)
1332 if os.path.isfile(usersldb):
1333 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1334 "%s.ldb"%str(names.rootdn).upper()))
1336 if os.path.isfile(configldb):
1337 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1338 "%s.ldb"%str(names.configdn).upper()))
1339 os.remove(configldb)
1342 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1343 """Upgrade the SAM DB contents for all the provision partitions
1345 :param ref_samdb: An LDB object connected to the sam.ldb of the reference
1347 :param samdb: An LDB object connected to the sam.ldb of the update
1349 :param names: List of key provision parameters
1350 :param provisionUSNs: A dictionary with range of USN modified during provision
1351 or upgradeprovision. Ranges are grouped by invocationID.
1352 :param schema: A Schema object that represent the schema of the provision
1353 :param prereloadfunc: A function that must be executed just before the reload
1357 message(SIMPLE, "Starting update of samdb")
1358 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1359 schema, provisionUSNs, prereloadfunc)
1361 message(SIMPLE, "Update of samdb finished")
1364 message(SIMPLE, "Update failed")
1368 def backup_provision(samdb, paths, dir, only_db):
1369 """This function backup the provision files so that a rollback
1372 :param paths: Paths to different objects
1373 :param dir: Directory where to store the backup
1374 :param only_db: Skip sysvol for users with big sysvol
1377 # Currently we default to tdb for the backend store type
1379 backend_store = "tdb"
1380 res = samdb.search(base="@PARTITION",
1381 scope=ldb.SCOPE_BASE,
1382 attrs=["backendStore"])
1383 if "backendStore" in res[0]:
1384 backend_store = str(res[0]["backendStore"][0])
1387 if paths.sysvol and not only_db:
1388 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1390 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1391 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1392 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1393 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1394 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1395 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1396 shutil.copy2(paths.smbconf, dir)
1397 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1399 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1400 if not os.path.isdir(samldbdir):
1401 samldbdir = paths.private_dir
1402 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1403 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1404 usersldb = os.path.join(paths.private_dir, "users.ldb")
1405 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1406 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1407 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1409 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
1411 for ldb_name in os.listdir(samldbdir):
1412 if not ldb_name.endswith("-lock"):
1413 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1414 mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1415 os.path.join(dir, "sam.ldb.d", ldb_name))
1417 tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1418 os.path.join(dir, "sam.ldb.d", ldb_name))
1421 def sync_calculated_attributes(samdb, names):
1422 """Synchronize attributes used for constructed ones, with the
1423 old constructed that were stored in the database.
1425 This apply for instance to msds-keyversionnumber that was
1426 stored and that is now constructed from replpropertymetadata.
1428 :param samdb: An LDB object attached to the currently upgraded samdb
1429 :param names: Various key parameter about current provision.
1431 listAttrs = ["msDs-KeyVersionNumber"]
1432 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1433 if "msDs-KeyVersionNumber" in hash:
1434 increment_calculated_keyversion_number(samdb, names.rootdn,
1435 hash["msDs-KeyVersionNumber"])
1437 # Synopsis for updateprovision
1438 # 1) get path related to provision to be update (called current)
1439 # 2) open current provision ldbs
1440 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1442 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1443 # by either upgradeprovision or provision
1444 # 5) creation of a new provision the latest version of provision script
1445 # (called reference)
1446 # 6) get reference provision paths
1447 # 7) open reference provision ldbs
1448 # 8) setup helpers data that will help the update process
1449 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of reference provision to
1450 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1451 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1452 # 10)get the oemInfo field, this field contains information about the different
1453 # provision that have been done
1454 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1455 # A) When alpha9 or alphaxx not specified (default)
1456 # The base sam.ldb file is updated by looking at the difference between
1457 # reference one and the current one. Everything is copied with the
1458 # exception of lastProvisionUSN attributes.
1459 # B) Other case (it reflect that that provision was done before alpha9)
1460 # The base sam.ldb of the reference provision is copied over
1461 # the current one, if necessary ldb related to partitions are moved
1463 # The highest used USN is fetched so that changed by upgradeprovision
1464 # usn can be tracked
1465 # 12)A Schema object is created, it will be used to provide a complete
1466 # schema to current provision during update (as the schema of the
1467 # current provision might not be complete and so won't allow some
1468 # object to be created)
1469 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1470 # 14)The secrets db is updated by pull all the difference from the reference
1471 # provision into the current provision
1472 # 15)As the previous step has most probably modified the password stored in
1473 # in secret for the current DC, a new password is generated,
1474 # the kvno is bumped and the entry in samdb is also updated
1475 # 16)For current provision older than alpha9, we must fix the SD a little bit
1476 # administrator to update them because SD used to be generated with the
1477 # system account before alpha9.
1478 # 17)The highest usn modified so far is searched in the database it will be
1479 # the upper limit for usn modified during provision.
1480 # This is done before potential SD recalculation because we do not want
1481 # SD modified during recalculation to be marked as modified during provision
1482 # (and so possibly remplaced at next upgradeprovision)
1483 # 18)Rebuilt SD if the flag indicate to do so
1484 # 19)Check difference between SD of reference provision and those of the
1485 # current provision. The check is done by getting the sddl representation
1486 # of the SD. Each sddl in chunked into parts (user,group,dacl,sacl)
1487 # Each part is verified separately, for dacl and sacl ACL is split into
1488 # ACEs and each ACE is verified separately (so that a permutation in ACE
1489 # didn't raise as an error).
1490 # 20)The oemInfo field is updated to add information about the fact that the
1491 # provision has been updated by the upgradeprovision version xxx
1492 # (the version is the one obtained when starting samba with the --version
1494 # 21)Check if the current provision has all the settings needed for dynamic
1495 # DNS update to work (that is to say the provision is newer than
1496 # january 2010). If not dns configuration file from reference provision
1497 # are copied in a sub folder and the administrator is invited to
1498 # do what is needed.
1499 # 22)If the lastProvisionUSN attribute was present it is updated to add
1500 # the range of usns modified by the current upgradeprovision
1503 # About updating the sam DB
1504 # The update takes place in update_partition function
1505 # This function read both current and reference provision and list all
1506 # the available DN of objects
1507 # If the string representation of a DN in reference provision is
1508 # equal to the string representation of a DN in current provision
1509 # (without taking care of case) then the object is flagged as being
1510 # present. If the object is not present in current provision the object
1511 # is being flagged as missing in current provision. Object present in current
1512 # provision but not in reference provision are ignored.
1513 # Once the list of objects present and missing is done, the deleted object
1514 # containers are created in the different partitions (if missing)
1516 # Then the function add_missing_entries is called
1517 # This function will go through the list of missing entries by calling
1518 # add_missing_object for the given object. If this function returns 0
1519 # it means that the object needs some other object in order to be created
1520 # The object is reappended at the end of the list to be created later
1521 # (and preferably after all the needed object have been created)
1522 # The function keeps on looping on the list of object to be created until
1523 # it's empty or that the number of deferred creation is equal to the number
1524 # of object that still needs to be created.
1526 # The function add_missing_object will first check if the object can be created.
1527 # That is to say that it didn't depends other not yet created objects
1528 # If requisite can't be fulfilled it exists with 0
1529 # Then it will try to create the missing entry by creating doing
1530 # an ldb_message_diff between the object in the reference provision and
1532 # This resulting object is filtered to remove all the back link attribute
1533 # (ie. memberOf) as they will be created by the other linked object (ie.
1534 # the one with the member attribute)
1535 # All attributes specified in the attrNotCopied array are
1536 # also removed it's most of the time generated attributes
1538 # After missing entries have been added the update_partition function will
1539 # take care of object that exist but that need some update.
1540 # In order to do so the function update_present is called with the list
1541 # of object that are present in both provision and that might need an update.
1543 # This function handle first case mismatch so that the DN in the current
1544 # provision have the same case as in reference provision
1546 # It will then construct an associative array consisting of attributes as
1547 # key and invocationid as value( if the originating invocation id is
1548 # different from the invocation id of the current DC the value is -1 instead).
1550 # If the range of provision modified attributes is present, the function will
1551 # use the replMetadataProperty update method which is the following:
1552 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1553 # creationTime, msDs-KeyVersionNumber, oEMInformation
1554 # Check for each attribute if its usn is within one of the modified by
1555 # provision range and if its originating id is the invocation id of the
1556 # current DC, then validate the update from reference to current.
1557 # If not or if there is no replMetatdataProperty for this attribute then we
1559 # Otherwise (case the range of provision modified attribute is not present) it
1560 # use the following process:
1561 # All attributes that need to be added are accepted at the exception of those
1562 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1563 # correct flags specified.
1564 # For attributes that need to be modified or removed, a check is performed
1565 # in OverwrittenAtt, if the attribute is present and the modification flag
1566 # (remove, delete) is one of those listed for this attribute then modification
1567 # is accepted. For complicated handling of attribute update, the control is passed
1568 # to handle_special_case
1572 if __name__ == '__main__':
1573 defSDmodified = False
1575 # From here start the big steps of the program
1576 # 1) First get files paths
1577 paths = get_paths(param, smbconf=smbconf)
1578 # Get ldbs with the system session, it is needed for searching
1579 # provision parameters
1580 session = system_session()
1582 # This variable will hold the last provision USN once if it exists.
1585 ldbs = get_ldbs(paths, creds, session, lp)
1586 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1587 prefix="backupprovision")
1588 backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1590 ldbs.startTransactions()
1592 # 3) Guess all the needed names (variables in fact) from the current
1594 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1597 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1598 if lastProvisionUSNs is not None:
1600 for k in lastProvisionUSNs.keys():
1601 for r in lastProvisionUSNs[k]:
1605 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1606 (len(lastProvisionUSNs.keys()), v /2 ))
1608 if lastProvisionUSNs.get("default") is not None:
1609 message(CHANGE, "Old style for usn ranges used")
1610 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1611 del lastProvisionUSNs["default"]
1613 message(SIMPLE, "Your provision lacks provision range information")
1614 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1615 ldbs.groupedRollback()
1617 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1618 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1619 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1620 " of hundred of objects or more")
1621 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1624 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1626 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1629 # Objects will be created with the admin session
1630 # (not anymore system session)
1631 adm_session = admin_session(lp, str(names.domainsid))
1632 # So we reget handle on objects
1633 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1635 if not sanitychecks(ldbs.sam, names):
1636 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1637 "Check the messages and correct the errors "
1638 "before rerunning upgradeprovision")
1639 ldbs.groupedRollback()
1642 # Let's see provision parameters
1643 print_provision_key_parameters(names)
1645 # 5) With all this information let's create a fresh new provision used as
1647 message(SIMPLE, "Creating a reference provision")
1648 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1649 prefix="referenceprovision")
1650 result = newprovision(names, session, smbconf, provisiondir,
1651 provision_logger, base_schema="2008_R2", adprep_level=None)
1652 result.report_logger(provision_logger)
1656 # We need to get a list of object which SD is directly computed from
1657 # defaultSecurityDescriptor.
1658 # This will allow us to know which object we can rebuild the SD in case
1659 # of change of the parent's SD or of the defaultSD.
1660 # Get file paths of this new provision
1661 newpaths = get_paths(param, targetdir=provisiondir)
1662 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1663 new_ldbs.startTransactions()
1665 populateNotReplicated(new_ldbs.sam, names.schemadn)
1666 # 8) Populate some associative array to ease the update process
1667 # List of attribute which are link and backlink
1668 populate_links(new_ldbs.sam, names.schemadn)
1669 # List of attribute with ASN DN synthax)
1670 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1671 # 9) (now skipped, was copy of privileges.ldb)
1673 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1674 # Do some modification on sam.ldb
1675 ldbs.groupedCommit()
1676 new_ldbs.groupedCommit()
1680 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1682 # Starting from alpha9 we can consider that the structure is quite ok
1683 # and that we should do only dela
1684 deltaattr = delta_update_basesamdb(newpaths.samdb,
1692 simple_update_basesamdb(newpaths, paths, names)
1693 ldbs = get_ldbs(paths, creds, session, lp)
1694 removeProvisionUSN(ldbs.sam)
1696 ldbs.startTransactions()
1697 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1698 new_ldbs.startTransactions()
1701 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1702 # We create a closure that will be invoked just before schema reload
1703 def schemareloadclosure():
1704 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1705 options=["modules:"])
1707 if deltaattr is not None and len(deltaattr) > 1:
1710 deltaattr.remove("dn")
1711 for att in deltaattr:
1712 if att.lower() == "dn":
1714 if (deltaattr.get(att) is not None
1715 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1717 elif deltaattr.get(att) is None:
1720 message(CHANGE, "Applying delta to @ATTRIBUTES")
1721 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1722 basesam.modify(deltaattr)
1724 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1725 "there is not only add")
1728 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1729 schema, schemareloadclosure):
1730 message(SIMPLE, "Rolling back all changes. Check the cause"
1732 message(SIMPLE, "Your system is as it was before the upgrade")
1733 ldbs.groupedRollback()
1734 new_ldbs.groupedRollback()
1735 shutil.rmtree(provisiondir)
1738 # Try to reapply the change also when we do not change the sam
1739 # as the delta_upgrade
1740 schemareloadclosure()
1741 sync_calculated_attributes(ldbs.sam, names)
1742 res = ldbs.sam.search(expression="(samaccountname=dns)",
1743 scope=SCOPE_SUBTREE, attrs=["dn"],
1744 controls=["search_options:1:2"])
1746 message(SIMPLE, "You still have the old DNS object for managing "
1747 "dynamic DNS, but you didn't supply --full so "
1748 "a correct update can't be done")
1749 ldbs.groupedRollback()
1750 new_ldbs.groupedRollback()
1751 shutil.rmtree(provisiondir)
1754 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1756 res = ldbs.sam.search(expression="(samaccountname=dns)",
1757 scope=SCOPE_SUBTREE, attrs=["dn"],
1758 controls=["search_options:1:2"])
1761 ldbs.sam.delete(res[0]["dn"])
1762 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1763 scope=SCOPE_SUBTREE, attrs=["dn"])
1764 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1765 message(SIMPLE, "IMPORTANT!!! "
1766 "If you were using Dynamic DNS before you need "
1767 "to update your configuration, so that the "
1768 "tkey-gssapi-credential has the following value: "
1769 "DNS/%s.%s" % (names.netbiosname.lower(),
1770 names.realm.lower()))
1772 message(SIMPLE, "Update machine account")
1773 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1775 # 16) SD should be created with admin but as some previous acl were so wrong
1776 # that admin can't modify them we have first to recreate them with the good
1777 # form but with system account and then give the ownership to admin ...
1778 if opts.very_old_pre_alpha9:
1779 message(SIMPLE, "Fixing very old provision SD")
1780 rebuild_sd(ldbs.sam, names)
1782 # We calculate the max USN before recalculating the SD because we might
1783 # touch object that have been modified after a provision and we do not
1784 # want that the next upgradeprovision thinks that it has a green light
1788 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1790 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1791 # defSDmodified is set.
1792 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1793 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1794 "changed, recalculating them")
1795 ldbs.sam.set_session_info(adm_session)
1796 rebuild_sd(ldbs.sam, names)
1799 # Now we are quite confident in the recalculate process of the SD, we make
1800 # it optional. And we don't do it if there is DN that we must touch
1801 # as we are assured that on this DNs we will have differences !
1802 # Also the check must be done in a clever way as for the moment we just
1804 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1805 message(CHANGESD, "Checking recalculated SDs")
1806 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1809 updateOEMInfo(ldbs.sam, str(names.rootdn))
1811 check_for_DNS(newpaths.private_dir, paths.private_dir,
1812 newpaths.binddns_dir, paths.binddns_dir,
1815 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1816 if opts.full and (names.policyid is None or names.policyid_dc is None):
1817 update_policyids(names, ldbs.sam)
1821 update_gpo(paths, names)
1822 except ProvisioningError as e:
1823 message(ERROR, "The policy for domain controller is missing. "
1824 "You should restart upgradeprovision with --full")
1826 ldbs.groupedCommit()
1827 new_ldbs.groupedCommit()
1828 message(SIMPLE, "Upgrade finished!")
1829 # remove reference provision now that everything is done !
1830 # So we have reindexed first if need when the merged schema was reloaded
1831 # (as new attributes could have quick in)
1832 # But the second part of the update (when we update existing objects
1833 # can also have an influence on indexing as some attribute might have their
1834 # searchflag modificated
1835 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1836 "after modification")
1837 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1838 message(SIMPLE, "Reindexing finished")
1840 shutil.rmtree(provisiondir)
1841 except Exception as err:
1842 message(ERROR, "A problem occurred while trying to upgrade your "
1843 "provision. A full backup is located at %s" % backupdir)
1844 if opts.debugall or opts.debugchange:
1845 (typ, val, tb) = sys.exc_info()
1846 traceback.print_exception(typ, val, tb)