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
39 from samba.samdb import get_default_backend_store
41 from base64 import b64encode
42 from samba.credentials import DONT_USE_KERBEROS
43 from samba.auth import system_session, admin_session
44 from samba import tdb_util
45 from samba import mdb_util
46 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
47 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
48 MessageElement, Message, Dn, LdbError)
49 from samba import param, dsdb, Ldb
50 from samba.common import confirm
51 from samba.descriptor import get_wellknown_sds, get_empty_descriptor, get_diff_sds
52 from samba.provision import (find_provision_key_parameters,
53 ProvisioningError, get_last_provision_usn,
54 get_max_usn, update_provision_usn, setup_path)
55 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
56 from samba.dcerpc import security, drsblobs
57 from samba.dcerpc.security import (
58 SECINFO_OWNER, SECINFO_GROUP, SECINFO_DACL, SECINFO_SACL)
59 from samba.ndr import ndr_unpack
60 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
61 get_ldbs, findprovisionrange,
62 usn_in_range, identic_rename,
63 update_secrets, CHANGE, ERROR, SIMPLE,
64 CHANGEALL, GUESS, CHANGESD, PROVISION,
65 updateOEMInfo, getOEMInfo, update_gpo,
66 delta_update_basesamdb, update_policyids,
67 update_machine_account_password,
68 search_constructed_attrs_stored,
69 int64range2str, update_dns_account_password,
70 increment_calculated_keyversion_number,
71 print_provision_ranges)
72 from samba.xattr import copytree_with_xattrs
74 # make sure the script dies immediately when hitting control-C,
75 # rather than raising KeyboardInterrupt. As we do all database
76 # operations using transactions, this is safe.
78 signal.signal(signal.SIGINT, signal.SIG_DFL)
80 replace=2**FLAG_MOD_REPLACE
82 delete=2**FLAG_MOD_DELETE
86 # Will be modified during provision to tell if default sd has been modified
89 #Errors are always logged
91 __docformat__ = "restructuredText"
93 # Attributes that are never copied from the reference provision (even if they
94 # do not exist in the destination object).
95 # This is most probably because they are populated automatcally when object is
97 # This also apply to imported object from reference provision
98 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
99 "parentGUID", "distinguishedName",
100 "instanceType", "cn",
101 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
102 "unicodePwd", "dBCSPwd", "supplementalCredentials",
103 "gPCUserExtensionNames", "gPCMachineExtensionNames",
104 "maxPwdAge", "secret", "possibleInferiors", "privilege",
105 "sAMAccountType", "oEMInformation", "creationTime" ]
107 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
108 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
110 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
113 attrNotCopied = replAttrNotCopied
114 attrNotCopied.extend(nonreplAttrNotCopied)
115 attrNotCopied.extend(nonDSDBAttrNotCopied)
116 # Usually for an object that already exists we do not overwrite attributes as
117 # they might have been changed for good reasons. Anyway for a few of them it's
118 # mandatory to replace them otherwise the provision will be broken somehow.
119 # But for attribute that are just missing we do not have to specify them as the default
120 # behavior is to add missing attribute
121 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
122 "systemOnly":replace, "searchFlags":replace,
123 "mayContain":replace, "systemFlags":replace+add,
124 "description":replace, "operatingSystemVersion":replace,
125 "adminPropertyPages":replace, "groupType":replace,
126 "wellKnownObjects":replace, "privilege":never,
127 "rIDAvailablePool": never,
128 "versionNumber" : add,
129 "rIDNextRID": add, "rIDUsedPool": never,
130 "defaultSecurityDescriptor": replace + add,
131 "isMemberOfPartialAttributeSet": delete,
132 "attributeDisplayNames": replace + add,
133 "versionNumber": add}
135 dnNotToRecalculateFound = False
138 forwardlinked = set()
141 def define_what_to_log(opts):
145 if opts.debugchangesd:
146 what = what | CHANGESD
149 if opts.debugprovision:
150 what = what | PROVISION
152 what = what | CHANGEALL
156 parser = optparse.OptionParser("provision [options]")
157 sambaopts = options.SambaOptions(parser)
158 parser.add_option_group(sambaopts)
159 parser.add_option_group(options.VersionOptions(parser))
160 credopts = options.CredentialsOptions(parser)
161 parser.add_option_group(credopts)
162 parser.add_option("--setupdir", type="string", metavar="DIR",
163 help="directory with setup files")
164 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
165 parser.add_option("--debugguess", action="store_true",
166 help="Print information on which values are guessed")
167 parser.add_option("--debugchange", action="store_true",
168 help="Print information on what is different but won't be changed")
169 parser.add_option("--debugchangesd", action="store_true",
170 help="Print security descriptor differences")
171 parser.add_option("--debugall", action="store_true",
172 help="Print all available information (very verbose)")
173 parser.add_option("--db_backup_only", action="store_true",
174 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
175 parser.add_option("--full", action="store_true",
176 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
177 parser.add_option("--very-old-pre-alpha9", action="store_true",
178 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
180 opts = parser.parse_args()[0]
182 handler = logging.StreamHandler(sys.stdout)
183 upgrade_logger = logging.getLogger("upgradeprovision")
184 upgrade_logger.setLevel(logging.INFO)
186 upgrade_logger.addHandler(handler)
188 provision_logger = logging.getLogger("provision")
189 provision_logger.addHandler(handler)
191 whatToLog = define_what_to_log(opts)
193 def message(what, text):
194 """Print a message if this message type has been selected to be printed
196 :param what: Category of the message
197 :param text: Message to print """
198 if (whatToLog & what) or what <= 0:
199 upgrade_logger.info("%s", text)
201 if len(sys.argv) == 1:
202 opts.interactive = True
203 lp = sambaopts.get_loadparm()
204 smbconf = lp.configfile
206 creds = credopts.get_credentials(lp)
207 creds.set_kerberos_state(DONT_USE_KERBEROS)
211 def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
212 """Check if the provision has already the requirement for dynamic dns
214 :param refprivate: The path to the private directory of the reference
216 :param private: The path to the private directory of the upgraded
219 spnfile = "%s/spn_update_list" % private
220 dnsfile = "%s/dns_update_list" % private
222 if not os.path.exists(spnfile):
223 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
225 if not os.path.exists(dnsfile):
226 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
228 if not os.path.exists(binddns_dir):
229 os.mkdir(binddns_dir)
231 if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
234 namedfile = lp.get("dnsupdate:path")
236 namedfile = "%s/named.conf.update" % binddns_dir
237 if not os.path.exists(namedfile):
238 destdir = "%s/new_dns" % binddns_dir
239 dnsdir = "%s/dns" % binddns_dir
241 if not os.path.exists(destdir):
243 if not os.path.exists(dnsdir):
245 shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
246 shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
247 message(SIMPLE, "It seems that your provision did not integrate "
248 "new rules for dynamic dns update of domain related entries")
249 message(SIMPLE, "A copy of the new bind configuration files and "
250 "template has been put in %s, you should read them and "
251 "configure dynamic dns updates" % destdir)
254 def populate_links(samdb, schemadn):
255 """Populate an array with all the back linked attributes
257 This attributes that are modified automaticaly when
258 front attibutes are changed
260 :param samdb: A LDB object for sam.ldb file
261 :param schemadn: DN of the schema for the partition"""
262 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
263 backlinked.extend(linkedAttHash.values())
264 for t in linkedAttHash.keys():
267 def isReplicated(att):
268 """ Indicate if the attribute is replicated or not
270 :param att: Name of the attribute to be tested
271 :return: True is the attribute is replicated, False otherwise
274 return (att not in not_replicated)
276 def populateNotReplicated(samdb, schemadn):
277 """Populate an array with all the attributes that are not replicated
279 :param samdb: A LDB object for sam.ldb file
280 :param schemadn: DN of the schema for the partition"""
281 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
282 str(schemadn)), scope=SCOPE_SUBTREE,
283 attrs=["lDAPDisplayName"])
285 not_replicated.append(str(elem["lDAPDisplayName"]))
288 def populate_dnsyntax(samdb, schemadn):
289 """Populate an array with all the attributes that have DN synthax
292 :param samdb: A LDB object for sam.ldb file
293 :param schemadn: DN of the schema for the partition"""
294 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
295 str(schemadn)), scope=SCOPE_SUBTREE,
296 attrs=["lDAPDisplayName"])
298 dn_syntax_att.append(elem["lDAPDisplayName"])
301 def sanitychecks(samdb, names):
302 """Make some checks before trying to update
304 :param samdb: An LDB object opened on sam.ldb
305 :param names: list of key provision parameters
306 :return: Status of check (1 for Ok, 0 for not Ok) """
307 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
308 scope=SCOPE_SUBTREE, attrs=["dn"],
309 controls=["search_options:1:2"])
311 print("No DC found. Your provision is most probably broken!")
314 print("Found %d domain controllers. For the moment " \
315 "upgradeprovision is not able to handle an upgrade on a " \
316 "domain with more than one DC. Please demote the other " \
317 "DC(s) before upgrading") % len(res)
323 def print_provision_key_parameters(names):
324 """Do a a pretty print of provision parameters
326 :param names: list of key provision parameters """
327 message(GUESS, "rootdn :" + str(names.rootdn))
328 message(GUESS, "configdn :" + str(names.configdn))
329 message(GUESS, "schemadn :" + str(names.schemadn))
330 message(GUESS, "serverdn :" + str(names.serverdn))
331 message(GUESS, "netbiosname :" + names.netbiosname)
332 message(GUESS, "defaultsite :" + names.sitename)
333 message(GUESS, "dnsdomain :" + names.dnsdomain)
334 message(GUESS, "hostname :" + names.hostname)
335 message(GUESS, "domain :" + names.domain)
336 message(GUESS, "realm :" + names.realm)
337 message(GUESS, "invocationid:" + names.invocation)
338 message(GUESS, "policyguid :" + names.policyid)
339 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
340 message(GUESS, "domainsid :" + str(names.domainsid))
341 message(GUESS, "domainguid :" + names.domainguid)
342 message(GUESS, "ntdsguid :" + names.ntdsguid)
343 message(GUESS, "domainlevel :" + str(names.domainlevel))
346 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
347 """Define more complicate update rules for some attributes
349 :param att: The attribute to be updated
350 :param delta: A messageElement object that correspond to the difference
351 between the updated object and the reference one
352 :param new: The reference object
353 :param old: The Updated object
354 :param useReplMetadata: A boolean that indicate if the update process
355 use replPropertyMetaData to decide what has to be updated.
356 :param basedn: The base DN of the provision
357 :param aldb: An ldb object used to build DN
358 :return: True to indicate that the attribute should be kept, False for
361 # We do most of the special case handle if we do not have the
362 # highest usn as otherwise the replPropertyMetaData will guide us more
364 if not useReplMetadata:
365 flag = delta.get(att).flags()
366 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
367 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
368 "CN=Services,CN=Configuration,%s" % basedn)
371 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
372 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
374 message(SIMPLE, "We suggest that you change the userAccountControl"
375 " for user Administrator from value %d to %d" %
376 (int(str(old[0][att])), int(str(new[0][att]))))
378 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
379 if (int(str(old[0][att])) == 0):
380 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
383 if (att == "member" and flag == FLAG_MOD_REPLACE):
387 for elem in old[0][att]:
388 hash[str(elem).lower()]=1
389 newval.append(str(elem))
391 for elem in new[0][att]:
392 if not hash.has_key(str(elem).lower()):
394 newval.append(str(elem))
396 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
401 if (att in ("gPLink", "gPCFileSysPath") and
402 flag == FLAG_MOD_REPLACE and
403 str(new[0].dn).lower() == str(old[0].dn).lower()):
407 if att == "forceLogoff":
408 ref=0x8000000000000000
409 oldval=int(old[0][att][0])
410 newval=int(new[0][att][0])
411 ref == old and ref == abs(new)
414 if att in ("adminDisplayName", "adminDescription"):
417 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
418 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
421 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
422 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
425 if (str(old[0].dn) == "%s" % (str(names.rootdn))
426 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
428 #Allow to change revision of ForestUpdates objects
429 if (att == "revision" or att == "objectVersion"):
430 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
432 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
435 # This is a bit of special animal as we might have added
436 # already SPN entries to the list that has to be modified
437 # So we go in detail to try to find out what has to be added ...
438 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
442 for elem in old[0][att]:
444 newval.append(str(elem))
446 for elem in new[0][att]:
447 if not hash.has_key(str(elem)):
449 newval.append(str(elem))
451 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
458 def dump_denied_change(dn, att, flagtxt, current, reference):
459 """Print detailed information about why a change is denied
461 :param dn: DN of the object which attribute is denied
462 :param att: Attribute that was supposed to be upgraded
463 :param flagtxt: Type of the update that should be performed
464 (add, change, remove, ...)
465 :param current: Value(s) of the current attribute
466 :param reference: Value(s) of the reference attribute"""
468 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
469 + " must not be changed/removed. Discarding the change")
470 if att == "objectSid" :
471 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
472 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
473 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
474 message(CHANGE, "old : %s" % int64range2str(current[0]))
475 message(CHANGE, "new : %s" % int64range2str(reference[0]))
478 for e in range(0, len(current)):
479 message(CHANGE, "old %d : %s" % (i, str(current[e])))
481 if reference is not None:
483 for e in range(0, len(reference)):
484 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
487 def handle_special_add(samdb, dn, names):
488 """Handle special operation (like remove) on some object needed during
491 This is mostly due to wrong creation of the object in previous provision.
492 :param samdb: An Ldb object representing the SAM database
493 :param dn: DN of the object to inspect
494 :param names: list of key provision parameters
498 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
500 #This entry was misplaced lets remove it if it exists
501 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
504 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
506 #This entry was misplaced lets remove it if it exists
507 dntoremove = "CN=Certificate Service DCOM Access,"\
508 "CN=Users, %s" % names.rootdn
510 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
512 #This entry was misplaced lets remove it if it exists
513 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
515 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
517 #This entry was misplaced lets remove it if it exists
518 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
520 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
521 "CN=Configuration,%s" % names.rootdn)
523 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
524 "CN=WellKnown Security Principals,"
525 "CN=Configuration,%s" % names.rootdn)
527 res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
528 base=str(names.rootdn),
529 scope=SCOPE_SUBTREE, attrs=["dn"],
530 controls=["search_options:1:2"])
532 res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
533 base=str(names.rootdn),
534 scope=SCOPE_SUBTREE, attrs=["dn"],
535 controls=["search_options:1:2"])
537 if len(res) > 0 and len(res2) == 0:
538 message(CHANGE, "Existing object %s must be replaced by %s. "
539 "Renaming old object" % (str(oldDn), str(dn)))
540 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
544 if dntoremove is not None:
545 res = samdb.search(expression="(cn=RID Set)",
546 base=str(names.rootdn),
547 scope=SCOPE_SUBTREE, attrs=["dn"],
548 controls=["search_options:1:2"])
552 res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
553 base=str(names.rootdn),
554 scope=SCOPE_SUBTREE, attrs=["dn"],
555 controls=["search_options:1:2"])
557 message(CHANGE, "Existing object %s must be replaced by %s. "
558 "Removing old object" % (dntoremove, str(dn)))
559 samdb.delete(res[0]["dn"])
565 def check_dn_nottobecreated(hash, index, listdn):
566 """Check if one of the DN present in the list has a creation order
567 greater than the current.
569 Hash is indexed by dn to be created, with each key
570 is associated the creation order.
572 First dn to be created has the creation order 0, second has 1, ...
573 Index contain the current creation order
575 :param hash: Hash holding the different DN of the object to be
577 :param index: Current creation order
578 :param listdn: List of DNs on which the current DN depends on
579 :return: None if the current object do not depend on other
580 object or if all object have been created before."""
584 key = str(dn).lower()
585 if hash.has_key(key) and hash[key] > index:
591 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
592 """Add a new object if the dependencies are satisfied
594 The function add the object if the object on which it depends are already
597 :param ref_samdb: Ldb object representing the SAM db of the reference
599 :param samdb: Ldb object representing the SAM db of the upgraded
601 :param dn: DN of the object to be added
602 :param names: List of key provision parameters
603 :param basedn: DN of the partition to be updated
604 :param hash: Hash holding the different DN of the object to be
606 :param index: Current creation order
607 :return: True if the object was created False otherwise"""
609 ret = handle_special_add(samdb, dn, names)
618 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
619 base=basedn, scope=SCOPE_SUBTREE,
620 controls=["search_options:1:2"])
622 delta = samdb.msg_diff(empty, reference[0])
626 if str(reference[0].get("cn")) == "RID Set":
627 for klass in reference[0].get("objectClass"):
628 if str(klass).lower() == "ridset":
631 if delta.get("objectSid"):
632 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
633 m = re.match(r".*-(\d+)$", sid)
634 if m and int(m.group(1))>999:
635 delta.remove("objectSid")
636 for att in attrNotCopied:
638 for att in backlinked:
640 for att in dn_syntax_att:
641 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
643 if depend_on_yet_tobecreated is not None:
644 message(CHANGE, "Object %s depends on %s in attribute %s. "
645 "Delaying the creation" % (dn,
646 depend_on_yet_tobecreated, att))
651 message(CHANGE,"Object %s will be added" % dn)
652 samdb.add(delta, ["relax:0", "provision:0"])
654 message(CHANGE,"Object %s was skipped" % dn)
658 def gen_dn_index_hash(listMissing):
659 """Generate a hash associating the DN to its creation order
661 :param listMissing: List of DN
662 :return: Hash with DN as keys and creation order as values"""
664 for i in range(0, len(listMissing)):
665 hash[str(listMissing[i]).lower()] = i
668 def add_deletedobj_containers(ref_samdb, samdb, names):
669 """Add the object containter: CN=Deleted Objects
671 This function create the container for each partition that need one and
672 then reference the object into the root of the partition
674 :param ref_samdb: Ldb object representing the SAM db of the reference
676 :param samdb: Ldb object representing the SAM db of the upgraded provision
677 :param names: List of key provision parameters"""
680 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
681 partitions = [str(names.rootdn), str(names.configdn)]
682 for part in partitions:
683 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
684 base=part, scope=SCOPE_SUBTREE,
686 controls=["show_deleted:0",
688 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
689 base=part, scope=SCOPE_SUBTREE,
691 controls=["show_deleted:0",
693 if len(ref_delObjCnt) > len(delObjCnt):
694 reference = ref_samdb.search(expression="cn=Deleted Objects",
695 base=part, scope=SCOPE_SUBTREE,
696 controls=["show_deleted:0",
699 delta = samdb.msg_diff(empty, reference[0])
701 delta.dn = Dn(samdb, str(reference[0]["dn"]))
702 for att in attrNotCopied:
705 modcontrols = ["relax:0", "provision:0"]
706 samdb.add(delta, modcontrols)
709 res = samdb.search(expression="(objectClass=*)", base=part,
711 attrs=["dn", "wellKnownObjects"])
713 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
717 wko = res[0]["wellKnownObjects"]
719 # The wellKnownObject that we want to add.
721 if str(o) == targetWKO:
723 listwko.append(str(o))
726 listwko.append(targetWKO)
729 delta.dn = Dn(samdb, str(res[0]["dn"]))
730 delta["wellKnownObjects"] = MessageElement(listwko,
735 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
736 """Add the missing object whose DN is the list
738 The function add the object if the objects on which it depends are
741 :param ref_samdb: Ldb object representing the SAM db of the reference
743 :param samdb: Ldb object representing the SAM db of the upgraded
745 :param dn: DN of the object to be added
746 :param names: List of key provision parameters
747 :param basedn: DN of the partition to be updated
748 :param list: List of DN to be added in the upgraded provision"""
753 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
755 listMissing = listDefered
757 hashMissing = gen_dn_index_hash(listMissing)
758 for dn in listMissing:
759 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
763 # DN can't be created because it depends on some
764 # other DN in the list
765 listDefered.append(dn)
767 if len(listDefered) != 0:
768 raise ProvisioningError("Unable to insert missing elements: "
769 "circular references")
771 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
772 """This function handle updates on links
774 :param samdb: An LDB object pointing to the updated provision
775 :param att: Attribute to update
776 :param basedn: The root DN of the provision
777 :param dn: The DN of the inspected object
778 :param value: The value of the attribute
779 :param ref_value: The value of this attribute in the reference provision
780 :param delta: The MessageElement object that will be applied for
781 transforming the current provision"""
783 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
792 newlinklist.append(str(v))
796 # for w2k domain level the reveal won't reveal anything ...
797 # it means that we can readd links that were removed on purpose ...
798 # Also this function in fact just accept add not removal
800 for e in res[0][att]:
801 if not hash.has_key(e):
802 # We put in the blacklist all the element that are in the "revealed"
803 # result and not in the "standard" result
804 # This element are links that were removed before and so that
805 # we don't wan't to readd
809 if not blacklist.has_key(e) and not hash.has_key(e):
810 newlinklist.append(str(e))
813 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
820 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
821 hash_attr_usn, basedn, usns, samdb):
822 """ Check if we should keep the attribute modification or not
824 :param delta: A message diff object
825 :param att: An attribute
826 :param message: A function to print messages
827 :param reference: A message object for the current entry comming from
828 the reference provision.
829 :param current: A message object for the current entry commin from
830 the current provision.
831 :param hash_attr_usn: A dictionnary with attribute name as keys,
832 USN and invocation id as values.
833 :param basedn: The DN of the partition
834 :param usns: A dictionnary with invocation ID as keys and USN ranges
836 :param samdb: A ldb object pointing to the sam DB
838 :return: The modified message diff.
845 for att in list(delta):
846 if att in ["dn", "objectSid"]:
850 # We have updated by provision usn information so let's exploit
851 # replMetadataProperties
852 if att in forwardlinked:
853 curval = current[0].get(att, ())
854 refval = reference[0].get(att, ())
855 delta = handle_links(samdb, att, basedn, current[0]["dn"],
856 curval, refval, delta)
860 if isFirst and len(list(delta)) > 1:
862 txt = "%s\n" % (str(dn))
864 if handle_special_case(att, delta, reference, current, True, None, None):
865 # This attribute is "complicated" to handle and handling
866 # was done in handle_special_case
870 if hash_attr_usn.get(att):
871 [attrUSN, attInvId] = hash_attr_usn.get(att)
874 # If it's a replicated attribute and we don't have any USN
875 # information about it. It means that we never saw it before
877 # If it is a replicated attribute but we are not master on it
878 # (ie. not initially added in the provision we masterize).
880 if isReplicated(att):
883 message(CHANGE, "Non replicated attribute %s changed" % att)
886 if att == "nTSecurityDescriptor":
887 cursd = ndr_unpack(security.descriptor,
888 str(current[0]["nTSecurityDescriptor"]))
889 refsd = ndr_unpack(security.descriptor,
890 str(reference[0]["nTSecurityDescriptor"]))
892 diff = get_diff_sds(refsd, cursd, names.domainsid)
894 # FIXME find a way to have it only with huge huge verbose mode
895 # message(CHANGE, "%ssd are identical" % txt)
901 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
904 message(CHANGESD, "But the SD has been changed by someonelse "
905 "so it's impossible to know if the difference"
906 " cames from the modification or from a previous bug")
907 global dnNotToRecalculateFound
908 dnNotToRecalculateFound = True
910 dnToRecalculate.append(dn)
914 # This attribute was last modified by another DC forget
916 message(CHANGE, "%sAttribute: %s has been "
917 "created/modified/deleted by another DC. "
918 "Doing nothing" % (txt, att))
922 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
923 message(CHANGE, "%sAttribute: %s was not "
924 "created/modified/deleted during a "
925 "provision or upgradeprovision. Current "
926 "usn: %d. Doing nothing" % (txt, att,
932 if att == "defaultSecurityDescriptor":
935 message(CHANGE, "%sAttribute: %s will be modified"
936 "/deleted it was last modified "
937 "during a provision. Current usn: "
938 "%d" % (txt, att, attrUSN))
941 message(CHANGE, "%sAttribute: %s will be added because "
942 "it did not exist before" % (txt, att))
948 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
949 """ This function updates the object that are already present in the
952 :param ref_samdb: An LDB object pointing to the reference provision
953 :param samdb: An LDB object pointing to the updated provision
954 :param basedn: A string with the value of the base DN for the provision
956 :param listPresent: A list of object that is present in the provision
957 :param usns: A list of USN range modified by previous provision and
958 upgradeprovision grouped by invocation ID
961 # This hash is meant to speedup lookup of attribute name from an oid,
962 # it's for the replPropertyMetaData handling
964 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
965 controls=["search_options:1:2"], attrs=["attributeID",
969 strDisplay = str(e.get("lDAPDisplayName"))
970 hash_oid_name[str(e.get("attributeID"))] = strDisplay
972 msg = "Unable to insert missing elements: circular references"
973 raise ProvisioningError(msg)
976 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
977 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
978 message(CHANGE, "Using replPropertyMetadata for change selection")
979 for dn in listPresent:
980 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
983 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
984 scope=SCOPE_SUBTREE, controls=controls)
987 (str(current[0].dn) != str(reference[0].dn)) and
988 (str(current[0].dn).upper() == str(reference[0].dn).upper())
990 message(CHANGE, "Names are the same except for the case. "
991 "Renaming %s to %s" % (str(current[0].dn),
992 str(reference[0].dn)))
993 identic_rename(samdb, reference[0].dn)
994 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
998 delta = samdb.msg_diff(current[0], reference[0])
1000 for att in backlinked:
1003 for att in attrNotCopied:
1006 delta.remove("name")
1008 nb_items = len(list(delta))
1014 # Fetch the replPropertyMetaData
1015 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1016 scope=SCOPE_SUBTREE, controls=controls,
1017 attrs=["replPropertyMetaData"])
1018 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1019 str(res[0]["replPropertyMetaData"])).ctr
1023 # We put in this hash only modification
1024 # made on the current host
1025 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1026 if str(o.originating_invocation_id) in usns.keys():
1027 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1029 hash_attr_usn[att] = [-1, None]
1031 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1032 current, hash_attr_usn,
1033 basedn, usns, samdb)
1039 # Skip dn as the value is not really changed ...
1040 attributes=", ".join(delta.keys()[1:])
1042 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1043 # Let's try to reduce as much as possible the use of relax control
1044 for attr in delta.keys():
1045 if attr.lower() in relaxedatt:
1046 modcontrols = ["relax:0", "provision:0"]
1047 message(CHANGE, "%s is different from the reference one, changed"
1048 " attributes: %s\n" % (dn, attributes))
1050 samdb.modify(delta, modcontrols)
1053 def reload_full_schema(samdb, names):
1054 """Load the updated schema with all the new and existing classes
1057 :param samdb: An LDB object connected to the sam.ldb of the update
1059 :param names: List of key provision parameters
1062 schemadn = str(names.schemadn)
1063 current = samdb.search(expression="objectClass=*", base=schemadn,
1064 scope=SCOPE_SUBTREE)
1069 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1071 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1072 prefixmap_data = b64encode(prefixmap_data).decode('utf8')
1074 # We don't actually add this ldif, just parse it
1075 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1077 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1080 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1081 """Check differences between the reference provision and the upgraded one.
1083 It looks for all objects which base DN is name.
1085 This function will also add the missing object and update existing object
1086 to add or remove attributes that were missing.
1088 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1090 :param samdb: An LDB object connected to the sam.ldb of the update
1092 :param basedn: String value of the DN of the partition
1093 :param names: List of key provision parameters
1094 :param schema: A Schema object
1095 :param provisionUSNs: A dictionnary with range of USN modified during provision
1096 or upgradeprovision. Ranges are grouped by invocationID.
1097 :param prereloadfunc: A function that must be executed just before the reload
1108 # Connect to the reference provision and get all the attribute in the
1109 # partition referred by name
1110 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1111 scope=SCOPE_SUBTREE, attrs=["dn"],
1112 controls=["search_options:1:2"])
1114 current = samdb.search(expression="objectClass=*", base=basedn,
1115 scope=SCOPE_SUBTREE, attrs=["dn"],
1116 controls=["search_options:1:2"])
1117 # Create a hash for speeding the search of new object
1118 for i in range(0, len(reference)):
1119 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1121 # Create a hash for speeding the search of existing object in the
1123 for i in range(0, len(current)):
1124 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1127 for k in hash_new.keys():
1128 if not hash.has_key(k):
1129 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1130 listMissing.append(hash_new[k])
1132 listPresent.append(hash_new[k])
1134 # Sort the missing object in order to have object of the lowest level
1135 # first (which can be containers for higher level objects)
1136 listMissing.sort(dn_sort)
1137 listPresent.sort(dn_sort)
1139 # The following lines is to load the up to
1140 # date schema into our current LDB
1141 # a complete schema is needed as the insertion of attributes
1142 # and class is done against it
1143 # and the schema is self validated
1144 samdb.set_schema(schema)
1146 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1147 add_deletedobj_containers(ref_samdb, samdb, names)
1149 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1152 message(SIMPLE, "Reloading a merged schema, which might trigger "
1153 "reindexing so please be patient")
1154 reload_full_schema(samdb, names)
1155 message(SIMPLE, "Schema reloaded!")
1157 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1159 message(SIMPLE, "There are %d changed objects" % (changed))
1162 except StandardError as err:
1163 message(ERROR, "Exception during upgrade of samdb:")
1164 (typ, val, tb) = sys.exc_info()
1165 traceback.print_exception(typ, val, tb)
1169 def check_updated_sd(ref_sam, cur_sam, names):
1170 """Check if the security descriptor in the upgraded provision are the same
1173 :param ref_sam: A LDB object connected to the sam.ldb file used as
1174 the reference provision
1175 :param cur_sam: A LDB object connected to the sam.ldb file used as
1177 :param names: List of key provision parameters"""
1178 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1179 scope=SCOPE_SUBTREE,
1180 attrs=["dn", "nTSecurityDescriptor"],
1181 controls=["search_options:1:2"])
1182 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1183 scope=SCOPE_SUBTREE,
1184 attrs=["dn", "nTSecurityDescriptor"],
1185 controls=["search_options:1:2"])
1187 for i in range(0, len(reference)):
1188 refsd_blob = str(reference[i]["nTSecurityDescriptor"])
1189 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1192 for i in range(0, len(current)):
1193 key = str(current[i]["dn"]).lower()
1194 if hash.has_key(key):
1195 cursd_blob = str(current[i]["nTSecurityDescriptor"])
1196 cursd = ndr_unpack(security.descriptor,
1198 if cursd_blob != hash[key]:
1199 refsd = ndr_unpack(security.descriptor,
1201 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1203 message(CHANGESD, "On object %s ACL is different"
1204 " \n%s" % (current[i]["dn"], txt))
1208 def fix_wellknown_sd(samdb, names):
1209 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1210 This is needed because some provision use to have broken SD on containers
1212 :param samdb: An LDB object pointing to the sam of the current provision
1213 :param names: A list of key provision parameters
1216 list_wellknown_dns = []
1218 subcontainers = get_wellknown_sds(samdb)
1220 for [dn, descriptor_fn] in subcontainers:
1221 list_wellknown_dns.append(dn)
1222 if dn in dnToRecalculate:
1225 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1226 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1227 "nTSecurityDescriptor" )
1229 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1231 return list_wellknown_dns
1233 def rebuild_sd(samdb, names):
1234 """Rebuild security descriptor of the current provision from scratch
1236 During the different pre release of samba4 security descriptors
1237 (SD) were notarly broken (up to alpha11 included)
1239 This function allows one to get them back in order, this function works
1240 only after the database comparison that --full mode uses and which
1241 populates the dnToRecalculate and dnNotToRecalculate lists.
1243 The idea is that the SD can be safely recalculated from scratch to get it right.
1245 :param names: List of key provision parameters"""
1247 listWellknown = fix_wellknown_sd(samdb, names)
1249 if len(dnToRecalculate) != 0:
1250 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1251 % (len(dnToRecalculate)))
1253 for dn in dnToRecalculate:
1254 # well known SDs have already been reset
1255 if dn in listWellknown:
1259 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1261 descr = get_empty_descriptor(names.domainsid)
1262 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1263 "nTSecurityDescriptor")
1264 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1265 except LdbError as e:
1266 samdb.transaction_cancel()
1267 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1269 attrs=["nTSecurityDescriptor"],
1270 controls=["sd_flags:1:%d" % sd_flags])
1271 badsd = ndr_unpack(security.descriptor,
1272 str(res[0]["nTSecurityDescriptor"]))
1273 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1276 def hasATProvision(samdb):
1277 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1281 if entry is not None and len(entry) == 1:
1286 def removeProvisionUSN(samdb):
1287 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1288 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1292 empty.dn = entry[0].dn
1293 delta = samdb.msg_diff(entry[0], empty)
1295 delta.dn = entry[0].dn
1298 def remove_stored_generated_attrs(paths, creds, session, lp):
1299 """Remove previously stored constructed attributes
1301 :param paths: List of paths for different provision objects
1302 from the upgraded provision
1303 :param creds: A credential object
1304 :param session: A session object
1305 :param lp: A line parser object
1306 :return: An associative array whose key are the different constructed
1307 attributes and the value the dn where this attributes were found.
1311 def simple_update_basesamdb(newpaths, paths, names):
1312 """Update the provision container db: sam.ldb
1313 This function is aimed at very old provision (before alpha9)
1315 :param newpaths: List of paths for different provision objects
1316 from the reference provision
1317 :param paths: List of paths for different provision objects
1318 from the upgraded provision
1319 :param names: List of key provision parameters"""
1321 message(SIMPLE, "Copy samdb")
1322 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1324 message(SIMPLE, "Update partitions filename if needed")
1325 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1326 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1327 usersldb = os.path.join(paths.private_dir, "users.ldb")
1328 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1330 if not os.path.isdir(samldbdir):
1332 os.chmod(samldbdir, 0700)
1333 if os.path.isfile(schemaldb):
1334 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1335 "%s.ldb"%str(names.schemadn).upper()))
1336 os.remove(schemaldb)
1337 if os.path.isfile(usersldb):
1338 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1339 "%s.ldb"%str(names.rootdn).upper()))
1341 if os.path.isfile(configldb):
1342 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1343 "%s.ldb"%str(names.configdn).upper()))
1344 os.remove(configldb)
1347 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1348 """Upgrade the SAM DB contents for all the provision partitions
1350 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1352 :param samdb: An LDB object connected to the sam.ldb of the update
1354 :param names: List of key provision parameters
1355 :param provisionUSNs: A dictionnary with range of USN modified during provision
1356 or upgradeprovision. Ranges are grouped by invocationID.
1357 :param schema: A Schema object that represent the schema of the provision
1358 :param prereloadfunc: A function that must be executed just before the reload
1362 message(SIMPLE, "Starting update of samdb")
1363 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1364 schema, provisionUSNs, prereloadfunc)
1366 message(SIMPLE, "Update of samdb finished")
1369 message(SIMPLE, "Update failed")
1373 def backup_provision(samdb, paths, dir, only_db):
1374 """This function backup the provision files so that a rollback
1377 :param paths: Paths to different objects
1378 :param dir: Directory where to store the backup
1379 :param only_db: Skip sysvol for users with big sysvol
1382 # Currently we default to tdb for the backend store type
1384 backend_store = "tdb"
1385 res = samdb.search(base="@PARTITION",
1386 scope=ldb.SCOPE_BASE,
1387 attrs=["backendStore"])
1388 if "backendStore" in res[0]:
1389 backend_store = res[0]["backendStore"][0]
1392 if paths.sysvol and not only_db:
1393 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1395 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1396 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1397 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1398 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1399 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1400 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1401 shutil.copy2(paths.smbconf, dir)
1402 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1404 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1405 if not os.path.isdir(samldbdir):
1406 samldbdir = paths.private_dir
1407 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1408 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1409 usersldb = os.path.join(paths.private_dir, "users.ldb")
1410 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1411 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1412 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1414 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0700)
1416 for ldb_name in os.listdir(samldbdir):
1417 if not ldb_name.endswith("-lock"):
1418 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1419 mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1420 os.path.join(dir, "sam.ldb.d", ldb_name))
1422 tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1423 os.path.join(dir, "sam.ldb.d", ldb_name))
1426 def sync_calculated_attributes(samdb, names):
1427 """Synchronize attributes used for constructed ones, with the
1428 old constructed that were stored in the database.
1430 This apply for instance to msds-keyversionnumber that was
1431 stored and that is now constructed from replpropertymetadata.
1433 :param samdb: An LDB object attached to the currently upgraded samdb
1434 :param names: Various key parameter about current provision.
1436 listAttrs = ["msDs-KeyVersionNumber"]
1437 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1438 if hash.has_key("msDs-KeyVersionNumber"):
1439 increment_calculated_keyversion_number(samdb, names.rootdn,
1440 hash["msDs-KeyVersionNumber"])
1442 # Synopsis for updateprovision
1443 # 1) get path related to provision to be update (called current)
1444 # 2) open current provision ldbs
1445 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1447 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1448 # by either upgradeprovision or provision
1449 # 5) creation of a new provision the latest version of provision script
1450 # (called reference)
1451 # 6) get reference provision paths
1452 # 7) open reference provision ldbs
1453 # 8) setup helpers data that will help the update process
1454 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1455 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1456 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1457 # 10)get the oemInfo field, this field contains information about the different
1458 # provision that have been done
1459 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1460 # A) When alpha9 or alphaxx not specified (default)
1461 # The base sam.ldb file is updated by looking at the difference between
1462 # referrence one and the current one. Everything is copied with the
1463 # exception of lastProvisionUSN attributes.
1464 # B) Other case (it reflect that that provision was done before alpha9)
1465 # The base sam.ldb of the reference provision is copied over
1466 # the current one, if necessary ldb related to partitions are moved
1468 # The highest used USN is fetched so that changed by upgradeprovision
1469 # usn can be tracked
1470 # 12)A Schema object is created, it will be used to provide a complete
1471 # schema to current provision during update (as the schema of the
1472 # current provision might not be complete and so won't allow some
1473 # object to be created)
1474 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1475 # 14)The secrets db is updated by pull all the difference from the reference
1476 # provision into the current provision
1477 # 15)As the previous step has most probably modified the password stored in
1478 # in secret for the current DC, a new password is generated,
1479 # the kvno is bumped and the entry in samdb is also updated
1480 # 16)For current provision older than alpha9, we must fix the SD a little bit
1481 # administrator to update them because SD used to be generated with the
1482 # system account before alpha9.
1483 # 17)The highest usn modified so far is searched in the database it will be
1484 # the upper limit for usn modified during provision.
1485 # This is done before potential SD recalculation because we do not want
1486 # SD modified during recalculation to be marked as modified during provision
1487 # (and so possibly remplaced at next upgradeprovision)
1488 # 18)Rebuilt SD if the flag indicate to do so
1489 # 19)Check difference between SD of reference provision and those of the
1490 # current provision. The check is done by getting the sddl representation
1491 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1492 # Each part is verified separetly, for dacl and sacl ACL is splited into
1493 # ACEs and each ACE is verified separately (so that a permutation in ACE
1494 # didn't raise as an error).
1495 # 20)The oemInfo field is updated to add information about the fact that the
1496 # provision has been updated by the upgradeprovision version xxx
1497 # (the version is the one obtained when starting samba with the --version
1499 # 21)Check if the current provision has all the settings needed for dynamic
1500 # DNS update to work (that is to say the provision is newer than
1501 # january 2010). If not dns configuration file from reference provision
1502 # are copied in a sub folder and the administrator is invited to
1503 # do what is needed.
1504 # 22)If the lastProvisionUSN attribute was present it is updated to add
1505 # the range of usns modified by the current upgradeprovision
1508 # About updating the sam DB
1509 # The update takes place in update_partition function
1510 # This function read both current and reference provision and list all
1511 # the available DN of objects
1512 # If the string representation of a DN in reference provision is
1513 # equal to the string representation of a DN in current provision
1514 # (without taking care of case) then the object is flaged as being
1515 # present. If the object is not present in current provision the object
1516 # is being flaged as missing in current provision. Object present in current
1517 # provision but not in reference provision are ignored.
1518 # Once the list of objects present and missing is done, the deleted object
1519 # containers are created in the differents partitions (if missing)
1521 # Then the function add_missing_entries is called
1522 # This function will go through the list of missing entries by calling
1523 # add_missing_object for the given object. If this function returns 0
1524 # it means that the object needs some other object in order to be created
1525 # The object is reappended at the end of the list to be created later
1526 # (and preferably after all the needed object have been created)
1527 # The function keeps on looping on the list of object to be created until
1528 # it's empty or that the number of deferred creation is equal to the number
1529 # of object that still needs to be created.
1531 # The function add_missing_object will first check if the object can be created.
1532 # That is to say that it didn't depends other not yet created objects
1533 # If requisit can't be fullfilled it exists with 0
1534 # Then it will try to create the missing entry by creating doing
1535 # an ldb_message_diff between the object in the reference provision and
1537 # This resulting object is filtered to remove all the back link attribute
1538 # (ie. memberOf) as they will be created by the other linked object (ie.
1539 # the one with the member attribute)
1540 # All attributes specified in the attrNotCopied array are
1541 # also removed it's most of the time generated attributes
1543 # After missing entries have been added the update_partition function will
1544 # take care of object that exist but that need some update.
1545 # In order to do so the function update_present is called with the list
1546 # of object that are present in both provision and that might need an update.
1548 # This function handle first case mismatch so that the DN in the current
1549 # provision have the same case as in reference provision
1551 # It will then construct an associative array consiting of attributes as
1552 # key and invocationid as value( if the originating invocation id is
1553 # different from the invocation id of the current DC the value is -1 instead).
1555 # If the range of provision modified attributes is present, the function will
1556 # use the replMetadataProperty update method which is the following:
1557 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1558 # creationTime, msDs-KeyVersionNumber, oEMInformation
1559 # Check for each attribute if its usn is within one of the modified by
1560 # provision range and if its originating id is the invocation id of the
1561 # current DC, then validate the update from reference to current.
1562 # If not or if there is no replMetatdataProperty for this attribute then we
1564 # Otherwise (case the range of provision modified attribute is not present) it
1565 # use the following process:
1566 # All attributes that need to be added are accepted at the exeption of those
1567 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1568 # correct flags specified.
1569 # For attributes that need to be modified or removed, a check is performed
1570 # in OverwrittenAtt, if the attribute is present and the modification flag
1571 # (remove, delete) is one of those listed for this attribute then modification
1572 # is accepted. For complicated handling of attribute update, the control is passed
1573 # to handle_special_case
1577 if __name__ == '__main__':
1578 global defSDmodified
1579 defSDmodified = False
1581 # From here start the big steps of the program
1582 # 1) First get files paths
1583 paths = get_paths(param, smbconf=smbconf)
1584 # Get ldbs with the system session, it is needed for searching
1585 # provision parameters
1586 session = system_session()
1588 # This variable will hold the last provision USN once if it exists.
1591 ldbs = get_ldbs(paths, creds, session, lp)
1592 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1593 prefix="backupprovision")
1594 backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1596 ldbs.startTransactions()
1598 # 3) Guess all the needed names (variables in fact) from the current
1600 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1603 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1604 if lastProvisionUSNs is not None:
1606 for k in lastProvisionUSNs.keys():
1607 for r in lastProvisionUSNs[k]:
1611 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1612 (len(lastProvisionUSNs.keys()), v /2 ))
1614 if lastProvisionUSNs.get("default") is not None:
1615 message(CHANGE, "Old style for usn ranges used")
1616 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1617 del lastProvisionUSNs["default"]
1619 message(SIMPLE, "Your provision lacks provision range information")
1620 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1621 ldbs.groupedRollback()
1623 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1624 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1625 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1626 " of hundred of objects or more")
1627 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1630 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1632 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1635 # Objects will be created with the admin session
1636 # (not anymore system session)
1637 adm_session = admin_session(lp, str(names.domainsid))
1638 # So we reget handle on objects
1639 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1641 if not sanitychecks(ldbs.sam, names):
1642 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1643 "Check the messages and correct the errors "
1644 "before rerunning upgradeprovision")
1645 ldbs.groupedRollback()
1648 # Let's see provision parameters
1649 print_provision_key_parameters(names)
1651 # 5) With all this information let's create a fresh new provision used as
1653 message(SIMPLE, "Creating a reference provision")
1654 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1655 prefix="referenceprovision")
1656 result = newprovision(names, session, smbconf, provisiondir,
1657 provision_logger, base_schema="2008_R2")
1658 result.report_logger(provision_logger)
1662 # We need to get a list of object which SD is directly computed from
1663 # defaultSecurityDescriptor.
1664 # This will allow us to know which object we can rebuild the SD in case
1665 # of change of the parent's SD or of the defaultSD.
1666 # Get file paths of this new provision
1667 newpaths = get_paths(param, targetdir=provisiondir)
1668 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1669 new_ldbs.startTransactions()
1671 populateNotReplicated(new_ldbs.sam, names.schemadn)
1672 # 8) Populate some associative array to ease the update process
1673 # List of attribute which are link and backlink
1674 populate_links(new_ldbs.sam, names.schemadn)
1675 # List of attribute with ASN DN synthax)
1676 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1677 # 9) (now skipped, was copy of privileges.ldb)
1679 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1680 # Do some modification on sam.ldb
1681 ldbs.groupedCommit()
1682 new_ldbs.groupedCommit()
1686 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1688 # Starting from alpha9 we can consider that the structure is quite ok
1689 # and that we should do only dela
1690 deltaattr = delta_update_basesamdb(newpaths.samdb,
1698 simple_update_basesamdb(newpaths, paths, names)
1699 ldbs = get_ldbs(paths, creds, session, lp)
1700 removeProvisionUSN(ldbs.sam)
1702 ldbs.startTransactions()
1703 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1704 new_ldbs.startTransactions()
1707 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1708 # We create a closure that will be invoked just before schema reload
1709 def schemareloadclosure():
1710 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1711 options=["modules:"])
1713 if deltaattr is not None and len(deltaattr) > 1:
1716 deltaattr.remove("dn")
1717 for att in deltaattr:
1718 if att.lower() == "dn":
1720 if (deltaattr.get(att) is not None
1721 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1723 elif deltaattr.get(att) is None:
1726 message(CHANGE, "Applying delta to @ATTRIBUTES")
1727 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1728 basesam.modify(deltaattr)
1730 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1731 "there is not only add")
1734 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1735 schema, schemareloadclosure):
1736 message(SIMPLE, "Rolling back all changes. Check the cause"
1738 message(SIMPLE, "Your system is as it was before the upgrade")
1739 ldbs.groupedRollback()
1740 new_ldbs.groupedRollback()
1741 shutil.rmtree(provisiondir)
1744 # Try to reapply the change also when we do not change the sam
1745 # as the delta_upgrade
1746 schemareloadclosure()
1747 sync_calculated_attributes(ldbs.sam, names)
1748 res = ldbs.sam.search(expression="(samaccountname=dns)",
1749 scope=SCOPE_SUBTREE, attrs=["dn"],
1750 controls=["search_options:1:2"])
1752 message(SIMPLE, "You still have the old DNS object for managing "
1753 "dynamic DNS, but you didn't supply --full so "
1754 "a correct update can't be done")
1755 ldbs.groupedRollback()
1756 new_ldbs.groupedRollback()
1757 shutil.rmtree(provisiondir)
1760 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1762 res = ldbs.sam.search(expression="(samaccountname=dns)",
1763 scope=SCOPE_SUBTREE, attrs=["dn"],
1764 controls=["search_options:1:2"])
1767 ldbs.sam.delete(res[0]["dn"])
1768 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1769 scope=SCOPE_SUBTREE, attrs=["dn"])
1770 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1771 message(SIMPLE, "IMPORTANT!!! "
1772 "If you were using Dynamic DNS before you need "
1773 "to update your configuration, so that the "
1774 "tkey-gssapi-credential has the following value: "
1775 "DNS/%s.%s" % (names.netbiosname.lower(),
1776 names.realm.lower()))
1778 message(SIMPLE, "Update machine account")
1779 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1781 # 16) SD should be created with admin but as some previous acl were so wrong
1782 # that admin can't modify them we have first to recreate them with the good
1783 # form but with system account and then give the ownership to admin ...
1784 if opts.very_old_pre_alpha9:
1785 message(SIMPLE, "Fixing very old provision SD")
1786 rebuild_sd(ldbs.sam, names)
1788 # We calculate the max USN before recalculating the SD because we might
1789 # touch object that have been modified after a provision and we do not
1790 # want that the next upgradeprovision thinks that it has a green light
1794 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1796 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1797 # defSDmodified is set.
1798 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1799 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1800 "changed, recalculating them")
1801 ldbs.sam.set_session_info(adm_session)
1802 rebuild_sd(ldbs.sam, names)
1805 # Now we are quite confident in the recalculate process of the SD, we make
1806 # it optional. And we don't do it if there is DN that we must touch
1807 # as we are assured that on this DNs we will have differences !
1808 # Also the check must be done in a clever way as for the moment we just
1810 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1811 message(CHANGESD, "Checking recalculated SDs")
1812 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1815 updateOEMInfo(ldbs.sam, str(names.rootdn))
1817 check_for_DNS(newpaths.private_dir, paths.private_dir,
1818 newpaths.binddns_dir, paths.binddns_dir,
1821 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1822 if opts.full and (names.policyid is None or names.policyid_dc is None):
1823 update_policyids(names, ldbs.sam)
1827 update_gpo(paths, ldbs.sam, names, lp, message)
1828 except ProvisioningError as e:
1829 message(ERROR, "The policy for domain controller is missing. "
1830 "You should restart upgradeprovision with --full")
1832 ldbs.groupedCommit()
1833 new_ldbs.groupedCommit()
1834 message(SIMPLE, "Upgrade finished!")
1835 # remove reference provision now that everything is done !
1836 # So we have reindexed first if need when the merged schema was reloaded
1837 # (as new attributes could have quick in)
1838 # But the second part of the update (when we update existing objects
1839 # can also have an influence on indexing as some attribute might have their
1840 # searchflag modificated
1841 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1842 "after modification")
1843 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1844 message(SIMPLE, "Reindexing finished")
1846 shutil.rmtree(provisiondir)
1847 except StandardError as err:
1848 message(ERROR, "A problem occurred while trying to upgrade your "
1849 "provision. A full backup is located at %s" % backupdir)
1850 if opts.debugall or opts.debugchange:
1851 (typ, val, tb) = sys.exc_info()
1852 traceback.print_exception(typ, val, tb)