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