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 samba import tdb_util
44 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
45 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
46 MessageElement, Message, Dn, LdbError)
47 from samba import param, dsdb, Ldb
48 from samba.common import confirm
49 from samba.provision import (find_provision_key_parameters,
51 get_config_descriptor,
52 get_config_partitions_descriptor,
53 get_config_sites_descriptor,
54 get_config_ntds_quotas_descriptor,
55 get_config_delete_protected1_descriptor,
56 get_config_delete_protected1wd_descriptor,
57 get_config_delete_protected2_descriptor,
58 get_domain_descriptor,
59 get_domain_infrastructure_descriptor,
60 get_domain_builtin_descriptor,
61 get_domain_computers_descriptor,
62 get_domain_users_descriptor,
63 get_domain_controllers_descriptor,
64 get_domain_delete_protected1_descriptor,
65 get_domain_delete_protected2_descriptor,
66 get_dns_partition_descriptor,
67 get_dns_forest_microsoft_dns_descriptor,
68 get_dns_domain_microsoft_dns_descriptor,
69 ProvisioningError, get_last_provision_usn,
70 get_max_usn, update_provision_usn, setup_path)
71 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
72 from samba.dcerpc import security, drsblobs
73 from samba.dcerpc.security import (
74 SECINFO_OWNER, SECINFO_GROUP, SECINFO_DACL, SECINFO_SACL)
75 from samba.ndr import ndr_unpack
76 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
77 get_ldbs, findprovisionrange,
78 usn_in_range, identic_rename, get_diff_sddls,
79 update_secrets, CHANGE, ERROR, SIMPLE,
80 CHANGEALL, GUESS, CHANGESD, PROVISION,
81 updateOEMInfo, getOEMInfo, update_gpo,
82 delta_update_basesamdb, update_policyids,
83 update_machine_account_password,
84 search_constructed_attrs_stored,
85 int64range2str, update_dns_account_password,
86 increment_calculated_keyversion_number,
87 print_provision_ranges)
88 from samba.xattr import copytree_with_xattrs
90 # make sure the script dies immediately when hitting control-C,
91 # rather than raising KeyboardInterrupt. As we do all database
92 # operations using transactions, this is safe.
94 signal.signal(signal.SIGINT, signal.SIG_DFL)
96 replace=2**FLAG_MOD_REPLACE
98 delete=2**FLAG_MOD_DELETE
102 # Will be modified during provision to tell if default sd has been modified
105 #Errors are always logged
107 __docformat__ = "restructuredText"
109 # Attributes that are never copied from the reference provision (even if they
110 # do not exist in the destination object).
111 # This is most probably because they are populated automatcally when object is
113 # This also apply to imported object from reference provision
114 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
115 "parentGUID", "objectCategory", "distinguishedName",
116 "instanceType", "cn",
117 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
118 "unicodePwd", "dBCSPwd", "supplementalCredentials",
119 "gPCUserExtensionNames", "gPCMachineExtensionNames",
120 "maxPwdAge", "secret", "possibleInferiors", "privilege",
121 "sAMAccountType", "oEMInformation", "creationTime" ]
123 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
124 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
126 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
129 attrNotCopied = replAttrNotCopied
130 attrNotCopied.extend(nonreplAttrNotCopied)
131 attrNotCopied.extend(nonDSDBAttrNotCopied)
132 # Usually for an object that already exists we do not overwrite attributes as
133 # they might have been changed for good reasons. Anyway for a few of them it's
134 # mandatory to replace them otherwise the provision will be broken somehow.
135 # But for attribute that are just missing we do not have to specify them as the default
136 # behavior is to add missing attribute
137 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
138 "systemOnly":replace, "searchFlags":replace,
139 "mayContain":replace, "systemFlags":replace+add,
140 "description":replace, "operatingSystemVersion":replace,
141 "adminPropertyPages":replace, "groupType":replace,
142 "wellKnownObjects":replace, "privilege":never,
143 "defaultSecurityDescriptor": replace,
144 "rIDAvailablePool": never,
145 "versionNumber" : add,
146 "rIDNextRID": add, "rIDUsedPool": never,
147 "defaultSecurityDescriptor": replace + add,
148 "isMemberOfPartialAttributeSet": delete,
149 "attributeDisplayNames": replace + add,
150 "versionNumber": add}
152 dnNotToRecalculate = []
155 forwardlinked = set()
158 def define_what_to_log(opts):
162 if opts.debugchangesd:
163 what = what | CHANGESD
166 if opts.debugprovision:
167 what = what | PROVISION
169 what = what | CHANGEALL
173 parser = optparse.OptionParser("provision [options]")
174 sambaopts = options.SambaOptions(parser)
175 parser.add_option_group(sambaopts)
176 parser.add_option_group(options.VersionOptions(parser))
177 credopts = options.CredentialsOptions(parser)
178 parser.add_option_group(credopts)
179 parser.add_option("--setupdir", type="string", metavar="DIR",
180 help="directory with setup files")
181 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
182 parser.add_option("--debugguess", action="store_true",
183 help="Print information on which values are guessed")
184 parser.add_option("--debugchange", action="store_true",
185 help="Print information on what is different but won't be changed")
186 parser.add_option("--debugchangesd", action="store_true",
187 help="Print security descriptor differences")
188 parser.add_option("--debugall", action="store_true",
189 help="Print all available information (very verbose)")
190 parser.add_option("--db_backup_only", action="store_true",
191 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
192 parser.add_option("--full", action="store_true",
193 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
194 parser.add_option("--very-old-pre-alpha9", action="store_true",
195 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
197 opts = parser.parse_args()[0]
199 handler = logging.StreamHandler(sys.stdout)
200 upgrade_logger = logging.getLogger("upgradeprovision")
201 upgrade_logger.setLevel(logging.INFO)
203 upgrade_logger.addHandler(handler)
205 provision_logger = logging.getLogger("provision")
206 provision_logger.addHandler(handler)
208 whatToLog = define_what_to_log(opts)
210 def message(what, text):
211 """Print a message if this message type has been selected to be printed
213 :param what: Category of the message
214 :param text: Message to print """
215 if (whatToLog & what) or what <= 0:
216 upgrade_logger.info("%s", text)
218 if len(sys.argv) == 1:
219 opts.interactive = True
220 lp = sambaopts.get_loadparm()
221 smbconf = lp.configfile
223 creds = credopts.get_credentials(lp)
224 creds.set_kerberos_state(DONT_USE_KERBEROS)
228 def check_for_DNS(refprivate, private, dns_backend):
229 """Check if the provision has already the requirement for dynamic dns
231 :param refprivate: The path to the private directory of the reference
233 :param private: The path to the private directory of the upgraded
236 spnfile = "%s/spn_update_list" % private
237 dnsfile = "%s/dns_update_list" % private
239 if not os.path.exists(spnfile):
240 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
242 if not os.path.exists(dnsfile):
243 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
245 if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
248 namedfile = lp.get("dnsupdate:path")
250 namedfile = "%s/named.conf.update" % private
251 if not os.path.exists(namedfile):
252 destdir = "%s/new_dns" % private
253 dnsdir = "%s/dns" % private
255 if not os.path.exists(destdir):
257 if not os.path.exists(dnsdir):
259 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
260 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
261 message(SIMPLE, "It seems that your provision did not integrate "
262 "new rules for dynamic dns update of domain related entries")
263 message(SIMPLE, "A copy of the new bind configuration files and "
264 "template has been put in %s, you should read them and "
265 "configure dynamic dns updates" % destdir)
268 def populate_links(samdb, schemadn):
269 """Populate an array with all the back linked attributes
271 This attributes that are modified automaticaly when
272 front attibutes are changed
274 :param samdb: A LDB object for sam.ldb file
275 :param schemadn: DN of the schema for the partition"""
276 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
277 backlinked.extend(linkedAttHash.values())
278 for t in linkedAttHash.keys():
281 def isReplicated(att):
282 """ Indicate if the attribute is replicated or not
284 :param att: Name of the attribute to be tested
285 :return: True is the attribute is replicated, False otherwise
288 return (att not in not_replicated)
290 def populateNotReplicated(samdb, schemadn):
291 """Populate an array with all the attributes that are not replicated
293 :param samdb: A LDB object for sam.ldb file
294 :param schemadn: DN of the schema for the partition"""
295 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
296 str(schemadn)), scope=SCOPE_SUBTREE,
297 attrs=["lDAPDisplayName"])
299 not_replicated.append(str(elem["lDAPDisplayName"]))
302 def populate_dnsyntax(samdb, schemadn):
303 """Populate an array with all the attributes that have DN synthax
306 :param samdb: A LDB object for sam.ldb file
307 :param schemadn: DN of the schema for the partition"""
308 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
309 str(schemadn)), scope=SCOPE_SUBTREE,
310 attrs=["lDAPDisplayName"])
312 dn_syntax_att.append(elem["lDAPDisplayName"])
315 def sanitychecks(samdb, names):
316 """Make some checks before trying to update
318 :param samdb: An LDB object opened on sam.ldb
319 :param names: list of key provision parameters
320 :return: Status of check (1 for Ok, 0 for not Ok) """
321 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
322 scope=SCOPE_SUBTREE, attrs=["dn"],
323 controls=["search_options:1:2"])
325 print "No DC found. Your provision is most probably broken!"
328 print "Found %d domain controllers. For the moment " \
329 "upgradeprovision is not able to handle an upgrade on a " \
330 "domain with more than one DC. Please demote the other " \
331 "DC(s) before upgrading" % len(res)
337 def print_provision_key_parameters(names):
338 """Do a a pretty print of provision parameters
340 :param names: list of key provision parameters """
341 message(GUESS, "rootdn :" + str(names.rootdn))
342 message(GUESS, "configdn :" + str(names.configdn))
343 message(GUESS, "schemadn :" + str(names.schemadn))
344 message(GUESS, "serverdn :" + str(names.serverdn))
345 message(GUESS, "netbiosname :" + names.netbiosname)
346 message(GUESS, "defaultsite :" + names.sitename)
347 message(GUESS, "dnsdomain :" + names.dnsdomain)
348 message(GUESS, "hostname :" + names.hostname)
349 message(GUESS, "domain :" + names.domain)
350 message(GUESS, "realm :" + names.realm)
351 message(GUESS, "invocationid:" + names.invocation)
352 message(GUESS, "policyguid :" + names.policyid)
353 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
354 message(GUESS, "domainsid :" + str(names.domainsid))
355 message(GUESS, "domainguid :" + names.domainguid)
356 message(GUESS, "ntdsguid :" + names.ntdsguid)
357 message(GUESS, "domainlevel :" + str(names.domainlevel))
360 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
361 """Define more complicate update rules for some attributes
363 :param att: The attribute to be updated
364 :param delta: A messageElement object that correspond to the difference
365 between the updated object and the reference one
366 :param new: The reference object
367 :param old: The Updated object
368 :param useReplMetadata: A boolean that indicate if the update process
369 use replPropertyMetaData to decide what has to be updated.
370 :param basedn: The base DN of the provision
371 :param aldb: An ldb object used to build DN
372 :return: True to indicate that the attribute should be kept, False for
375 # We do most of the special case handle if we do not have the
376 # highest usn as otherwise the replPropertyMetaData will guide us more
378 if not useReplMetadata:
379 flag = delta.get(att).flags()
380 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
381 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
382 "CN=Services,CN=Configuration,%s" % basedn)
385 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
386 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
388 message(SIMPLE, "We suggest that you change the userAccountControl"
389 " for user Administrator from value %d to %d" %
390 (int(str(old[0][att])), int(str(new[0][att]))))
392 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
393 if (long(str(old[0][att])) == 0):
394 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
397 if (att == "member" and flag == FLAG_MOD_REPLACE):
401 for elem in old[0][att]:
402 hash[str(elem).lower()]=1
403 newval.append(str(elem))
405 for elem in new[0][att]:
406 if not hash.has_key(str(elem).lower()):
408 newval.append(str(elem))
410 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
415 if (att in ("gPLink", "gPCFileSysPath") and
416 flag == FLAG_MOD_REPLACE and
417 str(new[0].dn).lower() == str(old[0].dn).lower()):
421 if att == "forceLogoff":
422 ref=0x8000000000000000
423 oldval=int(old[0][att][0])
424 newval=int(new[0][att][0])
425 ref == old and ref == abs(new)
428 if att in ("adminDisplayName", "adminDescription"):
431 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
432 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
435 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
436 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
439 if (str(old[0].dn) == "%s" % (str(names.rootdn))
440 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
442 #Allow to change revision of ForestUpdates objects
443 if (att == "revision" or att == "objectVersion"):
444 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
446 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
449 # This is a bit of special animal as we might have added
450 # already SPN entries to the list that has to be modified
451 # So we go in detail to try to find out what has to be added ...
452 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
456 for elem in old[0][att]:
458 newval.append(str(elem))
460 for elem in new[0][att]:
461 if not hash.has_key(str(elem)):
463 newval.append(str(elem))
465 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
472 def dump_denied_change(dn, att, flagtxt, current, reference):
473 """Print detailed information about why a change is denied
475 :param dn: DN of the object which attribute is denied
476 :param att: Attribute that was supposed to be upgraded
477 :param flagtxt: Type of the update that should be performed
478 (add, change, remove, ...)
479 :param current: Value(s) of the current attribute
480 :param reference: Value(s) of the reference attribute"""
482 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
483 + " must not be changed/removed. Discarding the change")
484 if att == "objectSid" :
485 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
486 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
487 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
488 message(CHANGE, "old : %s" % int64range2str(current[0]))
489 message(CHANGE, "new : %s" % int64range2str(reference[0]))
492 for e in range(0, len(current)):
493 message(CHANGE, "old %d : %s" % (i, str(current[e])))
495 if reference is not None:
497 for e in range(0, len(reference)):
498 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
501 def handle_special_add(samdb, dn, names):
502 """Handle special operation (like remove) on some object needed during
505 This is mostly due to wrong creation of the object in previous provision.
506 :param samdb: An Ldb object representing the SAM database
507 :param dn: DN of the object to inspect
508 :param names: list of key provision parameters
512 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
514 #This entry was misplaced lets remove it if it exists
515 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
518 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
520 #This entry was misplaced lets remove it if it exists
521 dntoremove = "CN=Certificate Service DCOM Access,"\
522 "CN=Users, %s" % names.rootdn
524 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
526 #This entry was misplaced lets remove it if it exists
527 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
529 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
531 #This entry was misplaced lets remove it if it exists
532 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
534 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
535 "CN=Configuration,%s" % names.rootdn)
537 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
538 "CN=WellKnown Security Principals,"
539 "CN=Configuration,%s" % names.rootdn)
541 res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
542 base=str(names.rootdn),
543 scope=SCOPE_SUBTREE, attrs=["dn"],
544 controls=["search_options:1:2"])
546 res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
547 base=str(names.rootdn),
548 scope=SCOPE_SUBTREE, attrs=["dn"],
549 controls=["search_options:1:2"])
551 if len(res) > 0 and len(res2) == 0:
552 message(CHANGE, "Existing object %s must be replaced by %s. "
553 "Renaming old object" % (str(oldDn), str(dn)))
554 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
558 if dntoremove is not None:
559 res = samdb.search(expression="(cn=RID Set)",
560 base=str(names.rootdn),
561 scope=SCOPE_SUBTREE, attrs=["dn"],
562 controls=["search_options:1:2"])
566 res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
567 base=str(names.rootdn),
568 scope=SCOPE_SUBTREE, attrs=["dn"],
569 controls=["search_options:1:2"])
571 message(CHANGE, "Existing object %s must be replaced by %s. "
572 "Removing old object" % (dntoremove, str(dn)))
573 samdb.delete(res[0]["dn"])
579 def check_dn_nottobecreated(hash, index, listdn):
580 """Check if one of the DN present in the list has a creation order
581 greater than the current.
583 Hash is indexed by dn to be created, with each key
584 is associated the creation order.
586 First dn to be created has the creation order 0, second has 1, ...
587 Index contain the current creation order
589 :param hash: Hash holding the different DN of the object to be
591 :param index: Current creation order
592 :param listdn: List of DNs on which the current DN depends on
593 :return: None if the current object do not depend on other
594 object or if all object have been created before."""
598 key = str(dn).lower()
599 if hash.has_key(key) and hash[key] > index:
605 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
606 """Add a new object if the dependencies are satisfied
608 The function add the object if the object on which it depends are already
611 :param ref_samdb: Ldb object representing the SAM db of the reference
613 :param samdb: Ldb object representing the SAM db of the upgraded
615 :param dn: DN of the object to be added
616 :param names: List of key provision parameters
617 :param basedn: DN of the partition to be updated
618 :param hash: Hash holding the different DN of the object to be
620 :param index: Current creation order
621 :return: True if the object was created False otherwise"""
623 ret = handle_special_add(samdb, dn, names)
632 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
633 base=basedn, scope=SCOPE_SUBTREE,
634 controls=["search_options:1:2"])
636 delta = samdb.msg_diff(empty, reference[0])
640 if str(reference[0].get("cn")) == "RID Set":
641 for klass in reference[0].get("objectClass"):
642 if str(klass).lower() == "ridset":
645 if delta.get("objectSid"):
646 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
647 m = re.match(r".*-(\d+)$", sid)
648 if m and int(m.group(1))>999:
649 delta.remove("objectSid")
650 for att in attrNotCopied:
652 for att in backlinked:
654 depend_on_yettobecreated = None
655 for att in dn_syntax_att:
656 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
658 if depend_on_yet_tobecreated is not None:
659 message(CHANGE, "Object %s depends on %s in attribute %s. "
660 "Delaying the creation" % (dn,
661 depend_on_yet_tobecreated, att))
666 message(CHANGE,"Object %s will be added" % dn)
667 samdb.add(delta, ["relax:0", "provision:0"])
669 message(CHANGE,"Object %s was skipped" % dn)
673 def gen_dn_index_hash(listMissing):
674 """Generate a hash associating the DN to its creation order
676 :param listMissing: List of DN
677 :return: Hash with DN as keys and creation order as values"""
679 for i in range(0, len(listMissing)):
680 hash[str(listMissing[i]).lower()] = i
683 def add_deletedobj_containers(ref_samdb, samdb, names):
684 """Add the object containter: CN=Deleted Objects
686 This function create the container for each partition that need one and
687 then reference the object into the root of the partition
689 :param ref_samdb: Ldb object representing the SAM db of the reference
691 :param samdb: Ldb object representing the SAM db of the upgraded provision
692 :param names: List of key provision parameters"""
695 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
696 partitions = [str(names.rootdn), str(names.configdn)]
697 for part in partitions:
698 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
699 base=part, scope=SCOPE_SUBTREE,
701 controls=["show_deleted:0",
703 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
704 base=part, scope=SCOPE_SUBTREE,
706 controls=["show_deleted:0",
708 if len(ref_delObjCnt) > len(delObjCnt):
709 reference = ref_samdb.search(expression="cn=Deleted Objects",
710 base=part, scope=SCOPE_SUBTREE,
711 controls=["show_deleted:0",
714 delta = samdb.msg_diff(empty, reference[0])
716 delta.dn = Dn(samdb, str(reference[0]["dn"]))
717 for att in attrNotCopied:
720 modcontrols = ["relax:0", "provision:0"]
721 samdb.add(delta, modcontrols)
724 res = samdb.search(expression="(objectClass=*)", base=part,
726 attrs=["dn", "wellKnownObjects"])
728 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
732 wko = res[0]["wellKnownObjects"]
734 # The wellKnownObject that we want to add.
736 if str(o) == targetWKO:
738 listwko.append(str(o))
741 listwko.append(targetWKO)
744 delta.dn = Dn(samdb, str(res[0]["dn"]))
745 delta["wellKnownObjects"] = MessageElement(listwko,
750 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
751 """Add the missing object whose DN is the list
753 The function add the object if the objects on which it depends are
756 :param ref_samdb: Ldb object representing the SAM db of the reference
758 :param samdb: Ldb object representing the SAM db of the upgraded
760 :param dn: DN of the object to be added
761 :param names: List of key provision parameters
762 :param basedn: DN of the partition to be updated
763 :param list: List of DN to be added in the upgraded provision"""
768 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
770 listMissing = listDefered
772 hashMissing = gen_dn_index_hash(listMissing)
773 for dn in listMissing:
774 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
778 # DN can't be created because it depends on some
779 # other DN in the list
780 listDefered.append(dn)
782 if len(listDefered) != 0:
783 raise ProvisioningError("Unable to insert missing elements: "
784 "circular references")
786 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
787 """This function handle updates on links
789 :param samdb: An LDB object pointing to the updated provision
790 :param att: Attribute to update
791 :param basedn: The root DN of the provision
792 :param dn: The DN of the inspected object
793 :param value: The value of the attribute
794 :param ref_value: The value of this attribute in the reference provision
795 :param delta: The MessageElement object that will be applied for
796 transforming the current provision"""
798 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
807 newlinklist.append(str(v))
811 # for w2k domain level the reveal won't reveal anything ...
812 # it means that we can readd links that were removed on purpose ...
813 # Also this function in fact just accept add not removal
815 for e in res[0][att]:
816 if not hash.has_key(e):
817 # We put in the blacklist all the element that are in the "revealed"
818 # result and not in the "standard" result
819 # This element are links that were removed before and so that
820 # we don't wan't to readd
824 if not blacklist.has_key(e) and not hash.has_key(e):
825 newlinklist.append(str(e))
828 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
835 msg_elt_flag_strs = {
836 ldb.FLAG_MOD_ADD: "MOD_ADD",
837 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
838 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
840 def checkKeepAttributeOldMtd(delta, att, reference, current,
842 """ Check if we should keep the attribute modification or not.
843 This function didn't use replicationMetadata to take a decision.
845 :param delta: A message diff object
846 :param att: An attribute
847 :param reference: A message object for the current entry comming from
848 the reference provision.
849 :param current: A message object for the current entry commin from
850 the current provision.
851 :param basedn: The DN of the partition
852 :param samdb: A ldb connection to the sam database of the current provision.
854 :return: The modified message diff.
856 # Old school way of handling things for pre alpha12 upgrade
862 for att in list(delta):
863 msgElt = delta.get(att)
865 if att == "nTSecurityDescriptor":
873 if not hashOverwrittenAtt.has_key(att):
874 if msgElt.flags() != FLAG_MOD_ADD:
875 if not handle_special_case(att, delta, reference, current,
876 False, basedn, samdb):
877 if opts.debugchange or opts.debugall:
879 dump_denied_change(dn, att,
880 msg_elt_flag_strs[msgElt.flags()],
881 current[0][att], reference[0][att])
883 dump_denied_change(dn, att,
884 msg_elt_flag_strs[msgElt.flags()],
885 current[0][att], None)
889 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
891 elif hashOverwrittenAtt.get(att) == never:
897 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
898 hash_attr_usn, basedn, usns, samdb):
899 """ Check if we should keep the attribute modification or not
901 :param delta: A message diff object
902 :param att: An attribute
903 :param message: A function to print messages
904 :param reference: A message object for the current entry comming from
905 the reference provision.
906 :param current: A message object for the current entry commin from
907 the current provision.
908 :param hash_attr_usn: A dictionnary with attribute name as keys,
909 USN and invocation id as values.
910 :param basedn: The DN of the partition
911 :param usns: A dictionnary with invocation ID as keys and USN ranges
913 :param samdb: A ldb object pointing to the sam DB
915 :return: The modified message diff.
922 for att in list(delta):
923 if att in ["dn", "objectSid"]:
927 # We have updated by provision usn information so let's exploit
928 # replMetadataProperties
929 if att in forwardlinked:
930 curval = current[0].get(att, ())
931 refval = reference[0].get(att, ())
932 delta = handle_links(samdb, att, basedn, current[0]["dn"],
933 curval, refval, delta)
937 if isFirst and len(list(delta)) > 1:
939 txt = "%s\n" % (str(dn))
941 if handle_special_case(att, delta, reference, current, True, None, None):
942 # This attribute is "complicated" to handle and handling
943 # was done in handle_special_case
947 if hash_attr_usn.get(att):
948 [attrUSN, attInvId] = hash_attr_usn.get(att)
951 # If it's a replicated attribute and we don't have any USN
952 # information about it. It means that we never saw it before
954 # If it is a replicated attribute but we are not master on it
955 # (ie. not initially added in the provision we masterize).
957 if isReplicated(att):
960 message(CHANGE, "Non replicated attribute %s changed" % att)
963 if att == "nTSecurityDescriptor":
964 cursd = ndr_unpack(security.descriptor,
965 str(current[0]["nTSecurityDescriptor"]))
966 cursddl = cursd.as_sddl(names.domainsid)
967 refsd = ndr_unpack(security.descriptor,
968 str(reference[0]["nTSecurityDescriptor"]))
969 refsddl = refsd.as_sddl(names.domainsid)
971 diff = get_diff_sddls(refsddl, cursddl)
973 # FIXME find a way to have it only with huge huge verbose mode
974 # message(CHANGE, "%ssd are identical" % txt)
980 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
983 message(CHANGESD, "But the SD has been changed by someonelse "
984 "so it's impossible to know if the difference"
985 " cames from the modification or from a previous bug")
986 dnNotToRecalculate.append(str(dn))
988 dnToRecalculate.append(str(dn))
992 # This attribute was last modified by another DC forget
994 message(CHANGE, "%sAttribute: %s has been "
995 "created/modified/deleted by another DC. "
996 "Doing nothing" % (txt, att))
1000 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
1001 message(CHANGE, "%sAttribute: %s was not "
1002 "created/modified/deleted during a "
1003 "provision or upgradeprovision. Current "
1004 "usn: %d. Doing nothing" % (txt, att,
1010 if att == "defaultSecurityDescriptor":
1011 defSDmodified = True
1013 message(CHANGE, "%sAttribute: %s will be modified"
1014 "/deleted it was last modified "
1015 "during a provision. Current usn: "
1016 "%d" % (txt, att, attrUSN))
1019 message(CHANGE, "%sAttribute: %s will be added because "
1020 "it did not exist before" % (txt, att))
1026 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
1027 """ This function updates the object that are already present in the
1030 :param ref_samdb: An LDB object pointing to the reference provision
1031 :param samdb: An LDB object pointing to the updated provision
1032 :param basedn: A string with the value of the base DN for the provision
1033 (ie. DC=foo, DC=bar)
1034 :param listPresent: A list of object that is present in the provision
1035 :param usns: A list of USN range modified by previous provision and
1036 upgradeprovision grouped by invocation ID
1039 # This hash is meant to speedup lookup of attribute name from an oid,
1040 # it's for the replPropertyMetaData handling
1042 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
1043 controls=["search_options:1:2"], attrs=["attributeID",
1047 strDisplay = str(e.get("lDAPDisplayName"))
1048 hash_oid_name[str(e.get("attributeID"))] = strDisplay
1050 msg = "Unable to insert missing elements: circular references"
1051 raise ProvisioningError(msg)
1054 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1055 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
1056 if usns is not None:
1057 message(CHANGE, "Using replPropertyMetadata for change selection")
1058 for dn in listPresent:
1059 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1060 scope=SCOPE_SUBTREE,
1062 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1063 scope=SCOPE_SUBTREE, controls=controls)
1066 (str(current[0].dn) != str(reference[0].dn)) and
1067 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1069 message(CHANGE, "Names are the same except for the case. "
1070 "Renaming %s to %s" % (str(current[0].dn),
1071 str(reference[0].dn)))
1072 identic_rename(samdb, reference[0].dn)
1073 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1074 scope=SCOPE_SUBTREE,
1077 delta = samdb.msg_diff(current[0], reference[0])
1079 for att in backlinked:
1082 for att in attrNotCopied:
1085 delta.remove("name")
1087 nb_items = len(list(delta))
1092 if nb_items > 1 and usns is not None:
1093 # Fetch the replPropertyMetaData
1094 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1095 scope=SCOPE_SUBTREE, controls=controls,
1096 attrs=["replPropertyMetaData"])
1097 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1098 str(res[0]["replPropertyMetaData"])).ctr
1102 # We put in this hash only modification
1103 # made on the current host
1104 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1105 if str(o.originating_invocation_id) in usns.keys():
1106 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1108 hash_attr_usn[att] = [-1, None]
1110 if usns is not None:
1111 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1112 current, hash_attr_usn,
1113 basedn, usns, samdb)
1115 delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1121 # Skip dn as the value is not really changed ...
1122 attributes=", ".join(delta.keys()[1:])
1124 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1125 # Let's try to reduce as much as possible the use of relax control
1126 for attr in delta.keys():
1127 if attr.lower() in relaxedatt:
1128 modcontrols = ["relax:0", "provision:0"]
1129 message(CHANGE, "%s is different from the reference one, changed"
1130 " attributes: %s\n" % (dn, attributes))
1132 samdb.modify(delta, modcontrols)
1135 def reload_full_schema(samdb, names):
1136 """Load the updated schema with all the new and existing classes
1139 :param samdb: An LDB object connected to the sam.ldb of the update
1141 :param names: List of key provision parameters
1144 schemadn = str(names.schemadn)
1145 current = samdb.search(expression="objectClass=*", base=schemadn,
1146 scope=SCOPE_SUBTREE)
1151 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1153 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1154 prefixmap_data = b64encode(prefixmap_data)
1156 # We don't actually add this ldif, just parse it
1157 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1159 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1162 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1163 """Check differences between the reference provision and the upgraded one.
1165 It looks for all objects which base DN is name.
1167 This function will also add the missing object and update existing object
1168 to add or remove attributes that were missing.
1170 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1172 :param samdb: An LDB object connected to the sam.ldb of the update
1174 :param basedn: String value of the DN of the partition
1175 :param names: List of key provision parameters
1176 :param schema: A Schema object
1177 :param provisionUSNs: A dictionnary with range of USN modified during provision
1178 or upgradeprovision. Ranges are grouped by invocationID.
1179 :param prereloadfunc: A function that must be executed just before the reload
1190 # Connect to the reference provision and get all the attribute in the
1191 # partition referred by name
1192 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1193 scope=SCOPE_SUBTREE, attrs=["dn"],
1194 controls=["search_options:1:2"])
1196 current = samdb.search(expression="objectClass=*", base=basedn,
1197 scope=SCOPE_SUBTREE, attrs=["dn"],
1198 controls=["search_options:1:2"])
1199 # Create a hash for speeding the search of new object
1200 for i in range(0, len(reference)):
1201 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1203 # Create a hash for speeding the search of existing object in the
1205 for i in range(0, len(current)):
1206 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1209 for k in hash_new.keys():
1210 if not hash.has_key(k):
1211 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1212 listMissing.append(hash_new[k])
1214 listPresent.append(hash_new[k])
1216 # Sort the missing object in order to have object of the lowest level
1217 # first (which can be containers for higher level objects)
1218 listMissing.sort(dn_sort)
1219 listPresent.sort(dn_sort)
1221 # The following lines is to load the up to
1222 # date schema into our current LDB
1223 # a complete schema is needed as the insertion of attributes
1224 # and class is done against it
1225 # and the schema is self validated
1226 samdb.set_schema(schema)
1228 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1229 add_deletedobj_containers(ref_samdb, samdb, names)
1231 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1234 message(SIMPLE, "Reloading a merged schema, which might trigger "
1235 "reindexing so please be patient")
1236 reload_full_schema(samdb, names)
1237 message(SIMPLE, "Schema reloaded!")
1239 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1241 message(SIMPLE, "There are %d changed objects" % (changed))
1244 except StandardError, err:
1245 message(ERROR, "Exception during upgrade of samdb:")
1246 (typ, val, tb) = sys.exc_info()
1247 traceback.print_exception(typ, val, tb)
1251 def check_updated_sd(ref_sam, cur_sam, names):
1252 """Check if the security descriptor in the upgraded provision are the same
1255 :param ref_sam: A LDB object connected to the sam.ldb file used as
1256 the reference provision
1257 :param cur_sam: A LDB object connected to the sam.ldb file used as
1259 :param names: List of key provision parameters"""
1260 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1261 scope=SCOPE_SUBTREE,
1262 attrs=["dn", "nTSecurityDescriptor"],
1263 controls=["search_options:1:2"])
1264 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1265 scope=SCOPE_SUBTREE,
1266 attrs=["dn", "nTSecurityDescriptor"],
1267 controls=["search_options:1:2"])
1269 for i in range(0, len(reference)):
1270 refsd = ndr_unpack(security.descriptor,
1271 str(reference[i]["nTSecurityDescriptor"]))
1272 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1275 for i in range(0, len(current)):
1276 key = str(current[i]["dn"]).lower()
1277 if hash.has_key(key):
1278 cursd = ndr_unpack(security.descriptor,
1279 str(current[i]["nTSecurityDescriptor"]))
1280 sddl = cursd.as_sddl(names.domainsid)
1281 if sddl != hash[key]:
1282 txt = get_diff_sddls(hash[key], sddl, False)
1284 message(CHANGESD, "On object %s ACL is different"
1285 " \n%s" % (current[i]["dn"], txt))
1289 def fix_wellknown_sd(samdb, names):
1290 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1291 This is needed because some provision use to have broken SD on containers
1293 :param samdb: An LDB object pointing to the sam of the current provision
1294 :param names: A list of key provision parameters
1296 alwaysRecalculate = False
1297 if len(dnToRecalculate) == 0 and len(dnNotToRecalculate) == 0:
1298 alwaysRecalculate = True
1300 list_wellknown_dns = []
1302 # Then subcontainers
1304 ("%s" % str(names.domaindn), get_domain_descriptor),
1305 ("CN=LostAndFound,%s" % str(names.domaindn), get_domain_delete_protected2_descriptor),
1306 ("CN=System,%s" % str(names.domaindn), get_domain_delete_protected1_descriptor),
1307 ("CN=Infrastructure,%s" % str(names.domaindn), get_domain_infrastructure_descriptor),
1308 ("CN=Builtin,%s" % str(names.domaindn), get_domain_builtin_descriptor),
1309 ("CN=Computers,%s" % str(names.domaindn), get_domain_computers_descriptor),
1310 ("CN=Users,%s" % str(names.domaindn), get_domain_users_descriptor),
1311 ("OU=Domain Controllers,%s" % str(names.domaindn), get_domain_controllers_descriptor),
1312 ("CN=MicrosoftDNS,CN=System,%s" % str(names.domaindn), get_dns_domain_microsoft_dns_descriptor),
1314 ("%s" % str(names.configdn), get_config_descriptor),
1315 ("CN=NTDS Quotas,%s" % str(names.configdn), get_config_ntds_quotas_descriptor),
1316 ("CN=LostAndFoundConfig,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1317 ("CN=Services,%s" % str(names.configdn), get_config_delete_protected1_descriptor),
1318 ("CN=Physical Locations,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1319 ("CN=WellKnown Security Principals,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1320 ("CN=ForestUpdates,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1321 ("CN=DisplaySpecifiers,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
1322 ("CN=Extended-Rights,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
1323 ("CN=Partitions,%s" % str(names.configdn), get_config_partitions_descriptor),
1324 ("CN=Sites,%s" % str(names.configdn), get_config_sites_descriptor),
1326 ("%s" % str(names.schemadn), get_schema_descriptor),
1329 if names.dnsforestdn is not None:
1330 c = ("%s" % str(names.dnsforestdn), get_dns_partition_descriptor)
1331 subcontainers.append(c)
1332 c = ("CN=Infrastructure,%s" % str(names.dnsforestdn),
1333 get_domain_delete_protected1_descriptor)
1334 subcontainers.append(c)
1335 c = ("CN=LostAndFound,%s" % str(names.dnsforestdn),
1336 get_domain_delete_protected2_descriptor)
1337 subcontainers.append(c)
1338 c = ("CN=MicrosoftDNS,%s" % str(names.dnsforestdn),
1339 get_dns_forest_microsoft_dns_descriptor)
1340 subcontainers.append(c)
1342 if names.dnsdomaindn is not None:
1343 c = ("%s" % str(names.dnsdomaindn), get_dns_partition_descriptor)
1344 subcontainers.append(c)
1345 c = ("CN=Infrastructure,%s" % str(names.dnsdomaindn),
1346 get_domain_delete_protected1_descriptor)
1347 subcontainers.append(c)
1348 c = ("CN=LostAndFound,%s" % str(names.dnsdomaindn),
1349 get_domain_delete_protected2_descriptor)
1350 subcontainers.append(c)
1351 c = ("CN=MicrosoftDNS,%s" % str(names.dnsdomaindn),
1352 get_dns_domain_microsoft_dns_descriptor)
1353 subcontainers.append(c)
1355 for [dn, descriptor_fn] in subcontainers:
1356 list_wellknown_dns.append(dn)
1357 if alwaysRecalculate or dn in dnToRecalculate:
1359 delta.dn = Dn(samdb, str(dn))
1360 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1361 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1362 "nTSecurityDescriptor" )
1364 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1366 return list_wellknown_dns
1368 def rebuild_sd(samdb, names):
1369 """Rebuild security descriptor of the current provision from scratch
1371 During the different pre release of samba4 security descriptors (SD)
1372 were notarly broken (up to alpha11 included)
1373 This function allow to get them back in order, this function make the
1374 assumption that nobody has modified manualy an SD
1375 and so SD can be safely recalculated from scratch to get them right.
1377 :param names: List of key provision parameters"""
1379 listWellknown = fix_wellknown_sd(samdb, names)
1382 if len(dnToRecalculate) == 0:
1383 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1384 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1385 controls=["search_options:1:2"])
1387 hash[str(obj["dn"])] = obj["whenCreated"]
1389 for dn in dnToRecalculate:
1390 if hash.has_key(dn):
1392 # fetch each dn to recalculate and their child within the same partition
1393 res = samdb.search(expression="objectClass=*", base=dn,
1394 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"])
1396 hash[str(obj["dn"])] = obj["whenCreated"]
1398 listKeys = list(set(hash.keys()))
1399 listKeys.sort(dn_sort)
1401 if len(dnToRecalculate) != 0:
1402 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1403 ", recalculating %d due to inheritance"
1404 % (len(dnToRecalculate), len(listKeys)))
1406 for key in listKeys:
1407 if key in listWellknown:
1409 if key in dnNotToRecalculate:
1412 delta.dn = Dn(samdb, key)
1413 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1415 descr = get_empty_descriptor(names.domainsid)
1416 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1417 "nTSecurityDescriptor")
1418 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1420 samdb.transaction_cancel()
1421 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1423 attrs=["nTSecurityDescriptor"],
1424 controls=["sd_flags:1:%d" % sd_flags])
1425 badsd = ndr_unpack(security.descriptor,
1426 str(res[0]["nTSecurityDescriptor"]))
1427 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1430 def hasATProvision(samdb):
1431 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1435 if entry is not None and len(entry) == 1:
1440 def removeProvisionUSN(samdb):
1441 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1442 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1446 empty.dn = entry[0].dn
1447 delta = samdb.msg_diff(entry[0], empty)
1449 delta.dn = entry[0].dn
1452 def remove_stored_generated_attrs(paths, creds, session, lp):
1453 """Remove previously stored constructed attributes
1455 :param paths: List of paths for different provision objects
1456 from the upgraded provision
1457 :param creds: A credential object
1458 :param session: A session object
1459 :param lp: A line parser object
1460 :return: An associative array whose key are the different constructed
1461 attributes and the value the dn where this attributes were found.
1465 def simple_update_basesamdb(newpaths, paths, names):
1466 """Update the provision container db: sam.ldb
1467 This function is aimed at very old provision (before alpha9)
1469 :param newpaths: List of paths for different provision objects
1470 from the reference provision
1471 :param paths: List of paths for different provision objects
1472 from the upgraded provision
1473 :param names: List of key provision parameters"""
1475 message(SIMPLE, "Copy samdb")
1476 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1478 message(SIMPLE, "Update partitions filename if needed")
1479 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1480 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1481 usersldb = os.path.join(paths.private_dir, "users.ldb")
1482 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1484 if not os.path.isdir(samldbdir):
1486 os.chmod(samldbdir, 0700)
1487 if os.path.isfile(schemaldb):
1488 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1489 "%s.ldb"%str(names.schemadn).upper()))
1490 os.remove(schemaldb)
1491 if os.path.isfile(usersldb):
1492 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1493 "%s.ldb"%str(names.rootdn).upper()))
1495 if os.path.isfile(configldb):
1496 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1497 "%s.ldb"%str(names.configdn).upper()))
1498 os.remove(configldb)
1501 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1502 """Upgrade the SAM DB contents for all the provision partitions
1504 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1506 :param samdb: An LDB object connected to the sam.ldb of the update
1508 :param names: List of key provision parameters
1509 :param provisionUSNs: A dictionnary with range of USN modified during provision
1510 or upgradeprovision. Ranges are grouped by invocationID.
1511 :param schema: A Schema object that represent the schema of the provision
1512 :param prereloadfunc: A function that must be executed just before the reload
1516 message(SIMPLE, "Starting update of samdb")
1517 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1518 schema, provisionUSNs, prereloadfunc)
1520 message(SIMPLE, "Update of samdb finished")
1523 message(SIMPLE, "Update failed")
1527 def backup_provision(paths, dir, only_db):
1528 """This function backup the provision files so that a rollback
1531 :param paths: Paths to different objects
1532 :param dir: Directory where to store the backup
1533 :param only_db: Skip sysvol for users with big sysvol
1535 if paths.sysvol and not only_db:
1536 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1537 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1538 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1539 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1540 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1541 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1542 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1543 shutil.copy2(paths.smbconf, dir)
1544 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1546 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1547 if not os.path.isdir(samldbdir):
1548 samldbdir = paths.private_dir
1549 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1550 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1551 usersldb = os.path.join(paths.private_dir, "users.ldb")
1552 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1553 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1554 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1556 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0700)
1558 for ldb in os.listdir(samldbdir):
1559 tdb_util.tdb_copy(os.path.join(samldbdir, ldb),
1560 os.path.join(dir, "sam.ldb.d", ldb))
1563 def sync_calculated_attributes(samdb, names):
1564 """Synchronize attributes used for constructed ones, with the
1565 old constructed that were stored in the database.
1567 This apply for instance to msds-keyversionnumber that was
1568 stored and that is now constructed from replpropertymetadata.
1570 :param samdb: An LDB object attached to the currently upgraded samdb
1571 :param names: Various key parameter about current provision.
1573 listAttrs = ["msDs-KeyVersionNumber"]
1574 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1575 if hash.has_key("msDs-KeyVersionNumber"):
1576 increment_calculated_keyversion_number(samdb, names.rootdn,
1577 hash["msDs-KeyVersionNumber"])
1579 # Synopsis for updateprovision
1580 # 1) get path related to provision to be update (called current)
1581 # 2) open current provision ldbs
1582 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1584 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1585 # by either upgradeprovision or provision
1586 # 5) creation of a new provision the latest version of provision script
1587 # (called reference)
1588 # 6) get reference provision paths
1589 # 7) open reference provision ldbs
1590 # 8) setup helpers data that will help the update process
1591 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1592 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1593 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1594 # 10)get the oemInfo field, this field contains information about the different
1595 # provision that have been done
1596 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1597 # A) When alpha9 or alphaxx not specified (default)
1598 # The base sam.ldb file is updated by looking at the difference between
1599 # referrence one and the current one. Everything is copied with the
1600 # exception of lastProvisionUSN attributes.
1601 # B) Other case (it reflect that that provision was done before alpha9)
1602 # The base sam.ldb of the reference provision is copied over
1603 # the current one, if necessary ldb related to partitions are moved
1605 # The highest used USN is fetched so that changed by upgradeprovision
1606 # usn can be tracked
1607 # 12)A Schema object is created, it will be used to provide a complete
1608 # schema to current provision during update (as the schema of the
1609 # current provision might not be complete and so won't allow some
1610 # object to be created)
1611 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1612 # 14)The secrets db is updated by pull all the difference from the reference
1613 # provision into the current provision
1614 # 15)As the previous step has most probably modified the password stored in
1615 # in secret for the current DC, a new password is generated,
1616 # the kvno is bumped and the entry in samdb is also updated
1617 # 16)For current provision older than alpha9, we must fix the SD a little bit
1618 # administrator to update them because SD used to be generated with the
1619 # system account before alpha9.
1620 # 17)The highest usn modified so far is searched in the database it will be
1621 # the upper limit for usn modified during provision.
1622 # This is done before potential SD recalculation because we do not want
1623 # SD modified during recalculation to be marked as modified during provision
1624 # (and so possibly remplaced at next upgradeprovision)
1625 # 18)Rebuilt SD if the flag indicate to do so
1626 # 19)Check difference between SD of reference provision and those of the
1627 # current provision. The check is done by getting the sddl representation
1628 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1629 # Each part is verified separetly, for dacl and sacl ACL is splited into
1630 # ACEs and each ACE is verified separately (so that a permutation in ACE
1631 # didn't raise as an error).
1632 # 20)The oemInfo field is updated to add information about the fact that the
1633 # provision has been updated by the upgradeprovision version xxx
1634 # (the version is the one obtained when starting samba with the --version
1636 # 21)Check if the current provision has all the settings needed for dynamic
1637 # DNS update to work (that is to say the provision is newer than
1638 # january 2010). If not dns configuration file from reference provision
1639 # are copied in a sub folder and the administrator is invited to
1640 # do what is needed.
1641 # 22)If the lastProvisionUSN attribute was present it is updated to add
1642 # the range of usns modified by the current upgradeprovision
1645 # About updating the sam DB
1646 # The update takes place in update_partition function
1647 # This function read both current and reference provision and list all
1648 # the available DN of objects
1649 # If the string representation of a DN in reference provision is
1650 # equal to the string representation of a DN in current provision
1651 # (without taking care of case) then the object is flaged as being
1652 # present. If the object is not present in current provision the object
1653 # is being flaged as missing in current provision. Object present in current
1654 # provision but not in reference provision are ignored.
1655 # Once the list of objects present and missing is done, the deleted object
1656 # containers are created in the differents partitions (if missing)
1658 # Then the function add_missing_entries is called
1659 # This function will go through the list of missing entries by calling
1660 # add_missing_object for the given object. If this function returns 0
1661 # it means that the object needs some other object in order to be created
1662 # The object is reappended at the end of the list to be created later
1663 # (and preferably after all the needed object have been created)
1664 # The function keeps on looping on the list of object to be created until
1665 # it's empty or that the number of defered creation is equal to the number
1666 # of object that still needs to be created.
1668 # The function add_missing_object will first check if the object can be created.
1669 # That is to say that it didn't depends other not yet created objects
1670 # If requisit can't be fullfilled it exists with 0
1671 # Then it will try to create the missing entry by creating doing
1672 # an ldb_message_diff between the object in the reference provision and
1674 # This resulting object is filtered to remove all the back link attribute
1675 # (ie. memberOf) as they will be created by the other linked object (ie.
1676 # the one with the member attribute)
1677 # All attributes specified in the attrNotCopied array are
1678 # also removed it's most of the time generated attributes
1680 # After missing entries have been added the update_partition function will
1681 # take care of object that exist but that need some update.
1682 # In order to do so the function update_present is called with the list
1683 # of object that are present in both provision and that might need an update.
1685 # This function handle first case mismatch so that the DN in the current
1686 # provision have the same case as in reference provision
1688 # It will then construct an associative array consiting of attributes as
1689 # key and invocationid as value( if the originating invocation id is
1690 # different from the invocation id of the current DC the value is -1 instead).
1692 # If the range of provision modified attributes is present, the function will
1693 # use the replMetadataProperty update method which is the following:
1694 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1695 # creationTime, msDs-KeyVersionNumber, oEMInformation
1696 # Check for each attribute if its usn is within one of the modified by
1697 # provision range and if its originating id is the invocation id of the
1698 # current DC, then validate the update from reference to current.
1699 # If not or if there is no replMetatdataProperty for this attribute then we
1701 # Otherwise (case the range of provision modified attribute is not present) it
1702 # use the following process:
1703 # All attributes that need to be added are accepted at the exeption of those
1704 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1705 # correct flags specified.
1706 # For attributes that need to be modified or removed, a check is performed
1707 # in OverwrittenAtt, if the attribute is present and the modification flag
1708 # (remove, delete) is one of those listed for this attribute then modification
1709 # is accepted. For complicated handling of attribute update, the control is passed
1710 # to handle_special_case
1714 if __name__ == '__main__':
1715 global defSDmodified
1716 defSDmodified = False
1718 # From here start the big steps of the program
1719 # 1) First get files paths
1720 paths = get_paths(param, smbconf=smbconf)
1721 # Get ldbs with the system session, it is needed for searching
1722 # provision parameters
1723 session = system_session()
1725 # This variable will hold the last provision USN once if it exists.
1728 ldbs = get_ldbs(paths, creds, session, lp)
1729 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1730 prefix="backupprovision")
1731 backup_provision(paths, backupdir, opts.db_backup_only)
1733 ldbs.startTransactions()
1735 # 3) Guess all the needed names (variables in fact) from the current
1737 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1740 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1741 if lastProvisionUSNs is not None:
1743 for k in lastProvisionUSNs.keys():
1744 for r in lastProvisionUSNs[k]:
1748 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1749 (len(lastProvisionUSNs.keys()), v /2 ))
1751 if lastProvisionUSNs.get("default") is not None:
1752 message(CHANGE, "Old style for usn ranges used")
1753 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1754 del lastProvisionUSNs["default"]
1756 message(SIMPLE, "Your provision lacks provision range information")
1757 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1758 ldbs.groupedRollback()
1760 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1761 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1762 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1763 " of hundred of objects or more")
1764 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1767 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1769 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1772 # Objects will be created with the admin session
1773 # (not anymore system session)
1774 adm_session = admin_session(lp, str(names.domainsid))
1775 # So we reget handle on objects
1776 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1778 if not sanitychecks(ldbs.sam, names):
1779 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1780 "Check the messages and correct the errors "
1781 "before rerunning upgradeprovision")
1782 ldbs.groupedRollback()
1785 # Let's see provision parameters
1786 print_provision_key_parameters(names)
1788 # 5) With all this information let's create a fresh new provision used as
1790 message(SIMPLE, "Creating a reference provision")
1791 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1792 prefix="referenceprovision")
1793 result = newprovision(names, creds, session, smbconf, provisiondir,
1795 result.report_logger(provision_logger)
1799 # We need to get a list of object which SD is directly computed from
1800 # defaultSecurityDescriptor.
1801 # This will allow us to know which object we can rebuild the SD in case
1802 # of change of the parent's SD or of the defaultSD.
1803 # Get file paths of this new provision
1804 newpaths = get_paths(param, targetdir=provisiondir)
1805 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1806 new_ldbs.startTransactions()
1808 populateNotReplicated(new_ldbs.sam, names.schemadn)
1809 # 8) Populate some associative array to ease the update process
1810 # List of attribute which are link and backlink
1811 populate_links(new_ldbs.sam, names.schemadn)
1812 # List of attribute with ASN DN synthax)
1813 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1814 # 9) (now skipped, was copy of privileges.ldb)
1816 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1817 # Do some modification on sam.ldb
1818 ldbs.groupedCommit()
1819 new_ldbs.groupedCommit()
1823 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1825 # Starting from alpha9 we can consider that the structure is quite ok
1826 # and that we should do only dela
1827 deltaattr = delta_update_basesamdb(newpaths.samdb,
1835 simple_update_basesamdb(newpaths, paths, names)
1836 ldbs = get_ldbs(paths, creds, session, lp)
1837 removeProvisionUSN(ldbs.sam)
1839 ldbs.startTransactions()
1840 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1841 new_ldbs.startTransactions()
1844 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1845 # We create a closure that will be invoked just before schema reload
1846 def schemareloadclosure():
1847 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1848 options=["modules:"])
1850 if deltaattr is not None and len(deltaattr) > 1:
1853 deltaattr.remove("dn")
1854 for att in deltaattr:
1855 if att.lower() == "dn":
1857 if (deltaattr.get(att) is not None
1858 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1860 elif deltaattr.get(att) is None:
1863 message(CHANGE, "Applying delta to @ATTRIBUTES")
1864 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1865 basesam.modify(deltaattr)
1867 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1868 "there is not only add")
1871 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1872 schema, schemareloadclosure):
1873 message(SIMPLE, "Rolling back all changes. Check the cause"
1875 message(SIMPLE, "Your system is as it was before the upgrade")
1876 ldbs.groupedRollback()
1877 new_ldbs.groupedRollback()
1878 shutil.rmtree(provisiondir)
1881 # Try to reapply the change also when we do not change the sam
1882 # as the delta_upgrade
1883 schemareloadclosure()
1884 sync_calculated_attributes(ldbs.sam, names)
1885 res = ldbs.sam.search(expression="(samaccountname=dns)",
1886 scope=SCOPE_SUBTREE, attrs=["dn"],
1887 controls=["search_options:1:2"])
1889 message(SIMPLE, "You still have the old DNS object for managing "
1890 "dynamic DNS, but you didn't supply --full so "
1891 "a correct update can't be done")
1892 ldbs.groupedRollback()
1893 new_ldbs.groupedRollback()
1894 shutil.rmtree(provisiondir)
1897 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1899 res = ldbs.sam.search(expression="(samaccountname=dns)",
1900 scope=SCOPE_SUBTREE, attrs=["dn"],
1901 controls=["search_options:1:2"])
1904 ldbs.sam.delete(res[0]["dn"])
1905 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1906 scope=SCOPE_SUBTREE, attrs=["dn"])
1907 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1908 message(SIMPLE, "IMPORTANT!!! "
1909 "If you were using Dynamic DNS before you need "
1910 "to update your configuration, so that the "
1911 "tkey-gssapi-credential has the following value: "
1912 "DNS/%s.%s" % (names.netbiosname.lower(),
1913 names.realm.lower()))
1915 message(SIMPLE, "Update machine account")
1916 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1918 dnToRecalculate.sort(dn_sort)
1919 # 16) SD should be created with admin but as some previous acl were so wrong
1920 # that admin can't modify them we have first to recreate them with the good
1921 # form but with system account and then give the ownership to admin ...
1922 if opts.very_old_pre_alpha9:
1923 message(SIMPLE, "Fixing very old provision SD")
1924 rebuild_sd(ldbs.sam, names)
1926 # We calculate the max USN before recalculating the SD because we might
1927 # touch object that have been modified after a provision and we do not
1928 # want that the next upgradeprovision thinks that it has a green light
1932 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1934 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1935 # defSDmodified is set.
1936 if defSDmodified or len(dnToRecalculate) >0:
1937 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1938 "changed, recalculating them")
1939 ldbs.sam.set_session_info(adm_session)
1940 rebuild_sd(ldbs.sam, names)
1943 # Now we are quite confident in the recalculate process of the SD, we make
1944 # it optional. And we don't do it if there is DN that we must touch
1945 # as we are assured that on this DNs we will have differences !
1946 # Also the check must be done in a clever way as for the moment we just
1948 if len(dnNotToRecalculate) == 0 and (opts.debugchangesd or opts.debugall):
1949 message(CHANGESD, "Checking recalculated SDs")
1950 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1953 updateOEMInfo(ldbs.sam, str(names.rootdn))
1955 check_for_DNS(newpaths.private_dir, paths.private_dir, names.dns_backend)
1957 if lastProvisionUSNs is not None:
1958 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1959 if opts.full and (names.policyid is None or names.policyid_dc is None):
1960 update_policyids(names, ldbs.sam)
1964 update_gpo(paths, ldbs.sam, names, lp, message)
1965 except ProvisioningError, e:
1966 message(ERROR, "The policy for domain controller is missing. "
1967 "You should restart upgradeprovision with --full")
1969 ldbs.groupedCommit()
1970 new_ldbs.groupedCommit()
1971 message(SIMPLE, "Upgrade finished!")
1972 # remove reference provision now that everything is done !
1973 # So we have reindexed first if need when the merged schema was reloaded
1974 # (as new attributes could have quick in)
1975 # But the second part of the update (when we update existing objects
1976 # can also have an influence on indexing as some attribute might have their
1977 # searchflag modificated
1978 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1979 "after modification")
1980 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1981 message(SIMPLE, "Reindexing finished")
1983 shutil.rmtree(provisiondir)
1984 except StandardError, err:
1985 message(ERROR, "A problem occurred while trying to upgrade your "
1986 "provision. A full backup is located at %s" % backupdir)
1987 if opts.debugall or opts.debugchange:
1988 (typ, val, tb) = sys.exc_info()
1989 traceback.print_exception(typ, val, tb)