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
.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
,
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
68 delete
=2**FLAG_MOD_DELETE
72 # Will be modified during provision to tell if default sd has been modified
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
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
= []
125 forwardlinked
= set()
128 def define_what_to_log(opts
):
132 if opts
.debugchangesd
:
133 what
= what | CHANGESD
136 if opts
.debugprovision
:
137 what
= what | PROVISION
139 what
= what | CHANGEALL
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
203 :param private: The path to the private directory of the upgraded
206 spnfile
= "%s/spn_update_list" % private
207 dnsfile
= "%s/dns_update_list" % private
208 namedfile
= lp
.get("dnsupdate:path")
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
):
225 if not os
.path
.exists(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():
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"])
267 not_replicated
.append(elem
["lDAPDisplayName"])
269 def populate_dnsyntax(samdb
, schemadn
):
270 """Populate an array with all the attributes that have DN synthax
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"])
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"])
292 print "No DC found. Your provision is most probably broken!"
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
)
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
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
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
)
352 if (att
== "userAccountControl" and flag
== FLAG_MOD_REPLACE
and
353 ldb
.Dn(aldb
, "CN=Administrator,CN=Users,%s" % basedn
)
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
]))))
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
)
364 if (att
== "member" and flag
== FLAG_MOD_REPLACE
):
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()):
375 newval
.append(str(elem
))
377 delta
[att
] = MessageElement(newval
, FLAG_MOD_REPLACE
, att
)
382 if (att
in ("gPLink", "gPCFileSysPath") and
383 flag
== FLAG_MOD_REPLACE
and
384 str(new
[0].dn
).lower() == str(old
[0].dn
).lower()):
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
)
395 if att
in ("adminDisplayName", "adminDescription"):
398 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain, %s" % (names
.schemadn
)
399 and att
== "defaultObjectCategory" and flag
== FLAG_MOD_REPLACE
):
402 if (str(old
[0].dn
) == "CN=Title, %s" % (str(names
.schemadn
)) and
403 att
== "rangeUpper" and flag
== FLAG_MOD_REPLACE
):
406 if (str(old
[0].dn
) == "%s" % (str(names
.rootdn
))
407 and att
== "subRefs" and flag
== FLAG_MOD_REPLACE
):
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:
413 if str(delta
.dn
).endswith("CN=DisplaySpecifiers, %s" % names
.configdn
):
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
):
423 for elem
in old
[0][att
]:
425 newval
.append(str(elem
))
427 for elem
in new
[0][att
]:
428 if not hash.has_key(str(elem
)):
430 newval
.append(str(elem
))
432 delta
[att
] = MessageElement(newval
, FLAG_MOD_REPLACE
, att
)
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]))
459 for e
in range(0, len(current
)):
460 message(CHANGE
, "old %d : %s" % (i
, str(current
[e
])))
462 if reference
is not None:
464 for e
in range(0, len(reference
)):
465 message(CHANGE
, "new %d : %s" % (i
, str(reference
[e
])))
468 def handle_special_add(samdb
, dn
, names
):
469 """Handle special operation (like remove) on some object needed during
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
479 objDn
= Dn(samdb
, "CN=IIS_IUSRS, CN=Builtin, %s" % names
.rootdn
)
481 #This entry was misplaced lets remove it if it exists
482 dntoremove
= "CN=IIS_IUSRS, CN=Users, %s" % names
.rootdn
485 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names
.rootdn
)
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
)
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
)
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
)
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"])
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"])
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"])
538 message(CHANGE
, "Existing object %s must be replaced by %s. "
539 "Removing old object" % (dntoremove
, str(dn
)))
540 samdb
.delete(res
[0]["dn"])
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
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."""
565 key
= str(dn
).lower()
566 if hash.has_key(key
) and hash[key
] > index
:
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
578 :param ref_samdb: Ldb object representing the SAM db of the reference
580 :param samdb: Ldb object representing the SAM db of the upgraded
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
587 :param index: Current creation order
588 :return: True if the object was created False otherwise"""
590 ret
= handle_special_add(samdb
, dn
, names
)
599 reference
= ref_samdb
.search(expression
="dn=%s" % (str(dn
)), base
=basedn
,
600 scope
=SCOPE_SUBTREE
, controls
=["search_options:1:2"])
602 delta
= samdb
.msg_diff(empty
, reference
[0])
606 if str(reference
[0].get("cn")) == "RID Set":
607 for klass
in reference
[0].get("objectClass"):
608 if str(klass
).lower() == "ridset":
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
:
618 for att
in backlinked
:
620 depend_on_yettobecreated
= None
621 for att
in dn_syntax_att
:
622 depend_on_yet_tobecreated
= check_dn_nottobecreated(hash, index
,
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
))
632 message(CHANGE
,"Object %s will be added" % dn
)
633 samdb
.add(delta
, ["relax:0", "provision:0"])
635 message(CHANGE
,"Object %s was skipped" % dn
)
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"""
645 for i
in range(0, len(listMissing
)):
646 hash[str(listMissing
[i
]).lower()] = i
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
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
,
667 controls
=["show_deleted:0",
669 delObjCnt
= samdb
.search(expression
="(cn=Deleted Objects)",
670 base
=part
, scope
=SCOPE_SUBTREE
,
672 controls
=["show_deleted: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",
680 delta
= samdb
.msg_diff(empty
, reference
[0])
682 delta
.dn
= Dn(samdb
, str(reference
[0]["dn"]))
683 for att
in attrNotCopied
:
686 modcontrols
= ["relax:0", "provision:0"]
687 samdb
.add(delta
, modcontrols
)
690 res
= samdb
.search(expression
="(objectClass=*)", base
=part
,
692 attrs
=["dn", "wellKnownObjects"])
694 targetWKO
= "%s:%s" % (wkoPrefix
, str(reference
[0]["dn"]))
698 wko
= res
[0]["wellKnownObjects"]
700 # The wellKnownObject that we want to add.
702 if str(o
) == targetWKO
:
704 listwko
.append(str(o
))
707 listwko
.append(targetWKO
)
710 delta
.dn
= Dn(samdb
, str(res
[0]["dn"]))
711 delta
["wellKnownObjects"] = MessageElement(listwko
,
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
722 :param ref_samdb: Ldb object representing the SAM db of the reference
724 :param samdb: Ldb object representing the SAM db of the upgraded
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"""
734 while(len(listDefered
) != len(listMissing
) and len(listDefered
) > 0):
736 listMissing
= listDefered
738 hashMissing
= gen_dn_index_hash(listMissing
)
739 for dn
in listMissing
:
740 ret
= add_missing_object(ref_samdb
, samdb
, dn
, names
, basedn
,
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"],
773 newlinklist
.extend(value
)
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
790 if not blacklist
.has_key(e
) and not hash.has_key(e
):
791 newlinklist
.append(str(e
))
794 delta
[att
] = MessageElement(newlinklist
, FLAG_MOD_REPLACE
, 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
,
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
826 for att
in list(delta
):
827 msgElt
= delta
.get(att
)
829 if att
== "nTSecurityDescriptor":
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
:
843 dump_denied_change(dn
, att
,
844 msg_elt_flag_strs
[msgElt
.flags()],
845 current
[0][att
], reference
[0][att
])
847 dump_denied_change(dn
, att
,
848 msg_elt_flag_strs
[msgElt
.flags()],
849 current
[0][att
], None)
853 if hashOverwrittenAtt
.get(att
)&2**msgElt
.flags() :
855 elif hashOverwrittenAtt
.get(att
)==never
:
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
877 :param samdb: A ldb object pointing to the sam DB
879 :return: The modified message diff.
886 for att
in list(delta
):
887 if att
in ["dn", "objectSid"]:
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
)
900 if isFirst
and len(delta
.items())>1:
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
910 if hash_attr_usn
.get(att
):
911 [attrUSN
, attInvId
] = hash_attr_usn
.get(att
)
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
917 # If it is a replicated attribute but we are not master on it
918 # (ie. not initially added in the provision we masterize).
920 if isReplicated(att
):
923 message(CHANGE
, "Non replicated attribute %s changed" % att
)
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
)
936 # FIXME find a way to have it only with huge huge verbose mode
937 # message(CHANGE, "%ssd are identical" % txt)
943 message(CHANGESD
, "%ssd are not identical:\n%s" % (txt
, diff
))
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
))
951 dnToRecalculate
.append(str(dn
))
955 # This attribute was last modified by another DC forget
957 message(CHANGE
, "%sAttribute: %s has been "
958 "created/modified/deleted by another DC. "
959 "Doing nothing" % (txt
, att
))
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
,
973 if att
== "defaultSecurityDescriptor":
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
))
982 message(CHANGE
, "%sAttribute: %s will be added because "
983 "it did not exist before" % (txt
, att
))
989 def update_present(ref_samdb
, samdb
, basedn
, listPresent
, usns
):
990 """ This function updates the object that are already present in the
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
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
1005 res
= samdb
.search(expression
="objectClass=attributeSchema", base
=basedn
,
1006 controls
=["search_options:1:2"], attrs
=["attributeID",
1010 strDisplay
= str(e
.get("lDAPDisplayName"))
1011 hash_oid_name
[str(e
.get("attributeID"))] = strDisplay
1013 msg
= "Unable to insert missing elements: circular references"
1014 raise ProvisioningError(msg
)
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
,
1024 current
= samdb
.search(expression
="dn=%s" % (str(dn
)), base
=basedn
,
1025 scope
=SCOPE_SUBTREE
, controls
=controls
)
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
,
1039 delta
= samdb
.msg_diff(current
[0], reference
[0])
1041 for att
in backlinked
:
1044 for att
in attrNotCopied
:
1047 delta
.remove("name")
1049 if len(delta
.items()) == 1:
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
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
)]
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
)
1075 delta
= checkKeepAttributeOldMtd(delta
, att
, reference
, current
, basedn
, samdb
)
1078 if len(delta
.items()) >1:
1079 # Skip dn as the value is not really changed ...
1080 attributes
=", ".join(delta
.keys()[1:])
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
))
1090 samdb
.modify(delta
, modcontrols
)
1093 def reload_full_schema(samdb
, names
):
1094 """Load the updated schema with all the new and existing classes
1097 :param samdb: An LDB object connected to the sam.ldb of the update
1099 :param names: List of key provision parameters
1102 current
= samdb
.search(expression
="objectClass=*", base
=str(names
.schemadn
),
1103 scope
=SCOPE_SUBTREE
)
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
1129 :param samdb: An LDB object connected to the sam.ldb of the update
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
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
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
])
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
)
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
)
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
,
1198 message(SIMPLE
, "There are %d changed objects" % (changed
))
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
)
1208 def check_updated_sd(ref_sam
, cur_sam
, names
):
1209 """Check if the security descriptor in the upgraded provision are the same
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
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"])
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)
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
:
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")
1268 # Then the config dn
1269 if alwaysRecalculate
or str(names
.configdn
) in dnToRecalculate
:
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" )
1277 # Then the schema dn
1278 if alwaysRecalculate
or str(names
.schemadn
) in dnToRecalculate
:
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" )
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
)]
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"])
1307 hash[str(obj
["dn"])] = obj
["whenCreated"]
1309 for dn
in dnToRecalculate
:
1310 if hash.has_key(dn
):
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"])
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
):
1331 delta
.dn
= Dn(samdb
, key
)
1333 delta
["whenCreated"] = MessageElement(hash[key
], FLAG_MOD_REPLACE
,
1335 samdb
.modify(delta
, ["recalculate_sd:0","relax:0"])
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
)))
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
,
1353 empty
.dn
= entry
[0].dn
1354 delta
= samdb
.msg_diff(entry
[0], empty
)
1356 delta
.dn
= entry
[0].dn
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
):
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()))
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
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
1425 :param samdb: An LDB object connected to the sam.ldb of the update
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
1435 message(SIMPLE
, "Starting update of samdb")
1436 ret
= update_partition(ref_samdb
, samdb
, str(names
.rootdn
), names
,
1437 schema
, provisionUSNs
, prereloadfunc
)
1439 message(SIMPLE
, "Update of samdb finished")
1442 message(SIMPLE
, "Update failed")
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"""
1455 for root
, dirs
, files
in os
.walk(dir, topdown
=True):
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
)
1463 os
.chown(tgt
, statsinfo
.st_uid
, statsinfo
.st_gid
)
1464 # Get the xattr attributes if any
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
,
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",
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
)
1486 os
.chown(os
.path
.join(root
, name
), statsinfo
.st_uid
,
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
,
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",
1506 def backup_provision(paths
, dir):
1507 """This function backup the provision files so that a rollback
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)
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
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
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
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
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
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.
1704 ldbs
= get_ldbs(paths
, creds
, session
, lp
)
1705 backupdir
= tempfile
.mkdtemp(dir=paths
.private_dir
,
1706 prefix
="backupprovision")
1707 backup_provision(paths
, backupdir
)
1709 ldbs
.startTransactions()
1711 # 3) Guess all the needed names (variables in fact) from the current
1713 names
= find_provision_key_parameters(ldbs
.sam
, ldbs
.secrets
, ldbs
.idmap
,
1716 lastProvisionUSNs
= get_last_provision_usn(ldbs
.sam
)
1717 if lastProvisionUSNs
is not None:
1719 for k
in lastProvisionUSNs
.keys():
1720 for r
in lastProvisionUSNs
[k
]:
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()
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
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
,
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
)
1772 update_privilege(newpaths
.private_dir
, paths
.private_dir
)
1774 oem
= getOEMInfo(ldbs
.sam
, str(names
.rootdn
))
1775 # Do some modification on sam.ldb
1776 ldbs
.groupedCommit()
1777 new_ldbs
.groupedCommit()
1780 if re
.match(".*alpha((9)|(\d\d+)).*", str(oem
)):
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
,
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()
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:"])
1807 if deltaattr
is not None and len(deltaattr
) > 1:
1810 deltaattr
.remove("dn")
1811 for att
in deltaattr
:
1812 if att
.lower() == "dn":
1814 if (deltaattr
.get(att
) is not None
1815 and deltaattr
.get(att
).flags() != FLAG_MOD_ADD
):
1817 elif deltaattr
.get(att
) is None:
1820 message(CHANGE
, "Applying delta to @ATTRIBUTES")
1821 deltaattr
.dn
= ldb
.Dn(basesam
, "@ATTRIBUTES")
1822 basesam
.modify(deltaattr
)
1824 message(CHANGE
, "Not applying delta to @ATTRIBUTES because "
1825 "there is not only add")
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"
1832 message(SIMPLE
, "Your system is as it was before the upgrade")
1833 ldbs
.groupedRollback()
1834 new_ldbs
.groupedRollback()
1835 shutil
.rmtree(provisiondir
)
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"])
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
)
1854 update_secrets(new_ldbs
.secrets
, ldbs
.secrets
, message
)
1856 res
= ldbs
.sam
.search(expression
="(samaccountname=dns)",
1857 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
1858 controls
=["search_options:1:2"])
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()))
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
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
)
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
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
)
1910 updateOEMInfo(ldbs
.sam
, str(names
.rootdn
))
1912 check_for_DNS(newpaths
.private_dir
, paths
.private_dir
)
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
:
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")
1925 message(ERROR
, "Setting ACL not supported on your filesystem")
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
)
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
)