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_sds,
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 dnNotToRecalculateFound = False
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 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
836 hash_attr_usn, basedn, usns, samdb):
837 """ Check if we should keep the attribute modification or not
839 :param delta: A message diff object
840 :param att: An attribute
841 :param message: A function to print messages
842 :param reference: A message object for the current entry comming from
843 the reference provision.
844 :param current: A message object for the current entry commin from
845 the current provision.
846 :param hash_attr_usn: A dictionnary with attribute name as keys,
847 USN and invocation id as values.
848 :param basedn: The DN of the partition
849 :param usns: A dictionnary with invocation ID as keys and USN ranges
851 :param samdb: A ldb object pointing to the sam DB
853 :return: The modified message diff.
860 for att in list(delta):
861 if att in ["dn", "objectSid"]:
865 # We have updated by provision usn information so let's exploit
866 # replMetadataProperties
867 if att in forwardlinked:
868 curval = current[0].get(att, ())
869 refval = reference[0].get(att, ())
870 delta = handle_links(samdb, att, basedn, current[0]["dn"],
871 curval, refval, delta)
875 if isFirst and len(list(delta)) > 1:
877 txt = "%s\n" % (str(dn))
879 if handle_special_case(att, delta, reference, current, True, None, None):
880 # This attribute is "complicated" to handle and handling
881 # was done in handle_special_case
885 if hash_attr_usn.get(att):
886 [attrUSN, attInvId] = hash_attr_usn.get(att)
889 # If it's a replicated attribute and we don't have any USN
890 # information about it. It means that we never saw it before
892 # If it is a replicated attribute but we are not master on it
893 # (ie. not initially added in the provision we masterize).
895 if isReplicated(att):
898 message(CHANGE, "Non replicated attribute %s changed" % att)
901 if att == "nTSecurityDescriptor":
902 cursd = ndr_unpack(security.descriptor,
903 str(current[0]["nTSecurityDescriptor"]))
904 refsd = ndr_unpack(security.descriptor,
905 str(reference[0]["nTSecurityDescriptor"]))
907 diff = get_diff_sds(refsd, cursd, names.domainsid)
909 # FIXME find a way to have it only with huge huge verbose mode
910 # message(CHANGE, "%ssd are identical" % txt)
916 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
919 message(CHANGESD, "But the SD has been changed by someonelse "
920 "so it's impossible to know if the difference"
921 " cames from the modification or from a previous bug")
922 dnNotToRecalculateFound = True
924 dnToRecalculate.append(dn)
928 # This attribute was last modified by another DC forget
930 message(CHANGE, "%sAttribute: %s has been "
931 "created/modified/deleted by another DC. "
932 "Doing nothing" % (txt, att))
936 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
937 message(CHANGE, "%sAttribute: %s was not "
938 "created/modified/deleted during a "
939 "provision or upgradeprovision. Current "
940 "usn: %d. Doing nothing" % (txt, att,
946 if att == "defaultSecurityDescriptor":
949 message(CHANGE, "%sAttribute: %s will be modified"
950 "/deleted it was last modified "
951 "during a provision. Current usn: "
952 "%d" % (txt, att, attrUSN))
955 message(CHANGE, "%sAttribute: %s will be added because "
956 "it did not exist before" % (txt, att))
962 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
963 """ This function updates the object that are already present in the
966 :param ref_samdb: An LDB object pointing to the reference provision
967 :param samdb: An LDB object pointing to the updated provision
968 :param basedn: A string with the value of the base DN for the provision
970 :param listPresent: A list of object that is present in the provision
971 :param usns: A list of USN range modified by previous provision and
972 upgradeprovision grouped by invocation ID
975 # This hash is meant to speedup lookup of attribute name from an oid,
976 # it's for the replPropertyMetaData handling
978 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
979 controls=["search_options:1:2"], attrs=["attributeID",
983 strDisplay = str(e.get("lDAPDisplayName"))
984 hash_oid_name[str(e.get("attributeID"))] = strDisplay
986 msg = "Unable to insert missing elements: circular references"
987 raise ProvisioningError(msg)
990 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
991 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
992 message(CHANGE, "Using replPropertyMetadata for change selection")
993 for dn in listPresent:
994 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
997 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
998 scope=SCOPE_SUBTREE, controls=controls)
1001 (str(current[0].dn) != str(reference[0].dn)) and
1002 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1004 message(CHANGE, "Names are the same except for the case. "
1005 "Renaming %s to %s" % (str(current[0].dn),
1006 str(reference[0].dn)))
1007 identic_rename(samdb, reference[0].dn)
1008 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1009 scope=SCOPE_SUBTREE,
1012 delta = samdb.msg_diff(current[0], reference[0])
1014 for att in backlinked:
1017 for att in attrNotCopied:
1020 delta.remove("name")
1022 nb_items = len(list(delta))
1028 # Fetch the replPropertyMetaData
1029 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1030 scope=SCOPE_SUBTREE, controls=controls,
1031 attrs=["replPropertyMetaData"])
1032 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1033 str(res[0]["replPropertyMetaData"])).ctr
1037 # We put in this hash only modification
1038 # made on the current host
1039 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1040 if str(o.originating_invocation_id) in usns.keys():
1041 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1043 hash_attr_usn[att] = [-1, None]
1045 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1046 current, hash_attr_usn,
1047 basedn, usns, samdb)
1053 # Skip dn as the value is not really changed ...
1054 attributes=", ".join(delta.keys()[1:])
1056 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1057 # Let's try to reduce as much as possible the use of relax control
1058 for attr in delta.keys():
1059 if attr.lower() in relaxedatt:
1060 modcontrols = ["relax:0", "provision:0"]
1061 message(CHANGE, "%s is different from the reference one, changed"
1062 " attributes: %s\n" % (dn, attributes))
1064 samdb.modify(delta, modcontrols)
1067 def reload_full_schema(samdb, names):
1068 """Load the updated schema with all the new and existing classes
1071 :param samdb: An LDB object connected to the sam.ldb of the update
1073 :param names: List of key provision parameters
1076 schemadn = str(names.schemadn)
1077 current = samdb.search(expression="objectClass=*", base=schemadn,
1078 scope=SCOPE_SUBTREE)
1083 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1085 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1086 prefixmap_data = b64encode(prefixmap_data)
1088 # We don't actually add this ldif, just parse it
1089 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1091 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1094 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1095 """Check differences between the reference provision and the upgraded one.
1097 It looks for all objects which base DN is name.
1099 This function will also add the missing object and update existing object
1100 to add or remove attributes that were missing.
1102 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1104 :param samdb: An LDB object connected to the sam.ldb of the update
1106 :param basedn: String value of the DN of the partition
1107 :param names: List of key provision parameters
1108 :param schema: A Schema object
1109 :param provisionUSNs: A dictionnary with range of USN modified during provision
1110 or upgradeprovision. Ranges are grouped by invocationID.
1111 :param prereloadfunc: A function that must be executed just before the reload
1122 # Connect to the reference provision and get all the attribute in the
1123 # partition referred by name
1124 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1125 scope=SCOPE_SUBTREE, attrs=["dn"],
1126 controls=["search_options:1:2"])
1128 current = samdb.search(expression="objectClass=*", base=basedn,
1129 scope=SCOPE_SUBTREE, attrs=["dn"],
1130 controls=["search_options:1:2"])
1131 # Create a hash for speeding the search of new object
1132 for i in range(0, len(reference)):
1133 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1135 # Create a hash for speeding the search of existing object in the
1137 for i in range(0, len(current)):
1138 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1141 for k in hash_new.keys():
1142 if not hash.has_key(k):
1143 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1144 listMissing.append(hash_new[k])
1146 listPresent.append(hash_new[k])
1148 # Sort the missing object in order to have object of the lowest level
1149 # first (which can be containers for higher level objects)
1150 listMissing.sort(dn_sort)
1151 listPresent.sort(dn_sort)
1153 # The following lines is to load the up to
1154 # date schema into our current LDB
1155 # a complete schema is needed as the insertion of attributes
1156 # and class is done against it
1157 # and the schema is self validated
1158 samdb.set_schema(schema)
1160 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1161 add_deletedobj_containers(ref_samdb, samdb, names)
1163 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1166 message(SIMPLE, "Reloading a merged schema, which might trigger "
1167 "reindexing so please be patient")
1168 reload_full_schema(samdb, names)
1169 message(SIMPLE, "Schema reloaded!")
1171 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1173 message(SIMPLE, "There are %d changed objects" % (changed))
1176 except StandardError, err:
1177 message(ERROR, "Exception during upgrade of samdb:")
1178 (typ, val, tb) = sys.exc_info()
1179 traceback.print_exception(typ, val, tb)
1183 def check_updated_sd(ref_sam, cur_sam, names):
1184 """Check if the security descriptor in the upgraded provision are the same
1187 :param ref_sam: A LDB object connected to the sam.ldb file used as
1188 the reference provision
1189 :param cur_sam: A LDB object connected to the sam.ldb file used as
1191 :param names: List of key provision parameters"""
1192 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1193 scope=SCOPE_SUBTREE,
1194 attrs=["dn", "nTSecurityDescriptor"],
1195 controls=["search_options:1:2"])
1196 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1197 scope=SCOPE_SUBTREE,
1198 attrs=["dn", "nTSecurityDescriptor"],
1199 controls=["search_options:1:2"])
1201 for i in range(0, len(reference)):
1202 refsd_blob = str(reference[i]["nTSecurityDescriptor"])
1203 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1206 for i in range(0, len(current)):
1207 key = str(current[i]["dn"]).lower()
1208 if hash.has_key(key):
1209 cursd_blob = str(current[i]["nTSecurityDescriptor"])
1210 cursd = ndr_unpack(security.descriptor,
1212 if cursd_blob != hash[key]:
1213 refsd = ndr_unpack(security.descriptor,
1215 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1217 message(CHANGESD, "On object %s ACL is different"
1218 " \n%s" % (current[i]["dn"], txt))
1222 def fix_wellknown_sd(samdb, names):
1223 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1224 This is needed because some provision use to have broken SD on containers
1226 :param samdb: An LDB object pointing to the sam of the current provision
1227 :param names: A list of key provision parameters
1230 list_wellknown_dns = []
1232 # Then subcontainers
1234 ("%s" % str(names.domaindn), get_domain_descriptor),
1235 ("CN=LostAndFound,%s" % str(names.domaindn), get_domain_delete_protected2_descriptor),
1236 ("CN=System,%s" % str(names.domaindn), get_domain_delete_protected1_descriptor),
1237 ("CN=Infrastructure,%s" % str(names.domaindn), get_domain_infrastructure_descriptor),
1238 ("CN=Builtin,%s" % str(names.domaindn), get_domain_builtin_descriptor),
1239 ("CN=Computers,%s" % str(names.domaindn), get_domain_computers_descriptor),
1240 ("CN=Users,%s" % str(names.domaindn), get_domain_users_descriptor),
1241 ("OU=Domain Controllers,%s" % str(names.domaindn), get_domain_controllers_descriptor),
1242 ("CN=MicrosoftDNS,CN=System,%s" % str(names.domaindn), get_dns_domain_microsoft_dns_descriptor),
1244 ("%s" % str(names.configdn), get_config_descriptor),
1245 ("CN=NTDS Quotas,%s" % str(names.configdn), get_config_ntds_quotas_descriptor),
1246 ("CN=LostAndFoundConfig,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1247 ("CN=Services,%s" % str(names.configdn), get_config_delete_protected1_descriptor),
1248 ("CN=Physical Locations,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1249 ("CN=WellKnown Security Principals,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1250 ("CN=ForestUpdates,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
1251 ("CN=DisplaySpecifiers,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
1252 ("CN=Extended-Rights,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
1253 ("CN=Partitions,%s" % str(names.configdn), get_config_partitions_descriptor),
1254 ("CN=Sites,%s" % str(names.configdn), get_config_sites_descriptor),
1256 ("%s" % str(names.schemadn), get_schema_descriptor),
1259 if names.dnsforestdn is not None:
1260 c = ("%s" % str(names.dnsforestdn), get_dns_partition_descriptor)
1261 subcontainers.append(c)
1262 c = ("CN=Infrastructure,%s" % str(names.dnsforestdn),
1263 get_domain_delete_protected1_descriptor)
1264 subcontainers.append(c)
1265 c = ("CN=LostAndFound,%s" % str(names.dnsforestdn),
1266 get_domain_delete_protected2_descriptor)
1267 subcontainers.append(c)
1268 c = ("CN=MicrosoftDNS,%s" % str(names.dnsforestdn),
1269 get_dns_forest_microsoft_dns_descriptor)
1270 subcontainers.append(c)
1272 if names.dnsdomaindn is not None:
1273 c = ("%s" % str(names.dnsdomaindn), get_dns_partition_descriptor)
1274 subcontainers.append(c)
1275 c = ("CN=Infrastructure,%s" % str(names.dnsdomaindn),
1276 get_domain_delete_protected1_descriptor)
1277 subcontainers.append(c)
1278 c = ("CN=LostAndFound,%s" % str(names.dnsdomaindn),
1279 get_domain_delete_protected2_descriptor)
1280 subcontainers.append(c)
1281 c = ("CN=MicrosoftDNS,%s" % str(names.dnsdomaindn),
1282 get_dns_domain_microsoft_dns_descriptor)
1283 subcontainers.append(c)
1285 for [strdn, descriptor_fn] in subcontainers:
1286 dn = Dn(samdb, strdn)
1287 list_wellknown_dns.append(dn)
1288 if dn in dnToRecalculate:
1291 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1292 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1293 "nTSecurityDescriptor" )
1295 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1297 return list_wellknown_dns
1299 def rebuild_sd(samdb, names):
1300 """Rebuild security descriptor of the current provision from scratch
1302 During the different pre release of samba4 security descriptors
1303 (SD) were notarly broken (up to alpha11 included)
1305 This function allows to get them back in order, this function works
1306 only after the database comparison that --full mode uses and which
1307 populates the dnToRecalculate and dnNotToRecalculate lists.
1309 The idea is that the SD can be safely recalculated from scratch to get it right.
1311 :param names: List of key provision parameters"""
1313 listWellknown = fix_wellknown_sd(samdb, names)
1315 if len(dnToRecalculate) != 0:
1316 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1317 % (len(dnToRecalculate)))
1319 for dn in dnToRecalculate:
1320 # well known SDs have already been reset
1321 if dn in listWellknown:
1325 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1327 descr = get_empty_descriptor(names.domainsid)
1328 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1329 "nTSecurityDescriptor")
1330 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1332 samdb.transaction_cancel()
1333 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1335 attrs=["nTSecurityDescriptor"],
1336 controls=["sd_flags:1:%d" % sd_flags])
1337 badsd = ndr_unpack(security.descriptor,
1338 str(res[0]["nTSecurityDescriptor"]))
1339 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1342 def hasATProvision(samdb):
1343 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1347 if entry is not None and len(entry) == 1:
1352 def removeProvisionUSN(samdb):
1353 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1354 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1358 empty.dn = entry[0].dn
1359 delta = samdb.msg_diff(entry[0], empty)
1361 delta.dn = entry[0].dn
1364 def remove_stored_generated_attrs(paths, creds, session, lp):
1365 """Remove previously stored constructed attributes
1367 :param paths: List of paths for different provision objects
1368 from the upgraded provision
1369 :param creds: A credential object
1370 :param session: A session object
1371 :param lp: A line parser object
1372 :return: An associative array whose key are the different constructed
1373 attributes and the value the dn where this attributes were found.
1377 def simple_update_basesamdb(newpaths, paths, names):
1378 """Update the provision container db: sam.ldb
1379 This function is aimed at very old provision (before alpha9)
1381 :param newpaths: List of paths for different provision objects
1382 from the reference provision
1383 :param paths: List of paths for different provision objects
1384 from the upgraded provision
1385 :param names: List of key provision parameters"""
1387 message(SIMPLE, "Copy samdb")
1388 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1390 message(SIMPLE, "Update partitions filename if needed")
1391 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1392 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1393 usersldb = os.path.join(paths.private_dir, "users.ldb")
1394 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1396 if not os.path.isdir(samldbdir):
1398 os.chmod(samldbdir, 0700)
1399 if os.path.isfile(schemaldb):
1400 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1401 "%s.ldb"%str(names.schemadn).upper()))
1402 os.remove(schemaldb)
1403 if os.path.isfile(usersldb):
1404 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1405 "%s.ldb"%str(names.rootdn).upper()))
1407 if os.path.isfile(configldb):
1408 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1409 "%s.ldb"%str(names.configdn).upper()))
1410 os.remove(configldb)
1413 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1414 """Upgrade the SAM DB contents for all the provision partitions
1416 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1418 :param samdb: An LDB object connected to the sam.ldb of the update
1420 :param names: List of key provision parameters
1421 :param provisionUSNs: A dictionnary with range of USN modified during provision
1422 or upgradeprovision. Ranges are grouped by invocationID.
1423 :param schema: A Schema object that represent the schema of the provision
1424 :param prereloadfunc: A function that must be executed just before the reload
1428 message(SIMPLE, "Starting update of samdb")
1429 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1430 schema, provisionUSNs, prereloadfunc)
1432 message(SIMPLE, "Update of samdb finished")
1435 message(SIMPLE, "Update failed")
1439 def backup_provision(paths, dir, only_db):
1440 """This function backup the provision files so that a rollback
1443 :param paths: Paths to different objects
1444 :param dir: Directory where to store the backup
1445 :param only_db: Skip sysvol for users with big sysvol
1447 if paths.sysvol and not only_db:
1448 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1449 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1450 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1451 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1452 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1453 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1454 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1455 shutil.copy2(paths.smbconf, dir)
1456 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1458 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1459 if not os.path.isdir(samldbdir):
1460 samldbdir = paths.private_dir
1461 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1462 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1463 usersldb = os.path.join(paths.private_dir, "users.ldb")
1464 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1465 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1466 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1468 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0700)
1470 for ldb in os.listdir(samldbdir):
1471 tdb_util.tdb_copy(os.path.join(samldbdir, ldb),
1472 os.path.join(dir, "sam.ldb.d", ldb))
1475 def sync_calculated_attributes(samdb, names):
1476 """Synchronize attributes used for constructed ones, with the
1477 old constructed that were stored in the database.
1479 This apply for instance to msds-keyversionnumber that was
1480 stored and that is now constructed from replpropertymetadata.
1482 :param samdb: An LDB object attached to the currently upgraded samdb
1483 :param names: Various key parameter about current provision.
1485 listAttrs = ["msDs-KeyVersionNumber"]
1486 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1487 if hash.has_key("msDs-KeyVersionNumber"):
1488 increment_calculated_keyversion_number(samdb, names.rootdn,
1489 hash["msDs-KeyVersionNumber"])
1491 # Synopsis for updateprovision
1492 # 1) get path related to provision to be update (called current)
1493 # 2) open current provision ldbs
1494 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1496 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1497 # by either upgradeprovision or provision
1498 # 5) creation of a new provision the latest version of provision script
1499 # (called reference)
1500 # 6) get reference provision paths
1501 # 7) open reference provision ldbs
1502 # 8) setup helpers data that will help the update process
1503 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1504 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1505 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1506 # 10)get the oemInfo field, this field contains information about the different
1507 # provision that have been done
1508 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1509 # A) When alpha9 or alphaxx not specified (default)
1510 # The base sam.ldb file is updated by looking at the difference between
1511 # referrence one and the current one. Everything is copied with the
1512 # exception of lastProvisionUSN attributes.
1513 # B) Other case (it reflect that that provision was done before alpha9)
1514 # The base sam.ldb of the reference provision is copied over
1515 # the current one, if necessary ldb related to partitions are moved
1517 # The highest used USN is fetched so that changed by upgradeprovision
1518 # usn can be tracked
1519 # 12)A Schema object is created, it will be used to provide a complete
1520 # schema to current provision during update (as the schema of the
1521 # current provision might not be complete and so won't allow some
1522 # object to be created)
1523 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1524 # 14)The secrets db is updated by pull all the difference from the reference
1525 # provision into the current provision
1526 # 15)As the previous step has most probably modified the password stored in
1527 # in secret for the current DC, a new password is generated,
1528 # the kvno is bumped and the entry in samdb is also updated
1529 # 16)For current provision older than alpha9, we must fix the SD a little bit
1530 # administrator to update them because SD used to be generated with the
1531 # system account before alpha9.
1532 # 17)The highest usn modified so far is searched in the database it will be
1533 # the upper limit for usn modified during provision.
1534 # This is done before potential SD recalculation because we do not want
1535 # SD modified during recalculation to be marked as modified during provision
1536 # (and so possibly remplaced at next upgradeprovision)
1537 # 18)Rebuilt SD if the flag indicate to do so
1538 # 19)Check difference between SD of reference provision and those of the
1539 # current provision. The check is done by getting the sddl representation
1540 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1541 # Each part is verified separetly, for dacl and sacl ACL is splited into
1542 # ACEs and each ACE is verified separately (so that a permutation in ACE
1543 # didn't raise as an error).
1544 # 20)The oemInfo field is updated to add information about the fact that the
1545 # provision has been updated by the upgradeprovision version xxx
1546 # (the version is the one obtained when starting samba with the --version
1548 # 21)Check if the current provision has all the settings needed for dynamic
1549 # DNS update to work (that is to say the provision is newer than
1550 # january 2010). If not dns configuration file from reference provision
1551 # are copied in a sub folder and the administrator is invited to
1552 # do what is needed.
1553 # 22)If the lastProvisionUSN attribute was present it is updated to add
1554 # the range of usns modified by the current upgradeprovision
1557 # About updating the sam DB
1558 # The update takes place in update_partition function
1559 # This function read both current and reference provision and list all
1560 # the available DN of objects
1561 # If the string representation of a DN in reference provision is
1562 # equal to the string representation of a DN in current provision
1563 # (without taking care of case) then the object is flaged as being
1564 # present. If the object is not present in current provision the object
1565 # is being flaged as missing in current provision. Object present in current
1566 # provision but not in reference provision are ignored.
1567 # Once the list of objects present and missing is done, the deleted object
1568 # containers are created in the differents partitions (if missing)
1570 # Then the function add_missing_entries is called
1571 # This function will go through the list of missing entries by calling
1572 # add_missing_object for the given object. If this function returns 0
1573 # it means that the object needs some other object in order to be created
1574 # The object is reappended at the end of the list to be created later
1575 # (and preferably after all the needed object have been created)
1576 # The function keeps on looping on the list of object to be created until
1577 # it's empty or that the number of defered creation is equal to the number
1578 # of object that still needs to be created.
1580 # The function add_missing_object will first check if the object can be created.
1581 # That is to say that it didn't depends other not yet created objects
1582 # If requisit can't be fullfilled it exists with 0
1583 # Then it will try to create the missing entry by creating doing
1584 # an ldb_message_diff between the object in the reference provision and
1586 # This resulting object is filtered to remove all the back link attribute
1587 # (ie. memberOf) as they will be created by the other linked object (ie.
1588 # the one with the member attribute)
1589 # All attributes specified in the attrNotCopied array are
1590 # also removed it's most of the time generated attributes
1592 # After missing entries have been added the update_partition function will
1593 # take care of object that exist but that need some update.
1594 # In order to do so the function update_present is called with the list
1595 # of object that are present in both provision and that might need an update.
1597 # This function handle first case mismatch so that the DN in the current
1598 # provision have the same case as in reference provision
1600 # It will then construct an associative array consiting of attributes as
1601 # key and invocationid as value( if the originating invocation id is
1602 # different from the invocation id of the current DC the value is -1 instead).
1604 # If the range of provision modified attributes is present, the function will
1605 # use the replMetadataProperty update method which is the following:
1606 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1607 # creationTime, msDs-KeyVersionNumber, oEMInformation
1608 # Check for each attribute if its usn is within one of the modified by
1609 # provision range and if its originating id is the invocation id of the
1610 # current DC, then validate the update from reference to current.
1611 # If not or if there is no replMetatdataProperty for this attribute then we
1613 # Otherwise (case the range of provision modified attribute is not present) it
1614 # use the following process:
1615 # All attributes that need to be added are accepted at the exeption of those
1616 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1617 # correct flags specified.
1618 # For attributes that need to be modified or removed, a check is performed
1619 # in OverwrittenAtt, if the attribute is present and the modification flag
1620 # (remove, delete) is one of those listed for this attribute then modification
1621 # is accepted. For complicated handling of attribute update, the control is passed
1622 # to handle_special_case
1626 if __name__ == '__main__':
1627 global defSDmodified
1628 defSDmodified = False
1630 # From here start the big steps of the program
1631 # 1) First get files paths
1632 paths = get_paths(param, smbconf=smbconf)
1633 # Get ldbs with the system session, it is needed for searching
1634 # provision parameters
1635 session = system_session()
1637 # This variable will hold the last provision USN once if it exists.
1640 ldbs = get_ldbs(paths, creds, session, lp)
1641 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1642 prefix="backupprovision")
1643 backup_provision(paths, backupdir, opts.db_backup_only)
1645 ldbs.startTransactions()
1647 # 3) Guess all the needed names (variables in fact) from the current
1649 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1652 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1653 if lastProvisionUSNs is not None:
1655 for k in lastProvisionUSNs.keys():
1656 for r in lastProvisionUSNs[k]:
1660 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1661 (len(lastProvisionUSNs.keys()), v /2 ))
1663 if lastProvisionUSNs.get("default") is not None:
1664 message(CHANGE, "Old style for usn ranges used")
1665 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1666 del lastProvisionUSNs["default"]
1668 message(SIMPLE, "Your provision lacks provision range information")
1669 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1670 ldbs.groupedRollback()
1672 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1673 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1674 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1675 " of hundred of objects or more")
1676 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1679 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1681 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1684 # Objects will be created with the admin session
1685 # (not anymore system session)
1686 adm_session = admin_session(lp, str(names.domainsid))
1687 # So we reget handle on objects
1688 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1690 if not sanitychecks(ldbs.sam, names):
1691 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1692 "Check the messages and correct the errors "
1693 "before rerunning upgradeprovision")
1694 ldbs.groupedRollback()
1697 # Let's see provision parameters
1698 print_provision_key_parameters(names)
1700 # 5) With all this information let's create a fresh new provision used as
1702 message(SIMPLE, "Creating a reference provision")
1703 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1704 prefix="referenceprovision")
1705 result = newprovision(names, creds, session, smbconf, provisiondir,
1707 result.report_logger(provision_logger)
1711 # We need to get a list of object which SD is directly computed from
1712 # defaultSecurityDescriptor.
1713 # This will allow us to know which object we can rebuild the SD in case
1714 # of change of the parent's SD or of the defaultSD.
1715 # Get file paths of this new provision
1716 newpaths = get_paths(param, targetdir=provisiondir)
1717 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1718 new_ldbs.startTransactions()
1720 populateNotReplicated(new_ldbs.sam, names.schemadn)
1721 # 8) Populate some associative array to ease the update process
1722 # List of attribute which are link and backlink
1723 populate_links(new_ldbs.sam, names.schemadn)
1724 # List of attribute with ASN DN synthax)
1725 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1726 # 9) (now skipped, was copy of privileges.ldb)
1728 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1729 # Do some modification on sam.ldb
1730 ldbs.groupedCommit()
1731 new_ldbs.groupedCommit()
1735 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1737 # Starting from alpha9 we can consider that the structure is quite ok
1738 # and that we should do only dela
1739 deltaattr = delta_update_basesamdb(newpaths.samdb,
1747 simple_update_basesamdb(newpaths, paths, names)
1748 ldbs = get_ldbs(paths, creds, session, lp)
1749 removeProvisionUSN(ldbs.sam)
1751 ldbs.startTransactions()
1752 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1753 new_ldbs.startTransactions()
1756 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1757 # We create a closure that will be invoked just before schema reload
1758 def schemareloadclosure():
1759 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1760 options=["modules:"])
1762 if deltaattr is not None and len(deltaattr) > 1:
1765 deltaattr.remove("dn")
1766 for att in deltaattr:
1767 if att.lower() == "dn":
1769 if (deltaattr.get(att) is not None
1770 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1772 elif deltaattr.get(att) is None:
1775 message(CHANGE, "Applying delta to @ATTRIBUTES")
1776 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1777 basesam.modify(deltaattr)
1779 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1780 "there is not only add")
1783 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1784 schema, schemareloadclosure):
1785 message(SIMPLE, "Rolling back all changes. Check the cause"
1787 message(SIMPLE, "Your system is as it was before the upgrade")
1788 ldbs.groupedRollback()
1789 new_ldbs.groupedRollback()
1790 shutil.rmtree(provisiondir)
1793 # Try to reapply the change also when we do not change the sam
1794 # as the delta_upgrade
1795 schemareloadclosure()
1796 sync_calculated_attributes(ldbs.sam, names)
1797 res = ldbs.sam.search(expression="(samaccountname=dns)",
1798 scope=SCOPE_SUBTREE, attrs=["dn"],
1799 controls=["search_options:1:2"])
1801 message(SIMPLE, "You still have the old DNS object for managing "
1802 "dynamic DNS, but you didn't supply --full so "
1803 "a correct update can't be done")
1804 ldbs.groupedRollback()
1805 new_ldbs.groupedRollback()
1806 shutil.rmtree(provisiondir)
1809 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1811 res = ldbs.sam.search(expression="(samaccountname=dns)",
1812 scope=SCOPE_SUBTREE, attrs=["dn"],
1813 controls=["search_options:1:2"])
1816 ldbs.sam.delete(res[0]["dn"])
1817 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1818 scope=SCOPE_SUBTREE, attrs=["dn"])
1819 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1820 message(SIMPLE, "IMPORTANT!!! "
1821 "If you were using Dynamic DNS before you need "
1822 "to update your configuration, so that the "
1823 "tkey-gssapi-credential has the following value: "
1824 "DNS/%s.%s" % (names.netbiosname.lower(),
1825 names.realm.lower()))
1827 message(SIMPLE, "Update machine account")
1828 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1830 # 16) SD should be created with admin but as some previous acl were so wrong
1831 # that admin can't modify them we have first to recreate them with the good
1832 # form but with system account and then give the ownership to admin ...
1833 if opts.very_old_pre_alpha9:
1834 message(SIMPLE, "Fixing very old provision SD")
1835 rebuild_sd(ldbs.sam, names)
1837 # We calculate the max USN before recalculating the SD because we might
1838 # touch object that have been modified after a provision and we do not
1839 # want that the next upgradeprovision thinks that it has a green light
1843 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1845 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1846 # defSDmodified is set.
1847 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1848 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1849 "changed, recalculating them")
1850 ldbs.sam.set_session_info(adm_session)
1851 rebuild_sd(ldbs.sam, names)
1854 # Now we are quite confident in the recalculate process of the SD, we make
1855 # it optional. And we don't do it if there is DN that we must touch
1856 # as we are assured that on this DNs we will have differences !
1857 # Also the check must be done in a clever way as for the moment we just
1859 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1860 message(CHANGESD, "Checking recalculated SDs")
1861 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1864 updateOEMInfo(ldbs.sam, str(names.rootdn))
1866 check_for_DNS(newpaths.private_dir, paths.private_dir, names.dns_backend)
1868 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1869 if opts.full and (names.policyid is None or names.policyid_dc is None):
1870 update_policyids(names, ldbs.sam)
1874 update_gpo(paths, ldbs.sam, names, lp, message)
1875 except ProvisioningError, e:
1876 message(ERROR, "The policy for domain controller is missing. "
1877 "You should restart upgradeprovision with --full")
1879 ldbs.groupedCommit()
1880 new_ldbs.groupedCommit()
1881 message(SIMPLE, "Upgrade finished!")
1882 # remove reference provision now that everything is done !
1883 # So we have reindexed first if need when the merged schema was reloaded
1884 # (as new attributes could have quick in)
1885 # But the second part of the update (when we update existing objects
1886 # can also have an influence on indexing as some attribute might have their
1887 # searchflag modificated
1888 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1889 "after modification")
1890 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1891 message(SIMPLE, "Reindexing finished")
1893 shutil.rmtree(provisiondir)
1894 except StandardError, err:
1895 message(ERROR, "A problem occurred while trying to upgrade your "
1896 "provision. A full backup is located at %s" % backupdir)
1897 if opts.debugall or opts.debugchange:
1898 (typ, val, tb) = sys.exc_info()
1899 traceback.print_exception(typ, val, tb)