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