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
73 from samba.compat import cmp_to_key_fn
75 # make sure the script dies immediately when hitting control-C,
76 # rather than raising KeyboardInterrupt. As we do all database
77 # operations using transactions, this is safe.
79 signal.signal(signal.SIGINT, signal.SIG_DFL)
81 replace=2**FLAG_MOD_REPLACE
83 delete=2**FLAG_MOD_DELETE
87 # Will be modified during provision to tell if default sd has been modified
90 #Errors are always logged
92 __docformat__ = "restructuredText"
94 # Attributes that are never copied from the reference provision (even if they
95 # do not exist in the destination object).
96 # This is most probably because they are populated automatcally when object is
98 # This also apply to imported object from reference provision
99 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
100 "parentGUID", "distinguishedName",
101 "instanceType", "cn",
102 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
103 "unicodePwd", "dBCSPwd", "supplementalCredentials",
104 "gPCUserExtensionNames", "gPCMachineExtensionNames",
105 "maxPwdAge", "secret", "possibleInferiors", "privilege",
106 "sAMAccountType", "oEMInformation", "creationTime" ]
108 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
109 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
111 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
114 attrNotCopied = replAttrNotCopied
115 attrNotCopied.extend(nonreplAttrNotCopied)
116 attrNotCopied.extend(nonDSDBAttrNotCopied)
117 # Usually for an object that already exists we do not overwrite attributes as
118 # they might have been changed for good reasons. Anyway for a few of them it's
119 # mandatory to replace them otherwise the provision will be broken somehow.
120 # But for attribute that are just missing we do not have to specify them as the default
121 # behavior is to add missing attribute
122 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
123 "systemOnly":replace, "searchFlags":replace,
124 "mayContain":replace, "systemFlags":replace+add,
125 "description":replace, "operatingSystemVersion":replace,
126 "adminPropertyPages":replace, "groupType":replace,
127 "wellKnownObjects":replace, "privilege":never,
128 "rIDAvailablePool": never,
129 "versionNumber" : add,
130 "rIDNextRID": add, "rIDUsedPool": never,
131 "defaultSecurityDescriptor": replace + add,
132 "isMemberOfPartialAttributeSet": delete,
133 "attributeDisplayNames": replace + add,
134 "versionNumber": add}
136 dnNotToRecalculateFound = False
139 forwardlinked = set()
142 def define_what_to_log(opts):
146 if opts.debugchangesd:
147 what = what | CHANGESD
150 if opts.debugprovision:
151 what = what | PROVISION
153 what = what | CHANGEALL
157 parser = optparse.OptionParser("provision [options]")
158 sambaopts = options.SambaOptions(parser)
159 parser.add_option_group(sambaopts)
160 parser.add_option_group(options.VersionOptions(parser))
161 credopts = options.CredentialsOptions(parser)
162 parser.add_option_group(credopts)
163 parser.add_option("--setupdir", type="string", metavar="DIR",
164 help="directory with setup files")
165 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
166 parser.add_option("--debugguess", action="store_true",
167 help="Print information on which values are guessed")
168 parser.add_option("--debugchange", action="store_true",
169 help="Print information on what is different but won't be changed")
170 parser.add_option("--debugchangesd", action="store_true",
171 help="Print security descriptor differences")
172 parser.add_option("--debugall", action="store_true",
173 help="Print all available information (very verbose)")
174 parser.add_option("--db_backup_only", action="store_true",
175 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
176 parser.add_option("--full", action="store_true",
177 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
178 parser.add_option("--very-old-pre-alpha9", action="store_true",
179 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
181 opts = parser.parse_args()[0]
183 handler = logging.StreamHandler(sys.stdout)
184 upgrade_logger = logging.getLogger("upgradeprovision")
185 upgrade_logger.setLevel(logging.INFO)
187 upgrade_logger.addHandler(handler)
189 provision_logger = logging.getLogger("provision")
190 provision_logger.addHandler(handler)
192 whatToLog = define_what_to_log(opts)
194 def message(what, text):
195 """Print a message if this message type has been selected to be printed
197 :param what: Category of the message
198 :param text: Message to print """
199 if (whatToLog & what) or what <= 0:
200 upgrade_logger.info("%s", text)
202 if len(sys.argv) == 1:
203 opts.interactive = True
204 lp = sambaopts.get_loadparm()
205 smbconf = lp.configfile
207 creds = credopts.get_credentials(lp)
208 creds.set_kerberos_state(DONT_USE_KERBEROS)
212 def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
213 """Check if the provision has already the requirement for dynamic dns
215 :param refprivate: The path to the private directory of the reference
217 :param private: The path to the private directory of the upgraded
220 spnfile = "%s/spn_update_list" % private
221 dnsfile = "%s/dns_update_list" % private
223 if not os.path.exists(spnfile):
224 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
226 if not os.path.exists(dnsfile):
227 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
229 if not os.path.exists(binddns_dir):
230 os.mkdir(binddns_dir)
232 if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
235 namedfile = lp.get("dnsupdate:path")
237 namedfile = "%s/named.conf.update" % binddns_dir
238 if not os.path.exists(namedfile):
239 destdir = "%s/new_dns" % binddns_dir
240 dnsdir = "%s/dns" % binddns_dir
242 if not os.path.exists(destdir):
244 if not os.path.exists(dnsdir):
246 shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
247 shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
248 message(SIMPLE, "It seems that your provision did not integrate "
249 "new rules for dynamic dns update of domain related entries")
250 message(SIMPLE, "A copy of the new bind configuration files and "
251 "template has been put in %s, you should read them and "
252 "configure dynamic dns updates" % destdir)
255 def populate_links(samdb, schemadn):
256 """Populate an array with all the back linked attributes
258 This attributes that are modified automaticaly when
259 front attibutes are changed
261 :param samdb: A LDB object for sam.ldb file
262 :param schemadn: DN of the schema for the partition"""
263 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
264 backlinked.extend(linkedAttHash.values())
265 for t in linkedAttHash.keys():
268 def isReplicated(att):
269 """ Indicate if the attribute is replicated or not
271 :param att: Name of the attribute to be tested
272 :return: True is the attribute is replicated, False otherwise
275 return (att not in not_replicated)
277 def populateNotReplicated(samdb, schemadn):
278 """Populate an array with all the attributes that are not replicated
280 :param samdb: A LDB object for sam.ldb file
281 :param schemadn: DN of the schema for the partition"""
282 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
283 str(schemadn)), scope=SCOPE_SUBTREE,
284 attrs=["lDAPDisplayName"])
286 not_replicated.append(str(elem["lDAPDisplayName"]))
289 def populate_dnsyntax(samdb, schemadn):
290 """Populate an array with all the attributes that have DN synthax
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="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
296 str(schemadn)), scope=SCOPE_SUBTREE,
297 attrs=["lDAPDisplayName"])
299 dn_syntax_att.append(elem["lDAPDisplayName"])
302 def sanitychecks(samdb, names):
303 """Make some checks before trying to update
305 :param samdb: An LDB object opened on sam.ldb
306 :param names: list of key provision parameters
307 :return: Status of check (1 for Ok, 0 for not Ok) """
308 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
309 scope=SCOPE_SUBTREE, attrs=["dn"],
310 controls=["search_options:1:2"])
312 print("No DC found. Your provision is most probably broken!")
315 print("Found %d domain controllers. For the moment " \
316 "upgradeprovision is not able to handle an upgrade on a " \
317 "domain with more than one DC. Please demote the other " \
318 "DC(s) before upgrading") % len(res)
324 def print_provision_key_parameters(names):
325 """Do a a pretty print of provision parameters
327 :param names: list of key provision parameters """
328 message(GUESS, "rootdn :" + str(names.rootdn))
329 message(GUESS, "configdn :" + str(names.configdn))
330 message(GUESS, "schemadn :" + str(names.schemadn))
331 message(GUESS, "serverdn :" + str(names.serverdn))
332 message(GUESS, "netbiosname :" + names.netbiosname)
333 message(GUESS, "defaultsite :" + names.sitename)
334 message(GUESS, "dnsdomain :" + names.dnsdomain)
335 message(GUESS, "hostname :" + names.hostname)
336 message(GUESS, "domain :" + names.domain)
337 message(GUESS, "realm :" + names.realm)
338 message(GUESS, "invocationid:" + names.invocation)
339 message(GUESS, "policyguid :" + names.policyid)
340 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
341 message(GUESS, "domainsid :" + str(names.domainsid))
342 message(GUESS, "domainguid :" + names.domainguid)
343 message(GUESS, "ntdsguid :" + names.ntdsguid)
344 message(GUESS, "domainlevel :" + str(names.domainlevel))
347 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
348 """Define more complicate update rules for some attributes
350 :param att: The attribute to be updated
351 :param delta: A messageElement object that correspond to the difference
352 between the updated object and the reference one
353 :param new: The reference object
354 :param old: The Updated object
355 :param useReplMetadata: A boolean that indicate if the update process
356 use replPropertyMetaData to decide what has to be updated.
357 :param basedn: The base DN of the provision
358 :param aldb: An ldb object used to build DN
359 :return: True to indicate that the attribute should be kept, False for
362 # We do most of the special case handle if we do not have the
363 # highest usn as otherwise the replPropertyMetaData will guide us more
365 if not useReplMetadata:
366 flag = delta.get(att).flags()
367 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
368 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
369 "CN=Services,CN=Configuration,%s" % basedn)
372 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
373 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
375 message(SIMPLE, "We suggest that you change the userAccountControl"
376 " for user Administrator from value %d to %d" %
377 (int(str(old[0][att])), int(str(new[0][att]))))
379 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
380 if (int(str(old[0][att])) == 0):
381 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
384 if (att == "member" and flag == FLAG_MOD_REPLACE):
388 for elem in old[0][att]:
389 hash[str(elem).lower()]=1
390 newval.append(str(elem))
392 for elem in new[0][att]:
393 if not str(elem).lower() in hash:
395 newval.append(str(elem))
397 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
402 if (att in ("gPLink", "gPCFileSysPath") and
403 flag == FLAG_MOD_REPLACE and
404 str(new[0].dn).lower() == str(old[0].dn).lower()):
408 if att == "forceLogoff":
409 ref=0x8000000000000000
410 oldval=int(old[0][att][0])
411 newval=int(new[0][att][0])
412 ref == old and ref == abs(new)
415 if att in ("adminDisplayName", "adminDescription"):
418 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
419 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
422 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
423 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
426 if (str(old[0].dn) == "%s" % (str(names.rootdn))
427 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
429 #Allow to change revision of ForestUpdates objects
430 if (att == "revision" or att == "objectVersion"):
431 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
433 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
436 # This is a bit of special animal as we might have added
437 # already SPN entries to the list that has to be modified
438 # So we go in detail to try to find out what has to be added ...
439 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
443 for elem in old[0][att]:
445 newval.append(str(elem))
447 for elem in new[0][att]:
448 if not str(elem) in hash:
450 newval.append(str(elem))
452 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
459 def dump_denied_change(dn, att, flagtxt, current, reference):
460 """Print detailed information about why a change is denied
462 :param dn: DN of the object which attribute is denied
463 :param att: Attribute that was supposed to be upgraded
464 :param flagtxt: Type of the update that should be performed
465 (add, change, remove, ...)
466 :param current: Value(s) of the current attribute
467 :param reference: Value(s) of the reference attribute"""
469 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
470 + " must not be changed/removed. Discarding the change")
471 if att == "objectSid" :
472 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
473 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
474 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
475 message(CHANGE, "old : %s" % int64range2str(current[0]))
476 message(CHANGE, "new : %s" % int64range2str(reference[0]))
479 for e in range(0, len(current)):
480 message(CHANGE, "old %d : %s" % (i, str(current[e])))
482 if reference is not None:
484 for e in range(0, len(reference)):
485 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
488 def handle_special_add(samdb, dn, names):
489 """Handle special operation (like remove) on some object needed during
492 This is mostly due to wrong creation of the object in previous provision.
493 :param samdb: An Ldb object representing the SAM database
494 :param dn: DN of the object to inspect
495 :param names: list of key provision parameters
499 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
501 #This entry was misplaced lets remove it if it exists
502 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
505 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
507 #This entry was misplaced lets remove it if it exists
508 dntoremove = "CN=Certificate Service DCOM Access,"\
509 "CN=Users, %s" % names.rootdn
511 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
513 #This entry was misplaced lets remove it if it exists
514 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
516 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
518 #This entry was misplaced lets remove it if it exists
519 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
521 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
522 "CN=Configuration,%s" % names.rootdn)
524 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
525 "CN=WellKnown Security Principals,"
526 "CN=Configuration,%s" % names.rootdn)
528 res = samdb.search(expression="(distinguishedName=%s)" % oldDn,
529 base=str(names.rootdn),
530 scope=SCOPE_SUBTREE, attrs=["dn"],
531 controls=["search_options:1:2"])
533 res2 = samdb.search(expression="(distinguishedName=%s)" % dn,
534 base=str(names.rootdn),
535 scope=SCOPE_SUBTREE, attrs=["dn"],
536 controls=["search_options:1:2"])
538 if len(res) > 0 and len(res2) == 0:
539 message(CHANGE, "Existing object %s must be replaced by %s. "
540 "Renaming old object" % (str(oldDn), str(dn)))
541 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
545 if dntoremove is not None:
546 res = samdb.search(expression="(cn=RID Set)",
547 base=str(names.rootdn),
548 scope=SCOPE_SUBTREE, attrs=["dn"],
549 controls=["search_options:1:2"])
553 res = samdb.search(expression="(distinguishedName=%s)" % dntoremove,
554 base=str(names.rootdn),
555 scope=SCOPE_SUBTREE, attrs=["dn"],
556 controls=["search_options:1:2"])
558 message(CHANGE, "Existing object %s must be replaced by %s. "
559 "Removing old object" % (dntoremove, str(dn)))
560 samdb.delete(res[0]["dn"])
566 def check_dn_nottobecreated(hash, index, listdn):
567 """Check if one of the DN present in the list has a creation order
568 greater than the current.
570 Hash is indexed by dn to be created, with each key
571 is associated the creation order.
573 First dn to be created has the creation order 0, second has 1, ...
574 Index contain the current creation order
576 :param hash: Hash holding the different DN of the object to be
578 :param index: Current creation order
579 :param listdn: List of DNs on which the current DN depends on
580 :return: None if the current object do not depend on other
581 object or if all object have been created before."""
585 key = str(dn).lower()
586 if key in hash and hash[key] > index:
592 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
593 """Add a new object if the dependencies are satisfied
595 The function add the object if the object on which it depends are already
598 :param ref_samdb: Ldb object representing the SAM db of the reference
600 :param samdb: Ldb object representing the SAM db of the upgraded
602 :param dn: DN of the object to be added
603 :param names: List of key provision parameters
604 :param basedn: DN of the partition to be updated
605 :param hash: Hash holding the different DN of the object to be
607 :param index: Current creation order
608 :return: True if the object was created False otherwise"""
610 ret = handle_special_add(samdb, dn, names)
619 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)),
620 base=basedn, scope=SCOPE_SUBTREE,
621 controls=["search_options:1:2"])
623 delta = samdb.msg_diff(empty, reference[0])
627 if str(reference[0].get("cn")) == "RID Set":
628 for klass in reference[0].get("objectClass"):
629 if str(klass).lower() == "ridset":
632 if delta.get("objectSid"):
633 sid = str(ndr_unpack(security.dom_sid, reference[0]["objectSid"][0]))
634 m = re.match(r".*-(\d+)$", sid)
635 if m and int(m.group(1))>999:
636 delta.remove("objectSid")
637 for att in attrNotCopied:
639 for att in backlinked:
641 for att in dn_syntax_att:
642 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
644 if depend_on_yet_tobecreated is not None:
645 message(CHANGE, "Object %s depends on %s in attribute %s. "
646 "Delaying the creation" % (dn,
647 depend_on_yet_tobecreated, att))
652 message(CHANGE,"Object %s will be added" % dn)
653 samdb.add(delta, ["relax:0", "provision:0"])
655 message(CHANGE,"Object %s was skipped" % dn)
659 def gen_dn_index_hash(listMissing):
660 """Generate a hash associating the DN to its creation order
662 :param listMissing: List of DN
663 :return: Hash with DN as keys and creation order as values"""
665 for i in range(0, len(listMissing)):
666 hash[str(listMissing[i]).lower()] = i
669 def add_deletedobj_containers(ref_samdb, samdb, names):
670 """Add the object containter: CN=Deleted Objects
672 This function create the container for each partition that need one and
673 then reference the object into the root of the partition
675 :param ref_samdb: Ldb object representing the SAM db of the reference
677 :param samdb: Ldb object representing the SAM db of the upgraded provision
678 :param names: List of key provision parameters"""
681 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
682 partitions = [str(names.rootdn), str(names.configdn)]
683 for part in partitions:
684 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
685 base=part, scope=SCOPE_SUBTREE,
687 controls=["show_deleted:0",
689 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
690 base=part, scope=SCOPE_SUBTREE,
692 controls=["show_deleted:0",
694 if len(ref_delObjCnt) > len(delObjCnt):
695 reference = ref_samdb.search(expression="cn=Deleted Objects",
696 base=part, scope=SCOPE_SUBTREE,
697 controls=["show_deleted:0",
700 delta = samdb.msg_diff(empty, reference[0])
702 delta.dn = Dn(samdb, str(reference[0]["dn"]))
703 for att in attrNotCopied:
706 modcontrols = ["relax:0", "provision:0"]
707 samdb.add(delta, modcontrols)
710 res = samdb.search(expression="(objectClass=*)", base=part,
712 attrs=["dn", "wellKnownObjects"])
714 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
718 wko = res[0]["wellKnownObjects"]
720 # The wellKnownObject that we want to add.
722 if str(o) == targetWKO:
724 listwko.append(str(o))
727 listwko.append(targetWKO)
730 delta.dn = Dn(samdb, str(res[0]["dn"]))
731 delta["wellKnownObjects"] = MessageElement(listwko,
736 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
737 """Add the missing object whose DN is the list
739 The function add the object if the objects on which it depends are
742 :param ref_samdb: Ldb object representing the SAM db of the reference
744 :param samdb: Ldb object representing the SAM db of the upgraded
746 :param dn: DN of the object to be added
747 :param names: List of key provision parameters
748 :param basedn: DN of the partition to be updated
749 :param list: List of DN to be added in the upgraded provision"""
754 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
756 listMissing = listDefered
758 hashMissing = gen_dn_index_hash(listMissing)
759 for dn in listMissing:
760 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
764 # DN can't be created because it depends on some
765 # other DN in the list
766 listDefered.append(dn)
768 if len(listDefered) != 0:
769 raise ProvisioningError("Unable to insert missing elements: "
770 "circular references")
772 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
773 """This function handle updates on links
775 :param samdb: An LDB object pointing to the updated provision
776 :param att: Attribute to update
777 :param basedn: The root DN of the provision
778 :param dn: The DN of the inspected object
779 :param value: The value of the attribute
780 :param ref_value: The value of this attribute in the reference provision
781 :param delta: The MessageElement object that will be applied for
782 transforming the current provision"""
784 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
793 newlinklist.append(str(v))
797 # for w2k domain level the reveal won't reveal anything ...
798 # it means that we can readd links that were removed on purpose ...
799 # Also this function in fact just accept add not removal
801 for e in res[0][att]:
803 # We put in the blacklist all the element that are in the "revealed"
804 # result and not in the "standard" result
805 # This element are links that were removed before and so that
806 # we don't wan't to readd
810 if not e in blacklist and not e in hash:
811 newlinklist.append(str(e))
814 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
821 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
822 hash_attr_usn, basedn, usns, samdb):
823 """ Check if we should keep the attribute modification or not
825 :param delta: A message diff object
826 :param att: An attribute
827 :param message: A function to print messages
828 :param reference: A message object for the current entry comming from
829 the reference provision.
830 :param current: A message object for the current entry commin from
831 the current provision.
832 :param hash_attr_usn: A dictionnary with attribute name as keys,
833 USN and invocation id as values.
834 :param basedn: The DN of the partition
835 :param usns: A dictionnary with invocation ID as keys and USN ranges
837 :param samdb: A ldb object pointing to the sam DB
839 :return: The modified message diff.
846 for att in list(delta):
847 if att in ["dn", "objectSid"]:
851 # We have updated by provision usn information so let's exploit
852 # replMetadataProperties
853 if att in forwardlinked:
854 curval = current[0].get(att, ())
855 refval = reference[0].get(att, ())
856 delta = handle_links(samdb, att, basedn, current[0]["dn"],
857 curval, refval, delta)
861 if isFirst and len(list(delta)) > 1:
863 txt = "%s\n" % (str(dn))
865 if handle_special_case(att, delta, reference, current, True, None, None):
866 # This attribute is "complicated" to handle and handling
867 # was done in handle_special_case
871 if hash_attr_usn.get(att):
872 [attrUSN, attInvId] = hash_attr_usn.get(att)
875 # If it's a replicated attribute and we don't have any USN
876 # information about it. It means that we never saw it before
878 # If it is a replicated attribute but we are not master on it
879 # (ie. not initially added in the provision we masterize).
881 if isReplicated(att):
884 message(CHANGE, "Non replicated attribute %s changed" % att)
887 if att == "nTSecurityDescriptor":
888 cursd = ndr_unpack(security.descriptor,
889 current[0]["nTSecurityDescriptor"][0])
890 refsd = ndr_unpack(security.descriptor,
891 reference[0]["nTSecurityDescriptor"][0])
893 diff = get_diff_sds(refsd, cursd, names.domainsid)
895 # FIXME find a way to have it only with huge huge verbose mode
896 # message(CHANGE, "%ssd are identical" % txt)
902 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
905 message(CHANGESD, "But the SD has been changed by someonelse "
906 "so it's impossible to know if the difference"
907 " cames from the modification or from a previous bug")
908 global dnNotToRecalculateFound
909 dnNotToRecalculateFound = True
911 dnToRecalculate.append(dn)
915 # This attribute was last modified by another DC forget
917 message(CHANGE, "%sAttribute: %s has been "
918 "created/modified/deleted by another DC. "
919 "Doing nothing" % (txt, att))
923 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
924 message(CHANGE, "%sAttribute: %s was not "
925 "created/modified/deleted during a "
926 "provision or upgradeprovision. Current "
927 "usn: %d. Doing nothing" % (txt, att,
933 if att == "defaultSecurityDescriptor":
936 message(CHANGE, "%sAttribute: %s will be modified"
937 "/deleted it was last modified "
938 "during a provision. Current usn: "
939 "%d" % (txt, att, attrUSN))
942 message(CHANGE, "%sAttribute: %s will be added because "
943 "it did not exist before" % (txt, att))
949 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
950 """ This function updates the object that are already present in the
953 :param ref_samdb: An LDB object pointing to the reference provision
954 :param samdb: An LDB object pointing to the updated provision
955 :param basedn: A string with the value of the base DN for the provision
957 :param listPresent: A list of object that is present in the provision
958 :param usns: A list of USN range modified by previous provision and
959 upgradeprovision grouped by invocation ID
962 # This hash is meant to speedup lookup of attribute name from an oid,
963 # it's for the replPropertyMetaData handling
965 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
966 controls=["search_options:1:2"], attrs=["attributeID",
970 strDisplay = str(e.get("lDAPDisplayName"))
971 hash_oid_name[str(e.get("attributeID"))] = strDisplay
973 msg = "Unable to insert missing elements: circular references"
974 raise ProvisioningError(msg)
977 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
978 controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
979 message(CHANGE, "Using replPropertyMetadata for change selection")
980 for dn in listPresent:
981 reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
984 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
985 scope=SCOPE_SUBTREE, controls=controls)
988 (str(current[0].dn) != str(reference[0].dn)) and
989 (str(current[0].dn).upper() == str(reference[0].dn).upper())
991 message(CHANGE, "Names are the same except for the case. "
992 "Renaming %s to %s" % (str(current[0].dn),
993 str(reference[0].dn)))
994 identic_rename(samdb, reference[0].dn)
995 current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
999 delta = samdb.msg_diff(current[0], reference[0])
1001 for att in backlinked:
1004 for att in attrNotCopied:
1007 delta.remove("name")
1009 nb_items = len(list(delta))
1015 # Fetch the replPropertyMetaData
1016 res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1017 scope=SCOPE_SUBTREE, controls=controls,
1018 attrs=["replPropertyMetaData"])
1019 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1020 res[0]["replPropertyMetaData"][0]).ctr
1024 # We put in this hash only modification
1025 # made on the current host
1026 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1027 if str(o.originating_invocation_id) in usns.keys():
1028 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1030 hash_attr_usn[att] = [-1, None]
1032 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1033 current, hash_attr_usn,
1034 basedn, usns, samdb)
1040 # Skip dn as the value is not really changed ...
1041 attributes=", ".join(delta.keys()[1:])
1043 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1044 # Let's try to reduce as much as possible the use of relax control
1045 for attr in delta.keys():
1046 if attr.lower() in relaxedatt:
1047 modcontrols = ["relax:0", "provision:0"]
1048 message(CHANGE, "%s is different from the reference one, changed"
1049 " attributes: %s\n" % (dn, attributes))
1051 samdb.modify(delta, modcontrols)
1054 def reload_full_schema(samdb, names):
1055 """Load the updated schema with all the new and existing classes
1058 :param samdb: An LDB object connected to the sam.ldb of the update
1060 :param names: List of key provision parameters
1063 schemadn = str(names.schemadn)
1064 current = samdb.search(expression="objectClass=*", base=schemadn,
1065 scope=SCOPE_SUBTREE)
1070 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1072 prefixmap_data = open(setup_path("prefixMap.txt"), 'rb').read()
1073 prefixmap_data = b64encode(prefixmap_data).decode('utf8')
1075 # We don't actually add this ldif, just parse it
1076 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1078 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1081 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1082 """Check differences between the reference provision and the upgraded one.
1084 It looks for all objects which base DN is name.
1086 This function will also add the missing object and update existing object
1087 to add or remove attributes that were missing.
1089 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1091 :param samdb: An LDB object connected to the sam.ldb of the update
1093 :param basedn: String value of the DN of the partition
1094 :param names: List of key provision parameters
1095 :param schema: A Schema object
1096 :param provisionUSNs: A dictionnary with range of USN modified during provision
1097 or upgradeprovision. Ranges are grouped by invocationID.
1098 :param prereloadfunc: A function that must be executed just before the reload
1109 # Connect to the reference provision and get all the attribute in the
1110 # partition referred by name
1111 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1112 scope=SCOPE_SUBTREE, attrs=["dn"],
1113 controls=["search_options:1:2"])
1115 current = samdb.search(expression="objectClass=*", base=basedn,
1116 scope=SCOPE_SUBTREE, attrs=["dn"],
1117 controls=["search_options:1:2"])
1118 # Create a hash for speeding the search of new object
1119 for i in range(0, len(reference)):
1120 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1122 # Create a hash for speeding the search of existing object in the
1124 for i in range(0, len(current)):
1125 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1128 for k in hash_new.keys():
1130 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1131 listMissing.append(hash_new[k])
1133 listPresent.append(hash_new[k])
1135 # Sort the missing object in order to have object of the lowest level
1136 # first (which can be containers for higher level objects)
1137 listMissing.sort(key=cmp_to_key_fn(dn_sort))
1138 listPresent.sort(key=cmp_to_key_fn(dn_sort))
1140 # The following lines is to load the up to
1141 # date schema into our current LDB
1142 # a complete schema is needed as the insertion of attributes
1143 # and class is done against it
1144 # and the schema is self validated
1145 samdb.set_schema(schema)
1147 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1148 add_deletedobj_containers(ref_samdb, samdb, names)
1150 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1153 message(SIMPLE, "Reloading a merged schema, which might trigger "
1154 "reindexing so please be patient")
1155 reload_full_schema(samdb, names)
1156 message(SIMPLE, "Schema reloaded!")
1158 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1160 message(SIMPLE, "There are %d changed objects" % (changed))
1163 except Exception as err:
1164 message(ERROR, "Exception during upgrade of samdb:")
1165 (typ, val, tb) = sys.exc_info()
1166 traceback.print_exception(typ, val, tb)
1170 def check_updated_sd(ref_sam, cur_sam, names):
1171 """Check if the security descriptor in the upgraded provision are the same
1174 :param ref_sam: A LDB object connected to the sam.ldb file used as
1175 the reference provision
1176 :param cur_sam: A LDB object connected to the sam.ldb file used as
1178 :param names: List of key provision parameters"""
1179 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1180 scope=SCOPE_SUBTREE,
1181 attrs=["dn", "nTSecurityDescriptor"],
1182 controls=["search_options:1:2"])
1183 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1184 scope=SCOPE_SUBTREE,
1185 attrs=["dn", "nTSecurityDescriptor"],
1186 controls=["search_options:1:2"])
1188 for i in range(0, len(reference)):
1189 refsd_blob = reference[i]["nTSecurityDescriptor"][0]
1190 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1193 for i in range(0, len(current)):
1194 key = str(current[i]["dn"]).lower()
1196 cursd_blob = current[i]["nTSecurityDescriptor"][0]
1197 cursd = ndr_unpack(security.descriptor,
1199 if cursd_blob != hash[key]:
1200 refsd = ndr_unpack(security.descriptor,
1202 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1204 message(CHANGESD, "On object %s ACL is different"
1205 " \n%s" % (current[i]["dn"], txt))
1209 def fix_wellknown_sd(samdb, names):
1210 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1211 This is needed because some provision use to have broken SD on containers
1213 :param samdb: An LDB object pointing to the sam of the current provision
1214 :param names: A list of key provision parameters
1217 list_wellknown_dns = []
1219 subcontainers = get_wellknown_sds(samdb)
1221 for [dn, descriptor_fn] in subcontainers:
1222 list_wellknown_dns.append(dn)
1223 if dn in dnToRecalculate:
1226 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1227 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1228 "nTSecurityDescriptor" )
1230 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1232 return list_wellknown_dns
1234 def rebuild_sd(samdb, names):
1235 """Rebuild security descriptor of the current provision from scratch
1237 During the different pre release of samba4 security descriptors
1238 (SD) were notarly broken (up to alpha11 included)
1240 This function allows one to get them back in order, this function works
1241 only after the database comparison that --full mode uses and which
1242 populates the dnToRecalculate and dnNotToRecalculate lists.
1244 The idea is that the SD can be safely recalculated from scratch to get it right.
1246 :param names: List of key provision parameters"""
1248 listWellknown = fix_wellknown_sd(samdb, names)
1250 if len(dnToRecalculate) != 0:
1251 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1252 % (len(dnToRecalculate)))
1254 for dn in dnToRecalculate:
1255 # well known SDs have already been reset
1256 if dn in listWellknown:
1260 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1262 descr = get_empty_descriptor(names.domainsid)
1263 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1264 "nTSecurityDescriptor")
1265 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1266 except LdbError as e:
1267 samdb.transaction_cancel()
1268 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1270 attrs=["nTSecurityDescriptor"],
1271 controls=["sd_flags:1:%d" % sd_flags])
1272 badsd = ndr_unpack(security.descriptor,
1273 res[0]["nTSecurityDescriptor"][0])
1274 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1277 def hasATProvision(samdb):
1278 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1282 if entry is not None and len(entry) == 1:
1287 def removeProvisionUSN(samdb):
1288 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1289 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1293 empty.dn = entry[0].dn
1294 delta = samdb.msg_diff(entry[0], empty)
1296 delta.dn = entry[0].dn
1299 def remove_stored_generated_attrs(paths, creds, session, lp):
1300 """Remove previously stored constructed attributes
1302 :param paths: List of paths for different provision objects
1303 from the upgraded provision
1304 :param creds: A credential object
1305 :param session: A session object
1306 :param lp: A line parser object
1307 :return: An associative array whose key are the different constructed
1308 attributes and the value the dn where this attributes were found.
1312 def simple_update_basesamdb(newpaths, paths, names):
1313 """Update the provision container db: sam.ldb
1314 This function is aimed at very old provision (before alpha9)
1316 :param newpaths: List of paths for different provision objects
1317 from the reference provision
1318 :param paths: List of paths for different provision objects
1319 from the upgraded provision
1320 :param names: List of key provision parameters"""
1322 message(SIMPLE, "Copy samdb")
1323 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1325 message(SIMPLE, "Update partitions filename if needed")
1326 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1327 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1328 usersldb = os.path.join(paths.private_dir, "users.ldb")
1329 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1331 if not os.path.isdir(samldbdir):
1333 os.chmod(samldbdir, 0o700)
1334 if os.path.isfile(schemaldb):
1335 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1336 "%s.ldb"%str(names.schemadn).upper()))
1337 os.remove(schemaldb)
1338 if os.path.isfile(usersldb):
1339 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1340 "%s.ldb"%str(names.rootdn).upper()))
1342 if os.path.isfile(configldb):
1343 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1344 "%s.ldb"%str(names.configdn).upper()))
1345 os.remove(configldb)
1348 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1349 """Upgrade the SAM DB contents for all the provision partitions
1351 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1353 :param samdb: An LDB object connected to the sam.ldb of the update
1355 :param names: List of key provision parameters
1356 :param provisionUSNs: A dictionnary with range of USN modified during provision
1357 or upgradeprovision. Ranges are grouped by invocationID.
1358 :param schema: A Schema object that represent the schema of the provision
1359 :param prereloadfunc: A function that must be executed just before the reload
1363 message(SIMPLE, "Starting update of samdb")
1364 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1365 schema, provisionUSNs, prereloadfunc)
1367 message(SIMPLE, "Update of samdb finished")
1370 message(SIMPLE, "Update failed")
1374 def backup_provision(samdb, paths, dir, only_db):
1375 """This function backup the provision files so that a rollback
1378 :param paths: Paths to different objects
1379 :param dir: Directory where to store the backup
1380 :param only_db: Skip sysvol for users with big sysvol
1383 # Currently we default to tdb for the backend store type
1385 backend_store = "tdb"
1386 res = samdb.search(base="@PARTITION",
1387 scope=ldb.SCOPE_BASE,
1388 attrs=["backendStore"])
1389 if "backendStore" in res[0]:
1390 backend_store = str(res[0]["backendStore"][0])
1393 if paths.sysvol and not only_db:
1394 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1396 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1397 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1398 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1399 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1400 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1401 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1402 shutil.copy2(paths.smbconf, dir)
1403 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1405 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1406 if not os.path.isdir(samldbdir):
1407 samldbdir = paths.private_dir
1408 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1409 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1410 usersldb = os.path.join(paths.private_dir, "users.ldb")
1411 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1412 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1413 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1415 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
1417 for ldb_name in os.listdir(samldbdir):
1418 if not ldb_name.endswith("-lock"):
1419 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1420 mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1421 os.path.join(dir, "sam.ldb.d", ldb_name))
1423 tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1424 os.path.join(dir, "sam.ldb.d", ldb_name))
1427 def sync_calculated_attributes(samdb, names):
1428 """Synchronize attributes used for constructed ones, with the
1429 old constructed that were stored in the database.
1431 This apply for instance to msds-keyversionnumber that was
1432 stored and that is now constructed from replpropertymetadata.
1434 :param samdb: An LDB object attached to the currently upgraded samdb
1435 :param names: Various key parameter about current provision.
1437 listAttrs = ["msDs-KeyVersionNumber"]
1438 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1439 if "msDs-KeyVersionNumber" in hash:
1440 increment_calculated_keyversion_number(samdb, names.rootdn,
1441 hash["msDs-KeyVersionNumber"])
1443 # Synopsis for updateprovision
1444 # 1) get path related to provision to be update (called current)
1445 # 2) open current provision ldbs
1446 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1448 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1449 # by either upgradeprovision or provision
1450 # 5) creation of a new provision the latest version of provision script
1451 # (called reference)
1452 # 6) get reference provision paths
1453 # 7) open reference provision ldbs
1454 # 8) setup helpers data that will help the update process
1455 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1456 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1457 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1458 # 10)get the oemInfo field, this field contains information about the different
1459 # provision that have been done
1460 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1461 # A) When alpha9 or alphaxx not specified (default)
1462 # The base sam.ldb file is updated by looking at the difference between
1463 # referrence one and the current one. Everything is copied with the
1464 # exception of lastProvisionUSN attributes.
1465 # B) Other case (it reflect that that provision was done before alpha9)
1466 # The base sam.ldb of the reference provision is copied over
1467 # the current one, if necessary ldb related to partitions are moved
1469 # The highest used USN is fetched so that changed by upgradeprovision
1470 # usn can be tracked
1471 # 12)A Schema object is created, it will be used to provide a complete
1472 # schema to current provision during update (as the schema of the
1473 # current provision might not be complete and so won't allow some
1474 # object to be created)
1475 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1476 # 14)The secrets db is updated by pull all the difference from the reference
1477 # provision into the current provision
1478 # 15)As the previous step has most probably modified the password stored in
1479 # in secret for the current DC, a new password is generated,
1480 # the kvno is bumped and the entry in samdb is also updated
1481 # 16)For current provision older than alpha9, we must fix the SD a little bit
1482 # administrator to update them because SD used to be generated with the
1483 # system account before alpha9.
1484 # 17)The highest usn modified so far is searched in the database it will be
1485 # the upper limit for usn modified during provision.
1486 # This is done before potential SD recalculation because we do not want
1487 # SD modified during recalculation to be marked as modified during provision
1488 # (and so possibly remplaced at next upgradeprovision)
1489 # 18)Rebuilt SD if the flag indicate to do so
1490 # 19)Check difference between SD of reference provision and those of the
1491 # current provision. The check is done by getting the sddl representation
1492 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1493 # Each part is verified separetly, for dacl and sacl ACL is splited into
1494 # ACEs and each ACE is verified separately (so that a permutation in ACE
1495 # didn't raise as an error).
1496 # 20)The oemInfo field is updated to add information about the fact that the
1497 # provision has been updated by the upgradeprovision version xxx
1498 # (the version is the one obtained when starting samba with the --version
1500 # 21)Check if the current provision has all the settings needed for dynamic
1501 # DNS update to work (that is to say the provision is newer than
1502 # january 2010). If not dns configuration file from reference provision
1503 # are copied in a sub folder and the administrator is invited to
1504 # do what is needed.
1505 # 22)If the lastProvisionUSN attribute was present it is updated to add
1506 # the range of usns modified by the current upgradeprovision
1509 # About updating the sam DB
1510 # The update takes place in update_partition function
1511 # This function read both current and reference provision and list all
1512 # the available DN of objects
1513 # If the string representation of a DN in reference provision is
1514 # equal to the string representation of a DN in current provision
1515 # (without taking care of case) then the object is flaged as being
1516 # present. If the object is not present in current provision the object
1517 # is being flaged as missing in current provision. Object present in current
1518 # provision but not in reference provision are ignored.
1519 # Once the list of objects present and missing is done, the deleted object
1520 # containers are created in the differents partitions (if missing)
1522 # Then the function add_missing_entries is called
1523 # This function will go through the list of missing entries by calling
1524 # add_missing_object for the given object. If this function returns 0
1525 # it means that the object needs some other object in order to be created
1526 # The object is reappended at the end of the list to be created later
1527 # (and preferably after all the needed object have been created)
1528 # The function keeps on looping on the list of object to be created until
1529 # it's empty or that the number of deferred creation is equal to the number
1530 # of object that still needs to be created.
1532 # The function add_missing_object will first check if the object can be created.
1533 # That is to say that it didn't depends other not yet created objects
1534 # If requisit can't be fullfilled it exists with 0
1535 # Then it will try to create the missing entry by creating doing
1536 # an ldb_message_diff between the object in the reference provision and
1538 # This resulting object is filtered to remove all the back link attribute
1539 # (ie. memberOf) as they will be created by the other linked object (ie.
1540 # the one with the member attribute)
1541 # All attributes specified in the attrNotCopied array are
1542 # also removed it's most of the time generated attributes
1544 # After missing entries have been added the update_partition function will
1545 # take care of object that exist but that need some update.
1546 # In order to do so the function update_present is called with the list
1547 # of object that are present in both provision and that might need an update.
1549 # This function handle first case mismatch so that the DN in the current
1550 # provision have the same case as in reference provision
1552 # It will then construct an associative array consiting of attributes as
1553 # key and invocationid as value( if the originating invocation id is
1554 # different from the invocation id of the current DC the value is -1 instead).
1556 # If the range of provision modified attributes is present, the function will
1557 # use the replMetadataProperty update method which is the following:
1558 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1559 # creationTime, msDs-KeyVersionNumber, oEMInformation
1560 # Check for each attribute if its usn is within one of the modified by
1561 # provision range and if its originating id is the invocation id of the
1562 # current DC, then validate the update from reference to current.
1563 # If not or if there is no replMetatdataProperty for this attribute then we
1565 # Otherwise (case the range of provision modified attribute is not present) it
1566 # use the following process:
1567 # All attributes that need to be added are accepted at the exeption of those
1568 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1569 # correct flags specified.
1570 # For attributes that need to be modified or removed, a check is performed
1571 # in OverwrittenAtt, if the attribute is present and the modification flag
1572 # (remove, delete) is one of those listed for this attribute then modification
1573 # is accepted. For complicated handling of attribute update, the control is passed
1574 # to handle_special_case
1578 if __name__ == '__main__':
1579 global defSDmodified
1580 defSDmodified = False
1582 # From here start the big steps of the program
1583 # 1) First get files paths
1584 paths = get_paths(param, smbconf=smbconf)
1585 # Get ldbs with the system session, it is needed for searching
1586 # provision parameters
1587 session = system_session()
1589 # This variable will hold the last provision USN once if it exists.
1592 ldbs = get_ldbs(paths, creds, session, lp)
1593 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1594 prefix="backupprovision")
1595 backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1597 ldbs.startTransactions()
1599 # 3) Guess all the needed names (variables in fact) from the current
1601 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1604 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1605 if lastProvisionUSNs is not None:
1607 for k in lastProvisionUSNs.keys():
1608 for r in lastProvisionUSNs[k]:
1612 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1613 (len(lastProvisionUSNs.keys()), v /2 ))
1615 if lastProvisionUSNs.get("default") is not None:
1616 message(CHANGE, "Old style for usn ranges used")
1617 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1618 del lastProvisionUSNs["default"]
1620 message(SIMPLE, "Your provision lacks provision range information")
1621 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1622 ldbs.groupedRollback()
1624 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1625 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1626 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1627 " of hundred of objects or more")
1628 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1631 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1633 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1636 # Objects will be created with the admin session
1637 # (not anymore system session)
1638 adm_session = admin_session(lp, str(names.domainsid))
1639 # So we reget handle on objects
1640 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1642 if not sanitychecks(ldbs.sam, names):
1643 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1644 "Check the messages and correct the errors "
1645 "before rerunning upgradeprovision")
1646 ldbs.groupedRollback()
1649 # Let's see provision parameters
1650 print_provision_key_parameters(names)
1652 # 5) With all this information let's create a fresh new provision used as
1654 message(SIMPLE, "Creating a reference provision")
1655 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1656 prefix="referenceprovision")
1657 result = newprovision(names, session, smbconf, provisiondir,
1658 provision_logger, base_schema="2008_R2")
1659 result.report_logger(provision_logger)
1663 # We need to get a list of object which SD is directly computed from
1664 # defaultSecurityDescriptor.
1665 # This will allow us to know which object we can rebuild the SD in case
1666 # of change of the parent's SD or of the defaultSD.
1667 # Get file paths of this new provision
1668 newpaths = get_paths(param, targetdir=provisiondir)
1669 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1670 new_ldbs.startTransactions()
1672 populateNotReplicated(new_ldbs.sam, names.schemadn)
1673 # 8) Populate some associative array to ease the update process
1674 # List of attribute which are link and backlink
1675 populate_links(new_ldbs.sam, names.schemadn)
1676 # List of attribute with ASN DN synthax)
1677 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1678 # 9) (now skipped, was copy of privileges.ldb)
1680 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1681 # Do some modification on sam.ldb
1682 ldbs.groupedCommit()
1683 new_ldbs.groupedCommit()
1687 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1689 # Starting from alpha9 we can consider that the structure is quite ok
1690 # and that we should do only dela
1691 deltaattr = delta_update_basesamdb(newpaths.samdb,
1699 simple_update_basesamdb(newpaths, paths, names)
1700 ldbs = get_ldbs(paths, creds, session, lp)
1701 removeProvisionUSN(ldbs.sam)
1703 ldbs.startTransactions()
1704 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1705 new_ldbs.startTransactions()
1708 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1709 # We create a closure that will be invoked just before schema reload
1710 def schemareloadclosure():
1711 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1712 options=["modules:"])
1714 if deltaattr is not None and len(deltaattr) > 1:
1717 deltaattr.remove("dn")
1718 for att in deltaattr:
1719 if att.lower() == "dn":
1721 if (deltaattr.get(att) is not None
1722 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1724 elif deltaattr.get(att) is None:
1727 message(CHANGE, "Applying delta to @ATTRIBUTES")
1728 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1729 basesam.modify(deltaattr)
1731 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1732 "there is not only add")
1735 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1736 schema, schemareloadclosure):
1737 message(SIMPLE, "Rolling back all changes. Check the cause"
1739 message(SIMPLE, "Your system is as it was before the upgrade")
1740 ldbs.groupedRollback()
1741 new_ldbs.groupedRollback()
1742 shutil.rmtree(provisiondir)
1745 # Try to reapply the change also when we do not change the sam
1746 # as the delta_upgrade
1747 schemareloadclosure()
1748 sync_calculated_attributes(ldbs.sam, names)
1749 res = ldbs.sam.search(expression="(samaccountname=dns)",
1750 scope=SCOPE_SUBTREE, attrs=["dn"],
1751 controls=["search_options:1:2"])
1753 message(SIMPLE, "You still have the old DNS object for managing "
1754 "dynamic DNS, but you didn't supply --full so "
1755 "a correct update can't be done")
1756 ldbs.groupedRollback()
1757 new_ldbs.groupedRollback()
1758 shutil.rmtree(provisiondir)
1761 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1763 res = ldbs.sam.search(expression="(samaccountname=dns)",
1764 scope=SCOPE_SUBTREE, attrs=["dn"],
1765 controls=["search_options:1:2"])
1768 ldbs.sam.delete(res[0]["dn"])
1769 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1770 scope=SCOPE_SUBTREE, attrs=["dn"])
1771 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1772 message(SIMPLE, "IMPORTANT!!! "
1773 "If you were using Dynamic DNS before you need "
1774 "to update your configuration, so that the "
1775 "tkey-gssapi-credential has the following value: "
1776 "DNS/%s.%s" % (names.netbiosname.lower(),
1777 names.realm.lower()))
1779 message(SIMPLE, "Update machine account")
1780 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1782 # 16) SD should be created with admin but as some previous acl were so wrong
1783 # that admin can't modify them we have first to recreate them with the good
1784 # form but with system account and then give the ownership to admin ...
1785 if opts.very_old_pre_alpha9:
1786 message(SIMPLE, "Fixing very old provision SD")
1787 rebuild_sd(ldbs.sam, names)
1789 # We calculate the max USN before recalculating the SD because we might
1790 # touch object that have been modified after a provision and we do not
1791 # want that the next upgradeprovision thinks that it has a green light
1795 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1797 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1798 # defSDmodified is set.
1799 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1800 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1801 "changed, recalculating them")
1802 ldbs.sam.set_session_info(adm_session)
1803 rebuild_sd(ldbs.sam, names)
1806 # Now we are quite confident in the recalculate process of the SD, we make
1807 # it optional. And we don't do it if there is DN that we must touch
1808 # as we are assured that on this DNs we will have differences !
1809 # Also the check must be done in a clever way as for the moment we just
1811 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1812 message(CHANGESD, "Checking recalculated SDs")
1813 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1816 updateOEMInfo(ldbs.sam, str(names.rootdn))
1818 check_for_DNS(newpaths.private_dir, paths.private_dir,
1819 newpaths.binddns_dir, paths.binddns_dir,
1822 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1823 if opts.full and (names.policyid is None or names.policyid_dc is None):
1824 update_policyids(names, ldbs.sam)
1828 update_gpo(paths, ldbs.sam, names, lp, message)
1829 except ProvisioningError as e:
1830 message(ERROR, "The policy for domain controller is missing. "
1831 "You should restart upgradeprovision with --full")
1833 ldbs.groupedCommit()
1834 new_ldbs.groupedCommit()
1835 message(SIMPLE, "Upgrade finished!")
1836 # remove reference provision now that everything is done !
1837 # So we have reindexed first if need when the merged schema was reloaded
1838 # (as new attributes could have quick in)
1839 # But the second part of the update (when we update existing objects
1840 # can also have an influence on indexing as some attribute might have their
1841 # searchflag modificated
1842 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1843 "after modification")
1844 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1845 message(SIMPLE, "Reindexing finished")
1847 shutil.rmtree(provisiondir)
1848 except Exception as err:
1849 message(ERROR, "A problem occurred while trying to upgrade your "
1850 "provision. A full backup is located at %s" % backupdir)
1851 if opts.debugall or opts.debugchange:
1852 (typ, val, tb) = sys.exc_info()
1853 traceback.print_exception(typ, val, tb)