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