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