s4-upgradeprovision: if there is nothing to really modify then skip it
[ddiss/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         if att == "dn":
882             # dn is not removable
883             continue
884         # We have updated by provision usn information so let's exploit
885         # replMetadataProperties
886         if att in forwardlinked:
887             curval = current[0].get(att, ())
888             refval = reference[0].get(att, ())
889             handle_links(samdb, att, basedn, current[0]["dn"],
890                             curval, refval, delta)
891             continue
892
893         if isFirst and len(delta.items())>1:
894             isFirst = False
895             txt = "%s\n" % (str(dn))
896
897         if handle_special_case(att, delta, reference, current, True, None, None):
898             # This attribute is "complicated" to handle and handling
899             # was done in handle_special_case
900             continue
901
902         attrUSN = None
903         if hash_attr_usn.get(att):
904             [attrUSN, attInvId] = hash_attr_usn.get(att)
905
906         if attrUSN is None:
907             # If it's a replicated attribute and we don't have any USN
908             # information about it. It means that we never saw it before
909             # so let's add it !
910             # If it is a replicated attribute but we are not master on it
911             # (ie. not initially added in the provision we masterize).
912             # attrUSN will be -1
913             if isReplicated(att):
914                 continue
915             else:
916                 message(CHANGE, "Non replicated attribute %s changed" % att)
917                 continue
918
919         if att == "nTSecurityDescriptor":
920             cursd = ndr_unpack(security.descriptor,
921                 str(current[0]["nTSecurityDescriptor"]))
922             cursddl = cursd.as_sddl(names.domainsid)
923             refsd = ndr_unpack(security.descriptor,
924                 str(reference[0]["nTSecurityDescriptor"]))
925             refsddl = refsd.as_sddl(names.domainsid)
926
927             if get_diff_sddls(refsddl, cursddl) == "":
928                 message(CHANGE, "sd are identical")
929             else:
930                 message(CHANGE, "sd are not identical")
931
932         if attrUSN == -1:
933             # This attribute was last modified by another DC forget
934             # about it
935             message(CHANGE, "%sAttribute: %s has been "
936                     "created/modified/deleted by another DC. "
937                     "Doing nothing" % (txt, att))
938             txt = ""
939             delta.remove(att)
940             continue
941         elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
942             message(CHANGE, "%sAttribute: %s was not "
943                             "created/modified/deleted during a "
944                             "provision or upgradeprovision. Current "
945                             "usn: %d. Doing nothing" % (txt, att,
946                                                         attrUSN))
947             txt = ""
948             delta.remove(att)
949             continue
950         else:
951             if att == "defaultSecurityDescriptor":
952                 defSDmodified = True
953             if attrUSN:
954                 message(CHANGE, "%sAttribute: %s will be modified"
955                                 "/deleted it was last modified "
956                                 "during a provision. Current usn: "
957                                 "%d" % (txt, att, attrUSN))
958                 txt = ""
959             else:
960                 message(CHANGE, "%sAttribute: %s will be added because "
961                                 "it did not exist before" % (txt, att))
962                 txt = ""
963             continue
964
965     return delta
966
967 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
968     """ This function updates the object that are already present in the
969         provision
970
971     :param ref_samdb: An LDB object pointing to the reference provision
972     :param samdb: An LDB object pointing to the updated provision
973     :param basedn: A string with the value of the base DN for the provision
974                    (ie. DC=foo, DC=bar)
975     :param listPresent: A list of object that is present in the provision
976     :param usns: A list of USN range modified by previous provision and
977                  upgradeprovision grouped by invocation ID
978     """
979
980     # This hash is meant to speedup lookup of attribute name from an oid,
981     # it's for the replPropertyMetaData handling
982     hash_oid_name = {}
983     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
984                         controls=["search_options:1:2"], attrs=["attributeID",
985                         "lDAPDisplayName"])
986     if len(res) > 0:
987         for e in res:
988             strDisplay = str(e.get("lDAPDisplayName"))
989             hash_oid_name[str(e.get("attributeID"))] = strDisplay
990     else:
991         msg = "Unable to insert missing elements: circular references"
992         raise ProvisioningError(msg)
993
994     changed = 0
995     controls = ["search_options:1:2", "sd_flags:1:0"]
996     if usns is not None:
997             message(CHANGE, "Using replPropertyMetadata for change selection")
998     for dn in listPresent:
999         reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1000                                         scope=SCOPE_SUBTREE,
1001                                         controls=controls)
1002         current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1003                                 scope=SCOPE_SUBTREE, controls=controls)
1004
1005         if (
1006              (str(current[0].dn) != str(reference[0].dn)) and
1007              (str(current[0].dn).upper() == str(reference[0].dn).upper())
1008            ):
1009             message(CHANGE, "Names are the same except for the case. "
1010                             "Renaming %s to %s" % (str(current[0].dn),
1011                                                    str(reference[0].dn)))
1012             identic_rename(samdb, reference[0].dn)
1013             current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1014                                     scope=SCOPE_SUBTREE,
1015                                     controls=controls)
1016
1017         delta = samdb.msg_diff(current[0], reference[0])
1018
1019         for att in backlinked:
1020             delta.remove(att)
1021
1022         for att in attrNotCopied:
1023             delta.remove(att)
1024
1025         delta.remove("name")
1026
1027         if len(delta.items()) == 1:
1028             continue
1029
1030         if len(delta.items()) > 1 and usns is not None:
1031             # Fetch the replPropertyMetaData
1032             res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1033                                 scope=SCOPE_SUBTREE, controls=controls,
1034                                 attrs=["replPropertyMetaData"])
1035             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1036                                 str(res[0]["replPropertyMetaData"])).ctr
1037
1038             hash_attr_usn = {}
1039             for o in ctr.array:
1040                 # We put in this hash only modification
1041                 # made on the current host
1042                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1043                 if str(o.originating_invocation_id) in usns.keys():
1044                     hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1045                 else:
1046                     hash_attr_usn[att] = [-1, None]
1047
1048         if usns is not None:
1049             delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1050                                                     current, hash_attr_usn,
1051                                                     basedn, usns, samdb)
1052         else:
1053             delta =  checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
1054
1055         delta.dn = dn
1056         if len(delta.items()) >1:
1057             # Skip dn as the value is not really changed ...
1058             attributes=", ".join(delta.keys()[1:])
1059             modcontrols = []
1060             relaxedatt = ['iscriticalsystemobject', 'grouptype']
1061             # Let's try to reduce as much as possible the use of relax control
1062             #for checkedatt in relaxedatt:
1063             for attr in delta.keys():
1064                 if attr.lower() in relaxedatt:
1065                     modcontrols = ["relax:0", "provision:0"]
1066             message(CHANGE, "%s is different from the reference one, changed"
1067                             " attributes: %s\n" % (dn, attributes))
1068             changed += 1
1069             samdb.modify(delta, modcontrols)
1070     return changed
1071
1072 def reload_full_schema(samdb, names):
1073     """Load the updated schema with all the new and existing classes
1074        and attributes.
1075
1076     :param samdb: An LDB object connected to the sam.ldb of the update
1077                   provision
1078     :param names: List of key provision parameters
1079     """
1080
1081     current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1082                                 scope=SCOPE_SUBTREE)
1083     schema_ldif = ""
1084     prefixmap_data = ""
1085
1086     for ent in current:
1087         schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
1088
1089     prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
1090     prefixmap_data = b64encode(prefixmap_data)
1091
1092     # We don't actually add this ldif, just parse it
1093     prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
1094
1095     dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
1096
1097
1098 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
1099     """Check differences between the reference provision and the upgraded one.
1100
1101     It looks for all objects which base DN is name.
1102
1103     This function will also add the missing object and update existing object
1104     to add or remove attributes that were missing.
1105
1106     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1107                        reference provision
1108     :param samdb: An LDB object connected to the sam.ldb of the update
1109                   provision
1110     :param basedn: String value of the DN of the partition
1111     :param names: List of key provision parameters
1112     :param schema: A Schema object
1113     :param provisionUSNs:  A dictionnary with range of USN modified during provision
1114                             or upgradeprovision. Ranges are grouped by invocationID.
1115     :param prereloadfunc: A function that must be executed just before the reload
1116                   of the schema
1117     """
1118
1119     hash_new = {}
1120     hash = {}
1121     listMissing = []
1122     listPresent = []
1123     reference = []
1124     current = []
1125
1126     # Connect to the reference provision and get all the attribute in the
1127     # partition referred by name
1128     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
1129                                     scope=SCOPE_SUBTREE, attrs=["dn"],
1130                                     controls=["search_options:1:2"])
1131
1132     current = samdb.search(expression="objectClass=*", base=basedn,
1133                                 scope=SCOPE_SUBTREE, attrs=["dn"],
1134                                 controls=["search_options:1:2"])
1135     # Create a hash for speeding the search of new object
1136     for i in range(0, len(reference)):
1137         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
1138
1139     # Create a hash for speeding the search of existing object in the
1140     # current provision
1141     for i in range(0, len(current)):
1142         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
1143
1144
1145     for k in hash_new.keys():
1146         if not hash.has_key(k):
1147             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
1148                 listMissing.append(hash_new[k])
1149         else:
1150             listPresent.append(hash_new[k])
1151
1152     # Sort the missing object in order to have object of the lowest level
1153     # first (which can be containers for higher level objects)
1154     listMissing.sort(dn_sort)
1155     listPresent.sort(dn_sort)
1156
1157     # The following lines is to load the up to
1158     # date schema into our current LDB
1159     # a complete schema is needed as the insertion of attributes
1160     # and class is done against it
1161     # and the schema is self validated
1162     samdb.set_schema(schema)
1163     try:
1164         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1165         add_deletedobj_containers(ref_samdb, samdb, names)
1166
1167         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1168
1169         prereloadfunc()
1170         message(SIMPLE, "Reloading a merged schema, which might trigger "
1171                         "reindexing so please be patient")
1172         reload_full_schema(samdb, names)
1173         message(SIMPLE, "Schema reloaded!")
1174
1175         changed = update_present(ref_samdb, samdb, basedn, listPresent,
1176                                     provisionUSNs)
1177         message(SIMPLE, "There are %d changed objects" % (changed))
1178         return 1
1179
1180     except StandardError, err:
1181         message(ERROR, "Exception during upgrade of samdb:")
1182         (typ, val, tb) = sys.exc_info()
1183         traceback.print_exception(typ, val, tb)
1184         return 0
1185
1186
1187 def check_updated_sd(ref_sam, cur_sam, names):
1188     """Check if the security descriptor in the upgraded provision are the same
1189        as the reference
1190
1191     :param ref_sam: A LDB object connected to the sam.ldb file used as
1192                     the reference provision
1193     :param cur_sam: A LDB object connected to the sam.ldb file used as
1194                     upgraded provision
1195     :param names: List of key provision parameters"""
1196     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1197                                 scope=SCOPE_SUBTREE,
1198                                 attrs=["dn", "nTSecurityDescriptor"],
1199                                 controls=["search_options:1:2"])
1200     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1201                                 scope=SCOPE_SUBTREE,
1202                                 attrs=["dn", "nTSecurityDescriptor"],
1203                                 controls=["search_options:1:2"])
1204     hash = {}
1205     for i in range(0, len(reference)):
1206         refsd = ndr_unpack(security.descriptor,
1207                     str(reference[i]["nTSecurityDescriptor"]))
1208         hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1209
1210
1211     for i in range(0, len(current)):
1212         key = str(current[i]["dn"]).lower()
1213         if hash.has_key(key):
1214             cursd = ndr_unpack(security.descriptor,
1215                         str(current[i]["nTSecurityDescriptor"]))
1216             sddl = cursd.as_sddl(names.domainsid)
1217             if sddl != hash[key]:
1218                 txt = get_diff_sddls(hash[key], sddl)
1219                 if txt != "":
1220                     message(CHANGESD, "On object %s ACL is different"
1221                                       " \n%s" % (current[i]["dn"], txt))
1222
1223
1224
1225 def fix_partition_sd(samdb, names):
1226     """This function fix the SD for partition containers (basedn, configdn, ...)
1227     This is needed because some provision use to have broken SD on containers
1228
1229     :param samdb: An LDB object pointing to the sam of the current provision
1230     :param names: A list of key provision parameters
1231     """
1232     # First update the SD for the rootdn
1233     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1234                          scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1235                          controls=["search_options:1:2"])
1236     delta = Message()
1237     delta.dn = Dn(samdb, str(res[0]["dn"]))
1238     descr = get_domain_descriptor(names.domainsid)
1239     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1240                                                     "nTSecurityDescriptor")
1241     samdb.modify(delta)
1242     # Then the config dn
1243     res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1244                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1245                         controls=["search_options:1:2"])
1246     delta = Message()
1247     delta.dn = Dn(samdb, str(res[0]["dn"]))
1248     descr = get_config_descriptor(names.domainsid)
1249     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1250                                                     "nTSecurityDescriptor" )
1251     samdb.modify(delta)
1252     # Then the schema dn
1253     res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1254                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1255                         controls=["search_options:1:2"])
1256
1257     delta = Message()
1258     delta.dn = Dn(samdb, str(res[0]["dn"]))
1259     descr = get_schema_descriptor(names.domainsid)
1260     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1261                                                     "nTSecurityDescriptor" )
1262     samdb.modify(delta)
1263
1264 def rebuild_sd(samdb, names):
1265     """Rebuild security descriptor of the current provision from scratch
1266
1267     During the different pre release of samba4 security descriptors (SD)
1268     were notarly broken (up to alpha11 included)
1269     This function allow to get them back in order, this function make the
1270     assumption that nobody has modified manualy an SD
1271     and so SD can be safely recalculated from scratch to get them right.
1272
1273     :param names: List of key provision parameters"""
1274
1275
1276     hash = {}
1277     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1278                         scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1279                         controls=["search_options:1:2"])
1280     for obj in res:
1281         if not (str(obj["dn"]) == str(names.rootdn) or
1282             str(obj["dn"]) == str(names.configdn) or
1283             str(obj["dn"]) == str(names.schemadn)):
1284             hash[str(obj["dn"])] = obj["whenCreated"]
1285
1286     listkeys = hash.keys()
1287     listkeys.sort(dn_sort)
1288
1289     for key in listkeys:
1290         try:
1291             delta = Message()
1292             delta.dn = Dn(samdb, key)
1293             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1294                                                     "whenCreated" )
1295             samdb.modify(delta, ["recalculate_sd:0"])
1296         except:
1297             # XXX: We should always catch an explicit exception.
1298             # What could go wrong here?
1299             samdb.transaction_cancel()
1300             res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1301                                 scope=SCOPE_SUBTREE,
1302                                 attrs=["dn", "nTSecurityDescriptor"],
1303                                 controls=["search_options:1:2"])
1304             badsd = ndr_unpack(security.descriptor,
1305                         str(res[0]["nTSecurityDescriptor"]))
1306             print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1307             return
1308
1309 def removeProvisionUSN(samdb):
1310         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1311         entry = samdb.search(expression="dn=@PROVISION", base = "",
1312                                 scope=SCOPE_SUBTREE,
1313                                 attrs=attrs)
1314         empty = Message()
1315         empty.dn = entry[0].dn
1316         delta = samdb.msg_diff(entry[0], empty)
1317         delta.remove("dn")
1318         delta.dn = entry[0].dn
1319         samdb.modify(delta)
1320
1321 def remove_stored_generated_attrs(paths, creds, session, lp):
1322     """Remove previously stored constructed attributes
1323
1324     :param paths: List of paths for different provision objects
1325                         from the upgraded provision
1326     :param creds: A credential object
1327     :param session: A session object
1328     :param lp: A line parser object
1329     :return: An associative array whose key are the different constructed
1330              attributes and the value the dn where this attributes were found.
1331      """
1332
1333
1334 def simple_update_basesamdb(newpaths, paths, names):
1335     """Update the provision container db: sam.ldb
1336     This function is aimed at very old provision (before alpha9)
1337
1338     :param newpaths: List of paths for different provision objects
1339                         from the reference provision
1340     :param paths: List of paths for different provision objects
1341                         from the upgraded provision
1342     :param names: List of key provision parameters"""
1343
1344     message(SIMPLE, "Copy samdb")
1345     shutil.copy(newpaths.samdb, paths.samdb)
1346
1347     message(SIMPLE, "Update partitions filename if needed")
1348     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1349     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1350     usersldb = os.path.join(paths.private_dir, "users.ldb")
1351     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1352
1353     if not os.path.isdir(samldbdir):
1354         os.mkdir(samldbdir)
1355         os.chmod(samldbdir, 0700)
1356     if os.path.isfile(schemaldb):
1357         shutil.copy(schemaldb, os.path.join(samldbdir,
1358                                             "%s.ldb"%str(names.schemadn).upper()))
1359         os.remove(schemaldb)
1360     if os.path.isfile(usersldb):
1361         shutil.copy(usersldb, os.path.join(samldbdir,
1362                                             "%s.ldb"%str(names.rootdn).upper()))
1363         os.remove(usersldb)
1364     if os.path.isfile(configldb):
1365         shutil.copy(configldb, os.path.join(samldbdir,
1366                                             "%s.ldb"%str(names.configdn).upper()))
1367         os.remove(configldb)
1368
1369
1370 def update_privilege(ref_private_path, cur_private_path):
1371     """Update the privilege database
1372
1373     :param ref_private_path: Path to the private directory of the reference
1374                              provision.
1375     :param cur_private_path: Path to the private directory of the current
1376                              (and to be updated) provision."""
1377     message(SIMPLE, "Copy privilege")
1378     shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1379                 os.path.join(cur_private_path, "privilege.ldb"))
1380
1381
1382 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
1383     """Upgrade the SAM DB contents for all the provision partitions
1384
1385     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1386                        provision
1387     :param samdb: An LDB object connected to the sam.ldb of the update
1388                   provision
1389     :param names: List of key provision parameters
1390     :param provisionUSNs:  A dictionnary with range of USN modified during provision
1391                             or upgradeprovision. Ranges are grouped by invocationID.
1392     :param schema: A Schema object that represent the schema of the provision
1393     :param prereloadfunc: A function that must be executed just before the reload
1394                   of the schema
1395     """
1396
1397     message(SIMPLE, "Starting update of samdb")
1398     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1399                             schema, provisionUSNs, prereloadfunc)
1400     if ret:
1401         message(SIMPLE, "Update of samdb finished")
1402         return 1
1403     else:
1404         message(SIMPLE, "Update failed")
1405         return 0
1406
1407
1408 def copyxattrs(dir, refdir):
1409     """ Copy owner, groups, extended ACL and NT acls from
1410     a reference dir to a destination dir
1411
1412     Both dir are supposed to hold the same files
1413     :param dir: Destination dir
1414     :param refdir: Reference directory"""
1415
1416     noxattr = 0
1417     for root, dirs, files in os.walk(dir, topdown=True):
1418         for name in files:
1419             subdir=root[len(dir):]
1420             ref = os.path.join("%s%s" % (refdir, subdir), name)
1421             statsinfo = os.stat(ref)
1422             tgt = os.path.join(root, name)
1423             try:
1424
1425                 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1426                 # Get the xattr attributes if any
1427                 try:
1428                     attribute = samba.xattr_native.wrap_getxattr(ref,
1429                                                  xattr.XATTR_NTACL_NAME)
1430                     samba.xattr_native.wrap_setxattr(tgt,
1431                                                  xattr.XATTR_NTACL_NAME,
1432                                                  attribute)
1433                 except:
1434                     noxattr = 1
1435                 attribute = samba.xattr_native.wrap_getxattr(ref,
1436                                                  "system.posix_acl_access")
1437                 samba.xattr_native.wrap_setxattr(tgt,
1438                                                  "system.posix_acl_access",
1439                                                   attribute)
1440             except:
1441                 continue
1442         for name in dirs:
1443             subdir=root[len(dir):]
1444             ref = os.path.join("%s%s" % (refdir, subdir), name)
1445             statsinfo = os.stat(ref)
1446             tgt = os.path.join(root, name)
1447             try:
1448                 os.chown(os.path.join(root, name), statsinfo.st_uid,
1449                           statsinfo.st_gid)
1450                 try:
1451                     attribute = samba.xattr_native.wrap_getxattr(ref,
1452                                                  xattr.XATTR_NTACL_NAME)
1453                     samba.xattr_native.wrap_setxattr(tgt,
1454                                                  xattr.XATTR_NTACL_NAME,
1455                                                  attribute)
1456                 except:
1457                     noxattr = 1
1458                 attribute = samba.xattr_native.wrap_getxattr(ref,
1459                                                  "system.posix_acl_access")
1460                 samba.xattr_native.wrap_setxattr(tgt,
1461                                                  "system.posix_acl_access",
1462                                                   attribute)
1463
1464             except:
1465                 continue
1466
1467
1468 def backup_provision(paths, dir):
1469     """This function backup the provision files so that a rollback
1470     is possible
1471
1472     :param paths: Paths to different objects
1473     :param dir: Directory where to store the backup
1474     """
1475
1476     shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1477     copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1478     shutil.copy2(paths.samdb, dir)
1479     shutil.copy2(paths.secrets, dir)
1480     shutil.copy2(paths.idmapdb, dir)
1481     shutil.copy2(paths.privilege, dir)
1482     if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1483         shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1484     shutil.copy2(paths.smbconf, dir)
1485     shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1486
1487     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1488     if not os.path.isdir(samldbdir):
1489         samldbdir = paths.private_dir
1490         schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1491         configldb = os.path.join(paths.private_dir, "configuration.ldb")
1492         usersldb = os.path.join(paths.private_dir, "users.ldb")
1493         shutil.copy2(schemaldb, dir)
1494         shutil.copy2(usersldb, dir)
1495         shutil.copy2(configldb, dir)
1496     else:
1497         shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1498
1499
1500
1501
1502 def sync_calculated_attributes(samdb, names):
1503    """Synchronize attributes used for constructed ones, with the
1504       old constructed that were stored in the database.
1505
1506       This apply for instance to msds-keyversionnumber that was
1507       stored and that is now constructed from replpropertymetadata.
1508
1509       :param samdb: An LDB object attached to the currently upgraded samdb
1510       :param names: Various key parameter about current provision.
1511    """
1512    listAttrs = ["msDs-KeyVersionNumber"]
1513    hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1514    if hash.has_key("msDs-KeyVersionNumber"):
1515        increment_calculated_keyversion_number(samdb, names.rootdn,
1516                                             hash["msDs-KeyVersionNumber"])
1517
1518 # Synopsis for updateprovision
1519 # 1) get path related to provision to be update (called current)
1520 # 2) open current provision ldbs
1521 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1522 #    of the DC ....)
1523 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1524 #    by either upgradeprovision or provision
1525 # 5) creation of a new provision the latest version of provision script
1526 #    (called reference)
1527 # 6) get reference provision paths
1528 # 7) open reference provision ldbs
1529 # 8) setup helpers data that will help the update process
1530 # 9) update the privilege ldb by copying the one of referecence provision to
1531 #    the current provision
1532 # 10)get the oemInfo field, this field contains information about the different
1533 #    provision that have been done
1534 # 11)Depending  on whether oemInfo has the string "alpha9" or alphaxx (x as an
1535 #    integer) or none of this the following things are done
1536 #    A) When alpha9 or alphaxx is present
1537 #       The base sam.ldb file is updated by looking at the difference between
1538 #       referrence one and the current one. Everything is copied with the
1539 #       exception of lastProvisionUSN attributes.
1540 #    B) Other case (it reflect that that provision was done before alpha9)
1541 #       The base sam.ldb of the reference provision is copied over
1542 #       the current one, if necessary ldb related to partitions are moved
1543 #       and renamed
1544 # The highest used USN is fetched so that changed by upgradeprovision
1545 # usn can be tracked
1546 # 12)A Schema object is created, it will be used to provide a complete
1547 #    schema to current provision during update (as the schema of the
1548 #    current provision might not be complete and so won't allow some
1549 #    object to be created)
1550 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1551 # 14)The secrets db is updated by pull all the difference from the reference
1552 #    provision into the current provision
1553 # 15)As the previous step has most probably modified the password stored in
1554 #    in secret for the current DC, a new password is generated,
1555 #    the kvno is bumped and the entry in samdb is also updated
1556 # 16)For current provision older than alpha9, we must fix the SD a little bit
1557 #    administrator to update them because SD used to be generated with the
1558 #    system account before alpha9.
1559 # 17)The highest usn modified so far is searched in the database it will be
1560 #    the upper limit for usn modified during provision.
1561 #    This is done before potential SD recalculation because we do not want
1562 #    SD modified during recalculation to be marked as modified during provision
1563 #    (and so possibly remplaced at next upgradeprovision)
1564 # 18)Rebuilt SD if the flag indicate to do so
1565 # 19)Check difference between SD of reference provision and those of the
1566 #    current provision. The check is done by getting the sddl representation
1567 #    of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1568 #    Each part is verified separetly, for dacl and sacl ACL is splited into
1569 #    ACEs and each ACE is verified separately (so that a permutation in ACE
1570 #    didn't raise as an error).
1571 # 20)The oemInfo field is updated to add information about the fact that the
1572 #    provision has been updated by the upgradeprovision version xxx
1573 #    (the version is the one obtained when starting samba with the --version
1574 #    parameter)
1575 # 21)Check if the current provision has all the settings needed for dynamic
1576 #    DNS update to work (that is to say the provision is newer than
1577 #    january 2010). If not dns configuration file from reference provision
1578 #    are copied in a sub folder and the administrator is invited to
1579 #    do what is needed.
1580 # 22)If the lastProvisionUSN attribute was present it is updated to add
1581 #    the range of usns modified by the current upgradeprovision
1582
1583
1584 # About updating the sam DB
1585 # The update takes place in update_partition function
1586 # This function read both current and reference provision and list all
1587 # the available DN of objects
1588 # If the string representation of a DN in reference provision is
1589 # equal to the string representation of a DN in current provision
1590 # (without taking care of case) then the object is flaged as being
1591 # present. If the object is not present in current provision the object
1592 # is being flaged as missing in current provision. Object present in current
1593 # provision but not in reference provision are ignored.
1594 # Once the list of objects present and missing is done, the deleted object
1595 # containers are created in the differents partitions (if missing)
1596 #
1597 # Then the function add_missing_entries is called
1598 # This function will go through the list of missing entries by calling
1599 # add_missing_object for the given object. If this function returns 0
1600 # it means that the object needs some other object in order to be created
1601 # The object is reappended at the end of the list to be created later
1602 # (and preferably after all the needed object have been created)
1603 # The function keeps on looping on the list of object to be created until
1604 # it's empty or that the number of defered creation is equal to the number
1605 # of object that still needs to be created.
1606
1607 # The function add_missing_object will first check if the object can be created.
1608 # That is to say that it didn't depends other not yet created objects
1609 # If requisit can't be fullfilled it exists with 0
1610 # Then it will try to create the missing entry by creating doing
1611 # an ldb_message_diff between the object in the reference provision and
1612 # an empty object.
1613 # This resulting object is filtered to remove all the back link attribute
1614 # (ie. memberOf) as they will be created by the other linked object (ie.
1615 # the one with the member attribute)
1616 # All attributes specified in the attrNotCopied array are
1617 # also removed it's most of the time generated attributes
1618
1619 # After missing entries have been added the update_partition function will
1620 # take care of object that exist but that need some update.
1621 # In order to do so the function update_present is called with the list
1622 # of object that are present in both provision and that might need an update.
1623
1624 # This function handle first case mismatch so that the DN in the current
1625 # provision have the same case as in reference provision
1626
1627 # It will then construct an associative array consiting of attributes as
1628 # key and invocationid as value( if the originating invocation id is
1629 # different from the invocation id of the current DC the value is -1 instead).
1630
1631 # If the range of provision modified attributes is present, the function will
1632 # use the replMetadataProperty update method which is the following:
1633 #  Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1634 #   creationTime, msDs-KeyVersionNumber, oEMInformation
1635 #  Check for each attribute if its usn is within one of the modified by
1636 #   provision range and if its originating id is the invocation id of the
1637 #   current DC, then validate the update from reference to current.
1638 #   If not or if there is no replMetatdataProperty for this attribute then we
1639 #   do not update it.
1640 # Otherwise (case the range of provision modified attribute is not present) it
1641 # use the following process:
1642 #  All attributes that need to be added are accepted at the exeption of those
1643 #   listed in hashOverwrittenAtt, in this case the attribute needs to have the
1644 #   correct flags specified.
1645 #  For attributes that need to be modified or removed, a check is performed
1646 #  in OverwrittenAtt, if the attribute is present and the modification flag
1647 #  (remove, delete) is one of those listed for this attribute then modification
1648 #  is accepted. For complicated handling of attribute update, the control is passed
1649 #  to handle_special_case
1650
1651
1652
1653 if __name__ == '__main__':
1654     global defSDmodified
1655     defSDmodified = False
1656     # From here start the big steps of the program
1657     # 1) First get files paths
1658     paths = get_paths(param, smbconf=smbconf)
1659     # Get ldbs with the system session, it is needed for searching
1660     # provision parameters
1661     session = system_session()
1662
1663     # This variable will hold the last provision USN once if it exists.
1664     minUSN = 0
1665     # 2)
1666     ldbs = get_ldbs(paths, creds, session, lp)
1667     backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1668                                     prefix="backupprovision")
1669     backup_provision(paths, backupdir)
1670     try:
1671         ldbs.startTransactions()
1672
1673         # 3) Guess all the needed names (variables in fact) from the current
1674         # provision.
1675         names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1676                                                 paths, smbconf, lp)
1677         # 4)
1678         lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1679         if lastProvisionUSNs is not None:
1680             v = 0
1681             for k in lastProvisionUSNs.keys():
1682                 for r in lastProvisionUSNs[k]:
1683                     v = v + 1
1684
1685             message(CHANGE,
1686                 "Find last provision USN, %d invocation(s) for a total of %d ranges" % \
1687                             (len(lastProvisionUSNs.keys()), v /2 ))
1688
1689             if lastProvisionUSNs.get("default") != None:
1690                 message(CHANGE, "Old style for usn ranges used")
1691                 lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
1692                 del lastProvisionUSNs["default"]
1693         # Objects will be created with the admin session
1694         # (not anymore system session)
1695         adm_session = admin_session(lp, str(names.domainsid))
1696         # So we reget handle on objects
1697         # ldbs = get_ldbs(paths, creds, adm_session, lp)
1698         if not opts.fixntacl:
1699             if not sanitychecks(ldbs.sam, names):
1700                 message(SIMPLE, "Sanity checks for the upgrade have failed. "
1701                         "Check the messages and correct the errors "
1702                         "before rerunning upgradeprovision")
1703                 ldbs.groupedRollback()
1704                 sys.exit(1)
1705
1706             # Let's see provision parameters
1707             print_provision_key_parameters(names)
1708
1709             # 5) With all this information let's create a fresh new provision used as
1710             # reference
1711             message(SIMPLE, "Creating a reference provision")
1712             provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1713                             prefix="referenceprovision")
1714             newprovision(names, creds, session, smbconf, provisiondir,
1715                     provision_logger)
1716
1717             # TODO
1718             # 6) and 7)
1719             # We need to get a list of object which SD is directly computed from
1720             # defaultSecurityDescriptor.
1721             # This will allow us to know which object we can rebuild the SD in case
1722             # of change of the parent's SD or of the defaultSD.
1723             # Get file paths of this new provision
1724             newpaths = get_paths(param, targetdir=provisiondir)
1725             new_ldbs = get_ldbs(newpaths, creds, session, lp)
1726             new_ldbs.startTransactions()
1727
1728             # 8) Populate some associative array to ease the update process
1729             # List of attribute which are link and backlink
1730             populate_links(new_ldbs.sam, names.schemadn)
1731             # List of attribute with ASN DN synthax)
1732             populate_dnsyntax(new_ldbs.sam, names.schemadn)
1733             # 9)
1734             update_privilege(newpaths.private_dir, paths.private_dir)
1735             # 10)
1736             oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1737             # Do some modification on sam.ldb
1738             ldbs.groupedCommit()
1739             new_ldbs.groupedCommit()
1740             deltaattr = None
1741         # 11)
1742             if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1743                 # 11) A
1744                 # Starting from alpha9 we can consider that the structure is quite ok
1745                 # and that we should do only dela
1746                 deltaattr = delta_update_basesamdb(newpaths.samdb,
1747                                 paths.samdb,
1748                                 creds,
1749                                 session,
1750                                 lp,
1751                                 message)
1752             else:
1753                 # 11) B
1754                 simple_update_basesamdb(newpaths, paths, names)
1755                 ldbs = get_ldbs(paths, creds, session, lp)
1756                 removeProvisionUSN(ldbs.sam)
1757
1758             ldbs.startTransactions()
1759             minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1760             new_ldbs.startTransactions()
1761
1762             # 12)
1763             schema = Schema(names.domainsid, schemadn=str(names.schemadn))
1764             # We create a closure that will be invoked just before schema reload
1765             def schemareloadclosure():
1766                 basesam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1767                         options=["modules:"])
1768                 doit = False
1769                 if deltaattr is not None and len(deltaattr) > 1:
1770                     doit = True
1771                 if doit:
1772                     deltaattr.remove("dn")
1773                 for att in deltaattr:
1774                     if att.lower() == "dn":
1775                         continue
1776                     if (deltaattr.get(att) is not None
1777                         and deltaattr.get(att).flags() != FLAG_MOD_ADD):
1778                         doit = False
1779                     elif deltaattr.get(att) is None:
1780                         doit = False
1781                 if doit:
1782                     message(CHANGE, "Applying delta to @ATTRIBUTES")
1783                     deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
1784                     basesam.modify(deltaattr)
1785                 else:
1786                     message(CHANGE, "Not applying delta to @ATTRIBUTES because "
1787                         "there is not only add")
1788             # 13)
1789             if opts.full:
1790                 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1791                         schema, schemareloadclosure):
1792                     message(SIMPLE, "Rolling back all changes. Check the cause"
1793                             " of the problem")
1794                     message(SIMPLE, "Your system is as it was before the upgrade")
1795                     ldbs.groupedRollback()
1796                     new_ldbs.groupedRollback()
1797                     shutil.rmtree(provisiondir)
1798                     sys.exit(1)
1799             else:
1800                 # Try to reapply the change also when we do not change the sam
1801                 # as the delta_upgrade
1802                 schemareloadclosure()
1803                 sync_calculated_attributes(ldbs.sam, names)
1804                 res = ldbs.sam.search(expression="(samaccountname=dns)",
1805                         scope=SCOPE_SUBTREE, attrs=["dn"],
1806                         controls=["search_options:1:2"])
1807                 if len(res) > 0:
1808                     message(SIMPLE, "You still have the old DNS object for managing "
1809                             "dynamic DNS, but you didn't supply --full so "
1810                             "a correct update can't be done")
1811                     ldbs.groupedRollback()
1812                     new_ldbs.groupedRollback()
1813                     shutil.rmtree(provisiondir)
1814                     sys.exit(1)
1815             # 14)
1816             update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1817             # 14bis)
1818             res = ldbs.sam.search(expression="(samaccountname=dns)",
1819                         scope=SCOPE_SUBTREE, attrs=["dn"],
1820                         controls=["search_options:1:2"])
1821
1822             if (len(res) == 1):
1823                 ldbs.sam.delete(res[0]["dn"])
1824                 res2 = ldbs.secrets.search(expression="(samaccountname=dns)",
1825                         scope=SCOPE_SUBTREE, attrs=["dn"])
1826                 update_dns_account_password(ldbs.sam, ldbs.secrets, names)
1827                 message(SIMPLE, "IMPORTANT!!! "
1828                         "If you were using Dynamic DNS before you need "
1829                         "to update your configuration, so that the "
1830                         "tkey-gssapi-credential has the following value: "
1831                         "DNS/%s.%s" % (names.netbiosname.lower(),
1832                             names.realm.lower()))
1833             # 15)
1834             message(SIMPLE, "Update machine account")
1835             update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1836
1837             # 16) SD should be created with admin but as some previous acl were so wrong
1838             # that admin can't modify them we have first to recreate them with the good
1839             # form but with system account and then give the ownership to admin ...
1840             if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1841                 message(SIMPLE, "Fixing old povision SD")
1842                 fix_partition_sd(ldbs.sam, names)
1843                 rebuild_sd(ldbs.sam, names)
1844
1845             # We calculate the max USN before recalculating the SD because we might
1846             # touch object that have been modified after a provision and we do not
1847             # want that the next upgradeprovision thinks that it has a green light
1848             # to modify them
1849
1850             # 17)
1851             maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1852
1853             # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1854             # But in fact we should do it also if one object has its SD modified as
1855             # child might need rebuild
1856             if defSDmodified:
1857                 message(SIMPLE, "Updating SD")
1858                 ldbs.sam.set_session_info(adm_session)
1859                 # Alpha10 was a bit broken still
1860                 if re.match(r'.*alpha(\d|10)', str(oem)):
1861                     fix_partition_sd(ldbs.sam, names)
1862                     rebuild_sd(ldbs.sam, names)
1863
1864             # 19)
1865             # Now we are quite confident in the recalculate process of the SD, we make
1866             # it optional.
1867             # Also the check must be done in a clever way as for the moment we just
1868             # compare SDDL
1869             if opts.debugchangesd:
1870                 check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1871
1872             # 20)
1873             updateOEMInfo(ldbs.sam, str(names.rootdn))
1874             # 21)
1875             check_for_DNS(newpaths.private_dir, paths.private_dir)
1876             # 22)
1877             if lastProvisionUSNs is not None:
1878                 update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
1879             if opts.full and (names.policyid is None or names.policyid_dc is None):
1880                 update_policyids(names, ldbs.sam)
1881         if opts.full or opts.resetfileacl or opts.fixntacl:
1882             try:
1883                 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1884             except ProvisioningError, e:
1885                 message(ERROR, "The policy for domain controller is missing. "
1886                             "You should restart upgradeprovision with --full")
1887             except IOError, e:
1888                 message(ERROR, "Setting ACL not supported on your filesystem")
1889         else:
1890             try:
1891                 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1892             except ProvisioningError, e:
1893                 message(ERROR, "The policy for domain controller is missing. "
1894                             "You should restart upgradeprovision with --full")
1895         if not opts.fixntacl:
1896             ldbs.groupedCommit()
1897             new_ldbs.groupedCommit()
1898             message(SIMPLE, "Upgrade finished!")
1899             # remove reference provision now that everything is done !
1900             # So we have reindexed first if need when the merged schema was reloaded
1901             # (as new attributes could have quick in)
1902             # But the second part of the update (when we update existing objects
1903             # can also have an influence on indexing as some attribute might have their
1904             # searchflag modificated
1905             message(SIMPLE, "Reopenning samdb to trigger reindexing if needed "
1906                     "after modification")
1907             samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1908             message(SIMPLE, "Reindexing finished")
1909
1910             shutil.rmtree(provisiondir)
1911         else:
1912             ldbs.groupedRollback()
1913             message(SIMPLE, "ACLs fixed !")
1914     except StandardError, err:
1915         message(ERROR, "A problem occurred while trying to upgrade your "
1916                    "provision. A full backup is located at %s" % backupdir)
1917         if opts.debugall or opts.debugchange:
1918             (typ, val, tb) = sys.exc_info()
1919             traceback.print_exception(typ, val, tb)
1920         sys.exit(1)