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