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