4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
38 import samba.getopt as options
40 from base64 import b64encode
41 from samba.credentials import DONT_USE_KERBEROS
42 from samba.auth import system_session, admin_session
43 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
44 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
45 MessageElement, Message, Dn, LdbError)
46 from samba import param, dsdb, Ldb
47 from samba.common import confirm
48 from samba.provision import (get_domain_descriptor, find_provision_key_parameters,
49 get_config_descriptor,
50 ProvisioningError, get_last_provision_usn,
51 get_max_usn, update_provision_usn, setup_path)
52 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
53 from samba.dcerpc import security, drsblobs
54 from samba.ndr import ndr_unpack
55 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
57 usn_in_range, identic_rename, get_diff_sddls,
58 update_secrets, CHANGE, ERROR, SIMPLE,
59 CHANGEALL, GUESS, CHANGESD, PROVISION,
60 updateOEMInfo, getOEMInfo, update_gpo,
61 delta_update_basesamdb, update_policyids,
62 update_machine_account_password,
63 search_constructed_attrs_stored,
64 int64range2str, update_dns_account_password,
65 increment_calculated_keyversion_number)
66 from samba.xattr import copytree_with_xattrs
68 replace=2**FLAG_MOD_REPLACE
70 delete=2**FLAG_MOD_DELETE
74 # Will be modified during provision to tell if default sd has been modified
77 #Errors are always logged
79 __docformat__ = "restructuredText"
81 # Attributes that are never copied from the reference provision (even if they
82 # do not exist in the destination object).
83 # This is most probably because they are populated automatcally when object is
85 # This also apply to imported object from reference provision
86 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
87 "parentGUID", "objectCategory", "distinguishedName",
89 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
90 "unicodePwd", "dBCSPwd", "supplementalCredentials",
91 "gPCUserExtensionNames", "gPCMachineExtensionNames",
92 "maxPwdAge", "secret", "possibleInferiors", "privilege",
93 "sAMAccountType", "oEMInformation", "creationTime" ]
95 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
96 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
98 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
101 attrNotCopied = replAttrNotCopied
102 attrNotCopied.extend(nonreplAttrNotCopied)
103 attrNotCopied.extend(nonDSDBAttrNotCopied)
104 # Usually for an object that already exists we do not overwrite attributes as
105 # they might have been changed for good reasons. Anyway for a few of them it's
106 # mandatory to replace them otherwise the provision will be broken somehow.
107 # But for attribute that are just missing we do not have to specify them as the default
108 # behavior is to add missing attribute
109 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
110 "systemOnly":replace, "searchFlags":replace,
111 "mayContain":replace, "systemFlags":replace+add,
112 "description":replace, "operatingSystemVersion":replace,
113 "adminPropertyPages":replace, "groupType":replace,
114 "wellKnownObjects":replace, "privilege":never,
115 "defaultSecurityDescriptor": replace,
116 "rIDAvailablePool": never,
117 "versionNumber" : add,
118 "rIDNextRID": add, "rIDUsedPool": never,
119 "defaultSecurityDescriptor": replace + add,
120 "isMemberOfPartialAttributeSet": delete,
121 "attributeDisplayNames": replace + add,
122 "versionNumber": add}
124 dnNotToRecalculate = []
127 forwardlinked = set()
130 def define_what_to_log(opts):
134 if opts.debugchangesd:
135 what = what | CHANGESD
138 if opts.debugprovision:
139 what = what | PROVISION
141 what = what | CHANGEALL
145 parser = optparse.OptionParser("provision [options]")
146 sambaopts = options.SambaOptions(parser)
147 parser.add_option_group(sambaopts)
148 parser.add_option_group(options.VersionOptions(parser))
149 credopts = options.CredentialsOptions(parser)
150 parser.add_option_group(credopts)
151 parser.add_option("--setupdir", type="string", metavar="DIR",
152 help="directory with setup files")
153 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
154 parser.add_option("--debugguess", action="store_true",
155 help="Print information on which values are guessed")
156 parser.add_option("--debugchange", action="store_true",
157 help="Print information on what is different but won't be changed")
158 parser.add_option("--debugchangesd", action="store_true",
159 help="Print security descriptor differences")
160 parser.add_option("--debugall", action="store_true",
161 help="Print all available information (very verbose)")
162 parser.add_option("--resetfileacl", action="store_true",
163 help="Force a reset on filesystem acls in sysvol / netlogon share")
164 parser.add_option("--nontaclfix", action="store_true",
165 help="In full upgrade mode do not try to upgrade sysvol / netlogon acls")
166 parser.add_option("--fixntacl", action="store_true",
167 help="Only fix NT ACLs in sysvol / netlogon share")
168 parser.add_option("--db_backup_only", action="store_true",
169 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
170 parser.add_option("--full", action="store_true",
171 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
173 opts = parser.parse_args()[0]
175 handler = logging.StreamHandler(sys.stdout)
176 upgrade_logger = logging.getLogger("upgradeprovision")
177 upgrade_logger.setLevel(logging.INFO)
179 upgrade_logger.addHandler(handler)
181 provision_logger = logging.getLogger("provision")
182 provision_logger.addHandler(handler)
184 whatToLog = define_what_to_log(opts)
186 def message(what, text):
187 """Print a message if this message type has been selected to be printed
189 :param what: Category of the message
190 :param text: Message to print """
191 if (whatToLog & what) or what <= 0:
192 upgrade_logger.info("%s", text)
194 if len(sys.argv) == 1:
195 opts.interactive = True
196 lp = sambaopts.get_loadparm()
197 smbconf = lp.configfile
199 creds = credopts.get_credentials(lp)
200 creds.set_kerberos_state(DONT_USE_KERBEROS)
204 def check_for_DNS(refprivate, private):
205 """Check if the provision has already the requirement for dynamic dns
207 :param refprivate: The path to the private directory of the reference
209 :param private: The path to the private directory of the upgraded
212 spnfile = "%s/spn_update_list" % private
213 dnsfile = "%s/dns_update_list" % private
214 namedfile = lp.get("dnsupdate:path")
217 namedfile = "%s/named.conf.update" % private
219 if not os.path.exists(spnfile):
220 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
222 if not os.path.exists(dnsfile):
223 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
225 destdir = "%s/new_dns" % private
226 dnsdir = "%s/dns" % private
228 if not os.path.exists(namedfile):
229 if not os.path.exists(destdir):
231 if not os.path.exists(dnsdir):
233 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
234 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
235 message(SIMPLE, "It seems that your provision did not integrate "
236 "new rules for dynamic dns update of domain related entries")
237 message(SIMPLE, "A copy of the new bind configuration files and "
238 "template has been put in %s, you should read them and "
239 "configure dynamic dns updates" % destdir)
242 def populate_links(samdb, schemadn):
243 """Populate an array with all the back linked attributes
245 This attributes that are modified automaticaly when
246 front attibutes are changed
248 :param samdb: A LDB object for sam.ldb file
249 :param schemadn: DN of the schema for the partition"""
250 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
251 backlinked.extend(linkedAttHash.values())
252 for t in linkedAttHash.keys():
255 def isReplicated(att):
256 """ Indicate if the attribute is replicated or not
258 :param att: Name of the attribute to be tested
259 :return: True is the attribute is replicated, False otherwise
262 return (att not in not_replicated)
264 def populateNotReplicated(samdb, schemadn):
265 """Populate an array with all the attributes that are not replicated
267 :param samdb: A LDB object for sam.ldb file
268 :param schemadn: DN of the schema for the partition"""
269 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
270 str(schemadn)), scope=SCOPE_SUBTREE,
271 attrs=["lDAPDisplayName"])
273 not_replicated.append(str(elem["lDAPDisplayName"]))
276 def populate_dnsyntax(samdb, schemadn):
277 """Populate an array with all the attributes that have DN synthax
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="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
283 str(schemadn)), scope=SCOPE_SUBTREE,
284 attrs=["lDAPDisplayName"])
286 dn_syntax_att.append(elem["lDAPDisplayName"])
289 def sanitychecks(samdb, names):
290 """Make some checks before trying to update
292 :param samdb: An LDB object opened on sam.ldb
293 :param names: list of key provision parameters
294 :return: Status of check (1 for Ok, 0 for not Ok) """
295 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
296 scope=SCOPE_SUBTREE, attrs=["dn"],
297 controls=["search_options:1:2"])
299 print "No DC found. Your provision is most probably broken!"
302 print "Found %d domain controllers. For the moment " \
303 "upgradeprovision is not able to handle an upgrade on a " \
304 "domain with more than one DC. Please demote the other " \
305 "DC(s) before upgrading" % len(res)
311 def print_provision_key_parameters(names):
312 """Do a a pretty print of provision parameters
314 :param names: list of key provision parameters """
315 message(GUESS, "rootdn :" + str(names.rootdn))
316 message(GUESS, "configdn :" + str(names.configdn))
317 message(GUESS, "schemadn :" + str(names.schemadn))
318 message(GUESS, "serverdn :" + str(names.serverdn))
319 message(GUESS, "netbiosname :" + names.netbiosname)
320 message(GUESS, "defaultsite :" + names.sitename)
321 message(GUESS, "dnsdomain :" + names.dnsdomain)
322 message(GUESS, "hostname :" + names.hostname)
323 message(GUESS, "domain :" + names.domain)
324 message(GUESS, "realm :" + names.realm)
325 message(GUESS, "invocationid:" + names.invocation)
326 message(GUESS, "policyguid :" + names.policyid)
327 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
328 message(GUESS, "domainsid :" + str(names.domainsid))
329 message(GUESS, "domainguid :" + names.domainguid)
330 message(GUESS, "ntdsguid :" + names.ntdsguid)
331 message(GUESS, "domainlevel :" + str(names.domainlevel))
334 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
335 """Define more complicate update rules for some attributes
337 :param att: The attribute to be updated
338 :param delta: A messageElement object that correspond to the difference
339 between the updated object and the reference one
340 :param new: The reference object
341 :param old: The Updated object
342 :param useReplMetadata: A boolean that indicate if the update process
343 use replPropertyMetaData to decide what has to be updated.
344 :param basedn: The base DN of the provision
345 :param aldb: An ldb object used to build DN
346 :return: True to indicate that the attribute should be kept, False for
349 # We do most of the special case handle if we do not have the
350 # highest usn as otherwise the replPropertyMetaData will guide us more
352 if not useReplMetadata:
353 flag = delta.get(att).flags()
354 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
355 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
356 "CN=Services,CN=Configuration,%s" % basedn)
359 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
360 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
362 message(SIMPLE, "We suggest that you change the userAccountControl"
363 " for user Administrator from value %d to %d" %
364 (int(str(old[0][att])), int(str(new[0][att]))))
366 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
367 if (long(str(old[0][att])) == 0):
368 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
371 if (att == "member" and flag == FLAG_MOD_REPLACE):
375 for elem in old[0][att]:
376 hash[str(elem).lower()]=1
377 newval.append(str(elem))
379 for elem in new[0][att]:
380 if not hash.has_key(str(elem).lower()):
382 newval.append(str(elem))
384 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
389 if (att in ("gPLink", "gPCFileSysPath") and
390 flag == FLAG_MOD_REPLACE and
391 str(new[0].dn).lower() == str(old[0].dn).lower()):
395 if att == "forceLogoff":
396 ref=0x8000000000000000
397 oldval=int(old[0][att][0])
398 newval=int(new[0][att][0])
399 ref == old and ref == abs(new)
402 if att in ("adminDisplayName", "adminDescription"):
405 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
406 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
409 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
410 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
413 if (str(old[0].dn) == "%s" % (str(names.rootdn))
414 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
416 #Allow to change revision of ForestUpdates objects
417 if (att == "revision" or att == "objectVersion"):
418 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
420 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
423 # This is a bit of special animal as we might have added
424 # already SPN entries to the list that has to be modified
425 # So we go in detail to try to find out what has to be added ...
426 if (att == "servicePrincipalName" and delta.get(att).flags() == FLAG_MOD_REPLACE):
430 for elem in old[0][att]:
432 newval.append(str(elem))
434 for elem in new[0][att]:
435 if not hash.has_key(str(elem)):
437 newval.append(str(elem))
439 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
446 def dump_denied_change(dn, att, flagtxt, current, reference):
447 """Print detailed information about why a change is denied
449 :param dn: DN of the object which attribute is denied
450 :param att: Attribute that was supposed to be upgraded
451 :param flagtxt: Type of the update that should be performed
452 (add, change, remove, ...)
453 :param current: Value(s) of the current attribute
454 :param reference: Value(s) of the reference attribute"""
456 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
457 + " must not be changed/removed. Discarding the change")
458 if att == "objectSid" :
459 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
460 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
461 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
462 message(CHANGE, "old : %s" % int64range2str(current[0]))
463 message(CHANGE, "new : %s" % int64range2str(reference[0]))
466 for e in range(0, len(current)):
467 message(CHANGE, "old %d : %s" % (i, str(current[e])))
469 if reference is not None:
471 for e in range(0, len(reference)):
472 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
475 def handle_special_add(samdb, dn, names):
476 """Handle special operation (like remove) on some object needed during
479 This is mostly due to wrong creation of the object in previous provision.
480 :param samdb: An Ldb object representing the SAM database
481 :param dn: DN of the object to inspect
482 :param names: list of key provision parameters
486 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
488 #This entry was misplaced lets remove it if it exists
489 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
492 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
494 #This entry was misplaced lets remove it if it exists
495 dntoremove = "CN=Certificate Service DCOM Access,"\
496 "CN=Users, %s" % names.rootdn
498 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
500 #This entry was misplaced lets remove it if it exists
501 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
503 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
505 #This entry was misplaced lets remove it if it exists
506 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
508 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
509 "CN=Configuration,%s" % names.rootdn)
511 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
512 "CN=WellKnown Security Principals,"
513 "CN=Configuration,%s" % names.rootdn)
515 res = samdb.search(expression="(dn=%s)" % oldDn,
516 base=str(names.rootdn),
517 scope=SCOPE_SUBTREE, attrs=["dn"],
518 controls=["search_options:1:2"])
520 res2 = samdb.search(expression="(dn=%s)" % dn,
521 base=str(names.rootdn),
522 scope=SCOPE_SUBTREE, attrs=["dn"],
523 controls=["search_options:1:2"])
525 if len(res) > 0 and len(res2) == 0:
526 message(CHANGE, "Existing object %s must be replaced by %s. "
527 "Renaming old object" % (str(oldDn), str(dn)))
528 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
532 if dntoremove is not None:
533 res = samdb.search(expression="(cn=RID Set)",
534 base=str(names.rootdn),
535 scope=SCOPE_SUBTREE, attrs=["dn"],
536 controls=["search_options:1:2"])
540 res = samdb.search(expression="(dn=%s)" % dntoremove,
541 base=str(names.rootdn),
542 scope=SCOPE_SUBTREE, attrs=["dn"],
543 controls=["search_options:1:2"])
545 message(CHANGE, "Existing object %s must be replaced by %s. "
546 "Removing old object" % (dntoremove, str(dn)))
547 samdb.delete(res[0]["dn"])
553 def check_dn_nottobecreated(hash, index, listdn):
554 """Check if one of the DN present in the list has a creation order
555 greater than the current.
557 Hash is indexed by dn to be created, with each key
558 is associated the creation order.
560 First dn to be created has the creation order 0, second has 1, ...
561 Index contain the current creation order
563 :param hash: Hash holding the different DN of the object to be
565 :param index: Current creation order
566 :param listdn: List of DNs on which the current DN depends on
567 :return: None if the current object do not depend on other
568 object or if all object have been created before."""
572 key = str(dn).lower()
573 if hash.has_key(key) and hash[key] > index:
579 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
580 """Add a new object if the dependencies are satisfied
582 The function add the object if the object on which it depends are already
585 :param ref_samdb: Ldb object representing the SAM db of the reference
587 :param samdb: Ldb object representing the SAM db of the upgraded
589 :param dn: DN of the object to be added
590 :param names: List of key provision parameters
591 :param basedn: DN of the partition to be updated
592 :param hash: Hash holding the different DN of the object to be
594 :param index: Current creation order
595 :return: True if the object was created False otherwise"""
597 ret = handle_special_add(samdb, dn, names)
606 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
607 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
609 delta = samdb.msg_diff(empty, reference[0])
613 if str(reference[0].get("cn")) == "RID Set":
614 for klass in reference[0].get("objectClass"):
615 if str(klass).lower() == "ridset":
618 if delta.get("objectSid"):
619 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
620 m = re.match(r".*-(\d+)$", sid)
621 if m and int(m.group(1))>999:
622 delta.remove("objectSid")
623 for att in attrNotCopied:
625 for att in backlinked:
627 depend_on_yettobecreated = None
628 for att in dn_syntax_att:
629 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
631 if depend_on_yet_tobecreated is not None:
632 message(CHANGE, "Object %s depends on %s in attribute %s. "
633 "Delaying the creation" % (dn,
634 depend_on_yet_tobecreated, att))
639 message(CHANGE,"Object %s will be added" % dn)
640 samdb.add(delta, ["relax:0", "provision:0"])
642 message(CHANGE,"Object %s was skipped" % dn)
646 def gen_dn_index_hash(listMissing):
647 """Generate a hash associating the DN to its creation order
649 :param listMissing: List of DN
650 :return: Hash with DN as keys and creation order as values"""
652 for i in range(0, len(listMissing)):
653 hash[str(listMissing[i]).lower()] = i
656 def add_deletedobj_containers(ref_samdb, samdb, names):
657 """Add the object containter: CN=Deleted Objects
659 This function create the container for each partition that need one and
660 then reference the object into the root of the partition
662 :param ref_samdb: Ldb object representing the SAM db of the reference
664 :param samdb: Ldb object representing the SAM db of the upgraded provision
665 :param names: List of key provision parameters"""
668 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
669 partitions = [str(names.rootdn), str(names.configdn)]
670 for part in partitions:
671 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
672 base=part, scope=SCOPE_SUBTREE,
674 controls=["show_deleted:0",
676 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
677 base=part, scope=SCOPE_SUBTREE,
679 controls=["show_deleted:0",
681 if len(ref_delObjCnt) > len(delObjCnt):
682 reference = ref_samdb.search(expression="cn=Deleted Objects",
683 base=part, scope=SCOPE_SUBTREE,
684 controls=["show_deleted:0",
687 delta = samdb.msg_diff(empty, reference[0])
689 delta.dn = Dn(samdb, str(reference[0]["dn"]))
690 for att in attrNotCopied:
693 modcontrols = ["relax:0", "provision:0"]
694 samdb.add(delta, modcontrols)
697 res = samdb.search(expression="(objectClass=*)", base=part,
699 attrs=["dn", "wellKnownObjects"])
701 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
705 wko = res[0]["wellKnownObjects"]
707 # The wellKnownObject that we want to add.
709 if str(o) == targetWKO:
711 listwko.append(str(o))
714 listwko.append(targetWKO)
717 delta.dn = Dn(samdb, str(res[0]["dn"]))
718 delta["wellKnownObjects"] = MessageElement(listwko,
723 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
724 """Add the missing object whose DN is the list
726 The function add the object if the objects on which it depends are
729 :param ref_samdb: Ldb object representing the SAM db of the reference
731 :param samdb: Ldb object representing the SAM db of the upgraded
733 :param dn: DN of the object to be added
734 :param names: List of key provision parameters
735 :param basedn: DN of the partition to be updated
736 :param list: List of DN to be added in the upgraded provision"""
741 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
743 listMissing = listDefered
745 hashMissing = gen_dn_index_hash(listMissing)
746 for dn in listMissing:
747 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
751 # DN can't be created because it depends on some
752 # other DN in the list
753 listDefered.append(dn)
755 if len(listDefered) != 0:
756 raise ProvisioningError("Unable to insert missing elements: "
757 "circular references")
759 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
760 """This function handle updates on links
762 :param samdb: An LDB object pointing to the updated provision
763 :param att: Attribute to update
764 :param basedn: The root DN of the provision
765 :param dn: The DN of the inspected object
766 :param value: The value of the attribute
767 :param ref_value: The value of this attribute in the reference provision
768 :param delta: The MessageElement object that will be applied for
769 transforming the current provision"""
771 res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
780 newlinklist.append(str(v))
784 # for w2k domain level the reveal won't reveal anything ...
785 # it means that we can readd links that were removed on purpose ...
786 # Also this function in fact just accept add not removal
788 for e in res[0][att]:
789 if not hash.has_key(e):
790 # We put in the blacklist all the element that are in the "revealed"
791 # result and not in the "standard" result
792 # This element are links that were removed before and so that
793 # we don't wan't to readd
797 if not blacklist.has_key(e) and not hash.has_key(e):
798 newlinklist.append(str(e))
801 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
808 msg_elt_flag_strs = {
809 ldb.FLAG_MOD_ADD: "MOD_ADD",
810 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
811 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
813 def checkKeepAttributeOldMtd(delta, att, reference, current,
815 """ Check if we should keep the attribute modification or not.
816 This function didn't use replicationMetadata to take a decision.
818 :param delta: A message diff object
819 :param att: An attribute
820 :param reference: A message object for the current entry comming from
821 the reference provision.
822 :param current: A message object for the current entry commin from
823 the current provision.
824 :param basedn: The DN of the partition
825 :param samdb: A ldb connection to the sam database of the current provision.
827 :return: The modified message diff.
829 # Old school way of handling things for pre alpha12 upgrade
835 for att in list(delta):
836 msgElt = delta.get(att)
838 if att == "nTSecurityDescriptor":
846 if not hashOverwrittenAtt.has_key(att):
847 if msgElt.flags() != FLAG_MOD_ADD:
848 if not handle_special_case(att, delta, reference, current,
849 False, basedn, samdb):
850 if opts.debugchange or opts.debugall:
852 dump_denied_change(dn, att,
853 msg_elt_flag_strs[msgElt.flags()],
854 current[0][att], reference[0][att])
856 dump_denied_change(dn, att,
857 msg_elt_flag_strs[msgElt.flags()],
858 current[0][att], None)
862 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
864 elif hashOverwrittenAtt.get(att)==never:
870 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
871 hash_attr_usn, basedn, usns, samdb):
872 """ Check if we should keep the attribute modification or not
874 :param delta: A message diff object
875 :param att: An attribute
876 :param message: A function to print messages
877 :param reference: A message object for the current entry comming from
878 the reference provision.
879 :param current: A message object for the current entry commin from
880 the current provision.
881 :param hash_attr_usn: A dictionnary with attribute name as keys,
882 USN and invocation id as values.
883 :param basedn: The DN of the partition
884 :param usns: A dictionnary with invocation ID as keys and USN ranges
886 :param samdb: A ldb object pointing to the sam DB
888 :return: The modified message diff.
895 for att in list(delta):
896 if att in ["dn", "objectSid"]:
900 # We have updated by provision usn information so let's exploit
901 # replMetadataProperties
902 if att in forwardlinked:
903 curval = current[0].get(att, ())
904 refval = reference[0].get(att, ())
905 delta = handle_links(samdb, att, basedn, current[0]["dn"],
906 curval, refval, delta)
910 if isFirst and len(list(delta)) > 1:
912 txt = "%s\n" % (str(dn))
914 if handle_special_case(att, delta, reference, current, True, None, None):
915 # This attribute is "complicated" to handle and handling
916 # was done in handle_special_case
920 if hash_attr_usn.get(att):
921 [attrUSN, attInvId] = hash_attr_usn.get(att)
924 # If it's a replicated attribute and we don't have any USN
925 # information about it. It means that we never saw it before
927 # If it is a replicated attribute but we are not master on it
928 # (ie. not initially added in the provision we masterize).
930 if isReplicated(att):
933 message(CHANGE, "Non replicated attribute %s changed" % att)
936 if att == "nTSecurityDescriptor":
937 cursd = ndr_unpack(security.descriptor,
938 str(current[0]["nTSecurityDescriptor"]))
939 cursddl = cursd.as_sddl(names.domainsid)
940 refsd = ndr_unpack(security.descriptor,
941 str(reference[0]["nTSecurityDescriptor"]))
942 refsddl = refsd.as_sddl(names.domainsid)
944 diff = get_diff_sddls(refsddl, cursddl)
946 # FIXME find a way to have it only with huge huge verbose mode
947 # message(CHANGE, "%ssd are identical" % txt)
953 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
956 message(CHANGESD, "But the SD has been changed by someonelse "\
957 "so it's impossible to know if the difference"\
958 " cames from the modification or from a previous bug")
959 dnNotToRecalculate.append(str(dn))
961 dnToRecalculate.append(str(dn))
965 # This attribute was last modified by another DC forget
967 message(CHANGE, "%sAttribute: %s has been "
968 "created/modified/deleted by another DC. "
969 "Doing nothing" % (txt, att))
973 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
974 message(CHANGE, "%sAttribute: %s was not "
975 "created/modified/deleted during a "
976 "provision or upgradeprovision. Current "
977 "usn: %d. Doing nothing" % (txt, att,
983 if att == "defaultSecurityDescriptor":
986 message(CHANGE, "%sAttribute: %s will be modified"
987 "/deleted it was last modified "
988 "during a provision. Current usn: "
989 "%d" % (txt, att, attrUSN))
992 message(CHANGE, "%sAttribute: %s will be added because "
993 "it did not exist before" % (txt, att))
999 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
1000 """ This function updates the object that are already present in the
1003 :param ref_samdb: An LDB object pointing to the reference provision
1004 :param samdb: An LDB object pointing to the updated provision
1005 :param basedn: A string with the value of the base DN for the provision
1006 (ie. DC=foo, DC=bar)
1007 :param listPresent: A list of object that is present in the provision
1008 :param usns: A list of USN range modified by previous provision and
1009 upgradeprovision grouped by invocation ID
1012 # This hash is meant to speedup lookup of attribute name from an oid,
1013 # it's for the replPropertyMetaData handling
1015 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
1016 controls=["search_options:1:2"], attrs=["attributeID",
1020 strDisplay = str(e.get("lDAPDisplayName"))
1021 hash_oid_name[str(e.get("attributeID"))] = strDisplay
1023 msg = "Unable to insert missing elements: circular references"
1024 raise ProvisioningError(msg)
1027 controls = ["search_options:1:2", "sd_flags:1:0"]
1028 if usns is not None:
1029 message(CHANGE, "Using replPropertyMetadata for change selection")
1030 for dn in listPresent:
1031 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1032 scope=SCOPE_SUBTREE,
1034 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1035 scope=SCOPE_SUBTREE, controls=controls)
1038 (str(current[0].dn) != str(reference[0].dn)) and
1039 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1041 message(CHANGE, "Names are the same except for the case. "
1042 "Renaming %s to %s" % (str(current[0].dn),
1043 str(reference[0].dn)))
1044 identic_rename(samdb, reference[0].dn)
1045 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1046 scope=SCOPE_SUBTREE,
1049 delta = samdb.msg_diff(current[0], reference[0])
1051 for att in backlinked:
1054 for att in attrNotCopied:
1057 delta.remove("name")
1059 nb_items = len(list(delta))
1064 if nb_items > 1 and usns is not None:
1065 # Fetch the replPropertyMetaData
1066 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1067 scope=SCOPE_SUBTREE, controls=controls,
1068 attrs=["replPropertyMetaData"])
1069 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1070 str(res[0]["replPropertyMetaData"])).ctr
1074 # We put in this hash only modification
1075 # made on the current host
1076 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1077 if str(o.originating_invocation_id) in usns.keys():
1078 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1080 hash_attr_usn[att] = [-1, None]
1082 if usns is not None:
1083 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1084 current, hash_attr_usn,
1085 basedn, usns, samdb)
1087 delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1093 # Skip dn as the value is not really changed ...
1094 attributes=", ".join(delta.keys()[1:])
1096 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1097 # Let's try to reduce as much as possible the use of relax control
1098 for attr in delta.keys():
1099 if attr.lower() in relaxedatt:
1100 modcontrols = ["relax:0", "provision:0"]
1101 message(CHANGE, "%s is different from the reference one, changed"
1102 " attributes: %s\n" % (dn, attributes))
1104 samdb.modify(delta, modcontrols)
1107 def reload_full_schema(samdb, names):
1108 """Load the updated schema with all the new and existing classes
1111 :param samdb: An LDB object connected to the sam.ldb of the update
1113 :param names: List of key provision parameters
1116 schemadn = str(names.schemadn)
1117 current = samdb.search(expression="objectClass=*", base=schemadn,
1118 scope=SCOPE_SUBTREE)
1123 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1125 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1126 prefixmap_data = b64encode(prefixmap_data)
1128 # We don't actually add this ldif, just parse it
1129 prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1131 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1134 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1135 """Check differences between the reference provision and the upgraded one.
1137 It looks for all objects which base DN is name.
1139 This function will also add the missing object and update existing object
1140 to add or remove attributes that were missing.
1142 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1144 :param samdb: An LDB object connected to the sam.ldb of the update
1146 :param basedn: String value of the DN of the partition
1147 :param names: List of key provision parameters
1148 :param schema: A Schema object
1149 :param provisionUSNs: A dictionnary with range of USN modified during provision
1150 or upgradeprovision. Ranges are grouped by invocationID.
1151 :param prereloadfunc: A function that must be executed just before the reload
1162 # Connect to the reference provision and get all the attribute in the
1163 # partition referred by name
1164 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1165 scope=SCOPE_SUBTREE, attrs=["dn"],
1166 controls=["search_options:1:2"])
1168 current = samdb.search(expression="objectClass=*", base=basedn,
1169 scope=SCOPE_SUBTREE, attrs=["dn"],
1170 controls=["search_options:1:2"])
1171 # Create a hash for speeding the search of new object
1172 for i in range(0, len(reference)):
1173 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1175 # Create a hash for speeding the search of existing object in the
1177 for i in range(0, len(current)):
1178 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1181 for k in hash_new.keys():
1182 if not hash.has_key(k):
1183 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1184 listMissing.append(hash_new[k])
1186 listPresent.append(hash_new[k])
1188 # Sort the missing object in order to have object of the lowest level
1189 # first (which can be containers for higher level objects)
1190 listMissing.sort(dn_sort)
1191 listPresent.sort(dn_sort)
1193 # The following lines is to load the up to
1194 # date schema into our current LDB
1195 # a complete schema is needed as the insertion of attributes
1196 # and class is done against it
1197 # and the schema is self validated
1198 samdb.set_schema(schema)
1200 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1201 add_deletedobj_containers(ref_samdb, samdb, names)
1203 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1206 message(SIMPLE, "Reloading a merged schema, which might trigger "
1207 "reindexing so please be patient")
1208 reload_full_schema(samdb, names)
1209 message(SIMPLE, "Schema reloaded!")
1211 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1213 message(SIMPLE, "There are %d changed objects" % (changed))
1216 except StandardError, err:
1217 message(ERROR, "Exception during upgrade of samdb:")
1218 (typ, val, tb) = sys.exc_info()
1219 traceback.print_exception(typ, val, tb)
1223 def check_updated_sd(ref_sam, cur_sam, names):
1224 """Check if the security descriptor in the upgraded provision are the same
1227 :param ref_sam: A LDB object connected to the sam.ldb file used as
1228 the reference provision
1229 :param cur_sam: A LDB object connected to the sam.ldb file used as
1231 :param names: List of key provision parameters"""
1232 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1233 scope=SCOPE_SUBTREE,
1234 attrs=["dn", "nTSecurityDescriptor"],
1235 controls=["search_options:1:2"])
1236 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1237 scope=SCOPE_SUBTREE,
1238 attrs=["dn", "nTSecurityDescriptor"],
1239 controls=["search_options:1:2"])
1241 for i in range(0, len(reference)):
1242 refsd = ndr_unpack(security.descriptor,
1243 str(reference[i]["nTSecurityDescriptor"]))
1244 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1247 for i in range(0, len(current)):
1248 key = str(current[i]["dn"]).lower()
1249 if hash.has_key(key):
1250 cursd = ndr_unpack(security.descriptor,
1251 str(current[i]["nTSecurityDescriptor"]))
1252 sddl = cursd.as_sddl(names.domainsid)
1253 if sddl != hash[key]:
1254 txt = get_diff_sddls(hash[key], sddl, False)
1256 message(CHANGESD, "On object %s ACL is different"
1257 " \n%s" % (current[i]["dn"], txt))
1261 def fix_partition_sd(samdb, names):
1262 """This function fix the SD for partition containers (basedn, configdn, ...)
1263 This is needed because some provision use to have broken SD on containers
1265 :param samdb: An LDB object pointing to the sam of the current provision
1266 :param names: A list of key provision parameters
1268 alwaysRecalculate = False
1269 if len(dnToRecalculate) == 0 and len(dnNotToRecalculate) == 0:
1270 alwaysRecalculate = True
1273 # NC's DN can't be both in dnToRecalculate and dnNotToRecalculate
1274 # First update the SD for the rootdn
1275 if alwaysRecalculate or str(names.rootdn) in dnToRecalculate:
1277 delta.dn = Dn(samdb, str(names.rootdn))
1278 descr = get_domain_descriptor(names.domainsid)
1279 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1280 "nTSecurityDescriptor")
1283 # Then the config dn
1284 if alwaysRecalculate or str(names.configdn) in dnToRecalculate:
1286 delta.dn = Dn(samdb, str(names.configdn))
1287 descr = get_config_descriptor(names.domainsid)
1288 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1289 "nTSecurityDescriptor" )
1292 # Then the schema dn
1293 if alwaysRecalculate or str(names.schemadn) in dnToRecalculate:
1295 delta.dn = Dn(samdb, str(names.schemadn))
1296 descr = get_schema_descriptor(names.domainsid)
1297 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1298 "nTSecurityDescriptor" )
1301 def rebuild_sd(samdb, names):
1302 """Rebuild security descriptor of the current provision from scratch
1304 During the different pre release of samba4 security descriptors (SD)
1305 were notarly broken (up to alpha11 included)
1306 This function allow to get them back in order, this function make the
1307 assumption that nobody has modified manualy an SD
1308 and so SD can be safely recalculated from scratch to get them right.
1310 :param names: List of key provision parameters"""
1312 fix_partition_sd(samdb, names)
1314 # List of namming contexts
1315 listNC = [str(names.rootdn), str(names.configdn), str(names.schemadn)]
1317 if len(dnToRecalculate) == 0:
1318 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1319 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1320 controls=["search_options:1:2"])
1322 hash[str(obj["dn"])] = obj["whenCreated"]
1324 for dn in dnToRecalculate:
1325 if hash.has_key(dn):
1327 # fetch each dn to recalculate and their child within the same partition
1328 res = samdb.search(expression="objectClass=*", base=dn,
1329 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"])
1331 hash[str(obj["dn"])] = obj["whenCreated"]
1333 listKeys = list(set(hash.keys()))
1334 listKeys.sort(dn_sort)
1336 if len(dnToRecalculate) != 0:
1337 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"\
1338 ", recalculating %d due to inheritance"
1339 % (len(dnToRecalculate), len(listKeys)))
1341 for key in listKeys:
1342 if (key in listNC or
1343 key in dnNotToRecalculate):
1346 delta.dn = Dn(samdb, key)
1348 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1350 samdb.modify(delta, ["recalculate_sd:0","relax:0"])
1352 samdb.transaction_cancel()
1353 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1354 scope=SCOPE_SUBTREE,
1355 attrs=["dn", "nTSecurityDescriptor"],
1356 controls=["search_options:1:2"])
1357 badsd = ndr_unpack(security.descriptor,
1358 str(res[0]["nTSecurityDescriptor"]))
1359 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1362 def hasATProvision(samdb):
1363 entry = samdb.search(expression="dn=@PROVISION", base = "",
1367 if entry != None and len(entry) == 1:
1372 def removeProvisionUSN(samdb):
1373 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1374 entry = samdb.search(expression="dn=@PROVISION", base = "",
1378 empty.dn = entry[0].dn
1379 delta = samdb.msg_diff(entry[0], empty)
1381 delta.dn = entry[0].dn
1384 def remove_stored_generated_attrs(paths, creds, session, lp):
1385 """Remove previously stored constructed attributes
1387 :param paths: List of paths for different provision objects
1388 from the upgraded provision
1389 :param creds: A credential object
1390 :param session: A session object
1391 :param lp: A line parser object
1392 :return: An associative array whose key are the different constructed
1393 attributes and the value the dn where this attributes were found.
1397 def simple_update_basesamdb(newpaths, paths, names):
1398 """Update the provision container db: sam.ldb
1399 This function is aimed at very old provision (before alpha9)
1401 :param newpaths: List of paths for different provision objects
1402 from the reference provision
1403 :param paths: List of paths for different provision objects
1404 from the upgraded provision
1405 :param names: List of key provision parameters"""
1407 message(SIMPLE, "Copy samdb")
1408 shutil.copy(newpaths.samdb, paths.samdb)
1410 message(SIMPLE, "Update partitions filename if needed")
1411 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1412 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1413 usersldb = os.path.join(paths.private_dir, "users.ldb")
1414 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1416 if not os.path.isdir(samldbdir):
1418 os.chmod(samldbdir, 0700)
1419 if os.path.isfile(schemaldb):
1420 shutil.copy(schemaldb, os.path.join(samldbdir,
1421 "%s.ldb"%str(names.schemadn).upper()))
1422 os.remove(schemaldb)
1423 if os.path.isfile(usersldb):
1424 shutil.copy(usersldb, os.path.join(samldbdir,
1425 "%s.ldb"%str(names.rootdn).upper()))
1427 if os.path.isfile(configldb):
1428 shutil.copy(configldb, os.path.join(samldbdir,
1429 "%s.ldb"%str(names.configdn).upper()))
1430 os.remove(configldb)
1433 def update_privilege(ref_private_path, cur_private_path):
1434 """Update the privilege database
1436 :param ref_private_path: Path to the private directory of the reference
1438 :param cur_private_path: Path to the private directory of the current
1439 (and to be updated) provision."""
1440 message(SIMPLE, "Copy privilege")
1441 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1442 os.path.join(cur_private_path, "privilege.ldb"))
1445 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1446 """Upgrade the SAM DB contents for all the provision partitions
1448 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1450 :param samdb: An LDB object connected to the sam.ldb of the update
1452 :param names: List of key provision parameters
1453 :param provisionUSNs: A dictionnary with range of USN modified during provision
1454 or upgradeprovision. Ranges are grouped by invocationID.
1455 :param schema: A Schema object that represent the schema of the provision
1456 :param prereloadfunc: A function that must be executed just before the reload
1460 message(SIMPLE, "Starting update of samdb")
1461 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1462 schema, provisionUSNs, prereloadfunc)
1464 message(SIMPLE, "Update of samdb finished")
1467 message(SIMPLE, "Update failed")
1471 def backup_provision(paths, dir, only_db):
1472 """This function backup the provision files so that a rollback
1475 :param paths: Paths to different objects
1476 :param dir: Directory where to store the backup
1477 :param only_db: Skip sysvol for users with big sysvol
1479 if paths.sysvol and not only_db:
1480 copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1481 shutil.copy2(paths.samdb, dir)
1482 shutil.copy2(paths.secrets, dir)
1483 shutil.copy2(paths.idmapdb, dir)
1484 shutil.copy2(paths.privilege, dir)
1485 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1486 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1487 shutil.copy2(paths.smbconf, dir)
1488 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1490 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1491 if not os.path.isdir(samldbdir):
1492 samldbdir = paths.private_dir
1493 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1494 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1495 usersldb = os.path.join(paths.private_dir, "users.ldb")
1496 shutil.copy2(schemaldb, dir)
1497 shutil.copy2(usersldb, dir)
1498 shutil.copy2(configldb, dir)
1500 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1503 def sync_calculated_attributes(samdb, names):
1504 """Synchronize attributes used for constructed ones, with the
1505 old constructed that were stored in the database.
1507 This apply for instance to msds-keyversionnumber that was
1508 stored and that is now constructed from replpropertymetadata.
1510 :param samdb: An LDB object attached to the currently upgraded samdb
1511 :param names: Various key parameter about current provision.
1513 listAttrs = ["msDs-KeyVersionNumber"]
1514 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1515 if hash.has_key("msDs-KeyVersionNumber"):
1516 increment_calculated_keyversion_number(samdb, names.rootdn,
1517 hash["msDs-KeyVersionNumber"])
1519 # Synopsis for updateprovision
1520 # 1) get path related to provision to be update (called current)
1521 # 2) open current provision ldbs
1522 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1524 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1525 # by either upgradeprovision or provision
1526 # 5) creation of a new provision the latest version of provision script
1527 # (called reference)
1528 # 6) get reference provision paths
1529 # 7) open reference provision ldbs
1530 # 8) setup helpers data that will help the update process
1531 # 9) update the privilege ldb by copying the one of referecence provision to
1532 # the current provision
1533 # 10)get the oemInfo field, this field contains information about the different
1534 # provision that have been done
1535 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1536 # integer) or none of this the following things are done
1537 # A) When alpha9 or alphaxx is present
1538 # The base sam.ldb file is updated by looking at the difference between
1539 # referrence one and the current one. Everything is copied with the
1540 # exception of lastProvisionUSN attributes.
1541 # B) Other case (it reflect that that provision was done before alpha9)
1542 # The base sam.ldb of the reference provision is copied over
1543 # the current one, if necessary ldb related to partitions are moved
1545 # The highest used USN is fetched so that changed by upgradeprovision
1546 # usn can be tracked
1547 # 12)A Schema object is created, it will be used to provide a complete
1548 # schema to current provision during update (as the schema of the
1549 # current provision might not be complete and so won't allow some
1550 # object to be created)
1551 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1552 # 14)The secrets db is updated by pull all the difference from the reference
1553 # provision into the current provision
1554 # 15)As the previous step has most probably modified the password stored in
1555 # in secret for the current DC, a new password is generated,
1556 # the kvno is bumped and the entry in samdb is also updated
1557 # 16)For current provision older than alpha9, we must fix the SD a little bit
1558 # administrator to update them because SD used to be generated with the
1559 # system account before alpha9.
1560 # 17)The highest usn modified so far is searched in the database it will be
1561 # the upper limit for usn modified during provision.
1562 # This is done before potential SD recalculation because we do not want
1563 # SD modified during recalculation to be marked as modified during provision
1564 # (and so possibly remplaced at next upgradeprovision)
1565 # 18)Rebuilt SD if the flag indicate to do so
1566 # 19)Check difference between SD of reference provision and those of the
1567 # current provision. The check is done by getting the sddl representation
1568 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1569 # Each part is verified separetly, for dacl and sacl ACL is splited into
1570 # ACEs and each ACE is verified separately (so that a permutation in ACE
1571 # didn't raise as an error).
1572 # 20)The oemInfo field is updated to add information about the fact that the
1573 # provision has been updated by the upgradeprovision version xxx
1574 # (the version is the one obtained when starting samba with the --version
1576 # 21)Check if the current provision has all the settings needed for dynamic
1577 # DNS update to work (that is to say the provision is newer than
1578 # january 2010). If not dns configuration file from reference provision
1579 # are copied in a sub folder and the administrator is invited to
1580 # do what is needed.
1581 # 22)If the lastProvisionUSN attribute was present it is updated to add
1582 # the range of usns modified by the current upgradeprovision
1585 # About updating the sam DB
1586 # The update takes place in update_partition function
1587 # This function read both current and reference provision and list all
1588 # the available DN of objects
1589 # If the string representation of a DN in reference provision is
1590 # equal to the string representation of a DN in current provision
1591 # (without taking care of case) then the object is flaged as being
1592 # present. If the object is not present in current provision the object
1593 # is being flaged as missing in current provision. Object present in current
1594 # provision but not in reference provision are ignored.
1595 # Once the list of objects present and missing is done, the deleted object
1596 # containers are created in the differents partitions (if missing)
1598 # Then the function add_missing_entries is called
1599 # This function will go through the list of missing entries by calling
1600 # add_missing_object for the given object. If this function returns 0
1601 # it means that the object needs some other object in order to be created
1602 # The object is reappended at the end of the list to be created later
1603 # (and preferably after all the needed object have been created)
1604 # The function keeps on looping on the list of object to be created until
1605 # it's empty or that the number of defered creation is equal to the number
1606 # of object that still needs to be created.
1608 # The function add_missing_object will first check if the object can be created.
1609 # That is to say that it didn't depends other not yet created objects
1610 # If requisit can't be fullfilled it exists with 0
1611 # Then it will try to create the missing entry by creating doing
1612 # an ldb_message_diff between the object in the reference provision and
1614 # This resulting object is filtered to remove all the back link attribute
1615 # (ie. memberOf) as they will be created by the other linked object (ie.
1616 # the one with the member attribute)
1617 # All attributes specified in the attrNotCopied array are
1618 # also removed it's most of the time generated attributes
1620 # After missing entries have been added the update_partition function will
1621 # take care of object that exist but that need some update.
1622 # In order to do so the function update_present is called with the list
1623 # of object that are present in both provision and that might need an update.
1625 # This function handle first case mismatch so that the DN in the current
1626 # provision have the same case as in reference provision
1628 # It will then construct an associative array consiting of attributes as
1629 # key and invocationid as value( if the originating invocation id is
1630 # different from the invocation id of the current DC the value is -1 instead).
1632 # If the range of provision modified attributes is present, the function will
1633 # use the replMetadataProperty update method which is the following:
1634 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1635 # creationTime, msDs-KeyVersionNumber, oEMInformation
1636 # Check for each attribute if its usn is within one of the modified by
1637 # provision range and if its originating id is the invocation id of the
1638 # current DC, then validate the update from reference to current.
1639 # If not or if there is no replMetatdataProperty for this attribute then we
1641 # Otherwise (case the range of provision modified attribute is not present) it
1642 # use the following process:
1643 # All attributes that need to be added are accepted at the exeption of those
1644 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1645 # correct flags specified.
1646 # For attributes that need to be modified or removed, a check is performed
1647 # in OverwrittenAtt, if the attribute is present and the modification flag
1648 # (remove, delete) is one of those listed for this attribute then modification
1649 # is accepted. For complicated handling of attribute update, the control is passed
1650 # to handle_special_case
1654 if __name__ == '__main__':
1655 global defSDmodified
1656 defSDmodified = False
1658 if opts.nontaclfix and opts.fixntacl:
1659 message(SIMPLE, "nontaclfix and fixntacl are mutally exclusive")
1660 # From here start the big steps of the program
1661 # 1) First get files paths
1662 paths = get_paths(param, smbconf=smbconf)
1663 # Get ldbs with the system session, it is needed for searching
1664 # provision parameters
1665 session = system_session()
1667 # This variable will hold the last provision USN once if it exists.
1670 ldbs = get_ldbs(paths, creds, session, lp)
1671 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1672 prefix="backupprovision")
1673 backup_provision(paths, backupdir, opts.db_backup_only)
1675 ldbs.startTransactions()
1677 # 3) Guess all the needed names (variables in fact) from the current
1679 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1682 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1683 if lastProvisionUSNs is not None:
1685 for k in lastProvisionUSNs.keys():
1686 for r in lastProvisionUSNs[k]:
1690 "Find last provision USN, %d invocation(s) for a total of %d ranges" % \
1691 (len(lastProvisionUSNs.keys()), v /2 ))
1693 if lastProvisionUSNs.get("default") != None:
1694 message(CHANGE, "Old style for usn ranges used")
1695 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1696 del lastProvisionUSNs["default"]
1698 message(SIMPLE, "Your provision lacks provision range information")
1699 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1700 ldbs.groupedRollback()
1701 os.system("%s %s %s %s %s" % (os.path.join(os.path.dirname(sys.argv[0]),
1702 "findprovisionusnranges"),
1707 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1710 # Objects will be created with the admin session
1711 # (not anymore system session)
1712 adm_session = admin_session(lp, str(names.domainsid))
1713 # So we reget handle on objects
1714 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1715 if not opts.fixntacl:
1716 if not sanitychecks(ldbs.sam, names):
1717 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1718 "Check the messages and correct the errors "
1719 "before rerunning upgradeprovision")
1720 ldbs.groupedRollback()
1723 # Let's see provision parameters
1724 print_provision_key_parameters(names)
1726 # 5) With all this information let's create a fresh new provision used as
1728 message(SIMPLE, "Creating a reference provision")
1729 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1730 prefix="referenceprovision")
1731 result = newprovision(names, creds, session, smbconf, provisiondir,
1733 result.report_logger(provision_logger)
1737 # We need to get a list of object which SD is directly computed from
1738 # defaultSecurityDescriptor.
1739 # This will allow us to know which object we can rebuild the SD in case
1740 # of change of the parent's SD or of the defaultSD.
1741 # Get file paths of this new provision
1742 newpaths = get_paths(param, targetdir=provisiondir)
1743 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1744 new_ldbs.startTransactions()
1746 populateNotReplicated(new_ldbs.sam, names.schemadn)
1747 # 8) Populate some associative array to ease the update process
1748 # List of attribute which are link and backlink
1749 populate_links(new_ldbs.sam, names.schemadn)
1750 # List of attribute with ASN DN synthax)
1751 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1753 update_privilege(newpaths.private_dir, paths.private_dir)
1755 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1756 # Do some modification on sam.ldb
1757 ldbs.groupedCommit()
1758 new_ldbs.groupedCommit()
1762 if oem is None or hasATProvision(ldbs.sam) or re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1764 # Starting from alpha9 we can consider that the structure is quite ok
1765 # and that we should do only dela
1766 deltaattr = delta_update_basesamdb(newpaths.samdb,
1774 simple_update_basesamdb(newpaths, paths, names)
1775 ldbs = get_ldbs(paths, creds, session, lp)
1776 removeProvisionUSN(ldbs.sam)
1778 ldbs.startTransactions()
1779 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1780 new_ldbs.startTransactions()
1783 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1784 # We create a closure that will be invoked just before schema reload
1785 def schemareloadclosure():
1786 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1787 options=["modules:"])
1789 if deltaattr is not None and len(deltaattr) > 1:
1792 deltaattr.remove("dn")
1793 for att in deltaattr:
1794 if att.lower() == "dn":
1796 if (deltaattr.get(att) is not None
1797 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1799 elif deltaattr.get(att) is None:
1802 message(CHANGE, "Applying delta to @ATTRIBUTES")
1803 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1804 basesam.modify(deltaattr)
1806 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1807 "there is not only add")
1810 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1811 schema, schemareloadclosure):
1812 message(SIMPLE, "Rolling back all changes. Check the cause"
1814 message(SIMPLE, "Your system is as it was before the upgrade")
1815 ldbs.groupedRollback()
1816 new_ldbs.groupedRollback()
1817 shutil.rmtree(provisiondir)
1820 # Try to reapply the change also when we do not change the sam
1821 # as the delta_upgrade
1822 schemareloadclosure()
1823 sync_calculated_attributes(ldbs.sam, names)
1824 res = ldbs.sam.search(expression="(samaccountname=dns)",
1825 scope=SCOPE_SUBTREE, attrs=["dn"],
1826 controls=["search_options:1:2"])
1828 message(SIMPLE, "You still have the old DNS object for managing "
1829 "dynamic DNS, but you didn't supply --full so "
1830 "a correct update can't be done")
1831 ldbs.groupedRollback()
1832 new_ldbs.groupedRollback()
1833 shutil.rmtree(provisiondir)
1836 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1838 res = ldbs.sam.search(expression="(samaccountname=dns)",
1839 scope=SCOPE_SUBTREE, attrs=["dn"],
1840 controls=["search_options:1:2"])
1843 ldbs.sam.delete(res[0]["dn"])
1844 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1845 scope=SCOPE_SUBTREE, attrs=["dn"])
1846 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1847 message(SIMPLE, "IMPORTANT!!! "
1848 "If you were using Dynamic DNS before you need "
1849 "to update your configuration, so that the "
1850 "tkey-gssapi-credential has the following value: "
1851 "DNS/%s.%s" % (names.netbiosname.lower(),
1852 names.realm.lower()))
1854 message(SIMPLE, "Update machine account")
1855 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1857 dnToRecalculate.sort(dn_sort)
1858 # 16) SD should be created with admin but as some previous acl were so wrong
1859 # that admin can't modify them we have first to recreate them with the good
1860 # form but with system account and then give the ownership to admin ...
1861 if str(oem) != "" and not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1862 message(SIMPLE, "Fixing very old provision SD")
1863 rebuild_sd(ldbs.sam, names)
1865 # We calculate the max USN before recalculating the SD because we might
1866 # touch object that have been modified after a provision and we do not
1867 # want that the next upgradeprovision thinks that it has a green light
1871 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1873 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1874 # defSDmodified is set.
1875 if defSDmodified or len(dnToRecalculate) >0:
1876 message(SIMPLE, "Some defaultSecurityDescriptors and/or"
1877 "securityDescriptor have changed, recalculating SD ")
1878 ldbs.sam.set_session_info(adm_session)
1879 rebuild_sd(ldbs.sam, names)
1882 # Now we are quite confident in the recalculate process of the SD, we make
1883 # it optional. And we don't do it if there is DN that we must touch
1884 # as we are assured that on this DNs we will have differences !
1885 # Also the check must be done in a clever way as for the moment we just
1887 if len(dnNotToRecalculate) == 0 and (opts.debugchangesd or opts.debugall):
1888 message(CHANGESD, "Checking recalculated SDs")
1889 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1892 updateOEMInfo(ldbs.sam, str(names.rootdn))
1894 check_for_DNS(newpaths.private_dir, paths.private_dir)
1896 if lastProvisionUSNs is not None:
1897 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1898 if opts.full and (names.policyid is None or names.policyid_dc is None):
1899 update_policyids(names, ldbs.sam)
1901 if opts.full or opts.resetfileacl or opts.fixntacl:
1903 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1904 except ProvisioningError, e:
1905 message(ERROR, "The policy for domain controller is missing. "
1906 "You should restart upgradeprovision with --full")
1908 message(ERROR, "Setting ACL not supported on your filesystem")
1911 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1912 except ProvisioningError, e:
1913 message(ERROR, "The policy for domain controller is missing. "
1914 "You should restart upgradeprovision with --full")
1915 if not opts.fixntacl:
1916 ldbs.groupedCommit()
1917 new_ldbs.groupedCommit()
1918 message(SIMPLE, "Upgrade finished!")
1919 # remove reference provision now that everything is done !
1920 # So we have reindexed first if need when the merged schema was reloaded
1921 # (as new attributes could have quick in)
1922 # But the second part of the update (when we update existing objects
1923 # can also have an influence on indexing as some attribute might have their
1924 # searchflag modificated
1925 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1926 "after modification")
1927 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1928 message(SIMPLE, "Reindexing finished")
1930 shutil.rmtree(provisiondir)
1932 ldbs.groupedRollback()
1933 message(SIMPLE, "ACLs fixed !")
1934 except StandardError, err:
1935 message(ERROR, "A problem occurred while trying to upgrade your "
1936 "provision. A full backup is located at %s" % backupdir)
1937 if opts.debugall or opts.debugchange:
1938 (typ, val, tb) = sys.exc_info()
1939 traceback.print_exception(typ, val, tb)