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