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