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 automatically 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 container: 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 dictionary with attribute name as keys,
833 USN and invocation id as values.
834 :param basedn: The DN of the partition
835 :param usns: A dictionary 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)
1067 schema_ldif = "".join(samdb.write_ldif(ent, ldb.CHANGETYPE_NONE) for ent in current)
1069 prefixmap_data = b64encode(open(setup_path("prefixMap.txt"), 'rb').read()).decode('utf8')
1071 # We don't actually add this ldif, just parse it
1072 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1074 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1077 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1078 """Check differences between the reference provision and the upgraded one.
1080 It looks for all objects which base DN is name.
1082 This function will also add the missing object and update existing object
1083 to add or remove attributes that were missing.
1085 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1087 :param samdb: An LDB object connected to the sam.ldb of the update
1089 :param basedn: String value of the DN of the partition
1090 :param names: List of key provision parameters
1091 :param schema: A Schema object
1092 :param provisionUSNs: A dictionary with range of USN modified during provision
1093 or upgradeprovision. Ranges are grouped by invocationID.
1094 :param prereloadfunc: A function that must be executed just before the reload
1105 # Connect to the reference provision and get all the attribute in the
1106 # partition referred by name
1107 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1108 scope=SCOPE_SUBTREE, attrs=["dn"],
1109 controls=["search_options:1:2"])
1111 current = samdb.search(expression="objectClass=*", base=basedn,
1112 scope=SCOPE_SUBTREE, attrs=["dn"],
1113 controls=["search_options:1:2"])
1114 # Create a hash for speeding the search of new object
1115 for i in range(0, len(reference)):
1116 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1118 # Create a hash for speeding the search of existing object in the
1120 for i in range(0, len(current)):
1121 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1124 for k in hash_new.keys():
1126 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1127 listMissing.append(hash_new[k])
1129 listPresent.append(hash_new[k])
1131 # Sort the missing object in order to have object of the lowest level
1132 # first (which can be containers for higher level objects)
1133 listMissing.sort(key=cmp_to_key_fn(dn_sort))
1134 listPresent.sort(key=cmp_to_key_fn(dn_sort))
1136 # The following lines is to load the up to
1137 # date schema into our current LDB
1138 # a complete schema is needed as the insertion of attributes
1139 # and class is done against it
1140 # and the schema is self validated
1141 samdb.set_schema(schema)
1143 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1144 add_deletedobj_containers(ref_samdb, samdb, names)
1146 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1149 message(SIMPLE, "Reloading a merged schema, which might trigger "
1150 "reindexing so please be patient")
1151 reload_full_schema(samdb, names)
1152 message(SIMPLE, "Schema reloaded!")
1154 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1156 message(SIMPLE, "There are %d changed objects" % (changed))
1159 except Exception as err:
1160 message(ERROR, "Exception during upgrade of samdb:")
1161 (typ, val, tb) = sys.exc_info()
1162 traceback.print_exception(typ, val, tb)
1166 def check_updated_sd(ref_sam, cur_sam, names):
1167 """Check if the security descriptor in the upgraded provision are the same
1170 :param ref_sam: A LDB object connected to the sam.ldb file used as
1171 the reference provision
1172 :param cur_sam: A LDB object connected to the sam.ldb file used as
1174 :param names: List of key provision parameters"""
1175 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1176 scope=SCOPE_SUBTREE,
1177 attrs=["dn", "nTSecurityDescriptor"],
1178 controls=["search_options:1:2"])
1179 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1180 scope=SCOPE_SUBTREE,
1181 attrs=["dn", "nTSecurityDescriptor"],
1182 controls=["search_options:1:2"])
1184 for i in range(0, len(reference)):
1185 refsd_blob = reference[i]["nTSecurityDescriptor"][0]
1186 hash[str(reference[i]["dn"]).lower()] = refsd_blob
1189 for i in range(0, len(current)):
1190 key = str(current[i]["dn"]).lower()
1192 cursd_blob = current[i]["nTSecurityDescriptor"][0]
1193 cursd = ndr_unpack(security.descriptor,
1195 if cursd_blob != hash[key]:
1196 refsd = ndr_unpack(security.descriptor,
1198 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1200 message(CHANGESD, "On object %s ACL is different"
1201 " \n%s" % (current[i]["dn"], txt))
1205 def fix_wellknown_sd(samdb, names):
1206 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1207 This is needed because some provision use to have broken SD on containers
1209 :param samdb: An LDB object pointing to the sam of the current provision
1210 :param names: A list of key provision parameters
1213 list_wellknown_dns = []
1215 subcontainers = get_wellknown_sds(samdb)
1217 for [dn, descriptor_fn] in subcontainers:
1218 list_wellknown_dns.append(dn)
1219 if dn in dnToRecalculate:
1222 descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1223 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1224 "nTSecurityDescriptor" )
1226 message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1228 return list_wellknown_dns
1230 def rebuild_sd(samdb, names):
1231 """Rebuild security descriptor of the current provision from scratch
1233 During the different pre release of samba4 security descriptors
1234 (SD) were notarly broken (up to alpha11 included)
1236 This function allows one to get them back in order, this function works
1237 only after the database comparison that --full mode uses and which
1238 populates the dnToRecalculate and dnNotToRecalculate lists.
1240 The idea is that the SD can be safely recalculated from scratch to get it right.
1242 :param names: List of key provision parameters"""
1244 listWellknown = fix_wellknown_sd(samdb, names)
1246 if len(dnToRecalculate) != 0:
1247 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1248 % (len(dnToRecalculate)))
1250 for dn in dnToRecalculate:
1251 # well known SDs have already been reset
1252 if dn in listWellknown:
1256 sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1258 descr = get_empty_descriptor(names.domainsid)
1259 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1260 "nTSecurityDescriptor")
1261 samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1262 except LdbError as e:
1263 samdb.transaction_cancel()
1264 res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1266 attrs=["nTSecurityDescriptor"],
1267 controls=["sd_flags:1:%d" % sd_flags])
1268 badsd = ndr_unpack(security.descriptor,
1269 res[0]["nTSecurityDescriptor"][0])
1270 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1273 def hasATProvision(samdb):
1274 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1278 if entry is not None and len(entry) == 1:
1283 def removeProvisionUSN(samdb):
1284 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1285 entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1289 empty.dn = entry[0].dn
1290 delta = samdb.msg_diff(entry[0], empty)
1292 delta.dn = entry[0].dn
1295 def remove_stored_generated_attrs(paths, creds, session, lp):
1296 """Remove previously stored constructed attributes
1298 :param paths: List of paths for different provision objects
1299 from the upgraded provision
1300 :param creds: A credential object
1301 :param session: A session object
1302 :param lp: A line parser object
1303 :return: An associative array whose key are the different constructed
1304 attributes and the value the dn where this attributes were found.
1308 def simple_update_basesamdb(newpaths, paths, names):
1309 """Update the provision container db: sam.ldb
1310 This function is aimed at very old provision (before alpha9)
1312 :param newpaths: List of paths for different provision objects
1313 from the reference provision
1314 :param paths: List of paths for different provision objects
1315 from the upgraded provision
1316 :param names: List of key provision parameters"""
1318 message(SIMPLE, "Copy samdb")
1319 tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1321 message(SIMPLE, "Update partitions filename if needed")
1322 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1323 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1324 usersldb = os.path.join(paths.private_dir, "users.ldb")
1325 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1327 if not os.path.isdir(samldbdir):
1329 os.chmod(samldbdir, 0o700)
1330 if os.path.isfile(schemaldb):
1331 tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1332 "%s.ldb"%str(names.schemadn).upper()))
1333 os.remove(schemaldb)
1334 if os.path.isfile(usersldb):
1335 tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1336 "%s.ldb"%str(names.rootdn).upper()))
1338 if os.path.isfile(configldb):
1339 tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1340 "%s.ldb"%str(names.configdn).upper()))
1341 os.remove(configldb)
1344 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1345 """Upgrade the SAM DB contents for all the provision partitions
1347 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1349 :param samdb: An LDB object connected to the sam.ldb of the update
1351 :param names: List of key provision parameters
1352 :param provisionUSNs: A dictionary with range of USN modified during provision
1353 or upgradeprovision. Ranges are grouped by invocationID.
1354 :param schema: A Schema object that represent the schema of the provision
1355 :param prereloadfunc: A function that must be executed just before the reload
1359 message(SIMPLE, "Starting update of samdb")
1360 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1361 schema, provisionUSNs, prereloadfunc)
1363 message(SIMPLE, "Update of samdb finished")
1366 message(SIMPLE, "Update failed")
1370 def backup_provision(samdb, paths, dir, only_db):
1371 """This function backup the provision files so that a rollback
1374 :param paths: Paths to different objects
1375 :param dir: Directory where to store the backup
1376 :param only_db: Skip sysvol for users with big sysvol
1379 # Currently we default to tdb for the backend store type
1381 backend_store = "tdb"
1382 res = samdb.search(base="@PARTITION",
1383 scope=ldb.SCOPE_BASE,
1384 attrs=["backendStore"])
1385 if "backendStore" in res[0]:
1386 backend_store = str(res[0]["backendStore"][0])
1389 if paths.sysvol and not only_db:
1390 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1392 tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1393 tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1394 tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1395 tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1396 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1397 tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1398 shutil.copy2(paths.smbconf, dir)
1399 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1401 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1402 if not os.path.isdir(samldbdir):
1403 samldbdir = paths.private_dir
1404 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1405 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1406 usersldb = os.path.join(paths.private_dir, "users.ldb")
1407 tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1408 tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1409 tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1411 os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
1413 for ldb_name in os.listdir(samldbdir):
1414 if not ldb_name.endswith("-lock"):
1415 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1416 mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1417 os.path.join(dir, "sam.ldb.d", ldb_name))
1419 tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1420 os.path.join(dir, "sam.ldb.d", ldb_name))
1423 def sync_calculated_attributes(samdb, names):
1424 """Synchronize attributes used for constructed ones, with the
1425 old constructed that were stored in the database.
1427 This apply for instance to msds-keyversionnumber that was
1428 stored and that is now constructed from replpropertymetadata.
1430 :param samdb: An LDB object attached to the currently upgraded samdb
1431 :param names: Various key parameter about current provision.
1433 listAttrs = ["msDs-KeyVersionNumber"]
1434 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1435 if "msDs-KeyVersionNumber" in hash:
1436 increment_calculated_keyversion_number(samdb, names.rootdn,
1437 hash["msDs-KeyVersionNumber"])
1439 # Synopsis for updateprovision
1440 # 1) get path related to provision to be update (called current)
1441 # 2) open current provision ldbs
1442 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1444 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1445 # by either upgradeprovision or provision
1446 # 5) creation of a new provision the latest version of provision script
1447 # (called reference)
1448 # 6) get reference provision paths
1449 # 7) open reference provision ldbs
1450 # 8) setup helpers data that will help the update process
1451 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1452 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1453 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1454 # 10)get the oemInfo field, this field contains information about the different
1455 # provision that have been done
1456 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1457 # A) When alpha9 or alphaxx not specified (default)
1458 # The base sam.ldb file is updated by looking at the difference between
1459 # referrence one and the current one. Everything is copied with the
1460 # exception of lastProvisionUSN attributes.
1461 # B) Other case (it reflect that that provision was done before alpha9)
1462 # The base sam.ldb of the reference provision is copied over
1463 # the current one, if necessary ldb related to partitions are moved
1465 # The highest used USN is fetched so that changed by upgradeprovision
1466 # usn can be tracked
1467 # 12)A Schema object is created, it will be used to provide a complete
1468 # schema to current provision during update (as the schema of the
1469 # current provision might not be complete and so won't allow some
1470 # object to be created)
1471 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1472 # 14)The secrets db is updated by pull all the difference from the reference
1473 # provision into the current provision
1474 # 15)As the previous step has most probably modified the password stored in
1475 # in secret for the current DC, a new password is generated,
1476 # the kvno is bumped and the entry in samdb is also updated
1477 # 16)For current provision older than alpha9, we must fix the SD a little bit
1478 # administrator to update them because SD used to be generated with the
1479 # system account before alpha9.
1480 # 17)The highest usn modified so far is searched in the database it will be
1481 # the upper limit for usn modified during provision.
1482 # This is done before potential SD recalculation because we do not want
1483 # SD modified during recalculation to be marked as modified during provision
1484 # (and so possibly remplaced at next upgradeprovision)
1485 # 18)Rebuilt SD if the flag indicate to do so
1486 # 19)Check difference between SD of reference provision and those of the
1487 # current provision. The check is done by getting the sddl representation
1488 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1489 # Each part is verified separetly, for dacl and sacl ACL is splited into
1490 # ACEs and each ACE is verified separately (so that a permutation in ACE
1491 # didn't raise as an error).
1492 # 20)The oemInfo field is updated to add information about the fact that the
1493 # provision has been updated by the upgradeprovision version xxx
1494 # (the version is the one obtained when starting samba with the --version
1496 # 21)Check if the current provision has all the settings needed for dynamic
1497 # DNS update to work (that is to say the provision is newer than
1498 # january 2010). If not dns configuration file from reference provision
1499 # are copied in a sub folder and the administrator is invited to
1500 # do what is needed.
1501 # 22)If the lastProvisionUSN attribute was present it is updated to add
1502 # the range of usns modified by the current upgradeprovision
1505 # About updating the sam DB
1506 # The update takes place in update_partition function
1507 # This function read both current and reference provision and list all
1508 # the available DN of objects
1509 # If the string representation of a DN in reference provision is
1510 # equal to the string representation of a DN in current provision
1511 # (without taking care of case) then the object is flaged as being
1512 # present. If the object is not present in current provision the object
1513 # is being flaged as missing in current provision. Object present in current
1514 # provision but not in reference provision are ignored.
1515 # Once the list of objects present and missing is done, the deleted object
1516 # containers are created in the differents partitions (if missing)
1518 # Then the function add_missing_entries is called
1519 # This function will go through the list of missing entries by calling
1520 # add_missing_object for the given object. If this function returns 0
1521 # it means that the object needs some other object in order to be created
1522 # The object is reappended at the end of the list to be created later
1523 # (and preferably after all the needed object have been created)
1524 # The function keeps on looping on the list of object to be created until
1525 # it's empty or that the number of deferred creation is equal to the number
1526 # of object that still needs to be created.
1528 # The function add_missing_object will first check if the object can be created.
1529 # That is to say that it didn't depends other not yet created objects
1530 # If requisit can't be fullfilled it exists with 0
1531 # Then it will try to create the missing entry by creating doing
1532 # an ldb_message_diff between the object in the reference provision and
1534 # This resulting object is filtered to remove all the back link attribute
1535 # (ie. memberOf) as they will be created by the other linked object (ie.
1536 # the one with the member attribute)
1537 # All attributes specified in the attrNotCopied array are
1538 # also removed it's most of the time generated attributes
1540 # After missing entries have been added the update_partition function will
1541 # take care of object that exist but that need some update.
1542 # In order to do so the function update_present is called with the list
1543 # of object that are present in both provision and that might need an update.
1545 # This function handle first case mismatch so that the DN in the current
1546 # provision have the same case as in reference provision
1548 # It will then construct an associative array consiting of attributes as
1549 # key and invocationid as value( if the originating invocation id is
1550 # different from the invocation id of the current DC the value is -1 instead).
1552 # If the range of provision modified attributes is present, the function will
1553 # use the replMetadataProperty update method which is the following:
1554 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1555 # creationTime, msDs-KeyVersionNumber, oEMInformation
1556 # Check for each attribute if its usn is within one of the modified by
1557 # provision range and if its originating id is the invocation id of the
1558 # current DC, then validate the update from reference to current.
1559 # If not or if there is no replMetatdataProperty for this attribute then we
1561 # Otherwise (case the range of provision modified attribute is not present) it
1562 # use the following process:
1563 # All attributes that need to be added are accepted at the exeption of those
1564 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1565 # correct flags specified.
1566 # For attributes that need to be modified or removed, a check is performed
1567 # in OverwrittenAtt, if the attribute is present and the modification flag
1568 # (remove, delete) is one of those listed for this attribute then modification
1569 # is accepted. For complicated handling of attribute update, the control is passed
1570 # to handle_special_case
1574 if __name__ == '__main__':
1575 global defSDmodified
1576 defSDmodified = False
1578 # From here start the big steps of the program
1579 # 1) First get files paths
1580 paths = get_paths(param, smbconf=smbconf)
1581 # Get ldbs with the system session, it is needed for searching
1582 # provision parameters
1583 session = system_session()
1585 # This variable will hold the last provision USN once if it exists.
1588 ldbs = get_ldbs(paths, creds, session, lp)
1589 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1590 prefix="backupprovision")
1591 backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1593 ldbs.startTransactions()
1595 # 3) Guess all the needed names (variables in fact) from the current
1597 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1600 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1601 if lastProvisionUSNs is not None:
1603 for k in lastProvisionUSNs.keys():
1604 for r in lastProvisionUSNs[k]:
1608 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1609 (len(lastProvisionUSNs.keys()), v /2 ))
1611 if lastProvisionUSNs.get("default") is not None:
1612 message(CHANGE, "Old style for usn ranges used")
1613 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1614 del lastProvisionUSNs["default"]
1616 message(SIMPLE, "Your provision lacks provision range information")
1617 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1618 ldbs.groupedRollback()
1620 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1621 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1622 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1623 " of hundred of objects or more")
1624 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1627 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1629 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1632 # Objects will be created with the admin session
1633 # (not anymore system session)
1634 adm_session = admin_session(lp, str(names.domainsid))
1635 # So we reget handle on objects
1636 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1638 if not sanitychecks(ldbs.sam, names):
1639 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1640 "Check the messages and correct the errors "
1641 "before rerunning upgradeprovision")
1642 ldbs.groupedRollback()
1645 # Let's see provision parameters
1646 print_provision_key_parameters(names)
1648 # 5) With all this information let's create a fresh new provision used as
1650 message(SIMPLE, "Creating a reference provision")
1651 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1652 prefix="referenceprovision")
1653 result = newprovision(names, session, smbconf, provisiondir,
1654 provision_logger, base_schema="2008_R2")
1655 result.report_logger(provision_logger)
1659 # We need to get a list of object which SD is directly computed from
1660 # defaultSecurityDescriptor.
1661 # This will allow us to know which object we can rebuild the SD in case
1662 # of change of the parent's SD or of the defaultSD.
1663 # Get file paths of this new provision
1664 newpaths = get_paths(param, targetdir=provisiondir)
1665 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1666 new_ldbs.startTransactions()
1668 populateNotReplicated(new_ldbs.sam, names.schemadn)
1669 # 8) Populate some associative array to ease the update process
1670 # List of attribute which are link and backlink
1671 populate_links(new_ldbs.sam, names.schemadn)
1672 # List of attribute with ASN DN synthax)
1673 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1674 # 9) (now skipped, was copy of privileges.ldb)
1676 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1677 # Do some modification on sam.ldb
1678 ldbs.groupedCommit()
1679 new_ldbs.groupedCommit()
1683 if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1685 # Starting from alpha9 we can consider that the structure is quite ok
1686 # and that we should do only dela
1687 deltaattr = delta_update_basesamdb(newpaths.samdb,
1695 simple_update_basesamdb(newpaths, paths, names)
1696 ldbs = get_ldbs(paths, creds, session, lp)
1697 removeProvisionUSN(ldbs.sam)
1699 ldbs.startTransactions()
1700 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1701 new_ldbs.startTransactions()
1704 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1705 # We create a closure that will be invoked just before schema reload
1706 def schemareloadclosure():
1707 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1708 options=["modules:"])
1710 if deltaattr is not None and len(deltaattr) > 1:
1713 deltaattr.remove("dn")
1714 for att in deltaattr:
1715 if att.lower() == "dn":
1717 if (deltaattr.get(att) is not None
1718 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1720 elif deltaattr.get(att) is None:
1723 message(CHANGE, "Applying delta to @ATTRIBUTES")
1724 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1725 basesam.modify(deltaattr)
1727 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1728 "there is not only add")
1731 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1732 schema, schemareloadclosure):
1733 message(SIMPLE, "Rolling back all changes. Check the cause"
1735 message(SIMPLE, "Your system is as it was before the upgrade")
1736 ldbs.groupedRollback()
1737 new_ldbs.groupedRollback()
1738 shutil.rmtree(provisiondir)
1741 # Try to reapply the change also when we do not change the sam
1742 # as the delta_upgrade
1743 schemareloadclosure()
1744 sync_calculated_attributes(ldbs.sam, names)
1745 res = ldbs.sam.search(expression="(samaccountname=dns)",
1746 scope=SCOPE_SUBTREE, attrs=["dn"],
1747 controls=["search_options:1:2"])
1749 message(SIMPLE, "You still have the old DNS object for managing "
1750 "dynamic DNS, but you didn't supply --full so "
1751 "a correct update can't be done")
1752 ldbs.groupedRollback()
1753 new_ldbs.groupedRollback()
1754 shutil.rmtree(provisiondir)
1757 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1759 res = ldbs.sam.search(expression="(samaccountname=dns)",
1760 scope=SCOPE_SUBTREE, attrs=["dn"],
1761 controls=["search_options:1:2"])
1764 ldbs.sam.delete(res[0]["dn"])
1765 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1766 scope=SCOPE_SUBTREE, attrs=["dn"])
1767 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1768 message(SIMPLE, "IMPORTANT!!! "
1769 "If you were using Dynamic DNS before you need "
1770 "to update your configuration, so that the "
1771 "tkey-gssapi-credential has the following value: "
1772 "DNS/%s.%s" % (names.netbiosname.lower(),
1773 names.realm.lower()))
1775 message(SIMPLE, "Update machine account")
1776 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1778 # 16) SD should be created with admin but as some previous acl were so wrong
1779 # that admin can't modify them we have first to recreate them with the good
1780 # form but with system account and then give the ownership to admin ...
1781 if opts.very_old_pre_alpha9:
1782 message(SIMPLE, "Fixing very old provision SD")
1783 rebuild_sd(ldbs.sam, names)
1785 # We calculate the max USN before recalculating the SD because we might
1786 # touch object that have been modified after a provision and we do not
1787 # want that the next upgradeprovision thinks that it has a green light
1791 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1793 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1794 # defSDmodified is set.
1795 if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1796 message(SIMPLE, "Some (default) security descriptors (SDs) have "
1797 "changed, recalculating them")
1798 ldbs.sam.set_session_info(adm_session)
1799 rebuild_sd(ldbs.sam, names)
1802 # Now we are quite confident in the recalculate process of the SD, we make
1803 # it optional. And we don't do it if there is DN that we must touch
1804 # as we are assured that on this DNs we will have differences !
1805 # Also the check must be done in a clever way as for the moment we just
1807 if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1808 message(CHANGESD, "Checking recalculated SDs")
1809 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1812 updateOEMInfo(ldbs.sam, str(names.rootdn))
1814 check_for_DNS(newpaths.private_dir, paths.private_dir,
1815 newpaths.binddns_dir, paths.binddns_dir,
1818 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1819 if opts.full and (names.policyid is None or names.policyid_dc is None):
1820 update_policyids(names, ldbs.sam)
1824 update_gpo(paths, ldbs.sam, names, lp, message)
1825 except ProvisioningError as e:
1826 message(ERROR, "The policy for domain controller is missing. "
1827 "You should restart upgradeprovision with --full")
1829 ldbs.groupedCommit()
1830 new_ldbs.groupedCommit()
1831 message(SIMPLE, "Upgrade finished!")
1832 # remove reference provision now that everything is done !
1833 # So we have reindexed first if need when the merged schema was reloaded
1834 # (as new attributes could have quick in)
1835 # But the second part of the update (when we update existing objects
1836 # can also have an influence on indexing as some attribute might have their
1837 # searchflag modificated
1838 message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1839 "after modification")
1840 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1841 message(SIMPLE, "Reindexing finished")
1843 shutil.rmtree(provisiondir)
1844 except Exception as err:
1845 message(ERROR, "A problem occurred while trying to upgrade your "
1846 "provision. A full backup is located at %s" % backupdir)
1847 if opts.debugall or opts.debugchange:
1848 (typ, val, tb) = sys.exc_info()
1849 traceback.print_exception(typ, val, tb)