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