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 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
100 attrNotCopied = replAttrNotCopied
101 attrNotCopied.extend(nonreplAttrNotCopied)
102 attrNotCopied.extend(nonDSDBAttrNotCopied)
103 # Usually for an object that already exists we do not overwrite attributes as
104 # they might have been changed for good reasons. Anyway for a few of them it's
105 # mandatory to replace them otherwise the provision will be broken somehow.
106 # But for attribute that are just missing we do not have to specify them as the default
107 # behavior is to add missing attribute
108 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
109 "systemOnly":replace, "searchFlags":replace,
110 "mayContain":replace, "systemFlags":replace+add,
111 "description":replace, "operatingSystemVersion":replace,
112 "adminPropertyPages":replace, "groupType":replace,
113 "wellKnownObjects":replace, "privilege":never,
114 "defaultSecurityDescriptor": replace,
115 "rIDAvailablePool": never,
116 "rIDNextRID": add, "rIDUsedPool": never,
117 "defaultSecurityDescriptor": replace + add,
118 "isMemberOfPartialAttributeSet": delete,
119 "attributeDisplayNames": replace + add,
120 "versionNumber": add}
123 forwardlinked = set()
126 def define_what_to_log(opts):
130 if opts.debugchangesd:
131 what = what | CHANGESD
134 if opts.debugprovision:
135 what = what | PROVISION
137 what = what | CHANGEALL
141 parser = optparse.OptionParser("provision [options]")
142 sambaopts = options.SambaOptions(parser)
143 parser.add_option_group(sambaopts)
144 parser.add_option_group(options.VersionOptions(parser))
145 credopts = options.CredentialsOptions(parser)
146 parser.add_option_group(credopts)
147 parser.add_option("--setupdir", type="string", metavar="DIR",
148 help="directory with setup files")
149 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
150 parser.add_option("--debugguess", action="store_true",
151 help="Print information on which values are guessed")
152 parser.add_option("--debugchange", action="store_true",
153 help="Print information on what is different but won't be changed")
154 parser.add_option("--debugchangesd", action="store_true",
155 help="Print security descriptor differences")
156 parser.add_option("--debugall", action="store_true",
157 help="Print all available information (very verbose)")
158 parser.add_option("--resetfileacl", action="store_true",
159 help="Force a reset on filesystem acls in sysvol / netlogon share")
160 parser.add_option("--fixntacl", action="store_true",
161 help="Only fix NT ACLs in sysvol / netlogon share")
162 parser.add_option("--full", action="store_true",
163 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
165 opts = parser.parse_args()[0]
167 handler = logging.StreamHandler(sys.stdout)
168 upgrade_logger = logging.getLogger("upgradeprovision")
169 upgrade_logger.setLevel(logging.INFO)
171 upgrade_logger.addHandler(handler)
173 provision_logger = logging.getLogger("provision")
174 provision_logger.addHandler(handler)
176 whatToLog = define_what_to_log(opts)
178 def message(what, text):
179 """Print a message if this message type has been selected to be printed
181 :param what: Category of the message
182 :param text: Message to print """
183 if (whatToLog & what) or what <= 0:
184 upgrade_logger.info("%s", text)
186 if len(sys.argv) == 1:
187 opts.interactive = True
188 lp = sambaopts.get_loadparm()
189 smbconf = lp.configfile
191 creds = credopts.get_credentials(lp)
192 creds.set_kerberos_state(DONT_USE_KERBEROS)
196 def check_for_DNS(refprivate, private):
197 """Check if the provision has already the requirement for dynamic dns
199 :param refprivate: The path to the private directory of the reference
201 :param private: The path to the private directory of the upgraded
204 spnfile = "%s/spn_update_list" % private
205 dnsfile = "%s/dns_update_list" % private
206 namedfile = lp.get("dnsupdate:path")
209 namedfile = "%s/named.conf.update" % private
211 if not os.path.exists(spnfile):
212 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
214 if not os.path.exists(dnsfile):
215 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
217 destdir = "%s/new_dns" % private
218 dnsdir = "%s/dns" % private
220 if not os.path.exists(namedfile):
221 if not os.path.exists(destdir):
223 if not os.path.exists(dnsdir):
225 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
226 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
227 message(SIMPLE, "It seems that your provision did not integrate "
228 "new rules for dynamic dns update of domain related entries")
229 message(SIMPLE, "A copy of the new bind configuration files and "
230 "template has been put in %s, you should read them and "
231 "configure dynamic dns updates" % destdir)
234 def populate_links(samdb, schemadn):
235 """Populate an array with all the back linked attributes
237 This attributes that are modified automaticaly when
238 front attibutes are changed
240 :param samdb: A LDB object for sam.ldb file
241 :param schemadn: DN of the schema for the partition"""
242 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
243 backlinked.extend(linkedAttHash.values())
244 for t in linkedAttHash.keys():
247 def isReplicated(att):
248 """ Indicate if the attribute is replicated or not
250 :param att: Name of the attribute to be tested
251 :return: True is the attribute is replicated, False otherwise
254 return (att not in not_replicated)
256 def populateNotReplicated(samdb, schemadn):
257 """Populate an array with all the attributes that are not replicated
259 :param samdb: A LDB object for sam.ldb file
260 :param schemadn: DN of the schema for the partition"""
261 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
262 str(schemadn)), scope=SCOPE_SUBTREE,
263 attrs=["lDAPDisplayName"])
265 not_replicated.append(elem["lDAPDisplayName"])
267 def populate_dnsyntax(samdb, schemadn):
268 """Populate an array with all the attributes that have DN synthax
271 :param samdb: A LDB object for sam.ldb file
272 :param schemadn: DN of the schema for the partition"""
273 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
274 str(schemadn)), scope=SCOPE_SUBTREE,
275 attrs=["lDAPDisplayName"])
277 dn_syntax_att.append(elem["lDAPDisplayName"])
280 def sanitychecks(samdb, names):
281 """Make some checks before trying to update
283 :param samdb: An LDB object opened on sam.ldb
284 :param names: list of key provision parameters
285 :return: Status of check (1 for Ok, 0 for not Ok) """
286 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
287 scope=SCOPE_SUBTREE, attrs=["dn"],
288 controls=["search_options:1:2"])
290 print "No DC found. Your provision is most probably broken!"
293 print "Found %d domain controllers. For the moment " \
294 "upgradeprovision is not able to handle an upgrade on a " \
295 "domain with more than one DC. Please demote the other " \
296 "DC(s) before upgrading" % len(res)
302 def print_provision_key_parameters(names):
303 """Do a a pretty print of provision parameters
305 :param names: list of key provision parameters """
306 message(GUESS, "rootdn :" + str(names.rootdn))
307 message(GUESS, "configdn :" + str(names.configdn))
308 message(GUESS, "schemadn :" + str(names.schemadn))
309 message(GUESS, "serverdn :" + str(names.serverdn))
310 message(GUESS, "netbiosname :" + names.netbiosname)
311 message(GUESS, "defaultsite :" + names.sitename)
312 message(GUESS, "dnsdomain :" + names.dnsdomain)
313 message(GUESS, "hostname :" + names.hostname)
314 message(GUESS, "domain :" + names.domain)
315 message(GUESS, "realm :" + names.realm)
316 message(GUESS, "invocationid:" + names.invocation)
317 message(GUESS, "policyguid :" + names.policyid)
318 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
319 message(GUESS, "domainsid :" + str(names.domainsid))
320 message(GUESS, "domainguid :" + names.domainguid)
321 message(GUESS, "ntdsguid :" + names.ntdsguid)
322 message(GUESS, "domainlevel :" + str(names.domainlevel))
325 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
326 """Define more complicate update rules for some attributes
328 :param att: The attribute to be updated
329 :param delta: A messageElement object that correspond to the difference
330 between the updated object and the reference one
331 :param new: The reference object
332 :param old: The Updated object
333 :param useReplMetadata: A boolean that indicate if the update process
334 use replPropertyMetaData to decide what has to be updated.
335 :param basedn: The base DN of the provision
336 :param aldb: An ldb object used to build DN
337 :return: True to indicate that the attribute should be kept, False for
340 flag = delta.get(att).flags()
341 # We do most of the special case handle if we do not have the
342 # highest usn as otherwise the replPropertyMetaData will guide us more
344 if not useReplMetadata:
345 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
346 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
347 "CN=Services,CN=Configuration,%s" % basedn)
350 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
351 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
353 message(SIMPLE, "We suggest that you change the userAccountControl"
354 " for user Administrator from value %d to %d" %
355 (int(str(old[0][att])), int(str(new[0][att]))))
357 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
358 if (long(str(old[0][att])) == 0):
359 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
362 if (att == "member" and flag == FLAG_MOD_REPLACE):
366 for elem in old[0][att]:
367 hash[str(elem).lower()]=1
368 newval.append(str(elem))
370 for elem in new[0][att]:
371 if not hash.has_key(str(elem).lower()):
373 newval.append(str(elem))
375 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
380 if (att in ("gPLink", "gPCFileSysPath") and
381 flag == FLAG_MOD_REPLACE and
382 str(new[0].dn).lower() == str(old[0].dn).lower()):
386 if att == "forceLogoff":
387 ref=0x8000000000000000
388 oldval=int(old[0][att][0])
389 newval=int(new[0][att][0])
390 ref == old and ref == abs(new)
393 if att in ("adminDisplayName", "adminDescription"):
396 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
397 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
400 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
401 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
404 if (str(old[0].dn) == "%s" % (str(names.rootdn))
405 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
407 #Allow to change revision of ForestUpdates objects
408 if (att == "revision" or att == "objectVersion"):
409 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
411 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
414 # This is a bit of special animal as we might have added
415 # already SPN entries to the list that has to be modified
416 # So we go in detail to try to find out what has to be added ...
417 if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
421 for elem in old[0][att]:
423 newval.append(str(elem))
425 for elem in new[0][att]:
426 if not hash.has_key(str(elem)):
428 newval.append(str(elem))
430 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
437 def dump_denied_change(dn, att, flagtxt, current, reference):
438 """Print detailed information about why a change is denied
440 :param dn: DN of the object which attribute is denied
441 :param att: Attribute that was supposed to be upgraded
442 :param flagtxt: Type of the update that should be performed
443 (add, change, remove, ...)
444 :param current: Value(s) of the current attribute
445 :param reference: Value(s) of the reference attribute"""
447 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
448 + " must not be changed/removed. Discarding the change")
449 if att == "objectSid" :
450 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
451 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
452 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
453 message(CHANGE, "old : %s" % int64range2str(current[0]))
454 message(CHANGE, "new : %s" % int64range2str(reference[0]))
457 for e in range(0, len(current)):
458 message(CHANGE, "old %d : %s" % (i, str(current[e])))
460 if reference is not None:
462 for e in range(0, len(reference)):
463 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
466 def handle_special_add(samdb, dn, names):
467 """Handle special operation (like remove) on some object needed during
470 This is mostly due to wrong creation of the object in previous provision.
471 :param samdb: An Ldb object representing the SAM database
472 :param dn: DN of the object to inspect
473 :param names: list of key provision parameters
477 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
479 #This entry was misplaced lets remove it if it exists
480 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
483 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
485 #This entry was misplaced lets remove it if it exists
486 dntoremove = "CN=Certificate Service DCOM Access,"\
487 "CN=Users, %s" % names.rootdn
489 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
491 #This entry was misplaced lets remove it if it exists
492 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
494 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
496 #This entry was misplaced lets remove it if it exists
497 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
499 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
500 "CN=Configuration,%s" % names.rootdn)
502 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
503 "CN=WellKnown Security Principals,"
504 "CN=Configuration,%s" % names.rootdn)
506 res = samdb.search(expression="(dn=%s)" % oldDn,
507 base=str(names.rootdn),
508 scope=SCOPE_SUBTREE, attrs=["dn"],
509 controls=["search_options:1:2"])
511 res2 = samdb.search(expression="(dn=%s)" % dn,
512 base=str(names.rootdn),
513 scope=SCOPE_SUBTREE, attrs=["dn"],
514 controls=["search_options:1:2"])
516 if len(res) > 0 and len(res2) == 0:
517 message(CHANGE, "Existing object %s must be replaced by %s. "
518 "Renaming old object" % (str(oldDn), str(dn)))
519 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
523 if dntoremove is not None:
524 res = samdb.search(expression="(cn=RID Set)",
525 base=str(names.rootdn),
526 scope=SCOPE_SUBTREE, attrs=["dn"],
527 controls=["search_options:1:2"])
531 res = samdb.search(expression="(dn=%s)" % dntoremove,
532 base=str(names.rootdn),
533 scope=SCOPE_SUBTREE, attrs=["dn"],
534 controls=["search_options:1:2"])
536 message(CHANGE, "Existing object %s must be replaced by %s. "
537 "Removing old object" % (dntoremove, str(dn)))
538 samdb.delete(res[0]["dn"])
544 def check_dn_nottobecreated(hash, index, listdn):
545 """Check if one of the DN present in the list has a creation order
546 greater than the current.
548 Hash is indexed by dn to be created, with each key
549 is associated the creation order.
551 First dn to be created has the creation order 0, second has 1, ...
552 Index contain the current creation order
554 :param hash: Hash holding the different DN of the object to be
556 :param index: Current creation order
557 :param listdn: List of DNs on which the current DN depends on
558 :return: None if the current object do not depend on other
559 object or if all object have been created before."""
563 key = str(dn).lower()
564 if hash.has_key(key) and hash[key] > index:
570 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
571 """Add a new object if the dependencies are satisfied
573 The function add the object if the object on which it depends are already
576 :param ref_samdb: Ldb object representing the SAM db of the reference
578 :param samdb: Ldb object representing the SAM db of the upgraded
580 :param dn: DN of the object to be added
581 :param names: List of key provision parameters
582 :param basedn: DN of the partition to be updated
583 :param hash: Hash holding the different DN of the object to be
585 :param index: Current creation order
586 :return: True if the object was created False otherwise"""
588 ret = handle_special_add(samdb, dn, names)
597 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
598 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
600 delta = samdb.msg_diff(empty, reference[0])
604 if str(reference[0].get("cn")) == "RID Set":
605 for klass in reference[0].get("objectClass"):
606 if str(klass).lower() == "ridset":
609 if delta.get("objectSid"):
610 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
611 m = re.match(r".*-(\d+)$", sid)
612 if m and int(m.group(1))>999:
613 delta.remove("objectSid")
614 for att in attrNotCopied:
616 for att in backlinked:
618 depend_on_yettobecreated = None
619 for att in dn_syntax_att:
620 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
622 if depend_on_yet_tobecreated is not None:
623 message(CHANGE, "Object %s depends on %s in attribute %s. "
624 "Delaying the creation" % (dn,
625 depend_on_yet_tobecreated, att))
630 message(CHANGE,"Object %s will be added" % dn)
631 samdb.add(delta, ["relax:0", "provision:0"])
633 message(CHANGE,"Object %s was skipped" % dn)
637 def gen_dn_index_hash(listMissing):
638 """Generate a hash associating the DN to its creation order
640 :param listMissing: List of DN
641 :return: Hash with DN as keys and creation order as values"""
643 for i in range(0, len(listMissing)):
644 hash[str(listMissing[i]).lower()] = i
647 def add_deletedobj_containers(ref_samdb, samdb, names):
648 """Add the object containter: CN=Deleted Objects
650 This function create the container for each partition that need one and
651 then reference the object into the root of the partition
653 :param ref_samdb: Ldb object representing the SAM db of the reference
655 :param samdb: Ldb object representing the SAM db of the upgraded provision
656 :param names: List of key provision parameters"""
659 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
660 partitions = [str(names.rootdn), str(names.configdn)]
661 for part in partitions:
662 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
663 base=part, scope=SCOPE_SUBTREE,
665 controls=["show_deleted:0",
667 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
668 base=part, scope=SCOPE_SUBTREE,
670 controls=["show_deleted:0",
672 if len(ref_delObjCnt) > len(delObjCnt):
673 reference = ref_samdb.search(expression="cn=Deleted Objects",
674 base=part, scope=SCOPE_SUBTREE,
675 controls=["show_deleted:0",
678 delta = samdb.msg_diff(empty, reference[0])
680 delta.dn = Dn(samdb, str(reference[0]["dn"]))
681 for att in attrNotCopied:
684 modcontrols = ["relax:0", "provision:0"]
685 samdb.add(delta, modcontrols)
688 res = samdb.search(expression="(objectClass=*)", base=part,
690 attrs=["dn", "wellKnownObjects"])
692 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
696 wko = res[0]["wellKnownObjects"]
698 # The wellKnownObject that we want to add.
700 if str(o) == targetWKO:
702 listwko.append(str(o))
705 listwko.append(targetWKO)
708 delta.dn = Dn(samdb, str(res[0]["dn"]))
709 delta["wellKnownObjects"] = MessageElement(listwko,
714 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
715 """Add the missing object whose DN is the list
717 The function add the object if the objects on which it depends are
720 :param ref_samdb: Ldb object representing the SAM db of the reference
722 :param samdb: Ldb object representing the SAM db of the upgraded
724 :param dn: DN of the object to be added
725 :param names: List of key provision parameters
726 :param basedn: DN of the partition to be updated
727 :param list: List of DN to be added in the upgraded provision"""
732 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
734 listMissing = listDefered
736 hashMissing = gen_dn_index_hash(listMissing)
737 for dn in listMissing:
738 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
742 # DN can't be created because it depends on some
743 # other DN in the list
744 listDefered.append(dn)
746 if len(listDefered) != 0:
747 raise ProvisioningError("Unable to insert missing elements: "
748 "circular references")
750 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
751 """This function handle updates on links
753 :param samdb: An LDB object pointing to the updated provision
754 :param att: Attribute to update
755 :param basedn: The root DN of the provision
756 :param dn: The DN of the inspected object
757 :param value: The value of the attribute
758 :param ref_value: The value of this attribute in the reference provision
759 :param delta: The MessageElement object that will be applied for
760 transforming the current provision"""
762 res = samdb.search(expression="dn=%s" % dn, base=basedn,
763 controls=["search_options:1:2", "reveal:1"],
771 newlinklist.extend(value)
775 # for w2k domain level the reveal won't reveal anything ...
776 # it means that we can readd links that were removed on purpose ...
777 # Also this function in fact just accept add not removal
779 for e in res[0][att]:
780 if not hash.has_key(e):
781 # We put in the blacklist all the element that are in the "revealed"
782 # result and not in the "standard" result
783 # This element are links that were removed before and so that
784 # we don't wan't to readd
788 if not blacklist.has_key(e) and not hash.has_key(e):
789 newlinklist.append(str(e))
792 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
797 msg_elt_flag_strs = {
798 ldb.FLAG_MOD_ADD: "MOD_ADD",
799 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
800 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
802 def checkKeepAttributeOldMtd(delta, att, reference, current,
804 """ Check if we should keep the attribute modification or not.
805 This function didn't use replicationMetadata to take a decision.
807 :param delta: A message diff object
808 :param att: An attribute
809 :param reference: A message object for the current entry comming from
810 the reference provision.
811 :param current: A message object for the current entry commin from
812 the current provision.
813 :param basedn: The DN of the partition
814 :param samdb: A ldb connection to the sam database of the current provision.
816 :return: The modified message diff.
818 # Old school way of handling things for pre alpha12 upgrade
824 for att in list(delta):
826 msgElt = delta.get(att)
828 if att == "nTSecurityDescriptor":
835 if not hashOverwrittenAtt.has_key(att):
836 if msgElt.flags() != FLAG_MOD_ADD:
837 if not handle_special_case(att, delta, reference, current,
838 False, basedn, samdb):
839 if opts.debugchange or opts.debugall:
841 dump_denied_change(dn, att,
842 msg_elt_flag_strs[msgElt.flags()],
843 current[0][att], reference[0][att])
845 dump_denied_change(dn, att,
846 msg_elt_flag_strs[msgElt.flags()],
847 current[0][att], None)
851 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
853 elif hashOverwrittenAtt.get(att)==never:
859 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
860 hash_attr_usn, basedn, usns, samdb):
861 """ Check if we should keep the attribute modification or not
863 :param delta: A message diff object
864 :param att: An attribute
865 :param message: A function to print messages
866 :param reference: A message object for the current entry comming from
867 the reference provision.
868 :param current: A message object for the current entry commin from
869 the current provision.
870 :param hash_attr_usn: A dictionnary with attribute name as keys,
871 USN and invocation id as values.
872 :param basedn: The DN of the partition
873 :param usns: A dictionnary with invocation ID as keys and USN ranges
875 :param samdb: A ldb object pointing to the sam DB
877 :return: The modified message diff.
884 for att in list(delta):
885 if att in ["dn", "objectSid"]:
889 # We have updated by provision usn information so let's exploit
890 # replMetadataProperties
891 if att in forwardlinked:
892 curval = current[0].get(att, ())
893 refval = reference[0].get(att, ())
894 handle_links(samdb, att, basedn, current[0]["dn"],
895 curval, refval, delta)
898 if isFirst and len(delta.items())>1:
900 txt = "%s\n" % (str(dn))
902 if handle_special_case(att, delta, reference, current, True, None, None):
903 # This attribute is "complicated" to handle and handling
904 # was done in handle_special_case
908 if hash_attr_usn.get(att):
909 [attrUSN, attInvId] = hash_attr_usn.get(att)
912 # If it's a replicated attribute and we don't have any USN
913 # information about it. It means that we never saw it before
915 # If it is a replicated attribute but we are not master on it
916 # (ie. not initially added in the provision we masterize).
918 if isReplicated(att):
921 message(CHANGE, "Non replicated attribute %s changed" % att)
924 if att == "nTSecurityDescriptor":
925 cursd = ndr_unpack(security.descriptor,
926 str(current[0]["nTSecurityDescriptor"]))
927 cursddl = cursd.as_sddl(names.domainsid)
928 refsd = ndr_unpack(security.descriptor,
929 str(reference[0]["nTSecurityDescriptor"]))
930 refsddl = refsd.as_sddl(names.domainsid)
932 if get_diff_sddls(refsddl, cursddl) == "":
933 message(CHANGE, "sd are identical")
935 message(CHANGE, "sd are not identical")
938 # This attribute was last modified by another DC forget
940 message(CHANGE, "%sAttribute: %s has been "
941 "created/modified/deleted by another DC. "
942 "Doing nothing" % (txt, att))
946 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
947 message(CHANGE, "%sAttribute: %s was not "
948 "created/modified/deleted during a "
949 "provision or upgradeprovision. Current "
950 "usn: %d. Doing nothing" % (txt, att,
956 if att == "defaultSecurityDescriptor":
959 message(CHANGE, "%sAttribute: %s will be modified"
960 "/deleted it was last modified "
961 "during a provision. Current usn: "
962 "%d" % (txt, att, attrUSN))
965 message(CHANGE, "%sAttribute: %s will be added because "
966 "it did not exist before" % (txt, att))
972 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
973 """ This function updates the object that are already present in the
976 :param ref_samdb: An LDB object pointing to the reference provision
977 :param samdb: An LDB object pointing to the updated provision
978 :param basedn: A string with the value of the base DN for the provision
980 :param listPresent: A list of object that is present in the provision
981 :param usns: A list of USN range modified by previous provision and
982 upgradeprovision grouped by invocation ID
985 # This hash is meant to speedup lookup of attribute name from an oid,
986 # it's for the replPropertyMetaData handling
988 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
989 controls=["search_options:1:2"], attrs=["attributeID",
993 strDisplay = str(e.get("lDAPDisplayName"))
994 hash_oid_name[str(e.get("attributeID"))] = strDisplay
996 msg = "Unable to insert missing elements: circular references"
997 raise ProvisioningError(msg)
1000 controls = ["search_options:1:2", "sd_flags:1:0"]
1001 if usns is not None:
1002 message(CHANGE, "Using replPropertyMetadata for change selection")
1003 for dn in listPresent:
1004 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1005 scope=SCOPE_SUBTREE,
1007 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1008 scope=SCOPE_SUBTREE, controls=controls)
1011 (str(current[0].dn) != str(reference[0].dn)) and
1012 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1014 message(CHANGE, "Names are the same except for the case. "
1015 "Renaming %s to %s" % (str(current[0].dn),
1016 str(reference[0].dn)))
1017 identic_rename(samdb, reference[0].dn)
1018 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1019 scope=SCOPE_SUBTREE,
1022 delta = samdb.msg_diff(current[0], reference[0])
1024 for att in backlinked:
1027 for att in attrNotCopied:
1030 delta.remove("name")
1032 if len(delta.items()) == 1:
1035 if len(delta.items()) > 1 and usns is not None:
1036 # Fetch the replPropertyMetaData
1037 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1038 scope=SCOPE_SUBTREE, controls=controls,
1039 attrs=["replPropertyMetaData"])
1040 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1041 str(res[0]["replPropertyMetaData"])).ctr
1045 # We put in this hash only modification
1046 # made on the current host
1047 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1048 if str(o.originating_invocation_id) in usns.keys():
1049 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1051 hash_attr_usn[att] = [-1, None]
1053 if usns is not None:
1054 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1055 current, hash_attr_usn,
1056 basedn, usns, samdb)
1058 delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1061 if len(delta.items()) >1:
1062 # Skip dn as the value is not really changed ...
1063 attributes=", ".join(delta.keys()[1:])
1065 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1066 # Let's try to reduce as much as possible the use of relax control
1067 for attr in delta.keys():
1068 if attr.lower() in relaxedatt:
1069 modcontrols = ["relax:0", "provision:0"]
1070 message(CHANGE, "%s is different from the reference one, changed"
1071 " attributes: %s\n" % (dn, attributes))
1073 samdb.modify(delta, modcontrols)
1076 def reload_full_schema(samdb, names):
1077 """Load the updated schema with all the new and existing classes
1080 :param samdb: An LDB object connected to the sam.ldb of the update
1082 :param names: List of key provision parameters
1085 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1086 scope=SCOPE_SUBTREE)
1091 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1093 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1094 prefixmap_data = b64encode(prefixmap_data)
1096 # We don't actually add this ldif, just parse it
1097 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1099 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1102 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1103 """Check differences between the reference provision and the upgraded one.
1105 It looks for all objects which base DN is name.
1107 This function will also add the missing object and update existing object
1108 to add or remove attributes that were missing.
1110 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1112 :param samdb: An LDB object connected to the sam.ldb of the update
1114 :param basedn: String value of the DN of the partition
1115 :param names: List of key provision parameters
1116 :param schema: A Schema object
1117 :param provisionUSNs: A dictionnary with range of USN modified during provision
1118 or upgradeprovision. Ranges are grouped by invocationID.
1119 :param prereloadfunc: A function that must be executed just before the reload
1130 # Connect to the reference provision and get all the attribute in the
1131 # partition referred by name
1132 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1133 scope=SCOPE_SUBTREE, attrs=["dn"],
1134 controls=["search_options:1:2"])
1136 current = samdb.search(expression="objectClass=*", base=basedn,
1137 scope=SCOPE_SUBTREE, attrs=["dn"],
1138 controls=["search_options:1:2"])
1139 # Create a hash for speeding the search of new object
1140 for i in range(0, len(reference)):
1141 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1143 # Create a hash for speeding the search of existing object in the
1145 for i in range(0, len(current)):
1146 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1149 for k in hash_new.keys():
1150 if not hash.has_key(k):
1151 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1152 listMissing.append(hash_new[k])
1154 listPresent.append(hash_new[k])
1156 # Sort the missing object in order to have object of the lowest level
1157 # first (which can be containers for higher level objects)
1158 listMissing.sort(dn_sort)
1159 listPresent.sort(dn_sort)
1161 # The following lines is to load the up to
1162 # date schema into our current LDB
1163 # a complete schema is needed as the insertion of attributes
1164 # and class is done against it
1165 # and the schema is self validated
1166 samdb.set_schema(schema)
1168 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1169 add_deletedobj_containers(ref_samdb, samdb, names)
1171 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1174 message(SIMPLE, "Reloading a merged schema, which might trigger "
1175 "reindexing so please be patient")
1176 reload_full_schema(samdb, names)
1177 message(SIMPLE, "Schema reloaded!")
1179 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1181 message(SIMPLE, "There are %d changed objects" % (changed))
1184 except StandardError, err:
1185 message(ERROR, "Exception during upgrade of samdb:")
1186 (typ, val, tb) = sys.exc_info()
1187 traceback.print_exception(typ, val, tb)
1191 def check_updated_sd(ref_sam, cur_sam, names):
1192 """Check if the security descriptor in the upgraded provision are the same
1195 :param ref_sam: A LDB object connected to the sam.ldb file used as
1196 the reference provision
1197 :param cur_sam: A LDB object connected to the sam.ldb file used as
1199 :param names: List of key provision parameters"""
1200 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1201 scope=SCOPE_SUBTREE,
1202 attrs=["dn", "nTSecurityDescriptor"],
1203 controls=["search_options:1:2"])
1204 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1205 scope=SCOPE_SUBTREE,
1206 attrs=["dn", "nTSecurityDescriptor"],
1207 controls=["search_options:1:2"])
1209 for i in range(0, len(reference)):
1210 refsd = ndr_unpack(security.descriptor,
1211 str(reference[i]["nTSecurityDescriptor"]))
1212 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1215 for i in range(0, len(current)):
1216 key = str(current[i]["dn"]).lower()
1217 if hash.has_key(key):
1218 cursd = ndr_unpack(security.descriptor,
1219 str(current[i]["nTSecurityDescriptor"]))
1220 sddl = cursd.as_sddl(names.domainsid)
1221 if sddl != hash[key]:
1222 txt = get_diff_sddls(hash[key], sddl)
1224 message(CHANGESD, "On object %s ACL is different"
1225 " \n%s" % (current[i]["dn"], txt))
1229 def fix_partition_sd(samdb, names):
1230 """This function fix the SD for partition containers (basedn, configdn, ...)
1231 This is needed because some provision use to have broken SD on containers
1233 :param samdb: An LDB object pointing to the sam of the current provision
1234 :param names: A list of key provision parameters
1236 # First update the SD for the rootdn
1237 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
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_domain_descriptor(names.domainsid)
1243 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1244 "nTSecurityDescriptor")
1246 # Then the config dn
1247 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1248 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1249 controls=["search_options:1:2"])
1251 delta.dn = Dn(samdb, str(res[0]["dn"]))
1252 descr = get_config_descriptor(names.domainsid)
1253 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1254 "nTSecurityDescriptor" )
1256 # Then the schema dn
1257 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1258 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1259 controls=["search_options:1:2"])
1262 delta.dn = Dn(samdb, str(res[0]["dn"]))
1263 descr = get_schema_descriptor(names.domainsid)
1264 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1265 "nTSecurityDescriptor" )
1268 def rebuild_sd(samdb, names):
1269 """Rebuild security descriptor of the current provision from scratch
1271 During the different pre release of samba4 security descriptors (SD)
1272 were notarly broken (up to alpha11 included)
1273 This function allow to get them back in order, this function make the
1274 assumption that nobody has modified manualy an SD
1275 and so SD can be safely recalculated from scratch to get them right.
1277 :param names: List of key provision parameters"""
1281 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1282 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1283 controls=["search_options:1:2"])
1285 if not (str(obj["dn"]) == str(names.rootdn) or
1286 str(obj["dn"]) == str(names.configdn) or
1287 str(obj["dn"]) == str(names.schemadn)):
1288 hash[str(obj["dn"])] = obj["whenCreated"]
1290 listkeys = hash.keys()
1291 listkeys.sort(dn_sort)
1293 for key in listkeys:
1296 delta.dn = Dn(samdb, key)
1297 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1299 samdb.modify(delta, ["recalculate_sd:0"])
1301 # XXX: We should always catch an explicit exception.
1302 # What could go wrong here?
1303 samdb.transaction_cancel()
1304 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1305 scope=SCOPE_SUBTREE,
1306 attrs=["dn", "nTSecurityDescriptor"],
1307 controls=["search_options:1:2"])
1308 badsd = ndr_unpack(security.descriptor,
1309 str(res[0]["nTSecurityDescriptor"]))
1310 print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1313 def removeProvisionUSN(samdb):
1314 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1315 entry = samdb.search(expression="dn=@PROVISION", base = "",
1316 scope=SCOPE_SUBTREE,
1319 empty.dn = entry[0].dn
1320 delta = samdb.msg_diff(entry[0], empty)
1322 delta.dn = entry[0].dn
1325 def remove_stored_generated_attrs(paths, creds, session, lp):
1326 """Remove previously stored constructed attributes
1328 :param paths: List of paths for different provision objects
1329 from the upgraded provision
1330 :param creds: A credential object
1331 :param session: A session object
1332 :param lp: A line parser object
1333 :return: An associative array whose key are the different constructed
1334 attributes and the value the dn where this attributes were found.
1338 def simple_update_basesamdb(newpaths, paths, names):
1339 """Update the provision container db: sam.ldb
1340 This function is aimed at very old provision (before alpha9)
1342 :param newpaths: List of paths for different provision objects
1343 from the reference provision
1344 :param paths: List of paths for different provision objects
1345 from the upgraded provision
1346 :param names: List of key provision parameters"""
1348 message(SIMPLE, "Copy samdb")
1349 shutil.copy(newpaths.samdb, paths.samdb)
1351 message(SIMPLE, "Update partitions filename if needed")
1352 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1353 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1354 usersldb = os.path.join(paths.private_dir, "users.ldb")
1355 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1357 if not os.path.isdir(samldbdir):
1359 os.chmod(samldbdir, 0700)
1360 if os.path.isfile(schemaldb):
1361 shutil.copy(schemaldb, os.path.join(samldbdir,
1362 "%s.ldb"%str(names.schemadn).upper()))
1363 os.remove(schemaldb)
1364 if os.path.isfile(usersldb):
1365 shutil.copy(usersldb, os.path.join(samldbdir,
1366 "%s.ldb"%str(names.rootdn).upper()))
1368 if os.path.isfile(configldb):
1369 shutil.copy(configldb, os.path.join(samldbdir,
1370 "%s.ldb"%str(names.configdn).upper()))
1371 os.remove(configldb)
1374 def update_privilege(ref_private_path, cur_private_path):
1375 """Update the privilege database
1377 :param ref_private_path: Path to the private directory of the reference
1379 :param cur_private_path: Path to the private directory of the current
1380 (and to be updated) provision."""
1381 message(SIMPLE, "Copy privilege")
1382 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1383 os.path.join(cur_private_path, "privilege.ldb"))
1386 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1387 """Upgrade the SAM DB contents for all the provision partitions
1389 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1391 :param samdb: An LDB object connected to the sam.ldb of the update
1393 :param names: List of key provision parameters
1394 :param provisionUSNs: A dictionnary with range of USN modified during provision
1395 or upgradeprovision. Ranges are grouped by invocationID.
1396 :param schema: A Schema object that represent the schema of the provision
1397 :param prereloadfunc: A function that must be executed just before the reload
1401 message(SIMPLE, "Starting update of samdb")
1402 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1403 schema, provisionUSNs, prereloadfunc)
1405 message(SIMPLE, "Update of samdb finished")
1408 message(SIMPLE, "Update failed")
1412 def copyxattrs(dir, refdir):
1413 """ Copy owner, groups, extended ACL and NT acls from
1414 a reference dir to a destination dir
1416 Both dir are supposed to hold the same files
1417 :param dir: Destination dir
1418 :param refdir: Reference directory"""
1421 for root, dirs, files in os.walk(dir, topdown=True):
1423 subdir=root[len(dir):]
1424 ref = os.path.join("%s%s" % (refdir, subdir), name)
1425 statsinfo = os.stat(ref)
1426 tgt = os.path.join(root, name)
1429 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1430 # Get the xattr attributes if any
1432 attribute = samba.xattr_native.wrap_getxattr(ref,
1433 xattr.XATTR_NTACL_NAME)
1434 samba.xattr_native.wrap_setxattr(tgt,
1435 xattr.XATTR_NTACL_NAME,
1439 attribute = samba.xattr_native.wrap_getxattr(ref,
1440 "system.posix_acl_access")
1441 samba.xattr_native.wrap_setxattr(tgt,
1442 "system.posix_acl_access",
1447 subdir=root[len(dir):]
1448 ref = os.path.join("%s%s" % (refdir, subdir), name)
1449 statsinfo = os.stat(ref)
1450 tgt = os.path.join(root, name)
1452 os.chown(os.path.join(root, name), statsinfo.st_uid,
1455 attribute = samba.xattr_native.wrap_getxattr(ref,
1456 xattr.XATTR_NTACL_NAME)
1457 samba.xattr_native.wrap_setxattr(tgt,
1458 xattr.XATTR_NTACL_NAME,
1462 attribute = samba.xattr_native.wrap_getxattr(ref,
1463 "system.posix_acl_access")
1464 samba.xattr_native.wrap_setxattr(tgt,
1465 "system.posix_acl_access",
1472 def backup_provision(paths, dir):
1473 """This function backup the provision files so that a rollback
1476 :param paths: Paths to different objects
1477 :param dir: Directory where to store the backup
1480 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1481 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1482 shutil.copy2(paths.samdb, dir)
1483 shutil.copy2(paths.secrets, dir)
1484 shutil.copy2(paths.idmapdb, dir)
1485 shutil.copy2(paths.privilege, dir)
1486 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1487 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1488 shutil.copy2(paths.smbconf, dir)
1489 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1491 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1492 if not os.path.isdir(samldbdir):
1493 samldbdir = paths.private_dir
1494 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1495 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1496 usersldb = os.path.join(paths.private_dir, "users.ldb")
1497 shutil.copy2(schemaldb, dir)
1498 shutil.copy2(usersldb, dir)
1499 shutil.copy2(configldb, dir)
1501 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1506 def sync_calculated_attributes(samdb, names):
1507 """Synchronize attributes used for constructed ones, with the
1508 old constructed that were stored in the database.
1510 This apply for instance to msds-keyversionnumber that was
1511 stored and that is now constructed from replpropertymetadata.
1513 :param samdb: An LDB object attached to the currently upgraded samdb
1514 :param names: Various key parameter about current provision.
1516 listAttrs = ["msDs-KeyVersionNumber"]
1517 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1518 if hash.has_key("msDs-KeyVersionNumber"):
1519 increment_calculated_keyversion_number(samdb, names.rootdn,
1520 hash["msDs-KeyVersionNumber"])
1522 # Synopsis for updateprovision
1523 # 1) get path related to provision to be update (called current)
1524 # 2) open current provision ldbs
1525 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1527 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1528 # by either upgradeprovision or provision
1529 # 5) creation of a new provision the latest version of provision script
1530 # (called reference)
1531 # 6) get reference provision paths
1532 # 7) open reference provision ldbs
1533 # 8) setup helpers data that will help the update process
1534 # 9) update the privilege ldb by copying the one of referecence provision to
1535 # the current provision
1536 # 10)get the oemInfo field, this field contains information about the different
1537 # provision that have been done
1538 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1539 # integer) or none of this the following things are done
1540 # A) When alpha9 or alphaxx is present
1541 # The base sam.ldb file is updated by looking at the difference between
1542 # referrence one and the current one. Everything is copied with the
1543 # exception of lastProvisionUSN attributes.
1544 # B) Other case (it reflect that that provision was done before alpha9)
1545 # The base sam.ldb of the reference provision is copied over
1546 # the current one, if necessary ldb related to partitions are moved
1548 # The highest used USN is fetched so that changed by upgradeprovision
1549 # usn can be tracked
1550 # 12)A Schema object is created, it will be used to provide a complete
1551 # schema to current provision during update (as the schema of the
1552 # current provision might not be complete and so won't allow some
1553 # object to be created)
1554 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1555 # 14)The secrets db is updated by pull all the difference from the reference
1556 # provision into the current provision
1557 # 15)As the previous step has most probably modified the password stored in
1558 # in secret for the current DC, a new password is generated,
1559 # the kvno is bumped and the entry in samdb is also updated
1560 # 16)For current provision older than alpha9, we must fix the SD a little bit
1561 # administrator to update them because SD used to be generated with the
1562 # system account before alpha9.
1563 # 17)The highest usn modified so far is searched in the database it will be
1564 # the upper limit for usn modified during provision.
1565 # This is done before potential SD recalculation because we do not want
1566 # SD modified during recalculation to be marked as modified during provision
1567 # (and so possibly remplaced at next upgradeprovision)
1568 # 18)Rebuilt SD if the flag indicate to do so
1569 # 19)Check difference between SD of reference provision and those of the
1570 # current provision. The check is done by getting the sddl representation
1571 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1572 # Each part is verified separetly, for dacl and sacl ACL is splited into
1573 # ACEs and each ACE is verified separately (so that a permutation in ACE
1574 # didn't raise as an error).
1575 # 20)The oemInfo field is updated to add information about the fact that the
1576 # provision has been updated by the upgradeprovision version xxx
1577 # (the version is the one obtained when starting samba with the --version
1579 # 21)Check if the current provision has all the settings needed for dynamic
1580 # DNS update to work (that is to say the provision is newer than
1581 # january 2010). If not dns configuration file from reference provision
1582 # are copied in a sub folder and the administrator is invited to
1583 # do what is needed.
1584 # 22)If the lastProvisionUSN attribute was present it is updated to add
1585 # the range of usns modified by the current upgradeprovision
1588 # About updating the sam DB
1589 # The update takes place in update_partition function
1590 # This function read both current and reference provision and list all
1591 # the available DN of objects
1592 # If the string representation of a DN in reference provision is
1593 # equal to the string representation of a DN in current provision
1594 # (without taking care of case) then the object is flaged as being
1595 # present. If the object is not present in current provision the object
1596 # is being flaged as missing in current provision. Object present in current
1597 # provision but not in reference provision are ignored.
1598 # Once the list of objects present and missing is done, the deleted object
1599 # containers are created in the differents partitions (if missing)
1601 # Then the function add_missing_entries is called
1602 # This function will go through the list of missing entries by calling
1603 # add_missing_object for the given object. If this function returns 0
1604 # it means that the object needs some other object in order to be created
1605 # The object is reappended at the end of the list to be created later
1606 # (and preferably after all the needed object have been created)
1607 # The function keeps on looping on the list of object to be created until
1608 # it's empty or that the number of defered creation is equal to the number
1609 # of object that still needs to be created.
1611 # The function add_missing_object will first check if the object can be created.
1612 # That is to say that it didn't depends other not yet created objects
1613 # If requisit can't be fullfilled it exists with 0
1614 # Then it will try to create the missing entry by creating doing
1615 # an ldb_message_diff between the object in the reference provision and
1617 # This resulting object is filtered to remove all the back link attribute
1618 # (ie. memberOf) as they will be created by the other linked object (ie.
1619 # the one with the member attribute)
1620 # All attributes specified in the attrNotCopied array are
1621 # also removed it's most of the time generated attributes
1623 # After missing entries have been added the update_partition function will
1624 # take care of object that exist but that need some update.
1625 # In order to do so the function update_present is called with the list
1626 # of object that are present in both provision and that might need an update.
1628 # This function handle first case mismatch so that the DN in the current
1629 # provision have the same case as in reference provision
1631 # It will then construct an associative array consiting of attributes as
1632 # key and invocationid as value( if the originating invocation id is
1633 # different from the invocation id of the current DC the value is -1 instead).
1635 # If the range of provision modified attributes is present, the function will
1636 # use the replMetadataProperty update method which is the following:
1637 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1638 # creationTime, msDs-KeyVersionNumber, oEMInformation
1639 # Check for each attribute if its usn is within one of the modified by
1640 # provision range and if its originating id is the invocation id of the
1641 # current DC, then validate the update from reference to current.
1642 # If not or if there is no replMetatdataProperty for this attribute then we
1644 # Otherwise (case the range of provision modified attribute is not present) it
1645 # use the following process:
1646 # All attributes that need to be added are accepted at the exeption of those
1647 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1648 # correct flags specified.
1649 # For attributes that need to be modified or removed, a check is performed
1650 # in OverwrittenAtt, if the attribute is present and the modification flag
1651 # (remove, delete) is one of those listed for this attribute then modification
1652 # is accepted. For complicated handling of attribute update, the control is passed
1653 # to handle_special_case
1657 if __name__ == '__main__':
1658 global defSDmodified
1659 defSDmodified = False
1660 # From here start the big steps of the program
1661 # 1) First get files paths
1662 paths = get_paths(param, smbconf=smbconf)
1663 # Get ldbs with the system session, it is needed for searching
1664 # provision parameters
1665 session = system_session()
1667 # This variable will hold the last provision USN once if it exists.
1670 ldbs = get_ldbs(paths, creds, session, lp)
1671 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1672 prefix="backupprovision")
1673 backup_provision(paths, backupdir)
1675 ldbs.startTransactions()
1677 # 3) Guess all the needed names (variables in fact) from the current
1679 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1682 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1683 if lastProvisionUSNs is not None:
1685 for k in lastProvisionUSNs.keys():
1686 for r in lastProvisionUSNs[k]:
1690 "Find last provision USN, %d invocation(s) for a total of %d ranges" % \
1691 (len(lastProvisionUSNs.keys()), v /2 ))
1693 if lastProvisionUSNs.get("default") != None:
1694 message(CHANGE, "Old style for usn ranges used")
1695 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1696 del lastProvisionUSNs["default"]
1697 # Objects will be created with the admin session
1698 # (not anymore system session)
1699 adm_session = admin_session(lp, str(names.domainsid))
1700 # So we reget handle on objects
1701 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1702 if not opts.fixntacl:
1703 if not sanitychecks(ldbs.sam, names):
1704 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1705 "Check the messages and correct the errors "
1706 "before rerunning upgradeprovision")
1707 ldbs.groupedRollback()
1710 # Let's see provision parameters
1711 print_provision_key_parameters(names)
1713 # 5) With all this information let's create a fresh new provision used as
1715 message(SIMPLE, "Creating a reference provision")
1716 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1717 prefix="referenceprovision")
1718 newprovision(names, creds, session, smbconf, provisiondir,
1723 # We need to get a list of object which SD is directly computed from
1724 # defaultSecurityDescriptor.
1725 # This will allow us to know which object we can rebuild the SD in case
1726 # of change of the parent's SD or of the defaultSD.
1727 # Get file paths of this new provision
1728 newpaths = get_paths(param, targetdir=provisiondir)
1729 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1730 new_ldbs.startTransactions()
1732 # 8) Populate some associative array to ease the update process
1733 # List of attribute which are link and backlink
1734 populate_links(new_ldbs.sam, names.schemadn)
1735 # List of attribute with ASN DN synthax)
1736 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1738 update_privilege(newpaths.private_dir, paths.private_dir)
1740 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1741 # Do some modification on sam.ldb
1742 ldbs.groupedCommit()
1743 new_ldbs.groupedCommit()
1746 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1748 # Starting from alpha9 we can consider that the structure is quite ok
1749 # and that we should do only dela
1750 deltaattr = delta_update_basesamdb(newpaths.samdb,
1758 simple_update_basesamdb(newpaths, paths, names)
1759 ldbs = get_ldbs(paths, creds, session, lp)
1760 removeProvisionUSN(ldbs.sam)
1762 ldbs.startTransactions()
1763 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1764 new_ldbs.startTransactions()
1767 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1768 # We create a closure that will be invoked just before schema reload
1769 def schemareloadclosure():
1770 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1771 options=["modules:"])
1773 if deltaattr is not None and len(deltaattr) > 1:
1776 deltaattr.remove("dn")
1777 for att in deltaattr:
1778 if att.lower() == "dn":
1780 if (deltaattr.get(att) is not None
1781 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1783 elif deltaattr.get(att) is None:
1786 message(CHANGE, "Applying delta to @ATTRIBUTES")
1787 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1788 basesam.modify(deltaattr)
1790 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1791 "there is not only add")
1794 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1795 schema, schemareloadclosure):
1796 message(SIMPLE, "Rolling back all changes. Check the cause"
1798 message(SIMPLE, "Your system is as it was before the upgrade")
1799 ldbs.groupedRollback()
1800 new_ldbs.groupedRollback()
1801 shutil.rmtree(provisiondir)
1804 # Try to reapply the change also when we do not change the sam
1805 # as the delta_upgrade
1806 schemareloadclosure()
1807 sync_calculated_attributes(ldbs.sam, names)
1808 res = ldbs.sam.search(expression="(samaccountname=dns)",
1809 scope=SCOPE_SUBTREE, attrs=["dn"],
1810 controls=["search_options:1:2"])
1812 message(SIMPLE, "You still have the old DNS object for managing "
1813 "dynamic DNS, but you didn't supply --full so "
1814 "a correct update can't be done")
1815 ldbs.groupedRollback()
1816 new_ldbs.groupedRollback()
1817 shutil.rmtree(provisiondir)
1820 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1822 res = ldbs.sam.search(expression="(samaccountname=dns)",
1823 scope=SCOPE_SUBTREE, attrs=["dn"],
1824 controls=["search_options:1:2"])
1827 ldbs.sam.delete(res[0]["dn"])
1828 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1829 scope=SCOPE_SUBTREE, attrs=["dn"])
1830 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1831 message(SIMPLE, "IMPORTANT!!! "
1832 "If you were using Dynamic DNS before you need "
1833 "to update your configuration, so that the "
1834 "tkey-gssapi-credential has the following value: "
1835 "DNS/%s.%s" % (names.netbiosname.lower(),
1836 names.realm.lower()))
1838 message(SIMPLE, "Update machine account")
1839 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1841 # 16) SD should be created with admin but as some previous acl were so wrong
1842 # that admin can't modify them we have first to recreate them with the good
1843 # form but with system account and then give the ownership to admin ...
1844 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1845 message(SIMPLE, "Fixing old povision SD")
1846 fix_partition_sd(ldbs.sam, names)
1847 rebuild_sd(ldbs.sam, names)
1849 # We calculate the max USN before recalculating the SD because we might
1850 # touch object that have been modified after a provision and we do not
1851 # want that the next upgradeprovision thinks that it has a green light
1855 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1857 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1858 # But in fact we should do it also if one object has its SD modified as
1859 # child might need rebuild
1861 message(SIMPLE, "Updating SD")
1862 ldbs.sam.set_session_info(adm_session)
1863 # Alpha10 was a bit broken still
1864 if re.match(r'.*alpha(\d|10)', str(oem)):
1865 fix_partition_sd(ldbs.sam, names)
1866 rebuild_sd(ldbs.sam, names)
1869 # Now we are quite confident in the recalculate process of the SD, we make
1871 # Also the check must be done in a clever way as for the moment we just
1873 if opts.debugchangesd:
1874 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1877 updateOEMInfo(ldbs.sam, str(names.rootdn))
1879 check_for_DNS(newpaths.private_dir, paths.private_dir)
1881 if lastProvisionUSNs is not None:
1882 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1883 if opts.full and (names.policyid is None or names.policyid_dc is None):
1884 update_policyids(names, ldbs.sam)
1885 if opts.full or opts.resetfileacl or opts.fixntacl:
1887 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1888 except ProvisioningError, e:
1889 message(ERROR, "The policy for domain controller is missing. "
1890 "You should restart upgradeprovision with --full")
1892 message(ERROR, "Setting ACL not supported on your filesystem")
1895 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1896 except ProvisioningError, e:
1897 message(ERROR, "The policy for domain controller is missing. "
1898 "You should restart upgradeprovision with --full")
1899 if not opts.fixntacl:
1900 ldbs.groupedCommit()
1901 new_ldbs.groupedCommit()
1902 message(SIMPLE, "Upgrade finished!")
1903 # remove reference provision now that everything is done !
1904 # So we have reindexed first if need when the merged schema was reloaded
1905 # (as new attributes could have quick in)
1906 # But the second part of the update (when we update existing objects
1907 # can also have an influence on indexing as some attribute might have their
1908 # searchflag modificated
1909 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1910 "after modification")
1911 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1912 message(SIMPLE, "Reindexing finished")
1914 shutil.rmtree(provisiondir)
1916 ldbs.groupedRollback()
1917 message(SIMPLE, "ACLs fixed !")
1918 except StandardError, err:
1919 message(ERROR, "A problem occurred while trying to upgrade your "
1920 "provision. A full backup is located at %s" % backupdir)
1921 if opts.debugall or opts.debugchange:
1922 (typ, val, tb) = sys.exc_info()
1923 traceback.print_exception(typ, val, tb)