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