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