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