4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
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")
37 import samba.getopt as options
38 from samba.credentials import DONT_USE_KERBEROS
39 from samba.auth import system_session, admin_session
40 from samba import Ldb, version
41 from ldb import SCOPE_ONELEVEL, SCOPE_SUBTREE, SCOPE_BASE,\
42 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,\
43 MessageElement, Message, Dn
44 from samba import param
45 from samba.misc import messageEltFlagToString
46 from samba.provision import find_setup_dir, get_domain_descriptor,\
47 get_config_descriptor, secretsdb_self_join,\
48 set_gpo_acl, getpolicypath,create_gpo_struct,\
49 ProvisioningError, getLastProvisionUSN,\
50 get_max_usn, updateProvisionUSN
51 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import security, drsblobs
53 from samba.ndr import ndr_unpack
54 from samba.dcerpc.misc import SEC_CHAN_BDC
55 from samba.upgradehelpers import dn_sort, get_paths, newprovision,\
56 find_provision_key_parameters, get_ldbs,\
57 usn_in_range, identic_rename, get_diff_sddls
59 replace=2**FLAG_MOD_REPLACE
61 delete=2**FLAG_MOD_DELETE
65 # Will be modified during provision to tell if default sd has been modified
68 #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 "versionNumber":1, "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 "defaultSecurityDescriptor": replace + add }
117 def define_what_to_log(opts):
121 if opts.debugchangesd:
122 what = what | CHANGESD
125 if opts.debugprovision:
126 what = what | PROVISION
128 what = what | CHANGEALL
132 parser = optparse.OptionParser("provision [options]")
133 sambaopts = options.SambaOptions(parser)
134 parser.add_option_group(sambaopts)
135 parser.add_option_group(options.VersionOptions(parser))
136 credopts = options.CredentialsOptions(parser)
137 parser.add_option_group(credopts)
138 parser.add_option("--setupdir", type="string", metavar="DIR",
139 help="directory with setup files")
140 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
141 parser.add_option("--debugguess", action="store_true",
142 help="Print information on what is different but won't be changed")
143 parser.add_option("--debugchange", action="store_true",
144 help="Print information on what is different but won't be changed")
145 parser.add_option("--debugchangesd", action="store_true",
146 help="Print information security descriptors differences")
147 parser.add_option("--debugall", action="store_true",
148 help="Print all available information (very verbose)")
149 parser.add_option("--full", action="store_true",
150 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
152 opts = parser.parse_args()[0]
154 handler = logging.StreamHandler(sys.stdout)
155 upgrade_logger = logging.getLogger("upgradeprovision")
156 upgrade_logger.addHandler(handler)
158 provision_logger = logging.getLogger("provision")
159 provision_logger.addHandler(handler)
161 whatToLog = define_what_to_log(opts)
163 def message(what, text):
164 """Print a message if this message type has been selected to be printed
166 :param what: Category of the message
167 :param text: Message to print """
168 if (whatToLog & what) or what <= 0:
169 upgrade_logger.info("%s", text)
171 if len(sys.argv) == 1:
172 opts.interactive = True
173 lp = sambaopts.get_loadparm()
174 smbconf = lp.configfile
176 creds = credopts.get_credentials(lp)
177 creds.set_kerberos_state(DONT_USE_KERBEROS)
178 setup_dir = opts.setupdir
179 if setup_dir is None:
180 setup_dir = find_setup_dir()
184 def check_for_DNS(refprivate, private):
185 """Check if the provision has already the requirement for dynamic dns
187 :param refprivate: The path to the private directory of the reference
189 :param private: The path to the private directory of the upgraded
192 spnfile = "%s/spn_update_list" % private
193 namedfile = lp.get("dnsupdate:path")
196 namedfile = "%s/named.conf.update" % private
198 if not os.path.exists(spnfile):
199 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
201 destdir = "%s/new_dns" % private
202 dnsdir = "%s/dns" % private
204 if not os.path.exists(namedfile):
205 if not os.path.exists(destdir):
207 if not os.path.exists(dnsdir):
209 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
210 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
211 message(SIMPLE, "It seems that you provision didn't integrate new rules "
212 "for dynamic dns update of domain related entries")
213 message(SIMPLE, "A copy of the new bind configuration files and "
214 "template as been put in %s, you should read them and configure dynamic "
215 " dns update" % destdir)
218 def populate_links(samdb, schemadn):
219 """Populate an array with all the back linked attributes
221 This attributes that are modified automaticaly when
222 front attibutes are changed
224 :param samdb: A LDB object for sam.ldb file
225 :param schemadn: DN of the schema for the partition"""
226 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
227 backlinked.extend(linkedAttHash.values())
228 for t in linkedAttHash.keys():
231 def populate_dnsyntax(samdb, schemadn):
232 """Populate an array with all the attributes that have DN synthax
235 :param samdb: A LDB object for sam.ldb file
236 :param schemadn: DN of the schema for the partition"""
237 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
238 str(schemadn)), scope=SCOPE_SUBTREE,
239 attrs=["lDAPDisplayName"])
241 dn_syntax_att.append(elem["lDAPDisplayName"])
244 def sanitychecks(samdb, names):
245 """Make some checks before trying to update
247 :param samdb: An LDB object opened on sam.ldb
248 :param names: list of key provision parameters
249 :return: Status of check (1 for Ok, 0 for not Ok) """
250 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
251 scope=SCOPE_SUBTREE, attrs=["dn"],
252 controls=["search_options:1:2"])
254 print "No DC found, your provision is most probably hardly broken !"
257 print "Found %d domain controllers, for the moment upgradeprovision" \
258 "is not able to handle upgrade on domain with more than one DC, please demote" \
259 " the other(s) DC(s) before upgrading" % len(res)
265 def print_provision_key_parameters(names):
266 """Do a a pretty print of provision parameters
268 :param names: list of key provision parameters """
269 message(GUESS, "rootdn :" + str(names.rootdn))
270 message(GUESS, "configdn :" + str(names.configdn))
271 message(GUESS, "schemadn :" + str(names.schemadn))
272 message(GUESS, "serverdn :" + str(names.serverdn))
273 message(GUESS, "netbiosname :" + names.netbiosname)
274 message(GUESS, "defaultsite :" + names.sitename)
275 message(GUESS, "dnsdomain :" + names.dnsdomain)
276 message(GUESS, "hostname :" + names.hostname)
277 message(GUESS, "domain :" + names.domain)
278 message(GUESS, "realm :" + names.realm)
279 message(GUESS, "invocationid:" + names.invocation)
280 message(GUESS, "policyguid :" + names.policyid)
281 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
282 message(GUESS, "domainsid :" + str(names.domainsid))
283 message(GUESS, "domainguid :" + names.domainguid)
284 message(GUESS, "ntdsguid :" + names.ntdsguid)
285 message(GUESS, "domainlevel :" + str(names.domainlevel))
288 def handle_special_case(att, delta, new, old, usn):
289 """Define more complicate update rules for some attributes
291 :param att: The attribute to be updated
292 :param delta: A messageElement object that correspond to the difference
293 between the updated object and the reference one
294 :param new: The reference object
295 :param old: The Updated object
296 :param usn: The highest usn modified by a previous (upgrade)provision
297 :return: True to indicate that the attribute should be kept, False for
300 flag = delta.get(att).flags()
301 # We do most of the special case handle if we do not have the
302 # highest usn as otherwise the replPropertyMetaData will guide us more
305 if (att == "member" and flag == FLAG_MOD_REPLACE):
309 for elem in old[0][att]:
311 newval.append(str(elem))
313 for elem in new[0][att]:
314 if not hash.has_key(str(elem)):
316 newval.append(str(elem))
318 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
323 if (att == "gPLink" or att == "gPCFileSysPath") and \
324 flag == FLAG_MOD_REPLACE and\
325 str(new[0].dn).lower() == str(old[0].dn).lower():
329 if att == "forceLogoff":
330 ref=0x8000000000000000
331 oldval=int(old[0][att][0])
332 newval=int(new[0][att][0])
333 ref == old and ref == abs(new)
336 if (att == "adminDisplayName" or att == "adminDescription"):
339 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (str(names.schemadn))\
340 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
343 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
344 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
347 if (str(old[0].dn) == "%s" % (str(names.rootdn))
348 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
351 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
354 # This is a bit of special animal as we might have added
355 # already SPN entries to the list that has to be modified
356 # So we go in detail to try to find out what has to be added ...
357 if ( att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
361 for elem in old[0][att]:
363 newval.append(str(elem))
365 for elem in new[0][att]:
366 if not hash.has_key(str(elem)):
368 newval.append(str(elem))
370 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
377 def update_secrets(newsecrets_ldb, secrets_ldb):
378 """Update secrets.ldb
380 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
381 of the reference provision
382 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
383 of the updated provision"""
385 message(SIMPLE, "update secrets.ldb")
386 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
388 current = secrets_ldb.search(expression="dn=@MODULES", base="",
390 delta = secrets_ldb.msg_diff(current[0], reference[0])
391 delta.dn = current[0].dn
392 secrets_ldb.modify(delta)
394 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
395 scope=SCOPE_SUBTREE, attrs=["dn"])
396 current = secrets_ldb.search(expression="objectClass=top", base="",
397 scope=SCOPE_SUBTREE, attrs=["dn"])
404 for i in range(0, len(reference)):
405 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
407 # Create a hash for speeding the search of existing object in the
409 for i in range(0, len(current)):
410 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
412 for k in hash_new.keys():
413 if not hash.has_key(k):
414 listMissing.append(hash_new[k])
416 listPresent.append(hash_new[k])
418 for entry in listMissing:
419 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
420 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
421 delta = secrets_ldb.msg_diff(empty,reference[0])
422 for att in hashAttrNotCopied.keys():
424 message(CHANGE, "Entry %s is missing from secrets.ldb"%reference[0].dn)
426 message(CHANGE, " Adding attribute %s"%att)
427 delta.dn = reference[0].dn
428 secrets_ldb.add(delta)
430 for entry in listPresent:
431 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
432 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
433 delta = secrets_ldb.msg_diff(current[0],reference[0])
434 for att in hashAttrNotCopied.keys():
438 message(CHANGE, "Found attribute name on %s, must rename the DN "%(current[0].dn))
439 identic_rename(secrets_ldb,reference[0].dn)
443 for entry in listPresent:
444 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
445 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
446 delta = secrets_ldb.msg_diff(current[0],reference[0])
447 for att in hashAttrNotCopied.keys():
451 message(CHANGE, " Adding/Changing attribute %s to %s"%(att,current[0].dn))
453 delta.dn = current[0].dn
454 secrets_ldb.modify(delta)
457 def dump_denied_change(dn, att, flagtxt, current, reference):
458 """Print detailed information about why a changed is denied
460 :param dn: DN of the object which attribute is denied
461 :param att: Attribute that was supposed to be upgraded
462 :param flagtxt: Type of the update that should be performed
463 (add, change, remove, ...)
464 :param current: Value(s) of the current attribute
465 :param reference: Value(s) of the reference attribute"""
467 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
468 +" is not allowed to be changed/removed, I discard this change")
469 if att != "objectSid" :
471 for e in range(0, len(current)):
472 message(CHANGE, "old %d : %s" % (i, str(current[e])))
474 if reference != None:
476 for e in range(0, len(reference)):
477 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
480 message(CHANGE, "old : %s" % str(ndr_unpack( security.dom_sid, current[0])))
481 message(CHANGE, "new : %s" % str(ndr_unpack( security.dom_sid, reference[0])))
484 def handle_special_add(samdb, dn, names):
485 """Handle special operation (like remove) on some object needed during
488 This is mostly due to wrong creation of the object in previous provision.
489 :param samdb: An Ldb object representing the SAM database
490 :param dn: DN of the object to inspect
491 :param names: list of key provision parameters"""
494 if str(dn).lower() == ("CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn).lower():
495 #This entry was misplaced lets remove it if it exists
496 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
498 objname = "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn
499 if str(dn).lower() == objname.lower():
500 #This entry was misplaced lets remove it if it exists
501 dntoremove = "CN=Certificate Service DCOM Access,"\
502 "CN=Users, %s" % names.rootdn
504 objname = "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn
505 if str(dn).lower() == objname.lower():
506 #This entry was misplaced lets remove it if it exists
507 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
509 objname = "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn
510 if str(dn).lower() == objname.lower():
511 #This entry was misplaced lets remove it if it exists
512 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
514 if dntoremove != None:
515 res = samdb.search(expression="(dn=%s)" % dntoremove,
516 base=str(names.rootdn),
517 scope=SCOPE_SUBTREE, attrs=["dn"],
518 controls=["search_options:1:2"])
520 message(CHANGE, "Existing object %s must be replaced by %s,"\
521 "removing old object" % (dntoremove, str(dn)))
522 samdb.delete(res[0]["dn"])
525 def check_dn_nottobecreated(hash, index, listdn):
526 """Check if one of the DN present in the list has a creation order
527 greater than the current.
529 Hash is indexed by dn to be created, with each key
530 is associated the creation order.
532 First dn to be created has the creation order 0, second has 1, ...
533 Index contain the current creation order
535 :param hash: Hash holding the different DN of the object to be
537 :param index: Current creation order
538 :param listdn: List of DNs on which the current DN depends on
539 :return: None if the current object do not depend on other
540 object or if all object have been created before."""
544 key = str(dn).lower()
545 if hash.has_key(key) and hash[key] > index:
551 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
552 """Add a new object if the dependencies are satisfied
554 The function add the object if the object on which it depends are already
557 :param ref_samdb: Ldb object representing the SAM db of the reference
559 :param samdb: Ldb object representing the SAM db of the upgraded
561 :param dn: DN of the object to be added
562 :param names: List of key provision parameters
563 :param basedn: DN of the partition to be updated
564 :param hash: Hash holding the different DN of the object to be
566 :param index: Current creation order
567 :return: True if the object was created False otherwise"""
569 handle_special_add(samdb, dn, names)
570 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
571 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
573 delta = samdb.msg_diff(empty, reference[0])
575 for att in hashAttrNotCopied.keys():
577 for att in backlinked:
579 depend_on_yettobecreated = None
580 for att in dn_syntax_att:
581 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
583 if depend_on_yet_tobecreated != None:
584 message(CHANGE, "Object %s depends on %s in attribute %s," \
585 "delaying the creation" % (str(dn), \
586 depend_on_yet_tobecreated, str(att)))
590 message(CHANGE,"Object %s will be added" % dn)
591 samdb.add(delta, ["relax:0"])
594 def gen_dn_index_hash(listMissing):
595 """Generate a hash associating the DN to its creation order
597 :param listMissing: List of DN
598 :return: Hash with DN as keys and creation order as values"""
600 for i in range(0, len(listMissing)):
601 hash[str(listMissing[i]).lower()] = i
604 def add_deletedobj_containers(ref_samdb, samdb, names):
605 """Add the object containter: CN=Deleted Objects
607 This function create the container for each partition that need one and
608 then reference the object into the root of the partition
610 :param ref_samdb: Ldb object representing the SAM db of the reference
612 :param samdb: Ldb object representing the SAM db of the upgraded provision
613 :param names: List of key provision parameters"""
616 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
617 partitions = [str(names.rootdn), str(names.configdn)]
618 for part in partitions:
619 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
620 base=part, scope=SCOPE_SUBTREE,
622 controls=["show_deleted:0"])
623 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
624 base=part, scope=SCOPE_SUBTREE,
626 controls=["show_deleted:0"])
627 if len(ref_delObjCnt) > len(delObjCnt):
628 reference = ref_samdb.search(expression="cn=Deleted Objects",
629 base=part, scope=SCOPE_SUBTREE,
630 controls=["show_deleted:0"])
632 delta = samdb.msg_diff(empty, reference[0])
634 delta.dn = Dn(samdb, str(reference[0]["dn"]))
635 for att in hashAttrNotCopied.keys():
640 res = samdb.search(expression="(objectClass=*)", base=part,
642 attrs=["dn", "wellKnownObjects"])
644 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
648 wko = res[0]["wellKnownObjects"]
650 # The wellKnownObject that we want to add.
652 if str(o) == targetWKO:
654 listwko.append(str(o))
657 listwko.append(targetWKO)
660 delta.dn = Dn(samdb, str(res[0]["dn"]))
661 delta["wellKnownObjects"] = MessageElement(listwko,
666 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
667 """Add the missing object whose DN is the list
669 The function add the object if the objects on which it depends are
672 :param ref_samdb: Ldb object representing the SAM db of the reference
674 :param samdb: Ldb object representing the SAM db of the upgraded
676 :param dn: DN of the object to be added
677 :param names: List of key provision parameters
678 :param basedn: DN of the partition to be updated
679 :param list: List of DN to be added in the upgraded provision"""
684 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
686 listMissing = listDefered
688 hashMissing = gen_dn_index_hash(listMissing)
689 for dn in listMissing:
690 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
694 # DN can't be created because it depends on some
695 # other DN in the list
696 listDefered.append(dn)
697 if len(listDefered) != 0:
698 raise ProvisioningError("Unable to insert missing elements:" \
699 "circular references")
701 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
702 """This function handle updates on links
704 :param samdb: An LDB object pointing to the updated provision
705 :param att: Attribute to update
706 :param basedn: The root DN of the provision
707 :param dn: The DN of the inspected object
708 :param value: The value of the attribute
709 :param ref_value: The value of this attribute in the reference provision
710 :param delta: The MessageElement object that will be applied for
711 transforming the current provision"""
713 res = samdb.search(expression="dn=%s" % dn, base=basedn,
714 controls=["search_options:1:2", "reveal:1"],
722 newlinklist.extend(value)
726 # for w2k domain level the reveal won't reveal anything ...
727 # it means that we can readd links that were removed on purpose ...
728 # Also this function in fact just accept add not removal
730 for e in res[0][att]:
731 if not hash.has_key(e):
732 # We put in the blacklist all the element that are in the "revealed"
733 # result and not in the "standard" result
734 # This element are links that were removed before and so that
735 # we don't wan't to readd
739 if not blacklist.has_key(e) and not hash.has_key(e):
740 newlinklist.append(str(e))
743 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
747 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
748 """ This function updates the object that are already present in the
751 :param ref_samdb: An LDB object pointing to the reference provision
752 :param samdb: An LDB object pointing to the updated provision
753 :param basedn: A string with the value of the base DN for the provision
755 :param listPresent: A list of object that is present in the provision
756 :param usns: A list of USN range modified by previous provision and
758 :param invocationid: The value of the invocationid for the current DC"""
761 # This hash is meant to speedup lookup of attribute name from an oid,
762 # it's for the replPropertyMetaData handling
764 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
765 controls=["search_options:1:2"], attrs=["attributeID",
769 strDisplay = str(e.get("lDAPDisplayName"))
770 hash_oid_name[str(e.get("attributeID"))] = strDisplay
772 msg = "Unable to insert missing elements: circular references"
773 raise ProvisioningError(msg)
776 controls = ["search_options:1:2", "sd_flags:1:2"]
777 for dn in listPresent:
778 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
781 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
782 scope=SCOPE_SUBTREE, controls=controls)
785 (str(current[0].dn) != str(reference[0].dn)) and
786 (str(current[0].dn).upper() == str(reference[0].dn).upper())
788 message(CHANGE, "Name are the same but case change,"\
789 "let's rename %s to %s" % (str(current[0].dn),
790 str(reference[0].dn)))
791 identic_rename(samdb, reference[0].dn)
792 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
794 controls=["search_options:1:2"])
796 delta = samdb.msg_diff(current[0], reference[0])
798 for att in hashAttrNotCopied.keys():
801 for att in backlinked:
806 if len(delta.items()) > 1 and usns != None:
807 # Fetch the replPropertyMetaData
808 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
809 scope=SCOPE_SUBTREE, controls=controls,
810 attrs=["replPropertyMetaData"])
811 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
812 str(res[0]["replPropertyMetaData"])).ctr
816 # We put in this hash only modification
817 # made on the current host
818 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
819 if str(o.originating_invocation_id) == str(invocationid):
820 # Note we could just use 1 here
821 hash_attr_usn[att] = o.originating_usn
823 hash_attr_usn[att] = -1
830 # We have updated by provision usn information so let's exploit
831 # replMetadataProperties
832 if forwardlinked.has_key(att):
833 handle_links(samdb, att, basedn, current[0]["dn"],
834 current[0][att], reference[0][att], delta)
836 if isFirst == 0 and len(delta.items())>1:
838 txt = "%s\n" % (str(dn))
840 # There is always a dn attribute after a msg_diff
842 if att == "rIDAvailablePool":
845 if att == "objectSid":
848 if att == "creationTime":
851 if att == "oEMInformation":
854 if att == "msDs-KeyVersionNumber":
855 # This is the kvno of the computer/user it's a very bad
859 if handle_special_case(att, delta, reference, current, usns):
860 # This attribute is "complicated" to handle and handling
861 # was done in handle_special_case
863 attrUSN = hash_attr_usn.get(att)
864 if att == "forceLogoff" and attrUSN == None:
871 # This attribute was last modified by another DC forget
873 message(CHANGE, "%sAttribute: %s has been" \
874 "created/modified/deleted by another DC,"
875 " do nothing" % (txt, att ))
879 elif usn_in_range(int(attrUSN), usns) == 0:
880 message(CHANGE, "%sAttribute: %s has been" \
881 "created/modified/deleted not during a" \
882 " provision or upgradeprovision: current" \
883 " usn %d , do nothing" % (txt, att, attrUSN))
888 if att == "defaultSecurityDescriptor":
891 message(CHANGE, "%sAttribute: %s will be modified" \
892 "/deleted it was last modified" \
893 "during a provision, current usn:" \
894 "%d" % (txt, att, attrUSN))
897 message(CHANGE, "%sAttribute: %s will be added because" \
898 " it hasn't existed before " % (txt, att))
903 # Old school way of handling things for pre alpha12 upgrade
905 msgElt = delta.get(att)
907 if att == "nTSecurityDescriptor":
914 if not hashOverwrittenAtt.has_key(att):
915 if msgElt.flags() != FLAG_MOD_ADD:
916 if not handle_special_case(att, delta, reference, current,
918 if opts.debugchange or opts.debugall:
920 dump_denied_change(dn, att,
921 messageEltFlagToString(msgElt.flags()),
922 current[0][att], reference[0][att])
924 dump_denied_change(dn, att,
925 messageEltFlagToString(msgElt.flags()),
926 current[0][att], None)
930 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
932 elif hashOverwrittenAtt.get(att)==never:
937 if len(delta.items()) >1:
938 attributes=", ".join(delta.keys())
939 message(CHANGE, "%s is different from the reference one, changed" \
940 " attributes: %s\n" % (dn, attributes))
941 changed = changed + 1
945 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
946 """Check differences between the reference provision and the upgraded one.
948 It looks for all objects which base DN is name.
950 This function will also add the missing object and update existing object
951 to add or remove attributes that were missing.
953 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
955 :param samdb: An LDB object connected to the sam.ldb of the update
957 :param basedn: String value of the DN of the partition
958 :param names: List of key provision parameters
959 :param schema: A Schema object
960 :param provisionUSNs: The USNs modified by provision/upgradeprovision
970 # Connect to the reference provision and get all the attribute in the
971 # partition referred by name
972 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
973 scope=SCOPE_SUBTREE, attrs=["dn"],
974 controls=["search_options:1:2"])
976 current = samdb.search(expression="objectClass=*", base=basedn,
977 scope=SCOPE_SUBTREE, attrs=["dn"],
978 controls=["search_options:1:2"])
979 # Create a hash for speeding the search of new object
980 for i in range(0, len(reference)):
981 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
983 # Create a hash for speeding the search of existing object in the
985 for i in range(0, len(current)):
986 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
989 for k in hash_new.keys():
990 if not hash.has_key(k):
991 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
992 listMissing.append(hash_new[k])
994 listPresent.append(hash_new[k])
996 # Sort the missing object in order to have object of the lowest level
997 # first (which can be containers for higher level objects)
998 listMissing.sort(dn_sort)
999 listPresent.sort(dn_sort)
1001 # The following lines is to load the up to
1002 # date schema into our current LDB
1003 # a complete schema is needed as the insertion of attributes
1004 # and class is done against it
1005 # and the schema is self validated
1006 samdb.set_schema_from_ldb(schema.ldb)
1008 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1009 add_deletedobj_containers(ref_samdb, samdb, names)
1011 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1012 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1013 provisionUSNs, names.invocation)
1014 message(SIMPLE, "There are %d changed objects" % (changed))
1017 except StandardError, err:
1018 message(ERROR, "Exception during upgrade of samdb:")
1019 (typ, val, tb) = sys.exc_info()
1020 traceback.print_exception(typ, val, tb)
1025 def check_updated_sd(ref_sam, cur_sam, names):
1026 """Check if the security descriptor in the upgraded provision are the same
1029 :param ref_sam: A LDB object connected to the sam.ldb file used as
1030 the reference provision
1031 :param cur_sam: A LDB object connected to the sam.ldb file used as
1033 :param names: List of key provision parameters"""
1034 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1035 scope=SCOPE_SUBTREE,
1036 attrs=["dn", "nTSecurityDescriptor"],
1037 controls=["search_options:1:2"])
1038 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1039 scope=SCOPE_SUBTREE,
1040 attrs=["dn", "nTSecurityDescriptor"],
1041 controls=["search_options:1:2"])
1043 for i in range(0, len(reference)):
1044 refsd = ndr_unpack(security.descriptor,
1045 str(reference[i]["nTSecurityDescriptor"]))
1046 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1048 for i in range(0, len(current)):
1049 key = str(current[i]["dn"]).lower()
1050 if hash.has_key(key):
1051 cursd = ndr_unpack(security.descriptor,
1052 str(current[i]["nTSecurityDescriptor"]))
1053 sddl = cursd.as_sddl(names.domainsid)
1054 if sddl != hash[key]:
1055 txt = get_diff_sddls(hash[key], sddl)
1057 message(CHANGESD, "On object %s ACL is different"\
1058 " \n%s" % (current[i]["dn"], txt))
1062 def fix_partition_sd(samdb, names):
1063 """This function fix the SD for partition containers (basedn, configdn, ...)
1064 This is needed because some provision use to have broken SD on containers
1066 :param samdb: An LDB object pointing to the sam of the current provision
1067 :param names: A list of key provision parameters
1069 # First update the SD for the rootdn
1070 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1071 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1072 controls=["search_options:1:2"])
1074 delta.dn = Dn(samdb, str(res[0]["dn"]))
1075 descr = get_domain_descriptor(names.domainsid)
1076 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1077 "nTSecurityDescriptor")
1078 samdb.modify(delta, ["recalculate_sd:0"])
1079 # Then the config dn
1080 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1081 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1082 controls=["search_options:1:2"])
1084 delta.dn = Dn(samdb, str(res[0]["dn"]))
1085 descr = get_config_descriptor(names.domainsid)
1086 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1087 "nTSecurityDescriptor" )
1088 samdb.modify(delta, ["recalculate_sd:0"])
1089 # Then the schema dn
1090 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1091 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1092 controls=["search_options:1:2"])
1095 delta.dn = Dn(samdb, str(res[0]["dn"]))
1096 descr = get_schema_descriptor(names.domainsid)
1097 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1098 "nTSecurityDescriptor" )
1099 samdb.modify(delta, ["recalculate_sd:0"])
1101 def rebuild_sd(samdb, names):
1102 """Rebuild security descriptor of the current provision from scratch
1104 During the different pre release of samba4 security descriptors (SD)
1105 were notarly broken (up to alpha11 included)
1106 This function allow to get them back in order, this function make the
1107 assumption that nobody has modified manualy an SD
1108 and so SD can be safely recalculated from scratch to get them right.
1110 :param names: List of key provision parameters"""
1114 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1115 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1116 controls=["search_options:1:2"])
1118 if not (str(obj["dn"]) == str(names.rootdn) or
1119 str(obj["dn"]) == str(names.configdn) or \
1120 str(obj["dn"]) == str(names.schemadn)):
1121 hash[str(obj["dn"])] = obj["whenCreated"]
1123 listkeys = hash.keys()
1124 listkeys.sort(dn_sort)
1126 for key in listkeys:
1129 delta.dn = Dn(samdb, key)
1130 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1132 samdb.modify(delta, ["recalculate_sd:0"])
1134 # XXX: We should always catch an explicit exception.
1135 # What could go wrong here?
1136 samdb.transaction_cancel()
1137 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1138 scope=SCOPE_SUBTREE,
1139 attrs=["dn", "nTSecurityDescriptor"],
1140 controls=["search_options:1:2"])
1141 badsd = ndr_unpack(security.descriptor,
1142 str(res[0]["nTSecurityDescriptor"]))
1143 print "bad stuff %s"%badsd.as_sddl(names.domainsid)
1146 def removeProvisionUSN(samdb):
1147 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1148 entry = samdb.search(expression="dn=@PROVISION", base = "",
1149 scope=SCOPE_SUBTREE,
1150 controls=["search_options:1:2"],
1153 empty.dn = entry[0].dn
1154 delta = samdb.msg_diff(entry[0], empty)
1156 delta.dn = entry[0].dn
1159 def delta_update_basesamdb(refpaths, paths, creds, session, lp):
1160 """Update the provision container db: sam.ldb
1161 This function is aimed for alpha9 and newer;
1163 :param refpaths: An object holding the different importants paths for
1164 reference provision object
1165 :param paths: An object holding the different importants paths for
1166 upgraded provision object
1167 :param creds: Credential used for openning LDB files
1168 :param session: Session to use for openning LDB files
1169 :param lp: A loadparam object"""
1172 "Update base samdb by searching difference with reference one")
1173 refsam = Ldb(refpaths.samdb, session_info=session, credentials=creds,
1174 lp=lp, options=["modules:"])
1175 sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1176 options=["modules:"])
1180 reference = refsam.search(expression="")
1182 for refentry in reference:
1183 entry = sam.search(expression="dn=%s" % refentry["dn"],
1184 scope=SCOPE_SUBTREE)
1186 message(CHANGE, "Adding %s to sam db" % str(delta.dn))
1187 delta = sam.msg_diff(empty, refentry)
1188 if str(refentry.dn) == "@PROVISION" and\
1189 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
1190 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
1191 delta.dn = refentry.dn
1194 delta = sam.msg_diff(entry[0], refentry)
1195 if str(refentry.dn) == "@PROVISION" and\
1196 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
1197 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
1198 if len(delta.items()) > 1:
1199 delta.dn = refentry.dn
1203 def simple_update_basesamdb(newpaths, paths, names):
1204 """Update the provision container db: sam.ldb
1205 This function is aimed at very old provision (before alpha9)
1207 :param newpaths: List of paths for different provision objects
1208 from the reference provision
1209 :param paths: List of paths for different provision objects
1210 from the upgraded provision
1211 :param names: List of key provision parameters"""
1213 message(SIMPLE, "Copy samdb")
1214 shutil.copy(newpaths.samdb, paths.samdb)
1216 message(SIMPLE, "Update partitions filename if needed")
1217 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1218 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1219 usersldb = os.path.join(paths.private_dir, "users.ldb")
1220 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1222 if not os.path.isdir(samldbdir):
1224 os.chmod(samldbdir, 0700)
1225 if os.path.isfile(schemaldb):
1226 shutil.copy(schemaldb, os.path.join(samldbdir,
1227 "%s.ldb"%str(names.schemadn).upper()))
1228 os.remove(schemaldb)
1229 if os.path.isfile(usersldb):
1230 shutil.copy(usersldb, os.path.join(samldbdir,
1231 "%s.ldb"%str(names.rootdn).upper()))
1233 if os.path.isfile(configldb):
1234 shutil.copy(configldb, os.path.join(samldbdir,
1235 "%s.ldb"%str(names.configdn).upper()))
1236 os.remove(configldb)
1239 def update_privilege(ref_private_path, cur_private_path):
1240 """Update the privilege database
1242 :param ref_private_path: Path to the private directory of the reference
1244 :param cur_private_path: Path to the private directory of the current
1245 (and to be updated) provision."""
1246 message(SIMPLE, "Copy privilege")
1247 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1248 os.path.join(cur_private_path, "privilege.ldb"))
1251 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1252 """Upgrade the SAM DB contents for all the provision partitions
1254 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1256 :param samdb: An LDB object connected to the sam.ldb of the update
1258 :param names: List of key provision parameters
1259 :param highestUSN: The highest USN modified by provision/upgradeprovision
1261 :param schema: A Schema object that represent the schema of the provision"""
1263 message(SIMPLE, "Starting update of samdb")
1264 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1267 message(SIMPLE, "Update of samdb finished")
1270 message(SIMPLE, "Update failed")
1274 def update_machine_account_password(samdb, secrets_ldb, names):
1275 """Update (change) the password of the current DC both in the SAM db and in
1278 :param samdb: An LDB object related to the sam.ldb file of a given provision
1279 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
1281 :param names: List of key provision parameters"""
1283 message(SIMPLE, "Update machine account")
1284 expression = "samAccountName=%s$" % names.netbiosname
1285 secrets_msg = secrets_ldb.search(expression=expression,
1286 attrs=["secureChannelType"])
1287 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
1288 res = samdb.search(expression=expression, attrs=[])
1289 assert(len(res) == 1)
1291 msg = Message(res[0].dn)
1292 machinepass = samba.generate_random_password(128, 255)
1293 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE,
1297 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
1298 attrs=["msDs-keyVersionNumber"])
1299 assert(len(res) == 1)
1300 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
1301 secChanType = int(secrets_msg[0]["secureChannelType"][0])
1303 secretsdb_self_join(secrets_ldb, domain=names.domain,
1304 realm=names.realm or sambaopts._lp.get('realm'),
1305 domainsid=names.domainsid,
1306 dnsdomain=names.dnsdomain,
1307 netbiosname=names.netbiosname,
1308 machinepass=machinepass,
1309 key_version_number=kvno,
1310 secure_channel_type=secChanType)
1312 raise ProvisioningError("Unable to find a Secure Channel" \
1313 "of type SEC_CHAN_BDC")
1316 def update_gpo(paths, creds, session, names):
1317 """Create missing GPO file object if needed
1319 Set ACL correctly also.
1321 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
1322 if not os.path.isdir(dir):
1323 create_gpo_struct(dir)
1325 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
1326 if not os.path.isdir(dir):
1327 create_gpo_struct(dir)
1328 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1329 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
1330 names.domaindn, samdb, lp)
1333 def getOEMInfo(samdb, rootdn):
1334 """Return OEM Information on the top level
1335 Samba4 use to store version info in this field
1337 :param samdb: An LDB object connect to sam.ldb
1338 :param rootdn: Root DN of the domain
1339 :return: The content of the field oEMInformation (if any)"""
1340 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
1341 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1343 info = res[0]["oEMInformation"]
1348 def updateOEMInfo(samdb, names):
1349 res = samdb.search(expression="(objectClass=*)", base=str(names.rootdn),
1350 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1352 info = res[0]["oEMInformation"]
1353 info = "%s, upgrade to %s" % (info, version)
1355 delta.dn = Dn(samdb, str(res[0]["dn"]))
1356 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
1361 def setup_path(file):
1362 return os.path.join(setup_dir, file)
1364 # Synopsis for updateprovision
1365 # 1) get path related to provision to be update (called current)
1366 # 2) open current provision ldbs
1367 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1369 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1370 # by either upgradeprovision or provision
1371 # 5) creation of a new provision the latest version of provision script
1372 # (called reference)
1373 # 6) get reference provision paths
1374 # 7) open reference provision ldbs
1375 # 8) setup helpers data that will help the update process
1376 # 9) update the privilege ldb by copying the one of referecence provision to
1377 # the current provision
1378 # 10)get the oemInfo field, this field contains information about the different
1379 # provision that have been done
1380 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1381 # integer) or none of this the following things are done
1382 # A) When alpha9 or alphaxx is present
1383 # The base sam.ldb file is updated by looking at the difference between
1384 # referrence one and the current one. Everything is copied with the
1385 # exception of lastProvisionUSN attributes. The highest used USN
1386 # is fetched so that changed by upgradeprovision usn can be tracked
1387 # B) Other case (it reflect that that provision was done before alpha9)
1388 # The base sam.ldb of the reference provision is copied over
1389 # the current one, if necessary ldb related to partitions are moved
1391 # 12)A Schema object is created, it will be used to provide a complete
1392 # schema to current provision during update (as the schema of the
1393 # current provision might not be complete and so won't allow some
1394 # object to be created)
1395 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1396 # 14)The secrets db is updated by pull all the difference from the reference
1397 # provision into the current provision
1398 # 15)As the previous step has most probably modified the password stored in
1399 # in secret for the current DC, a new password is generated,
1400 # the kvno is bumped and the entry in samdb is also updated
1401 # 16)For current provision older than alpha9, we must fix the SD a little bit
1402 # administrator to update them because SD used to be generated with the
1403 # system account before alpha9.
1404 # 17)The highest usn modified so far is searched in the database it will be
1405 # the upper limit for usn modified during provision.
1406 # This is done before potential SD recalculation because we do not want
1407 # SD modified during recalculation to be marked as modified during provision
1408 # (and so possibly remplaced at next upgradeprovision)
1409 # 18)Rebuilt SD if the flag indicate to do so
1410 # 19)Check difference between SD of reference provision and those of the
1411 # current provision. The check is done by getting the sddl representation
1412 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1413 # Each part is verified separetly, for dacl and sacl ACL is splited into
1414 # ACEs and each ACE is verified separately (so that a permutation in ACE
1415 # didn't raise as an error).
1416 # 20)The oemInfo field is updated to add information about the fact that the
1417 # provision has been updated by the upgradeprovision version xxx
1418 # (the version is the one obtained when starting samba with the --version
1420 # 21)Check if the current provision has all the settings needed for dynamic
1421 # DNS update to work (that is to say the provision is newer than
1422 # january 2010). If not dns configuration file from reference provision
1423 # are copied in a sub folder and the administrator is invited to
1424 # do what is needed.
1425 # 22)If the lastProvisionUSN attribute was present it is updated to add
1426 # the range of usns modified by the current upgradeprovision
1429 # About updating the sam DB
1430 # The update takes place in update_partition function
1431 # This function read both current and reference provision and list all
1432 # the available DN of objects
1433 # If the string representation of a DN in reference provision is
1434 # equal to the string representation of a DN in current provision
1435 # (without taking care of case) then the object is flaged as being
1436 # present. If the object is not present in current provision the object
1437 # is being flaged as missing in current provision. Object present in current
1438 # provision but not in reference provision are ignored.
1439 # Once the list of objects present and missing is done, the deleted object
1440 # containers are created in the differents partitions (if missing)
1442 # Then the function add_missing_entries is called
1443 # This function will go through the list of missing entries by calling
1444 # add_missing_object for the given object. If this function returns 0
1445 # it means that the object needs some other object in order to be created
1446 # The object is reappended at the end of the list to be created later
1447 # (and preferably after all the needed object have been created)
1448 # The function keeps on looping on the list of object to be created until
1449 # it's empty or that the number of defered creation is equal to the number
1450 # of object that still needs to be created.
1452 # The function add_missing_object will first check if the object can be created.
1453 # That is to say that it didn't depends other not yet created objects
1454 # If requisit can't be fullfilled it exists with 0
1455 # Then it will try to create the missing entry by creating doing
1456 # an ldb_message_diff between the object in the reference provision and
1458 # This resulting object is filtered to remove all the back link attribute
1459 # (ie. memberOf) as they will be created by the other linked object (ie.
1460 # the one with the member attribute)
1461 # All attributes specified in the hashAttrNotCopied associative array are
1462 # also removed it's most of the time generated attributes
1464 # After missing entries have been added the update_partition function will
1465 # take care of object that exist but that need some update.
1466 # In order to do so the function update_present is called with the list
1467 # of object that are present in both provision and that might need an update.
1469 # This function handle first case mismatch so that the DN in the current
1470 # provision have the same case as in reference provision
1472 # It will then construct an associative array consiting of attributes as
1473 # key and invocationid as value( if the originating invocation id is
1474 # different from the invocation id of the current DC the value is -1 instead).
1476 # If the range of provision modified attributes is present, the function will
1477 # use the replMetadataProperty update method which is the following:
1478 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1479 # creationTime, msDs-KeyVersionNumber, oEMInformation
1480 # Check for each attribute if its usn is within one of the modified by
1481 # provision range and if its originating id is the invocation id of the
1482 # current DC, then validate the update from reference to current.
1483 # If not or if there is no replMetatdataProperty for this attribute then we
1485 # Otherwise (case the range of provision modified attribute is not present) it
1486 # use the following process:
1487 # All attributes that need to be added are accepted at the exeption of those
1488 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1489 # correct flags specified.
1490 # For attributes that need to be modified or removed, a check is performed
1491 # in OverwrittenAtt, if the attribute is present and the modification flag
1492 # (remove, delete) is one of those listed for this attribute then modification
1493 # is accepted. For complicated handling of attribute update, the control is passed
1494 # to handle_special_case
1498 if __name__ == '__main__':
1499 global defSDmodified
1501 # From here start the big steps of the program
1502 # 1) First get files paths
1503 paths = get_paths(param, smbconf=smbconf)
1504 paths.setup = setup_dir
1505 # Get ldbs with the system session, it is needed for searching
1506 # provision parameters
1507 session = system_session()
1509 # This variable will hold the last provision USN once if it exists.
1512 ldbs = get_ldbs(paths, creds, session, lp)
1513 ldbs.startTransactions()
1515 # 3) Guess all the needed names (variables in fact) from the current
1517 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, paths,
1520 lastProvisionUSNs = getLastProvisionUSN(ldbs.sam)
1521 if lastProvisionUSNs != None:
1523 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1525 # Objects will be created with the admin session
1526 # (not anymore system session)
1527 adm_session = admin_session(lp, str(names.domainsid))
1528 # So we reget handle on objects
1529 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1531 if not sanitychecks(ldbs.sam, names):
1532 message(SIMPLE, "Sanity checks for the upgrade fails, checks messages" \
1533 " and correct them before rerunning upgradeprovision")
1536 # Let's see provision parameters
1537 print_provision_key_parameters(names)
1539 # 5) With all this information let's create a fresh new provision used as
1541 message(SIMPLE, "Creating a reference provision")
1542 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1543 prefix="referenceprovision")
1544 newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1549 # We need to get a list of object which SD is directly computed from
1550 # defaultSecurityDescriptor.
1551 # This will allow us to know which object we can rebuild the SD in case
1552 # of change of the parent's SD or of the defaultSD.
1553 # Get file paths of this new provision
1554 newpaths = get_paths(param, targetdir=provisiondir)
1555 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1556 new_ldbs.startTransactions()
1558 # 8) Populate some associative array to ease the update process
1559 # List of attribute which are link and backlink
1560 populate_links(new_ldbs.sam, names.schemadn)
1561 # List of attribute with ASN DN synthax)
1562 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1564 update_privilege(newpaths.private_dir, paths.private_dir)
1566 oem = getOEMInfo(ldbs.sam, names.rootdn)
1567 # Do some modification on sam.ldb
1568 ldbs.groupedCommit()
1570 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1572 # Starting from alpha9 we can consider that the structure is quite ok
1573 # and that we should do only dela
1574 new_ldbs.groupedCommit()
1575 delta_update_basesamdb(newpaths, paths, creds, session, lp)
1576 ldbs.startTransactions()
1577 minUSN = get_max_usn(ldbs.sam, str(names.rootdn)) + 1
1578 new_ldbs.startTransactions()
1581 simple_update_basesamdb(newpaths, paths, names)
1582 ldbs = get_ldbs(paths, creds, session, lp)
1583 ldbs.startTransactions()
1584 removeProvisionUSN(ldbs.sam)
1587 schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1588 serverdn=str(names.serverdn))
1591 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1593 message(SIMPLE, "Rollbacking every changes. Check the reason" \
1595 message(SIMPLE, "In any case your system as it was before" \
1597 ldbs.groupedRollback()
1598 new_ldbs.groupedRollback()
1599 shutil.rmtree(provisiondir)
1602 update_secrets(new_ldbs.secrets, ldbs.secrets)
1604 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1606 # 16) SD should be created with admin but as some previous acl were so wrong
1607 # that admin can't modify them we have first to recreate them with the good
1608 # form but with system account and then give the ownership to admin ...
1609 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1610 message(SIMPLE, "Fixing old povision SD")
1611 fix_partition_sd(ldbs.sam, names)
1612 rebuild_sd(ldbs.sam, names)
1614 # We calculate the max USN before recalculating the SD because we might
1615 # touch object that have been modified after a provision and we do not
1616 # want that the next upgradeprovision thinks that it has a green light
1620 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1622 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1623 # But in fact we should do it also if one object has its SD modified as
1624 # child might need rebuild
1625 if defSDmodified == 1:
1626 message(SIMPLE, "Updating SD")
1627 ldbs.sam.set_session_info(adm_session)
1628 # Alpha10 was a bit broken still
1629 if re.match(r'.*alpha(\d|10)', str(oem)):
1630 fix_partition_sd(ldbs.sam, names)
1631 rebuild_sd(ldbs.sam, names)
1634 # Now we are quite confident in the recalculate process of the SD, we make
1636 # Also the check must be done in a clever way as for the moment we just
1638 if opts.debugchangesd:
1639 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1642 updateOEMInfo(ldbs.sam, names)
1644 check_for_DNS(newpaths.private_dir, paths.private_dir)
1646 if lastProvisionUSNs != None:
1647 updateProvisionUSN(ldbs.sam, minUSN, maxUSN)
1648 ldbs.groupedCommit()
1649 new_ldbs.groupedCommit()
1650 message(SIMPLE, "Upgrade finished !")
1651 # remove reference provision now that everything is done !
1652 shutil.rmtree(provisiondir)