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 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
85 "objectGUID": 1, "uSNCreated": 1,
86 "replPropertyMetaData": 1, "uSNChanged": 1,
87 "parentGUID": 1, "objectCategory": 1,
88 "distinguishedName": 1, "nTMixedDomain": 1,
89 "showInAdvancedViewOnly": 1, "instanceType": 1,
90 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
91 "lmPwdHistory":1, "pwdLastSet": 1,
92 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
93 "supplementalCredentials":1, "gPCUserExtensionNames":1,
94 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
95 "possibleInferiors":1, "privilege":1,
98 # Usually for an object that already exists we do not overwrite attributes as
99 # they might have been changed for good reasons. Anyway for a few of them it's
100 # mandatory to replace them otherwise the provision will be broken somehow.
101 # But for attribute that are just missing we do not have to specify them as the default
102 # behavior is to add missing attribute
103 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
104 "systemOnly":replace, "searchFlags":replace,
105 "mayContain":replace, "systemFlags":replace+add,
106 "description":replace, "operatingSystemVersion":replace,
107 "adminPropertyPages":replace, "groupType":replace,
108 "wellKnownObjects":replace, "privilege":never,
109 "defaultSecurityDescriptor": replace,
110 "rIDAvailablePool": never,
111 "rIDNextRID": add, "rIDUsedPool": never,
112 "defaultSecurityDescriptor": replace + add,
113 "isMemberOfPartialAttributeSet": delete,
114 "attributeDisplayNames": replace + add,
115 "versionNumber": add}
118 forwardlinked = set()
120 def define_what_to_log(opts):
124 if opts.debugchangesd:
125 what = what | CHANGESD
128 if opts.debugprovision:
129 what = what | PROVISION
131 what = what | CHANGEALL
135 parser = optparse.OptionParser("provision [options]")
136 sambaopts = options.SambaOptions(parser)
137 parser.add_option_group(sambaopts)
138 parser.add_option_group(options.VersionOptions(parser))
139 credopts = options.CredentialsOptions(parser)
140 parser.add_option_group(credopts)
141 parser.add_option("--setupdir", type="string", metavar="DIR",
142 help="directory with setup files")
143 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
144 parser.add_option("--debugguess", action="store_true",
145 help="Print information on which values are guessed")
146 parser.add_option("--debugchange", action="store_true",
147 help="Print information on what is different but won't be changed")
148 parser.add_option("--debugchangesd", action="store_true",
149 help="Print security descriptor differences")
150 parser.add_option("--debugall", action="store_true",
151 help="Print all available information (very verbose)")
152 parser.add_option("--resetfileacl", action="store_true",
153 help="Force a reset on filesystem acls in sysvol / netlogon share")
154 parser.add_option("--fixntacl", action="store_true",
155 help="Only fix NT ACLs in sysvol / netlogon share")
156 parser.add_option("--full", action="store_true",
157 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
159 opts = parser.parse_args()[0]
161 handler = logging.StreamHandler(sys.stdout)
162 upgrade_logger = logging.getLogger("upgradeprovision")
163 upgrade_logger.setLevel(logging.INFO)
165 upgrade_logger.addHandler(handler)
167 provision_logger = logging.getLogger("provision")
168 provision_logger.addHandler(handler)
170 whatToLog = define_what_to_log(opts)
172 def message(what, text):
173 """Print a message if this message type has been selected to be printed
175 :param what: Category of the message
176 :param text: Message to print """
177 if (whatToLog & what) or what <= 0:
178 upgrade_logger.info("%s", text)
180 if len(sys.argv) == 1:
181 opts.interactive = True
182 lp = sambaopts.get_loadparm()
183 smbconf = lp.configfile
185 creds = credopts.get_credentials(lp)
186 creds.set_kerberos_state(DONT_USE_KERBEROS)
190 def check_for_DNS(refprivate, private):
191 """Check if the provision has already the requirement for dynamic dns
193 :param refprivate: The path to the private directory of the reference
195 :param private: The path to the private directory of the upgraded
198 spnfile = "%s/spn_update_list" % private
199 dnsfile = "%s/dns_update_list" % private
200 namedfile = lp.get("dnsupdate:path")
203 namedfile = "%s/named.conf.update" % private
205 if not os.path.exists(spnfile):
206 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
208 if not os.path.exists(dnsfile):
209 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
211 destdir = "%s/new_dns" % private
212 dnsdir = "%s/dns" % private
214 if not os.path.exists(namedfile):
215 if not os.path.exists(destdir):
217 if not os.path.exists(dnsdir):
219 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
220 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
221 message(SIMPLE, "It seems that your provision did not integrate "
222 "new rules for dynamic dns update of domain related entries")
223 message(SIMPLE, "A copy of the new bind configuration files and "
224 "template has been put in %s, you should read them and "
225 "configure dynamic dns updates" % destdir)
228 def populate_links(samdb, schemadn):
229 """Populate an array with all the back linked attributes
231 This attributes that are modified automaticaly when
232 front attibutes are changed
234 :param samdb: A LDB object for sam.ldb file
235 :param schemadn: DN of the schema for the partition"""
236 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
237 backlinked.extend(linkedAttHash.values())
238 for t in linkedAttHash.keys():
242 def populate_dnsyntax(samdb, schemadn):
243 """Populate an array with all the attributes that have DN synthax
246 :param samdb: A LDB object for sam.ldb file
247 :param schemadn: DN of the schema for the partition"""
248 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
249 str(schemadn)), scope=SCOPE_SUBTREE,
250 attrs=["lDAPDisplayName"])
252 dn_syntax_att.append(elem["lDAPDisplayName"])
255 def sanitychecks(samdb, names):
256 """Make some checks before trying to update
258 :param samdb: An LDB object opened on sam.ldb
259 :param names: list of key provision parameters
260 :return: Status of check (1 for Ok, 0 for not Ok) """
261 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
262 scope=SCOPE_SUBTREE, attrs=["dn"],
263 controls=["search_options:1:2"])
265 print "No DC found. Your provision is most probably broken!"
268 print "Found %d domain controllers. For the moment " \
269 "upgradeprovision is not able to handle an upgrade on a " \
270 "domain with more than one DC. Please demote the other " \
271 "DC(s) before upgrading" % len(res)
277 def print_provision_key_parameters(names):
278 """Do a a pretty print of provision parameters
280 :param names: list of key provision parameters """
281 message(GUESS, "rootdn :" + str(names.rootdn))
282 message(GUESS, "configdn :" + str(names.configdn))
283 message(GUESS, "schemadn :" + str(names.schemadn))
284 message(GUESS, "serverdn :" + str(names.serverdn))
285 message(GUESS, "netbiosname :" + names.netbiosname)
286 message(GUESS, "defaultsite :" + names.sitename)
287 message(GUESS, "dnsdomain :" + names.dnsdomain)
288 message(GUESS, "hostname :" + names.hostname)
289 message(GUESS, "domain :" + names.domain)
290 message(GUESS, "realm :" + names.realm)
291 message(GUESS, "invocationid:" + names.invocation)
292 message(GUESS, "policyguid :" + names.policyid)
293 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
294 message(GUESS, "domainsid :" + str(names.domainsid))
295 message(GUESS, "domainguid :" + names.domainguid)
296 message(GUESS, "ntdsguid :" + names.ntdsguid)
297 message(GUESS, "domainlevel :" + str(names.domainlevel))
300 def handle_special_case(att, delta, new, old, usn, basedn, aldb):
301 """Define more complicate update rules for some attributes
303 :param att: The attribute to be updated
304 :param delta: A messageElement object that correspond to the difference
305 between the updated object and the reference one
306 :param new: The reference object
307 :param old: The Updated object
308 :param usn: The highest usn modified by a previous (upgrade)provision
309 :param basedn: The base DN of the provision
310 :param aldb: An ldb object used to build DN
311 :return: True to indicate that the attribute should be kept, False for
314 flag = delta.get(att).flags()
315 # We do most of the special case handle if we do not have the
316 # highest usn as otherwise the replPropertyMetaData will guide us more
319 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
320 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
321 "CN=Services,CN=Configuration,%s" % basedn)
324 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
325 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
327 message(SIMPLE, "We suggest that you change the userAccountControl"
328 " for user Administrator from value %d to %d" %
329 (int(str(old[0][att])), int(str(new[0][att]))))
331 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
332 if (long(str(old[0][att])) == 0):
333 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
336 if (att == "member" and flag == FLAG_MOD_REPLACE):
340 for elem in old[0][att]:
341 hash[str(elem).lower()]=1
342 newval.append(str(elem))
344 for elem in new[0][att]:
345 if not hash.has_key(str(elem).lower()):
347 newval.append(str(elem))
349 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
354 if (att in ("gPLink", "gPCFileSysPath") and
355 flag == FLAG_MOD_REPLACE and
356 str(new[0].dn).lower() == str(old[0].dn).lower()):
360 if att == "forceLogoff":
361 ref=0x8000000000000000
362 oldval=int(old[0][att][0])
363 newval=int(new[0][att][0])
364 ref == old and ref == abs(new)
367 if att in ("adminDisplayName", "adminDescription"):
370 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
371 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
374 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
375 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
378 if (str(old[0].dn) == "%s" % (str(names.rootdn))
379 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
381 #Allow to change revision of ForestUpdates objects
382 if (att == "revision" or att == "objectVersion"):
383 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
385 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
388 # This is a bit of special animal as we might have added
389 # already SPN entries to the list that has to be modified
390 # So we go in detail to try to find out what has to be added ...
391 if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
395 for elem in old[0][att]:
397 newval.append(str(elem))
399 for elem in new[0][att]:
400 if not hash.has_key(str(elem)):
402 newval.append(str(elem))
404 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
411 def dump_denied_change(dn, att, flagtxt, current, reference):
412 """Print detailed information about why a change is denied
414 :param dn: DN of the object which attribute is denied
415 :param att: Attribute that was supposed to be upgraded
416 :param flagtxt: Type of the update that should be performed
417 (add, change, remove, ...)
418 :param current: Value(s) of the current attribute
419 :param reference: Value(s) of the reference attribute"""
421 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
422 + " must not be changed/removed. Discarding the change")
423 if att == "objectSid" :
424 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
425 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
426 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
427 message(CHANGE, "old : %s" % int64range2str(current[0]))
428 message(CHANGE, "new : %s" % int64range2str(reference[0]))
431 for e in range(0, len(current)):
432 message(CHANGE, "old %d : %s" % (i, str(current[e])))
434 if reference is not None:
436 for e in range(0, len(reference)):
437 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
440 def handle_special_add(samdb, dn, names):
441 """Handle special operation (like remove) on some object needed during
444 This is mostly due to wrong creation of the object in previous provision.
445 :param samdb: An Ldb object representing the SAM database
446 :param dn: DN of the object to inspect
447 :param names: list of key provision parameters
451 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
453 #This entry was misplaced lets remove it if it exists
454 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
457 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
459 #This entry was misplaced lets remove it if it exists
460 dntoremove = "CN=Certificate Service DCOM Access,"\
461 "CN=Users, %s" % names.rootdn
463 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
465 #This entry was misplaced lets remove it if it exists
466 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
468 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
470 #This entry was misplaced lets remove it if it exists
471 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
473 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
474 "CN=Configuration,%s" % names.rootdn)
476 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
477 "CN=WellKnown Security Principals,"
478 "CN=Configuration,%s" % names.rootdn)
480 res = samdb.search(expression="(dn=%s)" % oldDn,
481 base=str(names.rootdn),
482 scope=SCOPE_SUBTREE, attrs=["dn"],
483 controls=["search_options:1:2"])
485 res2 = samdb.search(expression="(dn=%s)" % dn,
486 base=str(names.rootdn),
487 scope=SCOPE_SUBTREE, attrs=["dn"],
488 controls=["search_options:1:2"])
490 if len(res) > 0 and len(res2) == 0:
491 message(CHANGE, "Existing object %s must be replaced by %s. "
492 "Renaming old object" % (str(oldDn), str(dn)))
493 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
497 if dntoremove is not None:
498 res = samdb.search(expression="(cn=RID Set)",
499 base=str(names.rootdn),
500 scope=SCOPE_SUBTREE, attrs=["dn"],
501 controls=["search_options:1:2"])
505 res = samdb.search(expression="(dn=%s)" % dntoremove,
506 base=str(names.rootdn),
507 scope=SCOPE_SUBTREE, attrs=["dn"],
508 controls=["search_options:1:2"])
510 message(CHANGE, "Existing object %s must be replaced by %s. "
511 "Removing old object" % (dntoremove, str(dn)))
512 samdb.delete(res[0]["dn"])
518 def check_dn_nottobecreated(hash, index, listdn):
519 """Check if one of the DN present in the list has a creation order
520 greater than the current.
522 Hash is indexed by dn to be created, with each key
523 is associated the creation order.
525 First dn to be created has the creation order 0, second has 1, ...
526 Index contain the current creation order
528 :param hash: Hash holding the different DN of the object to be
530 :param index: Current creation order
531 :param listdn: List of DNs on which the current DN depends on
532 :return: None if the current object do not depend on other
533 object or if all object have been created before."""
537 key = str(dn).lower()
538 if hash.has_key(key) and hash[key] > index:
544 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
545 """Add a new object if the dependencies are satisfied
547 The function add the object if the object on which it depends are already
550 :param ref_samdb: Ldb object representing the SAM db of the reference
552 :param samdb: Ldb object representing the SAM db of the upgraded
554 :param dn: DN of the object to be added
555 :param names: List of key provision parameters
556 :param basedn: DN of the partition to be updated
557 :param hash: Hash holding the different DN of the object to be
559 :param index: Current creation order
560 :return: True if the object was created False otherwise"""
562 ret = handle_special_add(samdb, dn, names)
571 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
572 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
574 delta = samdb.msg_diff(empty, reference[0])
578 if str(reference[0].get("cn")) == "RID Set":
579 for klass in reference[0].get("objectClass"):
580 if str(klass).lower() == "ridset":
583 if delta.get("objectSid"):
584 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
585 m = re.match(r".*-(\d+)$", sid)
586 if m and int(m.group(1))>999:
587 delta.remove("objectSid")
588 for att in hashAttrNotCopied.keys():
590 for att in backlinked:
592 depend_on_yettobecreated = None
593 for att in dn_syntax_att:
594 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
596 if depend_on_yet_tobecreated is not None:
597 message(CHANGE, "Object %s depends on %s in attribute %s. "
598 "Delaying the creation" % (dn,
599 depend_on_yet_tobecreated, att))
604 message(CHANGE,"Object %s will be added" % dn)
605 samdb.add(delta, ["relax:0", "provision:0"])
607 message(CHANGE,"Object %s was skipped" % dn)
611 def gen_dn_index_hash(listMissing):
612 """Generate a hash associating the DN to its creation order
614 :param listMissing: List of DN
615 :return: Hash with DN as keys and creation order as values"""
617 for i in range(0, len(listMissing)):
618 hash[str(listMissing[i]).lower()] = i
621 def add_deletedobj_containers(ref_samdb, samdb, names):
622 """Add the object containter: CN=Deleted Objects
624 This function create the container for each partition that need one and
625 then reference the object into the root of the partition
627 :param ref_samdb: Ldb object representing the SAM db of the reference
629 :param samdb: Ldb object representing the SAM db of the upgraded provision
630 :param names: List of key provision parameters"""
633 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
634 partitions = [str(names.rootdn), str(names.configdn)]
635 for part in partitions:
636 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
637 base=part, scope=SCOPE_SUBTREE,
639 controls=["show_deleted:0",
641 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
642 base=part, scope=SCOPE_SUBTREE,
644 controls=["show_deleted:0",
646 if len(ref_delObjCnt) > len(delObjCnt):
647 reference = ref_samdb.search(expression="cn=Deleted Objects",
648 base=part, scope=SCOPE_SUBTREE,
649 controls=["show_deleted:0",
652 delta = samdb.msg_diff(empty, reference[0])
654 delta.dn = Dn(samdb, str(reference[0]["dn"]))
655 for att in hashAttrNotCopied.keys():
658 modcontrols = ["relax:0", "provision:0"]
659 samdb.add(delta, modcontrols)
662 res = samdb.search(expression="(objectClass=*)", base=part,
664 attrs=["dn", "wellKnownObjects"])
666 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
670 wko = res[0]["wellKnownObjects"]
672 # The wellKnownObject that we want to add.
674 if str(o) == targetWKO:
676 listwko.append(str(o))
679 listwko.append(targetWKO)
682 delta.dn = Dn(samdb, str(res[0]["dn"]))
683 delta["wellKnownObjects"] = MessageElement(listwko,
688 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
689 """Add the missing object whose DN is the list
691 The function add the object if the objects on which it depends are
694 :param ref_samdb: Ldb object representing the SAM db of the reference
696 :param samdb: Ldb object representing the SAM db of the upgraded
698 :param dn: DN of the object to be added
699 :param names: List of key provision parameters
700 :param basedn: DN of the partition to be updated
701 :param list: List of DN to be added in the upgraded provision"""
706 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
708 listMissing = listDefered
710 hashMissing = gen_dn_index_hash(listMissing)
711 for dn in listMissing:
712 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
716 # DN can't be created because it depends on some
717 # other DN in the list
718 listDefered.append(dn)
720 if len(listDefered) != 0:
721 raise ProvisioningError("Unable to insert missing elements: "
722 "circular references")
724 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
725 """This function handle updates on links
727 :param samdb: An LDB object pointing to the updated provision
728 :param att: Attribute to update
729 :param basedn: The root DN of the provision
730 :param dn: The DN of the inspected object
731 :param value: The value of the attribute
732 :param ref_value: The value of this attribute in the reference provision
733 :param delta: The MessageElement object that will be applied for
734 transforming the current provision"""
736 res = samdb.search(expression="dn=%s" % dn, base=basedn,
737 controls=["search_options:1:2", "reveal:1"],
745 newlinklist.extend(value)
749 # for w2k domain level the reveal won't reveal anything ...
750 # it means that we can readd links that were removed on purpose ...
751 # Also this function in fact just accept add not removal
753 for e in res[0][att]:
754 if not hash.has_key(e):
755 # We put in the blacklist all the element that are in the "revealed"
756 # result and not in the "standard" result
757 # This element are links that were removed before and so that
758 # we don't wan't to readd
762 if not blacklist.has_key(e) and not hash.has_key(e):
763 newlinklist.append(str(e))
766 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
771 msg_elt_flag_strs = {
772 ldb.FLAG_MOD_ADD: "MOD_ADD",
773 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
774 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
777 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
778 """ This function updates the object that are already present in the
781 :param ref_samdb: An LDB object pointing to the reference provision
782 :param samdb: An LDB object pointing to the updated provision
783 :param basedn: A string with the value of the base DN for the provision
785 :param listPresent: A list of object that is present in the provision
786 :param usns: A list of USN range modified by previous provision and
788 :param invocationid: The value of the invocationid for the current DC"""
791 # This hash is meant to speedup lookup of attribute name from an oid,
792 # it's for the replPropertyMetaData handling
794 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
795 controls=["search_options:1:2"], attrs=["attributeID",
799 strDisplay = str(e.get("lDAPDisplayName"))
800 hash_oid_name[str(e.get("attributeID"))] = strDisplay
802 msg = "Unable to insert missing elements: circular references"
803 raise ProvisioningError(msg)
806 controls = ["search_options:1:2", "sd_flags:1:2"]
807 for dn in listPresent:
808 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
811 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
812 scope=SCOPE_SUBTREE, controls=controls)
815 (str(current[0].dn) != str(reference[0].dn)) and
816 (str(current[0].dn).upper() == str(reference[0].dn).upper())
818 message(CHANGE, "Names are the same except for the case. "
819 "Renaming %s to %s" % (str(current[0].dn),
820 str(reference[0].dn)))
821 identic_rename(samdb, reference[0].dn)
822 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
826 delta = samdb.msg_diff(current[0], reference[0])
828 for att in hashAttrNotCopied.keys():
831 for att in backlinked:
836 if len(delta.items()) > 1 and usns is not None:
837 # Fetch the replPropertyMetaData
838 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
839 scope=SCOPE_SUBTREE, controls=controls,
840 attrs=["replPropertyMetaData"])
841 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
842 str(res[0]["replPropertyMetaData"])).ctr
846 # We put in this hash only modification
847 # made on the current host
848 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
849 if str(o.originating_invocation_id) == str(invocationid):
850 # Note we could just use 1 here
851 hash_attr_usn[att] = o.originating_usn
853 hash_attr_usn[att] = -1
858 for att in list(delta):
860 # We have updated by provision usn information so let's exploit
861 # replMetadataProperties
862 if att in forwardlinked:
863 curval = current[0].get(att, ())
864 refval = reference[0].get(att, ())
865 handle_links(samdb, att, basedn, current[0]["dn"],
866 curval, refval, delta)
869 if isFirst == 0 and len(delta.items())>1:
871 txt = "%s\n" % (str(dn))
873 # There is always a dn attribute after a msg_diff
875 if att == "rIDAvailablePool":
878 if att == "objectSid":
881 if att == "creationTime":
884 if att == "oEMInformation":
887 if att == "msDs-KeyVersionNumber":
888 # This is the kvno of the computer/user it's a very bad
892 if handle_special_case(att, delta, reference, current, usns, basedn, samdb):
893 # This attribute is "complicated" to handle and handling
894 # was done in handle_special_case
896 attrUSN = hash_attr_usn.get(att)
897 if att == "forceLogoff" and attrUSN is None:
902 if att == "nTSecurityDescriptor":
903 cursd = ndr_unpack(security.descriptor,
904 str(current[0]["nTSecurityDescriptor"]))
905 cursddl = cursd.as_sddl(names.domainsid)
906 refsd = ndr_unpack(security.descriptor,
907 str(reference[0]["nTSecurityDescriptor"]))
908 refsddl = refsd.as_sddl(names.domainsid)
910 if get_diff_sddls(refsddl, cursddl) == "":
911 message(CHANGE, "sd are identical")
913 message(CHANGE, "sd are not identical")
915 # This attribute was last modified by another DC forget
917 message(CHANGE, "%sAttribute: %s has been "
918 "created/modified/deleted by another DC. "
919 "Doing nothing" % (txt, att))
923 elif not usn_in_range(int(attrUSN), usns):
924 message(CHANGE, "%sAttribute: %s was not "
925 "created/modified/deleted during a "
926 "provision or upgradeprovision. Current "
927 "usn: %d. Doing nothing" % (txt, att,
933 if att == "defaultSecurityDescriptor":
936 message(CHANGE, "%sAttribute: %s will be modified"
937 "/deleted it was last modified "
938 "during a provision. Current usn: "
939 "%d" % (txt, att, attrUSN))
942 message(CHANGE, "%sAttribute: %s will be added because "
943 "it did not exist before" % (txt, att))
948 # Old school way of handling things for pre alpha12 upgrade
950 msgElt = delta.get(att)
952 if att == "nTSecurityDescriptor":
959 if not hashOverwrittenAtt.has_key(att):
960 if msgElt.flags() != FLAG_MOD_ADD:
961 if not handle_special_case(att, delta, reference, current,
962 usns, basedn, samdb):
963 if opts.debugchange or opts.debugall:
965 dump_denied_change(dn, att,
966 msg_elt_flag_strs[msgElt.flags()],
967 current[0][att], reference[0][att])
969 dump_denied_change(dn, att,
970 msg_elt_flag_strs[msgElt.flags()],
971 current[0][att], None)
975 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
977 elif hashOverwrittenAtt.get(att)==never:
982 if len(delta.items()) >1:
983 attributes=", ".join(delta.keys())
985 relaxedatt = ['iscriticalsystemobject', 'grouptype']
986 # Let's try to reduce as much as possible the use of relax control
987 #for checkedatt in relaxedatt:
988 for attr in delta.keys():
989 if attr.lower() in relaxedatt:
990 modcontrols = ["relax:0", "provision:0"]
991 message(CHANGE, "%s is different from the reference one, changed"
992 " attributes: %s\n" % (dn, attributes))
994 samdb.modify(delta, modcontrols)
997 def reload_full_schema(samdb, names):
998 """Load the updated schema with all the new and existing classes
1001 :param samdb: An LDB object connected to the sam.ldb of the update
1003 :param names: List of key provision parameters
1006 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1007 scope=SCOPE_SUBTREE)
1012 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1014 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1015 prefixmap_data = b64encode(prefixmap_data)
1017 # We don't actually add this ldif, just parse it
1018 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1020 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1023 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1024 """Check differences between the reference provision and the upgraded one.
1026 It looks for all objects which base DN is name.
1028 This function will also add the missing object and update existing object
1029 to add or remove attributes that were missing.
1031 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1033 :param samdb: An LDB object connected to the sam.ldb of the update
1035 :param basedn: String value of the DN of the partition
1036 :param names: List of key provision parameters
1037 :param schema: A Schema object
1038 :param provisionUSNs: The USNs modified by provision/upgradeprovision
1040 :param prereloadfunc: A function that must be executed just before the reload
1051 # Connect to the reference provision and get all the attribute in the
1052 # partition referred by name
1053 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1054 scope=SCOPE_SUBTREE, attrs=["dn"],
1055 controls=["search_options:1:2"])
1057 current = samdb.search(expression="objectClass=*", base=basedn,
1058 scope=SCOPE_SUBTREE, attrs=["dn"],
1059 controls=["search_options:1:2"])
1060 # Create a hash for speeding the search of new object
1061 for i in range(0, len(reference)):
1062 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1064 # Create a hash for speeding the search of existing object in the
1066 for i in range(0, len(current)):
1067 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1070 for k in hash_new.keys():
1071 if not hash.has_key(k):
1072 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1073 listMissing.append(hash_new[k])
1075 listPresent.append(hash_new[k])
1077 # Sort the missing object in order to have object of the lowest level
1078 # first (which can be containers for higher level objects)
1079 listMissing.sort(dn_sort)
1080 listPresent.sort(dn_sort)
1082 # The following lines is to load the up to
1083 # date schema into our current LDB
1084 # a complete schema is needed as the insertion of attributes
1085 # and class is done against it
1086 # and the schema is self validated
1087 samdb.set_schema(schema)
1089 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1090 add_deletedobj_containers(ref_samdb, samdb, names)
1092 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1095 message(SIMPLE, "Reloading a merged schema, which might trigger "
1096 "reindexing so please be patient")
1097 reload_full_schema(samdb, names)
1098 message(SIMPLE, "Schema reloaded!")
1100 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1101 provisionUSNs, names.invocation)
1102 message(SIMPLE, "There are %d changed objects" % (changed))
1105 except StandardError, err:
1106 message(ERROR, "Exception during upgrade of samdb:")
1107 (typ, val, tb) = sys.exc_info()
1108 traceback.print_exception(typ, val, tb)
1112 def check_updated_sd(ref_sam, cur_sam, names):
1113 """Check if the security descriptor in the upgraded provision are the same
1116 :param ref_sam: A LDB object connected to the sam.ldb file used as
1117 the reference provision
1118 :param cur_sam: A LDB object connected to the sam.ldb file used as
1120 :param names: List of key provision parameters"""
1121 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1122 scope=SCOPE_SUBTREE,
1123 attrs=["dn", "nTSecurityDescriptor"],
1124 controls=["search_options:1:2"])
1125 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1126 scope=SCOPE_SUBTREE,
1127 attrs=["dn", "nTSecurityDescriptor"],
1128 controls=["search_options:1:2"])
1130 for i in range(0, len(reference)):
1131 refsd = ndr_unpack(security.descriptor,
1132 str(reference[i]["nTSecurityDescriptor"]))
1133 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1136 for i in range(0, len(current)):
1137 key = str(current[i]["dn"]).lower()
1138 if hash.has_key(key):
1139 cursd = ndr_unpack(security.descriptor,
1140 str(current[i]["nTSecurityDescriptor"]))
1141 sddl = cursd.as_sddl(names.domainsid)
1142 if sddl != hash[key]:
1143 txt = get_diff_sddls(hash[key], sddl)
1145 message(CHANGESD, "On object %s ACL is different"
1146 " \n%s" % (current[i]["dn"], txt))
1150 def fix_partition_sd(samdb, names):
1151 """This function fix the SD for partition containers (basedn, configdn, ...)
1152 This is needed because some provision use to have broken SD on containers
1154 :param samdb: An LDB object pointing to the sam of the current provision
1155 :param names: A list of key provision parameters
1157 # First update the SD for the rootdn
1158 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1159 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1160 controls=["search_options:1:2"])
1162 delta.dn = Dn(samdb, str(res[0]["dn"]))
1163 descr = get_domain_descriptor(names.domainsid)
1164 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1165 "nTSecurityDescriptor")
1167 # Then the config dn
1168 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1169 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1170 controls=["search_options:1:2"])
1172 delta.dn = Dn(samdb, str(res[0]["dn"]))
1173 descr = get_config_descriptor(names.domainsid)
1174 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1175 "nTSecurityDescriptor" )
1177 # Then the schema dn
1178 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1179 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1180 controls=["search_options:1:2"])
1183 delta.dn = Dn(samdb, str(res[0]["dn"]))
1184 descr = get_schema_descriptor(names.domainsid)
1185 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1186 "nTSecurityDescriptor" )
1189 def rebuild_sd(samdb, names):
1190 """Rebuild security descriptor of the current provision from scratch
1192 During the different pre release of samba4 security descriptors (SD)
1193 were notarly broken (up to alpha11 included)
1194 This function allow to get them back in order, this function make the
1195 assumption that nobody has modified manualy an SD
1196 and so SD can be safely recalculated from scratch to get them right.
1198 :param names: List of key provision parameters"""
1202 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1203 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1204 controls=["search_options:1:2"])
1206 if not (str(obj["dn"]) == str(names.rootdn) or
1207 str(obj["dn"]) == str(names.configdn) or
1208 str(obj["dn"]) == str(names.schemadn)):
1209 hash[str(obj["dn"])] = obj["whenCreated"]
1211 listkeys = hash.keys()
1212 listkeys.sort(dn_sort)
1214 for key in listkeys:
1217 delta.dn = Dn(samdb, key)
1218 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1220 samdb.modify(delta, ["recalculate_sd:0"])
1222 # XXX: We should always catch an explicit exception.
1223 # What could go wrong here?
1224 samdb.transaction_cancel()
1225 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1226 scope=SCOPE_SUBTREE,
1227 attrs=["dn", "nTSecurityDescriptor"],
1228 controls=["search_options:1:2"])
1229 badsd = ndr_unpack(security.descriptor,
1230 str(res[0]["nTSecurityDescriptor"]))
1231 print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1234 def removeProvisionUSN(samdb):
1235 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1236 entry = samdb.search(expression="dn=@PROVISION", base = "",
1237 scope=SCOPE_SUBTREE,
1238 controls=["search_options:1:2"],
1241 empty.dn = entry[0].dn
1242 delta = samdb.msg_diff(entry[0], empty)
1244 delta.dn = entry[0].dn
1247 def remove_stored_generated_attrs(paths, creds, session, lp):
1248 """Remove previously stored constructed attributes
1250 :param paths: List of paths for different provision objects
1251 from the upgraded provision
1252 :param creds: A credential object
1253 :param session: A session object
1254 :param lp: A line parser object
1255 :return: An associative array whose key are the different constructed
1256 attributes and the value the dn where this attributes were found.
1260 def simple_update_basesamdb(newpaths, paths, names):
1261 """Update the provision container db: sam.ldb
1262 This function is aimed at very old provision (before alpha9)
1264 :param newpaths: List of paths for different provision objects
1265 from the reference provision
1266 :param paths: List of paths for different provision objects
1267 from the upgraded provision
1268 :param names: List of key provision parameters"""
1270 message(SIMPLE, "Copy samdb")
1271 shutil.copy(newpaths.samdb, paths.samdb)
1273 message(SIMPLE, "Update partitions filename if needed")
1274 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1275 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1276 usersldb = os.path.join(paths.private_dir, "users.ldb")
1277 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1279 if not os.path.isdir(samldbdir):
1281 os.chmod(samldbdir, 0700)
1282 if os.path.isfile(schemaldb):
1283 shutil.copy(schemaldb, os.path.join(samldbdir,
1284 "%s.ldb"%str(names.schemadn).upper()))
1285 os.remove(schemaldb)
1286 if os.path.isfile(usersldb):
1287 shutil.copy(usersldb, os.path.join(samldbdir,
1288 "%s.ldb"%str(names.rootdn).upper()))
1290 if os.path.isfile(configldb):
1291 shutil.copy(configldb, os.path.join(samldbdir,
1292 "%s.ldb"%str(names.configdn).upper()))
1293 os.remove(configldb)
1296 def update_privilege(ref_private_path, cur_private_path):
1297 """Update the privilege database
1299 :param ref_private_path: Path to the private directory of the reference
1301 :param cur_private_path: Path to the private directory of the current
1302 (and to be updated) provision."""
1303 message(SIMPLE, "Copy privilege")
1304 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1305 os.path.join(cur_private_path, "privilege.ldb"))
1308 def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
1309 """Upgrade the SAM DB contents for all the provision partitions
1311 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1313 :param samdb: An LDB object connected to the sam.ldb of the update
1315 :param names: List of key provision parameters
1316 :param highestUSN: The highest USN modified by provision/upgradeprovision
1318 :param schema: A Schema object that represent the schema of the provision
1319 :param prereloadfunc: A function that must be executed just before the reload
1323 message(SIMPLE, "Starting update of samdb")
1324 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1325 schema, highestUSN, prereloadfunc)
1327 message(SIMPLE, "Update of samdb finished")
1330 message(SIMPLE, "Update failed")
1334 def copyxattrs(dir, refdir):
1335 """ Copy owner, groups, extended ACL and NT acls from
1336 a reference dir to a destination dir
1338 Both dir are supposed to hold the same files
1339 :param dir: Destination dir
1340 :param refdir: Reference directory"""
1343 for root, dirs, files in os.walk(dir, topdown=True):
1345 subdir=root[len(dir):]
1346 ref = os.path.join("%s%s" % (refdir, subdir), name)
1347 statsinfo = os.stat(ref)
1348 tgt = os.path.join(root, name)
1351 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1352 # Get the xattr attributes if any
1354 attribute = samba.xattr_native.wrap_getxattr(ref,
1355 xattr.XATTR_NTACL_NAME)
1356 samba.xattr_native.wrap_setxattr(tgt,
1357 xattr.XATTR_NTACL_NAME,
1361 attribute = samba.xattr_native.wrap_getxattr(ref,
1362 "system.posix_acl_access")
1363 samba.xattr_native.wrap_setxattr(tgt,
1364 "system.posix_acl_access",
1369 subdir=root[len(dir):]
1370 ref = os.path.join("%s%s" % (refdir, subdir), name)
1371 statsinfo = os.stat(ref)
1372 tgt = os.path.join(root, name)
1374 os.chown(os.path.join(root, name), statsinfo.st_uid,
1377 attribute = samba.xattr_native.wrap_getxattr(ref,
1378 xattr.XATTR_NTACL_NAME)
1379 samba.xattr_native.wrap_setxattr(tgt,
1380 xattr.XATTR_NTACL_NAME,
1384 attribute = samba.xattr_native.wrap_getxattr(ref,
1385 "system.posix_acl_access")
1386 samba.xattr_native.wrap_setxattr(tgt,
1387 "system.posix_acl_access",
1394 def backup_provision(paths, dir):
1395 """This function backup the provision files so that a rollback
1398 :param paths: Paths to different objects
1399 :param dir: Directory where to store the backup
1402 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1403 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1404 shutil.copy2(paths.samdb, dir)
1405 shutil.copy2(paths.secrets, dir)
1406 shutil.copy2(paths.idmapdb, dir)
1407 shutil.copy2(paths.privilege, dir)
1408 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1409 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1410 shutil.copy2(paths.smbconf, dir)
1411 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1413 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1414 if not os.path.isdir(samldbdir):
1415 samldbdir = paths.private_dir
1416 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1417 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1418 usersldb = os.path.join(paths.private_dir, "users.ldb")
1419 shutil.copy2(schemaldb, dir)
1420 shutil.copy2(usersldb, dir)
1421 shutil.copy2(configldb, dir)
1423 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1428 def sync_calculated_attributes(samdb, names):
1429 """Synchronize attributes used for constructed ones, with the
1430 old constructed that were stored in the database.
1432 This apply for instance to msds-keyversionnumber that was
1433 stored and that is now constructed from replpropertymetadata.
1435 :param samdb: An LDB object attached to the currently upgraded samdb
1436 :param names: Various key parameter about current provision.
1438 listAttrs = ["msDs-KeyVersionNumber"]
1439 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1440 if hash.has_key("msDs-KeyVersionNumber"):
1441 increment_calculated_keyversion_number(samdb, names.rootdn,
1442 hash["msDs-KeyVersionNumber"])
1444 # Synopsis for updateprovision
1445 # 1) get path related to provision to be update (called current)
1446 # 2) open current provision ldbs
1447 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1449 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1450 # by either upgradeprovision or provision
1451 # 5) creation of a new provision the latest version of provision script
1452 # (called reference)
1453 # 6) get reference provision paths
1454 # 7) open reference provision ldbs
1455 # 8) setup helpers data that will help the update process
1456 # 9) update the privilege ldb by copying the one of referecence provision to
1457 # the current provision
1458 # 10)get the oemInfo field, this field contains information about the different
1459 # provision that have been done
1460 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1461 # integer) or none of this the following things are done
1462 # A) When alpha9 or alphaxx is present
1463 # The base sam.ldb file is updated by looking at the difference between
1464 # referrence one and the current one. Everything is copied with the
1465 # exception of lastProvisionUSN attributes.
1466 # B) Other case (it reflect that that provision was done before alpha9)
1467 # The base sam.ldb of the reference provision is copied over
1468 # the current one, if necessary ldb related to partitions are moved
1470 # The highest used USN is fetched so that changed by upgradeprovision
1471 # usn can be tracked
1472 # 12)A Schema object is created, it will be used to provide a complete
1473 # schema to current provision during update (as the schema of the
1474 # current provision might not be complete and so won't allow some
1475 # object to be created)
1476 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1477 # 14)The secrets db is updated by pull all the difference from the reference
1478 # provision into the current provision
1479 # 15)As the previous step has most probably modified the password stored in
1480 # in secret for the current DC, a new password is generated,
1481 # the kvno is bumped and the entry in samdb is also updated
1482 # 16)For current provision older than alpha9, we must fix the SD a little bit
1483 # administrator to update them because SD used to be generated with the
1484 # system account before alpha9.
1485 # 17)The highest usn modified so far is searched in the database it will be
1486 # the upper limit for usn modified during provision.
1487 # This is done before potential SD recalculation because we do not want
1488 # SD modified during recalculation to be marked as modified during provision
1489 # (and so possibly remplaced at next upgradeprovision)
1490 # 18)Rebuilt SD if the flag indicate to do so
1491 # 19)Check difference between SD of reference provision and those of the
1492 # current provision. The check is done by getting the sddl representation
1493 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1494 # Each part is verified separetly, for dacl and sacl ACL is splited into
1495 # ACEs and each ACE is verified separately (so that a permutation in ACE
1496 # didn't raise as an error).
1497 # 20)The oemInfo field is updated to add information about the fact that the
1498 # provision has been updated by the upgradeprovision version xxx
1499 # (the version is the one obtained when starting samba with the --version
1501 # 21)Check if the current provision has all the settings needed for dynamic
1502 # DNS update to work (that is to say the provision is newer than
1503 # january 2010). If not dns configuration file from reference provision
1504 # are copied in a sub folder and the administrator is invited to
1505 # do what is needed.
1506 # 22)If the lastProvisionUSN attribute was present it is updated to add
1507 # the range of usns modified by the current upgradeprovision
1510 # About updating the sam DB
1511 # The update takes place in update_partition function
1512 # This function read both current and reference provision and list all
1513 # the available DN of objects
1514 # If the string representation of a DN in reference provision is
1515 # equal to the string representation of a DN in current provision
1516 # (without taking care of case) then the object is flaged as being
1517 # present. If the object is not present in current provision the object
1518 # is being flaged as missing in current provision. Object present in current
1519 # provision but not in reference provision are ignored.
1520 # Once the list of objects present and missing is done, the deleted object
1521 # containers are created in the differents partitions (if missing)
1523 # Then the function add_missing_entries is called
1524 # This function will go through the list of missing entries by calling
1525 # add_missing_object for the given object. If this function returns 0
1526 # it means that the object needs some other object in order to be created
1527 # The object is reappended at the end of the list to be created later
1528 # (and preferably after all the needed object have been created)
1529 # The function keeps on looping on the list of object to be created until
1530 # it's empty or that the number of defered creation is equal to the number
1531 # of object that still needs to be created.
1533 # The function add_missing_object will first check if the object can be created.
1534 # That is to say that it didn't depends other not yet created objects
1535 # If requisit can't be fullfilled it exists with 0
1536 # Then it will try to create the missing entry by creating doing
1537 # an ldb_message_diff between the object in the reference provision and
1539 # This resulting object is filtered to remove all the back link attribute
1540 # (ie. memberOf) as they will be created by the other linked object (ie.
1541 # the one with the member attribute)
1542 # All attributes specified in the hashAttrNotCopied associative array are
1543 # also removed it's most of the time generated attributes
1545 # After missing entries have been added the update_partition function will
1546 # take care of object that exist but that need some update.
1547 # In order to do so the function update_present is called with the list
1548 # of object that are present in both provision and that might need an update.
1550 # This function handle first case mismatch so that the DN in the current
1551 # provision have the same case as in reference provision
1553 # It will then construct an associative array consiting of attributes as
1554 # key and invocationid as value( if the originating invocation id is
1555 # different from the invocation id of the current DC the value is -1 instead).
1557 # If the range of provision modified attributes is present, the function will
1558 # use the replMetadataProperty update method which is the following:
1559 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1560 # creationTime, msDs-KeyVersionNumber, oEMInformation
1561 # Check for each attribute if its usn is within one of the modified by
1562 # provision range and if its originating id is the invocation id of the
1563 # current DC, then validate the update from reference to current.
1564 # If not or if there is no replMetatdataProperty for this attribute then we
1566 # Otherwise (case the range of provision modified attribute is not present) it
1567 # use the following process:
1568 # All attributes that need to be added are accepted at the exeption of those
1569 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1570 # correct flags specified.
1571 # For attributes that need to be modified or removed, a check is performed
1572 # in OverwrittenAtt, if the attribute is present and the modification flag
1573 # (remove, delete) is one of those listed for this attribute then modification
1574 # is accepted. For complicated handling of attribute update, the control is passed
1575 # to handle_special_case
1579 if __name__ == '__main__':
1580 global defSDmodified
1581 defSDmodified = False
1582 # From here start the big steps of the program
1583 # 1) First get files paths
1584 paths = get_paths(param, smbconf=smbconf)
1585 # Get ldbs with the system session, it is needed for searching
1586 # provision parameters
1587 session = system_session()
1589 # This variable will hold the last provision USN once if it exists.
1592 ldbs = get_ldbs(paths, creds, session, lp)
1593 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1594 prefix="backupprovision")
1595 backup_provision(paths, backupdir)
1597 ldbs.startTransactions()
1599 # 3) Guess all the needed names (variables in fact) from the current
1601 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1604 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1605 if lastProvisionUSNs is not None:
1607 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1609 # Objects will be created with the admin session
1610 # (not anymore system session)
1611 adm_session = admin_session(lp, str(names.domainsid))
1612 # So we reget handle on objects
1613 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1614 if not opts.fixntacl:
1615 if not sanitychecks(ldbs.sam, names):
1616 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1617 "Check the messages and correct the errors "
1618 "before rerunning upgradeprovision")
1619 ldbs.groupedRollback()
1622 # Let's see provision parameters
1623 print_provision_key_parameters(names)
1625 # 5) With all this information let's create a fresh new provision used as
1627 message(SIMPLE, "Creating a reference provision")
1628 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1629 prefix="referenceprovision")
1630 newprovision(names, creds, session, smbconf, provisiondir,
1635 # We need to get a list of object which SD is directly computed from
1636 # defaultSecurityDescriptor.
1637 # This will allow us to know which object we can rebuild the SD in case
1638 # of change of the parent's SD or of the defaultSD.
1639 # Get file paths of this new provision
1640 newpaths = get_paths(param, targetdir=provisiondir)
1641 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1642 new_ldbs.startTransactions()
1644 # 8) Populate some associative array to ease the update process
1645 # List of attribute which are link and backlink
1646 populate_links(new_ldbs.sam, names.schemadn)
1647 # List of attribute with ASN DN synthax)
1648 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1650 update_privilege(newpaths.private_dir, paths.private_dir)
1652 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1653 # Do some modification on sam.ldb
1654 ldbs.groupedCommit()
1655 new_ldbs.groupedCommit()
1658 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1660 # Starting from alpha9 we can consider that the structure is quite ok
1661 # and that we should do only dela
1662 deltaattr = delta_update_basesamdb(newpaths.samdb,
1670 simple_update_basesamdb(newpaths, paths, names)
1671 ldbs = get_ldbs(paths, creds, session, lp)
1672 removeProvisionUSN(ldbs.sam)
1674 ldbs.startTransactions()
1675 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1676 new_ldbs.startTransactions()
1679 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1680 # We create a closure that will be invoked just before schema reload
1681 def schemareloadclosure():
1682 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1683 options=["modules:"])
1685 if deltaattr is not None and len(deltaattr) > 1:
1688 deltaattr.remove("dn")
1689 for att in deltaattr:
1690 if att.lower() == "dn":
1692 if (deltaattr.get(att) is not None
1693 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1695 elif deltaattr.get(att) is None:
1698 message(CHANGE, "Applying delta to @ATTRIBUTES")
1699 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1700 basesam.modify(deltaattr)
1702 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1703 "there is not only add")
1706 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1707 schema, schemareloadclosure):
1708 message(SIMPLE, "Rolling back all changes. Check the cause"
1710 message(SIMPLE, "Your system is as it was before the upgrade")
1711 ldbs.groupedRollback()
1712 new_ldbs.groupedRollback()
1713 shutil.rmtree(provisiondir)
1716 # Try to reapply the change also when we do not change the sam
1717 # as the delta_upgrade
1718 schemareloadclosure()
1719 sync_calculated_attributes(ldbs.sam, names)
1720 res = ldbs.sam.search(expression="(samaccountname=dns)",
1721 scope=SCOPE_SUBTREE, attrs=["dn"],
1722 controls=["search_options:1:2"])
1724 message(SIMPLE, "You still have the old DNS object for managing "
1725 "dynamic DNS, but you didn't supply --full so "
1726 "a correct update can't be done")
1727 ldbs.groupedRollback()
1728 new_ldbs.groupedRollback()
1729 shutil.rmtree(provisiondir)
1732 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1734 res = ldbs.sam.search(expression="(samaccountname=dns)",
1735 scope=SCOPE_SUBTREE, attrs=["dn"],
1736 controls=["search_options:1:2"])
1739 ldbs.sam.delete(res[0]["dn"])
1740 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1741 scope=SCOPE_SUBTREE, attrs=["dn"])
1742 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1743 message(SIMPLE, "IMPORTANT!!! "
1744 "If you were using Dynamic DNS before you need "
1745 "to update your configuration, so that the "
1746 "tkey-gssapi-credential has the following value: "
1747 "DNS/%s.%s" % (names.netbiosname.lower(),
1748 names.realm.lower()))
1750 message(SIMPLE, "Update machine account")
1751 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1753 # 16) SD should be created with admin but as some previous acl were so wrong
1754 # that admin can't modify them we have first to recreate them with the good
1755 # form but with system account and then give the ownership to admin ...
1756 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1757 message(SIMPLE, "Fixing old povision SD")
1758 fix_partition_sd(ldbs.sam, names)
1759 rebuild_sd(ldbs.sam, names)
1761 # We calculate the max USN before recalculating the SD because we might
1762 # touch object that have been modified after a provision and we do not
1763 # want that the next upgradeprovision thinks that it has a green light
1767 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1769 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1770 # But in fact we should do it also if one object has its SD modified as
1771 # child might need rebuild
1773 message(SIMPLE, "Updating SD")
1774 ldbs.sam.set_session_info(adm_session)
1775 # Alpha10 was a bit broken still
1776 if re.match(r'.*alpha(\d|10)', str(oem)):
1777 fix_partition_sd(ldbs.sam, names)
1778 rebuild_sd(ldbs.sam, names)
1781 # Now we are quite confident in the recalculate process of the SD, we make
1783 # Also the check must be done in a clever way as for the moment we just
1785 if opts.debugchangesd:
1786 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1789 updateOEMInfo(ldbs.sam, str(names.rootdn))
1791 check_for_DNS(newpaths.private_dir, paths.private_dir)
1793 if lastProvisionUSNs is not None:
1794 update_provision_usn(ldbs.sam, minUSN, maxUSN)
1795 if opts.full and (names.policyid is None or names.policyid_dc is None):
1796 update_policyids(names, ldbs.sam)
1797 if opts.full or opts.resetfileacl or opts.fixntacl:
1799 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1800 except ProvisioningError, e:
1801 message(ERROR, "The policy for domain controller is missing. "
1802 "You should restart upgradeprovision with --full")
1804 message(ERROR, "Setting ACL not supported on your filesystem")
1807 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1808 except ProvisioningError, e:
1809 message(ERROR, "The policy for domain controller is missing. "
1810 "You should restart upgradeprovision with --full")
1811 if not opts.fixntacl:
1812 ldbs.groupedCommit()
1813 new_ldbs.groupedCommit()
1814 message(SIMPLE, "Upgrade finished!")
1815 # remove reference provision now that everything is done !
1816 # So we have reindexed first if need when the merged schema was reloaded
1817 # (as new attributes could have quick in)
1818 # But the second part of the update (when we update existing objects
1819 # can also have an influence on indexing as some attribute might have their
1820 # searchflag modificated
1821 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1822 "after modification")
1823 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1824 message(SIMPLE, "Reindexing finished")
1826 shutil.rmtree(provisiondir)
1828 ldbs.groupedRollback()
1829 message(SIMPLE, "ACLs fixed !")
1830 except StandardError, err:
1831 message(ERROR, "A problem occurred while trying to upgrade your "
1832 "provision. A full backup is located at %s" % backupdir)
1833 if opts.debugall or opts.debugchange:
1834 (typ, val, tb) = sys.exc_info()
1835 traceback.print_exception(typ, val, tb)