4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
38 import samba.getopt as options
40 from base64 import b64encode
41 from samba.credentials import DONT_USE_KERBEROS
42 from samba.auth import system_session, admin_session
43 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
44 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
45 MessageElement, Message, Dn)
46 from samba import param, dsdb, Ldb
47 from samba.provision import (find_setup_dir, get_domain_descriptor,
48 get_config_descriptor,
49 ProvisioningError, get_last_provision_usn,
50 get_max_usn, update_provision_usn)
51 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import security, drsblobs, xattr
53 from samba.ndr import ndr_unpack
54 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
55 find_provision_key_parameters, get_ldbs,
56 usn_in_range, identic_rename, get_diff_sddls,
57 update_secrets, CHANGE, ERROR, SIMPLE,
58 CHANGEALL, GUESS, CHANGESD, PROVISION,
59 updateOEMInfo, getOEMInfo, update_gpo,
60 delta_update_basesamdb, update_policyids,
61 update_machine_account_password,
62 search_constructed_attrs_stored,
63 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 what is different but won't be changed")
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 information security descriptors 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("--full", action="store_true",
155 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
157 opts = parser.parse_args()[0]
159 handler = logging.StreamHandler(sys.stdout)
160 upgrade_logger = logging.getLogger("upgradeprovision")
161 upgrade_logger.setLevel(logging.INFO)
163 upgrade_logger.addHandler(handler)
165 provision_logger = logging.getLogger("provision")
166 provision_logger.addHandler(handler)
168 whatToLog = define_what_to_log(opts)
170 def message(what, text):
171 """Print a message if this message type has been selected to be printed
173 :param what: Category of the message
174 :param text: Message to print """
175 if (whatToLog & what) or what <= 0:
176 upgrade_logger.info("%s", text)
178 if len(sys.argv) == 1:
179 opts.interactive = True
180 lp = sambaopts.get_loadparm()
181 smbconf = lp.configfile
183 creds = credopts.get_credentials(lp)
184 creds.set_kerberos_state(DONT_USE_KERBEROS)
185 setup_dir = opts.setupdir
186 if setup_dir is None:
187 setup_dir = find_setup_dir()
191 def check_for_DNS(refprivate, private):
192 """Check if the provision has already the requirement for dynamic dns
194 :param refprivate: The path to the private directory of the reference
196 :param private: The path to the private directory of the upgraded
199 spnfile = "%s/spn_update_list" % private
200 dnsfile = "%s/dns_update_list" % private
201 namedfile = lp.get("dnsupdate:path")
204 namedfile = "%s/named.conf.update" % private
206 if not os.path.exists(spnfile):
207 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
209 if not os.path.exists(dnsfile):
210 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
212 destdir = "%s/new_dns" % private
213 dnsdir = "%s/dns" % private
215 if not os.path.exists(namedfile):
216 if not os.path.exists(destdir):
218 if not os.path.exists(dnsdir):
220 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
221 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
222 message(SIMPLE, "It seems that you provision didn't integrate new rules "
223 "for dynamic dns update of domain related entries")
224 message(SIMPLE, "A copy of the new bind configuration files and "
225 "template as been put in %s, you should read them and configure dynamic "
226 " dns update" % destdir)
229 def populate_links(samdb, schemadn):
230 """Populate an array with all the back linked attributes
232 This attributes that are modified automaticaly when
233 front attibutes are changed
235 :param samdb: A LDB object for sam.ldb file
236 :param schemadn: DN of the schema for the partition"""
237 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
238 backlinked.extend(linkedAttHash.values())
239 for t in linkedAttHash.keys():
243 def populate_dnsyntax(samdb, schemadn):
244 """Populate an array with all the attributes that have DN synthax
247 :param samdb: A LDB object for sam.ldb file
248 :param schemadn: DN of the schema for the partition"""
249 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
250 str(schemadn)), scope=SCOPE_SUBTREE,
251 attrs=["lDAPDisplayName"])
253 dn_syntax_att.append(elem["lDAPDisplayName"])
256 def sanitychecks(samdb, names):
257 """Make some checks before trying to update
259 :param samdb: An LDB object opened on sam.ldb
260 :param names: list of key provision parameters
261 :return: Status of check (1 for Ok, 0 for not Ok) """
262 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
263 scope=SCOPE_SUBTREE, attrs=["dn"],
264 controls=["search_options:1:2"])
266 print "No DC found, your provision is most probably hardly broken !"
269 print "Found %d domain controllers, for the moment upgradeprovision" \
270 "is not able to handle upgrade on domain with more than one DC, please demote" \
271 " the other(s) 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 changed 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 +" is not allowed to be changed/removed, I discard this 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", "local_oid:1.3.6.1.4.1.7165.4.3.16: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", "local_oid:1.3.6.1.4.1.7165.4.3.16: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():
660 res = samdb.search(expression="(objectClass=*)", base=part,
662 attrs=["dn", "wellKnownObjects"])
664 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
668 wko = res[0]["wellKnownObjects"]
670 # The wellKnownObject that we want to add.
672 if str(o) == targetWKO:
674 listwko.append(str(o))
677 listwko.append(targetWKO)
680 delta.dn = Dn(samdb, str(res[0]["dn"]))
681 delta["wellKnownObjects"] = MessageElement(listwko,
686 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
687 """Add the missing object whose DN is the list
689 The function add the object if the objects on which it depends are
692 :param ref_samdb: Ldb object representing the SAM db of the reference
694 :param samdb: Ldb object representing the SAM db of the upgraded
696 :param dn: DN of the object to be added
697 :param names: List of key provision parameters
698 :param basedn: DN of the partition to be updated
699 :param list: List of DN to be added in the upgraded provision"""
704 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
706 listMissing = listDefered
708 hashMissing = gen_dn_index_hash(listMissing)
709 for dn in listMissing:
710 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
714 # DN can't be created because it depends on some
715 # other DN in the list
716 listDefered.append(dn)
718 if len(listDefered) != 0:
719 raise ProvisioningError("Unable to insert missing elements:"
720 "circular references")
722 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
723 """This function handle updates on links
725 :param samdb: An LDB object pointing to the updated provision
726 :param att: Attribute to update
727 :param basedn: The root DN of the provision
728 :param dn: The DN of the inspected object
729 :param value: The value of the attribute
730 :param ref_value: The value of this attribute in the reference provision
731 :param delta: The MessageElement object that will be applied for
732 transforming the current provision"""
734 res = samdb.search(expression="dn=%s" % dn, base=basedn,
735 controls=["search_options:1:2", "reveal:1"],
743 newlinklist.extend(value)
747 # for w2k domain level the reveal won't reveal anything ...
748 # it means that we can readd links that were removed on purpose ...
749 # Also this function in fact just accept add not removal
751 for e in res[0][att]:
752 if not hash.has_key(e):
753 # We put in the blacklist all the element that are in the "revealed"
754 # result and not in the "standard" result
755 # This element are links that were removed before and so that
756 # we don't wan't to readd
760 if not blacklist.has_key(e) and not hash.has_key(e):
761 newlinklist.append(str(e))
764 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
769 msg_elt_flag_strs = {
770 ldb.FLAG_MOD_ADD: "MOD_ADD",
771 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
772 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
775 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
776 """ This function updates the object that are already present in the
779 :param ref_samdb: An LDB object pointing to the reference provision
780 :param samdb: An LDB object pointing to the updated provision
781 :param basedn: A string with the value of the base DN for the provision
783 :param listPresent: A list of object that is present in the provision
784 :param usns: A list of USN range modified by previous provision and
786 :param invocationid: The value of the invocationid for the current DC"""
789 # This hash is meant to speedup lookup of attribute name from an oid,
790 # it's for the replPropertyMetaData handling
792 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
793 controls=["search_options:1:2"], attrs=["attributeID",
797 strDisplay = str(e.get("lDAPDisplayName"))
798 hash_oid_name[str(e.get("attributeID"))] = strDisplay
800 msg = "Unable to insert missing elements: circular references"
801 raise ProvisioningError(msg)
804 controls = ["search_options:1:2", "sd_flags:1:2"]
805 for dn in listPresent:
806 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
809 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
810 scope=SCOPE_SUBTREE, controls=controls)
813 (str(current[0].dn) != str(reference[0].dn)) and
814 (str(current[0].dn).upper() == str(reference[0].dn).upper())
816 message(CHANGE, "Name are the same but case change,"\
817 "let's rename %s to %s" % (str(current[0].dn),
818 str(reference[0].dn)))
819 identic_rename(samdb, reference[0].dn)
820 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
824 delta = samdb.msg_diff(current[0], reference[0])
826 for att in hashAttrNotCopied.keys():
829 for att in backlinked:
834 if len(delta.items()) > 1 and usns is not None:
835 # Fetch the replPropertyMetaData
836 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
837 scope=SCOPE_SUBTREE, controls=controls,
838 attrs=["replPropertyMetaData"])
839 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
840 str(res[0]["replPropertyMetaData"])).ctr
844 # We put in this hash only modification
845 # made on the current host
846 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
847 if str(o.originating_invocation_id) == str(invocationid):
848 # Note we could just use 1 here
849 hash_attr_usn[att] = o.originating_usn
851 hash_attr_usn[att] = -1
858 # We have updated by provision usn information so let's exploit
859 # replMetadataProperties
860 if att in forwardlinked:
862 curval = current[0][att]
865 if reference[0].get():
866 refval = reference[0][att]
869 handle_links(samdb, att, basedn, current[0]["dn"],
870 curval, refval, delta)
873 if isFirst == 0 and len(delta.items())>1:
875 txt = "%s\n" % (str(dn))
877 # There is always a dn attribute after a msg_diff
879 if att == "rIDAvailablePool":
882 if att == "objectSid":
885 if att == "creationTime":
888 if att == "oEMInformation":
891 if att == "msDs-KeyVersionNumber":
892 # This is the kvno of the computer/user it's a very bad
896 if handle_special_case(att, delta, reference, current, usns, basedn, samdb):
897 # This attribute is "complicated" to handle and handling
898 # was done in handle_special_case
900 attrUSN = hash_attr_usn.get(att)
901 if att == "forceLogoff" and attrUSN is None:
906 if att == "nTSecurityDescriptor":
907 cursd = ndr_unpack(security.descriptor,
908 str(current[0]["nTSecurityDescriptor"]))
909 cursddl = cursd.as_sddl(names.domainsid)
910 refsd = ndr_unpack(security.descriptor,
911 str(reference[0]["nTSecurityDescriptor"]))
912 refsddl = cursd.as_sddl(names.domainsid)
914 if get_diff_sddls(refsddl, cursddl) == "":
915 message(CHANGE, "sd are identical")
917 message(CHANGE, "sd are not identical")
919 # This attribute was last modified by another DC forget
921 message(CHANGE, "%sAttribute: %s has been"
922 "created/modified/deleted by another DC,"
923 " do nothing" % (txt, att ))
927 elif not usn_in_range(int(attrUSN), usns):
928 message(CHANGE, "%sAttribute: %s has been"
929 "created/modified/deleted not during a"
930 " provision or upgradeprovision: current"
931 " usn %d , do nothing" % (txt, att, attrUSN))
936 if att == "defaultSecurityDescriptor":
939 message(CHANGE, "%sAttribute: %s will be modified"
940 "/deleted it was last modified"
941 " during a provision, current usn:"
942 "%d" % (txt, att, attrUSN))
945 message(CHANGE, "%sAttribute: %s will be added because"
946 " it hasn't existed before " % (txt, att))
951 # Old school way of handling things for pre alpha12 upgrade
953 msgElt = delta.get(att)
955 if att == "nTSecurityDescriptor":
962 if not hashOverwrittenAtt.has_key(att):
963 if msgElt.flags() != FLAG_MOD_ADD:
964 if not handle_special_case(att, delta, reference, current,
965 usns, basedn, samdb):
966 if opts.debugchange or opts.debugall:
968 dump_denied_change(dn, att,
969 msg_elt_flag_strs[msgElt.flags()],
970 current[0][att], reference[0][att])
972 dump_denied_change(dn, att,
973 msg_elt_flag_strs[msgElt.flags()],
974 current[0][att], None)
978 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
980 elif hashOverwrittenAtt.get(att)==never:
985 if len(delta.items()) >1:
986 attributes=", ".join(delta.keys())
988 relaxedatt = ['iscriticalsystemobject', 'grouptype']
989 # Let's try to reduce as much as possible the use of relax control
990 #for checkedatt in relaxedatt:
991 for attr in delta.keys():
992 if attr.lower() in relaxedatt:
993 modcontrols = ["relax:0", "local_oid:1.3.6.1.4.1.7165.4.3.16:0"]
994 message(CHANGE, "%s is different from the reference one, changed"
995 " attributes: %s\n" % (dn, attributes))
997 samdb.modify(delta, modcontrols)
1000 def reload_full_schema(samdb, names):
1001 """Load the updated schema with all the new and existing classes
1004 :param samdb: An LDB object connected to the sam.ldb of the update
1006 :param names: List of key provision parameters
1009 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1010 scope=SCOPE_SUBTREE)
1015 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1017 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1018 prefixmap_data = b64encode(prefixmap_data)
1020 # We don't actually add this ldif, just parse it
1021 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1023 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1026 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1027 """Check differences between the reference provision and the upgraded one.
1029 It looks for all objects which base DN is name.
1031 This function will also add the missing object and update existing object
1032 to add or remove attributes that were missing.
1034 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1036 :param samdb: An LDB object connected to the sam.ldb of the update
1038 :param basedn: String value of the DN of the partition
1039 :param names: List of key provision parameters
1040 :param schema: A Schema object
1041 :param provisionUSNs: The USNs modified by provision/upgradeprovision
1043 :param prereloadfunc: A function that must be executed just before the reload
1054 # Connect to the reference provision and get all the attribute in the
1055 # partition referred by name
1056 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1057 scope=SCOPE_SUBTREE, attrs=["dn"],
1058 controls=["search_options:1:2"])
1060 current = samdb.search(expression="objectClass=*", base=basedn,
1061 scope=SCOPE_SUBTREE, attrs=["dn"],
1062 controls=["search_options:1:2"])
1063 # Create a hash for speeding the search of new object
1064 for i in range(0, len(reference)):
1065 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1067 # Create a hash for speeding the search of existing object in the
1069 for i in range(0, len(current)):
1070 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1073 for k in hash_new.keys():
1074 if not hash.has_key(k):
1075 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1076 listMissing.append(hash_new[k])
1078 listPresent.append(hash_new[k])
1080 # Sort the missing object in order to have object of the lowest level
1081 # first (which can be containers for higher level objects)
1082 listMissing.sort(dn_sort)
1083 listPresent.sort(dn_sort)
1085 # The following lines is to load the up to
1086 # date schema into our current LDB
1087 # a complete schema is needed as the insertion of attributes
1088 # and class is done against it
1089 # and the schema is self validated
1090 samdb.set_schema(schema)
1092 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1093 add_deletedobj_containers(ref_samdb, samdb, names)
1095 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1098 message(SIMPLE, "Reloading a merged schema, it might trigger"\
1099 " reindexing so please be patient")
1100 reload_full_schema(samdb, names)
1101 message(SIMPLE, "Schema reloaded !")
1103 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1104 provisionUSNs, names.invocation)
1105 message(SIMPLE, "There are %d changed objects" % (changed))
1108 except StandardError, err:
1109 message(ERROR, "Exception during upgrade of samdb:")
1110 (typ, val, tb) = sys.exc_info()
1111 traceback.print_exception(typ, val, tb)
1115 def check_updated_sd(ref_sam, cur_sam, names):
1116 """Check if the security descriptor in the upgraded provision are the same
1119 :param ref_sam: A LDB object connected to the sam.ldb file used as
1120 the reference provision
1121 :param cur_sam: A LDB object connected to the sam.ldb file used as
1123 :param names: List of key provision parameters"""
1124 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1125 scope=SCOPE_SUBTREE,
1126 attrs=["dn", "nTSecurityDescriptor"],
1127 controls=["search_options:1:2"])
1128 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1129 scope=SCOPE_SUBTREE,
1130 attrs=["dn", "nTSecurityDescriptor"],
1131 controls=["search_options:1:2"])
1133 for i in range(0, len(reference)):
1134 refsd = ndr_unpack(security.descriptor,
1135 str(reference[i]["nTSecurityDescriptor"]))
1136 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1139 for i in range(0, len(current)):
1140 key = str(current[i]["dn"]).lower()
1141 if hash.has_key(key):
1142 cursd = ndr_unpack(security.descriptor,
1143 str(current[i]["nTSecurityDescriptor"]))
1144 sddl = cursd.as_sddl(names.domainsid)
1145 if sddl != hash[key]:
1146 txt = get_diff_sddls(hash[key], sddl)
1148 message(CHANGESD, "On object %s ACL is different"
1149 " \n%s" % (current[i]["dn"], txt))
1153 def fix_partition_sd(samdb, names):
1154 """This function fix the SD for partition containers (basedn, configdn, ...)
1155 This is needed because some provision use to have broken SD on containers
1157 :param samdb: An LDB object pointing to the sam of the current provision
1158 :param names: A list of key provision parameters
1160 # First update the SD for the rootdn
1161 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1162 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1163 controls=["search_options:1:2"])
1165 delta.dn = Dn(samdb, str(res[0]["dn"]))
1166 descr = get_domain_descriptor(names.domainsid)
1167 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1168 "nTSecurityDescriptor")
1170 # Then the config dn
1171 res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1172 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1173 controls=["search_options:1:2"])
1175 delta.dn = Dn(samdb, str(res[0]["dn"]))
1176 descr = get_config_descriptor(names.domainsid)
1177 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1178 "nTSecurityDescriptor" )
1180 # Then the schema dn
1181 res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1182 scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1183 controls=["search_options:1:2"])
1186 delta.dn = Dn(samdb, str(res[0]["dn"]))
1187 descr = get_schema_descriptor(names.domainsid)
1188 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1189 "nTSecurityDescriptor" )
1192 def rebuild_sd(samdb, names):
1193 """Rebuild security descriptor of the current provision from scratch
1195 During the different pre release of samba4 security descriptors (SD)
1196 were notarly broken (up to alpha11 included)
1197 This function allow to get them back in order, this function make the
1198 assumption that nobody has modified manualy an SD
1199 and so SD can be safely recalculated from scratch to get them right.
1201 :param names: List of key provision parameters"""
1205 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1206 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1207 controls=["search_options:1:2"])
1209 if not (str(obj["dn"]) == str(names.rootdn) or
1210 str(obj["dn"]) == str(names.configdn) or
1211 str(obj["dn"]) == str(names.schemadn)):
1212 hash[str(obj["dn"])] = obj["whenCreated"]
1214 listkeys = hash.keys()
1215 listkeys.sort(dn_sort)
1217 for key in listkeys:
1220 delta.dn = Dn(samdb, key)
1221 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1223 samdb.modify(delta, ["recalculate_sd:0"])
1225 # XXX: We should always catch an explicit exception.
1226 # What could go wrong here?
1227 samdb.transaction_cancel()
1228 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1229 scope=SCOPE_SUBTREE,
1230 attrs=["dn", "nTSecurityDescriptor"],
1231 controls=["search_options:1:2"])
1232 badsd = ndr_unpack(security.descriptor,
1233 str(res[0]["nTSecurityDescriptor"]))
1234 print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1237 def removeProvisionUSN(samdb):
1238 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1239 entry = samdb.search(expression="dn=@PROVISION", base = "",
1240 scope=SCOPE_SUBTREE,
1241 controls=["search_options:1:2"],
1244 empty.dn = entry[0].dn
1245 delta = samdb.msg_diff(entry[0], empty)
1247 delta.dn = entry[0].dn
1250 def remove_stored_generated_attrs(paths, creds, session, lp):
1251 """Remove previously stored constructed attributes
1253 :param paths: List of paths for different provision objects
1254 from the upgraded provision
1255 :param creds: A credential object
1256 :param session: A session object
1257 :param lp: A line parser object
1258 :return: An associative array whose key are the different constructed
1259 attributes and the value the dn where this attributes were found.
1263 def simple_update_basesamdb(newpaths, paths, names):
1264 """Update the provision container db: sam.ldb
1265 This function is aimed at very old provision (before alpha9)
1267 :param newpaths: List of paths for different provision objects
1268 from the reference provision
1269 :param paths: List of paths for different provision objects
1270 from the upgraded provision
1271 :param names: List of key provision parameters"""
1273 message(SIMPLE, "Copy samdb")
1274 shutil.copy(newpaths.samdb, paths.samdb)
1276 message(SIMPLE, "Update partitions filename if needed")
1277 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1278 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1279 usersldb = os.path.join(paths.private_dir, "users.ldb")
1280 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1282 if not os.path.isdir(samldbdir):
1284 os.chmod(samldbdir, 0700)
1285 if os.path.isfile(schemaldb):
1286 shutil.copy(schemaldb, os.path.join(samldbdir,
1287 "%s.ldb"%str(names.schemadn).upper()))
1288 os.remove(schemaldb)
1289 if os.path.isfile(usersldb):
1290 shutil.copy(usersldb, os.path.join(samldbdir,
1291 "%s.ldb"%str(names.rootdn).upper()))
1293 if os.path.isfile(configldb):
1294 shutil.copy(configldb, os.path.join(samldbdir,
1295 "%s.ldb"%str(names.configdn).upper()))
1296 os.remove(configldb)
1299 def update_privilege(ref_private_path, cur_private_path):
1300 """Update the privilege database
1302 :param ref_private_path: Path to the private directory of the reference
1304 :param cur_private_path: Path to the private directory of the current
1305 (and to be updated) provision."""
1306 message(SIMPLE, "Copy privilege")
1307 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1308 os.path.join(cur_private_path, "privilege.ldb"))
1311 def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
1312 """Upgrade the SAM DB contents for all the provision partitions
1314 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1316 :param samdb: An LDB object connected to the sam.ldb of the update
1318 :param names: List of key provision parameters
1319 :param highestUSN: The highest USN modified by provision/upgradeprovision
1321 :param schema: A Schema object that represent the schema of the provision
1322 :param prereloadfunc: A function that must be executed just before the reload
1326 message(SIMPLE, "Starting update of samdb")
1327 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1328 schema, highestUSN, prereloadfunc)
1330 message(SIMPLE, "Update of samdb finished")
1333 message(SIMPLE, "Update failed")
1337 def copyxattrs(dir, refdir):
1338 """ Copy owner, groups, extended ACL and NT acls from
1339 a reference dir to a destination dir
1341 Both dir are supposed to hold the same files
1342 :param dir: Destination dir
1343 :param refdir: Reference directory"""
1346 for root, dirs, files in os.walk(dir, topdown=True):
1348 subdir=root[len(dir):]
1349 ref = os.path.join("%s%s" % (refdir, subdir), name)
1350 statsinfo = os.stat(ref)
1351 tgt = os.path.join(root, name)
1354 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1355 # Get the xattr attributes if any
1357 attribute = samba.xattr_native.wrap_getxattr(ref,
1358 xattr.XATTR_NTACL_NAME)
1359 samba.xattr_native.wrap_setxattr(tgt,
1360 xattr.XATTR_NTACL_NAME,
1364 attribute = samba.xattr_native.wrap_getxattr(ref,
1365 "system.posix_acl_access")
1366 samba.xattr_native.wrap_setxattr(tgt,
1367 "system.posix_acl_access",
1372 subdir=root[len(dir):]
1373 ref = os.path.join("%s%s" % (refdir, subdir), name)
1374 statsinfo = os.stat(ref)
1375 tgt = os.path.join(root, name)
1377 os.chown(os.path.join(root, name), statsinfo.st_uid,
1380 attribute = samba.xattr_native.wrap_getxattr(ref,
1381 xattr.XATTR_NTACL_NAME)
1382 samba.xattr_native.wrap_setxattr(tgt,
1383 xattr.XATTR_NTACL_NAME,
1387 attribute = samba.xattr_native.wrap_getxattr(ref,
1388 "system.posix_acl_access")
1389 samba.xattr_native.wrap_setxattr(tgt,
1390 "system.posix_acl_access",
1397 def backup_provision(paths, dir):
1398 """This function backup the provision files so that a rollback
1401 :param paths: Paths to different objects
1402 :param dir: Directory where to store the backup
1405 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1406 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1407 shutil.copy2(paths.samdb, dir)
1408 shutil.copy2(paths.secrets, dir)
1409 shutil.copy2(paths.idmapdb, dir)
1410 shutil.copy2(paths.privilege, dir)
1411 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1412 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1413 shutil.copy2(paths.smbconf, dir)
1414 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1416 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1417 if not os.path.isdir(samldbdir):
1418 samldbdir = paths.private_dir
1419 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1420 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1421 usersldb = os.path.join(paths.private_dir, "users.ldb")
1422 shutil.copy2(schemaldb, dir)
1423 shutil.copy2(usersldb, dir)
1424 shutil.copy2(configldb, dir)
1426 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1431 def sync_calculated_attributes(samdb, names):
1432 """Synchronize attributes used for constructed ones, with the
1433 old constructed that were stored in the database.
1435 This apply for instance to msds-keyversionnumber that was
1436 stored and that is now constructed from replpropertymetadata.
1438 :param samdb: An LDB object attached to the currently upgraded samdb
1439 :param names: Various key parameter about current provision.
1441 listAttrs = ["msDs-KeyVersionNumber"]
1442 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1443 if hash.has_key("msDs-KeyVersionNumber"):
1444 increment_calculated_keyversion_number(samdb, names.rootdn,
1445 hash["msDs-KeyVersionNumber"])
1447 def setup_path(file):
1448 return os.path.join(setup_dir, file)
1450 # Synopsis for updateprovision
1451 # 1) get path related to provision to be update (called current)
1452 # 2) open current provision ldbs
1453 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1455 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1456 # by either upgradeprovision or provision
1457 # 5) creation of a new provision the latest version of provision script
1458 # (called reference)
1459 # 6) get reference provision paths
1460 # 7) open reference provision ldbs
1461 # 8) setup helpers data that will help the update process
1462 # 9) update the privilege ldb by copying the one of referecence provision to
1463 # the current provision
1464 # 10)get the oemInfo field, this field contains information about the different
1465 # provision that have been done
1466 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1467 # integer) or none of this the following things are done
1468 # A) When alpha9 or alphaxx is present
1469 # The base sam.ldb file is updated by looking at the difference between
1470 # referrence one and the current one. Everything is copied with the
1471 # exception of lastProvisionUSN attributes.
1472 # B) Other case (it reflect that that provision was done before alpha9)
1473 # The base sam.ldb of the reference provision is copied over
1474 # the current one, if necessary ldb related to partitions are moved
1476 # The highest used USN is fetched so that changed by upgradeprovision
1477 # usn can be tracked
1478 # 12)A Schema object is created, it will be used to provide a complete
1479 # schema to current provision during update (as the schema of the
1480 # current provision might not be complete and so won't allow some
1481 # object to be created)
1482 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1483 # 14)The secrets db is updated by pull all the difference from the reference
1484 # provision into the current provision
1485 # 15)As the previous step has most probably modified the password stored in
1486 # in secret for the current DC, a new password is generated,
1487 # the kvno is bumped and the entry in samdb is also updated
1488 # 16)For current provision older than alpha9, we must fix the SD a little bit
1489 # administrator to update them because SD used to be generated with the
1490 # system account before alpha9.
1491 # 17)The highest usn modified so far is searched in the database it will be
1492 # the upper limit for usn modified during provision.
1493 # This is done before potential SD recalculation because we do not want
1494 # SD modified during recalculation to be marked as modified during provision
1495 # (and so possibly remplaced at next upgradeprovision)
1496 # 18)Rebuilt SD if the flag indicate to do so
1497 # 19)Check difference between SD of reference provision and those of the
1498 # current provision. The check is done by getting the sddl representation
1499 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1500 # Each part is verified separetly, for dacl and sacl ACL is splited into
1501 # ACEs and each ACE is verified separately (so that a permutation in ACE
1502 # didn't raise as an error).
1503 # 20)The oemInfo field is updated to add information about the fact that the
1504 # provision has been updated by the upgradeprovision version xxx
1505 # (the version is the one obtained when starting samba with the --version
1507 # 21)Check if the current provision has all the settings needed for dynamic
1508 # DNS update to work (that is to say the provision is newer than
1509 # january 2010). If not dns configuration file from reference provision
1510 # are copied in a sub folder and the administrator is invited to
1511 # do what is needed.
1512 # 22)If the lastProvisionUSN attribute was present it is updated to add
1513 # the range of usns modified by the current upgradeprovision
1516 # About updating the sam DB
1517 # The update takes place in update_partition function
1518 # This function read both current and reference provision and list all
1519 # the available DN of objects
1520 # If the string representation of a DN in reference provision is
1521 # equal to the string representation of a DN in current provision
1522 # (without taking care of case) then the object is flaged as being
1523 # present. If the object is not present in current provision the object
1524 # is being flaged as missing in current provision. Object present in current
1525 # provision but not in reference provision are ignored.
1526 # Once the list of objects present and missing is done, the deleted object
1527 # containers are created in the differents partitions (if missing)
1529 # Then the function add_missing_entries is called
1530 # This function will go through the list of missing entries by calling
1531 # add_missing_object for the given object. If this function returns 0
1532 # it means that the object needs some other object in order to be created
1533 # The object is reappended at the end of the list to be created later
1534 # (and preferably after all the needed object have been created)
1535 # The function keeps on looping on the list of object to be created until
1536 # it's empty or that the number of defered creation is equal to the number
1537 # of object that still needs to be created.
1539 # The function add_missing_object will first check if the object can be created.
1540 # That is to say that it didn't depends other not yet created objects
1541 # If requisit can't be fullfilled it exists with 0
1542 # Then it will try to create the missing entry by creating doing
1543 # an ldb_message_diff between the object in the reference provision and
1545 # This resulting object is filtered to remove all the back link attribute
1546 # (ie. memberOf) as they will be created by the other linked object (ie.
1547 # the one with the member attribute)
1548 # All attributes specified in the hashAttrNotCopied associative array are
1549 # also removed it's most of the time generated attributes
1551 # After missing entries have been added the update_partition function will
1552 # take care of object that exist but that need some update.
1553 # In order to do so the function update_present is called with the list
1554 # of object that are present in both provision and that might need an update.
1556 # This function handle first case mismatch so that the DN in the current
1557 # provision have the same case as in reference provision
1559 # It will then construct an associative array consiting of attributes as
1560 # key and invocationid as value( if the originating invocation id is
1561 # different from the invocation id of the current DC the value is -1 instead).
1563 # If the range of provision modified attributes is present, the function will
1564 # use the replMetadataProperty update method which is the following:
1565 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1566 # creationTime, msDs-KeyVersionNumber, oEMInformation
1567 # Check for each attribute if its usn is within one of the modified by
1568 # provision range and if its originating id is the invocation id of the
1569 # current DC, then validate the update from reference to current.
1570 # If not or if there is no replMetatdataProperty for this attribute then we
1572 # Otherwise (case the range of provision modified attribute is not present) it
1573 # use the following process:
1574 # All attributes that need to be added are accepted at the exeption of those
1575 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1576 # correct flags specified.
1577 # For attributes that need to be modified or removed, a check is performed
1578 # in OverwrittenAtt, if the attribute is present and the modification flag
1579 # (remove, delete) is one of those listed for this attribute then modification
1580 # is accepted. For complicated handling of attribute update, the control is passed
1581 # to handle_special_case
1585 if __name__ == '__main__':
1586 global defSDmodified
1587 defSDmodified = False
1588 # From here start the big steps of the program
1589 # 1) First get files paths
1590 paths = get_paths(param, smbconf=smbconf)
1591 paths.setup = setup_dir
1592 # Get ldbs with the system session, it is needed for searching
1593 # provision parameters
1594 session = system_session()
1596 # This variable will hold the last provision USN once if it exists.
1599 ldbs = get_ldbs(paths, creds, session, lp)
1600 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1601 prefix="backupprovision")
1602 backup_provision(paths, backupdir)
1604 ldbs.startTransactions()
1606 # 3) Guess all the needed names (variables in fact) from the current
1608 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1611 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1612 if lastProvisionUSNs is not None:
1614 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1616 # Objects will be created with the admin session
1617 # (not anymore system session)
1618 adm_session = admin_session(lp, str(names.domainsid))
1619 # So we reget handle on objects
1620 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1622 if not sanitychecks(ldbs.sam, names):
1623 message(SIMPLE, "Sanity checks for the upgrade fails, checks messages"
1624 " and correct them before rerunning upgradeprovision")
1627 # Let's see provision parameters
1628 print_provision_key_parameters(names)
1630 # 5) With all this information let's create a fresh new provision used as
1632 message(SIMPLE, "Creating a reference provision")
1633 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1634 prefix="referenceprovision")
1635 newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1640 # We need to get a list of object which SD is directly computed from
1641 # defaultSecurityDescriptor.
1642 # This will allow us to know which object we can rebuild the SD in case
1643 # of change of the parent's SD or of the defaultSD.
1644 # Get file paths of this new provision
1645 newpaths = get_paths(param, targetdir=provisiondir)
1646 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1647 new_ldbs.startTransactions()
1649 # 8) Populate some associative array to ease the update process
1650 # List of attribute which are link and backlink
1651 populate_links(new_ldbs.sam, names.schemadn)
1652 # List of attribute with ASN DN synthax)
1653 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1655 update_privilege(newpaths.private_dir, paths.private_dir)
1657 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1658 # Do some modification on sam.ldb
1659 ldbs.groupedCommit()
1660 new_ldbs.groupedCommit()
1663 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1665 # Starting from alpha9 we can consider that the structure is quite ok
1666 # and that we should do only dela
1667 deltaattr = delta_update_basesamdb(newpaths.samdb,
1675 simple_update_basesamdb(newpaths, paths, names)
1676 ldbs = get_ldbs(paths, creds, session, lp)
1677 removeProvisionUSN(ldbs.sam)
1679 ldbs.startTransactions()
1680 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1681 new_ldbs.startTransactions()
1684 schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn))
1685 # We create a closure that will be invoked just before schema reload
1686 def schemareloadclosure():
1687 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1688 options=["modules:"])
1690 if deltaattr is not None and len(deltaattr) > 1:
1693 deltaattr.remove("dn")
1694 for att in deltaattr:
1695 if att.lower() == "dn":
1697 if deltaattr.get(att) is not None \
1698 and deltaattr.get(att).flags() != FLAG_MOD_ADD:
1700 elif deltaattr.get(att) is None:
1703 message(CHANGE, "Applying delta to @ATTRIBUTES")
1704 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1705 basesam.modify(deltaattr)
1707 message(CHANGE, "Not applying delta to @ATTRIBUTES because "\
1708 "there is not only add")
1711 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1712 schema, schemareloadclosure):
1713 message(SIMPLE, "Rollbacking every changes. Check the reason"
1715 message(SIMPLE, "In any case your system as it was before"
1717 ldbs.groupedRollback()
1718 new_ldbs.groupedRollback()
1719 shutil.rmtree(provisiondir)
1722 # Try to reapply the change also when we do not change the sam
1723 # as the delta_upgrade
1724 schemareloadclosure()
1725 sync_calculated_attributes(ldbs.sam, names)
1726 res = ldbs.sam.search(expression="(samaccountname=dns)",
1727 scope=SCOPE_SUBTREE, attrs=["dn"],
1728 controls=["search_options:1:2"])
1730 message(SIMPLE, "You still have the old dns object for managing"
1731 "dynamic DNS, but you didn't supply --full so "
1732 "correct update can't be done")
1733 ldbs.groupedRollback()
1734 new_ldbs.groupedRollback()
1735 shutil.rmtree(provisiondir)
1738 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1740 res = ldbs.sam.search(expression="(samaccountname=dns)",
1741 scope=SCOPE_SUBTREE, attrs=["dn"],
1742 controls=["search_options:1:2"])
1745 ldbs.sam.delete(res[0]["dn"])
1746 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1747 scope=SCOPE_SUBTREE, attrs=["dn"])
1748 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1749 message(SIMPLE, "IMPORTANT !!! "
1750 "If you were using Dynmaic DNS before you need"
1751 " to update your configuration, so that the "
1752 "tkey-gssapi-credential has the following value:"
1753 "DNS/%s.%s" % (names.netbiosname.lower(), names.realm.lower()))
1755 message(SIMPLE, "Update machine account")
1756 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1758 # 16) SD should be created with admin but as some previous acl were so wrong
1759 # that admin can't modify them we have first to recreate them with the good
1760 # form but with system account and then give the ownership to admin ...
1761 if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1762 message(SIMPLE, "Fixing old povision SD")
1763 fix_partition_sd(ldbs.sam, names)
1764 rebuild_sd(ldbs.sam, names)
1766 # We calculate the max USN before recalculating the SD because we might
1767 # touch object that have been modified after a provision and we do not
1768 # want that the next upgradeprovision thinks that it has a green light
1772 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1774 # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1775 # But in fact we should do it also if one object has its SD modified as
1776 # child might need rebuild
1778 message(SIMPLE, "Updating SD")
1779 ldbs.sam.set_session_info(adm_session)
1780 # Alpha10 was a bit broken still
1781 if re.match(r'.*alpha(\d|10)', str(oem)):
1782 fix_partition_sd(ldbs.sam, names)
1783 rebuild_sd(ldbs.sam, names)
1786 # Now we are quite confident in the recalculate process of the SD, we make
1788 # Also the check must be done in a clever way as for the moment we just
1790 if opts.debugchangesd:
1791 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1794 updateOEMInfo(ldbs.sam, str(names.rootdn))
1796 check_for_DNS(newpaths.private_dir, paths.private_dir)
1798 if lastProvisionUSNs is not None:
1799 update_provision_usn(ldbs.sam, minUSN, maxUSN)
1800 if opts.full and (names.policyid is None or names.policyid_dc is None):
1801 update_policyids(names, ldbs.sam)
1802 if opts.full or opts.resetfileacl:
1804 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1805 except ProvisioningError, e:
1806 message(ERROR, "The policy for domain controller is missing,"
1807 " you should restart upgradeprovision with --full")
1809 message(ERROR, "Setting ACL not supported on your filesystem")
1812 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1813 except ProvisioningError, e:
1814 message(ERROR, "The policy for domain controller is missing,"
1815 " you should restart upgradeprovision with --full")
1816 ldbs.groupedCommit()
1817 new_ldbs.groupedCommit()
1818 message(SIMPLE, "Upgrade finished !")
1819 # remove reference provision now that everything is done !
1820 # So we have reindexed first if need when the merged schema was reloaded
1821 # (as new attributes could have quick in)
1822 # But the second part of the update (when we update existing objects
1823 # can also have an influence on indexing as some attribute might have their
1824 # searchflag modificated
1825 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed after"\
1827 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1828 message(SIMPLE, "Reindexing finished")
1830 shutil.rmtree(provisiondir)
1831 except StandardError, err:
1832 message(ERROR,"A problem has occured when trying to upgrade your provision,"
1833 " a full backup is located at %s" % backupdir)
1834 if opts.debugall or opts.debugchange:
1835 (typ, val, tb) = sys.exc_info()
1836 traceback.print_exception(typ, val, tb)