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 (find_setup_dir, get_domain_descriptor,
48 get_config_descriptor,
49 ProvisioningError, get_last_provision_usn,
50 get_max_usn, update_provision_usn)
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,
55 find_provision_key_parameters, get_ldbs,
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 increment_calculated_keyversion_number)
65 replace=2**FLAG_MOD_REPLACE
67 delete=2**FLAG_MOD_DELETE
71 # Will be modified during provision to tell if default sd has been modified
74 #Errors are always logged
76 __docformat__ = "restructuredText"
78 # Attributes that are never copied from the reference provision (even if they
79 # do not exist in the destination object).
80 # This is most probably because they are populated automatcally when object is
82 # This also apply to imported object from reference provision
83 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
84 "objectGUID": 1, "uSNCreated": 1,
85 "replPropertyMetaData": 1, "uSNChanged": 1,
86 "parentGUID": 1, "objectCategory": 1,
87 "distinguishedName": 1, "nTMixedDomain": 1,
88 "showInAdvancedViewOnly": 1, "instanceType": 1,
89 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
90 "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
91 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
92 "supplementalCredentials":1, "gPCUserExtensionNames":1,
93 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
94 "possibleInferiors":1, "privilege":1,
97 # Usually for an object that already exists we do not overwrite attributes as
98 # they might have been changed for good reasons. Anyway for a few of them it's
99 # mandatory to replace them otherwise the provision will be broken somehow.
100 # But for attribute that are just missing we do not have to specify them as the default
101 # behavior is to add missing attribute
102 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
103 "systemOnly":replace, "searchFlags":replace,
104 "mayContain":replace, "systemFlags":replace+add,
105 "description":replace, "operatingSystemVersion":replace,
106 "adminPropertyPages":replace, "groupType":replace,
107 "wellKnownObjects":replace, "privilege":never,
108 "defaultSecurityDescriptor": replace,
109 "rIDAvailablePool": never,
110 "defaultSecurityDescriptor": replace + add,
111 "isMemberOfPartialAttributeSet": delete,
112 "attributeDisplayNames": replace + add}
116 forwardlinked = set()
118 def define_what_to_log(opts):
122 if opts.debugchangesd:
123 what = what | CHANGESD
126 if opts.debugprovision:
127 what = what | PROVISION
129 what = what | CHANGEALL
133 parser = optparse.OptionParser("provision [options]")
134 sambaopts = options.SambaOptions(parser)
135 parser.add_option_group(sambaopts)
136 parser.add_option_group(options.VersionOptions(parser))
137 credopts = options.CredentialsOptions(parser)
138 parser.add_option_group(credopts)
139 parser.add_option("--setupdir", type="string", metavar="DIR",
140 help="directory with setup files")
141 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
142 parser.add_option("--debugguess", action="store_true",
143 help="Print information on what is different but won't be changed")
144 parser.add_option("--debugchange", action="store_true",
145 help="Print information on what is different but won't be changed")
146 parser.add_option("--debugchangesd", action="store_true",
147 help="Print information security descriptors differences")
148 parser.add_option("--debugall", action="store_true",
149 help="Print all available information (very verbose)")
150 parser.add_option("--resetfileacl", action="store_true",
151 help="Force a reset on filesystem acls in sysvol / netlogon share")
152 parser.add_option("--full", action="store_true",
153 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
155 opts = parser.parse_args()[0]
157 handler = logging.StreamHandler(sys.stdout)
158 upgrade_logger = logging.getLogger("upgradeprovision")
159 upgrade_logger.setLevel(logging.INFO)
161 upgrade_logger.addHandler(handler)
163 provision_logger = logging.getLogger("provision")
164 provision_logger.addHandler(handler)
166 whatToLog = define_what_to_log(opts)
168 def message(what, text):
169 """Print a message if this message type has been selected to be printed
171 :param what: Category of the message
172 :param text: Message to print """
173 if (whatToLog & what) or what <= 0:
174 upgrade_logger.info("%s", text)
176 if len(sys.argv) == 1:
177 opts.interactive = True
178 lp = sambaopts.get_loadparm()
179 smbconf = lp.configfile
181 creds = credopts.get_credentials(lp)
182 creds.set_kerberos_state(DONT_USE_KERBEROS)
183 setup_dir = opts.setupdir
184 if setup_dir is None:
185 setup_dir = find_setup_dir()
189 def check_for_DNS(refprivate, private):
190 """Check if the provision has already the requirement for dynamic dns
192 :param refprivate: The path to the private directory of the reference
194 :param private: The path to the private directory of the upgraded
197 spnfile = "%s/spn_update_list" % private
198 namedfile = lp.get("dnsupdate:path")
201 namedfile = "%s/named.conf.update" % private
203 if not os.path.exists(spnfile):
204 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
206 destdir = "%s/new_dns" % private
207 dnsdir = "%s/dns" % private
209 if not os.path.exists(namedfile):
210 if not os.path.exists(destdir):
212 if not os.path.exists(dnsdir):
214 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
215 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
216 message(SIMPLE, "It seems that you provision didn't integrate new rules "
217 "for dynamic dns update of domain related entries")
218 message(SIMPLE, "A copy of the new bind configuration files and "
219 "template as been put in %s, you should read them and configure dynamic "
220 " dns update" % destdir)
223 def populate_links(samdb, schemadn):
224 """Populate an array with all the back linked attributes
226 This attributes that are modified automaticaly when
227 front attibutes are changed
229 :param samdb: A LDB object for sam.ldb file
230 :param schemadn: DN of the schema for the partition"""
231 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
232 backlinked.extend(linkedAttHash.values())
233 for t in linkedAttHash.keys():
237 def populate_dnsyntax(samdb, schemadn):
238 """Populate an array with all the attributes that have DN synthax
241 :param samdb: A LDB object for sam.ldb file
242 :param schemadn: DN of the schema for the partition"""
243 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
244 str(schemadn)), scope=SCOPE_SUBTREE,
245 attrs=["lDAPDisplayName"])
247 dn_syntax_att.append(elem["lDAPDisplayName"])
250 def sanitychecks(samdb, names):
251 """Make some checks before trying to update
253 :param samdb: An LDB object opened on sam.ldb
254 :param names: list of key provision parameters
255 :return: Status of check (1 for Ok, 0 for not Ok) """
256 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
257 scope=SCOPE_SUBTREE, attrs=["dn"],
258 controls=["search_options:1:2"])
260 print "No DC found, your provision is most probably hardly broken !"
263 print "Found %d domain controllers, for the moment upgradeprovision" \
264 "is not able to handle upgrade on domain with more than one DC, please demote" \
265 " the other(s) DC(s) before upgrading" % len(res)
271 def print_provision_key_parameters(names):
272 """Do a a pretty print of provision parameters
274 :param names: list of key provision parameters """
275 message(GUESS, "rootdn :" + str(names.rootdn))
276 message(GUESS, "configdn :" + str(names.configdn))
277 message(GUESS, "schemadn :" + str(names.schemadn))
278 message(GUESS, "serverdn :" + str(names.serverdn))
279 message(GUESS, "netbiosname :" + names.netbiosname)
280 message(GUESS, "defaultsite :" + names.sitename)
281 message(GUESS, "dnsdomain :" + names.dnsdomain)
282 message(GUESS, "hostname :" + names.hostname)
283 message(GUESS, "domain :" + names.domain)
284 message(GUESS, "realm :" + names.realm)
285 message(GUESS, "invocationid:" + names.invocation)
286 message(GUESS, "policyguid :" + names.policyid)
287 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
288 message(GUESS, "domainsid :" + str(names.domainsid))
289 message(GUESS, "domainguid :" + names.domainguid)
290 message(GUESS, "ntdsguid :" + names.ntdsguid)
291 message(GUESS, "domainlevel :" + str(names.domainlevel))
294 def handle_special_case(att, delta, new, old, usn, basedn, aldb):
295 """Define more complicate update rules for some attributes
297 :param att: The attribute to be updated
298 :param delta: A messageElement object that correspond to the difference
299 between the updated object and the reference one
300 :param new: The reference object
301 :param old: The Updated object
302 :param usn: The highest usn modified by a previous (upgrade)provision
303 :param basedn: The base DN of the provision
304 :param aldb: An ldb object used to build DN
305 :return: True to indicate that the attribute should be kept, False for
308 flag = delta.get(att).flags()
309 # We do most of the special case handle if we do not have the
310 # highest usn as otherwise the replPropertyMetaData will guide us more
313 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
314 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
315 "CN=Services,CN=Configuration,%s" % basedn)
318 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
319 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
321 message(SIMPLE, "We suggest that you change the userAccountControl"
322 " for user Administrator from value %d to %d" %
323 (int(str(old[0][att])), int(str(new[0][att]))))
325 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
326 if (long(str(old[0][att])) == 0):
327 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
330 if (att == "member" and flag == FLAG_MOD_REPLACE):
334 for elem in old[0][att]:
335 hash[str(elem).lower()]=1
336 newval.append(str(elem))
338 for elem in new[0][att]:
339 if not hash.has_key(str(elem).lower()):
341 newval.append(str(elem))
343 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
348 if (att in ("gPLink", "gPCFileSysPath") and
349 flag == FLAG_MOD_REPLACE and
350 str(new[0].dn).lower() == str(old[0].dn).lower()):
354 if att == "forceLogoff":
355 ref=0x8000000000000000
356 oldval=int(old[0][att][0])
357 newval=int(new[0][att][0])
358 ref == old and ref == abs(new)
361 if att in ("adminDisplayName", "adminDescription"):
364 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
365 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
368 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
369 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
372 if (str(old[0].dn) == "%s" % (str(names.rootdn))
373 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
376 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
379 # This is a bit of special animal as we might have added
380 # already SPN entries to the list that has to be modified
381 # So we go in detail to try to find out what has to be added ...
382 if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
386 for elem in old[0][att]:
388 newval.append(str(elem))
390 for elem in new[0][att]:
391 if not hash.has_key(str(elem)):
393 newval.append(str(elem))
395 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
402 def dump_denied_change(dn, att, flagtxt, current, reference):
403 """Print detailed information about why a changed is denied
405 :param dn: DN of the object which attribute is denied
406 :param att: Attribute that was supposed to be upgraded
407 :param flagtxt: Type of the update that should be performed
408 (add, change, remove, ...)
409 :param current: Value(s) of the current attribute
410 :param reference: Value(s) of the reference attribute"""
412 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
413 +" is not allowed to be changed/removed, I discard this change")
414 if att != "objectSid" :
416 for e in range(0, len(current)):
417 message(CHANGE, "old %d : %s" % (i, str(current[e])))
419 if reference is not None:
421 for e in range(0, len(reference)):
422 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
425 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
426 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
429 def handle_special_add(samdb, dn, names):
430 """Handle special operation (like remove) on some object needed during
433 This is mostly due to wrong creation of the object in previous provision.
434 :param samdb: An Ldb object representing the SAM database
435 :param dn: DN of the object to inspect
436 :param names: list of key provision parameters
440 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
442 #This entry was misplaced lets remove it if it exists
443 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
446 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
448 #This entry was misplaced lets remove it if it exists
449 dntoremove = "CN=Certificate Service DCOM Access,"\
450 "CN=Users, %s" % names.rootdn
453 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
455 #This entry was misplaced lets remove it if it exists
456 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
458 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
460 #This entry was misplaced lets remove it if it exists
461 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
463 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
464 "CN=Configuration,%s" % names.rootdn)
466 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
467 "CN=WellKnown Security Principals,"
468 "CN=Configuration,%s" % names.rootdn)
470 res = samdb.search(expression="(dn=%s)" % oldDn,
471 base=str(names.rootdn),
472 scope=SCOPE_SUBTREE, attrs=["dn"],
473 controls=["search_options:1:2"])
475 message(CHANGE, "Existing object %s must be replaced by %s,"
476 "Renaming old object" % (str(oldDn), str(dn)))
477 samdb.rename(oldDn, objDn)
481 if dntoremove is not None:
482 res = samdb.search(expression="(dn=%s)" % dntoremove,
483 base=str(names.rootdn),
484 scope=SCOPE_SUBTREE, attrs=["dn"],
485 controls=["search_options:1:2"])
487 message(CHANGE, "Existing object %s must be replaced by %s,"
488 "removing old object" % (dntoremove, str(dn)))
489 samdb.delete(res[0]["dn"])
493 def check_dn_nottobecreated(hash, index, listdn):
494 """Check if one of the DN present in the list has a creation order
495 greater than the current.
497 Hash is indexed by dn to be created, with each key
498 is associated the creation order.
500 First dn to be created has the creation order 0, second has 1, ...
501 Index contain the current creation order
503 :param hash: Hash holding the different DN of the object to be
505 :param index: Current creation order
506 :param listdn: List of DNs on which the current DN depends on
507 :return: None if the current object do not depend on other
508 object or if all object have been created before."""
512 key = str(dn).lower()
513 if hash.has_key(key) and hash[key] > index:
519 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
520 """Add a new object if the dependencies are satisfied
522 The function add the object if the object on which it depends are already
525 :param ref_samdb: Ldb object representing the SAM db of the reference
527 :param samdb: Ldb object representing the SAM db of the upgraded
529 :param dn: DN of the object to be added
530 :param names: List of key provision parameters
531 :param basedn: DN of the partition to be updated
532 :param hash: Hash holding the different DN of the object to be
534 :param index: Current creation order
535 :return: True if the object was created False otherwise"""
537 if handle_special_add(samdb, dn, names):
539 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
540 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
542 delta = samdb.msg_diff(empty, reference[0])
546 if str(reference[0].get("cn")) == "RID Set":
547 for klass in reference[0].get("objectClass"):
548 if str(klass).lower() == "ridset":
551 if delta.get("objectSid"):
552 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
553 m = re.match(r".*-(\d+)$", sid)
554 if m and int(m.group(1))>999:
555 delta.remove("objectSid")
556 for att in hashAttrNotCopied.keys():
558 for att in backlinked:
560 depend_on_yettobecreated = None
561 for att in dn_syntax_att:
562 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
564 if depend_on_yet_tobecreated is not None:
565 message(CHANGE, "Object %s depends on %s in attribute %s,"
566 "delaying the creation" % (dn,
567 depend_on_yet_tobecreated, att))
572 message(CHANGE,"Object %s will be added" % dn)
573 samdb.add(delta, ["relax:0"])
575 message(CHANGE,"Object %s was skipped" % dn)
579 def gen_dn_index_hash(listMissing):
580 """Generate a hash associating the DN to its creation order
582 :param listMissing: List of DN
583 :return: Hash with DN as keys and creation order as values"""
585 for i in range(0, len(listMissing)):
586 hash[str(listMissing[i]).lower()] = i
589 def add_deletedobj_containers(ref_samdb, samdb, names):
590 """Add the object containter: CN=Deleted Objects
592 This function create the container for each partition that need one and
593 then reference the object into the root of the partition
595 :param ref_samdb: Ldb object representing the SAM db of the reference
597 :param samdb: Ldb object representing the SAM db of the upgraded provision
598 :param names: List of key provision parameters"""
601 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
602 partitions = [str(names.rootdn), str(names.configdn)]
603 for part in partitions:
604 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
605 base=part, scope=SCOPE_SUBTREE,
607 controls=["show_deleted:0"])
608 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
609 base=part, scope=SCOPE_SUBTREE,
611 controls=["show_deleted:0"])
612 if len(ref_delObjCnt) > len(delObjCnt):
613 reference = ref_samdb.search(expression="cn=Deleted Objects",
614 base=part, scope=SCOPE_SUBTREE,
615 controls=["show_deleted:0"])
617 delta = samdb.msg_diff(empty, reference[0])
619 delta.dn = Dn(samdb, str(reference[0]["dn"]))
620 for att in hashAttrNotCopied.keys():
625 res = samdb.search(expression="(objectClass=*)", base=part,
627 attrs=["dn", "wellKnownObjects"])
629 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
633 wko = res[0]["wellKnownObjects"]
635 # The wellKnownObject that we want to add.
637 if str(o) == targetWKO:
639 listwko.append(str(o))
642 listwko.append(targetWKO)
645 delta.dn = Dn(samdb, str(res[0]["dn"]))
646 delta["wellKnownObjects"] = MessageElement(listwko,
651 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
652 """Add the missing object whose DN is the list
654 The function add the object if the objects on which it depends are
657 :param ref_samdb: Ldb object representing the SAM db of the reference
659 :param samdb: Ldb object representing the SAM db of the upgraded
661 :param dn: DN of the object to be added
662 :param names: List of key provision parameters
663 :param basedn: DN of the partition to be updated
664 :param list: List of DN to be added in the upgraded provision"""
669 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
671 listMissing = listDefered
673 hashMissing = gen_dn_index_hash(listMissing)
674 for dn in listMissing:
675 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
679 # DN can't be created because it depends on some
680 # other DN in the list
681 listDefered.append(dn)
682 if len(listDefered) != 0:
683 raise ProvisioningError("Unable to insert missing elements:" \
684 "circular references")
686 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
687 """This function handle updates on links
689 :param samdb: An LDB object pointing to the updated provision
690 :param att: Attribute to update
691 :param basedn: The root DN of the provision
692 :param dn: The DN of the inspected object
693 :param value: The value of the attribute
694 :param ref_value: The value of this attribute in the reference provision
695 :param delta: The MessageElement object that will be applied for
696 transforming the current provision"""
698 res = samdb.search(expression="dn=%s" % dn, base=basedn,
699 controls=["search_options:1:2", "reveal:1"],
707 newlinklist.extend(value)
711 # for w2k domain level the reveal won't reveal anything ...
712 # it means that we can readd links that were removed on purpose ...
713 # Also this function in fact just accept add not removal
715 for e in res[0][att]:
716 if not hash.has_key(e):
717 # We put in the blacklist all the element that are in the "revealed"
718 # result and not in the "standard" result
719 # This element are links that were removed before and so that
720 # we don't wan't to readd
724 if not blacklist.has_key(e) and not hash.has_key(e):
725 newlinklist.append(str(e))
728 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
733 msg_elt_flag_strs = {
734 ldb.FLAG_MOD_ADD: "MOD_ADD",
735 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
736 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
739 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
740 """ This function updates the object that are already present in the
743 :param ref_samdb: An LDB object pointing to the reference provision
744 :param samdb: An LDB object pointing to the updated provision
745 :param basedn: A string with the value of the base DN for the provision
747 :param listPresent: A list of object that is present in the provision
748 :param usns: A list of USN range modified by previous provision and
750 :param invocationid: The value of the invocationid for the current DC"""
753 # This hash is meant to speedup lookup of attribute name from an oid,
754 # it's for the replPropertyMetaData handling
756 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
757 controls=["search_options:1:2"], attrs=["attributeID",
761 strDisplay = str(e.get("lDAPDisplayName"))
762 hash_oid_name[str(e.get("attributeID"))] = strDisplay
764 msg = "Unable to insert missing elements: circular references"
765 raise ProvisioningError(msg)
768 controls = ["search_options:1:2", "sd_flags:1:2"]
769 for dn in listPresent:
770 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
773 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
774 scope=SCOPE_SUBTREE, controls=controls)
777 (str(current[0].dn) != str(reference[0].dn)) and
778 (str(current[0].dn).upper() == str(reference[0].dn).upper())
780 message(CHANGE, "Name are the same but case change,"\
781 "let's rename %s to %s" % (str(current[0].dn),
782 str(reference[0].dn)))
783 identic_rename(samdb, reference[0].dn)
784 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
786 controls=["search_options:1:2"])
788 delta = samdb.msg_diff(current[0], reference[0])
790 for att in hashAttrNotCopied.keys():
793 for att in backlinked:
798 if len(delta.items()) > 1 and usns is not None:
799 # Fetch the replPropertyMetaData
800 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
801 scope=SCOPE_SUBTREE, controls=controls,
802 attrs=["replPropertyMetaData"])
803 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
804 str(res[0]["replPropertyMetaData"])).ctr
808 # We put in this hash only modification
809 # made on the current host
810 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
811 if str(o.originating_invocation_id) == str(invocationid):
812 # Note we could just use 1 here
813 hash_attr_usn[att] = o.originating_usn
815 hash_attr_usn[att] = -1
822 # We have updated by provision usn information so let's exploit
823 # replMetadataProperties
824 if att in forwardlinked:
825 handle_links(samdb, att, basedn, current[0]["dn"],
826 current[0][att], reference[0][att], delta)
828 if isFirst == 0 and len(delta.items())>1:
830 txt = "%s\n" % (str(dn))
832 # There is always a dn attribute after a msg_diff
834 if att == "rIDAvailablePool":
837 if att == "objectSid":
840 if att == "creationTime":
843 if att == "oEMInformation":
846 if att == "msDs-KeyVersionNumber":
847 # This is the kvno of the computer/user it's a very bad
851 if handle_special_case(att, delta, reference, current, usns, basedn, samdb):
852 # This attribute is "complicated" to handle and handling
853 # was done in handle_special_case
855 attrUSN = hash_attr_usn.get(att)
856 if att == "forceLogoff" and attrUSN is None:
863 # This attribute was last modified by another DC forget
865 message(CHANGE, "%sAttribute: %s has been"
866 "created/modified/deleted by another DC,"
867 " do nothing" % (txt, att ))
871 elif not usn_in_range(int(attrUSN), usns):
872 message(CHANGE, "%sAttribute: %s has been"
873 "created/modified/deleted not during a"
874 " provision or upgradeprovision: current"
875 " usn %d , do nothing" % (txt, att, attrUSN))
880 if att == "defaultSecurityDescriptor":
883 message(CHANGE, "%sAttribute: %s will be modified"
884 "/deleted it was last modified"
885 "during a provision, current usn:"
886 "%d" % (txt, att, attrUSN))
889 message(CHANGE, "%sAttribute: %s will be added because"
890 " it hasn't existed before " % (txt, att))
895 # Old school way of handling things for pre alpha12 upgrade
897 msgElt = delta.get(att)
899 if att == "nTSecurityDescriptor":
906 if not hashOverwrittenAtt.has_key(att):
907 if msgElt.flags() != FLAG_MOD_ADD:
908 if not handle_special_case(att, delta, reference, current,
909 usns, basedn, samdb):
910 if opts.debugchange or opts.debugall:
912 dump_denied_change(dn, att,
913 msg_elt_flag_strs[msgElt.flags()],
914 current[0][att], reference[0][att])
916 dump_denied_change(dn, att,
917 msg_elt_flag_strs[msgElt.flags()],
918 current[0][att], None)
922 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
924 elif hashOverwrittenAtt.get(att)==never:
929 if len(delta.items()) >1:
930 attributes=", ".join(delta.keys())
931 message(CHANGE, "%s is different from the reference one, changed"
932 " attributes: %s\n" % (dn, attributes))
937 def reload_full_schema(samdb, names):
938 """Load the updated schema with all the new and existing classes
941 :param samdb: An LDB object connected to the sam.ldb of the update
943 :param names: List of key provision parameters
946 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
952 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
954 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
955 prefixmap_data = b64encode(prefixmap_data)
957 # We don't actually add this ldif, just parse it
958 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
960 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
963 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
964 """Check differences between the reference provision and the upgraded one.
966 It looks for all objects which base DN is name.
968 This function will also add the missing object and update existing object
969 to add or remove attributes that were missing.
971 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
973 :param samdb: An LDB object connected to the sam.ldb of the update
975 :param basedn: String value of the DN of the partition
976 :param names: List of key provision parameters
977 :param schema: A Schema object
978 :param provisionUSNs: The USNs modified by provision/upgradeprovision
980 :param prereloadfunc: A function that must be executed just before the reload
991 # Connect to the reference provision and get all the attribute in the
992 # partition referred by name
993 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
994 scope=SCOPE_SUBTREE, attrs=["dn"],
995 controls=["search_options:1:2"])
997 current = samdb.search(expression="objectClass=*", base=basedn,
998 scope=SCOPE_SUBTREE, attrs=["dn"],
999 controls=["search_options:1:2"])
1000 # Create a hash for speeding the search of new object
1001 for i in range(0, len(reference)):
1002 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1004 # Create a hash for speeding the search of existing object in the
1006 for i in range(0, len(current)):
1007 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1010 for k in hash_new.keys():
1011 if not hash.has_key(k):
1012 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1013 listMissing.append(hash_new[k])
1015 listPresent.append(hash_new[k])
1017 # Sort the missing object in order to have object of the lowest level
1018 # first (which can be containers for higher level objects)
1019 listMissing.sort(dn_sort)
1020 listPresent.sort(dn_sort)
1022 # The following lines is to load the up to
1023 # date schema into our current LDB
1024 # a complete schema is needed as the insertion of attributes
1025 # and class is done against it
1026 # and the schema is self validated
1027 samdb.set_schema(schema)
1029 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1030 add_deletedobj_containers(ref_samdb, samdb, names)
1032 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1035 message(SIMPLE, "Reloading a merged schema, it might trigger"\
1036 " reindexing so please be patient")
1037 reload_full_schema(samdb, names)
1038 message(SIMPLE, "Schema reloaded !")
1040 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1041 provisionUSNs, names.invocation)
1042 message(SIMPLE, "There are %d changed objects" % (changed))
1045 except StandardError, err:
1046 message(ERROR, "Exception during upgrade of samdb:")
1047 (typ, val, tb) = sys.exc_info()
1048 traceback.print_exception(typ, val, tb)
1052 def check_updated_sd(ref_sam, cur_sam, names):
1053 """Check if the security descriptor in the upgraded provision are the same
1056 :param ref_sam: A LDB object connected to the sam.ldb file used as
1057 the reference provision
1058 :param cur_sam: A LDB object connected to the sam.ldb file used as
1060 :param names: List of key provision parameters"""
1061 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1062 scope=SCOPE_SUBTREE,
1063 attrs=["dn", "nTSecurityDescriptor"],
1064 controls=["search_options:1:2"])
1065 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1066 scope=SCOPE_SUBTREE,
1067 attrs=["dn", "nTSecurityDescriptor"],
1068 controls=["search_options:1:2"])
1070 for i in range(0, len(reference)):
1071 refsd = ndr_unpack(security.descriptor,
1072 str(reference[i]["nTSecurityDescriptor"]))
1073 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1076 for i in range(0, len(current)):
1077 key = str(current[i]["dn"]).lower()
1078 if hash.has_key(key):
1079 cursd = ndr_unpack(security.descriptor,
1080 str(current[i]["nTSecurityDescriptor"]))
1081 sddl = cursd.as_sddl(names.domainsid)
1082 if sddl != hash[key]:
1083 txt = get_diff_sddls(hash[key], sddl)
1085 message(CHANGESD, "On object %s ACL is different"
1086 " \n%s" % (current[i]["dn"], txt))
1090 def fix_partition_sd(samdb, names):
1091 """This function fix the SD for partition containers (basedn, configdn, ...)
1092 This is needed because some provision use to have broken SD on containers
1094 :param samdb: An LDB object pointing to the sam of the current provision
1095 :param names: A list of key provision parameters
1097 # First update the SD for the rootdn
1098 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1099 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1100 controls=["search_options:1:2"])
1102 delta.dn = Dn(samdb, str(res[0]["dn"]))
1103 descr = get_domain_descriptor(names.domainsid)
1104 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1105 "nTSecurityDescriptor")
1106 samdb.modify(delta, ["recalculate_sd:0"])
1107 # Then the config dn
1108 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1109 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1110 controls=["search_options:1:2"])
1112 delta.dn = Dn(samdb, str(res[0]["dn"]))
1113 descr = get_config_descriptor(names.domainsid)
1114 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1115 "nTSecurityDescriptor" )
1116 samdb.modify(delta, ["recalculate_sd:0"])
1117 # Then the schema dn
1118 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1119 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1120 controls=["search_options:1:2"])
1123 delta.dn = Dn(samdb, str(res[0]["dn"]))
1124 descr = get_schema_descriptor(names.domainsid)
1125 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1126 "nTSecurityDescriptor" )
1127 samdb.modify(delta, ["recalculate_sd:0"])
1129 def rebuild_sd(samdb, names):
1130 """Rebuild security descriptor of the current provision from scratch
1132 During the different pre release of samba4 security descriptors (SD)
1133 were notarly broken (up to alpha11 included)
1134 This function allow to get them back in order, this function make the
1135 assumption that nobody has modified manualy an SD
1136 and so SD can be safely recalculated from scratch to get them right.
1138 :param names: List of key provision parameters"""
1142 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1143 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1144 controls=["search_options:1:2"])
1146 if not (str(obj["dn"]) == str(names.rootdn) or
1147 str(obj["dn"]) == str(names.configdn) or
1148 str(obj["dn"]) == str(names.schemadn)):
1149 hash[str(obj["dn"])] = obj["whenCreated"]
1151 listkeys = hash.keys()
1152 listkeys.sort(dn_sort)
1154 for key in listkeys:
1157 delta.dn = Dn(samdb, key)
1158 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1160 samdb.modify(delta, ["recalculate_sd:0"])
1162 # XXX: We should always catch an explicit exception.
1163 # What could go wrong here?
1164 samdb.transaction_cancel()
1165 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1166 scope=SCOPE_SUBTREE,
1167 attrs=["dn", "nTSecurityDescriptor"],
1168 controls=["search_options:1:2"])
1169 badsd = ndr_unpack(security.descriptor,
1170 str(res[0]["nTSecurityDescriptor"]))
1171 print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1174 def removeProvisionUSN(samdb):
1175 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1176 entry = samdb.search(expression="dn=@PROVISION", base = "",
1177 scope=SCOPE_SUBTREE,
1178 controls=["search_options:1:2"],
1181 empty.dn = entry[0].dn
1182 delta = samdb.msg_diff(entry[0], empty)
1184 delta.dn = entry[0].dn
1187 def remove_stored_generated_attrs(paths, creds, session, lp):
1188 """Remove previously stored constructed attributes
1190 :param paths: List of paths for different provision objects
1191 from the upgraded provision
1192 :param creds: A credential object
1193 :param session: A session object
1194 :param lp: A line parser object
1195 :return: An associative array whose key are the different constructed
1196 attributes and the value the dn where this attributes were found.
1200 def simple_update_basesamdb(newpaths, paths, names):
1201 """Update the provision container db: sam.ldb
1202 This function is aimed at very old provision (before alpha9)
1204 :param newpaths: List of paths for different provision objects
1205 from the reference provision
1206 :param paths: List of paths for different provision objects
1207 from the upgraded provision
1208 :param names: List of key provision parameters"""
1210 message(SIMPLE, "Copy samdb")
1211 shutil.copy(newpaths.samdb, paths.samdb)
1213 message(SIMPLE, "Update partitions filename if needed")
1214 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1215 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1216 usersldb = os.path.join(paths.private_dir, "users.ldb")
1217 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1219 if not os.path.isdir(samldbdir):
1221 os.chmod(samldbdir, 0700)
1222 if os.path.isfile(schemaldb):
1223 shutil.copy(schemaldb, os.path.join(samldbdir,
1224 "%s.ldb"%str(names.schemadn).upper()))
1225 os.remove(schemaldb)
1226 if os.path.isfile(usersldb):
1227 shutil.copy(usersldb, os.path.join(samldbdir,
1228 "%s.ldb"%str(names.rootdn).upper()))
1230 if os.path.isfile(configldb):
1231 shutil.copy(configldb, os.path.join(samldbdir,
1232 "%s.ldb"%str(names.configdn).upper()))
1233 os.remove(configldb)
1236 def update_privilege(ref_private_path, cur_private_path):
1237 """Update the privilege database
1239 :param ref_private_path: Path to the private directory of the reference
1241 :param cur_private_path: Path to the private directory of the current
1242 (and to be updated) provision."""
1243 message(SIMPLE, "Copy privilege")
1244 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1245 os.path.join(cur_private_path, "privilege.ldb"))
1248 def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
1249 """Upgrade the SAM DB contents for all the provision partitions
1251 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1253 :param samdb: An LDB object connected to the sam.ldb of the update
1255 :param names: List of key provision parameters
1256 :param highestUSN: The highest USN modified by provision/upgradeprovision
1258 :param schema: A Schema object that represent the schema of the provision
1259 :param prereloadfunc: A function that must be executed just before the reload
1263 message(SIMPLE, "Starting update of samdb")
1264 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1265 schema, highestUSN, prereloadfunc)
1267 message(SIMPLE, "Update of samdb finished")
1270 message(SIMPLE, "Update failed")
1274 def copyxattrs(dir, refdir):
1275 """ Copy owner, groups, extended ACL and NT acls from
1276 a reference dir to a destination dir
1278 Both dir are supposed to hold the same files
1279 :param dir: Destination dir
1280 :param refdir: Reference directory"""
1283 for root, dirs, files in os.walk(dir, topdown=True):
1285 subdir=root[len(dir):]
1286 ref = os.path.join("%s%s" % (refdir, subdir), name)
1287 statsinfo = os.stat(ref)
1288 tgt = os.path.join(root, name)
1291 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1292 # Get the xattr attributes if any
1294 attribute = samba.xattr_native.wrap_getxattr(ref,
1295 xattr.XATTR_NTACL_NAME)
1296 samba.xattr_native.wrap_setxattr(tgt,
1297 xattr.XATTR_NTACL_NAME,
1301 attribute = samba.xattr_native.wrap_getxattr(ref,
1302 "system.posix_acl_access")
1303 samba.xattr_native.wrap_setxattr(tgt,
1304 "system.posix_acl_access",
1309 subdir=root[len(dir):]
1310 ref = os.path.join("%s%s" % (refdir, subdir), name)
1311 statsinfo = os.stat(ref)
1312 tgt = os.path.join(root, name)
1314 os.chown(os.path.join(root, name), statsinfo.st_uid,
1317 attribute = samba.xattr_native.wrap_getxattr(ref,
1318 xattr.XATTR_NTACL_NAME)
1319 samba.xattr_native.wrap_setxattr(tgt,
1320 xattr.XATTR_NTACL_NAME,
1324 attribute = samba.xattr_native.wrap_getxattr(ref,
1325 "system.posix_acl_access")
1326 samba.xattr_native.wrap_setxattr(tgt,
1327 "system.posix_acl_access",
1334 def backup_provision(paths, dir):
1335 """This function backup the provision files so that a rollback
1338 :param paths: Paths to different objects
1339 :param dir: Directory where to store the backup
1342 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1343 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1344 shutil.copy2(paths.samdb, dir)
1345 shutil.copy2(paths.secrets, dir)
1346 shutil.copy2(paths.idmapdb, dir)
1347 shutil.copy2(paths.privilege, dir)
1348 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1349 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1350 shutil.copy2(paths.smbconf, dir)
1351 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1353 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1354 if not os.path.isdir(samldbdir):
1355 samldbdir = paths.private_dir
1356 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1357 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1358 usersldb = os.path.join(paths.private_dir, "users.ldb")
1359 shutil.copy2(schemaldb, dir)
1360 shutil.copy2(usersldb, dir)
1361 shutil.copy2(configldb, dir)
1363 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1368 def sync_calculated_attributes(samdb, names):
1369 """Synchronize attributes used for constructed ones, with the
1370 old constructed that were stored in the database.
1372 This apply for instance to msds-keyversionnumber that was
1373 stored and that is now constructed from replpropertymetadata.
1375 :param samdb: An LDB object attached to the currently upgraded samdb
1376 :param names: Various key parameter about current provision.
1378 listAttrs = ["msDs-KeyVersionNumber"]
1379 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1380 if hash.has_key("msDs-KeyVersionNumber"):
1381 increment_calculated_keyversion_number(samdb, names.rootdn,
1382 hash["msDs-KeyVersionNumber"])
1384 def setup_path(file):
1385 return os.path.join(setup_dir, file)
1387 # Synopsis for updateprovision
1388 # 1) get path related to provision to be update (called current)
1389 # 2) open current provision ldbs
1390 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1392 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1393 # by either upgradeprovision or provision
1394 # 5) creation of a new provision the latest version of provision script
1395 # (called reference)
1396 # 6) get reference provision paths
1397 # 7) open reference provision ldbs
1398 # 8) setup helpers data that will help the update process
1399 # 9) update the privilege ldb by copying the one of referecence provision to
1400 # the current provision
1401 # 10)get the oemInfo field, this field contains information about the different
1402 # provision that have been done
1403 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1404 # integer) or none of this the following things are done
1405 # A) When alpha9 or alphaxx is present
1406 # The base sam.ldb file is updated by looking at the difference between
1407 # referrence one and the current one. Everything is copied with the
1408 # exception of lastProvisionUSN attributes.
1409 # B) Other case (it reflect that that provision was done before alpha9)
1410 # The base sam.ldb of the reference provision is copied over
1411 # the current one, if necessary ldb related to partitions are moved
1413 # The highest used USN is fetched so that changed by upgradeprovision
1414 # usn can be tracked
1415 # 12)A Schema object is created, it will be used to provide a complete
1416 # schema to current provision during update (as the schema of the
1417 # current provision might not be complete and so won't allow some
1418 # object to be created)
1419 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1420 # 14)The secrets db is updated by pull all the difference from the reference
1421 # provision into the current provision
1422 # 15)As the previous step has most probably modified the password stored in
1423 # in secret for the current DC, a new password is generated,
1424 # the kvno is bumped and the entry in samdb is also updated
1425 # 16)For current provision older than alpha9, we must fix the SD a little bit
1426 # administrator to update them because SD used to be generated with the
1427 # system account before alpha9.
1428 # 17)The highest usn modified so far is searched in the database it will be
1429 # the upper limit for usn modified during provision.
1430 # This is done before potential SD recalculation because we do not want
1431 # SD modified during recalculation to be marked as modified during provision
1432 # (and so possibly remplaced at next upgradeprovision)
1433 # 18)Rebuilt SD if the flag indicate to do so
1434 # 19)Check difference between SD of reference provision and those of the
1435 # current provision. The check is done by getting the sddl representation
1436 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1437 # Each part is verified separetly, for dacl and sacl ACL is splited into
1438 # ACEs and each ACE is verified separately (so that a permutation in ACE
1439 # didn't raise as an error).
1440 # 20)The oemInfo field is updated to add information about the fact that the
1441 # provision has been updated by the upgradeprovision version xxx
1442 # (the version is the one obtained when starting samba with the --version
1444 # 21)Check if the current provision has all the settings needed for dynamic
1445 # DNS update to work (that is to say the provision is newer than
1446 # january 2010). If not dns configuration file from reference provision
1447 # are copied in a sub folder and the administrator is invited to
1448 # do what is needed.
1449 # 22)If the lastProvisionUSN attribute was present it is updated to add
1450 # the range of usns modified by the current upgradeprovision
1453 # About updating the sam DB
1454 # The update takes place in update_partition function
1455 # This function read both current and reference provision and list all
1456 # the available DN of objects
1457 # If the string representation of a DN in reference provision is
1458 # equal to the string representation of a DN in current provision
1459 # (without taking care of case) then the object is flaged as being
1460 # present. If the object is not present in current provision the object
1461 # is being flaged as missing in current provision. Object present in current
1462 # provision but not in reference provision are ignored.
1463 # Once the list of objects present and missing is done, the deleted object
1464 # containers are created in the differents partitions (if missing)
1466 # Then the function add_missing_entries is called
1467 # This function will go through the list of missing entries by calling
1468 # add_missing_object for the given object. If this function returns 0
1469 # it means that the object needs some other object in order to be created
1470 # The object is reappended at the end of the list to be created later
1471 # (and preferably after all the needed object have been created)
1472 # The function keeps on looping on the list of object to be created until
1473 # it's empty or that the number of defered creation is equal to the number
1474 # of object that still needs to be created.
1476 # The function add_missing_object will first check if the object can be created.
1477 # That is to say that it didn't depends other not yet created objects
1478 # If requisit can't be fullfilled it exists with 0
1479 # Then it will try to create the missing entry by creating doing
1480 # an ldb_message_diff between the object in the reference provision and
1482 # This resulting object is filtered to remove all the back link attribute
1483 # (ie. memberOf) as they will be created by the other linked object (ie.
1484 # the one with the member attribute)
1485 # All attributes specified in the hashAttrNotCopied associative array are
1486 # also removed it's most of the time generated attributes
1488 # After missing entries have been added the update_partition function will
1489 # take care of object that exist but that need some update.
1490 # In order to do so the function update_present is called with the list
1491 # of object that are present in both provision and that might need an update.
1493 # This function handle first case mismatch so that the DN in the current
1494 # provision have the same case as in reference provision
1496 # It will then construct an associative array consiting of attributes as
1497 # key and invocationid as value( if the originating invocation id is
1498 # different from the invocation id of the current DC the value is -1 instead).
1500 # If the range of provision modified attributes is present, the function will
1501 # use the replMetadataProperty update method which is the following:
1502 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1503 # creationTime, msDs-KeyVersionNumber, oEMInformation
1504 # Check for each attribute if its usn is within one of the modified by
1505 # provision range and if its originating id is the invocation id of the
1506 # current DC, then validate the update from reference to current.
1507 # If not or if there is no replMetatdataProperty for this attribute then we
1509 # Otherwise (case the range of provision modified attribute is not present) it
1510 # use the following process:
1511 # All attributes that need to be added are accepted at the exeption of those
1512 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1513 # correct flags specified.
1514 # For attributes that need to be modified or removed, a check is performed
1515 # in OverwrittenAtt, if the attribute is present and the modification flag
1516 # (remove, delete) is one of those listed for this attribute then modification
1517 # is accepted. For complicated handling of attribute update, the control is passed
1518 # to handle_special_case
1522 if __name__ == '__main__':
1523 global defSDmodified
1524 defSDmodified = False
1525 # From here start the big steps of the program
1526 # 1) First get files paths
1527 paths = get_paths(param, smbconf=smbconf)
1528 paths.setup = setup_dir
1529 # Get ldbs with the system session, it is needed for searching
1530 # provision parameters
1531 session = system_session()
1533 # This variable will hold the last provision USN once if it exists.
1536 ldbs = get_ldbs(paths, creds, session, lp)
1537 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1538 prefix="backupprovision")
1539 backup_provision(paths, backupdir)
1541 ldbs.startTransactions()
1543 # 3) Guess all the needed names (variables in fact) from the current
1545 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1548 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1549 if lastProvisionUSNs is not None:
1551 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1553 # Objects will be created with the admin session
1554 # (not anymore system session)
1555 adm_session = admin_session(lp, str(names.domainsid))
1556 # So we reget handle on objects
1557 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1559 if not sanitychecks(ldbs.sam, names):
1560 message(SIMPLE, "Sanity checks for the upgrade fails, checks messages"
1561 " and correct them before rerunning upgradeprovision")
1564 # Let's see provision parameters
1565 print_provision_key_parameters(names)
1567 # 5) With all this information let's create a fresh new provision used as
1569 message(SIMPLE, "Creating a reference provision")
1570 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1571 prefix="referenceprovision")
1572 newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1577 # We need to get a list of object which SD is directly computed from
1578 # defaultSecurityDescriptor.
1579 # This will allow us to know which object we can rebuild the SD in case
1580 # of change of the parent's SD or of the defaultSD.
1581 # Get file paths of this new provision
1582 newpaths = get_paths(param, targetdir=provisiondir)
1583 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1584 new_ldbs.startTransactions()
1586 # 8) Populate some associative array to ease the update process
1587 # List of attribute which are link and backlink
1588 populate_links(new_ldbs.sam, names.schemadn)
1589 # List of attribute with ASN DN synthax)
1590 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1592 update_privilege(newpaths.private_dir, paths.private_dir)
1594 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1595 # Do some modification on sam.ldb
1596 ldbs.groupedCommit()
1597 new_ldbs.groupedCommit()
1600 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1602 # Starting from alpha9 we can consider that the structure is quite ok
1603 # and that we should do only dela
1604 deltaattr = delta_update_basesamdb(newpaths.samdb,
1612 simple_update_basesamdb(newpaths, paths, names)
1613 ldbs = get_ldbs(paths, creds, session, lp)
1614 removeProvisionUSN(ldbs.sam)
1616 ldbs.startTransactions()
1617 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1618 new_ldbs.startTransactions()
1621 schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1622 serverdn=str(names.serverdn))
1623 # We create a closure that will be invoked just before schema reload
1624 def schemareloadclosure():
1625 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1626 options=["modules:"])
1628 if deltaattr is not None and len(deltaattr) > 1:
1630 deltaattr.remove("dn")
1631 for att in deltaattr:
1632 if att.lower() == "dn":
1634 if deltaattr.get(att) is not None \
1635 and deltaattr.get(att).flags() != FLAG_MOD_ADD:
1637 elif deltaattr.get(att) is None:
1640 message(CHANGE, "Applying delta to @ATTRIBUTES")
1641 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1642 basesam.modify(deltaattr)
1644 message(CHANGE, "Not applying delta to @ATTRIBUTES because "\
1645 "there is not only add")
1648 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1649 schema, schemareloadclosure):
1650 message(SIMPLE, "Rollbacking every changes. Check the reason"
1652 message(SIMPLE, "In any case your system as it was before"
1654 ldbs.groupedRollback()
1655 new_ldbs.groupedRollback()
1656 shutil.rmtree(provisiondir)
1659 # Try to reapply the change also when we do not change the sam
1660 # as the delta_upgrade
1661 schemareloadclosure()
1662 sync_calculated_attributes(ldbs.sam, names)
1664 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1666 message(SIMPLE, "Update machine account")
1667 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1669 # 16) SD should be created with admin but as some previous acl were so wrong
1670 # that admin can't modify them we have first to recreate them with the good
1671 # form but with system account and then give the ownership to admin ...
1672 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1673 message(SIMPLE, "Fixing old povision SD")
1674 fix_partition_sd(ldbs.sam, names)
1675 rebuild_sd(ldbs.sam, names)
1677 # We calculate the max USN before recalculating the SD because we might
1678 # touch object that have been modified after a provision and we do not
1679 # want that the next upgradeprovision thinks that it has a green light
1683 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1685 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1686 # But in fact we should do it also if one object has its SD modified as
1687 # child might need rebuild
1689 message(SIMPLE, "Updating SD")
1690 ldbs.sam.set_session_info(adm_session)
1691 # Alpha10 was a bit broken still
1692 if re.match(r'.*alpha(\d|10)', str(oem)):
1693 fix_partition_sd(ldbs.sam, names)
1694 rebuild_sd(ldbs.sam, names)
1697 # Now we are quite confident in the recalculate process of the SD, we make
1699 # Also the check must be done in a clever way as for the moment we just
1701 if opts.debugchangesd:
1702 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1705 updateOEMInfo(ldbs.sam, str(names.rootdn))
1707 check_for_DNS(newpaths.private_dir, paths.private_dir)
1709 if lastProvisionUSNs is not None:
1710 update_provision_usn(ldbs.sam, minUSN, maxUSN)
1711 if opts.full and (names.policyid is None or names.policyid_dc is None):
1712 update_policyids(names, ldbs.sam)
1713 if opts.full or opts.resetfileacl:
1715 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1716 except ProvisioningError, e:
1717 message(ERROR, "The policy for domain controller is missing,"
1718 " you should restart upgradeprovision with --full")
1720 message(ERROR, "Setting ACL not supported on your filesystem")
1723 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1724 except ProvisioningError, e:
1725 message(ERROR, "The policy for domain controller is missing,"
1726 " you should restart upgradeprovision with --full")
1727 ldbs.groupedCommit()
1728 new_ldbs.groupedCommit()
1729 message(SIMPLE, "Upgrade finished !")
1730 # remove reference provision now that everything is done !
1731 # So we have reindexed first if need when the merged schema was reloaded
1732 # (as new attributes could have quick in)
1733 # But the second part of the update (when we update existing objects
1734 # can also have an influence on indexing as some attribute might have their
1735 # searchflag modificated
1736 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed after"\
1738 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1739 message(SIMPLE, "Reindexing finished")
1741 shutil.rmtree(provisiondir)
1742 except StandardError, err:
1743 message(ERROR,"A problem has occured when trying to upgrade your provision,"
1744 " a full backup is located at %s" % backupdir)
1745 if opts.debugall or opts.debugchange:
1746 (typ, val, tb) = sys.exc_info()
1747 traceback.print_exception(typ, val, tb)