s4 upgradeprovision: move some functions to upgradehelpers for unit tests
[sfrench/samba-autobuild/.git] / source4 / scripting / bin / upgradeprovision
1 #!/usr/bin/env python
2 # vim: expandtab
3 #
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
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 samba
37 import samba.getopt as options
38 from samba.credentials import DONT_USE_KERBEROS
39 from samba.auth import system_session, admin_session
40 from samba import Ldb, version
41 from ldb import SCOPE_ONELEVEL, SCOPE_SUBTREE, SCOPE_BASE,\
42                 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,\
43                 MessageElement, Message, Dn
44 from samba import param
45 from samba.misc import messageEltFlagToString
46 from samba.provision import find_setup_dir, get_domain_descriptor,\
47                             get_config_descriptor, secretsdb_self_join,\
48                             set_gpo_acl, getpolicypath,create_gpo_struct,\
49                             ProvisioningError, getLastProvisionUSN,\
50                             get_max_usn, updateProvisionUSN
51 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import security, drsblobs
53 from samba.ndr import ndr_unpack
54 from samba.dcerpc.misc import SEC_CHAN_BDC
55 from samba.upgradehelpers import dn_sort, get_paths, newprovision,\
56                                  find_provision_key_parameters, get_ldbs,\
57                                  usn_in_range, identic_rename, get_diff_sddls
58
59 replace=2**FLAG_MOD_REPLACE
60 add=2**FLAG_MOD_ADD
61 delete=2**FLAG_MOD_DELETE
62 never=0
63
64
65 # Will be modified during provision to tell if default sd has been modified
66 # somehow ...
67
68 #Errors are always logged
69 ERROR =     -1
70 SIMPLE =     0x00
71 CHANGE =     0x01
72 CHANGESD =     0x02
73 GUESS =     0x04
74 PROVISION =    0x08
75 CHANGEALL =    0xff
76
77 __docformat__ = "restructuredText"
78
79 # Attributes that are never copied from the reference provision (even if they
80 # do not exist in the destination object).
81 # This is most probably because they are populated automatcally when object is
82 # created
83 # This also apply to imported object from reference provision
84 hashAttrNotCopied = {   "dn": 1, "whenCreated": 1, "whenChanged": 1,
85                         "objectGUID": 1, "uSNCreated": 1,
86                         "replPropertyMetaData": 1, "uSNChanged": 1,
87                         "parentGUID": 1, "objectCategory": 1,
88                         "distinguishedName": 1, "nTMixedDomain": 1,
89                         "showInAdvancedViewOnly": 1, "instanceType": 1,
90                         "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
91                         "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
92                         "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
93                         "supplementalCredentials":1, "gPCUserExtensionNames":1,
94                         "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
95                         "possibleInferiors":1, "privilege":1,
96                         "sAMAccountType":1 }
97
98 # Usually for an object that already exists we do not overwrite attributes as
99 # they might have been changed for good reasons. Anyway for a few of them it's
100 # mandatory to replace them otherwise the provision will be broken somehow.
101 # But for attribute that are just missing we do not have to specify them as the default
102 # behavior is to add missing attribute
103 hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
104                         "systemOnly":replace, "searchFlags":replace,
105                         "mayContain":replace, "systemFlags":replace+add,
106                         "description":replace, "operatingSystemVersion":replace,
107                         "adminPropertyPages":replace, "groupType":replace,
108                         "wellKnownObjects":replace, "privilege":never,
109                         "defaultSecurityDescriptor": replace,
110                         "rIDAvailablePool": never,
111                         "defaultSecurityDescriptor": replace + add }
112
113
114 backlinked = []
115 forwardlinked = {}
116 dn_syntax_att = []
117 def define_what_to_log(opts):
118     what = 0
119     if opts.debugchange:
120         what = what | CHANGE
121     if opts.debugchangesd:
122         what = what | CHANGESD
123     if opts.debugguess:
124         what = what | GUESS
125     if opts.debugprovision:
126         what = what | PROVISION
127     if opts.debugall:
128         what = what | CHANGEALL
129     return what
130
131
132 parser = optparse.OptionParser("provision [options]")
133 sambaopts = options.SambaOptions(parser)
134 parser.add_option_group(sambaopts)
135 parser.add_option_group(options.VersionOptions(parser))
136 credopts = options.CredentialsOptions(parser)
137 parser.add_option_group(credopts)
138 parser.add_option("--setupdir", type="string", metavar="DIR",
139                   help="directory with setup files")
140 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
141 parser.add_option("--debugguess", action="store_true",
142                   help="Print information on what is different but won't be changed")
143 parser.add_option("--debugchange", action="store_true",
144                   help="Print information on what is different but won't be changed")
145 parser.add_option("--debugchangesd", action="store_true",
146                   help="Print information security descriptors differences")
147 parser.add_option("--debugall", action="store_true",
148                   help="Print all available information (very verbose)")
149 parser.add_option("--full", action="store_true",
150                   help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
151
152 opts = parser.parse_args()[0]
153
154 handler = logging.StreamHandler(sys.stdout)
155 upgrade_logger = logging.getLogger("upgradeprovision")
156 upgrade_logger.addHandler(handler)
157
158 provision_logger = logging.getLogger("provision")
159 provision_logger.addHandler(handler)
160
161 whatToLog = define_what_to_log(opts)
162
163 def message(what, text):
164     """Print a message if this message type has been selected to be printed
165
166     :param what: Category of the message
167     :param text: Message to print """
168     if (whatToLog & what) or what <= 0:
169         upgrade_logger.info("%s", text)
170
171 if len(sys.argv) == 1:
172     opts.interactive = True
173 lp = sambaopts.get_loadparm()
174 smbconf = lp.configfile
175
176 creds = credopts.get_credentials(lp)
177 creds.set_kerberos_state(DONT_USE_KERBEROS)
178 setup_dir = opts.setupdir
179 if setup_dir is None:
180     setup_dir = find_setup_dir()
181
182
183
184 def check_for_DNS(refprivate, private):
185     """Check if the provision has already the requirement for dynamic dns
186
187     :param refprivate: The path to the private directory of the reference
188                        provision
189     :param private: The path to the private directory of the upgraded
190                     provision"""
191
192     spnfile = "%s/spn_update_list" % private
193     namedfile = lp.get("dnsupdate:path")
194
195     if not namedfile:
196        namedfile = "%s/named.conf.update" % private
197
198     if not os.path.exists(spnfile):
199         shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
200
201     destdir = "%s/new_dns" % private
202     dnsdir = "%s/dns" % private
203
204     if not os.path.exists(namedfile):
205         if not os.path.exists(destdir):
206             os.mkdir(destdir)
207         if not os.path.exists(dnsdir):
208             os.mkdir(dnsdir)
209         shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
210         shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
211         message(SIMPLE, "It seems that you provision didn't integrate new rules "
212                 "for dynamic dns update of domain related entries")
213         message(SIMPLE, "A copy of the new bind configuration files and "
214                 "template as been put in %s, you should read them and configure dynamic "
215                 " dns update" % destdir)
216
217
218 def populate_links(samdb, schemadn):
219     """Populate an array with all the back linked attributes
220
221     This attributes that are modified automaticaly when
222     front attibutes are changed
223
224     :param samdb: A LDB object for sam.ldb file
225     :param schemadn: DN of the schema for the partition"""
226     linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
227     backlinked.extend(linkedAttHash.values())
228     for t in linkedAttHash.keys():
229         forwardlinked[t] = 1
230
231 def populate_dnsyntax(samdb, schemadn):
232     """Populate an array with all the attributes that have DN synthax
233        (oid 2.5.5.1)
234
235     :param samdb: A LDB object for sam.ldb file
236     :param schemadn: DN of the schema for the partition"""
237     res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
238                         str(schemadn)), scope=SCOPE_SUBTREE,
239                         attrs=["lDAPDisplayName"])
240     for elem in res:
241         dn_syntax_att.append(elem["lDAPDisplayName"])
242
243
244 def sanitychecks(samdb, names):
245     """Make some checks before trying to update
246
247     :param samdb: An LDB object opened on sam.ldb
248     :param names: list of key provision parameters
249     :return: Status of check (1 for Ok, 0 for not Ok) """
250     res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
251                          scope=SCOPE_SUBTREE, attrs=["dn"],
252                          controls=["search_options:1:2"])
253     if len(res) == 0:
254         print "No DC found, your provision is most probably hardly broken !"
255         return False
256     elif len(res) != 1:
257         print "Found %d domain controllers, for the moment upgradeprovision" \
258               "is not able to handle upgrade on domain with more than one DC, please demote" \
259               " the other(s) DC(s) before upgrading" % len(res)
260         return False
261     else:
262         return True
263
264
265 def print_provision_key_parameters(names):
266     """Do a a pretty print of provision parameters
267
268     :param names: list of key provision parameters """
269     message(GUESS, "rootdn      :" + str(names.rootdn))
270     message(GUESS, "configdn    :" + str(names.configdn))
271     message(GUESS, "schemadn    :" + str(names.schemadn))
272     message(GUESS, "serverdn    :" + str(names.serverdn))
273     message(GUESS, "netbiosname :" + names.netbiosname)
274     message(GUESS, "defaultsite :" + names.sitename)
275     message(GUESS, "dnsdomain   :" + names.dnsdomain)
276     message(GUESS, "hostname    :" + names.hostname)
277     message(GUESS, "domain      :" + names.domain)
278     message(GUESS, "realm       :" + names.realm)
279     message(GUESS, "invocationid:" + names.invocation)
280     message(GUESS, "policyguid  :" + names.policyid)
281     message(GUESS, "policyguiddc:" + str(names.policyid_dc))
282     message(GUESS, "domainsid   :" + str(names.domainsid))
283     message(GUESS, "domainguid  :" + names.domainguid)
284     message(GUESS, "ntdsguid    :" + names.ntdsguid)
285     message(GUESS, "domainlevel :" + str(names.domainlevel))
286
287
288 def handle_special_case(att, delta, new, old, usn):
289     """Define more complicate update rules for some attributes
290
291     :param att: The attribute to be updated
292     :param delta: A messageElement object that correspond to the difference
293                   between the updated object and the reference one
294     :param new: The reference object
295     :param old: The Updated object
296     :param usn: The highest usn modified by a previous (upgrade)provision
297     :return: True to indicate that the attribute should be kept, False for
298              discarding it"""
299
300     flag = delta.get(att).flags()
301     # We do most of the special case handle if we do not have the
302     # highest usn as otherwise the replPropertyMetaData will guide us more
303     # correctly
304     if usn == None:
305         if (att == "member" and flag == FLAG_MOD_REPLACE):
306             hash = {}
307             newval = []
308             changeDelta=0
309             for elem in old[0][att]:
310                 hash[str(elem)]=1
311                 newval.append(str(elem))
312
313             for elem in new[0][att]:
314                 if not hash.has_key(str(elem)):
315                     changeDelta=1
316                     newval.append(str(elem))
317             if changeDelta == 1:
318                 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
319             else:
320                 delta.remove(att)
321             return True
322
323         if (att == "gPLink" or att == "gPCFileSysPath") and \
324             flag == FLAG_MOD_REPLACE and\
325             str(new[0].dn).lower() == str(old[0].dn).lower():
326             delta.remove(att)
327             return True
328
329         if att == "forceLogoff":
330             ref=0x8000000000000000
331             oldval=int(old[0][att][0])
332             newval=int(new[0][att][0])
333             ref == old and ref == abs(new)
334             return True
335
336         if (att == "adminDisplayName" or att == "adminDescription"):
337             return True
338
339         if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (str(names.schemadn))\
340             and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
341             return True
342
343         if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
344                 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
345             return True
346
347         if (str(old[0].dn) == "%s" % (str(names.rootdn))
348                 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
349             return True
350
351         if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
352             return True
353
354     # This is a bit of special animal as we might have added
355     # already SPN entries to the list that has to be modified
356     # So we go in detail to try to find out what has to be added ...
357     if ( att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
358         hash = {}
359         newval = []
360         changeDelta=0
361         for elem in old[0][att]:
362             hash[str(elem)]=1
363             newval.append(str(elem))
364
365         for elem in new[0][att]:
366             if not hash.has_key(str(elem)):
367                 changeDelta=1
368                 newval.append(str(elem))
369         if changeDelta == 1:
370             delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
371         else:
372             delta.remove(att)
373         return True
374
375     return False
376
377 def update_secrets(newsecrets_ldb, secrets_ldb):
378     """Update secrets.ldb
379
380     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
381                             of the reference provision
382     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
383                             of the updated provision"""
384
385     message(SIMPLE, "update secrets.ldb")
386     reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
387                                         scope=SCOPE_SUBTREE)
388     current = secrets_ldb.search(expression="dn=@MODULES", base="",
389                                         scope=SCOPE_SUBTREE)
390     delta = secrets_ldb.msg_diff(current[0], reference[0])
391     delta.dn = current[0].dn
392     secrets_ldb.modify(delta)
393
394     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
395                                         scope=SCOPE_SUBTREE, attrs=["dn"])
396     current = secrets_ldb.search(expression="objectClass=top", base="",
397                                         scope=SCOPE_SUBTREE, attrs=["dn"])
398     hash_new = {}
399     hash = {}
400     listMissing = []
401     listPresent = []
402
403     empty = Message()
404     for i in range(0, len(reference)):
405         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
406
407     # Create a hash for speeding the search of existing object in the
408     # current provision
409     for i in range(0, len(current)):
410         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
411
412     for k in hash_new.keys():
413         if not hash.has_key(k):
414             listMissing.append(hash_new[k])
415         else:
416             listPresent.append(hash_new[k])
417
418     for entry in listMissing:
419         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
420         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
421         delta = secrets_ldb.msg_diff(empty,reference[0])
422         for att in hashAttrNotCopied.keys():
423             delta.remove(att)
424         message(CHANGE, "Entry %s is missing from secrets.ldb"%reference[0].dn)
425         for att in delta:
426             message(CHANGE, " Adding attribute %s"%att)
427         delta.dn = reference[0].dn
428         secrets_ldb.add(delta)
429
430     for entry in listPresent:
431         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
432         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
433         delta = secrets_ldb.msg_diff(current[0],reference[0])
434         for att in hashAttrNotCopied.keys():
435             delta.remove(att)
436         for att in delta:
437             if att == "name":
438                 message(CHANGE, "Found attribute name on  %s, must rename the DN "%(current[0].dn))
439                 identic_rename(secrets_ldb,reference[0].dn)
440             else:
441                 delta.remove(att)
442
443     for entry in listPresent:
444         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
445         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
446         delta = secrets_ldb.msg_diff(current[0],reference[0])
447         for att in hashAttrNotCopied.keys():
448             delta.remove(att)
449         for att in delta:
450             if att != "dn":
451                 message(CHANGE, " Adding/Changing attribute %s to %s"%(att,current[0].dn))
452
453         delta.dn = current[0].dn
454         secrets_ldb.modify(delta)
455
456
457 def dump_denied_change(dn, att, flagtxt, current, reference):
458     """Print detailed information about why a changed is denied
459
460     :param dn: DN of the object which attribute is denied
461     :param att: Attribute that was supposed to be upgraded
462     :param flagtxt: Type of the update that should be performed
463                     (add, change, remove, ...)
464     :param current: Value(s) of the current attribute
465     :param reference: Value(s) of the reference attribute"""
466
467     message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
468                 +" is not allowed to be changed/removed, I discard this change")
469     if att != "objectSid" :
470         i = 0
471         for e in range(0, len(current)):
472             message(CHANGE, "old %d : %s" % (i, str(current[e])))
473             i+=1
474         if reference != None:
475             i = 0
476             for e in range(0, len(reference)):
477                 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
478                 i+=1
479     else:
480         message(CHANGE, "old : %s" % str(ndr_unpack( security.dom_sid, current[0])))
481         message(CHANGE, "new : %s" % str(ndr_unpack( security.dom_sid, reference[0])))
482
483
484 def handle_special_add(samdb, dn, names):
485     """Handle special operation (like remove) on some object needed during
486        upgrade
487
488     This is mostly due to wrong creation of the object in previous provision.
489     :param samdb: An Ldb object representing the SAM database
490     :param dn: DN of the object to inspect
491     :param names: list of key provision parameters"""
492
493     dntoremove = None
494     if str(dn).lower() == ("CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn).lower():
495         #This entry was misplaced lets remove it if it exists
496         dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
497
498     objname = "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn
499     if str(dn).lower() == objname.lower():
500         #This entry was misplaced lets remove it if it exists
501         dntoremove = "CN=Certificate Service DCOM Access,"\
502                      "CN=Users, %s" % names.rootdn
503
504     objname = "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn
505     if str(dn).lower() == objname.lower():
506         #This entry was misplaced lets remove it if it exists
507         dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
508
509     objname = "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn
510     if str(dn).lower() == objname.lower():
511         #This entry was misplaced lets remove it if it exists
512         dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
513
514     if dntoremove != None:
515         res = samdb.search(expression="(dn=%s)" % dntoremove,
516                             base=str(names.rootdn),
517                             scope=SCOPE_SUBTREE, attrs=["dn"],
518                             controls=["search_options:1:2"])
519         if len(res) > 0:
520             message(CHANGE, "Existing object %s must be replaced by %s,"\
521                             "removing old object" % (dntoremove, str(dn)))
522             samdb.delete(res[0]["dn"])
523
524
525 def check_dn_nottobecreated(hash, index, listdn):
526     """Check if one of the DN present in the list has a creation order
527        greater than the current.
528
529     Hash is indexed by dn to be created, with each key
530     is associated the creation order.
531
532     First dn to be created has the creation order 0, second has 1, ...
533     Index contain the current creation order
534
535     :param hash: Hash holding the different DN of the object to be
536                   created as key
537     :param index: Current creation order
538     :param listdn: List of DNs on which the current DN depends on
539     :return: None if the current object do not depend on other
540               object or if all object have been created before."""
541     if listdn == None:
542         return None
543     for dn in listdn:
544         key = str(dn).lower()
545         if hash.has_key(key) and hash[key] > index:
546             return str(dn)
547     return None
548
549
550
551 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
552     """Add a new object if the dependencies are satisfied
553
554     The function add the object if the object on which it depends are already
555     created
556
557     :param ref_samdb: Ldb object representing the SAM db of the reference
558                        provision
559     :param samdb: Ldb object representing the SAM db of the upgraded
560                    provision
561     :param dn: DN of the object to be added
562     :param names: List of key provision parameters
563     :param basedn: DN of the partition to be updated
564     :param hash: Hash holding the different DN of the object to be
565                   created as key
566     :param index: Current creation order
567     :return: True if the object was created False otherwise"""
568
569     handle_special_add(samdb, dn, names)
570     reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
571                     scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
572     empty = Message()
573     delta = samdb.msg_diff(empty, reference[0])
574     delta.dn
575     for att in hashAttrNotCopied.keys():
576         delta.remove(att)
577     for att in backlinked:
578         delta.remove(att)
579     depend_on_yettobecreated = None
580     for att in dn_syntax_att:
581         depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
582                                                             delta.get(str(att)))
583         if depend_on_yet_tobecreated != None:
584             message(CHANGE, "Object %s depends on %s in attribute %s," \
585                             "delaying the creation" % (str(dn), \
586                                       depend_on_yet_tobecreated, str(att)))
587             return False
588
589     delta.dn = dn
590     message(CHANGE,"Object %s will be added" % dn)
591     samdb.add(delta, ["relax:0"])
592     return True
593
594 def gen_dn_index_hash(listMissing):
595     """Generate a hash associating the DN to its creation order
596
597     :param listMissing: List of DN
598     :return: Hash with DN as keys and creation order as values"""
599     hash = {}
600     for i in range(0, len(listMissing)):
601         hash[str(listMissing[i]).lower()] = i
602     return hash
603
604 def add_deletedobj_containers(ref_samdb, samdb, names):
605     """Add the object containter: CN=Deleted Objects
606
607     This function create the container for each partition that need one and
608     then reference the object into the root of the partition
609
610     :param ref_samdb: Ldb object representing the SAM db of the reference
611                        provision
612     :param samdb: Ldb object representing the SAM db of the upgraded provision
613     :param names: List of key provision parameters"""
614
615
616     wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
617     partitions = [str(names.rootdn), str(names.configdn)]
618     for part in partitions:
619         ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
620                                             base=part, scope=SCOPE_SUBTREE,
621                                             attrs=["dn"],
622                                             controls=["show_deleted:0"])
623         delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
624                                     base=part, scope=SCOPE_SUBTREE,
625                                     attrs=["dn"],
626                                     controls=["show_deleted:0"])
627         if len(ref_delObjCnt) > len(delObjCnt):
628             reference = ref_samdb.search(expression="cn=Deleted Objects",
629                                             base=part, scope=SCOPE_SUBTREE,
630                                             controls=["show_deleted:0"])
631             empty = Message()
632             delta = samdb.msg_diff(empty, reference[0])
633
634             delta.dn = Dn(samdb, str(reference[0]["dn"]))
635             for att in hashAttrNotCopied.keys():
636                 delta.remove(att)
637             samdb.add(delta)
638
639             listwko = []
640             res = samdb.search(expression="(objectClass=*)", base=part,
641                                scope=SCOPE_BASE,
642                                attrs=["dn", "wellKnownObjects"])
643
644             targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
645             found = 0
646
647             if len(res[0]) > 0:
648                 wko = res[0]["wellKnownObjects"]
649
650                 # The wellKnownObject that we want to add.
651                 for o in wko:
652                     if str(o) == targetWKO:
653                         found = 1
654                     listwko.append(str(o))
655
656             if not found:
657                 listwko.append(targetWKO)
658
659                 delta = Message()
660                 delta.dn = Dn(samdb, str(res[0]["dn"]))
661                 delta["wellKnownObjects"] = MessageElement(listwko,
662                                                 FLAG_MOD_REPLACE,
663                                                 "wellKnownObjects" )
664                 samdb.modify(delta)
665
666 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
667     """Add the missing object whose DN is the list
668
669     The function add the object if the objects on which it depends are
670     already created.
671
672     :param ref_samdb: Ldb object representing the SAM db of the reference
673                       provision
674     :param samdb: Ldb object representing the SAM db of the upgraded
675                   provision
676     :param dn: DN of the object to be added
677     :param names: List of key provision parameters
678     :param basedn: DN of the partition to be updated
679     :param list: List of DN to be added in the upgraded provision"""
680
681     listMissing = []
682     listDefered = list
683
684     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
685         index = 0
686         listMissing = listDefered
687         listDefered = []
688         hashMissing = gen_dn_index_hash(listMissing)
689         for dn in listMissing:
690             ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
691                                         hashMissing, index)
692             index = index + 1
693             if ret == 0:
694                 # DN can't be created because it depends on some
695                 # other DN in the list
696                 listDefered.append(dn)
697     if len(listDefered) != 0:
698         raise ProvisioningError("Unable to insert missing elements:" \
699                                 "circular references")
700
701 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
702     """This function handle updates on links
703
704     :param samdb: An LDB object pointing to the updated provision
705     :param att: Attribute to update
706     :param basedn: The root DN of the provision
707     :param dn: The DN of the inspected object
708     :param value: The value of the attribute
709     :param ref_value: The value of this attribute in the reference provision
710     :param delta: The MessageElement object that will be applied for
711                    transforming the current provision"""
712
713     res = samdb.search(expression="dn=%s" % dn, base=basedn,
714                         controls=["search_options:1:2", "reveal:1"],
715                         attrs=[att])
716
717     blacklist = {}
718     hash = {}
719     newlinklist = []
720     changed = 0
721
722     newlinklist.extend(value)
723
724     for e in value:
725         hash[e] = 1
726     # for w2k domain level the reveal won't reveal anything ...
727     # it means that we can readd links that were removed on purpose ...
728     # Also this function in fact just accept add not removal
729
730     for e in res[0][att]:
731         if not hash.has_key(e):
732             # We put in the blacklist all the element that are in the "revealed"
733             # result and not in the "standard" result
734             # This element are links that were removed before and so that
735             # we don't wan't to readd
736             blacklist[e] = 1
737
738     for e in ref_value:
739         if not blacklist.has_key(e) and not hash.has_key(e):
740             newlinklist.append(str(e))
741             changed = 1
742     if changed:
743         delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
744     else:
745         delta.remove(att)
746
747 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
748     """ This function updates the object that are already present in the
749         provision
750
751     :param ref_samdb: An LDB object pointing to the reference provision
752     :param samdb: An LDB object pointing to the updated provision
753     :param basedn: A string with the value of the base DN for the provision
754                    (ie. DC=foo, DC=bar)
755     :param listPresent: A list of object that is present in the provision
756     :param usns: A list of USN range modified by previous provision and
757                  upgradeprovision
758     :param invocationid: The value of the invocationid for the current DC"""
759
760     global defSDmodified
761     # This hash is meant to speedup lookup of attribute name from an oid,
762     # it's for the replPropertyMetaData handling
763     hash_oid_name = {}
764     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
765                         controls=["search_options:1:2"], attrs=["attributeID",
766                         "lDAPDisplayName"])
767     if len(res) > 0:
768         for e in res:
769             strDisplay = str(e.get("lDAPDisplayName"))
770             hash_oid_name[str(e.get("attributeID"))] = strDisplay
771     else:
772         msg = "Unable to insert missing elements: circular references"
773         raise ProvisioningError(msg)
774
775     changed = 0
776     controls = ["search_options:1:2", "sd_flags:1:2"]
777     for dn in listPresent:
778         reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
779                                         scope=SCOPE_SUBTREE,
780                                         controls=controls)
781         current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
782                                 scope=SCOPE_SUBTREE, controls=controls)
783
784         if (
785              (str(current[0].dn) != str(reference[0].dn)) and
786              (str(current[0].dn).upper() == str(reference[0].dn).upper())
787            ):
788             message(CHANGE, "Name are the same but case change,"\
789                             "let's rename %s to %s" % (str(current[0].dn),
790                                                        str(reference[0].dn)))
791             identic_rename(samdb, reference[0].dn)
792             current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
793                                     scope=SCOPE_SUBTREE,
794                                     controls=["search_options:1:2"])
795
796         delta = samdb.msg_diff(current[0], reference[0])
797
798         for att in hashAttrNotCopied.keys():
799             delta.remove(att)
800
801         for att in backlinked:
802             delta.remove(att)
803
804         delta.remove("name")
805
806         if len(delta.items()) > 1 and usns != None:
807             # Fetch the replPropertyMetaData
808             res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
809                                 scope=SCOPE_SUBTREE, controls=controls,
810                                 attrs=["replPropertyMetaData"])
811             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
812                                 str(res[0]["replPropertyMetaData"])).ctr
813
814             hash_attr_usn = {}
815             for o in ctr.array:
816                 # We put in this hash only modification
817                 # made on the current host
818                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
819                 if str(o.originating_invocation_id) == str(invocationid):
820                     hash_attr_usn[att] = o.originating_usn
821                 else:
822                     hash_attr_usn[att] = -1
823
824         isFirst = 0
825         txt = ""
826         for att in delta:
827             if usns != None:
828                 if forwardlinked.has_key(att):
829                     handle_links(samdb, att, basedn, current[0]["dn"],
830                                     current[0][att], reference[0][att], delta)
831
832                 if isFirst == 0 and len(delta.items())>1:
833                     isFirst = 1
834                     txt = "%s\n" % (str(dn))
835                 if att == "dn":
836                     continue
837                 if att == "rIDAvailablePool":
838                     delta.remove(att)
839                     continue
840                 if att == "objectSid":
841                     delta.remove(att)
842                     continue
843                 if att == "creationTime":
844                     delta.remove(att)
845                     continue
846                 if att == "oEMInformation":
847                     delta.remove(att)
848                     continue
849                 if att == "msDs-KeyVersionNumber":
850                     delta.remove(att)
851                     continue
852                 if handle_special_case(att, delta, reference, current, usns):
853                     # This attribute is "complicated" to handle and handling
854                     # was done in handle_special_case
855                     continue
856                 attrUSN = hash_attr_usn.get(att)
857                 if att == "forceLogoff" and attrUSN == None:
858                     continue
859                 if  attrUSN == None:
860                     delta.remove(att)
861                     continue
862
863                 if attrUSN == -1:
864                     # This attribute was last modified by another DC forget
865                     # about it
866                     message(CHANGE, "%sAttribute: %s has been" \
867                             "created/modified/deleted  by another DC,"
868                             " do nothing" % (txt, att ))
869                     txt = ""
870                     delta.remove(att)
871                     continue
872                 elif usn_in_range(int(attrUSN), usns) == 0:
873                     message(CHANGE, "%sAttribute: %s has been" \
874                                     "created/modified/deleted not during a" \
875                                     " provision or upgradeprovision: current" \
876                                     " usn %d , do nothing" % (txt, att, attrUSN))
877                     txt = ""
878                     delta.remove(att)
879                     continue
880                 else:
881                     if att == "defaultSecurityDescriptor":
882                         defSDmodified = 1
883                     if attrUSN:
884                         message(CHANGE, "%sAttribute: %s will be modified" \
885                                         "/deleted it was last modified" \
886                                         "during a provision, current usn:" \
887                                         "%d" % (txt, att,  attrUSN))
888                         txt = ""
889                     else:
890                         message(CHANGE, "%sAttribute: %s will be added because" \
891                                         " it hasn't existed before " % (txt, att))
892                         txt = ""
893                     continue
894
895             else:
896             # Old school way of handling things for pre alpha12 upgrade
897                 defSDmodified = 1
898                 msgElt = delta.get(att)
899
900                 if att == "nTSecurityDescriptor":
901                     delta.remove(att)
902                     continue
903
904                 if att == "dn":
905                     continue
906
907                 if not hashOverwrittenAtt.has_key(att):
908                     if msgElt.flags() != FLAG_MOD_ADD:
909                         if not handle_special_case(att, delta, reference, current,
910                                                     usns):
911                             if opts.debugchange or opts.debugall:
912                                 try:
913                                     dump_denied_change(dn, att,
914                                         messageEltFlagToString(msgElt.flags()),
915                                         current[0][att], reference[0][att])
916                                 except KeyError:
917                                     dump_denied_change(dn, att,
918                                         messageEltFlagToString(msgElt.flags()),
919                                         current[0][att], None)
920                             delta.remove(att)
921                         continue
922                 else:
923                     if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
924                         continue
925                     elif  hashOverwrittenAtt.get(att)==never:
926                         delta.remove(att)
927                         continue
928
929         delta.dn = dn
930         if len(delta.items()) >1:
931             attributes=", ".join(delta.keys())
932             message(CHANGE, "%s is different from the reference one, changed" \
933                             " attributes: %s\n" % (dn, attributes))
934             changed = changed + 1
935             samdb.modify(delta)
936     return changed
937
938 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
939     """Check differences between the reference provision and the upgraded one.
940
941     It looks for all objects which base DN is name.
942
943     This function will also add the missing object and update existing object
944     to add or remove attributes that were missing.
945
946     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
947                        reference provision
948     :param samdb: An LDB object connected to the sam.ldb of the update
949                   provision
950     :param basedn: String value of the DN of the partition
951     :param names: List of key provision parameters
952     :param schema: A Schema object
953     :param provisionUSNs:  The USNs modified by provision/upgradeprovision
954                            last time"""
955
956     hash_new = {}
957     hash = {}
958     listMissing = []
959     listPresent = []
960     reference = []
961     current = []
962
963     # Connect to the reference provision and get all the attribute in the
964     # partition referred by name
965     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
966                                     scope=SCOPE_SUBTREE, attrs=["dn"],
967                                     controls=["search_options:1:2"])
968
969     current = samdb.search(expression="objectClass=*", base=basedn,
970                                 scope=SCOPE_SUBTREE, attrs=["dn"],
971                                 controls=["search_options:1:2"])
972     # Create a hash for speeding the search of new object
973     for i in range(0, len(reference)):
974         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
975
976     # Create a hash for speeding the search of existing object in the
977     # current provision
978     for i in range(0, len(current)):
979         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
980
981
982     for k in hash_new.keys():
983         if not hash.has_key(k):
984             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
985                 listMissing.append(hash_new[k])
986         else:
987             listPresent.append(hash_new[k])
988
989     # Sort the missing object in order to have object of the lowest level
990     # first (which can be containers for higher level objects)
991     listMissing.sort(dn_sort)
992     listPresent.sort(dn_sort)
993
994     # The following lines is to load the up to
995     # date schema into our current LDB
996     # a complete schema is needed as the insertion of attributes
997     # and class is done against it
998     # and the schema is self validated
999     samdb.set_schema_from_ldb(schema.ldb)
1000     try:
1001         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1002         add_deletedobj_containers(ref_samdb, samdb, names)
1003
1004         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1005         changed = update_present(ref_samdb, samdb, basedn, listPresent,
1006                                     provisionUSNs, names.invocation)
1007         message(SIMPLE, "There are %d changed objects" % (changed))
1008         return 1
1009
1010     except StandardError, err:
1011         message(ERROR, "Exception during upgrade of samdb:")
1012         (typ, val, tb) = sys.exc_info()
1013         traceback.print_exception(typ, val, tb)
1014         return 0
1015
1016
1017
1018 def check_updated_sd(ref_sam, cur_sam, names):
1019     """Check if the security descriptor in the upgraded provision are the same
1020        as the reference
1021
1022     :param ref_sam: A LDB object connected to the sam.ldb file used as
1023                     the reference provision
1024     :param cur_sam: A LDB object connected to the sam.ldb file used as
1025                     upgraded provision
1026     :param names: List of key provision parameters"""
1027     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1028                                 scope=SCOPE_SUBTREE,
1029                                 attrs=["dn", "nTSecurityDescriptor"],
1030                                 controls=["search_options:1:2"])
1031     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1032                                 scope=SCOPE_SUBTREE,
1033                                 attrs=["dn", "nTSecurityDescriptor"],
1034                                 controls=["search_options:1:2"])
1035     hash = {}
1036     for i in range(0, len(reference)):
1037         refsd = ndr_unpack(security.descriptor,
1038                     str(reference[i]["nTSecurityDescriptor"]))
1039         hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1040
1041     for i in range(0, len(current)):
1042         key = str(current[i]["dn"]).lower()
1043         if hash.has_key(key):
1044             cursd = ndr_unpack(security.descriptor,
1045                         str(current[i]["nTSecurityDescriptor"]))
1046             sddl = cursd.as_sddl(names.domainsid)
1047             if sddl != hash[key]:
1048                 txt = get_diff_sddls(hash[key], sddl)
1049                 if txt != "":
1050                     message(CHANGESD, "On object %s ACL is different"\
1051                                       " \n%s" % (current[i]["dn"], txt))
1052
1053
1054
1055 def fix_partition_sd(samdb, names):
1056     """This function fix the SD for partition containers (basedn, configdn, ...)
1057     This is needed because some provision use to have broken SD on containers
1058
1059     :param samdb: An LDB object pointing to the sam of the current provision
1060     :param names: A list of key provision parameters
1061     """
1062     # First update the SD for the rootdn
1063     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1064                          scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1065                          controls=["search_options:1:2"])
1066     delta = Message()
1067     delta.dn = Dn(samdb, str(res[0]["dn"]))
1068     descr = get_domain_descriptor(names.domainsid)
1069     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1070                                                     "nTSecurityDescriptor")
1071     samdb.modify(delta, ["recalculate_sd:0"])
1072     # Then the config dn
1073     res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1074                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1075                         controls=["search_options:1:2"])
1076     delta = Message()
1077     delta.dn = Dn(samdb, str(res[0]["dn"]))
1078     descr = get_config_descriptor(names.domainsid)
1079     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1080                                                     "nTSecurityDescriptor" )
1081     samdb.modify(delta, ["recalculate_sd:0"])
1082     # Then the schema dn
1083     res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1084                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1085                         controls=["search_options:1:2"])
1086
1087     delta = Message()
1088     delta.dn = Dn(samdb, str(res[0]["dn"]))
1089     descr = get_schema_descriptor(names.domainsid)
1090     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1091                                                     "nTSecurityDescriptor" )
1092     samdb.modify(delta, ["recalculate_sd:0"])
1093
1094 def rebuild_sd(samdb, names):
1095     """Rebuild security descriptor of the current provision from scratch
1096
1097     During the different pre release of samba4 security descriptors (SD)
1098     were notarly broken (up to alpha11 included)
1099     This function allow to get them back in order, this function make the
1100     assumption that nobody has modified manualy an SD
1101     and so SD can be safely recalculated from scratch to get them right.
1102
1103     :param names: List of key provision parameters"""
1104
1105
1106     hash = {}
1107     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1108                         scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1109                         controls=["search_options:1:2"])
1110     for obj in res:
1111         if not (str(obj["dn"]) == str(names.rootdn) or
1112             str(obj["dn"]) == str(names.configdn) or \
1113             str(obj["dn"]) == str(names.schemadn)):
1114             hash[str(obj["dn"])] = obj["whenCreated"]
1115
1116     listkeys = hash.keys()
1117     listkeys.sort(dn_sort)
1118
1119     for key in listkeys:
1120         try:
1121             delta = Message()
1122             delta.dn = Dn(samdb, key)
1123             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1124                                                     "whenCreated" )
1125             samdb.modify(delta, ["recalculate_sd:0"])
1126         except:
1127             # XXX: We should always catch an explicit exception.
1128             # What could go wrong here?
1129             samdb.transaction_cancel()
1130             res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1131                                 scope=SCOPE_SUBTREE,
1132                                 attrs=["dn", "nTSecurityDescriptor"],
1133                                 controls=["search_options:1:2"])
1134             badsd = ndr_unpack(security.descriptor,
1135                         str(res[0]["nTSecurityDescriptor"]))
1136             print "bad stuff %s"%badsd.as_sddl(names.domainsid)
1137             return
1138
1139 def removeProvisionUSN(samdb):
1140         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1141         entry = samdb.search(expression="dn=@PROVISION", base = "",
1142                                 scope=SCOPE_SUBTREE,
1143                                 controls=["search_options:1:2"],
1144                                 attrs=attrs)
1145         empty = Message()
1146         empty.dn = entry[0].dn
1147         delta = samdb.msg_diff(entry[0], empty)
1148         delta.remove("dn")
1149         delta.dn = entry[0].dn
1150         samdb.modify(delta)
1151
1152 def delta_update_basesamdb(refpaths, paths, creds, session, lp):
1153     """Update the provision container db: sam.ldb
1154     This function is aimed for alpha9 and newer;
1155
1156     :param refpaths: An object holding the different importants paths for
1157                      reference provision object
1158     :param paths: An object holding the different importants paths for
1159                   upgraded provision object
1160     :param creds: Credential used for openning LDB files
1161     :param session: Session to use for openning LDB files
1162     :param lp: A loadparam object"""
1163
1164     message(SIMPLE,
1165             "Update base samdb by searching difference with reference one")
1166     refsam = Ldb(refpaths.samdb, session_info=session, credentials=creds,
1167                     lp=lp, options=["modules:"])
1168     sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1169                 options=["modules:"])
1170
1171     empty = Message()
1172
1173     reference = refsam.search(expression="")
1174
1175     for refentry in reference:
1176         entry = sam.search(expression="dn=%s" % refentry["dn"],
1177                             scope=SCOPE_SUBTREE)
1178         if not len(entry):
1179             message(CHANGE, "Adding %s to sam db" % str(delta.dn))
1180             delta = sam.msg_diff(empty, refentry)
1181             if str(refentry.dn) == "@PROVISION" and\
1182                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
1183                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
1184             delta.dn = refentry.dn
1185             sam.add(delta)
1186         else:
1187             delta = sam.msg_diff(entry[0], refentry)
1188             if str(refentry.dn) == "@PROVISION" and\
1189                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
1190                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
1191             if len(delta.items()) > 1:
1192                 delta.dn = refentry.dn
1193                 sam.modify(delta)
1194
1195
1196 def simple_update_basesamdb(newpaths, paths, names):
1197     """Update the provision container db: sam.ldb
1198     This function is aimed at very old provision (before alpha9)
1199
1200     :param newpaths: List of paths for different provision objects
1201                         from the reference provision
1202     :param paths: List of paths for different provision objects
1203                         from the upgraded provision
1204     :param names: List of key provision parameters"""
1205
1206     message(SIMPLE, "Copy samdb")
1207     shutil.copy(newpaths.samdb, paths.samdb)
1208
1209     message(SIMPLE, "Update partitions filename if needed")
1210     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1211     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1212     usersldb = os.path.join(paths.private_dir, "users.ldb")
1213     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1214
1215     if not os.path.isdir(samldbdir):
1216         os.mkdir(samldbdir)
1217         os.chmod(samldbdir, 0700)
1218     if os.path.isfile(schemaldb):
1219         shutil.copy(schemaldb, os.path.join(samldbdir,
1220                                             "%s.ldb"%str(names.schemadn).upper()))
1221         os.remove(schemaldb)
1222     if os.path.isfile(usersldb):
1223         shutil.copy(usersldb, os.path.join(samldbdir,
1224                                             "%s.ldb"%str(names.rootdn).upper()))
1225         os.remove(usersldb)
1226     if os.path.isfile(configldb):
1227         shutil.copy(configldb, os.path.join(samldbdir,
1228                                             "%s.ldb"%str(names.configdn).upper()))
1229         os.remove(configldb)
1230
1231
1232 def update_privilege(ref_private_path, cur_private_path):
1233     """Update the privilege database
1234
1235     :param ref_private_path: Path to the private directory of the reference
1236                              provision.
1237     :param cur_private_path: Path to the private directory of the current
1238                              (and to be updated) provision."""
1239     message(SIMPLE, "Copy privilege")
1240     shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1241                 os.path.join(cur_private_path, "privilege.ldb"))
1242
1243
1244 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1245     """Upgrade the SAM DB contents for all the provision partitions
1246
1247     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1248                        provision
1249     :param samdb: An LDB object connected to the sam.ldb of the update
1250                   provision
1251     :param names: List of key provision parameters
1252     :param highestUSN:  The highest USN modified by provision/upgradeprovision
1253                         last time
1254     :param schema: A Schema object that represent the schema of the provision"""
1255
1256     message(SIMPLE, "Starting update of samdb")
1257     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1258                             schema, highestUSN)
1259     if ret:
1260         message(SIMPLE, "Update of samdb finished")
1261         return 1
1262     else:
1263         message(SIMPLE, "Update failed")
1264         return 0
1265
1266
1267 def update_machine_account_password(samdb, secrets_ldb, names):
1268     """Update (change) the password of the current DC both in the SAM db and in
1269        secret one
1270
1271     :param samdb: An LDB object related to the sam.ldb file of a given provision
1272     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
1273                         provision
1274     :param names: List of key provision parameters"""
1275
1276     message(SIMPLE, "Update machine account")
1277     expression = "samAccountName=%s$" % names.netbiosname
1278     secrets_msg = secrets_ldb.search(expression=expression,
1279                                         attrs=["secureChannelType"])
1280     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
1281         res = samdb.search(expression=expression, attrs=[])
1282         assert(len(res) == 1)
1283
1284         msg = Message(res[0].dn)
1285         machinepass = samba.generate_random_password(128, 255)
1286         msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE,
1287                                                 "userPassword")
1288         samdb.modify(msg)
1289
1290         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
1291                      attrs=["msDs-keyVersionNumber"])
1292         assert(len(res) == 1)
1293         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
1294         secChanType = int(secrets_msg[0]["secureChannelType"][0])
1295
1296         secretsdb_self_join(secrets_ldb, domain=names.domain,
1297                     realm=names.realm or sambaopts._lp.get('realm'),
1298                     domainsid=names.domainsid,
1299                     dnsdomain=names.dnsdomain,
1300                     netbiosname=names.netbiosname,
1301                     machinepass=machinepass,
1302                     key_version_number=kvno,
1303                     secure_channel_type=secChanType)
1304     else:
1305         raise ProvisioningError("Unable to find a Secure Channel" \
1306                                 "of type SEC_CHAN_BDC")
1307
1308
1309 def update_gpo(paths, creds, session, names):
1310     """Create missing GPO file object if needed
1311
1312     Set ACL correctly also.
1313     """
1314     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
1315     if not os.path.isdir(dir):
1316         create_gpo_struct(dir)
1317
1318     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
1319     if not os.path.isdir(dir):
1320         create_gpo_struct(dir)
1321     samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1322     set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
1323         names.domaindn, samdb, lp)
1324
1325
1326 def getOEMInfo(samdb, rootdn):
1327     """Return OEM Information on the top level
1328     Samba4 use to store version info in this field
1329
1330     :param samdb: An LDB object connect to sam.ldb
1331     :param rootdn: Root DN of the domain
1332     :return: The content of the field oEMInformation (if any)"""
1333     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
1334                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1335     if len(res) > 0:
1336         info = res[0]["oEMInformation"]
1337         return info
1338     else:
1339         return ""
1340
1341 def updateOEMInfo(samdb, names):
1342     res = samdb.search(expression="(objectClass=*)", base=str(names.rootdn),
1343                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1344     if len(res) > 0:
1345         info = res[0]["oEMInformation"]
1346         info = "%s, upgrade to %s" % (info, version)
1347         delta = Message()
1348         delta.dn = Dn(samdb, str(res[0]["dn"]))
1349         delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
1350             "oEMInformation" )
1351         samdb.modify(delta)
1352
1353
1354 def setup_path(file):
1355     return os.path.join(setup_dir, file)
1356
1357
1358 if __name__ == '__main__':
1359     global defSDmodified
1360     defSDmodified = 0
1361     # From here start the big steps of the program
1362     # First get files paths
1363     paths = get_paths(param, smbconf=smbconf)
1364     paths.setup = setup_dir
1365     # Get ldbs with the system session, it is needed for searching
1366     # provision parameters
1367     session = system_session()
1368
1369     # This variable will hold the last provision USN once if it exists.
1370     minUSN = 0
1371
1372     ldbs = get_ldbs(paths, creds, session, lp)
1373     ldbs.startTransactions()
1374
1375     # Guess all the needed names (variables in fact) from the current
1376     # provision.
1377     names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, paths,
1378                                             smbconf, lp)
1379     lastProvisionUSNs = getLastProvisionUSN(ldbs.sam)
1380     if lastProvisionUSNs != None:
1381         message(CHANGE,
1382             "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1383
1384     # Objects will be created with the admin session
1385     # (not anymore system session)
1386     adm_session = admin_session(lp, str(names.domainsid))
1387     # So we reget handle on objects
1388     # ldbs = get_ldbs(paths, creds, adm_session, lp)
1389
1390     if not sanitychecks(ldbs.sam, names):
1391         message(SIMPLE, "Sanity checks for the upgrade fails, checks messages" \
1392                         " and correct them before rerunning upgradeprovision")
1393         sys.exit(1)
1394
1395     # Let's see provision parameters
1396     print_provision_key_parameters(names)
1397
1398     # 5) With all this information let's create a fresh new provision used as
1399     # reference
1400     message(SIMPLE, "Creating a reference provision")
1401     provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1402                                     prefix="referenceprovision")
1403     newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1404                     provision_logger)
1405
1406     # TODO
1407     # We need to get a list of object which SD is directly computed from
1408     # defaultSecurityDescriptor.
1409     # This will allow us to know which object we can rebuild the SD in case
1410     # of change of the parent's SD or of the defaultSD.
1411     # Get file paths of this new provision
1412     newpaths = get_paths(param, targetdir=provisiondir)
1413     new_ldbs = get_ldbs(newpaths, creds, session, lp)
1414     new_ldbs.startTransactions()
1415
1416     # Populate some associative array to ease the update process
1417     # List of attribute which are link and backlink
1418     populate_links(new_ldbs.sam, names.schemadn)
1419     # List of attribute with ASN DN synthax)
1420     populate_dnsyntax(new_ldbs.sam, names.schemadn)
1421
1422     update_privilege(newpaths.private_dir, paths.private_dir)
1423     oem = getOEMInfo(ldbs.sam, names.rootdn)
1424     # Do some modification on sam.ldb
1425     ldbs.groupedCommit()
1426     if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1427         # Starting from alpha9 we can consider that the structure is quite ok
1428         # and that we should do only dela
1429         new_ldbs.groupedCommit()
1430         delta_update_basesamdb(newpaths, paths, creds, session, lp)
1431         ldbs.startTransactions()
1432         minUSN = get_max_usn(ldbs.sam, str(names.rootdn)) + 1
1433         new_ldbs.startTransactions()
1434     else:
1435         simple_update_basesamdb(newpaths, paths, names)
1436         ldbs = get_ldbs(paths, creds, session, lp)
1437         ldbs.startTransactions()
1438         removeProvisionUSN(ldbs.sam)
1439
1440     schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1441                     serverdn=str(names.serverdn))
1442
1443     if opts.full:
1444         if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1445                             schema):
1446             message(SIMPLE, "Rollbacking every changes. Check the reason" \
1447                             " of the problem")
1448             message(SIMPLE, "In any case your system as it was before" \
1449                             " the upgrade")
1450             ldbs.groupedRollback()
1451             new_ldbs.groupedRollback()
1452             shutil.rmtree(provisiondir)
1453             sys.exit(1)
1454
1455     update_secrets(new_ldbs.secrets, ldbs.secrets)
1456     update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1457
1458     # SD should be created with admin but as some previous acl were so wrong
1459     # that admin can't modify them we have first to recreate them with the good
1460     # form but with system account and then give the ownership to admin ...
1461     if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1462         message(SIMPLE, "Fixing old povision SD")
1463         fix_partition_sd(ldbs.sam, names)
1464         rebuild_sd(ldbs.sam, names)
1465
1466     # We calculate the max USN before recalculating the SD because we might
1467     # touch object that have been modified after a provision and we do not
1468     # want that the next upgradeprovision thinks that it has a green light
1469     # to modify them
1470
1471     maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1472
1473     # We rebuild SD only if defaultSecurityDescriptor is modified
1474     # But in fact we should do it also if one object has its SD modified as
1475     # child might need rebuild
1476     if defSDmodified == 1:
1477         message(SIMPLE, "Updating SD")
1478         ldbs.sam.set_session_info(adm_session)
1479         # Alpha10 was a bit broken still
1480         if re.match(r'.*alpha(\d|10)', str(oem)):
1481             fix_partition_sd(ldbs.sam, names)
1482         rebuild_sd(ldbs.sam, names)
1483
1484     # Now we are quite confident in the recalculate process of the SD, we make
1485     # it optional.
1486     # Also the check must be done in a clever way as for the moment we just
1487     # compare SDDL
1488     if opts.debugchangesd:
1489         check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1490
1491     updateOEMInfo(ldbs.sam, names)
1492     check_for_DNS(newpaths.private_dir, paths.private_dir)
1493     if lastProvisionUSNs != None:
1494         updateProvisionUSN(ldbs.sam, minUSN, maxUSN)
1495     ldbs.groupedCommit()
1496     new_ldbs.groupedCommit()
1497     message(SIMPLE, "Upgrade finished !")
1498     # remove reference provision now that everything is done !
1499     shutil.rmtree(provisiondir)