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