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
40 from base64 import b64encode
41 from samba.credentials import DONT_USE_KERBEROS
42 from samba.auth import system_session, admin_session
43 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
44 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
45 MessageElement, Message, Dn)
46 from samba import param, dsdb, Ldb
47 from samba.provision import (get_domain_descriptor, find_provision_key_parameters,
48 get_config_descriptor,
49 ProvisioningError, get_last_provision_usn,
50 get_max_usn, update_provision_usn, setup_path)
51 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import security, drsblobs, xattr
53 from samba.ndr import ndr_unpack
54 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
56 usn_in_range, identic_rename, get_diff_sddls,
57 update_secrets, CHANGE, ERROR, SIMPLE,
58 CHANGEALL, GUESS, CHANGESD, PROVISION,
59 updateOEMInfo, getOEMInfo, update_gpo,
60 delta_update_basesamdb, update_policyids,
61 update_machine_account_password,
62 search_constructed_attrs_stored,
63 int64range2str, update_dns_account_password,
64 increment_calculated_keyversion_number)
66 replace=2**FLAG_MOD_REPLACE
68 delete=2**FLAG_MOD_DELETE
72 # Will be modified during provision to tell if default sd has been modified
75 #Errors are always logged
77 __docformat__ = "restructuredText"
79 # Attributes that are never copied from the reference provision (even if they
80 # do not exist in the destination object).
81 # This is most probably because they are populated automatcally when object is
83 # This also apply to imported object from reference provision
84 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
85 "parentGUID", "objectCategory", "distinguishedName",
86 "nTMixedDomain", "showInAdvancedViewOnly",
87 "instanceType", "msDS-Behavior-Version", "cn",
88 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
89 "unicodePwd", "dBCSPwd", "supplementalCredentials",
90 "gPCUserExtensionNames", "gPCMachineExtensionNames",
91 "maxPwdAge", "secret", "possibleInferiors", "privilege",
92 "sAMAccountType", "oEMInformation", "creationTime" ]
94 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
95 "nextRid" ,"rIDNextRID"]
97 attrNotCopied = replAttrNotCopied
98 attrNotCopied.extend(nonreplAttrNotCopied)
99 # Usually for an object that already exists we do not overwrite attributes as
100 # they might have been changed for good reasons. Anyway for a few of them it's
101 # mandatory to replace them otherwise the provision will be broken somehow.
102 # But for attribute that are just missing we do not have to specify them as the default
103 # behavior is to add missing attribute
104 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
105 "systemOnly":replace, "searchFlags":replace,
106 "mayContain":replace, "systemFlags":replace+add,
107 "description":replace, "operatingSystemVersion":replace,
108 "adminPropertyPages":replace, "groupType":replace,
109 "wellKnownObjects":replace, "privilege":never,
110 "defaultSecurityDescriptor": replace,
111 "rIDAvailablePool": never,
112 "rIDNextRID": add, "rIDUsedPool": never,
113 "defaultSecurityDescriptor": replace + add,
114 "isMemberOfPartialAttributeSet": delete,
115 "attributeDisplayNames": replace + add,
116 "versionNumber": add}
119 forwardlinked = set()
122 def define_what_to_log(opts):
126 if opts.debugchangesd:
127 what = what | CHANGESD
130 if opts.debugprovision:
131 what = what | PROVISION
133 what = what | CHANGEALL
137 parser = optparse.OptionParser("provision [options]")
138 sambaopts = options.SambaOptions(parser)
139 parser.add_option_group(sambaopts)
140 parser.add_option_group(options.VersionOptions(parser))
141 credopts = options.CredentialsOptions(parser)
142 parser.add_option_group(credopts)
143 parser.add_option("--setupdir", type="string", metavar="DIR",
144 help="directory with setup files")
145 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
146 parser.add_option("--debugguess", action="store_true",
147 help="Print information on which values are guessed")
148 parser.add_option("--debugchange", action="store_true",
149 help="Print information on what is different but won't be changed")
150 parser.add_option("--debugchangesd", action="store_true",
151 help="Print security descriptor differences")
152 parser.add_option("--debugall", action="store_true",
153 help="Print all available information (very verbose)")
154 parser.add_option("--resetfileacl", action="store_true",
155 help="Force a reset on filesystem acls in sysvol / netlogon share")
156 parser.add_option("--fixntacl", action="store_true",
157 help="Only fix NT ACLs in sysvol / netlogon share")
158 parser.add_option("--full", action="store_true",
159 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
161 opts = parser.parse_args()[0]
163 handler = logging.StreamHandler(sys.stdout)
164 upgrade_logger = logging.getLogger("upgradeprovision")
165 upgrade_logger.setLevel(logging.INFO)
167 upgrade_logger.addHandler(handler)
169 provision_logger = logging.getLogger("provision")
170 provision_logger.addHandler(handler)
172 whatToLog = define_what_to_log(opts)
174 def message(what, text):
175 """Print a message if this message type has been selected to be printed
177 :param what: Category of the message
178 :param text: Message to print """
179 if (whatToLog & what) or what <= 0:
180 upgrade_logger.info("%s", text)
182 if len(sys.argv) == 1:
183 opts.interactive = True
184 lp = sambaopts.get_loadparm()
185 smbconf = lp.configfile
187 creds = credopts.get_credentials(lp)
188 creds.set_kerberos_state(DONT_USE_KERBEROS)
192 def check_for_DNS(refprivate, private):
193 """Check if the provision has already the requirement for dynamic dns
195 :param refprivate: The path to the private directory of the reference
197 :param private: The path to the private directory of the upgraded
200 spnfile = "%s/spn_update_list" % private
201 dnsfile = "%s/dns_update_list" % private
202 namedfile = lp.get("dnsupdate:path")
205 namedfile = "%s/named.conf.update" % private
207 if not os.path.exists(spnfile):
208 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
210 if not os.path.exists(dnsfile):
211 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
213 destdir = "%s/new_dns" % private
214 dnsdir = "%s/dns" % private
216 if not os.path.exists(namedfile):
217 if not os.path.exists(destdir):
219 if not os.path.exists(dnsdir):
221 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
222 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
223 message(SIMPLE, "It seems that your provision did not integrate "
224 "new rules for dynamic dns update of domain related entries")
225 message(SIMPLE, "A copy of the new bind configuration files and "
226 "template has been put in %s, you should read them and "
227 "configure dynamic dns updates" % destdir)
230 def populate_links(samdb, schemadn):
231 """Populate an array with all the back linked attributes
233 This attributes that are modified automaticaly when
234 front attibutes are changed
236 :param samdb: A LDB object for sam.ldb file
237 :param schemadn: DN of the schema for the partition"""
238 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
239 backlinked.extend(linkedAttHash.values())
240 for t in linkedAttHash.keys():
243 def isReplicated(att):
244 """ Indicate if the attribute is replicated or not
246 :param att: Name of the attribute to be tested
247 :return: True is the attribute is replicated, False otherwise
250 return (att not in not_replicated)
252 def populateNotReplicated(samdb, schemadn):
253 """Populate an array with all the attributes that are not replicated
255 :param samdb: A LDB object for sam.ldb file
256 :param schemadn: DN of the schema for the partition"""
257 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
258 str(schemadn)), scope=SCOPE_SUBTREE,
259 attrs=["lDAPDisplayName"])
261 not_replicated.append(elem["lDAPDisplayName"])
263 def populate_dnsyntax(samdb, schemadn):
264 """Populate an array with all the attributes that have DN synthax
267 :param samdb: A LDB object for sam.ldb file
268 :param schemadn: DN of the schema for the partition"""
269 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
270 str(schemadn)), scope=SCOPE_SUBTREE,
271 attrs=["lDAPDisplayName"])
273 dn_syntax_att.append(elem["lDAPDisplayName"])
276 def sanitychecks(samdb, names):
277 """Make some checks before trying to update
279 :param samdb: An LDB object opened on sam.ldb
280 :param names: list of key provision parameters
281 :return: Status of check (1 for Ok, 0 for not Ok) """
282 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
283 scope=SCOPE_SUBTREE, attrs=["dn"],
284 controls=["search_options:1:2"])
286 print "No DC found. Your provision is most probably broken!"
289 print "Found %d domain controllers. For the moment " \
290 "upgradeprovision is not able to handle an upgrade on a " \
291 "domain with more than one DC. Please demote the other " \
292 "DC(s) before upgrading" % len(res)
298 def print_provision_key_parameters(names):
299 """Do a a pretty print of provision parameters
301 :param names: list of key provision parameters """
302 message(GUESS, "rootdn :" + str(names.rootdn))
303 message(GUESS, "configdn :" + str(names.configdn))
304 message(GUESS, "schemadn :" + str(names.schemadn))
305 message(GUESS, "serverdn :" + str(names.serverdn))
306 message(GUESS, "netbiosname :" + names.netbiosname)
307 message(GUESS, "defaultsite :" + names.sitename)
308 message(GUESS, "dnsdomain :" + names.dnsdomain)
309 message(GUESS, "hostname :" + names.hostname)
310 message(GUESS, "domain :" + names.domain)
311 message(GUESS, "realm :" + names.realm)
312 message(GUESS, "invocationid:" + names.invocation)
313 message(GUESS, "policyguid :" + names.policyid)
314 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
315 message(GUESS, "domainsid :" + str(names.domainsid))
316 message(GUESS, "domainguid :" + names.domainguid)
317 message(GUESS, "ntdsguid :" + names.ntdsguid)
318 message(GUESS, "domainlevel :" + str(names.domainlevel))
321 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
322 """Define more complicate update rules for some attributes
324 :param att: The attribute to be updated
325 :param delta: A messageElement object that correspond to the difference
326 between the updated object and the reference one
327 :param new: The reference object
328 :param old: The Updated object
329 :param useReplMetadata: A boolean that indicate if the update process
330 use replPropertyMetaData to decide what has to be updated.
331 :param basedn: The base DN of the provision
332 :param aldb: An ldb object used to build DN
333 :return: True to indicate that the attribute should be kept, False for
336 flag = delta.get(att).flags()
337 # We do most of the special case handle if we do not have the
338 # highest usn as otherwise the replPropertyMetaData will guide us more
340 if not useReplMetadata:
341 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
342 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
343 "CN=Services,CN=Configuration,%s" % basedn)
346 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
347 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
349 message(SIMPLE, "We suggest that you change the userAccountControl"
350 " for user Administrator from value %d to %d" %
351 (int(str(old[0][att])), int(str(new[0][att]))))
353 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
354 if (long(str(old[0][att])) == 0):
355 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
358 if (att == "member" and flag == FLAG_MOD_REPLACE):
362 for elem in old[0][att]:
363 hash[str(elem).lower()]=1
364 newval.append(str(elem))
366 for elem in new[0][att]:
367 if not hash.has_key(str(elem).lower()):
369 newval.append(str(elem))
371 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
376 if (att in ("gPLink", "gPCFileSysPath") and
377 flag == FLAG_MOD_REPLACE and
378 str(new[0].dn).lower() == str(old[0].dn).lower()):
382 if att == "forceLogoff":
383 ref=0x8000000000000000
384 oldval=int(old[0][att][0])
385 newval=int(new[0][att][0])
386 ref == old and ref == abs(new)
389 if att in ("adminDisplayName", "adminDescription"):
392 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
393 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
396 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
397 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
400 if (str(old[0].dn) == "%s" % (str(names.rootdn))
401 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
403 #Allow to change revision of ForestUpdates objects
404 if (att == "revision" or att == "objectVersion"):
405 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
407 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
410 # This is a bit of special animal as we might have added
411 # already SPN entries to the list that has to be modified
412 # So we go in detail to try to find out what has to be added ...
413 if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
417 for elem in old[0][att]:
419 newval.append(str(elem))
421 for elem in new[0][att]:
422 if not hash.has_key(str(elem)):
424 newval.append(str(elem))
426 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
433 def dump_denied_change(dn, att, flagtxt, current, reference):
434 """Print detailed information about why a change is denied
436 :param dn: DN of the object which attribute is denied
437 :param att: Attribute that was supposed to be upgraded
438 :param flagtxt: Type of the update that should be performed
439 (add, change, remove, ...)
440 :param current: Value(s) of the current attribute
441 :param reference: Value(s) of the reference attribute"""
443 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
444 + " must not be changed/removed. Discarding the change")
445 if att == "objectSid" :
446 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
447 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
448 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
449 message(CHANGE, "old : %s" % int64range2str(current[0]))
450 message(CHANGE, "new : %s" % int64range2str(reference[0]))
453 for e in range(0, len(current)):
454 message(CHANGE, "old %d : %s" % (i, str(current[e])))
456 if reference is not None:
458 for e in range(0, len(reference)):
459 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
462 def handle_special_add(samdb, dn, names):
463 """Handle special operation (like remove) on some object needed during
466 This is mostly due to wrong creation of the object in previous provision.
467 :param samdb: An Ldb object representing the SAM database
468 :param dn: DN of the object to inspect
469 :param names: list of key provision parameters
473 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
475 #This entry was misplaced lets remove it if it exists
476 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
479 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
481 #This entry was misplaced lets remove it if it exists
482 dntoremove = "CN=Certificate Service DCOM Access,"\
483 "CN=Users, %s" % names.rootdn
485 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
487 #This entry was misplaced lets remove it if it exists
488 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
490 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
492 #This entry was misplaced lets remove it if it exists
493 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
495 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
496 "CN=Configuration,%s" % names.rootdn)
498 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
499 "CN=WellKnown Security Principals,"
500 "CN=Configuration,%s" % names.rootdn)
502 res = samdb.search(expression="(dn=%s)" % oldDn,
503 base=str(names.rootdn),
504 scope=SCOPE_SUBTREE, attrs=["dn"],
505 controls=["search_options:1:2"])
507 res2 = samdb.search(expression="(dn=%s)" % dn,
508 base=str(names.rootdn),
509 scope=SCOPE_SUBTREE, attrs=["dn"],
510 controls=["search_options:1:2"])
512 if len(res) > 0 and len(res2) == 0:
513 message(CHANGE, "Existing object %s must be replaced by %s. "
514 "Renaming old object" % (str(oldDn), str(dn)))
515 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
519 if dntoremove is not None:
520 res = samdb.search(expression="(cn=RID Set)",
521 base=str(names.rootdn),
522 scope=SCOPE_SUBTREE, attrs=["dn"],
523 controls=["search_options:1:2"])
527 res = samdb.search(expression="(dn=%s)" % dntoremove,
528 base=str(names.rootdn),
529 scope=SCOPE_SUBTREE, attrs=["dn"],
530 controls=["search_options:1:2"])
532 message(CHANGE, "Existing object %s must be replaced by %s. "
533 "Removing old object" % (dntoremove, str(dn)))
534 samdb.delete(res[0]["dn"])
540 def check_dn_nottobecreated(hash, index, listdn):
541 """Check if one of the DN present in the list has a creation order
542 greater than the current.
544 Hash is indexed by dn to be created, with each key
545 is associated the creation order.
547 First dn to be created has the creation order 0, second has 1, ...
548 Index contain the current creation order
550 :param hash: Hash holding the different DN of the object to be
552 :param index: Current creation order
553 :param listdn: List of DNs on which the current DN depends on
554 :return: None if the current object do not depend on other
555 object or if all object have been created before."""
559 key = str(dn).lower()
560 if hash.has_key(key) and hash[key] > index:
566 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
567 """Add a new object if the dependencies are satisfied
569 The function add the object if the object on which it depends are already
572 :param ref_samdb: Ldb object representing the SAM db of the reference
574 :param samdb: Ldb object representing the SAM db of the upgraded
576 :param dn: DN of the object to be added
577 :param names: List of key provision parameters
578 :param basedn: DN of the partition to be updated
579 :param hash: Hash holding the different DN of the object to be
581 :param index: Current creation order
582 :return: True if the object was created False otherwise"""
584 ret = handle_special_add(samdb, dn, names)
593 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
594 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
596 delta = samdb.msg_diff(empty, reference[0])
600 if str(reference[0].get("cn")) == "RID Set":
601 for klass in reference[0].get("objectClass"):
602 if str(klass).lower() == "ridset":
605 if delta.get("objectSid"):
606 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
607 m = re.match(r".*-(\d+)$", sid)
608 if m and int(m.group(1))>999:
609 delta.remove("objectSid")
610 for att in attrNotCopied:
612 for att in backlinked:
614 depend_on_yettobecreated = None
615 for att in dn_syntax_att:
616 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
618 if depend_on_yet_tobecreated is not None:
619 message(CHANGE, "Object %s depends on %s in attribute %s. "
620 "Delaying the creation" % (dn,
621 depend_on_yet_tobecreated, att))
626 message(CHANGE,"Object %s will be added" % dn)
627 samdb.add(delta, ["relax:0", "provision:0"])
629 message(CHANGE,"Object %s was skipped" % dn)
633 def gen_dn_index_hash(listMissing):
634 """Generate a hash associating the DN to its creation order
636 :param listMissing: List of DN
637 :return: Hash with DN as keys and creation order as values"""
639 for i in range(0, len(listMissing)):
640 hash[str(listMissing[i]).lower()] = i
643 def add_deletedobj_containers(ref_samdb, samdb, names):
644 """Add the object containter: CN=Deleted Objects
646 This function create the container for each partition that need one and
647 then reference the object into the root of the partition
649 :param ref_samdb: Ldb object representing the SAM db of the reference
651 :param samdb: Ldb object representing the SAM db of the upgraded provision
652 :param names: List of key provision parameters"""
655 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
656 partitions = [str(names.rootdn), str(names.configdn)]
657 for part in partitions:
658 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
659 base=part, scope=SCOPE_SUBTREE,
661 controls=["show_deleted:0",
663 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
664 base=part, scope=SCOPE_SUBTREE,
666 controls=["show_deleted:0",
668 if len(ref_delObjCnt) > len(delObjCnt):
669 reference = ref_samdb.search(expression="cn=Deleted Objects",
670 base=part, scope=SCOPE_SUBTREE,
671 controls=["show_deleted:0",
674 delta = samdb.msg_diff(empty, reference[0])
676 delta.dn = Dn(samdb, str(reference[0]["dn"]))
677 for att in attrNotCopied:
680 modcontrols = ["relax:0", "provision:0"]
681 samdb.add(delta, modcontrols)
684 res = samdb.search(expression="(objectClass=*)", base=part,
686 attrs=["dn", "wellKnownObjects"])
688 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
692 wko = res[0]["wellKnownObjects"]
694 # The wellKnownObject that we want to add.
696 if str(o) == targetWKO:
698 listwko.append(str(o))
701 listwko.append(targetWKO)
704 delta.dn = Dn(samdb, str(res[0]["dn"]))
705 delta["wellKnownObjects"] = MessageElement(listwko,
710 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
711 """Add the missing object whose DN is the list
713 The function add the object if the objects on which it depends are
716 :param ref_samdb: Ldb object representing the SAM db of the reference
718 :param samdb: Ldb object representing the SAM db of the upgraded
720 :param dn: DN of the object to be added
721 :param names: List of key provision parameters
722 :param basedn: DN of the partition to be updated
723 :param list: List of DN to be added in the upgraded provision"""
728 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
730 listMissing = listDefered
732 hashMissing = gen_dn_index_hash(listMissing)
733 for dn in listMissing:
734 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
738 # DN can't be created because it depends on some
739 # other DN in the list
740 listDefered.append(dn)
742 if len(listDefered) != 0:
743 raise ProvisioningError("Unable to insert missing elements: "
744 "circular references")
746 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
747 """This function handle updates on links
749 :param samdb: An LDB object pointing to the updated provision
750 :param att: Attribute to update
751 :param basedn: The root DN of the provision
752 :param dn: The DN of the inspected object
753 :param value: The value of the attribute
754 :param ref_value: The value of this attribute in the reference provision
755 :param delta: The MessageElement object that will be applied for
756 transforming the current provision"""
758 res = samdb.search(expression="dn=%s" % dn, base=basedn,
759 controls=["search_options:1:2", "reveal:1"],
767 newlinklist.extend(value)
771 # for w2k domain level the reveal won't reveal anything ...
772 # it means that we can readd links that were removed on purpose ...
773 # Also this function in fact just accept add not removal
775 for e in res[0][att]:
776 if not hash.has_key(e):
777 # We put in the blacklist all the element that are in the "revealed"
778 # result and not in the "standard" result
779 # This element are links that were removed before and so that
780 # we don't wan't to readd
784 if not blacklist.has_key(e) and not hash.has_key(e):
785 newlinklist.append(str(e))
788 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
793 msg_elt_flag_strs = {
794 ldb.FLAG_MOD_ADD: "MOD_ADD",
795 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
796 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
798 def checkKeepAttributeOldMtd(delta, att, reference, current,
800 """ Check if we should keep the attribute modification or not.
801 This function didn't use replicationMetadata to take a decision.
803 :param delta: A message diff object
804 :param att: An attribute
805 :param reference: A message object for the current entry comming from
806 the reference provision.
807 :param current: A message object for the current entry commin from
808 the current provision.
809 :param basedn: The DN of the partition
810 :param samdb: A ldb connection to the sam database of the current provision.
812 :return: The modified message diff.
814 # Old school way of handling things for pre alpha12 upgrade
820 for att in list(delta):
822 msgElt = delta.get(att)
824 if att == "nTSecurityDescriptor":
831 if not hashOverwrittenAtt.has_key(att):
832 if msgElt.flags() != FLAG_MOD_ADD:
833 if not handle_special_case(att, delta, reference, current,
834 False, basedn, samdb):
835 if opts.debugchange or opts.debugall:
837 dump_denied_change(dn, att,
838 msg_elt_flag_strs[msgElt.flags()],
839 current[0][att], reference[0][att])
841 dump_denied_change(dn, att,
842 msg_elt_flag_strs[msgElt.flags()],
843 current[0][att], None)
847 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
849 elif hashOverwrittenAtt.get(att)==never:
855 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
856 hash_attr_usn, basedn, usns, samdb):
857 """ Check if we should keep the attribute modification or not
859 :param delta: A message diff object
860 :param att: An attribute
861 :param message: A function to print messages
862 :param reference: A message object for the current entry comming from
863 the reference provision.
864 :param current: A message object for the current entry commin from
865 the current provision.
866 :param hash_attr_usn: A dictionnary with attribute name as keys,
867 USN and invocation id as values.
868 :param basedn: The DN of the partition
869 :param usns: A dictionnary with invocation ID as keys and USN ranges
871 :param samdb: A ldb object pointing to the sam DB
873 :return: The modified message diff.
880 for att in list(delta):
881 # We have updated by provision usn information so let's exploit
882 # replMetadataProperties
883 if att in forwardlinked:
884 curval = current[0].get(att, ())
885 refval = reference[0].get(att, ())
886 handle_links(samdb, att, basedn, current[0]["dn"],
887 curval, refval, delta)
890 if isFirst and len(delta.items())>1:
892 txt = "%s\n" % (str(dn))
894 if handle_special_case(att, delta, reference, current, True, None, None):
895 # This attribute is "complicated" to handle and handling
896 # was done in handle_special_case
900 if hash_attr_usn.get(att):
901 [attrUSN, attInvId] = hash_attr_usn.get(att)
904 # If it's a replicated attribute and we don't have any USN
905 # information about it. It means that we never saw it before
907 # If it is a replicated attribute but we are not master on it
908 # (ie. not initially added in the provision we masterize).
910 if isReplicated(att):
913 message(CHANGE, "Non replicated attribute %s changed" % att)
916 if att == "nTSecurityDescriptor":
917 cursd = ndr_unpack(security.descriptor,
918 str(current[0]["nTSecurityDescriptor"]))
919 cursddl = cursd.as_sddl(names.domainsid)
920 refsd = ndr_unpack(security.descriptor,
921 str(reference[0]["nTSecurityDescriptor"]))
922 refsddl = refsd.as_sddl(names.domainsid)
924 if get_diff_sddls(refsddl, cursddl) == "":
925 message(CHANGE, "sd are identical")
927 message(CHANGE, "sd are not identical")
930 # This attribute was last modified by another DC forget
932 message(CHANGE, "%sAttribute: %s has been "
933 "created/modified/deleted by another DC. "
934 "Doing nothing" % (txt, att))
938 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
939 message(CHANGE, "%sAttribute: %s was not "
940 "created/modified/deleted during a "
941 "provision or upgradeprovision. Current "
942 "usn: %d. Doing nothing" % (txt, att,
948 if att == "defaultSecurityDescriptor":
951 message(CHANGE, "%sAttribute: %s will be modified"
952 "/deleted it was last modified "
953 "during a provision. Current usn: "
954 "%d" % (txt, att, attrUSN))
957 message(CHANGE, "%sAttribute: %s will be added because "
958 "it did not exist before" % (txt, att))
964 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
965 """ This function updates the object that are already present in the
968 :param ref_samdb: An LDB object pointing to the reference provision
969 :param samdb: An LDB object pointing to the updated provision
970 :param basedn: A string with the value of the base DN for the provision
972 :param listPresent: A list of object that is present in the provision
973 :param usns: A list of USN range modified by previous provision and
974 upgradeprovision grouped by invocation ID
977 # This hash is meant to speedup lookup of attribute name from an oid,
978 # it's for the replPropertyMetaData handling
980 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
981 controls=["search_options:1:2"], attrs=["attributeID",
985 strDisplay = str(e.get("lDAPDisplayName"))
986 hash_oid_name[str(e.get("attributeID"))] = strDisplay
988 msg = "Unable to insert missing elements: circular references"
989 raise ProvisioningError(msg)
992 controls = ["search_options:1:2", "sd_flags:1:0"]
994 message(CHANGE, "Using replPropertyMetadata for change selection")
995 for dn in listPresent:
996 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
999 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1000 scope=SCOPE_SUBTREE, controls=controls)
1003 (str(current[0].dn) != str(reference[0].dn)) and
1004 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1006 message(CHANGE, "Names are the same except for the case. "
1007 "Renaming %s to %s" % (str(current[0].dn),
1008 str(reference[0].dn)))
1009 identic_rename(samdb, reference[0].dn)
1010 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1011 scope=SCOPE_SUBTREE,
1014 delta = samdb.msg_diff(current[0], reference[0])
1016 for att in backlinked:
1019 for att in attrNotCopied:
1022 delta.remove("name")
1024 if len(delta.items()) > 1 and usns is not None:
1025 # Fetch the replPropertyMetaData
1026 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1027 scope=SCOPE_SUBTREE, controls=controls,
1028 attrs=["replPropertyMetaData"])
1029 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1030 str(res[0]["replPropertyMetaData"])).ctr
1034 # We put in this hash only modification
1035 # made on the current host
1036 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1037 if str(o.originating_invocation_id) in usns.keys():
1038 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1040 hash_attr_usn[att] = [-1, None]
1042 if usns is not None:
1043 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1044 current, hash_attr_usn,
1045 basedn, usns, samdb)
1047 delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1050 if len(delta.items()) >1:
1051 # Skip dn as the value is not really changed ...
1052 attributes=", ".join(delta.keys()[1:])
1054 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1055 # Let's try to reduce as much as possible the use of relax control
1056 #for checkedatt in relaxedatt:
1057 for attr in delta.keys():
1058 if attr.lower() in relaxedatt:
1059 modcontrols = ["relax:0", "provision:0"]
1060 message(CHANGE, "%s is different from the reference one, changed"
1061 " attributes: %s\n" % (dn, attributes))
1063 samdb.modify(delta, modcontrols)
1066 def reload_full_schema(samdb, names):
1067 """Load the updated schema with all the new and existing classes
1070 :param samdb: An LDB object connected to the sam.ldb of the update
1072 :param names: List of key provision parameters
1075 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1076 scope=SCOPE_SUBTREE)
1081 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1083 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1084 prefixmap_data = b64encode(prefixmap_data)
1086 # We don't actually add this ldif, just parse it
1087 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1089 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1092 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1093 """Check differences between the reference provision and the upgraded one.
1095 It looks for all objects which base DN is name.
1097 This function will also add the missing object and update existing object
1098 to add or remove attributes that were missing.
1100 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1102 :param samdb: An LDB object connected to the sam.ldb of the update
1104 :param basedn: String value of the DN of the partition
1105 :param names: List of key provision parameters
1106 :param schema: A Schema object
1107 :param provisionUSNs: A dictionnary with range of USN modified during provision
1108 or upgradeprovision. Ranges are grouped by invocationID.
1109 :param prereloadfunc: A function that must be executed just before the reload
1120 # Connect to the reference provision and get all the attribute in the
1121 # partition referred by name
1122 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1123 scope=SCOPE_SUBTREE, attrs=["dn"],
1124 controls=["search_options:1:2"])
1126 current = samdb.search(expression="objectClass=*", base=basedn,
1127 scope=SCOPE_SUBTREE, attrs=["dn"],
1128 controls=["search_options:1:2"])
1129 # Create a hash for speeding the search of new object
1130 for i in range(0, len(reference)):
1131 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1133 # Create a hash for speeding the search of existing object in the
1135 for i in range(0, len(current)):
1136 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1139 for k in hash_new.keys():
1140 if not hash.has_key(k):
1141 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1142 listMissing.append(hash_new[k])
1144 listPresent.append(hash_new[k])
1146 # Sort the missing object in order to have object of the lowest level
1147 # first (which can be containers for higher level objects)
1148 listMissing.sort(dn_sort)
1149 listPresent.sort(dn_sort)
1151 # The following lines is to load the up to
1152 # date schema into our current LDB
1153 # a complete schema is needed as the insertion of attributes
1154 # and class is done against it
1155 # and the schema is self validated
1156 samdb.set_schema(schema)
1158 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1159 add_deletedobj_containers(ref_samdb, samdb, names)
1161 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1164 message(SIMPLE, "Reloading a merged schema, which might trigger "
1165 "reindexing so please be patient")
1166 reload_full_schema(samdb, names)
1167 message(SIMPLE, "Schema reloaded!")
1169 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1171 message(SIMPLE, "There are %d changed objects" % (changed))
1174 except StandardError, err:
1175 message(ERROR, "Exception during upgrade of samdb:")
1176 (typ, val, tb) = sys.exc_info()
1177 traceback.print_exception(typ, val, tb)
1181 def check_updated_sd(ref_sam, cur_sam, names):
1182 """Check if the security descriptor in the upgraded provision are the same
1185 :param ref_sam: A LDB object connected to the sam.ldb file used as
1186 the reference provision
1187 :param cur_sam: A LDB object connected to the sam.ldb file used as
1189 :param names: List of key provision parameters"""
1190 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1191 scope=SCOPE_SUBTREE,
1192 attrs=["dn", "nTSecurityDescriptor"],
1193 controls=["search_options:1:2"])
1194 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1195 scope=SCOPE_SUBTREE,
1196 attrs=["dn", "nTSecurityDescriptor"],
1197 controls=["search_options:1:2"])
1199 for i in range(0, len(reference)):
1200 refsd = ndr_unpack(security.descriptor,
1201 str(reference[i]["nTSecurityDescriptor"]))
1202 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1205 for i in range(0, len(current)):
1206 key = str(current[i]["dn"]).lower()
1207 if hash.has_key(key):
1208 cursd = ndr_unpack(security.descriptor,
1209 str(current[i]["nTSecurityDescriptor"]))
1210 sddl = cursd.as_sddl(names.domainsid)
1211 if sddl != hash[key]:
1212 txt = get_diff_sddls(hash[key], sddl)
1214 message(CHANGESD, "On object %s ACL is different"
1215 " \n%s" % (current[i]["dn"], txt))
1219 def fix_partition_sd(samdb, names):
1220 """This function fix the SD for partition containers (basedn, configdn, ...)
1221 This is needed because some provision use to have broken SD on containers
1223 :param samdb: An LDB object pointing to the sam of the current provision
1224 :param names: A list of key provision parameters
1226 # First update the SD for the rootdn
1227 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1228 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1229 controls=["search_options:1:2"])
1231 delta.dn = Dn(samdb, str(res[0]["dn"]))
1232 descr = get_domain_descriptor(names.domainsid)
1233 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1234 "nTSecurityDescriptor")
1236 # Then the config dn
1237 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1238 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1239 controls=["search_options:1:2"])
1241 delta.dn = Dn(samdb, str(res[0]["dn"]))
1242 descr = get_config_descriptor(names.domainsid)
1243 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1244 "nTSecurityDescriptor" )
1246 # Then the schema dn
1247 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1248 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1249 controls=["search_options:1:2"])
1252 delta.dn = Dn(samdb, str(res[0]["dn"]))
1253 descr = get_schema_descriptor(names.domainsid)
1254 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1255 "nTSecurityDescriptor" )
1258 def rebuild_sd(samdb, names):
1259 """Rebuild security descriptor of the current provision from scratch
1261 During the different pre release of samba4 security descriptors (SD)
1262 were notarly broken (up to alpha11 included)
1263 This function allow to get them back in order, this function make the
1264 assumption that nobody has modified manualy an SD
1265 and so SD can be safely recalculated from scratch to get them right.
1267 :param names: List of key provision parameters"""
1271 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1272 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1273 controls=["search_options:1:2"])
1275 if not (str(obj["dn"]) == str(names.rootdn) or
1276 str(obj["dn"]) == str(names.configdn) or
1277 str(obj["dn"]) == str(names.schemadn)):
1278 hash[str(obj["dn"])] = obj["whenCreated"]
1280 listkeys = hash.keys()
1281 listkeys.sort(dn_sort)
1283 for key in listkeys:
1286 delta.dn = Dn(samdb, key)
1287 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1289 samdb.modify(delta, ["recalculate_sd:0"])
1291 # XXX: We should always catch an explicit exception.
1292 # What could go wrong here?
1293 samdb.transaction_cancel()
1294 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1295 scope=SCOPE_SUBTREE,
1296 attrs=["dn", "nTSecurityDescriptor"],
1297 controls=["search_options:1:2"])
1298 badsd = ndr_unpack(security.descriptor,
1299 str(res[0]["nTSecurityDescriptor"]))
1300 print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1303 def removeProvisionUSN(samdb):
1304 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1305 entry = samdb.search(expression="dn=@PROVISION", base = "",
1306 scope=SCOPE_SUBTREE,
1309 empty.dn = entry[0].dn
1310 delta = samdb.msg_diff(entry[0], empty)
1312 delta.dn = entry[0].dn
1315 def remove_stored_generated_attrs(paths, creds, session, lp):
1316 """Remove previously stored constructed attributes
1318 :param paths: List of paths for different provision objects
1319 from the upgraded provision
1320 :param creds: A credential object
1321 :param session: A session object
1322 :param lp: A line parser object
1323 :return: An associative array whose key are the different constructed
1324 attributes and the value the dn where this attributes were found.
1328 def simple_update_basesamdb(newpaths, paths, names):
1329 """Update the provision container db: sam.ldb
1330 This function is aimed at very old provision (before alpha9)
1332 :param newpaths: List of paths for different provision objects
1333 from the reference provision
1334 :param paths: List of paths for different provision objects
1335 from the upgraded provision
1336 :param names: List of key provision parameters"""
1338 message(SIMPLE, "Copy samdb")
1339 shutil.copy(newpaths.samdb, paths.samdb)
1341 message(SIMPLE, "Update partitions filename if needed")
1342 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1343 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1344 usersldb = os.path.join(paths.private_dir, "users.ldb")
1345 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1347 if not os.path.isdir(samldbdir):
1349 os.chmod(samldbdir, 0700)
1350 if os.path.isfile(schemaldb):
1351 shutil.copy(schemaldb, os.path.join(samldbdir,
1352 "%s.ldb"%str(names.schemadn).upper()))
1353 os.remove(schemaldb)
1354 if os.path.isfile(usersldb):
1355 shutil.copy(usersldb, os.path.join(samldbdir,
1356 "%s.ldb"%str(names.rootdn).upper()))
1358 if os.path.isfile(configldb):
1359 shutil.copy(configldb, os.path.join(samldbdir,
1360 "%s.ldb"%str(names.configdn).upper()))
1361 os.remove(configldb)
1364 def update_privilege(ref_private_path, cur_private_path):
1365 """Update the privilege database
1367 :param ref_private_path: Path to the private directory of the reference
1369 :param cur_private_path: Path to the private directory of the current
1370 (and to be updated) provision."""
1371 message(SIMPLE, "Copy privilege")
1372 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1373 os.path.join(cur_private_path, "privilege.ldb"))
1376 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1377 """Upgrade the SAM DB contents for all the provision partitions
1379 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1381 :param samdb: An LDB object connected to the sam.ldb of the update
1383 :param names: List of key provision parameters
1384 :param provisionUSNs: A dictionnary with range of USN modified during provision
1385 or upgradeprovision. Ranges are grouped by invocationID.
1386 :param schema: A Schema object that represent the schema of the provision
1387 :param prereloadfunc: A function that must be executed just before the reload
1391 message(SIMPLE, "Starting update of samdb")
1392 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1393 schema, provisionUSNs, prereloadfunc)
1395 message(SIMPLE, "Update of samdb finished")
1398 message(SIMPLE, "Update failed")
1402 def copyxattrs(dir, refdir):
1403 """ Copy owner, groups, extended ACL and NT acls from
1404 a reference dir to a destination dir
1406 Both dir are supposed to hold the same files
1407 :param dir: Destination dir
1408 :param refdir: Reference directory"""
1411 for root, dirs, files in os.walk(dir, topdown=True):
1413 subdir=root[len(dir):]
1414 ref = os.path.join("%s%s" % (refdir, subdir), name)
1415 statsinfo = os.stat(ref)
1416 tgt = os.path.join(root, name)
1419 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1420 # Get the xattr attributes if any
1422 attribute = samba.xattr_native.wrap_getxattr(ref,
1423 xattr.XATTR_NTACL_NAME)
1424 samba.xattr_native.wrap_setxattr(tgt,
1425 xattr.XATTR_NTACL_NAME,
1429 attribute = samba.xattr_native.wrap_getxattr(ref,
1430 "system.posix_acl_access")
1431 samba.xattr_native.wrap_setxattr(tgt,
1432 "system.posix_acl_access",
1437 subdir=root[len(dir):]
1438 ref = os.path.join("%s%s" % (refdir, subdir), name)
1439 statsinfo = os.stat(ref)
1440 tgt = os.path.join(root, name)
1442 os.chown(os.path.join(root, name), statsinfo.st_uid,
1445 attribute = samba.xattr_native.wrap_getxattr(ref,
1446 xattr.XATTR_NTACL_NAME)
1447 samba.xattr_native.wrap_setxattr(tgt,
1448 xattr.XATTR_NTACL_NAME,
1452 attribute = samba.xattr_native.wrap_getxattr(ref,
1453 "system.posix_acl_access")
1454 samba.xattr_native.wrap_setxattr(tgt,
1455 "system.posix_acl_access",
1462 def backup_provision(paths, dir):
1463 """This function backup the provision files so that a rollback
1466 :param paths: Paths to different objects
1467 :param dir: Directory where to store the backup
1470 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1471 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1472 shutil.copy2(paths.samdb, dir)
1473 shutil.copy2(paths.secrets, dir)
1474 shutil.copy2(paths.idmapdb, dir)
1475 shutil.copy2(paths.privilege, dir)
1476 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1477 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1478 shutil.copy2(paths.smbconf, dir)
1479 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1481 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1482 if not os.path.isdir(samldbdir):
1483 samldbdir = paths.private_dir
1484 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1485 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1486 usersldb = os.path.join(paths.private_dir, "users.ldb")
1487 shutil.copy2(schemaldb, dir)
1488 shutil.copy2(usersldb, dir)
1489 shutil.copy2(configldb, dir)
1491 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1496 def sync_calculated_attributes(samdb, names):
1497 """Synchronize attributes used for constructed ones, with the
1498 old constructed that were stored in the database.
1500 This apply for instance to msds-keyversionnumber that was
1501 stored and that is now constructed from replpropertymetadata.
1503 :param samdb: An LDB object attached to the currently upgraded samdb
1504 :param names: Various key parameter about current provision.
1506 listAttrs = ["msDs-KeyVersionNumber"]
1507 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1508 if hash.has_key("msDs-KeyVersionNumber"):
1509 increment_calculated_keyversion_number(samdb, names.rootdn,
1510 hash["msDs-KeyVersionNumber"])
1512 # Synopsis for updateprovision
1513 # 1) get path related to provision to be update (called current)
1514 # 2) open current provision ldbs
1515 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1517 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1518 # by either upgradeprovision or provision
1519 # 5) creation of a new provision the latest version of provision script
1520 # (called reference)
1521 # 6) get reference provision paths
1522 # 7) open reference provision ldbs
1523 # 8) setup helpers data that will help the update process
1524 # 9) update the privilege ldb by copying the one of referecence provision to
1525 # the current provision
1526 # 10)get the oemInfo field, this field contains information about the different
1527 # provision that have been done
1528 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1529 # integer) or none of this the following things are done
1530 # A) When alpha9 or alphaxx is present
1531 # The base sam.ldb file is updated by looking at the difference between
1532 # referrence one and the current one. Everything is copied with the
1533 # exception of lastProvisionUSN attributes.
1534 # B) Other case (it reflect that that provision was done before alpha9)
1535 # The base sam.ldb of the reference provision is copied over
1536 # the current one, if necessary ldb related to partitions are moved
1538 # The highest used USN is fetched so that changed by upgradeprovision
1539 # usn can be tracked
1540 # 12)A Schema object is created, it will be used to provide a complete
1541 # schema to current provision during update (as the schema of the
1542 # current provision might not be complete and so won't allow some
1543 # object to be created)
1544 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1545 # 14)The secrets db is updated by pull all the difference from the reference
1546 # provision into the current provision
1547 # 15)As the previous step has most probably modified the password stored in
1548 # in secret for the current DC, a new password is generated,
1549 # the kvno is bumped and the entry in samdb is also updated
1550 # 16)For current provision older than alpha9, we must fix the SD a little bit
1551 # administrator to update them because SD used to be generated with the
1552 # system account before alpha9.
1553 # 17)The highest usn modified so far is searched in the database it will be
1554 # the upper limit for usn modified during provision.
1555 # This is done before potential SD recalculation because we do not want
1556 # SD modified during recalculation to be marked as modified during provision
1557 # (and so possibly remplaced at next upgradeprovision)
1558 # 18)Rebuilt SD if the flag indicate to do so
1559 # 19)Check difference between SD of reference provision and those of the
1560 # current provision. The check is done by getting the sddl representation
1561 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1562 # Each part is verified separetly, for dacl and sacl ACL is splited into
1563 # ACEs and each ACE is verified separately (so that a permutation in ACE
1564 # didn't raise as an error).
1565 # 20)The oemInfo field is updated to add information about the fact that the
1566 # provision has been updated by the upgradeprovision version xxx
1567 # (the version is the one obtained when starting samba with the --version
1569 # 21)Check if the current provision has all the settings needed for dynamic
1570 # DNS update to work (that is to say the provision is newer than
1571 # january 2010). If not dns configuration file from reference provision
1572 # are copied in a sub folder and the administrator is invited to
1573 # do what is needed.
1574 # 22)If the lastProvisionUSN attribute was present it is updated to add
1575 # the range of usns modified by the current upgradeprovision
1578 # About updating the sam DB
1579 # The update takes place in update_partition function
1580 # This function read both current and reference provision and list all
1581 # the available DN of objects
1582 # If the string representation of a DN in reference provision is
1583 # equal to the string representation of a DN in current provision
1584 # (without taking care of case) then the object is flaged as being
1585 # present. If the object is not present in current provision the object
1586 # is being flaged as missing in current provision. Object present in current
1587 # provision but not in reference provision are ignored.
1588 # Once the list of objects present and missing is done, the deleted object
1589 # containers are created in the differents partitions (if missing)
1591 # Then the function add_missing_entries is called
1592 # This function will go through the list of missing entries by calling
1593 # add_missing_object for the given object. If this function returns 0
1594 # it means that the object needs some other object in order to be created
1595 # The object is reappended at the end of the list to be created later
1596 # (and preferably after all the needed object have been created)
1597 # The function keeps on looping on the list of object to be created until
1598 # it's empty or that the number of defered creation is equal to the number
1599 # of object that still needs to be created.
1601 # The function add_missing_object will first check if the object can be created.
1602 # That is to say that it didn't depends other not yet created objects
1603 # If requisit can't be fullfilled it exists with 0
1604 # Then it will try to create the missing entry by creating doing
1605 # an ldb_message_diff between the object in the reference provision and
1607 # This resulting object is filtered to remove all the back link attribute
1608 # (ie. memberOf) as they will be created by the other linked object (ie.
1609 # the one with the member attribute)
1610 # All attributes specified in the attrNotCopied array are
1611 # also removed it's most of the time generated attributes
1613 # After missing entries have been added the update_partition function will
1614 # take care of object that exist but that need some update.
1615 # In order to do so the function update_present is called with the list
1616 # of object that are present in both provision and that might need an update.
1618 # This function handle first case mismatch so that the DN in the current
1619 # provision have the same case as in reference provision
1621 # It will then construct an associative array consiting of attributes as
1622 # key and invocationid as value( if the originating invocation id is
1623 # different from the invocation id of the current DC the value is -1 instead).
1625 # If the range of provision modified attributes is present, the function will
1626 # use the replMetadataProperty update method which is the following:
1627 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1628 # creationTime, msDs-KeyVersionNumber, oEMInformation
1629 # Check for each attribute if its usn is within one of the modified by
1630 # provision range and if its originating id is the invocation id of the
1631 # current DC, then validate the update from reference to current.
1632 # If not or if there is no replMetatdataProperty for this attribute then we
1634 # Otherwise (case the range of provision modified attribute is not present) it
1635 # use the following process:
1636 # All attributes that need to be added are accepted at the exeption of those
1637 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1638 # correct flags specified.
1639 # For attributes that need to be modified or removed, a check is performed
1640 # in OverwrittenAtt, if the attribute is present and the modification flag
1641 # (remove, delete) is one of those listed for this attribute then modification
1642 # is accepted. For complicated handling of attribute update, the control is passed
1643 # to handle_special_case
1647 if __name__ == '__main__':
1648 global defSDmodified
1649 defSDmodified = False
1650 # From here start the big steps of the program
1651 # 1) First get files paths
1652 paths = get_paths(param, smbconf=smbconf)
1653 # Get ldbs with the system session, it is needed for searching
1654 # provision parameters
1655 session = system_session()
1657 # This variable will hold the last provision USN once if it exists.
1660 ldbs = get_ldbs(paths, creds, session, lp)
1661 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1662 prefix="backupprovision")
1663 backup_provision(paths, backupdir)
1665 ldbs.startTransactions()
1667 # 3) Guess all the needed names (variables in fact) from the current
1669 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1672 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1673 if lastProvisionUSNs is not None:
1675 for k in lastProvisionUSNs.keys():
1676 for r in lastProvisionUSNs[k]:
1680 "Find last provision USN, %d invocation(s) for a total of %d ranges" % \
1681 (len(lastProvisionUSNs.keys()), v /2 ))
1683 if lastProvisionUSNs.get("default") != None:
1684 message(CHANGE, "Old style for usn ranges used")
1685 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1686 del lastProvisionUSNs["default"]
1687 # Objects will be created with the admin session
1688 # (not anymore system session)
1689 adm_session = admin_session(lp, str(names.domainsid))
1690 # So we reget handle on objects
1691 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1692 if not opts.fixntacl:
1693 if not sanitychecks(ldbs.sam, names):
1694 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1695 "Check the messages and correct the errors "
1696 "before rerunning upgradeprovision")
1697 ldbs.groupedRollback()
1700 # Let's see provision parameters
1701 print_provision_key_parameters(names)
1703 # 5) With all this information let's create a fresh new provision used as
1705 message(SIMPLE, "Creating a reference provision")
1706 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1707 prefix="referenceprovision")
1708 newprovision(names, creds, session, smbconf, provisiondir,
1713 # We need to get a list of object which SD is directly computed from
1714 # defaultSecurityDescriptor.
1715 # This will allow us to know which object we can rebuild the SD in case
1716 # of change of the parent's SD or of the defaultSD.
1717 # Get file paths of this new provision
1718 newpaths = get_paths(param, targetdir=provisiondir)
1719 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1720 new_ldbs.startTransactions()
1722 # 8) Populate some associative array to ease the update process
1723 # List of attribute which are link and backlink
1724 populate_links(new_ldbs.sam, names.schemadn)
1725 # List of attribute with ASN DN synthax)
1726 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1728 update_privilege(newpaths.private_dir, paths.private_dir)
1730 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1731 # Do some modification on sam.ldb
1732 ldbs.groupedCommit()
1733 new_ldbs.groupedCommit()
1736 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1738 # Starting from alpha9 we can consider that the structure is quite ok
1739 # and that we should do only dela
1740 deltaattr = delta_update_basesamdb(newpaths.samdb,
1748 simple_update_basesamdb(newpaths, paths, names)
1749 ldbs = get_ldbs(paths, creds, session, lp)
1750 removeProvisionUSN(ldbs.sam)
1752 ldbs.startTransactions()
1753 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1754 new_ldbs.startTransactions()
1757 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1758 # We create a closure that will be invoked just before schema reload
1759 def schemareloadclosure():
1760 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1761 options=["modules:"])
1763 if deltaattr is not None and len(deltaattr) > 1:
1766 deltaattr.remove("dn")
1767 for att in deltaattr:
1768 if att.lower() == "dn":
1770 if (deltaattr.get(att) is not None
1771 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1773 elif deltaattr.get(att) is None:
1776 message(CHANGE, "Applying delta to @ATTRIBUTES")
1777 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1778 basesam.modify(deltaattr)
1780 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1781 "there is not only add")
1784 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1785 schema, schemareloadclosure):
1786 message(SIMPLE, "Rolling back all changes. Check the cause"
1788 message(SIMPLE, "Your system is as it was before the upgrade")
1789 ldbs.groupedRollback()
1790 new_ldbs.groupedRollback()
1791 shutil.rmtree(provisiondir)
1794 # Try to reapply the change also when we do not change the sam
1795 # as the delta_upgrade
1796 schemareloadclosure()
1797 sync_calculated_attributes(ldbs.sam, names)
1798 res = ldbs.sam.search(expression="(samaccountname=dns)",
1799 scope=SCOPE_SUBTREE, attrs=["dn"],
1800 controls=["search_options:1:2"])
1802 message(SIMPLE, "You still have the old DNS object for managing "
1803 "dynamic DNS, but you didn't supply --full so "
1804 "a correct update can't be done")
1805 ldbs.groupedRollback()
1806 new_ldbs.groupedRollback()
1807 shutil.rmtree(provisiondir)
1810 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1812 res = ldbs.sam.search(expression="(samaccountname=dns)",
1813 scope=SCOPE_SUBTREE, attrs=["dn"],
1814 controls=["search_options:1:2"])
1817 ldbs.sam.delete(res[0]["dn"])
1818 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1819 scope=SCOPE_SUBTREE, attrs=["dn"])
1820 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1821 message(SIMPLE, "IMPORTANT!!! "
1822 "If you were using Dynamic DNS before you need "
1823 "to update your configuration, so that the "
1824 "tkey-gssapi-credential has the following value: "
1825 "DNS/%s.%s" % (names.netbiosname.lower(),
1826 names.realm.lower()))
1828 message(SIMPLE, "Update machine account")
1829 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1831 # 16) SD should be created with admin but as some previous acl were so wrong
1832 # that admin can't modify them we have first to recreate them with the good
1833 # form but with system account and then give the ownership to admin ...
1834 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1835 message(SIMPLE, "Fixing old povision SD")
1836 fix_partition_sd(ldbs.sam, names)
1837 rebuild_sd(ldbs.sam, names)
1839 # We calculate the max USN before recalculating the SD because we might
1840 # touch object that have been modified after a provision and we do not
1841 # want that the next upgradeprovision thinks that it has a green light
1845 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1847 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1848 # But in fact we should do it also if one object has its SD modified as
1849 # child might need rebuild
1851 message(SIMPLE, "Updating SD")
1852 ldbs.sam.set_session_info(adm_session)
1853 # Alpha10 was a bit broken still
1854 if re.match(r'.*alpha(\d|10)', str(oem)):
1855 fix_partition_sd(ldbs.sam, names)
1856 rebuild_sd(ldbs.sam, names)
1859 # Now we are quite confident in the recalculate process of the SD, we make
1861 # Also the check must be done in a clever way as for the moment we just
1863 if opts.debugchangesd:
1864 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1867 updateOEMInfo(ldbs.sam, str(names.rootdn))
1869 check_for_DNS(newpaths.private_dir, paths.private_dir)
1871 if lastProvisionUSNs is not None:
1872 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1873 if opts.full and (names.policyid is None or names.policyid_dc is None):
1874 update_policyids(names, ldbs.sam)
1875 if opts.full or opts.resetfileacl or opts.fixntacl:
1877 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1878 except ProvisioningError, e:
1879 message(ERROR, "The policy for domain controller is missing. "
1880 "You should restart upgradeprovision with --full")
1882 message(ERROR, "Setting ACL not supported on your filesystem")
1885 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1886 except ProvisioningError, e:
1887 message(ERROR, "The policy for domain controller is missing. "
1888 "You should restart upgradeprovision with --full")
1889 if not opts.fixntacl:
1890 ldbs.groupedCommit()
1891 new_ldbs.groupedCommit()
1892 message(SIMPLE, "Upgrade finished!")
1893 # remove reference provision now that everything is done !
1894 # So we have reindexed first if need when the merged schema was reloaded
1895 # (as new attributes could have quick in)
1896 # But the second part of the update (when we update existing objects
1897 # can also have an influence on indexing as some attribute might have their
1898 # searchflag modificated
1899 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1900 "after modification")
1901 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1902 message(SIMPLE, "Reindexing finished")
1904 shutil.rmtree(provisiondir)
1906 ldbs.groupedRollback()
1907 message(SIMPLE, "ACLs fixed !")
1908 except StandardError, err:
1909 message(ERROR, "A problem occurred while trying to upgrade your "
1910 "provision. A full backup is located at %s" % backupdir)
1911 if opts.debugall or opts.debugchange:
1912 (typ, val, tb) = sys.exc_info()
1913 traceback.print_exception(typ, val, tb)