s4-upgradeprovision: deltaattr can be empty or none too
[Samba/gebeck_regimport.git] / source4 / scripting / bin / upgradeprovision
blob9bf09bd432ee750dc7375b985ca3fb48491d75d8
1 #!/usr/bin/env python
2 # vim: expandtab
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/>.
25 import logging
26 import optparse
27 import os
28 import shutil
29 import sys
30 import tempfile
31 import re
32 import traceback
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
36 import ldb
37 import samba
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.provision import (get_domain_descriptor, find_provision_key_parameters,
48 get_config_descriptor,
49 ProvisioningError, get_last_provision_usn,
50 get_max_usn, update_provision_usn, setup_path)
51 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import security, drsblobs, xattr
53 from samba.ndr import ndr_unpack
54 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
55 get_ldbs,
56 usn_in_range, identic_rename, get_diff_sddls,
57 update_secrets, CHANGE, ERROR, SIMPLE,
58 CHANGEALL, GUESS, CHANGESD, PROVISION,
59 updateOEMInfo, getOEMInfo, update_gpo,
60 delta_update_basesamdb, update_policyids,
61 update_machine_account_password,
62 search_constructed_attrs_stored,
63 int64range2str, update_dns_account_password,
64 increment_calculated_keyversion_number)
66 replace=2**FLAG_MOD_REPLACE
67 add=2**FLAG_MOD_ADD
68 delete=2**FLAG_MOD_DELETE
69 never=0
72 # Will be modified during provision to tell if default sd has been modified
73 # somehow ...
75 #Errors are always logged
77 __docformat__ = "restructuredText"
79 # Attributes that are never copied from the reference provision (even if they
80 # do not exist in the destination object).
81 # This is most probably because they are populated automatcally when object is
82 # created
83 # This also apply to imported object from reference provision
84 replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
85 "parentGUID", "objectCategory", "distinguishedName",
86 "nTMixedDomain", "showInAdvancedViewOnly",
87 "instanceType", "msDS-Behavior-Version", "cn",
88 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
89 "unicodePwd", "dBCSPwd", "supplementalCredentials",
90 "gPCUserExtensionNames", "gPCMachineExtensionNames",
91 "maxPwdAge", "secret", "possibleInferiors", "privilege",
92 "sAMAccountType", "oEMInformation", "creationTime" ]
94 nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
95 "nextRid" ,"rIDNextRID"]
97 nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
100 attrNotCopied = replAttrNotCopied
101 attrNotCopied.extend(nonreplAttrNotCopied)
102 attrNotCopied.extend(nonDSDBAttrNotCopied)
103 # Usually for an object that already exists we do not overwrite attributes as
104 # they might have been changed for good reasons. Anyway for a few of them it's
105 # mandatory to replace them otherwise the provision will be broken somehow.
106 # But for attribute that are just missing we do not have to specify them as the default
107 # behavior is to add missing attribute
108 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,
109 "systemOnly":replace, "searchFlags":replace,
110 "mayContain":replace, "systemFlags":replace+add,
111 "description":replace, "operatingSystemVersion":replace,
112 "adminPropertyPages":replace, "groupType":replace,
113 "wellKnownObjects":replace, "privilege":never,
114 "defaultSecurityDescriptor": replace,
115 "rIDAvailablePool": never,
116 "rIDNextRID": add, "rIDUsedPool": never,
117 "defaultSecurityDescriptor": replace + add,
118 "isMemberOfPartialAttributeSet": delete,
119 "attributeDisplayNames": replace + add,
120 "versionNumber": add}
122 dnNotToRecalculate = []
123 dnToRecalculate = []
124 backlinked = []
125 forwardlinked = set()
126 dn_syntax_att = []
127 not_replicated = []
128 def define_what_to_log(opts):
129 what = 0
130 if opts.debugchange:
131 what = what | CHANGE
132 if opts.debugchangesd:
133 what = what | CHANGESD
134 if opts.debugguess:
135 what = what | GUESS
136 if opts.debugprovision:
137 what = what | PROVISION
138 if opts.debugall:
139 what = what | CHANGEALL
140 return what
143 parser = optparse.OptionParser("provision [options]")
144 sambaopts = options.SambaOptions(parser)
145 parser.add_option_group(sambaopts)
146 parser.add_option_group(options.VersionOptions(parser))
147 credopts = options.CredentialsOptions(parser)
148 parser.add_option_group(credopts)
149 parser.add_option("--setupdir", type="string", metavar="DIR",
150 help="directory with setup files")
151 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
152 parser.add_option("--debugguess", action="store_true",
153 help="Print information on which values are guessed")
154 parser.add_option("--debugchange", action="store_true",
155 help="Print information on what is different but won't be changed")
156 parser.add_option("--debugchangesd", action="store_true",
157 help="Print security descriptor differences")
158 parser.add_option("--debugall", action="store_true",
159 help="Print all available information (very verbose)")
160 parser.add_option("--resetfileacl", action="store_true",
161 help="Force a reset on filesystem acls in sysvol / netlogon share")
162 parser.add_option("--fixntacl", action="store_true",
163 help="Only fix NT ACLs in sysvol / netlogon share")
164 parser.add_option("--full", action="store_true",
165 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
167 opts = parser.parse_args()[0]
169 handler = logging.StreamHandler(sys.stdout)
170 upgrade_logger = logging.getLogger("upgradeprovision")
171 upgrade_logger.setLevel(logging.INFO)
173 upgrade_logger.addHandler(handler)
175 provision_logger = logging.getLogger("provision")
176 provision_logger.addHandler(handler)
178 whatToLog = define_what_to_log(opts)
180 def message(what, text):
181 """Print a message if this message type has been selected to be printed
183 :param what: Category of the message
184 :param text: Message to print """
185 if (whatToLog & what) or what <= 0:
186 upgrade_logger.info("%s", text)
188 if len(sys.argv) == 1:
189 opts.interactive = True
190 lp = sambaopts.get_loadparm()
191 smbconf = lp.configfile
193 creds = credopts.get_credentials(lp)
194 creds.set_kerberos_state(DONT_USE_KERBEROS)
198 def check_for_DNS(refprivate, private):
199 """Check if the provision has already the requirement for dynamic dns
201 :param refprivate: The path to the private directory of the reference
202 provision
203 :param private: The path to the private directory of the upgraded
204 provision"""
206 spnfile = "%s/spn_update_list" % private
207 dnsfile = "%s/dns_update_list" % private
208 namedfile = lp.get("dnsupdate:path")
210 if not namedfile:
211 namedfile = "%s/named.conf.update" % private
213 if not os.path.exists(spnfile):
214 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
216 if not os.path.exists(dnsfile):
217 shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
219 destdir = "%s/new_dns" % private
220 dnsdir = "%s/dns" % private
222 if not os.path.exists(namedfile):
223 if not os.path.exists(destdir):
224 os.mkdir(destdir)
225 if not os.path.exists(dnsdir):
226 os.mkdir(dnsdir)
227 shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
228 shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
229 message(SIMPLE, "It seems that your provision did not integrate "
230 "new rules for dynamic dns update of domain related entries")
231 message(SIMPLE, "A copy of the new bind configuration files and "
232 "template has been put in %s, you should read them and "
233 "configure dynamic dns updates" % destdir)
236 def populate_links(samdb, schemadn):
237 """Populate an array with all the back linked attributes
239 This attributes that are modified automaticaly when
240 front attibutes are changed
242 :param samdb: A LDB object for sam.ldb file
243 :param schemadn: DN of the schema for the partition"""
244 linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
245 backlinked.extend(linkedAttHash.values())
246 for t in linkedAttHash.keys():
247 forwardlinked.add(t)
249 def isReplicated(att):
250 """ Indicate if the attribute is replicated or not
252 :param att: Name of the attribute to be tested
253 :return: True is the attribute is replicated, False otherwise
256 return (att not in not_replicated)
258 def populateNotReplicated(samdb, schemadn):
259 """Populate an array with all the attributes that are not replicated
261 :param samdb: A LDB object for sam.ldb file
262 :param schemadn: DN of the schema for the partition"""
263 res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
264 str(schemadn)), scope=SCOPE_SUBTREE,
265 attrs=["lDAPDisplayName"])
266 for elem in res:
267 not_replicated.append(elem["lDAPDisplayName"])
269 def populate_dnsyntax(samdb, schemadn):
270 """Populate an array with all the attributes that have DN synthax
271 (oid 2.5.5.1)
273 :param samdb: A LDB object for sam.ldb file
274 :param schemadn: DN of the schema for the partition"""
275 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
276 str(schemadn)), scope=SCOPE_SUBTREE,
277 attrs=["lDAPDisplayName"])
278 for elem in res:
279 dn_syntax_att.append(elem["lDAPDisplayName"])
282 def sanitychecks(samdb, names):
283 """Make some checks before trying to update
285 :param samdb: An LDB object opened on sam.ldb
286 :param names: list of key provision parameters
287 :return: Status of check (1 for Ok, 0 for not Ok) """
288 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
289 scope=SCOPE_SUBTREE, attrs=["dn"],
290 controls=["search_options:1:2"])
291 if len(res) == 0:
292 print "No DC found. Your provision is most probably broken!"
293 return False
294 elif len(res) != 1:
295 print "Found %d domain controllers. For the moment " \
296 "upgradeprovision is not able to handle an upgrade on a " \
297 "domain with more than one DC. Please demote the other " \
298 "DC(s) before upgrading" % len(res)
299 return False
300 else:
301 return True
304 def print_provision_key_parameters(names):
305 """Do a a pretty print of provision parameters
307 :param names: list of key provision parameters """
308 message(GUESS, "rootdn :" + str(names.rootdn))
309 message(GUESS, "configdn :" + str(names.configdn))
310 message(GUESS, "schemadn :" + str(names.schemadn))
311 message(GUESS, "serverdn :" + str(names.serverdn))
312 message(GUESS, "netbiosname :" + names.netbiosname)
313 message(GUESS, "defaultsite :" + names.sitename)
314 message(GUESS, "dnsdomain :" + names.dnsdomain)
315 message(GUESS, "hostname :" + names.hostname)
316 message(GUESS, "domain :" + names.domain)
317 message(GUESS, "realm :" + names.realm)
318 message(GUESS, "invocationid:" + names.invocation)
319 message(GUESS, "policyguid :" + names.policyid)
320 message(GUESS, "policyguiddc:" + str(names.policyid_dc))
321 message(GUESS, "domainsid :" + str(names.domainsid))
322 message(GUESS, "domainguid :" + names.domainguid)
323 message(GUESS, "ntdsguid :" + names.ntdsguid)
324 message(GUESS, "domainlevel :" + str(names.domainlevel))
327 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
328 """Define more complicate update rules for some attributes
330 :param att: The attribute to be updated
331 :param delta: A messageElement object that correspond to the difference
332 between the updated object and the reference one
333 :param new: The reference object
334 :param old: The Updated object
335 :param useReplMetadata: A boolean that indicate if the update process
336 use replPropertyMetaData to decide what has to be updated.
337 :param basedn: The base DN of the provision
338 :param aldb: An ldb object used to build DN
339 :return: True to indicate that the attribute should be kept, False for
340 discarding it"""
342 flag = delta.get(att).flags()
343 # We do most of the special case handle if we do not have the
344 # highest usn as otherwise the replPropertyMetaData will guide us more
345 # correctly
346 if not useReplMetadata:
347 if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
348 ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
349 "CN=Services,CN=Configuration,%s" % basedn)
350 == old[0].dn):
351 return True
352 if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
353 ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
354 == old[0].dn):
355 message(SIMPLE, "We suggest that you change the userAccountControl"
356 " for user Administrator from value %d to %d" %
357 (int(str(old[0][att])), int(str(new[0][att]))))
358 return False
359 if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
360 if (long(str(old[0][att])) == 0):
361 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
362 return True
364 if (att == "member" and flag == FLAG_MOD_REPLACE):
365 hash = {}
366 newval = []
367 changeDelta=0
368 for elem in old[0][att]:
369 hash[str(elem).lower()]=1
370 newval.append(str(elem))
372 for elem in new[0][att]:
373 if not hash.has_key(str(elem).lower()):
374 changeDelta=1
375 newval.append(str(elem))
376 if changeDelta == 1:
377 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
378 else:
379 delta.remove(att)
380 return True
382 if (att in ("gPLink", "gPCFileSysPath") and
383 flag == FLAG_MOD_REPLACE and
384 str(new[0].dn).lower() == str(old[0].dn).lower()):
385 delta.remove(att)
386 return True
388 if att == "forceLogoff":
389 ref=0x8000000000000000
390 oldval=int(old[0][att][0])
391 newval=int(new[0][att][0])
392 ref == old and ref == abs(new)
393 return True
395 if att in ("adminDisplayName", "adminDescription"):
396 return True
398 if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
399 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
400 return True
402 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
403 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
404 return True
406 if (str(old[0].dn) == "%s" % (str(names.rootdn))
407 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
408 return True
409 #Allow to change revision of ForestUpdates objects
410 if (att == "revision" or att == "objectVersion"):
411 if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
412 return True
413 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
414 return True
416 # This is a bit of special animal as we might have added
417 # already SPN entries to the list that has to be modified
418 # So we go in detail to try to find out what has to be added ...
419 if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
420 hash = {}
421 newval = []
422 changeDelta = 0
423 for elem in old[0][att]:
424 hash[str(elem)]=1
425 newval.append(str(elem))
427 for elem in new[0][att]:
428 if not hash.has_key(str(elem)):
429 changeDelta = 1
430 newval.append(str(elem))
431 if changeDelta == 1:
432 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
433 else:
434 delta.remove(att)
435 return True
437 return False
439 def dump_denied_change(dn, att, flagtxt, current, reference):
440 """Print detailed information about why a change is denied
442 :param dn: DN of the object which attribute is denied
443 :param att: Attribute that was supposed to be upgraded
444 :param flagtxt: Type of the update that should be performed
445 (add, change, remove, ...)
446 :param current: Value(s) of the current attribute
447 :param reference: Value(s) of the reference attribute"""
449 message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
450 + " must not be changed/removed. Discarding the change")
451 if att == "objectSid" :
452 message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
453 message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
454 elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
455 message(CHANGE, "old : %s" % int64range2str(current[0]))
456 message(CHANGE, "new : %s" % int64range2str(reference[0]))
457 else:
458 i = 0
459 for e in range(0, len(current)):
460 message(CHANGE, "old %d : %s" % (i, str(current[e])))
461 i+=1
462 if reference is not None:
463 i = 0
464 for e in range(0, len(reference)):
465 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
466 i+=1
468 def handle_special_add(samdb, dn, names):
469 """Handle special operation (like remove) on some object needed during
470 upgrade
472 This is mostly due to wrong creation of the object in previous provision.
473 :param samdb: An Ldb object representing the SAM database
474 :param dn: DN of the object to inspect
475 :param names: list of key provision parameters
478 dntoremove = None
479 objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
480 if dn == objDn :
481 #This entry was misplaced lets remove it if it exists
482 dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
484 objDn = Dn(samdb,
485 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
486 if dn == objDn:
487 #This entry was misplaced lets remove it if it exists
488 dntoremove = "CN=Certificate Service DCOM Access,"\
489 "CN=Users, %s" % names.rootdn
491 objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
492 if dn == objDn:
493 #This entry was misplaced lets remove it if it exists
494 dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
496 objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
497 if dn == objDn:
498 #This entry was misplaced lets remove it if it exists
499 dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
501 objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
502 "CN=Configuration,%s" % names.rootdn)
503 if dn == objDn:
504 oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
505 "CN=WellKnown Security Principals,"
506 "CN=Configuration,%s" % names.rootdn)
508 res = samdb.search(expression="(dn=%s)" % oldDn,
509 base=str(names.rootdn),
510 scope=SCOPE_SUBTREE, attrs=["dn"],
511 controls=["search_options:1:2"])
513 res2 = samdb.search(expression="(dn=%s)" % dn,
514 base=str(names.rootdn),
515 scope=SCOPE_SUBTREE, attrs=["dn"],
516 controls=["search_options:1:2"])
518 if len(res) > 0 and len(res2) == 0:
519 message(CHANGE, "Existing object %s must be replaced by %s. "
520 "Renaming old object" % (str(oldDn), str(dn)))
521 samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
523 return 0
525 if dntoremove is not None:
526 res = samdb.search(expression="(cn=RID Set)",
527 base=str(names.rootdn),
528 scope=SCOPE_SUBTREE, attrs=["dn"],
529 controls=["search_options:1:2"])
531 if len(res) == 0:
532 return 2
533 res = samdb.search(expression="(dn=%s)" % dntoremove,
534 base=str(names.rootdn),
535 scope=SCOPE_SUBTREE, attrs=["dn"],
536 controls=["search_options:1:2"])
537 if len(res) > 0:
538 message(CHANGE, "Existing object %s must be replaced by %s. "
539 "Removing old object" % (dntoremove, str(dn)))
540 samdb.delete(res[0]["dn"])
541 return 0
543 return 1
546 def check_dn_nottobecreated(hash, index, listdn):
547 """Check if one of the DN present in the list has a creation order
548 greater than the current.
550 Hash is indexed by dn to be created, with each key
551 is associated the creation order.
553 First dn to be created has the creation order 0, second has 1, ...
554 Index contain the current creation order
556 :param hash: Hash holding the different DN of the object to be
557 created as key
558 :param index: Current creation order
559 :param listdn: List of DNs on which the current DN depends on
560 :return: None if the current object do not depend on other
561 object or if all object have been created before."""
562 if listdn is None:
563 return None
564 for dn in listdn:
565 key = str(dn).lower()
566 if hash.has_key(key) and hash[key] > index:
567 return str(dn)
568 return None
572 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
573 """Add a new object if the dependencies are satisfied
575 The function add the object if the object on which it depends are already
576 created
578 :param ref_samdb: Ldb object representing the SAM db of the reference
579 provision
580 :param samdb: Ldb object representing the SAM db of the upgraded
581 provision
582 :param dn: DN of the object to be added
583 :param names: List of key provision parameters
584 :param basedn: DN of the partition to be updated
585 :param hash: Hash holding the different DN of the object to be
586 created as key
587 :param index: Current creation order
588 :return: True if the object was created False otherwise"""
590 ret = handle_special_add(samdb, dn, names)
592 if ret == 2:
593 return False
595 if ret == 0:
596 return True
599 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
600 scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
601 empty = Message()
602 delta = samdb.msg_diff(empty, reference[0])
603 delta.dn
604 skip = False
605 try:
606 if str(reference[0].get("cn")) == "RID Set":
607 for klass in reference[0].get("objectClass"):
608 if str(klass).lower() == "ridset":
609 skip = True
610 finally:
611 if delta.get("objectSid"):
612 sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
613 m = re.match(r".*-(\d+)$", sid)
614 if m and int(m.group(1))>999:
615 delta.remove("objectSid")
616 for att in attrNotCopied:
617 delta.remove(att)
618 for att in backlinked:
619 delta.remove(att)
620 depend_on_yettobecreated = None
621 for att in dn_syntax_att:
622 depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
623 delta.get(str(att)))
624 if depend_on_yet_tobecreated is not None:
625 message(CHANGE, "Object %s depends on %s in attribute %s. "
626 "Delaying the creation" % (dn,
627 depend_on_yet_tobecreated, att))
628 return False
630 delta.dn = dn
631 if not skip:
632 message(CHANGE,"Object %s will be added" % dn)
633 samdb.add(delta, ["relax:0", "provision:0"])
634 else:
635 message(CHANGE,"Object %s was skipped" % dn)
637 return True
639 def gen_dn_index_hash(listMissing):
640 """Generate a hash associating the DN to its creation order
642 :param listMissing: List of DN
643 :return: Hash with DN as keys and creation order as values"""
644 hash = {}
645 for i in range(0, len(listMissing)):
646 hash[str(listMissing[i]).lower()] = i
647 return hash
649 def add_deletedobj_containers(ref_samdb, samdb, names):
650 """Add the object containter: CN=Deleted Objects
652 This function create the container for each partition that need one and
653 then reference the object into the root of the partition
655 :param ref_samdb: Ldb object representing the SAM db of the reference
656 provision
657 :param samdb: Ldb object representing the SAM db of the upgraded provision
658 :param names: List of key provision parameters"""
661 wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
662 partitions = [str(names.rootdn), str(names.configdn)]
663 for part in partitions:
664 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
665 base=part, scope=SCOPE_SUBTREE,
666 attrs=["dn"],
667 controls=["show_deleted:0",
668 "show_recycled:0"])
669 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
670 base=part, scope=SCOPE_SUBTREE,
671 attrs=["dn"],
672 controls=["show_deleted:0",
673 "show_recycled:0"])
674 if len(ref_delObjCnt) > len(delObjCnt):
675 reference = ref_samdb.search(expression="cn=Deleted Objects",
676 base=part, scope=SCOPE_SUBTREE,
677 controls=["show_deleted:0",
678 "show_recycled:0"])
679 empty = Message()
680 delta = samdb.msg_diff(empty, reference[0])
682 delta.dn = Dn(samdb, str(reference[0]["dn"]))
683 for att in attrNotCopied:
684 delta.remove(att)
686 modcontrols = ["relax:0", "provision:0"]
687 samdb.add(delta, modcontrols)
689 listwko = []
690 res = samdb.search(expression="(objectClass=*)", base=part,
691 scope=SCOPE_BASE,
692 attrs=["dn", "wellKnownObjects"])
694 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
695 found = False
697 if len(res[0]) > 0:
698 wko = res[0]["wellKnownObjects"]
700 # The wellKnownObject that we want to add.
701 for o in wko:
702 if str(o) == targetWKO:
703 found = True
704 listwko.append(str(o))
706 if not found:
707 listwko.append(targetWKO)
709 delta = Message()
710 delta.dn = Dn(samdb, str(res[0]["dn"]))
711 delta["wellKnownObjects"] = MessageElement(listwko,
712 FLAG_MOD_REPLACE,
713 "wellKnownObjects" )
714 samdb.modify(delta)
716 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
717 """Add the missing object whose DN is the list
719 The function add the object if the objects on which it depends are
720 already created.
722 :param ref_samdb: Ldb object representing the SAM db of the reference
723 provision
724 :param samdb: Ldb object representing the SAM db of the upgraded
725 provision
726 :param dn: DN of the object to be added
727 :param names: List of key provision parameters
728 :param basedn: DN of the partition to be updated
729 :param list: List of DN to be added in the upgraded provision"""
731 listMissing = []
732 listDefered = list
734 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
735 index = 0
736 listMissing = listDefered
737 listDefered = []
738 hashMissing = gen_dn_index_hash(listMissing)
739 for dn in listMissing:
740 ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
741 hashMissing, index)
742 index = index + 1
743 if ret == 0:
744 # DN can't be created because it depends on some
745 # other DN in the list
746 listDefered.append(dn)
748 if len(listDefered) != 0:
749 raise ProvisioningError("Unable to insert missing elements: "
750 "circular references")
752 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
753 """This function handle updates on links
755 :param samdb: An LDB object pointing to the updated provision
756 :param att: Attribute to update
757 :param basedn: The root DN of the provision
758 :param dn: The DN of the inspected object
759 :param value: The value of the attribute
760 :param ref_value: The value of this attribute in the reference provision
761 :param delta: The MessageElement object that will be applied for
762 transforming the current provision"""
764 res = samdb.search(expression="dn=%s" % dn, base=basedn,
765 controls=["search_options:1:2", "reveal:1"],
766 attrs=[att])
768 blacklist = {}
769 hash = {}
770 newlinklist = []
771 changed = False
773 newlinklist.extend(value)
775 for e in value:
776 hash[e] = 1
777 # for w2k domain level the reveal won't reveal anything ...
778 # it means that we can readd links that were removed on purpose ...
779 # Also this function in fact just accept add not removal
781 for e in res[0][att]:
782 if not hash.has_key(e):
783 # We put in the blacklist all the element that are in the "revealed"
784 # result and not in the "standard" result
785 # This element are links that were removed before and so that
786 # we don't wan't to readd
787 blacklist[e] = 1
789 for e in ref_value:
790 if not blacklist.has_key(e) and not hash.has_key(e):
791 newlinklist.append(str(e))
792 changed = True
793 if changed:
794 delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
795 else:
796 delta.remove(att)
799 msg_elt_flag_strs = {
800 ldb.FLAG_MOD_ADD: "MOD_ADD",
801 ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
802 ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
804 def checkKeepAttributeOldMtd(delta, att, reference, current,
805 basedn, samdb):
806 """ Check if we should keep the attribute modification or not.
807 This function didn't use replicationMetadata to take a decision.
809 :param delta: A message diff object
810 :param att: An attribute
811 :param reference: A message object for the current entry comming from
812 the reference provision.
813 :param current: A message object for the current entry commin from
814 the current provision.
815 :param basedn: The DN of the partition
816 :param samdb: A ldb connection to the sam database of the current provision.
818 :return: The modified message diff.
820 # Old school way of handling things for pre alpha12 upgrade
821 global defSDmodified
822 isFirst = False
823 txt = ""
824 dn = current[0].dn
826 for att in list(delta):
827 msgElt = delta.get(att)
829 if att == "nTSecurityDescriptor":
830 defSDmodified = True
831 delta.remove(att)
832 continue
834 if att == "dn":
835 continue
837 if not hashOverwrittenAtt.has_key(att):
838 if msgElt.flags() != FLAG_MOD_ADD:
839 if not handle_special_case(att, delta, reference, current,
840 False, basedn, samdb):
841 if opts.debugchange or opts.debugall:
842 try:
843 dump_denied_change(dn, att,
844 msg_elt_flag_strs[msgElt.flags()],
845 current[0][att], reference[0][att])
846 except KeyError:
847 dump_denied_change(dn, att,
848 msg_elt_flag_strs[msgElt.flags()],
849 current[0][att], None)
850 delta.remove(att)
851 continue
852 else:
853 if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
854 continue
855 elif hashOverwrittenAtt.get(att)==never:
856 delta.remove(att)
857 continue
859 return delta
861 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
862 hash_attr_usn, basedn, usns, samdb):
863 """ Check if we should keep the attribute modification or not
865 :param delta: A message diff object
866 :param att: An attribute
867 :param message: A function to print messages
868 :param reference: A message object for the current entry comming from
869 the reference provision.
870 :param current: A message object for the current entry commin from
871 the current provision.
872 :param hash_attr_usn: A dictionnary with attribute name as keys,
873 USN and invocation id as values.
874 :param basedn: The DN of the partition
875 :param usns: A dictionnary with invocation ID as keys and USN ranges
876 as values.
877 :param samdb: A ldb object pointing to the sam DB
879 :return: The modified message diff.
881 global defSDmodified
882 isFirst = True
883 txt = ""
884 dn = current[0].dn
886 for att in list(delta):
887 if att in ["dn", "objectSid"]:
888 delta.remove(att)
889 continue
891 # We have updated by provision usn information so let's exploit
892 # replMetadataProperties
893 if att in forwardlinked:
894 curval = current[0].get(att, ())
895 refval = reference[0].get(att, ())
896 handle_links(samdb, att, basedn, current[0]["dn"],
897 curval, refval, delta)
898 continue
900 if isFirst and len(delta.items())>1:
901 isFirst = False
902 txt = "%s\n" % (str(dn))
904 if handle_special_case(att, delta, reference, current, True, None, None):
905 # This attribute is "complicated" to handle and handling
906 # was done in handle_special_case
907 continue
909 attrUSN = None
910 if hash_attr_usn.get(att):
911 [attrUSN, attInvId] = hash_attr_usn.get(att)
913 if attrUSN is None:
914 # If it's a replicated attribute and we don't have any USN
915 # information about it. It means that we never saw it before
916 # so let's add it !
917 # If it is a replicated attribute but we are not master on it
918 # (ie. not initially added in the provision we masterize).
919 # attrUSN will be -1
920 if isReplicated(att):
921 continue
922 else:
923 message(CHANGE, "Non replicated attribute %s changed" % att)
924 continue
926 if att == "nTSecurityDescriptor":
927 cursd = ndr_unpack(security.descriptor,
928 str(current[0]["nTSecurityDescriptor"]))
929 cursddl = cursd.as_sddl(names.domainsid)
930 refsd = ndr_unpack(security.descriptor,
931 str(reference[0]["nTSecurityDescriptor"]))
932 refsddl = refsd.as_sddl(names.domainsid)
934 diff = get_diff_sddls(refsddl, cursddl)
935 if diff == "":
936 # FIXME find a way to have it only with huge huge verbose mode
937 # message(CHANGE, "%ssd are identical" % txt)
938 # txt = ""
939 delta.remove(att)
940 continue
941 else:
942 delta.remove(att)
943 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
944 txt = ""
945 if attrUSN == -1:
946 message(CHANGESD, "But the SD has been changed by someonelse "\
947 "so it's impossible to know if the difference"\
948 " cames from the modification or from a previous bug")
949 dnNotToRecalculate.append(str(dn))
950 else:
951 dnToRecalculate.append(str(dn))
952 continue
954 if attrUSN == -1:
955 # This attribute was last modified by another DC forget
956 # about it
957 message(CHANGE, "%sAttribute: %s has been "
958 "created/modified/deleted by another DC. "
959 "Doing nothing" % (txt, att))
960 txt = ""
961 delta.remove(att)
962 continue
963 elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
964 message(CHANGE, "%sAttribute: %s was not "
965 "created/modified/deleted during a "
966 "provision or upgradeprovision. Current "
967 "usn: %d. Doing nothing" % (txt, att,
968 attrUSN))
969 txt = ""
970 delta.remove(att)
971 continue
972 else:
973 if att == "defaultSecurityDescriptor":
974 defSDmodified = True
975 if attrUSN:
976 message(CHANGE, "%sAttribute: %s will be modified"
977 "/deleted it was last modified "
978 "during a provision. Current usn: "
979 "%d" % (txt, att, attrUSN))
980 txt = ""
981 else:
982 message(CHANGE, "%sAttribute: %s will be added because "
983 "it did not exist before" % (txt, att))
984 txt = ""
985 continue
987 return delta
989 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
990 """ This function updates the object that are already present in the
991 provision
993 :param ref_samdb: An LDB object pointing to the reference provision
994 :param samdb: An LDB object pointing to the updated provision
995 :param basedn: A string with the value of the base DN for the provision
996 (ie. DC=foo, DC=bar)
997 :param listPresent: A list of object that is present in the provision
998 :param usns: A list of USN range modified by previous provision and
999 upgradeprovision grouped by invocation ID
1002 # This hash is meant to speedup lookup of attribute name from an oid,
1003 # it's for the replPropertyMetaData handling
1004 hash_oid_name = {}
1005 res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
1006 controls=["search_options:1:2"], attrs=["attributeID",
1007 "lDAPDisplayName"])
1008 if len(res) > 0:
1009 for e in res:
1010 strDisplay = str(e.get("lDAPDisplayName"))
1011 hash_oid_name[str(e.get("attributeID"))] = strDisplay
1012 else:
1013 msg = "Unable to insert missing elements: circular references"
1014 raise ProvisioningError(msg)
1016 changed = 0
1017 controls = ["search_options:1:2", "sd_flags:1:0"]
1018 if usns is not None:
1019 message(CHANGE, "Using replPropertyMetadata for change selection")
1020 for dn in listPresent:
1021 reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1022 scope=SCOPE_SUBTREE,
1023 controls=controls)
1024 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1025 scope=SCOPE_SUBTREE, controls=controls)
1027 if (
1028 (str(current[0].dn) != str(reference[0].dn)) and
1029 (str(current[0].dn).upper() == str(reference[0].dn).upper())
1031 message(CHANGE, "Names are the same except for the case. "
1032 "Renaming %s to %s" % (str(current[0].dn),
1033 str(reference[0].dn)))
1034 identic_rename(samdb, reference[0].dn)
1035 current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1036 scope=SCOPE_SUBTREE,
1037 controls=controls)
1039 delta = samdb.msg_diff(current[0], reference[0])
1041 for att in backlinked:
1042 delta.remove(att)
1044 for att in attrNotCopied:
1045 delta.remove(att)
1047 delta.remove("name")
1049 if len(delta.items()) == 1:
1050 continue
1052 if len(delta.items()) > 1 and usns is not None:
1053 # Fetch the replPropertyMetaData
1054 res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1055 scope=SCOPE_SUBTREE, controls=controls,
1056 attrs=["replPropertyMetaData"])
1057 ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1058 str(res[0]["replPropertyMetaData"])).ctr
1060 hash_attr_usn = {}
1061 for o in ctr.array:
1062 # We put in this hash only modification
1063 # made on the current host
1064 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1065 if str(o.originating_invocation_id) in usns.keys():
1066 hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1067 else:
1068 hash_attr_usn[att] = [-1, None]
1070 if usns is not None:
1071 delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1072 current, hash_attr_usn,
1073 basedn, usns, samdb)
1074 else:
1075 delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1077 delta.dn = dn
1078 if len(delta.items()) >1:
1079 # Skip dn as the value is not really changed ...
1080 attributes=", ".join(delta.keys()[1:])
1081 modcontrols = []
1082 relaxedatt = ['iscriticalsystemobject', 'grouptype']
1083 # Let's try to reduce as much as possible the use of relax control
1084 for attr in delta.keys():
1085 if attr.lower() in relaxedatt:
1086 modcontrols = ["relax:0", "provision:0"]
1087 message(CHANGE, "%s is different from the reference one, changed"
1088 " attributes: %s\n" % (dn, attributes))
1089 changed += 1
1090 samdb.modify(delta, modcontrols)
1091 return changed
1093 def reload_full_schema(samdb, names):
1094 """Load the updated schema with all the new and existing classes
1095 and attributes.
1097 :param samdb: An LDB object connected to the sam.ldb of the update
1098 provision
1099 :param names: List of key provision parameters
1102 current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1103 scope=SCOPE_SUBTREE)
1104 schema_ldif = ""
1105 prefixmap_data = ""
1107 for ent in current:
1108 schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1110 prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1111 prefixmap_data = b64encode(prefixmap_data)
1113 # We don't actually add this ldif, just parse it
1114 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1116 dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1119 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1120 """Check differences between the reference provision and the upgraded one.
1122 It looks for all objects which base DN is name.
1124 This function will also add the missing object and update existing object
1125 to add or remove attributes that were missing.
1127 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1128 reference provision
1129 :param samdb: An LDB object connected to the sam.ldb of the update
1130 provision
1131 :param basedn: String value of the DN of the partition
1132 :param names: List of key provision parameters
1133 :param schema: A Schema object
1134 :param provisionUSNs: A dictionnary with range of USN modified during provision
1135 or upgradeprovision. Ranges are grouped by invocationID.
1136 :param prereloadfunc: A function that must be executed just before the reload
1137 of the schema
1140 hash_new = {}
1141 hash = {}
1142 listMissing = []
1143 listPresent = []
1144 reference = []
1145 current = []
1147 # Connect to the reference provision and get all the attribute in the
1148 # partition referred by name
1149 reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1150 scope=SCOPE_SUBTREE, attrs=["dn"],
1151 controls=["search_options:1:2"])
1153 current = samdb.search(expression="objectClass=*", base=basedn,
1154 scope=SCOPE_SUBTREE, attrs=["dn"],
1155 controls=["search_options:1:2"])
1156 # Create a hash for speeding the search of new object
1157 for i in range(0, len(reference)):
1158 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1160 # Create a hash for speeding the search of existing object in the
1161 # current provision
1162 for i in range(0, len(current)):
1163 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1166 for k in hash_new.keys():
1167 if not hash.has_key(k):
1168 if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1169 listMissing.append(hash_new[k])
1170 else:
1171 listPresent.append(hash_new[k])
1173 # Sort the missing object in order to have object of the lowest level
1174 # first (which can be containers for higher level objects)
1175 listMissing.sort(dn_sort)
1176 listPresent.sort(dn_sort)
1178 # The following lines is to load the up to
1179 # date schema into our current LDB
1180 # a complete schema is needed as the insertion of attributes
1181 # and class is done against it
1182 # and the schema is self validated
1183 samdb.set_schema(schema)
1184 try:
1185 message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1186 add_deletedobj_containers(ref_samdb, samdb, names)
1188 add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1190 prereloadfunc()
1191 message(SIMPLE, "Reloading a merged schema, which might trigger "
1192 "reindexing so please be patient")
1193 reload_full_schema(samdb, names)
1194 message(SIMPLE, "Schema reloaded!")
1196 changed = update_present(ref_samdb, samdb, basedn, listPresent,
1197 provisionUSNs)
1198 message(SIMPLE, "There are %d changed objects" % (changed))
1199 return 1
1201 except StandardError, err:
1202 message(ERROR, "Exception during upgrade of samdb:")
1203 (typ, val, tb) = sys.exc_info()
1204 traceback.print_exception(typ, val, tb)
1205 return 0
1208 def check_updated_sd(ref_sam, cur_sam, names):
1209 """Check if the security descriptor in the upgraded provision are the same
1210 as the reference
1212 :param ref_sam: A LDB object connected to the sam.ldb file used as
1213 the reference provision
1214 :param cur_sam: A LDB object connected to the sam.ldb file used as
1215 upgraded provision
1216 :param names: List of key provision parameters"""
1217 reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1218 scope=SCOPE_SUBTREE,
1219 attrs=["dn", "nTSecurityDescriptor"],
1220 controls=["search_options:1:2"])
1221 current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1222 scope=SCOPE_SUBTREE,
1223 attrs=["dn", "nTSecurityDescriptor"],
1224 controls=["search_options:1:2"])
1225 hash = {}
1226 for i in range(0, len(reference)):
1227 refsd = ndr_unpack(security.descriptor,
1228 str(reference[i]["nTSecurityDescriptor"]))
1229 hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1232 for i in range(0, len(current)):
1233 key = str(current[i]["dn"]).lower()
1234 if hash.has_key(key):
1235 cursd = ndr_unpack(security.descriptor,
1236 str(current[i]["nTSecurityDescriptor"]))
1237 sddl = cursd.as_sddl(names.domainsid)
1238 if sddl != hash[key]:
1239 txt = get_diff_sddls(hash[key], sddl, False)
1240 if txt != "":
1241 message(CHANGESD, "On object %s ACL is different"
1242 " \n%s" % (current[i]["dn"], txt))
1246 def fix_partition_sd(samdb, names):
1247 """This function fix the SD for partition containers (basedn, configdn, ...)
1248 This is needed because some provision use to have broken SD on containers
1250 :param samdb: An LDB object pointing to the sam of the current provision
1251 :param names: A list of key provision parameters
1253 alwaysRecalculate = False
1254 if len(dnToRecalculate) == 0 and len(dnNotToRecalculate) == 0:
1255 alwaysRecalculate = True
1258 # NC's DN can't be both in dnToRecalculate and dnNotToRecalculate
1259 # First update the SD for the rootdn
1260 if alwaysRecalculate or str(names.rootdn) in dnToRecalculate:
1261 delta = Message()
1262 delta.dn = Dn(samdb, str(names.rootdn))
1263 descr = get_domain_descriptor(names.domainsid)
1264 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1265 "nTSecurityDescriptor")
1266 samdb.modify(delta)
1268 # Then the config dn
1269 if alwaysRecalculate or str(names.configdn) in dnToRecalculate:
1270 delta = Message()
1271 delta.dn = Dn(samdb, str(names.configdn))
1272 descr = get_config_descriptor(names.domainsid)
1273 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1274 "nTSecurityDescriptor" )
1275 samdb.modify(delta)
1277 # Then the schema dn
1278 if alwaysRecalculate or str(names.schemadn) in dnToRecalculate:
1279 delta = Message()
1280 delta.dn = Dn(samdb, str(names.schemadn))
1281 descr = get_schema_descriptor(names.domainsid)
1282 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1283 "nTSecurityDescriptor" )
1284 samdb.modify(delta)
1286 def rebuild_sd(samdb, names):
1287 """Rebuild security descriptor of the current provision from scratch
1289 During the different pre release of samba4 security descriptors (SD)
1290 were notarly broken (up to alpha11 included)
1291 This function allow to get them back in order, this function make the
1292 assumption that nobody has modified manualy an SD
1293 and so SD can be safely recalculated from scratch to get them right.
1295 :param names: List of key provision parameters"""
1297 fix_partition_sd(samdb, names)
1299 # List of namming contexts
1300 listNC = [str(names.rootdn), str(names.configdn), str(names.schemadn)]
1301 hash = {}
1302 if len(dnToRecalculate) == 0:
1303 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1304 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1305 controls=["search_options:1:2"])
1306 for obj in res:
1307 hash[str(obj["dn"])] = obj["whenCreated"]
1308 else:
1309 for dn in dnToRecalculate:
1310 if hash.has_key(dn):
1311 continue
1312 # fetch each dn to recalculate and their child within the same partition
1313 res = samdb.search(expression="objectClass=*", base=dn,
1314 scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"])
1315 for obj in res:
1316 hash[str(obj["dn"])] = obj["whenCreated"]
1318 listKeys = list(set(hash.keys()))
1319 listKeys.sort(dn_sort)
1321 if len(dnToRecalculate) != 0:
1322 message(CHANGESD, "%d DNs have been marked as needed to be recalculated"\
1323 ", recalculating %d due to inheritance"
1324 % (len(dnToRecalculate), len(listKeys)))
1326 for key in listKeys:
1327 if (key in listNC or
1328 key in dnNotToRecalculate):
1329 continue
1330 delta = Message()
1331 delta.dn = Dn(samdb, key)
1332 try:
1333 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1334 "whenCreated" )
1335 samdb.modify(delta, ["recalculate_sd:0","relax:0"])
1336 except LdbError, e:
1337 samdb.transaction_cancel()
1338 res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1339 scope=SCOPE_SUBTREE,
1340 attrs=["dn", "nTSecurityDescriptor"],
1341 controls=["search_options:1:2"])
1342 badsd = ndr_unpack(security.descriptor,
1343 str(res[0]["nTSecurityDescriptor"]))
1344 message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1345 return
1347 def removeProvisionUSN(samdb):
1348 attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1349 entry = samdb.search(expression="dn=@PROVISION", base = "",
1350 scope=SCOPE_SUBTREE,
1351 attrs=attrs)
1352 empty = Message()
1353 empty.dn = entry[0].dn
1354 delta = samdb.msg_diff(entry[0], empty)
1355 delta.remove("dn")
1356 delta.dn = entry[0].dn
1357 samdb.modify(delta)
1359 def remove_stored_generated_attrs(paths, creds, session, lp):
1360 """Remove previously stored constructed attributes
1362 :param paths: List of paths for different provision objects
1363 from the upgraded provision
1364 :param creds: A credential object
1365 :param session: A session object
1366 :param lp: A line parser object
1367 :return: An associative array whose key are the different constructed
1368 attributes and the value the dn where this attributes were found.
1372 def simple_update_basesamdb(newpaths, paths, names):
1373 """Update the provision container db: sam.ldb
1374 This function is aimed at very old provision (before alpha9)
1376 :param newpaths: List of paths for different provision objects
1377 from the reference provision
1378 :param paths: List of paths for different provision objects
1379 from the upgraded provision
1380 :param names: List of key provision parameters"""
1382 message(SIMPLE, "Copy samdb")
1383 shutil.copy(newpaths.samdb, paths.samdb)
1385 message(SIMPLE, "Update partitions filename if needed")
1386 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1387 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1388 usersldb = os.path.join(paths.private_dir, "users.ldb")
1389 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1391 if not os.path.isdir(samldbdir):
1392 os.mkdir(samldbdir)
1393 os.chmod(samldbdir, 0700)
1394 if os.path.isfile(schemaldb):
1395 shutil.copy(schemaldb, os.path.join(samldbdir,
1396 "%s.ldb"%str(names.schemadn).upper()))
1397 os.remove(schemaldb)
1398 if os.path.isfile(usersldb):
1399 shutil.copy(usersldb, os.path.join(samldbdir,
1400 "%s.ldb"%str(names.rootdn).upper()))
1401 os.remove(usersldb)
1402 if os.path.isfile(configldb):
1403 shutil.copy(configldb, os.path.join(samldbdir,
1404 "%s.ldb"%str(names.configdn).upper()))
1405 os.remove(configldb)
1408 def update_privilege(ref_private_path, cur_private_path):
1409 """Update the privilege database
1411 :param ref_private_path: Path to the private directory of the reference
1412 provision.
1413 :param cur_private_path: Path to the private directory of the current
1414 (and to be updated) provision."""
1415 message(SIMPLE, "Copy privilege")
1416 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1417 os.path.join(cur_private_path, "privilege.ldb"))
1420 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1421 """Upgrade the SAM DB contents for all the provision partitions
1423 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1424 provision
1425 :param samdb: An LDB object connected to the sam.ldb of the update
1426 provision
1427 :param names: List of key provision parameters
1428 :param provisionUSNs: A dictionnary with range of USN modified during provision
1429 or upgradeprovision. Ranges are grouped by invocationID.
1430 :param schema: A Schema object that represent the schema of the provision
1431 :param prereloadfunc: A function that must be executed just before the reload
1432 of the schema
1435 message(SIMPLE, "Starting update of samdb")
1436 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1437 schema, provisionUSNs, prereloadfunc)
1438 if ret:
1439 message(SIMPLE, "Update of samdb finished")
1440 return 1
1441 else:
1442 message(SIMPLE, "Update failed")
1443 return 0
1446 def copyxattrs(dir, refdir):
1447 """ Copy owner, groups, extended ACL and NT acls from
1448 a reference dir to a destination dir
1450 Both dir are supposed to hold the same files
1451 :param dir: Destination dir
1452 :param refdir: Reference directory"""
1454 noxattr = 0
1455 for root, dirs, files in os.walk(dir, topdown=True):
1456 for name in files:
1457 subdir=root[len(dir):]
1458 ref = os.path.join("%s%s" % (refdir, subdir), name)
1459 statsinfo = os.stat(ref)
1460 tgt = os.path.join(root, name)
1461 try:
1463 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1464 # Get the xattr attributes if any
1465 try:
1466 attribute = samba.xattr_native.wrap_getxattr(ref,
1467 xattr.XATTR_NTACL_NAME)
1468 samba.xattr_native.wrap_setxattr(tgt,
1469 xattr.XATTR_NTACL_NAME,
1470 attribute)
1471 except:
1472 noxattr = 1
1473 attribute = samba.xattr_native.wrap_getxattr(ref,
1474 "system.posix_acl_access")
1475 samba.xattr_native.wrap_setxattr(tgt,
1476 "system.posix_acl_access",
1477 attribute)
1478 except:
1479 continue
1480 for name in dirs:
1481 subdir=root[len(dir):]
1482 ref = os.path.join("%s%s" % (refdir, subdir), name)
1483 statsinfo = os.stat(ref)
1484 tgt = os.path.join(root, name)
1485 try:
1486 os.chown(os.path.join(root, name), statsinfo.st_uid,
1487 statsinfo.st_gid)
1488 try:
1489 attribute = samba.xattr_native.wrap_getxattr(ref,
1490 xattr.XATTR_NTACL_NAME)
1491 samba.xattr_native.wrap_setxattr(tgt,
1492 xattr.XATTR_NTACL_NAME,
1493 attribute)
1494 except:
1495 noxattr = 1
1496 attribute = samba.xattr_native.wrap_getxattr(ref,
1497 "system.posix_acl_access")
1498 samba.xattr_native.wrap_setxattr(tgt,
1499 "system.posix_acl_access",
1500 attribute)
1502 except:
1503 continue
1506 def backup_provision(paths, dir):
1507 """This function backup the provision files so that a rollback
1508 is possible
1510 :param paths: Paths to different objects
1511 :param dir: Directory where to store the backup
1514 shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1515 copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1516 shutil.copy2(paths.samdb, dir)
1517 shutil.copy2(paths.secrets, dir)
1518 shutil.copy2(paths.idmapdb, dir)
1519 shutil.copy2(paths.privilege, dir)
1520 if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1521 shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1522 shutil.copy2(paths.smbconf, dir)
1523 shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1525 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1526 if not os.path.isdir(samldbdir):
1527 samldbdir = paths.private_dir
1528 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1529 configldb = os.path.join(paths.private_dir, "configuration.ldb")
1530 usersldb = os.path.join(paths.private_dir, "users.ldb")
1531 shutil.copy2(schemaldb, dir)
1532 shutil.copy2(usersldb, dir)
1533 shutil.copy2(configldb, dir)
1534 else:
1535 shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1540 def sync_calculated_attributes(samdb, names):
1541 """Synchronize attributes used for constructed ones, with the
1542 old constructed that were stored in the database.
1544 This apply for instance to msds-keyversionnumber that was
1545 stored and that is now constructed from replpropertymetadata.
1547 :param samdb: An LDB object attached to the currently upgraded samdb
1548 :param names: Various key parameter about current provision.
1550 listAttrs = ["msDs-KeyVersionNumber"]
1551 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1552 if hash.has_key("msDs-KeyVersionNumber"):
1553 increment_calculated_keyversion_number(samdb, names.rootdn,
1554 hash["msDs-KeyVersionNumber"])
1556 # Synopsis for updateprovision
1557 # 1) get path related to provision to be update (called current)
1558 # 2) open current provision ldbs
1559 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1560 # of the DC ....)
1561 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1562 # by either upgradeprovision or provision
1563 # 5) creation of a new provision the latest version of provision script
1564 # (called reference)
1565 # 6) get reference provision paths
1566 # 7) open reference provision ldbs
1567 # 8) setup helpers data that will help the update process
1568 # 9) update the privilege ldb by copying the one of referecence provision to
1569 # the current provision
1570 # 10)get the oemInfo field, this field contains information about the different
1571 # provision that have been done
1572 # 11)Depending on whether oemInfo has the string "alpha9" or alphaxx (x as an
1573 # integer) or none of this the following things are done
1574 # A) When alpha9 or alphaxx is present
1575 # The base sam.ldb file is updated by looking at the difference between
1576 # referrence one and the current one. Everything is copied with the
1577 # exception of lastProvisionUSN attributes.
1578 # B) Other case (it reflect that that provision was done before alpha9)
1579 # The base sam.ldb of the reference provision is copied over
1580 # the current one, if necessary ldb related to partitions are moved
1581 # and renamed
1582 # The highest used USN is fetched so that changed by upgradeprovision
1583 # usn can be tracked
1584 # 12)A Schema object is created, it will be used to provide a complete
1585 # schema to current provision during update (as the schema of the
1586 # current provision might not be complete and so won't allow some
1587 # object to be created)
1588 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1589 # 14)The secrets db is updated by pull all the difference from the reference
1590 # provision into the current provision
1591 # 15)As the previous step has most probably modified the password stored in
1592 # in secret for the current DC, a new password is generated,
1593 # the kvno is bumped and the entry in samdb is also updated
1594 # 16)For current provision older than alpha9, we must fix the SD a little bit
1595 # administrator to update them because SD used to be generated with the
1596 # system account before alpha9.
1597 # 17)The highest usn modified so far is searched in the database it will be
1598 # the upper limit for usn modified during provision.
1599 # This is done before potential SD recalculation because we do not want
1600 # SD modified during recalculation to be marked as modified during provision
1601 # (and so possibly remplaced at next upgradeprovision)
1602 # 18)Rebuilt SD if the flag indicate to do so
1603 # 19)Check difference between SD of reference provision and those of the
1604 # current provision. The check is done by getting the sddl representation
1605 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1606 # Each part is verified separetly, for dacl and sacl ACL is splited into
1607 # ACEs and each ACE is verified separately (so that a permutation in ACE
1608 # didn't raise as an error).
1609 # 20)The oemInfo field is updated to add information about the fact that the
1610 # provision has been updated by the upgradeprovision version xxx
1611 # (the version is the one obtained when starting samba with the --version
1612 # parameter)
1613 # 21)Check if the current provision has all the settings needed for dynamic
1614 # DNS update to work (that is to say the provision is newer than
1615 # january 2010). If not dns configuration file from reference provision
1616 # are copied in a sub folder and the administrator is invited to
1617 # do what is needed.
1618 # 22)If the lastProvisionUSN attribute was present it is updated to add
1619 # the range of usns modified by the current upgradeprovision
1622 # About updating the sam DB
1623 # The update takes place in update_partition function
1624 # This function read both current and reference provision and list all
1625 # the available DN of objects
1626 # If the string representation of a DN in reference provision is
1627 # equal to the string representation of a DN in current provision
1628 # (without taking care of case) then the object is flaged as being
1629 # present. If the object is not present in current provision the object
1630 # is being flaged as missing in current provision. Object present in current
1631 # provision but not in reference provision are ignored.
1632 # Once the list of objects present and missing is done, the deleted object
1633 # containers are created in the differents partitions (if missing)
1635 # Then the function add_missing_entries is called
1636 # This function will go through the list of missing entries by calling
1637 # add_missing_object for the given object. If this function returns 0
1638 # it means that the object needs some other object in order to be created
1639 # The object is reappended at the end of the list to be created later
1640 # (and preferably after all the needed object have been created)
1641 # The function keeps on looping on the list of object to be created until
1642 # it's empty or that the number of defered creation is equal to the number
1643 # of object that still needs to be created.
1645 # The function add_missing_object will first check if the object can be created.
1646 # That is to say that it didn't depends other not yet created objects
1647 # If requisit can't be fullfilled it exists with 0
1648 # Then it will try to create the missing entry by creating doing
1649 # an ldb_message_diff between the object in the reference provision and
1650 # an empty object.
1651 # This resulting object is filtered to remove all the back link attribute
1652 # (ie. memberOf) as they will be created by the other linked object (ie.
1653 # the one with the member attribute)
1654 # All attributes specified in the attrNotCopied array are
1655 # also removed it's most of the time generated attributes
1657 # After missing entries have been added the update_partition function will
1658 # take care of object that exist but that need some update.
1659 # In order to do so the function update_present is called with the list
1660 # of object that are present in both provision and that might need an update.
1662 # This function handle first case mismatch so that the DN in the current
1663 # provision have the same case as in reference provision
1665 # It will then construct an associative array consiting of attributes as
1666 # key and invocationid as value( if the originating invocation id is
1667 # different from the invocation id of the current DC the value is -1 instead).
1669 # If the range of provision modified attributes is present, the function will
1670 # use the replMetadataProperty update method which is the following:
1671 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1672 # creationTime, msDs-KeyVersionNumber, oEMInformation
1673 # Check for each attribute if its usn is within one of the modified by
1674 # provision range and if its originating id is the invocation id of the
1675 # current DC, then validate the update from reference to current.
1676 # If not or if there is no replMetatdataProperty for this attribute then we
1677 # do not update it.
1678 # Otherwise (case the range of provision modified attribute is not present) it
1679 # use the following process:
1680 # All attributes that need to be added are accepted at the exeption of those
1681 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1682 # correct flags specified.
1683 # For attributes that need to be modified or removed, a check is performed
1684 # in OverwrittenAtt, if the attribute is present and the modification flag
1685 # (remove, delete) is one of those listed for this attribute then modification
1686 # is accepted. For complicated handling of attribute update, the control is passed
1687 # to handle_special_case
1691 if __name__ == '__main__':
1692 global defSDmodified
1693 defSDmodified = False
1694 # From here start the big steps of the program
1695 # 1) First get files paths
1696 paths = get_paths(param, smbconf=smbconf)
1697 # Get ldbs with the system session, it is needed for searching
1698 # provision parameters
1699 session = system_session()
1701 # This variable will hold the last provision USN once if it exists.
1702 minUSN = 0
1703 # 2)
1704 ldbs = get_ldbs(paths, creds, session, lp)
1705 backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1706 prefix="backupprovision")
1707 backup_provision(paths, backupdir)
1708 try:
1709 ldbs.startTransactions()
1711 # 3) Guess all the needed names (variables in fact) from the current
1712 # provision.
1713 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1714 paths, smbconf, lp)
1715 # 4)
1716 lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1717 if lastProvisionUSNs is not None:
1718 v = 0
1719 for k in lastProvisionUSNs.keys():
1720 for r in lastProvisionUSNs[k]:
1721 v = v + 1
1723 message(CHANGE,
1724 "Find last provision USN, %d invocation(s) for a total of %d ranges" % \
1725 (len(lastProvisionUSNs.keys()), v /2 ))
1727 if lastProvisionUSNs.get("default") != None:
1728 message(CHANGE, "Old style for usn ranges used")
1729 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1730 del lastProvisionUSNs["default"]
1731 # Objects will be created with the admin session
1732 # (not anymore system session)
1733 adm_session = admin_session(lp, str(names.domainsid))
1734 # So we reget handle on objects
1735 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1736 if not opts.fixntacl:
1737 if not sanitychecks(ldbs.sam, names):
1738 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1739 "Check the messages and correct the errors "
1740 "before rerunning upgradeprovision")
1741 ldbs.groupedRollback()
1742 sys.exit(1)
1744 # Let's see provision parameters
1745 print_provision_key_parameters(names)
1747 # 5) With all this information let's create a fresh new provision used as
1748 # reference
1749 message(SIMPLE, "Creating a reference provision")
1750 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1751 prefix="referenceprovision")
1752 newprovision(names, creds, session, smbconf, provisiondir,
1753 provision_logger)
1755 # TODO
1756 # 6) and 7)
1757 # We need to get a list of object which SD is directly computed from
1758 # defaultSecurityDescriptor.
1759 # This will allow us to know which object we can rebuild the SD in case
1760 # of change of the parent's SD or of the defaultSD.
1761 # Get file paths of this new provision
1762 newpaths = get_paths(param, targetdir=provisiondir)
1763 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1764 new_ldbs.startTransactions()
1766 # 8) Populate some associative array to ease the update process
1767 # List of attribute which are link and backlink
1768 populate_links(new_ldbs.sam, names.schemadn)
1769 # List of attribute with ASN DN synthax)
1770 populate_dnsyntax(new_ldbs.sam, names.schemadn)
1771 # 9)
1772 update_privilege(newpaths.private_dir, paths.private_dir)
1773 # 10)
1774 oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1775 # Do some modification on sam.ldb
1776 ldbs.groupedCommit()
1777 new_ldbs.groupedCommit()
1778 deltaattr = None
1779 # 11)
1780 if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1781 # 11) A
1782 # Starting from alpha9 we can consider that the structure is quite ok
1783 # and that we should do only dela
1784 deltaattr = delta_update_basesamdb(newpaths.samdb,
1785 paths.samdb,
1786 creds,
1787 session,
1789 message)
1790 else:
1791 # 11) B
1792 simple_update_basesamdb(newpaths, paths, names)
1793 ldbs = get_ldbs(paths, creds, session, lp)
1794 removeProvisionUSN(ldbs.sam)
1796 ldbs.startTransactions()
1797 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1798 new_ldbs.startTransactions()
1800 # 12)
1801 schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1802 # We create a closure that will be invoked just before schema reload
1803 def schemareloadclosure():
1804 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1805 options=["modules:"])
1806 doit = False
1807 if deltaattr is not None and len(deltaattr) > 1:
1808 doit = True
1809 if doit:
1810 deltaattr.remove("dn")
1811 for att in deltaattr:
1812 if att.lower() == "dn":
1813 continue
1814 if (deltaattr.get(att) is not None
1815 and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1816 doit = False
1817 elif deltaattr.get(att) is None:
1818 doit = False
1819 if doit:
1820 message(CHANGE, "Applying delta to @ATTRIBUTES")
1821 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1822 basesam.modify(deltaattr)
1823 else:
1824 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1825 "there is not only add")
1826 # 13)
1827 if opts.full:
1828 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1829 schema, schemareloadclosure):
1830 message(SIMPLE, "Rolling back all changes. Check the cause"
1831 " of the problem")
1832 message(SIMPLE, "Your system is as it was before the upgrade")
1833 ldbs.groupedRollback()
1834 new_ldbs.groupedRollback()
1835 shutil.rmtree(provisiondir)
1836 sys.exit(1)
1837 else:
1838 # Try to reapply the change also when we do not change the sam
1839 # as the delta_upgrade
1840 schemareloadclosure()
1841 sync_calculated_attributes(ldbs.sam, names)
1842 res = ldbs.sam.search(expression="(samaccountname=dns)",
1843 scope=SCOPE_SUBTREE, attrs=["dn"],
1844 controls=["search_options:1:2"])
1845 if len(res) > 0:
1846 message(SIMPLE, "You still have the old DNS object for managing "
1847 "dynamic DNS, but you didn't supply --full so "
1848 "a correct update can't be done")
1849 ldbs.groupedRollback()
1850 new_ldbs.groupedRollback()
1851 shutil.rmtree(provisiondir)
1852 sys.exit(1)
1853 # 14)
1854 update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1855 # 14bis)
1856 res = ldbs.sam.search(expression="(samaccountname=dns)",
1857 scope=SCOPE_SUBTREE, attrs=["dn"],
1858 controls=["search_options:1:2"])
1860 if (len(res) == 1):
1861 ldbs.sam.delete(res[0]["dn"])
1862 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1863 scope=SCOPE_SUBTREE, attrs=["dn"])
1864 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1865 message(SIMPLE, "IMPORTANT!!! "
1866 "If you were using Dynamic DNS before you need "
1867 "to update your configuration, so that the "
1868 "tkey-gssapi-credential has the following value: "
1869 "DNS/%s.%s" % (names.netbiosname.lower(),
1870 names.realm.lower()))
1871 # 15)
1872 message(SIMPLE, "Update machine account")
1873 update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1875 dnToRecalculate.sort(dn_sort)
1876 # 16) SD should be created with admin but as some previous acl were so wrong
1877 # that admin can't modify them we have first to recreate them with the good
1878 # form but with system account and then give the ownership to admin ...
1879 if str(oem) != "" and not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1880 message(SIMPLE, "Fixing very old provision SD")
1881 rebuild_sd(ldbs.sam, names)
1883 # We calculate the max USN before recalculating the SD because we might
1884 # touch object that have been modified after a provision and we do not
1885 # want that the next upgradeprovision thinks that it has a green light
1886 # to modify them
1888 # 17)
1889 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1891 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1892 # defSDmodified is set.
1893 if defSDmodified or len(dnToRecalculate) >0:
1894 message(SIMPLE, "Some defaultSecurityDescriptors and/or"
1895 "securityDescriptor have changed, recalculating SD ")
1896 ldbs.sam.set_session_info(adm_session)
1897 rebuild_sd(ldbs.sam, names)
1899 # 19)
1900 # Now we are quite confident in the recalculate process of the SD, we make
1901 # it optional. And we don't do it if there is DN that we must touch
1902 # as we are assured that on this DNs we will have differences !
1903 # Also the check must be done in a clever way as for the moment we just
1904 # compare SDDL
1905 if len(dnNotToRecalculate) == 0 and (opts.debugchangesd or opts.debugall):
1906 message(CHANGESD, "Checking recalculated SDs")
1907 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1909 # 20)
1910 updateOEMInfo(ldbs.sam, str(names.rootdn))
1911 # 21)
1912 check_for_DNS(newpaths.private_dir, paths.private_dir)
1913 # 22)
1914 if lastProvisionUSNs is not None:
1915 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1916 if opts.full and (names.policyid is None or names.policyid_dc is None):
1917 update_policyids(names, ldbs.sam)
1918 if opts.full or opts.resetfileacl or opts.fixntacl:
1919 try:
1920 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1921 except ProvisioningError, e:
1922 message(ERROR, "The policy for domain controller is missing. "
1923 "You should restart upgradeprovision with --full")
1924 except IOError, e:
1925 message(ERROR, "Setting ACL not supported on your filesystem")
1926 else:
1927 try:
1928 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1929 except ProvisioningError, e:
1930 message(ERROR, "The policy for domain controller is missing. "
1931 "You should restart upgradeprovision with --full")
1932 if not opts.fixntacl:
1933 ldbs.groupedCommit()
1934 new_ldbs.groupedCommit()
1935 message(SIMPLE, "Upgrade finished!")
1936 # remove reference provision now that everything is done !
1937 # So we have reindexed first if need when the merged schema was reloaded
1938 # (as new attributes could have quick in)
1939 # But the second part of the update (when we update existing objects
1940 # can also have an influence on indexing as some attribute might have their
1941 # searchflag modificated
1942 message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1943 "after modification")
1944 samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1945 message(SIMPLE, "Reindexing finished")
1947 shutil.rmtree(provisiondir)
1948 else:
1949 ldbs.groupedRollback()
1950 message(SIMPLE, "ACLs fixed !")
1951 except StandardError, err:
1952 message(ERROR, "A problem occurred while trying to upgrade your "
1953 "provision. A full backup is located at %s" % backupdir)
1954 if opts.debugall or opts.debugchange:
1955 (typ, val, tb) = sys.exc_info()
1956 traceback.print_exception(typ, val, tb)
1957 sys.exit(1)