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