s4/scripting: PY3 need to convert cmp funct to key func for sort.
[samba.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 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 samba.compat import cmp_to_key_fn
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 automatcally 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, "operatingSystemVersion":replace,
126                         "adminPropertyPages":replace, "groupType":replace,
127                         "wellKnownObjects":replace, "privilege":never,
128                         "rIDAvailablePool": never,
129                         "versionNumber" : add,
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("provision [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 automaticaly when
259     front attibutes 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 hash.has_key(str(elem).lower()):
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 hash.has_key(str(elem)):
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 hash.has_key(key) 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     delta.dn
625     skip = False
626     try:
627         if str(reference[0].get("cn"))  == "RID Set":
628             for klass in reference[0].get("objectClass"):
629                 if str(klass).lower() == "ridset":
630                     skip = True
631     finally:
632         if delta.get("objectSid"):
633             sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
634             m = re.match(r".*-(\d+)$", sid)
635             if m and int(m.group(1))>999:
636                 delta.remove("objectSid")
637         for att in attrNotCopied:
638             delta.remove(att)
639         for att in backlinked:
640             delta.remove(att)
641         for att in dn_syntax_att:
642             depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
643                                                                 delta.get(str(att)))
644             if depend_on_yet_tobecreated is not None:
645                 message(CHANGE, "Object %s depends on %s in attribute %s. "
646                                 "Delaying the creation" % (dn,
647                                           depend_on_yet_tobecreated, att))
648                 return False
649
650         delta.dn = dn
651         if not skip:
652             message(CHANGE,"Object %s will be added" % dn)
653             samdb.add(delta, ["relax:0", "provision:0"])
654         else:
655             message(CHANGE,"Object %s was skipped" % dn)
656
657         return True
658
659 def gen_dn_index_hash(listMissing):
660     """Generate a hash associating the DN to its creation order
661
662     :param listMissing: List of DN
663     :return: Hash with DN as keys and creation order as values"""
664     hash = {}
665     for i in range(0, len(listMissing)):
666         hash[str(listMissing[i]).lower()] = i
667     return hash
668
669 def add_deletedobj_containers(ref_samdb, samdb, names):
670     """Add the object containter: CN=Deleted Objects
671
672     This function create the container for each partition that need one and
673     then reference the object into the root of the partition
674
675     :param ref_samdb: Ldb object representing the SAM db of the reference
676                        provision
677     :param samdb: Ldb object representing the SAM db of the upgraded provision
678     :param names: List of key provision parameters"""
679
680
681     wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
682     partitions = [str(names.rootdn), str(names.configdn)]
683     for part in partitions:
684         ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
685                                             base=part, scope=SCOPE_SUBTREE,
686                                             attrs=["dn"],
687                                             controls=["show_deleted:0",
688                                                       "show_recycled:0"])
689         delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
690                                     base=part, scope=SCOPE_SUBTREE,
691                                     attrs=["dn"],
692                                     controls=["show_deleted:0",
693                                               "show_recycled:0"])
694         if len(ref_delObjCnt) > len(delObjCnt):
695             reference = ref_samdb.search(expression="cn=Deleted Objects",
696                                             base=part, scope=SCOPE_SUBTREE,
697                                             controls=["show_deleted:0",
698                                                       "show_recycled:0"])
699             empty = Message()
700             delta = samdb.msg_diff(empty, reference[0])
701
702             delta.dn = Dn(samdb, str(reference[0]["dn"]))
703             for att in attrNotCopied:
704                 delta.remove(att)
705
706             modcontrols = ["relax:0", "provision:0"]
707             samdb.add(delta, modcontrols)
708
709             listwko = []
710             res = samdb.search(expression="(objectClass=*)", base=part,
711                                scope=SCOPE_BASE,
712                                attrs=["dn", "wellKnownObjects"])
713
714             targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
715             found = False
716
717             if len(res[0]) > 0:
718                 wko = res[0]["wellKnownObjects"]
719
720                 # The wellKnownObject that we want to add.
721                 for o in wko:
722                     if str(o) == targetWKO:
723                         found = True
724                     listwko.append(str(o))
725
726             if not found:
727                 listwko.append(targetWKO)
728
729                 delta = Message()
730                 delta.dn = Dn(samdb, str(res[0]["dn"]))
731                 delta["wellKnownObjects"] = MessageElement(listwko,
732                                                 FLAG_MOD_REPLACE,
733                                                 "wellKnownObjects" )
734                 samdb.modify(delta)
735
736 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
737     """Add the missing object whose DN is the list
738
739     The function add the object if the objects on which it depends are
740     already created.
741
742     :param ref_samdb: Ldb object representing the SAM db of the reference
743                       provision
744     :param samdb: Ldb object representing the SAM db of the upgraded
745                   provision
746     :param dn: DN of the object to be added
747     :param names: List of key provision parameters
748     :param basedn: DN of the partition to be updated
749     :param list: List of DN to be added in the upgraded provision"""
750
751     listMissing = []
752     listDefered = list
753
754     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
755         index = 0
756         listMissing = listDefered
757         listDefered = []
758         hashMissing = gen_dn_index_hash(listMissing)
759         for dn in listMissing:
760             ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
761                                         hashMissing, index)
762             index = index + 1
763             if ret == 0:
764                 # DN can't be created because it depends on some
765                 # other DN in the list
766                 listDefered.append(dn)
767
768     if len(listDefered) != 0:
769         raise ProvisioningError("Unable to insert missing elements: "
770                                 "circular references")
771
772 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
773     """This function handle updates on links
774
775     :param samdb: An LDB object pointing to the updated provision
776     :param att: Attribute to update
777     :param basedn: The root DN of the provision
778     :param dn: The DN of the inspected object
779     :param value: The value of the attribute
780     :param ref_value: The value of this attribute in the reference provision
781     :param delta: The MessageElement object that will be applied for
782                    transforming the current provision"""
783
784     res = samdb.search(base=dn, controls=["search_options:1:2", "reveal:1"],
785                         attrs=[att])
786
787     blacklist = {}
788     hash = {}
789     newlinklist = []
790     changed = False
791
792     for v in value:
793         newlinklist.append(str(v))
794
795     for e in value:
796         hash[e] = 1
797     # for w2k domain level the reveal won't reveal anything ...
798     # it means that we can readd links that were removed on purpose ...
799     # Also this function in fact just accept add not removal
800
801     for e in res[0][att]:
802         if not hash.has_key(e):
803             # We put in the blacklist all the element that are in the "revealed"
804             # result and not in the "standard" result
805             # This element are links that were removed before and so that
806             # we don't wan't to readd
807             blacklist[e] = 1
808
809     for e in ref_value:
810         if not blacklist.has_key(e) and not hash.has_key(e):
811             newlinklist.append(str(e))
812             changed = True
813     if changed:
814         delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
815     else:
816         delta.remove(att)
817
818     return delta
819
820
821 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
822                                     hash_attr_usn, basedn, usns, samdb):
823     """ Check if we should keep the attribute modification or not
824
825         :param delta: A message diff object
826         :param att: An attribute
827         :param message: A function to print messages
828         :param reference: A message object for the current entry comming from
829                             the reference provision.
830         :param current: A message object for the current entry commin from
831                             the current provision.
832         :param hash_attr_usn: A dictionnary with attribute name as keys,
833                                 USN and invocation id as values.
834         :param basedn: The DN of the partition
835         :param usns: A dictionnary with invocation ID as keys and USN ranges
836                      as values.
837         :param samdb: A ldb object pointing to the sam DB
838
839         :return: The modified message diff.
840     """
841     global defSDmodified
842     isFirst = True
843     txt = ""
844     dn = current[0].dn
845
846     for att in list(delta):
847         if att in ["dn", "objectSid"]:
848             delta.remove(att)
849             continue
850
851         # We have updated by provision usn information so let's exploit
852         # replMetadataProperties
853         if att in forwardlinked:
854             curval = current[0].get(att, ())
855             refval = reference[0].get(att, ())
856             delta = handle_links(samdb, att, basedn, current[0]["dn"],
857                             curval, refval, delta)
858             continue
859
860
861         if isFirst and len(list(delta)) > 1:
862             isFirst = False
863             txt = "%s\n" % (str(dn))
864
865         if handle_special_case(att, delta, reference, current, True, None, None):
866             # This attribute is "complicated" to handle and handling
867             # was done in handle_special_case
868             continue
869
870         attrUSN = None
871         if hash_attr_usn.get(att):
872             [attrUSN, attInvId] = hash_attr_usn.get(att)
873
874         if attrUSN is None:
875             # If it's a replicated attribute and we don't have any USN
876             # information about it. It means that we never saw it before
877             # so let's add it !
878             # If it is a replicated attribute but we are not master on it
879             # (ie. not initially added in the provision we masterize).
880             # attrUSN will be -1
881             if isReplicated(att):
882                 continue
883             else:
884                 message(CHANGE, "Non replicated attribute %s changed" % att)
885                 continue
886
887         if att == "nTSecurityDescriptor":
888             cursd = ndr_unpack(security.descriptor,
889                 str(current[0]["nTSecurityDescriptor"]))
890             refsd = ndr_unpack(security.descriptor,
891                 str(reference[0]["nTSecurityDescriptor"]))
892
893             diff = get_diff_sds(refsd, cursd, names.domainsid)
894             if diff == "":
895                 # FIXME find a way to have it only with huge huge verbose mode
896                 # message(CHANGE, "%ssd are identical" % txt)
897                 # txt = ""
898                 delta.remove(att)
899                 continue
900             else:
901                 delta.remove(att)
902                 message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
903                 txt = ""
904                 if attrUSN == -1:
905                     message(CHANGESD, "But the SD has been changed by someonelse "
906                                     "so it's impossible to know if the difference"
907                                     " cames from the modification or from a previous bug")
908                     global dnNotToRecalculateFound
909                     dnNotToRecalculateFound = True
910                 else:
911                     dnToRecalculate.append(dn)
912                 continue
913
914         if attrUSN == -1:
915             # This attribute was last modified by another DC forget
916             # about it
917             message(CHANGE, "%sAttribute: %s has been "
918                     "created/modified/deleted by another DC. "
919                     "Doing nothing" % (txt, att))
920             txt = ""
921             delta.remove(att)
922             continue
923         elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
924             message(CHANGE, "%sAttribute: %s was not "
925                             "created/modified/deleted during a "
926                             "provision or upgradeprovision. Current "
927                             "usn: %d. Doing nothing" % (txt, att,
928                                                         attrUSN))
929             txt = ""
930             delta.remove(att)
931             continue
932         else:
933             if att == "defaultSecurityDescriptor":
934                 defSDmodified = True
935             if attrUSN:
936                 message(CHANGE, "%sAttribute: %s will be modified"
937                                 "/deleted it was last modified "
938                                 "during a provision. Current usn: "
939                                 "%d" % (txt, att, attrUSN))
940                 txt = ""
941             else:
942                 message(CHANGE, "%sAttribute: %s will be added because "
943                                 "it did not exist before" % (txt, att))
944                 txt = ""
945             continue
946
947     return delta
948
949 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
950     """ This function updates the object that are already present in the
951         provision
952
953     :param ref_samdb: An LDB object pointing to the reference provision
954     :param samdb: An LDB object pointing to the updated provision
955     :param basedn: A string with the value of the base DN for the provision
956                    (ie. DC=foo, DC=bar)
957     :param listPresent: A list of object that is present in the provision
958     :param usns: A list of USN range modified by previous provision and
959                  upgradeprovision grouped by invocation ID
960     """
961
962     # This hash is meant to speedup lookup of attribute name from an oid,
963     # it's for the replPropertyMetaData handling
964     hash_oid_name = {}
965     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
966                         controls=["search_options:1:2"], attrs=["attributeID",
967                         "lDAPDisplayName"])
968     if len(res) > 0:
969         for e in res:
970             strDisplay = str(e.get("lDAPDisplayName"))
971             hash_oid_name[str(e.get("attributeID"))] = strDisplay
972     else:
973         msg = "Unable to insert missing elements: circular references"
974         raise ProvisioningError(msg)
975
976     changed = 0
977     sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
978     controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
979     message(CHANGE, "Using replPropertyMetadata for change selection")
980     for dn in listPresent:
981         reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
982                                         scope=SCOPE_SUBTREE,
983                                         controls=controls)
984         current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
985                                 scope=SCOPE_SUBTREE, controls=controls)
986
987         if (
988              (str(current[0].dn) != str(reference[0].dn)) and
989              (str(current[0].dn).upper() == str(reference[0].dn).upper())
990            ):
991             message(CHANGE, "Names are the same except for the case. "
992                             "Renaming %s to %s" % (str(current[0].dn),
993                                                    str(reference[0].dn)))
994             identic_rename(samdb, reference[0].dn)
995             current = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
996                                     scope=SCOPE_SUBTREE,
997                                     controls=controls)
998
999         delta = samdb.msg_diff(current[0], reference[0])
1000
1001         for att in backlinked:
1002             delta.remove(att)
1003
1004         for att in attrNotCopied:
1005             delta.remove(att)
1006
1007         delta.remove("name")
1008
1009         nb_items = len(list(delta))
1010
1011         if nb_items == 1:
1012             continue
1013
1014         if nb_items > 1:
1015             # Fetch the replPropertyMetaData
1016             res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
1017                                 scope=SCOPE_SUBTREE, controls=controls,
1018                                 attrs=["replPropertyMetaData"])
1019             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1020                                 str(res[0]["replPropertyMetaData"])).ctr
1021
1022             hash_attr_usn = {}
1023             for o in ctr.array:
1024                 # We put in this hash only modification
1025                 # made on the current host
1026                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1027                 if str(o.originating_invocation_id) in usns.keys():
1028                     hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1029                 else:
1030                     hash_attr_usn[att] = [-1, None]
1031
1032         delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1033                                                current, hash_attr_usn,
1034                                                basedn, usns, samdb)
1035
1036         delta.dn = dn
1037
1038
1039         if len(delta) >1:
1040             # Skip dn as the value is not really changed ...
1041             attributes=", ".join(delta.keys()[1:])
1042             modcontrols = []
1043             relaxedatt = ['iscriticalsystemobject', 'grouptype']
1044             # Let's try to reduce as much as possible the use of relax control
1045             for attr in delta.keys():
1046                 if attr.lower() in relaxedatt:
1047                     modcontrols = ["relax:0", "provision:0"]
1048             message(CHANGE, "%s is different from the reference one, changed"
1049                             " attributes: %s\n" % (dn, attributes))
1050             changed += 1
1051             samdb.modify(delta, modcontrols)
1052     return changed
1053
1054 def reload_full_schema(samdb, names):
1055     """Load the updated schema with all the new and existing classes
1056        and attributes.
1057
1058     :param samdb: An LDB object connected to the sam.ldb of the update
1059                   provision
1060     :param names: List of key provision parameters
1061     """
1062
1063     schemadn = str(names.schemadn)
1064     current = samdb.search(expression="objectClass=*", base=schemadn,
1065                                 scope=SCOPE_SUBTREE)
1066     schema_ldif = ""
1067     prefixmap_data = ""
1068
1069     for ent in current:
1070         schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1071
1072     prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1073     prefixmap_data = b64encode(prefixmap_data).decode('utf8')
1074
1075     # We don't actually add this ldif, just parse it
1076     prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
1077
1078     dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
1079
1080
1081 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1082     """Check differences between the reference provision and the upgraded one.
1083
1084     It looks for all objects which base DN is name.
1085
1086     This function will also add the missing object and update existing object
1087     to add or remove attributes that were missing.
1088
1089     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1090                        reference provision
1091     :param samdb: An LDB object connected to the sam.ldb of the update
1092                   provision
1093     :param basedn: String value of the DN of the partition
1094     :param names: List of key provision parameters
1095     :param schema: A Schema object
1096     :param provisionUSNs:  A dictionnary with range of USN modified during provision
1097                             or upgradeprovision. Ranges are grouped by invocationID.
1098     :param prereloadfunc: A function that must be executed just before the reload
1099                   of the schema
1100     """
1101
1102     hash_new = {}
1103     hash = {}
1104     listMissing = []
1105     listPresent = []
1106     reference = []
1107     current = []
1108
1109     # Connect to the reference provision and get all the attribute in the
1110     # partition referred by name
1111     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1112                                     scope=SCOPE_SUBTREE, attrs=["dn"],
1113                                     controls=["search_options:1:2"])
1114
1115     current = samdb.search(expression="objectClass=*", base=basedn,
1116                                 scope=SCOPE_SUBTREE, attrs=["dn"],
1117                                 controls=["search_options:1:2"])
1118     # Create a hash for speeding the search of new object
1119     for i in range(0, len(reference)):
1120         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1121
1122     # Create a hash for speeding the search of existing object in the
1123     # current provision
1124     for i in range(0, len(current)):
1125         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1126
1127
1128     for k in hash_new.keys():
1129         if not hash.has_key(k):
1130             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1131                 listMissing.append(hash_new[k])
1132         else:
1133             listPresent.append(hash_new[k])
1134
1135     # Sort the missing object in order to have object of the lowest level
1136     # first (which can be containers for higher level objects)
1137     listMissing.sort(key=cmp_to_key_fn(dn_sort))
1138     listPresent.sort(key=cmp_to_key_fn(dn_sort))
1139
1140     # The following lines is to load the up to
1141     # date schema into our current LDB
1142     # a complete schema is needed as the insertion of attributes
1143     # and class is done against it
1144     # and the schema is self validated
1145     samdb.set_schema(schema)
1146     try:
1147         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1148         add_deletedobj_containers(ref_samdb, samdb, names)
1149
1150         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1151
1152         prereloadfunc()
1153         message(SIMPLE, "Reloading a merged schema, which might trigger "
1154                         "reindexing so please be patient")
1155         reload_full_schema(samdb, names)
1156         message(SIMPLE, "Schema reloaded!")
1157
1158         changed = update_present(ref_samdb, samdb, basedn, listPresent,
1159                                     provisionUSNs)
1160         message(SIMPLE, "There are %d changed objects" % (changed))
1161         return 1
1162
1163     except StandardError as err:
1164         message(ERROR, "Exception during upgrade of samdb:")
1165         (typ, val, tb) = sys.exc_info()
1166         traceback.print_exception(typ, val, tb)
1167         return 0
1168
1169
1170 def check_updated_sd(ref_sam, cur_sam, names):
1171     """Check if the security descriptor in the upgraded provision are the same
1172        as the reference
1173
1174     :param ref_sam: A LDB object connected to the sam.ldb file used as
1175                     the reference provision
1176     :param cur_sam: A LDB object connected to the sam.ldb file used as
1177                     upgraded provision
1178     :param names: List of key provision parameters"""
1179     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1180                                 scope=SCOPE_SUBTREE,
1181                                 attrs=["dn", "nTSecurityDescriptor"],
1182                                 controls=["search_options:1:2"])
1183     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1184                                 scope=SCOPE_SUBTREE,
1185                                 attrs=["dn", "nTSecurityDescriptor"],
1186                                 controls=["search_options:1:2"])
1187     hash = {}
1188     for i in range(0, len(reference)):
1189         refsd_blob = str(reference[i]["nTSecurityDescriptor"])
1190         hash[str(reference[i]["dn"]).lower()] = refsd_blob
1191
1192
1193     for i in range(0, len(current)):
1194         key = str(current[i]["dn"]).lower()
1195         if hash.has_key(key):
1196             cursd_blob = str(current[i]["nTSecurityDescriptor"])
1197             cursd = ndr_unpack(security.descriptor,
1198                                cursd_blob)
1199             if cursd_blob != hash[key]:
1200                 refsd = ndr_unpack(security.descriptor,
1201                                    hash[key])
1202                 txt = get_diff_sds(refsd, cursd, names.domainsid, False)
1203                 if txt != "":
1204                     message(CHANGESD, "On object %s ACL is different"
1205                                       " \n%s" % (current[i]["dn"], txt))
1206
1207
1208
1209 def fix_wellknown_sd(samdb, names):
1210     """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1211     This is needed because some provision use to have broken SD on containers
1212
1213     :param samdb: An LDB object pointing to the sam of the current provision
1214     :param names: A list of key provision parameters
1215     """
1216
1217     list_wellknown_dns = []
1218
1219     subcontainers = get_wellknown_sds(samdb)
1220
1221     for [dn, descriptor_fn] in subcontainers:
1222         list_wellknown_dns.append(dn)
1223         if dn in dnToRecalculate:
1224             delta = Message()
1225             delta.dn = dn
1226             descr = descriptor_fn(names.domainsid, name_map=names.name_map)
1227             delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1228                                                             "nTSecurityDescriptor" )
1229             samdb.modify(delta)
1230             message(CHANGESD, "nTSecurityDescriptor updated on wellknown DN: %s" % delta.dn)
1231
1232     return list_wellknown_dns
1233
1234 def rebuild_sd(samdb, names):
1235     """Rebuild security descriptor of the current provision from scratch
1236
1237     During the different pre release of samba4 security descriptors
1238     (SD) were notarly broken (up to alpha11 included)
1239
1240     This function allows one to get them back in order, this function works
1241     only after the database comparison that --full mode uses and which
1242     populates the dnToRecalculate and dnNotToRecalculate lists.
1243
1244     The idea is that the SD can be safely recalculated from scratch to get it right.
1245
1246     :param names: List of key provision parameters"""
1247
1248     listWellknown = fix_wellknown_sd(samdb, names)
1249
1250     if len(dnToRecalculate) != 0:
1251         message(CHANGESD, "%d DNs have been marked as needed to be recalculated"
1252                             % (len(dnToRecalculate)))
1253
1254     for dn in dnToRecalculate:
1255         # well known SDs have already been reset
1256         if dn in listWellknown:
1257             continue
1258         delta = Message()
1259         delta.dn = dn
1260         sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1261         try:
1262             descr = get_empty_descriptor(names.domainsid)
1263             delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1264                                                     "nTSecurityDescriptor")
1265             samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
1266         except LdbError as e:
1267             samdb.transaction_cancel()
1268             res = samdb.search(expression="objectClass=*", base=str(delta.dn),
1269                                 scope=SCOPE_BASE,
1270                                 attrs=["nTSecurityDescriptor"],
1271                                 controls=["sd_flags:1:%d" % sd_flags])
1272             badsd = ndr_unpack(security.descriptor,
1273                         str(res[0]["nTSecurityDescriptor"]))
1274             message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
1275             return
1276
1277 def hasATProvision(samdb):
1278         entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1279                                 scope=SCOPE_BASE,
1280                                 attrs=["dn"])
1281
1282         if entry is not None and len(entry) == 1:
1283             return True
1284         else:
1285             return False
1286
1287 def removeProvisionUSN(samdb):
1288         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1289         entry = samdb.search(expression="(distinguishedName=@PROVISION)", base = "",
1290                                 scope=SCOPE_BASE,
1291                                 attrs=attrs)
1292         empty = Message()
1293         empty.dn = entry[0].dn
1294         delta = samdb.msg_diff(entry[0], empty)
1295         delta.remove("dn")
1296         delta.dn = entry[0].dn
1297         samdb.modify(delta)
1298
1299 def remove_stored_generated_attrs(paths, creds, session, lp):
1300     """Remove previously stored constructed attributes
1301
1302     :param paths: List of paths for different provision objects
1303                         from the upgraded provision
1304     :param creds: A credential object
1305     :param session: A session object
1306     :param lp: A line parser object
1307     :return: An associative array whose key are the different constructed
1308              attributes and the value the dn where this attributes were found.
1309      """
1310
1311
1312 def simple_update_basesamdb(newpaths, paths, names):
1313     """Update the provision container db: sam.ldb
1314     This function is aimed at very old provision (before alpha9)
1315
1316     :param newpaths: List of paths for different provision objects
1317                         from the reference provision
1318     :param paths: List of paths for different provision objects
1319                         from the upgraded provision
1320     :param names: List of key provision parameters"""
1321
1322     message(SIMPLE, "Copy samdb")
1323     tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
1324
1325     message(SIMPLE, "Update partitions filename if needed")
1326     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1327     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1328     usersldb = os.path.join(paths.private_dir, "users.ldb")
1329     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1330
1331     if not os.path.isdir(samldbdir):
1332         os.mkdir(samldbdir)
1333         os.chmod(samldbdir, 0700)
1334     if os.path.isfile(schemaldb):
1335         tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
1336                                             "%s.ldb"%str(names.schemadn).upper()))
1337         os.remove(schemaldb)
1338     if os.path.isfile(usersldb):
1339         tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
1340                                             "%s.ldb"%str(names.rootdn).upper()))
1341         os.remove(usersldb)
1342     if os.path.isfile(configldb):
1343         tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
1344                                             "%s.ldb"%str(names.configdn).upper()))
1345         os.remove(configldb)
1346
1347
1348 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1349     """Upgrade the SAM DB contents for all the provision partitions
1350
1351     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1352                        provision
1353     :param samdb: An LDB object connected to the sam.ldb of the update
1354                   provision
1355     :param names: List of key provision parameters
1356     :param provisionUSNs:  A dictionnary with range of USN modified during provision
1357                             or upgradeprovision. Ranges are grouped by invocationID.
1358     :param schema: A Schema object that represent the schema of the provision
1359     :param prereloadfunc: A function that must be executed just before the reload
1360                   of the schema
1361     """
1362
1363     message(SIMPLE, "Starting update of samdb")
1364     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1365                             schema, provisionUSNs, prereloadfunc)
1366     if ret:
1367         message(SIMPLE, "Update of samdb finished")
1368         return 1
1369     else:
1370         message(SIMPLE, "Update failed")
1371         return 0
1372
1373
1374 def backup_provision(samdb, paths, dir, only_db):
1375     """This function backup the provision files so that a rollback
1376     is possible
1377
1378     :param paths: Paths to different objects
1379     :param dir: Directory where to store the backup
1380     :param only_db: Skip sysvol for users with big sysvol
1381     """
1382
1383     # Currently we default to tdb for the backend store type
1384     #
1385     backend_store = "tdb"
1386     res = samdb.search(base="@PARTITION",
1387                        scope=ldb.SCOPE_BASE,
1388                        attrs=["backendStore"])
1389     if "backendStore" in res[0]:
1390         backend_store = res[0]["backendStore"][0]
1391
1392
1393     if paths.sysvol and not only_db:
1394         copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
1395
1396     tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
1397     tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
1398     tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
1399     tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
1400     if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1401         tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
1402     shutil.copy2(paths.smbconf, dir)
1403     shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1404
1405     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1406     if not os.path.isdir(samldbdir):
1407         samldbdir = paths.private_dir
1408         schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1409         configldb = os.path.join(paths.private_dir, "configuration.ldb")
1410         usersldb = os.path.join(paths.private_dir, "users.ldb")
1411         tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
1412         tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
1413         tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
1414     else:
1415         os.mkdir(os.path.join(dir, "sam.ldb.d"), 0700)
1416
1417         for ldb_name in os.listdir(samldbdir):
1418             if not ldb_name.endswith("-lock"):
1419                 if backend_store == "mdb" and ldb_name != "metadata.tdb":
1420                     mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
1421                                       os.path.join(dir, "sam.ldb.d", ldb_name))
1422                 else:
1423                     tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
1424                                       os.path.join(dir, "sam.ldb.d", ldb_name))
1425
1426
1427 def sync_calculated_attributes(samdb, names):
1428    """Synchronize attributes used for constructed ones, with the
1429       old constructed that were stored in the database.
1430
1431       This apply for instance to msds-keyversionnumber that was
1432       stored and that is now constructed from replpropertymetadata.
1433
1434       :param samdb: An LDB object attached to the currently upgraded samdb
1435       :param names: Various key parameter about current provision.
1436    """
1437    listAttrs = ["msDs-KeyVersionNumber"]
1438    hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1439    if hash.has_key("msDs-KeyVersionNumber"):
1440        increment_calculated_keyversion_number(samdb, names.rootdn,
1441                                             hash["msDs-KeyVersionNumber"])
1442
1443 # Synopsis for updateprovision
1444 # 1) get path related to provision to be update (called current)
1445 # 2) open current provision ldbs
1446 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1447 #    of the DC ....)
1448 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1449 #    by either upgradeprovision or provision
1450 # 5) creation of a new provision the latest version of provision script
1451 #    (called reference)
1452 # 6) get reference provision paths
1453 # 7) open reference provision ldbs
1454 # 8) setup helpers data that will help the update process
1455 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1456 #    the current provision, because a shutil.copy would break the transaction locks both databases are under
1457 #    and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1458 # 10)get the oemInfo field, this field contains information about the different
1459 #    provision that have been done
1460 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1461 #    A) When alpha9 or alphaxx not specified (default)
1462 #       The base sam.ldb file is updated by looking at the difference between
1463 #       referrence one and the current one. Everything is copied with the
1464 #       exception of lastProvisionUSN attributes.
1465 #    B) Other case (it reflect that that provision was done before alpha9)
1466 #       The base sam.ldb of the reference provision is copied over
1467 #       the current one, if necessary ldb related to partitions are moved
1468 #       and renamed
1469 # The highest used USN is fetched so that changed by upgradeprovision
1470 # usn can be tracked
1471 # 12)A Schema object is created, it will be used to provide a complete
1472 #    schema to current provision during update (as the schema of the
1473 #    current provision might not be complete and so won't allow some
1474 #    object to be created)
1475 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1476 # 14)The secrets db is updated by pull all the difference from the reference
1477 #    provision into the current provision
1478 # 15)As the previous step has most probably modified the password stored in
1479 #    in secret for the current DC, a new password is generated,
1480 #    the kvno is bumped and the entry in samdb is also updated
1481 # 16)For current provision older than alpha9, we must fix the SD a little bit
1482 #    administrator to update them because SD used to be generated with the
1483 #    system account before alpha9.
1484 # 17)The highest usn modified so far is searched in the database it will be
1485 #    the upper limit for usn modified during provision.
1486 #    This is done before potential SD recalculation because we do not want
1487 #    SD modified during recalculation to be marked as modified during provision
1488 #    (and so possibly remplaced at next upgradeprovision)
1489 # 18)Rebuilt SD if the flag indicate to do so
1490 # 19)Check difference between SD of reference provision and those of the
1491 #    current provision. The check is done by getting the sddl representation
1492 #    of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1493 #    Each part is verified separetly, for dacl and sacl ACL is splited into
1494 #    ACEs and each ACE is verified separately (so that a permutation in ACE
1495 #    didn't raise as an error).
1496 # 20)The oemInfo field is updated to add information about the fact that the
1497 #    provision has been updated by the upgradeprovision version xxx
1498 #    (the version is the one obtained when starting samba with the --version
1499 #    parameter)
1500 # 21)Check if the current provision has all the settings needed for dynamic
1501 #    DNS update to work (that is to say the provision is newer than
1502 #    january 2010). If not dns configuration file from reference provision
1503 #    are copied in a sub folder and the administrator is invited to
1504 #    do what is needed.
1505 # 22)If the lastProvisionUSN attribute was present it is updated to add
1506 #    the range of usns modified by the current upgradeprovision
1507
1508
1509 # About updating the sam DB
1510 # The update takes place in update_partition function
1511 # This function read both current and reference provision and list all
1512 # the available DN of objects
1513 # If the string representation of a DN in reference provision is
1514 # equal to the string representation of a DN in current provision
1515 # (without taking care of case) then the object is flaged as being
1516 # present. If the object is not present in current provision the object
1517 # is being flaged as missing in current provision. Object present in current
1518 # provision but not in reference provision are ignored.
1519 # Once the list of objects present and missing is done, the deleted object
1520 # containers are created in the differents partitions (if missing)
1521 #
1522 # Then the function add_missing_entries is called
1523 # This function will go through the list of missing entries by calling
1524 # add_missing_object for the given object. If this function returns 0
1525 # it means that the object needs some other object in order to be created
1526 # The object is reappended at the end of the list to be created later
1527 # (and preferably after all the needed object have been created)
1528 # The function keeps on looping on the list of object to be created until
1529 # it's empty or that the number of deferred creation is equal to the number
1530 # of object that still needs to be created.
1531
1532 # The function add_missing_object will first check if the object can be created.
1533 # That is to say that it didn't depends other not yet created objects
1534 # If requisit can't be fullfilled it exists with 0
1535 # Then it will try to create the missing entry by creating doing
1536 # an ldb_message_diff between the object in the reference provision and
1537 # an empty object.
1538 # This resulting object is filtered to remove all the back link attribute
1539 # (ie. memberOf) as they will be created by the other linked object (ie.
1540 # the one with the member attribute)
1541 # All attributes specified in the attrNotCopied array are
1542 # also removed it's most of the time generated attributes
1543
1544 # After missing entries have been added the update_partition function will
1545 # take care of object that exist but that need some update.
1546 # In order to do so the function update_present is called with the list
1547 # of object that are present in both provision and that might need an update.
1548
1549 # This function handle first case mismatch so that the DN in the current
1550 # provision have the same case as in reference provision
1551
1552 # It will then construct an associative array consiting of attributes as
1553 # key and invocationid as value( if the originating invocation id is
1554 # different from the invocation id of the current DC the value is -1 instead).
1555
1556 # If the range of provision modified attributes is present, the function will
1557 # use the replMetadataProperty update method which is the following:
1558 #  Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1559 #   creationTime, msDs-KeyVersionNumber, oEMInformation
1560 #  Check for each attribute if its usn is within one of the modified by
1561 #   provision range and if its originating id is the invocation id of the
1562 #   current DC, then validate the update from reference to current.
1563 #   If not or if there is no replMetatdataProperty for this attribute then we
1564 #   do not update it.
1565 # Otherwise (case the range of provision modified attribute is not present) it
1566 # use the following process:
1567 #  All attributes that need to be added are accepted at the exeption of those
1568 #   listed in hashOverwrittenAtt, in this case the attribute needs to have the
1569 #   correct flags specified.
1570 #  For attributes that need to be modified or removed, a check is performed
1571 #  in OverwrittenAtt, if the attribute is present and the modification flag
1572 #  (remove, delete) is one of those listed for this attribute then modification
1573 #  is accepted. For complicated handling of attribute update, the control is passed
1574 #  to handle_special_case
1575
1576
1577
1578 if __name__ == '__main__':
1579     global defSDmodified
1580     defSDmodified = False
1581
1582     # From here start the big steps of the program
1583     # 1) First get files paths
1584     paths = get_paths(param, smbconf=smbconf)
1585     # Get ldbs with the system session, it is needed for searching
1586     # provision parameters
1587     session = system_session()
1588
1589     # This variable will hold the last provision USN once if it exists.
1590     minUSN = 0
1591     # 2)
1592     ldbs = get_ldbs(paths, creds, session, lp)
1593     backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1594                                     prefix="backupprovision")
1595     backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
1596     try:
1597         ldbs.startTransactions()
1598
1599         # 3) Guess all the needed names (variables in fact) from the current
1600         # provision.
1601         names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1602                                                 paths, smbconf, lp)
1603         # 4)
1604         lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1605         if lastProvisionUSNs is not None:
1606             v = 0
1607             for k in lastProvisionUSNs.keys():
1608                 for r in lastProvisionUSNs[k]:
1609                     v = v + 1
1610
1611             message(CHANGE,
1612                 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1613                             (len(lastProvisionUSNs.keys()), v /2 ))
1614
1615             if lastProvisionUSNs.get("default") is not None:
1616                 message(CHANGE, "Old style for usn ranges used")
1617                 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1618                 del lastProvisionUSNs["default"]
1619         else:
1620             message(SIMPLE, "Your provision lacks provision range information")
1621             if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1622                 ldbs.groupedRollback()
1623                 minobj = 5
1624                 (hash_id, nb_obj) = findprovisionrange(ldbs.sam, ldb.Dn(ldbs.sam, str(names.rootdn)))
1625                 message(SIMPLE, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj)
1626                 message(SIMPLE, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1627                         " of hundred of objects or more")
1628                 message(SIMPLE, "Total number of objects: %d" % nb_obj)
1629                 message(SIMPLE, "")
1630
1631                 print_provision_ranges(hash_id, minobj, None, str(paths.samdb), str(names.invocation))
1632
1633                 message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1634                 sys.exit(0)
1635
1636         # Objects will be created with the admin session
1637         # (not anymore system session)
1638         adm_session = admin_session(lp, str(names.domainsid))
1639         # So we reget handle on objects
1640         # ldbs = get_ldbs(paths, creds, adm_session, lp)
1641
1642         if not sanitychecks(ldbs.sam, names):
1643             message(SIMPLE, "Sanity checks for the upgrade have failed. "
1644                     "Check the messages and correct the errors "
1645                     "before rerunning upgradeprovision")
1646             ldbs.groupedRollback()
1647             sys.exit(1)
1648
1649         # Let's see provision parameters
1650         print_provision_key_parameters(names)
1651
1652         # 5) With all this information let's create a fresh new provision used as
1653         # reference
1654         message(SIMPLE, "Creating a reference provision")
1655         provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1656                         prefix="referenceprovision")
1657         result = newprovision(names, session, smbconf, provisiondir,
1658                 provision_logger, base_schema="2008_R2")
1659         result.report_logger(provision_logger)
1660
1661         # TODO
1662         # 6) and 7)
1663         # We need to get a list of object which SD is directly computed from
1664         # defaultSecurityDescriptor.
1665         # This will allow us to know which object we can rebuild the SD in case
1666         # of change of the parent's SD or of the defaultSD.
1667         # Get file paths of this new provision
1668         newpaths = get_paths(param, targetdir=provisiondir)
1669         new_ldbs = get_ldbs(newpaths, creds, session, lp)
1670         new_ldbs.startTransactions()
1671
1672         populateNotReplicated(new_ldbs.sam, names.schemadn)
1673         # 8) Populate some associative array to ease the update process
1674         # List of attribute which are link and backlink
1675         populate_links(new_ldbs.sam, names.schemadn)
1676         # List of attribute with ASN DN synthax)
1677         populate_dnsyntax(new_ldbs.sam, names.schemadn)
1678         # 9) (now skipped, was copy of privileges.ldb)
1679         # 10)
1680         oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1681         # Do some modification on sam.ldb
1682         ldbs.groupedCommit()
1683         new_ldbs.groupedCommit()
1684         deltaattr = None
1685     # 11)
1686         message(GUESS, oem)
1687         if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
1688             # 11) A
1689             # Starting from alpha9 we can consider that the structure is quite ok
1690             # and that we should do only dela
1691             deltaattr = delta_update_basesamdb(newpaths.samdb,
1692                             paths.samdb,
1693                             creds,
1694                             session,
1695                             lp,
1696                             message)
1697         else:
1698             # 11) B
1699             simple_update_basesamdb(newpaths, paths, names)
1700             ldbs = get_ldbs(paths, creds, session, lp)
1701             removeProvisionUSN(ldbs.sam)
1702
1703         ldbs.startTransactions()
1704         minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1705         new_ldbs.startTransactions()
1706
1707         # 12)
1708         schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1709         # We create a closure that will be invoked just before schema reload
1710         def schemareloadclosure():
1711             basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1712                     options=["modules:"])
1713             doit = False
1714             if deltaattr is not None and len(deltaattr) > 1:
1715                 doit = True
1716             if doit:
1717                 deltaattr.remove("dn")
1718                 for att in deltaattr:
1719                     if att.lower() == "dn":
1720                         continue
1721                     if (deltaattr.get(att) is not None
1722                         and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1723                         doit = False
1724                     elif deltaattr.get(att) is None:
1725                         doit = False
1726             if doit:
1727                 message(CHANGE, "Applying delta to @ATTRIBUTES")
1728                 deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1729                 basesam.modify(deltaattr)
1730             else:
1731                 message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1732                     "there is not only add")
1733         # 13)
1734         if opts.full:
1735             if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1736                     schema, schemareloadclosure):
1737                 message(SIMPLE, "Rolling back all changes. Check the cause"
1738                         " of the problem")
1739                 message(SIMPLE, "Your system is as it was before the upgrade")
1740                 ldbs.groupedRollback()
1741                 new_ldbs.groupedRollback()
1742                 shutil.rmtree(provisiondir)
1743                 sys.exit(1)
1744         else:
1745             # Try to reapply the change also when we do not change the sam
1746             # as the delta_upgrade
1747             schemareloadclosure()
1748             sync_calculated_attributes(ldbs.sam, names)
1749             res = ldbs.sam.search(expression="(samaccountname=dns)",
1750                     scope=SCOPE_SUBTREE, attrs=["dn"],
1751                     controls=["search_options:1:2"])
1752             if len(res) > 0:
1753                 message(SIMPLE, "You still have the old DNS object for managing "
1754                         "dynamic DNS, but you didn't supply --full so "
1755                         "a correct update can't be done")
1756                 ldbs.groupedRollback()
1757                 new_ldbs.groupedRollback()
1758                 shutil.rmtree(provisiondir)
1759                 sys.exit(1)
1760         # 14)
1761         update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1762         # 14bis)
1763         res = ldbs.sam.search(expression="(samaccountname=dns)",
1764                     scope=SCOPE_SUBTREE, attrs=["dn"],
1765                     controls=["search_options:1:2"])
1766
1767         if (len(res) == 1):
1768             ldbs.sam.delete(res[0]["dn"])
1769             res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1770                     scope=SCOPE_SUBTREE, attrs=["dn"])
1771             update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1772             message(SIMPLE, "IMPORTANT!!! "
1773                     "If you were using Dynamic DNS before you need "
1774                     "to update your configuration, so that the "
1775                     "tkey-gssapi-credential has the following value: "
1776                     "DNS/%s.%s" % (names.netbiosname.lower(),
1777                         names.realm.lower()))
1778         # 15)
1779         message(SIMPLE, "Update machine account")
1780         update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1781
1782         # 16) SD should be created with admin but as some previous acl were so wrong
1783         # that admin can't modify them we have first to recreate them with the good
1784         # form but with system account and then give the ownership to admin ...
1785         if opts.very_old_pre_alpha9:
1786             message(SIMPLE, "Fixing very old provision SD")
1787             rebuild_sd(ldbs.sam, names)
1788
1789         # We calculate the max USN before recalculating the SD because we might
1790         # touch object that have been modified after a provision and we do not
1791         # want that the next upgradeprovision thinks that it has a green light
1792         # to modify them
1793
1794         # 17)
1795         maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1796
1797         # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1798         # defSDmodified is set.
1799         if opts.full and (defSDmodified or len(dnToRecalculate) >0):
1800             message(SIMPLE, "Some (default) security descriptors (SDs) have "
1801                             "changed, recalculating them")
1802             ldbs.sam.set_session_info(adm_session)
1803             rebuild_sd(ldbs.sam, names)
1804
1805         # 19)
1806         # Now we are quite confident in the recalculate process of the SD, we make
1807         # it optional. And we don't do it if there is DN that we must touch
1808         # as we are assured that on this DNs we will have differences !
1809         # Also the check must be done in a clever way as for the moment we just
1810         # compare SDDL
1811         if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
1812             message(CHANGESD, "Checking recalculated SDs")
1813             check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1814
1815         # 20)
1816         updateOEMInfo(ldbs.sam, str(names.rootdn))
1817         # 21)
1818         check_for_DNS(newpaths.private_dir, paths.private_dir,
1819                       newpaths.binddns_dir, paths.binddns_dir,
1820                       names.dns_backend)
1821         # 22)
1822         update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1823         if opts.full and (names.policyid is None or names.policyid_dc is None):
1824             update_policyids(names, ldbs.sam)
1825
1826         if opts.full:
1827             try:
1828                 update_gpo(paths, ldbs.sam, names, lp, message)
1829             except ProvisioningError as e:
1830                 message(ERROR, "The policy for domain controller is missing. "
1831                             "You should restart upgradeprovision with --full")
1832
1833         ldbs.groupedCommit()
1834         new_ldbs.groupedCommit()
1835         message(SIMPLE, "Upgrade finished!")
1836         # remove reference provision now that everything is done !
1837         # So we have reindexed first if need when the merged schema was reloaded
1838         # (as new attributes could have quick in)
1839         # But the second part of the update (when we update existing objects
1840         # can also have an influence on indexing as some attribute might have their
1841         # searchflag modificated
1842         message(SIMPLE, "Reopening samdb to trigger reindexing if needed "
1843                 "after modification")
1844         samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1845         message(SIMPLE, "Reindexing finished")
1846
1847         shutil.rmtree(provisiondir)
1848     except StandardError as err:
1849         message(ERROR, "A problem occurred while trying to upgrade your "
1850                    "provision. A full backup is located at %s" % backupdir)
1851         if opts.debugall or opts.debugchange:
1852             (typ, val, tb) = sys.exc_info()
1853             traceback.print_exception(typ, val, tb)
1854         sys.exit(1)