s4-upgradeprovision: don't print dn in the list of modified attributes
[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 hashAttrNotCopied = {   "dn": 1, "whenCreated": 1, "whenChanged": 1,
85                         "objectGUID": 1, "uSNCreated": 1,
86                         "replPropertyMetaData": 1, "uSNChanged": 1,
87                         "parentGUID": 1, "objectCategory": 1,
88                         "distinguishedName": 1, "nTMixedDomain": 1,
89                         "showInAdvancedViewOnly": 1, "instanceType": 1,
90                         "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
91                         "lmPwdHistory":1, "pwdLastSet": 1,
92                         "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
93                         "supplementalCredentials":1, "gPCUserExtensionNames":1,
94                         "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
95                         "possibleInferiors":1, "privilege":1,
96                         "sAMAccountType":1 }
97
98 # Usually for an object that already exists we do not overwrite attributes as
99 # they might have been changed for good reasons. Anyway for a few of them it's
100 # mandatory to replace them otherwise the provision will be broken somehow.
101 # But for attribute that are just missing we do not have to specify them as the default
102 # behavior is to add missing attribute
103 hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
104                         "systemOnly":replace, "searchFlags":replace,
105                         "mayContain":replace, "systemFlags":replace+add,
106                         "description":replace, "operatingSystemVersion":replace,
107                         "adminPropertyPages":replace, "groupType":replace,
108                         "wellKnownObjects":replace, "privilege":never,
109                         "defaultSecurityDescriptor": replace,
110                         "rIDAvailablePool": never,
111                         "rIDNextRID": add, "rIDUsedPool": never,
112                         "defaultSecurityDescriptor": replace + add,
113                         "isMemberOfPartialAttributeSet": delete,
114                         "attributeDisplayNames": replace + add,
115                         "versionNumber": add}
116
117 backlinked = []
118 forwardlinked = set()
119 dn_syntax_att = []
120 not_replicated = []
121 def define_what_to_log(opts):
122     what = 0
123     if opts.debugchange:
124         what = what | CHANGE
125     if opts.debugchangesd:
126         what = what | CHANGESD
127     if opts.debugguess:
128         what = what | GUESS
129     if opts.debugprovision:
130         what = what | PROVISION
131     if opts.debugall:
132         what = what | CHANGEALL
133     return what
134
135
136 parser = optparse.OptionParser("provision [options]")
137 sambaopts = options.SambaOptions(parser)
138 parser.add_option_group(sambaopts)
139 parser.add_option_group(options.VersionOptions(parser))
140 credopts = options.CredentialsOptions(parser)
141 parser.add_option_group(credopts)
142 parser.add_option("--setupdir", type="string", metavar="DIR",
143                   help="directory with setup files")
144 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
145 parser.add_option("--debugguess", action="store_true",
146                   help="Print information on which values are guessed")
147 parser.add_option("--debugchange", action="store_true",
148                   help="Print information on what is different but won't be changed")
149 parser.add_option("--debugchangesd", action="store_true",
150                   help="Print security descriptor differences")
151 parser.add_option("--debugall", action="store_true",
152                   help="Print all available information (very verbose)")
153 parser.add_option("--resetfileacl", action="store_true",
154                   help="Force a reset on filesystem acls in sysvol / netlogon share")
155 parser.add_option("--fixntacl", action="store_true",
156                   help="Only fix NT ACLs in sysvol / netlogon share")
157 parser.add_option("--full", action="store_true",
158                   help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
159
160 opts = parser.parse_args()[0]
161
162 handler = logging.StreamHandler(sys.stdout)
163 upgrade_logger = logging.getLogger("upgradeprovision")
164 upgrade_logger.setLevel(logging.INFO)
165
166 upgrade_logger.addHandler(handler)
167
168 provision_logger = logging.getLogger("provision")
169 provision_logger.addHandler(handler)
170
171 whatToLog = define_what_to_log(opts)
172
173 def message(what, text):
174     """Print a message if this message type has been selected to be printed
175
176     :param what: Category of the message
177     :param text: Message to print """
178     if (whatToLog & what) or what <= 0:
179         upgrade_logger.info("%s", text)
180
181 if len(sys.argv) == 1:
182     opts.interactive = True
183 lp = sambaopts.get_loadparm()
184 smbconf = lp.configfile
185
186 creds = credopts.get_credentials(lp)
187 creds.set_kerberos_state(DONT_USE_KERBEROS)
188
189
190
191 def check_for_DNS(refprivate, private):
192     """Check if the provision has already the requirement for dynamic dns
193
194     :param refprivate: The path to the private directory of the reference
195                        provision
196     :param private: The path to the private directory of the upgraded
197                     provision"""
198
199     spnfile = "%s/spn_update_list" % private
200     dnsfile = "%s/dns_update_list" % private
201     namedfile = lp.get("dnsupdate:path")
202
203     if not namedfile:
204        namedfile = "%s/named.conf.update" % private
205
206     if not os.path.exists(spnfile):
207         shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
208
209     if not os.path.exists(dnsfile):
210         shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
211
212     destdir = "%s/new_dns" % private
213     dnsdir = "%s/dns" % private
214
215     if not os.path.exists(namedfile):
216         if not os.path.exists(destdir):
217             os.mkdir(destdir)
218         if not os.path.exists(dnsdir):
219             os.mkdir(dnsdir)
220         shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
221         shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
222         message(SIMPLE, "It seems that your provision did not integrate "
223                 "new rules for dynamic dns update of domain related entries")
224         message(SIMPLE, "A copy of the new bind configuration files and "
225                 "template has been put in %s, you should read them and "
226                 "configure dynamic dns updates" % destdir)
227
228
229 def populate_links(samdb, schemadn):
230     """Populate an array with all the back linked attributes
231
232     This attributes that are modified automaticaly when
233     front attibutes are changed
234
235     :param samdb: A LDB object for sam.ldb file
236     :param schemadn: DN of the schema for the partition"""
237     linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
238     backlinked.extend(linkedAttHash.values())
239     for t in linkedAttHash.keys():
240         forwardlinked.add(t)
241
242 def isReplicated(att):
243     """ Indicate if the attribute is replicated or not
244
245     :param att: Name of the attribute to be tested
246     :return: True is the attribute is replicated, False otherwise
247     """
248
249     return (att not in not_replicated)
250
251 def populateNotReplicated(samdb, schemadn):
252     """Populate an array with all the attributes that are not replicated
253
254     :param samdb: A LDB object for sam.ldb file
255     :param schemadn: DN of the schema for the partition"""
256     res = samdb.search(expression="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base=Dn(samdb,
257                         str(schemadn)), scope=SCOPE_SUBTREE,
258                         attrs=["lDAPDisplayName"])
259     for elem in res:
260         not_replicated.append(elem["lDAPDisplayName"])
261
262 def populate_dnsyntax(samdb, schemadn):
263     """Populate an array with all the attributes that have DN synthax
264        (oid 2.5.5.1)
265
266     :param samdb: A LDB object for sam.ldb file
267     :param schemadn: DN of the schema for the partition"""
268     res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
269                         str(schemadn)), scope=SCOPE_SUBTREE,
270                         attrs=["lDAPDisplayName"])
271     for elem in res:
272         dn_syntax_att.append(elem["lDAPDisplayName"])
273
274
275 def sanitychecks(samdb, names):
276     """Make some checks before trying to update
277
278     :param samdb: An LDB object opened on sam.ldb
279     :param names: list of key provision parameters
280     :return: Status of check (1 for Ok, 0 for not Ok) """
281     res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
282                          scope=SCOPE_SUBTREE, attrs=["dn"],
283                          controls=["search_options:1:2"])
284     if len(res) == 0:
285         print "No DC found. Your provision is most probably broken!"
286         return False
287     elif len(res) != 1:
288         print "Found %d domain controllers. For the moment " \
289               "upgradeprovision is not able to handle an upgrade on a " \
290               "domain with more than one DC. Please demote the other " \
291               "DC(s) before upgrading" % len(res)
292         return False
293     else:
294         return True
295
296
297 def print_provision_key_parameters(names):
298     """Do a a pretty print of provision parameters
299
300     :param names: list of key provision parameters """
301     message(GUESS, "rootdn      :" + str(names.rootdn))
302     message(GUESS, "configdn    :" + str(names.configdn))
303     message(GUESS, "schemadn    :" + str(names.schemadn))
304     message(GUESS, "serverdn    :" + str(names.serverdn))
305     message(GUESS, "netbiosname :" + names.netbiosname)
306     message(GUESS, "defaultsite :" + names.sitename)
307     message(GUESS, "dnsdomain   :" + names.dnsdomain)
308     message(GUESS, "hostname    :" + names.hostname)
309     message(GUESS, "domain      :" + names.domain)
310     message(GUESS, "realm       :" + names.realm)
311     message(GUESS, "invocationid:" + names.invocation)
312     message(GUESS, "policyguid  :" + names.policyid)
313     message(GUESS, "policyguiddc:" + str(names.policyid_dc))
314     message(GUESS, "domainsid   :" + str(names.domainsid))
315     message(GUESS, "domainguid  :" + names.domainguid)
316     message(GUESS, "ntdsguid    :" + names.ntdsguid)
317     message(GUESS, "domainlevel :" + str(names.domainlevel))
318
319
320 def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
321     """Define more complicate update rules for some attributes
322
323     :param att: The attribute to be updated
324     :param delta: A messageElement object that correspond to the difference
325                   between the updated object and the reference one
326     :param new: The reference object
327     :param old: The Updated object
328     :param useReplMetadata: A boolean that indicate if the update process
329                 use replPropertyMetaData to decide what has to be updated.
330     :param basedn: The base DN of the provision
331     :param aldb: An ldb object used to build DN
332     :return: True to indicate that the attribute should be kept, False for
333              discarding it"""
334
335     flag = delta.get(att).flags()
336     # We do most of the special case handle if we do not have the
337     # highest usn as otherwise the replPropertyMetaData will guide us more
338     # correctly
339     if not useReplMetadata:
340         if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
341             ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
342                         "CN=Services,CN=Configuration,%s" % basedn)
343                         == old[0].dn):
344             return True
345         if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
346             ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
347                         == old[0].dn):
348             message(SIMPLE, "We suggest that you change the userAccountControl"
349                             " for user Administrator from value %d to %d" %
350                             (int(str(old[0][att])), int(str(new[0][att]))))
351             return False
352         if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
353             if (long(str(old[0][att])) == 0):
354                 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
355             return True
356
357         if (att == "member" and flag == FLAG_MOD_REPLACE):
358             hash = {}
359             newval = []
360             changeDelta=0
361             for elem in old[0][att]:
362                 hash[str(elem).lower()]=1
363                 newval.append(str(elem))
364
365             for elem in new[0][att]:
366                 if not hash.has_key(str(elem).lower()):
367                     changeDelta=1
368                     newval.append(str(elem))
369             if changeDelta == 1:
370                 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
371             else:
372                 delta.remove(att)
373             return True
374
375         if (att in ("gPLink", "gPCFileSysPath") and
376             flag == FLAG_MOD_REPLACE and
377             str(new[0].dn).lower() == str(old[0].dn).lower()):
378             delta.remove(att)
379             return True
380
381         if att == "forceLogoff":
382             ref=0x8000000000000000
383             oldval=int(old[0][att][0])
384             newval=int(new[0][att][0])
385             ref == old and ref == abs(new)
386             return True
387
388         if att in ("adminDisplayName", "adminDescription"):
389             return True
390
391         if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
392             and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
393             return True
394
395         if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
396                 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
397             return True
398
399         if (str(old[0].dn) == "%s" % (str(names.rootdn))
400                 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
401             return True
402         #Allow to change revision of ForestUpdates objects
403         if (att == "revision" or att == "objectVersion"):
404             if str(delta.dn).lower().find("domainupdates") and str(delta.dn).lower().find("forestupdates") > 0:
405                 return True
406         if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
407             return True
408
409     # This is a bit of special animal as we might have added
410     # already SPN entries to the list that has to be modified
411     # So we go in detail to try to find out what has to be added ...
412     if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
413         hash = {}
414         newval = []
415         changeDelta = 0
416         for elem in old[0][att]:
417             hash[str(elem)]=1
418             newval.append(str(elem))
419
420         for elem in new[0][att]:
421             if not hash.has_key(str(elem)):
422                 changeDelta = 1
423                 newval.append(str(elem))
424         if changeDelta == 1:
425             delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
426         else:
427             delta.remove(att)
428         return True
429
430     return False
431
432 def dump_denied_change(dn, att, flagtxt, current, reference):
433     """Print detailed information about why a change is denied
434
435     :param dn: DN of the object which attribute is denied
436     :param att: Attribute that was supposed to be upgraded
437     :param flagtxt: Type of the update that should be performed
438                     (add, change, remove, ...)
439     :param current: Value(s) of the current attribute
440     :param reference: Value(s) of the reference attribute"""
441
442     message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
443                 + " must not be changed/removed. Discarding the change")
444     if att == "objectSid" :
445         message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
446         message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
447     elif att == "rIDPreviousAllocationPool" or att == "rIDAllocationPool":
448         message(CHANGE, "old : %s" % int64range2str(current[0]))
449         message(CHANGE, "new : %s" % int64range2str(reference[0]))
450     else:
451         i = 0
452         for e in range(0, len(current)):
453             message(CHANGE, "old %d : %s" % (i, str(current[e])))
454             i+=1
455         if reference is not None:
456             i = 0
457             for e in range(0, len(reference)):
458                 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
459                 i+=1
460
461 def handle_special_add(samdb, dn, names):
462     """Handle special operation (like remove) on some object needed during
463     upgrade
464
465     This is mostly due to wrong creation of the object in previous provision.
466     :param samdb: An Ldb object representing the SAM database
467     :param dn: DN of the object to inspect
468     :param names: list of key provision parameters
469     """
470
471     dntoremove = None
472     objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
473     if dn == objDn :
474         #This entry was misplaced lets remove it if it exists
475         dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
476
477     objDn = Dn(samdb,
478                 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
479     if dn == objDn:
480         #This entry was misplaced lets remove it if it exists
481         dntoremove = "CN=Certificate Service DCOM Access,"\
482                      "CN=Users, %s" % names.rootdn
483
484     objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
485     if dn == objDn:
486         #This entry was misplaced lets remove it if it exists
487         dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
488
489     objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
490     if dn == objDn:
491         #This entry was misplaced lets remove it if it exists
492         dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
493
494     objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
495                      "CN=Configuration,%s" % names.rootdn)
496     if dn == objDn:
497         oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
498                          "CN=WellKnown Security Principals,"
499                          "CN=Configuration,%s" % names.rootdn)
500
501         res = samdb.search(expression="(dn=%s)" % oldDn,
502                             base=str(names.rootdn),
503                             scope=SCOPE_SUBTREE, attrs=["dn"],
504                             controls=["search_options:1:2"])
505
506         res2 = samdb.search(expression="(dn=%s)" % dn,
507                             base=str(names.rootdn),
508                             scope=SCOPE_SUBTREE, attrs=["dn"],
509                             controls=["search_options:1:2"])
510
511         if len(res) > 0 and len(res2) == 0:
512             message(CHANGE, "Existing object %s must be replaced by %s. "
513                             "Renaming old object" % (str(oldDn), str(dn)))
514             samdb.rename(oldDn, objDn, ["relax:0", "provision:0"])
515
516         return 0
517
518     if dntoremove is not None:
519         res = samdb.search(expression="(cn=RID Set)",
520                             base=str(names.rootdn),
521                             scope=SCOPE_SUBTREE, attrs=["dn"],
522                             controls=["search_options:1:2"])
523
524         if len(res) == 0:
525             return 2
526         res = samdb.search(expression="(dn=%s)" % dntoremove,
527                             base=str(names.rootdn),
528                             scope=SCOPE_SUBTREE, attrs=["dn"],
529                             controls=["search_options:1:2"])
530         if len(res) > 0:
531             message(CHANGE, "Existing object %s must be replaced by %s. "
532                             "Removing old object" % (dntoremove, str(dn)))
533             samdb.delete(res[0]["dn"])
534             return 0
535
536     return 1
537
538
539 def check_dn_nottobecreated(hash, index, listdn):
540     """Check if one of the DN present in the list has a creation order
541        greater than the current.
542
543     Hash is indexed by dn to be created, with each key
544     is associated the creation order.
545
546     First dn to be created has the creation order 0, second has 1, ...
547     Index contain the current creation order
548
549     :param hash: Hash holding the different DN of the object to be
550                   created as key
551     :param index: Current creation order
552     :param listdn: List of DNs on which the current DN depends on
553     :return: None if the current object do not depend on other
554               object or if all object have been created before."""
555     if listdn is None:
556         return None
557     for dn in listdn:
558         key = str(dn).lower()
559         if hash.has_key(key) and hash[key] > index:
560             return str(dn)
561     return None
562
563
564
565 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
566     """Add a new object if the dependencies are satisfied
567
568     The function add the object if the object on which it depends are already
569     created
570
571     :param ref_samdb: Ldb object representing the SAM db of the reference
572                        provision
573     :param samdb: Ldb object representing the SAM db of the upgraded
574                    provision
575     :param dn: DN of the object to be added
576     :param names: List of key provision parameters
577     :param basedn: DN of the partition to be updated
578     :param hash: Hash holding the different DN of the object to be
579                   created as key
580     :param index: Current creation order
581     :return: True if the object was created False otherwise"""
582
583     ret = handle_special_add(samdb, dn, names)
584
585     if ret == 2:
586         return False
587
588     if ret == 0:
589         return True
590
591
592     reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
593                     scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
594     empty = Message()
595     delta = samdb.msg_diff(empty, reference[0])
596     delta.dn
597     skip = False
598     try:
599         if str(reference[0].get("cn"))  == "RID Set":
600             for klass in reference[0].get("objectClass"):
601                 if str(klass).lower() == "ridset":
602                     skip = True
603     finally:
604         if delta.get("objectSid"):
605             sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
606             m = re.match(r".*-(\d+)$", sid)
607             if m and int(m.group(1))>999:
608                 delta.remove("objectSid")
609         for att in hashAttrNotCopied.keys():
610             delta.remove(att)
611         for att in backlinked:
612             delta.remove(att)
613         depend_on_yettobecreated = None
614         for att in dn_syntax_att:
615             depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
616                                                                 delta.get(str(att)))
617             if depend_on_yet_tobecreated is not None:
618                 message(CHANGE, "Object %s depends on %s in attribute %s. "
619                                 "Delaying the creation" % (dn,
620                                           depend_on_yet_tobecreated, att))
621                 return False
622
623         delta.dn = dn
624         if not skip:
625             message(CHANGE,"Object %s will be added" % dn)
626             samdb.add(delta, ["relax:0", "provision:0"])
627         else:
628             message(CHANGE,"Object %s was skipped" % dn)
629
630         return True
631
632 def gen_dn_index_hash(listMissing):
633     """Generate a hash associating the DN to its creation order
634
635     :param listMissing: List of DN
636     :return: Hash with DN as keys and creation order as values"""
637     hash = {}
638     for i in range(0, len(listMissing)):
639         hash[str(listMissing[i]).lower()] = i
640     return hash
641
642 def add_deletedobj_containers(ref_samdb, samdb, names):
643     """Add the object containter: CN=Deleted Objects
644
645     This function create the container for each partition that need one and
646     then reference the object into the root of the partition
647
648     :param ref_samdb: Ldb object representing the SAM db of the reference
649                        provision
650     :param samdb: Ldb object representing the SAM db of the upgraded provision
651     :param names: List of key provision parameters"""
652
653
654     wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
655     partitions = [str(names.rootdn), str(names.configdn)]
656     for part in partitions:
657         ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
658                                             base=part, scope=SCOPE_SUBTREE,
659                                             attrs=["dn"],
660                                             controls=["show_deleted:0",
661                                                       "show_recycled:0"])
662         delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
663                                     base=part, scope=SCOPE_SUBTREE,
664                                     attrs=["dn"],
665                                     controls=["show_deleted:0",
666                                               "show_recycled:0"])
667         if len(ref_delObjCnt) > len(delObjCnt):
668             reference = ref_samdb.search(expression="cn=Deleted Objects",
669                                             base=part, scope=SCOPE_SUBTREE,
670                                             controls=["show_deleted:0",
671                                                       "show_recycled:0"])
672             empty = Message()
673             delta = samdb.msg_diff(empty, reference[0])
674
675             delta.dn = Dn(samdb, str(reference[0]["dn"]))
676             for att in hashAttrNotCopied.keys():
677                 delta.remove(att)
678
679             modcontrols = ["relax:0", "provision:0"]
680             samdb.add(delta, modcontrols)
681
682             listwko = []
683             res = samdb.search(expression="(objectClass=*)", base=part,
684                                scope=SCOPE_BASE,
685                                attrs=["dn", "wellKnownObjects"])
686
687             targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
688             found = False
689
690             if len(res[0]) > 0:
691                 wko = res[0]["wellKnownObjects"]
692
693                 # The wellKnownObject that we want to add.
694                 for o in wko:
695                     if str(o) == targetWKO:
696                         found = True
697                     listwko.append(str(o))
698
699             if not found:
700                 listwko.append(targetWKO)
701
702                 delta = Message()
703                 delta.dn = Dn(samdb, str(res[0]["dn"]))
704                 delta["wellKnownObjects"] = MessageElement(listwko,
705                                                 FLAG_MOD_REPLACE,
706                                                 "wellKnownObjects" )
707                 samdb.modify(delta)
708
709 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
710     """Add the missing object whose DN is the list
711
712     The function add the object if the objects on which it depends are
713     already created.
714
715     :param ref_samdb: Ldb object representing the SAM db of the reference
716                       provision
717     :param samdb: Ldb object representing the SAM db of the upgraded
718                   provision
719     :param dn: DN of the object to be added
720     :param names: List of key provision parameters
721     :param basedn: DN of the partition to be updated
722     :param list: List of DN to be added in the upgraded provision"""
723
724     listMissing = []
725     listDefered = list
726
727     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
728         index = 0
729         listMissing = listDefered
730         listDefered = []
731         hashMissing = gen_dn_index_hash(listMissing)
732         for dn in listMissing:
733             ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
734                                         hashMissing, index)
735             index = index + 1
736             if ret == 0:
737                 # DN can't be created because it depends on some
738                 # other DN in the list
739                 listDefered.append(dn)
740
741     if len(listDefered) != 0:
742         raise ProvisioningError("Unable to insert missing elements: "
743                                 "circular references")
744
745 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
746     """This function handle updates on links
747
748     :param samdb: An LDB object pointing to the updated provision
749     :param att: Attribute to update
750     :param basedn: The root DN of the provision
751     :param dn: The DN of the inspected object
752     :param value: The value of the attribute
753     :param ref_value: The value of this attribute in the reference provision
754     :param delta: The MessageElement object that will be applied for
755                    transforming the current provision"""
756
757     res = samdb.search(expression="dn=%s" % dn, base=basedn,
758                         controls=["search_options:1:2", "reveal:1"],
759                         attrs=[att])
760
761     blacklist = {}
762     hash = {}
763     newlinklist = []
764     changed = False
765
766     newlinklist.extend(value)
767
768     for e in value:
769         hash[e] = 1
770     # for w2k domain level the reveal won't reveal anything ...
771     # it means that we can readd links that were removed on purpose ...
772     # Also this function in fact just accept add not removal
773
774     for e in res[0][att]:
775         if not hash.has_key(e):
776             # We put in the blacklist all the element that are in the "revealed"
777             # result and not in the "standard" result
778             # This element are links that were removed before and so that
779             # we don't wan't to readd
780             blacklist[e] = 1
781
782     for e in ref_value:
783         if not blacklist.has_key(e) and not hash.has_key(e):
784             newlinklist.append(str(e))
785             changed = True
786     if changed:
787         delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
788     else:
789         delta.remove(att)
790
791
792 msg_elt_flag_strs = {
793     ldb.FLAG_MOD_ADD: "MOD_ADD",
794     ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
795     ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
796
797 def checkKeepAttributeOldMtd(delta, att, reference, current,
798                                     basedn, samdb):
799     """ Check if we should keep the attribute modification or not.
800         This function didn't use replicationMetadata to take a decision.
801
802         :param delta: A message diff object
803         :param att: An attribute
804         :param reference: A message object for the current entry comming from
805                             the reference provision.
806         :param current: A message object for the current entry commin from
807                             the current provision.
808         :param basedn: The DN of the partition
809         :param samdb: A ldb connection to the sam database of the current provision.
810
811         :return: The modified message diff.
812     """
813     # Old school way of handling things for pre alpha12 upgrade
814     global defSDmodified
815     isFirst = False
816     txt = ""
817     dn = current[0].dn
818
819     for att in list(delta):
820         defSDmodified = True
821         msgElt = delta.get(att)
822
823         if att == "nTSecurityDescriptor":
824             delta.remove(att)
825             continue
826
827         if att == "dn":
828             continue
829
830         if not hashOverwrittenAtt.has_key(att):
831             if msgElt.flags() != FLAG_MOD_ADD:
832                 if not handle_special_case(att, delta, reference, current,
833                                             False, basedn, samdb):
834                     if opts.debugchange or opts.debugall:
835                         try:
836                             dump_denied_change(dn, att,
837                                 msg_elt_flag_strs[msgElt.flags()],
838                                 current[0][att], reference[0][att])
839                         except KeyError:
840                             dump_denied_change(dn, att,
841                                 msg_elt_flag_strs[msgElt.flags()],
842                                 current[0][att], None)
843                     delta.remove(att)
844                 continue
845         else:
846             if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
847                 continue
848             elif  hashOverwrittenAtt.get(att)==never:
849                 delta.remove(att)
850                 continue
851
852     return delta
853
854 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
855                                     hash_attr_usn, basedn, usns, samdb):
856     """ Check if we should keep the attribute modification or not
857
858         :param delta: A message diff object
859         :param att: An attribute
860         :param message: A function to print messages
861         :param reference: A message object for the current entry comming from
862                             the reference provision.
863         :param current: A message object for the current entry commin from
864                             the current provision.
865         :param hash_attr_usn: A dictionnary with attribute name as keys,
866                                 USN and invocation id as values.
867         :param basedn: The DN of the partition
868         :param usns: A dictionnary with invocation ID as keys and USN ranges
869                      as values.
870         :param samdb: A ldb object pointing to the sam DB
871
872         :return: The modified message diff.
873     """
874     global defSDmodified
875     isFirst = False
876     txt = ""
877     dn = current[0].dn
878
879     for att in list(delta):
880         # We have updated by provision usn information so let's exploit
881         # replMetadataProperties
882         if att in forwardlinked:
883             curval = current[0].get(att, ())
884             refval = reference[0].get(att, ())
885             handle_links(samdb, att, basedn, current[0]["dn"],
886                             curval, refval, delta)
887             continue
888
889         if isFirst and len(delta.items())>1:
890             isFirst = True
891             txt = "%s\n" % (str(dn))
892
893         keptAttr = ["dn", "rIDAvailablePool", "objectSid", "creationTime", "oEMInformation", "msDs-KeyVersionNumber"]
894         if att in keptAttr:
895             delta.remove(att)
896             continue
897
898         if handle_special_case(att, delta, reference, current, True, None, None):
899             # This attribute is "complicated" to handle and handling
900             # was done in handle_special_case
901             continue
902
903         attrUSN = None
904         if hash_attr_usn.get(att):
905             [attrUSN, attInvId] = hash_attr_usn.get(att)
906
907         if attrUSN is None:
908             # If it's a replicated attribute and we don't have any USN
909             # information about it. It means that we never saw it before
910             # so let's add it !
911             # If it is a replicated attribute but we are not master on it
912             # (ie. not initially added in the provision we masterize).
913             # attrUSN will be -1
914             if isReplicated(att):
915                 continue
916             elif att in hashAttrNotCopied.keys():
917                 delta.remove(att)
918             else:
919                 continue
920
921         if attrUSN is None:
922             delta.remove(att)
923             continue
924         if att == "nTSecurityDescriptor":
925             cursd = ndr_unpack(security.descriptor,
926                 str(current[0]["nTSecurityDescriptor"]))
927             cursddl = cursd.as_sddl(names.domainsid)
928             refsd = ndr_unpack(security.descriptor,
929                 str(reference[0]["nTSecurityDescriptor"]))
930             refsddl = refsd.as_sddl(names.domainsid)
931
932             if get_diff_sddls(refsddl, cursddl) == "":
933                 message(CHANGE, "sd are identical")
934             else:
935                 message(CHANGE, "sd are not identical")
936         if attrUSN == -1:
937             # This attribute was last modified by another DC forget
938             # about it
939             message(CHANGE, "%sAttribute: %s has been "
940                     "created/modified/deleted by another DC. "
941                     "Doing nothing" % (txt, att))
942             txt = ""
943             delta.remove(att)
944             continue
945         elif not usn_in_range(int(attrUSN), usns.get(attInvId)):
946             message(CHANGE, "%sAttribute: %s was not "
947                             "created/modified/deleted during a "
948                             "provision or upgradeprovision. Current "
949                             "usn: %d. Doing nothing" % (txt, att,
950                                                         attrUSN))
951             txt = ""
952             delta.remove(att)
953             continue
954         else:
955             if att == "defaultSecurityDescriptor":
956                 defSDmodified = True
957             if attrUSN:
958                 message(CHANGE, "%sAttribute: %s will be modified"
959                                 "/deleted it was last modified "
960                                 "during a provision. Current usn: "
961                                 "%d" % (txt, att, attrUSN))
962                 txt = ""
963             else:
964                 message(CHANGE, "%sAttribute: %s will be added because "
965                                 "it did not exist before" % (txt, att))
966                 txt = ""
967             continue
968
969     return delta
970
971 def update_present(ref_samdb, samdb, basedn, listPresent, usns):
972     """ This function updates the object that are already present in the
973         provision
974
975     :param ref_samdb: An LDB object pointing to the reference provision
976     :param samdb: An LDB object pointing to the updated provision
977     :param basedn: A string with the value of the base DN for the provision
978                    (ie. DC=foo, DC=bar)
979     :param listPresent: A list of object that is present in the provision
980     :param usns: A list of USN range modified by previous provision and
981                  upgradeprovision grouped by invocation ID
982     """
983
984     # This hash is meant to speedup lookup of attribute name from an oid,
985     # it's for the replPropertyMetaData handling
986     hash_oid_name = {}
987     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
988                         controls=["search_options:1:2"], attrs=["attributeID",
989                         "lDAPDisplayName"])
990     if len(res) > 0:
991         for e in res:
992             strDisplay = str(e.get("lDAPDisplayName"))
993             hash_oid_name[str(e.get("attributeID"))] = strDisplay
994     else:
995         msg = "Unable to insert missing elements: circular references"
996         raise ProvisioningError(msg)
997
998     changed = 0
999     controls = ["search_options:1:2", "sd_flags:1:2"]
1000     if usns is not None:
1001             message(CHANGE, "Using replPropertyMetadata for change selection")
1002     for dn in listPresent:
1003         reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1004                                         scope=SCOPE_SUBTREE,
1005                                         controls=controls)
1006         current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1007                                 scope=SCOPE_SUBTREE, controls=controls)
1008
1009         if (
1010              (str(current[0].dn) != str(reference[0].dn)) and
1011              (str(current[0].dn).upper() == str(reference[0].dn).upper())
1012            ):
1013             message(CHANGE, "Names are the same except for the case. "
1014                             "Renaming %s to %s" % (str(current[0].dn),
1015                                                    str(reference[0].dn)))
1016             identic_rename(samdb, reference[0].dn)
1017             current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1018                                     scope=SCOPE_SUBTREE,
1019                                     controls=controls)
1020
1021         delta = samdb.msg_diff(current[0], reference[0])
1022
1023         for att in backlinked:
1024             delta.remove(att)
1025
1026         delta.remove("name")
1027
1028         if len(delta.items()) > 1 and usns is not None:
1029             # Fetch the replPropertyMetaData
1030             res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
1031                                 scope=SCOPE_SUBTREE, controls=controls,
1032                                 attrs=["replPropertyMetaData"])
1033             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1034                                 str(res[0]["replPropertyMetaData"])).ctr
1035
1036             hash_attr_usn = {}
1037             for o in ctr.array:
1038                 # We put in this hash only modification
1039                 # made on the current host
1040                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
1041                 if str(o.originating_invocation_id) in usns.keys():
1042                     hash_attr_usn[att] = [o.originating_usn, str(o.originating_invocation_id)]
1043                 else:
1044                     hash_attr_usn[att] = [-1, None]
1045
1046         if usns is not None:
1047             delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
1048                                                     current, hash_attr_usn,
1049                                                     basedn, usns, samdb)
1050         else:
1051             for att in hashAttrNotCopied.keys():
1052                 delta.remove(att)
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 hashAttrNotCopied associative 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)