s4 upgradeprovision: Add documentation on the update process
[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                 # Note we could just use 1 here
821                     hash_attr_usn[att] = o.originating_usn
822                 else:
823                     hash_attr_usn[att] = -1
824
825         isFirst = 0
826         txt = ""
827
828         for att in delta:
829             if usns != None:
830                 # We have updated by provision usn information so let's exploit
831                 # replMetadataProperties
832                 if forwardlinked.has_key(att):
833                     handle_links(samdb, att, basedn, current[0]["dn"],
834                                     current[0][att], reference[0][att], delta)
835
836                 if isFirst == 0 and len(delta.items())>1:
837                     isFirst = 1
838                     txt = "%s\n" % (str(dn))
839                 if att == "dn":
840                     # There is always a dn attribute after a msg_diff
841                     continue
842                 if att == "rIDAvailablePool":
843                     delta.remove(att)
844                     continue
845                 if att == "objectSid":
846                     delta.remove(att)
847                     continue
848                 if att == "creationTime":
849                     delta.remove(att)
850                     continue
851                 if att == "oEMInformation":
852                     delta.remove(att)
853                     continue
854                 if att == "msDs-KeyVersionNumber":
855                 # This is the kvno of the computer/user it's a very bad
856                 # idea to change it
857                     delta.remove(att)
858                     continue
859                 if handle_special_case(att, delta, reference, current, usns):
860                     # This attribute is "complicated" to handle and handling
861                     # was done in handle_special_case
862                     continue
863                 attrUSN = hash_attr_usn.get(att)
864                 if att == "forceLogoff" and attrUSN == None:
865                     continue
866                 if  attrUSN == None:
867                     delta.remove(att)
868                     continue
869
870                 if attrUSN == -1:
871                     # This attribute was last modified by another DC forget
872                     # about it
873                     message(CHANGE, "%sAttribute: %s has been" \
874                             "created/modified/deleted  by another DC,"
875                             " do nothing" % (txt, att ))
876                     txt = ""
877                     delta.remove(att)
878                     continue
879                 elif usn_in_range(int(attrUSN), usns) == 0:
880                     message(CHANGE, "%sAttribute: %s has been" \
881                                     "created/modified/deleted not during a" \
882                                     " provision or upgradeprovision: current" \
883                                     " usn %d , do nothing" % (txt, att, attrUSN))
884                     txt = ""
885                     delta.remove(att)
886                     continue
887                 else:
888                     if att == "defaultSecurityDescriptor":
889                         defSDmodified = 1
890                     if attrUSN:
891                         message(CHANGE, "%sAttribute: %s will be modified" \
892                                         "/deleted it was last modified" \
893                                         "during a provision, current usn:" \
894                                         "%d" % (txt, att,  attrUSN))
895                         txt = ""
896                     else:
897                         message(CHANGE, "%sAttribute: %s will be added because" \
898                                         " it hasn't existed before " % (txt, att))
899                         txt = ""
900                     continue
901
902             else:
903             # Old school way of handling things for pre alpha12 upgrade
904                 defSDmodified = 1
905                 msgElt = delta.get(att)
906
907                 if att == "nTSecurityDescriptor":
908                     delta.remove(att)
909                     continue
910
911                 if att == "dn":
912                     continue
913
914                 if not hashOverwrittenAtt.has_key(att):
915                     if msgElt.flags() != FLAG_MOD_ADD:
916                         if not handle_special_case(att, delta, reference, current,
917                                                     usns):
918                             if opts.debugchange or opts.debugall:
919                                 try:
920                                     dump_denied_change(dn, att,
921                                         messageEltFlagToString(msgElt.flags()),
922                                         current[0][att], reference[0][att])
923                                 except KeyError:
924                                     dump_denied_change(dn, att,
925                                         messageEltFlagToString(msgElt.flags()),
926                                         current[0][att], None)
927                             delta.remove(att)
928                         continue
929                 else:
930                     if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
931                         continue
932                     elif  hashOverwrittenAtt.get(att)==never:
933                         delta.remove(att)
934                         continue
935
936         delta.dn = dn
937         if len(delta.items()) >1:
938             attributes=", ".join(delta.keys())
939             message(CHANGE, "%s is different from the reference one, changed" \
940                             " attributes: %s\n" % (dn, attributes))
941             changed = changed + 1
942             samdb.modify(delta)
943     return changed
944
945 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
946     """Check differences between the reference provision and the upgraded one.
947
948     It looks for all objects which base DN is name.
949
950     This function will also add the missing object and update existing object
951     to add or remove attributes that were missing.
952
953     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
954                        reference provision
955     :param samdb: An LDB object connected to the sam.ldb of the update
956                   provision
957     :param basedn: String value of the DN of the partition
958     :param names: List of key provision parameters
959     :param schema: A Schema object
960     :param provisionUSNs:  The USNs modified by provision/upgradeprovision
961                            last time"""
962
963     hash_new = {}
964     hash = {}
965     listMissing = []
966     listPresent = []
967     reference = []
968     current = []
969
970     # Connect to the reference provision and get all the attribute in the
971     # partition referred by name
972     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
973                                     scope=SCOPE_SUBTREE, attrs=["dn"],
974                                     controls=["search_options:1:2"])
975
976     current = samdb.search(expression="objectClass=*", base=basedn,
977                                 scope=SCOPE_SUBTREE, attrs=["dn"],
978                                 controls=["search_options:1:2"])
979     # Create a hash for speeding the search of new object
980     for i in range(0, len(reference)):
981         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
982
983     # Create a hash for speeding the search of existing object in the
984     # current provision
985     for i in range(0, len(current)):
986         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
987
988
989     for k in hash_new.keys():
990         if not hash.has_key(k):
991             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
992                 listMissing.append(hash_new[k])
993         else:
994             listPresent.append(hash_new[k])
995
996     # Sort the missing object in order to have object of the lowest level
997     # first (which can be containers for higher level objects)
998     listMissing.sort(dn_sort)
999     listPresent.sort(dn_sort)
1000
1001     # The following lines is to load the up to
1002     # date schema into our current LDB
1003     # a complete schema is needed as the insertion of attributes
1004     # and class is done against it
1005     # and the schema is self validated
1006     samdb.set_schema_from_ldb(schema.ldb)
1007     try:
1008         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1009         add_deletedobj_containers(ref_samdb, samdb, names)
1010
1011         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1012         changed = update_present(ref_samdb, samdb, basedn, listPresent,
1013                                     provisionUSNs, names.invocation)
1014         message(SIMPLE, "There are %d changed objects" % (changed))
1015         return 1
1016
1017     except StandardError, err:
1018         message(ERROR, "Exception during upgrade of samdb:")
1019         (typ, val, tb) = sys.exc_info()
1020         traceback.print_exception(typ, val, tb)
1021         return 0
1022
1023
1024
1025 def check_updated_sd(ref_sam, cur_sam, names):
1026     """Check if the security descriptor in the upgraded provision are the same
1027        as the reference
1028
1029     :param ref_sam: A LDB object connected to the sam.ldb file used as
1030                     the reference provision
1031     :param cur_sam: A LDB object connected to the sam.ldb file used as
1032                     upgraded provision
1033     :param names: List of key provision parameters"""
1034     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1035                                 scope=SCOPE_SUBTREE,
1036                                 attrs=["dn", "nTSecurityDescriptor"],
1037                                 controls=["search_options:1:2"])
1038     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1039                                 scope=SCOPE_SUBTREE,
1040                                 attrs=["dn", "nTSecurityDescriptor"],
1041                                 controls=["search_options:1:2"])
1042     hash = {}
1043     for i in range(0, len(reference)):
1044         refsd = ndr_unpack(security.descriptor,
1045                     str(reference[i]["nTSecurityDescriptor"]))
1046         hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1047
1048     for i in range(0, len(current)):
1049         key = str(current[i]["dn"]).lower()
1050         if hash.has_key(key):
1051             cursd = ndr_unpack(security.descriptor,
1052                         str(current[i]["nTSecurityDescriptor"]))
1053             sddl = cursd.as_sddl(names.domainsid)
1054             if sddl != hash[key]:
1055                 txt = get_diff_sddls(hash[key], sddl)
1056                 if txt != "":
1057                     message(CHANGESD, "On object %s ACL is different"\
1058                                       " \n%s" % (current[i]["dn"], txt))
1059
1060
1061
1062 def fix_partition_sd(samdb, names):
1063     """This function fix the SD for partition containers (basedn, configdn, ...)
1064     This is needed because some provision use to have broken SD on containers
1065
1066     :param samdb: An LDB object pointing to the sam of the current provision
1067     :param names: A list of key provision parameters
1068     """
1069     # First update the SD for the rootdn
1070     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1071                          scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1072                          controls=["search_options:1:2"])
1073     delta = Message()
1074     delta.dn = Dn(samdb, str(res[0]["dn"]))
1075     descr = get_domain_descriptor(names.domainsid)
1076     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1077                                                     "nTSecurityDescriptor")
1078     samdb.modify(delta, ["recalculate_sd:0"])
1079     # Then the config dn
1080     res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1081                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1082                         controls=["search_options:1:2"])
1083     delta = Message()
1084     delta.dn = Dn(samdb, str(res[0]["dn"]))
1085     descr = get_config_descriptor(names.domainsid)
1086     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1087                                                     "nTSecurityDescriptor" )
1088     samdb.modify(delta, ["recalculate_sd:0"])
1089     # Then the schema dn
1090     res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1091                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1092                         controls=["search_options:1:2"])
1093
1094     delta = Message()
1095     delta.dn = Dn(samdb, str(res[0]["dn"]))
1096     descr = get_schema_descriptor(names.domainsid)
1097     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1098                                                     "nTSecurityDescriptor" )
1099     samdb.modify(delta, ["recalculate_sd:0"])
1100
1101 def rebuild_sd(samdb, names):
1102     """Rebuild security descriptor of the current provision from scratch
1103
1104     During the different pre release of samba4 security descriptors (SD)
1105     were notarly broken (up to alpha11 included)
1106     This function allow to get them back in order, this function make the
1107     assumption that nobody has modified manualy an SD
1108     and so SD can be safely recalculated from scratch to get them right.
1109
1110     :param names: List of key provision parameters"""
1111
1112
1113     hash = {}
1114     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1115                         scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1116                         controls=["search_options:1:2"])
1117     for obj in res:
1118         if not (str(obj["dn"]) == str(names.rootdn) or
1119             str(obj["dn"]) == str(names.configdn) or \
1120             str(obj["dn"]) == str(names.schemadn)):
1121             hash[str(obj["dn"])] = obj["whenCreated"]
1122
1123     listkeys = hash.keys()
1124     listkeys.sort(dn_sort)
1125
1126     for key in listkeys:
1127         try:
1128             delta = Message()
1129             delta.dn = Dn(samdb, key)
1130             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1131                                                     "whenCreated" )
1132             samdb.modify(delta, ["recalculate_sd:0"])
1133         except:
1134             # XXX: We should always catch an explicit exception.
1135             # What could go wrong here?
1136             samdb.transaction_cancel()
1137             res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1138                                 scope=SCOPE_SUBTREE,
1139                                 attrs=["dn", "nTSecurityDescriptor"],
1140                                 controls=["search_options:1:2"])
1141             badsd = ndr_unpack(security.descriptor,
1142                         str(res[0]["nTSecurityDescriptor"]))
1143             print "bad stuff %s"%badsd.as_sddl(names.domainsid)
1144             return
1145
1146 def removeProvisionUSN(samdb):
1147         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1148         entry = samdb.search(expression="dn=@PROVISION", base = "",
1149                                 scope=SCOPE_SUBTREE,
1150                                 controls=["search_options:1:2"],
1151                                 attrs=attrs)
1152         empty = Message()
1153         empty.dn = entry[0].dn
1154         delta = samdb.msg_diff(entry[0], empty)
1155         delta.remove("dn")
1156         delta.dn = entry[0].dn
1157         samdb.modify(delta)
1158
1159 def delta_update_basesamdb(refpaths, paths, creds, session, lp):
1160     """Update the provision container db: sam.ldb
1161     This function is aimed for alpha9 and newer;
1162
1163     :param refpaths: An object holding the different importants paths for
1164                      reference provision object
1165     :param paths: An object holding the different importants paths for
1166                   upgraded provision object
1167     :param creds: Credential used for openning LDB files
1168     :param session: Session to use for openning LDB files
1169     :param lp: A loadparam object"""
1170
1171     message(SIMPLE,
1172             "Update base samdb by searching difference with reference one")
1173     refsam = Ldb(refpaths.samdb, session_info=session, credentials=creds,
1174                     lp=lp, options=["modules:"])
1175     sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
1176                 options=["modules:"])
1177
1178     empty = Message()
1179
1180     reference = refsam.search(expression="")
1181
1182     for refentry in reference:
1183         entry = sam.search(expression="dn=%s" % refentry["dn"],
1184                             scope=SCOPE_SUBTREE)
1185         if not len(entry):
1186             message(CHANGE, "Adding %s to sam db" % str(delta.dn))
1187             delta = sam.msg_diff(empty, 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             delta.dn = refentry.dn
1192             sam.add(delta)
1193         else:
1194             delta = sam.msg_diff(entry[0], refentry)
1195             if str(refentry.dn) == "@PROVISION" and\
1196                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
1197                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
1198             if len(delta.items()) > 1:
1199                 delta.dn = refentry.dn
1200                 sam.modify(delta)
1201
1202
1203 def simple_update_basesamdb(newpaths, paths, names):
1204     """Update the provision container db: sam.ldb
1205     This function is aimed at very old provision (before alpha9)
1206
1207     :param newpaths: List of paths for different provision objects
1208                         from the reference provision
1209     :param paths: List of paths for different provision objects
1210                         from the upgraded provision
1211     :param names: List of key provision parameters"""
1212
1213     message(SIMPLE, "Copy samdb")
1214     shutil.copy(newpaths.samdb, paths.samdb)
1215
1216     message(SIMPLE, "Update partitions filename if needed")
1217     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1218     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1219     usersldb = os.path.join(paths.private_dir, "users.ldb")
1220     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1221
1222     if not os.path.isdir(samldbdir):
1223         os.mkdir(samldbdir)
1224         os.chmod(samldbdir, 0700)
1225     if os.path.isfile(schemaldb):
1226         shutil.copy(schemaldb, os.path.join(samldbdir,
1227                                             "%s.ldb"%str(names.schemadn).upper()))
1228         os.remove(schemaldb)
1229     if os.path.isfile(usersldb):
1230         shutil.copy(usersldb, os.path.join(samldbdir,
1231                                             "%s.ldb"%str(names.rootdn).upper()))
1232         os.remove(usersldb)
1233     if os.path.isfile(configldb):
1234         shutil.copy(configldb, os.path.join(samldbdir,
1235                                             "%s.ldb"%str(names.configdn).upper()))
1236         os.remove(configldb)
1237
1238
1239 def update_privilege(ref_private_path, cur_private_path):
1240     """Update the privilege database
1241
1242     :param ref_private_path: Path to the private directory of the reference
1243                              provision.
1244     :param cur_private_path: Path to the private directory of the current
1245                              (and to be updated) provision."""
1246     message(SIMPLE, "Copy privilege")
1247     shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1248                 os.path.join(cur_private_path, "privilege.ldb"))
1249
1250
1251 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1252     """Upgrade the SAM DB contents for all the provision partitions
1253
1254     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1255                        provision
1256     :param samdb: An LDB object connected to the sam.ldb of the update
1257                   provision
1258     :param names: List of key provision parameters
1259     :param highestUSN:  The highest USN modified by provision/upgradeprovision
1260                         last time
1261     :param schema: A Schema object that represent the schema of the provision"""
1262
1263     message(SIMPLE, "Starting update of samdb")
1264     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1265                             schema, highestUSN)
1266     if ret:
1267         message(SIMPLE, "Update of samdb finished")
1268         return 1
1269     else:
1270         message(SIMPLE, "Update failed")
1271         return 0
1272
1273
1274 def update_machine_account_password(samdb, secrets_ldb, names):
1275     """Update (change) the password of the current DC both in the SAM db and in
1276        secret one
1277
1278     :param samdb: An LDB object related to the sam.ldb file of a given provision
1279     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
1280                         provision
1281     :param names: List of key provision parameters"""
1282
1283     message(SIMPLE, "Update machine account")
1284     expression = "samAccountName=%s$" % names.netbiosname
1285     secrets_msg = secrets_ldb.search(expression=expression,
1286                                         attrs=["secureChannelType"])
1287     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
1288         res = samdb.search(expression=expression, attrs=[])
1289         assert(len(res) == 1)
1290
1291         msg = Message(res[0].dn)
1292         machinepass = samba.generate_random_password(128, 255)
1293         msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE,
1294                                                 "userPassword")
1295         samdb.modify(msg)
1296
1297         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
1298                      attrs=["msDs-keyVersionNumber"])
1299         assert(len(res) == 1)
1300         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
1301         secChanType = int(secrets_msg[0]["secureChannelType"][0])
1302
1303         secretsdb_self_join(secrets_ldb, domain=names.domain,
1304                     realm=names.realm or sambaopts._lp.get('realm'),
1305                     domainsid=names.domainsid,
1306                     dnsdomain=names.dnsdomain,
1307                     netbiosname=names.netbiosname,
1308                     machinepass=machinepass,
1309                     key_version_number=kvno,
1310                     secure_channel_type=secChanType)
1311     else:
1312         raise ProvisioningError("Unable to find a Secure Channel" \
1313                                 "of type SEC_CHAN_BDC")
1314
1315
1316 def update_gpo(paths, creds, session, names):
1317     """Create missing GPO file object if needed
1318
1319     Set ACL correctly also.
1320     """
1321     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
1322     if not os.path.isdir(dir):
1323         create_gpo_struct(dir)
1324
1325     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
1326     if not os.path.isdir(dir):
1327         create_gpo_struct(dir)
1328     samdb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp)
1329     set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
1330         names.domaindn, samdb, lp)
1331
1332
1333 def getOEMInfo(samdb, rootdn):
1334     """Return OEM Information on the top level
1335     Samba4 use to store version info in this field
1336
1337     :param samdb: An LDB object connect to sam.ldb
1338     :param rootdn: Root DN of the domain
1339     :return: The content of the field oEMInformation (if any)"""
1340     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
1341                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1342     if len(res) > 0:
1343         info = res[0]["oEMInformation"]
1344         return info
1345     else:
1346         return ""
1347
1348 def updateOEMInfo(samdb, names):
1349     res = samdb.search(expression="(objectClass=*)", base=str(names.rootdn),
1350                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
1351     if len(res) > 0:
1352         info = res[0]["oEMInformation"]
1353         info = "%s, upgrade to %s" % (info, version)
1354         delta = Message()
1355         delta.dn = Dn(samdb, str(res[0]["dn"]))
1356         delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
1357             "oEMInformation" )
1358         samdb.modify(delta)
1359
1360
1361 def setup_path(file):
1362     return os.path.join(setup_dir, file)
1363
1364 # Synopsis for updateprovision
1365 # 1) get path related to provision to be update (called current)
1366 # 2) open current provision ldbs
1367 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1368 #    of the DC ....)
1369 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1370 #    by either upgradeprovision or provision
1371 # 5) creation of a new provision the latest version of provision script
1372 #    (called reference)
1373 # 6) get reference provision paths
1374 # 7) open reference provision ldbs
1375 # 8) setup helpers data that will help the update process
1376 # 9) update the privilege ldb by copying the one of referecence provision to
1377 #    the current provision
1378 # 10)get the oemInfo field, this field contains information about the different
1379 #    provision that have been done
1380 # 11)Depending  on whether oemInfo has the string "alpha9" or alphaxx (x as an
1381 #    integer) or none of this the following things are done
1382 #    A) When alpha9 or alphaxx is present
1383 #       The base sam.ldb file is updated by looking at the difference between
1384 #       referrence one and the current one. Everything is copied with the
1385 #       exception of lastProvisionUSN attributes. The highest used USN
1386 #       is fetched so that changed by upgradeprovision usn can be tracked
1387 #    B) Other case (it reflect that that provision was done before alpha9)
1388 #       The base sam.ldb of the reference provision is copied over
1389 #       the current one, if necessary ldb related to partitions are moved
1390 #       and renamed
1391 # 12)A Schema object is created, it will be used to provide a complete
1392 #    schema to current provision during update (as the schema of the
1393 #    current provision might not be complete and so won't allow some
1394 #    object to be created)
1395 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1396 # 14)The secrets db is updated by pull all the difference from the reference
1397 #    provision into the current provision
1398 # 15)As the previous step has most probably modified the password stored in
1399 #    in secret for the current DC, a new password is generated,
1400 #    the kvno is bumped and the entry in samdb is also updated
1401 # 16)For current provision older than alpha9, we must fix the SD a little bit
1402 #    administrator to update them because SD used to be generated with the
1403 #    system account before alpha9.
1404 # 17)The highest usn modified so far is searched in the database it will be
1405 #    the upper limit for usn modified during provision.
1406 #    This is done before potential SD recalculation because we do not want
1407 #    SD modified during recalculation to be marked as modified during provision
1408 #    (and so possibly remplaced at next upgradeprovision)
1409 # 18)Rebuilt SD if the flag indicate to do so
1410 # 19)Check difference between SD of reference provision and those of the
1411 #    current provision. The check is done by getting the sddl representation
1412 #    of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1413 #    Each part is verified separetly, for dacl and sacl ACL is splited into
1414 #    ACEs and each ACE is verified separately (so that a permutation in ACE
1415 #    didn't raise as an error).
1416 # 20)The oemInfo field is updated to add information about the fact that the
1417 #    provision has been updated by the upgradeprovision version xxx
1418 #    (the version is the one obtained when starting samba with the --version
1419 #    parameter)
1420 # 21)Check if the current provision has all the settings needed for dynamic
1421 #    DNS update to work (that is to say the provision is newer than
1422 #    january 2010). If not dns configuration file from reference provision
1423 #    are copied in a sub folder and the administrator is invited to
1424 #    do what is needed.
1425 # 22)If the lastProvisionUSN attribute was present it is updated to add
1426 #    the range of usns modified by the current upgradeprovision
1427
1428
1429 # About updating the sam DB
1430 # The update takes place in update_partition function
1431 # This function read both current and reference provision and list all
1432 # the available DN of objects
1433 # If the string representation of a DN in reference provision is
1434 # equal to the string representation of a DN in current provision
1435 # (without taking care of case) then the object is flaged as being
1436 # present. If the object is not present in current provision the object
1437 # is being flaged as missing in current provision. Object present in current
1438 # provision but not in reference provision are ignored.
1439 # Once the list of objects present and missing is done, the deleted object
1440 # containers are created in the differents partitions (if missing)
1441 #
1442 # Then the function add_missing_entries is called
1443 # This function will go through the list of missing entries by calling
1444 # add_missing_object for the given object. If this function returns 0
1445 # it means that the object needs some other object in order to be created
1446 # The object is reappended at the end of the list to be created later
1447 # (and preferably after all the needed object have been created)
1448 # The function keeps on looping on the list of object to be created until
1449 # it's empty or that the number of defered creation is equal to the number
1450 # of object that still needs to be created.
1451
1452 # The function add_missing_object will first check if the object can be created.
1453 # That is to say that it didn't depends other not yet created objects
1454 # If requisit can't be fullfilled it exists with 0
1455 # Then it will try to create the missing entry by creating doing
1456 # an ldb_message_diff between the object in the reference provision and
1457 # an empty object.
1458 # This resulting object is filtered to remove all the back link attribute
1459 # (ie. memberOf) as they will be created by the other linked object (ie.
1460 # the one with the member attribute)
1461 # All attributes specified in the hashAttrNotCopied associative array are
1462 # also removed it's most of the time generated attributes
1463
1464 # After missing entries have been added the update_partition function will
1465 # take care of object that exist but that need some update.
1466 # In order to do so the function update_present is called with the list
1467 # of object that are present in both provision and that might need an update.
1468
1469 # This function handle first case mismatch so that the DN in the current
1470 # provision have the same case as in reference provision
1471
1472 # It will then construct an associative array consiting of attributes as
1473 # key and invocationid as value( if the originating invocation id is
1474 # different from the invocation id of the current DC the value is -1 instead).
1475
1476 # If the range of provision modified attributes is present, the function will
1477 # use the replMetadataProperty update method which is the following:
1478 #  Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1479 #   creationTime, msDs-KeyVersionNumber, oEMInformation
1480 #  Check for each attribute if its usn is within one of the modified by
1481 #   provision range and if its originating id is the invocation id of the
1482 #   current DC, then validate the update from reference to current.
1483 #   If not or if there is no replMetatdataProperty for this attribute then we
1484 #   do not update it.
1485 # Otherwise (case the range of provision modified attribute is not present) it
1486 # use the following process:
1487 #  All attributes that need to be added are accepted at the exeption of those
1488 #   listed in hashOverwrittenAtt, in this case the attribute needs to have the
1489 #   correct flags specified.
1490 #  For attributes that need to be modified or removed, a check is performed
1491 #  in OverwrittenAtt, if the attribute is present and the modification flag
1492 #  (remove, delete) is one of those listed for this attribute then modification
1493 #  is accepted. For complicated handling of attribute update, the control is passed
1494 #  to handle_special_case
1495
1496
1497
1498 if __name__ == '__main__':
1499     global defSDmodified
1500     defSDmodified = 0
1501     # From here start the big steps of the program
1502     # 1) First get files paths
1503     paths = get_paths(param, smbconf=smbconf)
1504     paths.setup = setup_dir
1505     # Get ldbs with the system session, it is needed for searching
1506     # provision parameters
1507     session = system_session()
1508
1509     # This variable will hold the last provision USN once if it exists.
1510     minUSN = 0
1511     # 2)
1512     ldbs = get_ldbs(paths, creds, session, lp)
1513     ldbs.startTransactions()
1514
1515     # 3) Guess all the needed names (variables in fact) from the current
1516     # provision.
1517     names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, paths,
1518                                             smbconf, lp)
1519     # 4)
1520     lastProvisionUSNs = getLastProvisionUSN(ldbs.sam)
1521     if lastProvisionUSNs != None:
1522         message(CHANGE,
1523             "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1524
1525     # Objects will be created with the admin session
1526     # (not anymore system session)
1527     adm_session = admin_session(lp, str(names.domainsid))
1528     # So we reget handle on objects
1529     # ldbs = get_ldbs(paths, creds, adm_session, lp)
1530
1531     if not sanitychecks(ldbs.sam, names):
1532         message(SIMPLE, "Sanity checks for the upgrade fails, checks messages" \
1533                         " and correct them before rerunning upgradeprovision")
1534         sys.exit(1)
1535
1536     # Let's see provision parameters
1537     print_provision_key_parameters(names)
1538
1539     # 5) With all this information let's create a fresh new provision used as
1540     # reference
1541     message(SIMPLE, "Creating a reference provision")
1542     provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1543                                     prefix="referenceprovision")
1544     newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1545                     provision_logger)
1546
1547     # TODO
1548     # 6) and 7)
1549     # We need to get a list of object which SD is directly computed from
1550     # defaultSecurityDescriptor.
1551     # This will allow us to know which object we can rebuild the SD in case
1552     # of change of the parent's SD or of the defaultSD.
1553     # Get file paths of this new provision
1554     newpaths = get_paths(param, targetdir=provisiondir)
1555     new_ldbs = get_ldbs(newpaths, creds, session, lp)
1556     new_ldbs.startTransactions()
1557
1558     # 8) Populate some associative array to ease the update process
1559     # List of attribute which are link and backlink
1560     populate_links(new_ldbs.sam, names.schemadn)
1561     # List of attribute with ASN DN synthax)
1562     populate_dnsyntax(new_ldbs.sam, names.schemadn)
1563     # 9)
1564     update_privilege(newpaths.private_dir, paths.private_dir)
1565     # 10)
1566     oem = getOEMInfo(ldbs.sam, names.rootdn)
1567     # Do some modification on sam.ldb
1568     ldbs.groupedCommit()
1569     # 11)
1570     if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1571         # 11) A
1572         # Starting from alpha9 we can consider that the structure is quite ok
1573         # and that we should do only dela
1574         new_ldbs.groupedCommit()
1575         delta_update_basesamdb(newpaths, paths, creds, session, lp)
1576         ldbs.startTransactions()
1577         minUSN = get_max_usn(ldbs.sam, str(names.rootdn)) + 1
1578         new_ldbs.startTransactions()
1579     else:
1580         # 11) B
1581         simple_update_basesamdb(newpaths, paths, names)
1582         ldbs = get_ldbs(paths, creds, session, lp)
1583         ldbs.startTransactions()
1584         removeProvisionUSN(ldbs.sam)
1585
1586     # 12)
1587     schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1588                     serverdn=str(names.serverdn))
1589     # 13)
1590     if opts.full:
1591         if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1592                             schema):
1593             message(SIMPLE, "Rollbacking every changes. Check the reason" \
1594                             " of the problem")
1595             message(SIMPLE, "In any case your system as it was before" \
1596                             " the upgrade")
1597             ldbs.groupedRollback()
1598             new_ldbs.groupedRollback()
1599             shutil.rmtree(provisiondir)
1600             sys.exit(1)
1601     # 14)
1602     update_secrets(new_ldbs.secrets, ldbs.secrets)
1603     # 15)
1604     update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1605
1606     # 16) SD should be created with admin but as some previous acl were so wrong
1607     # that admin can't modify them we have first to recreate them with the good
1608     # form but with system account and then give the ownership to admin ...
1609     if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1610         message(SIMPLE, "Fixing old povision SD")
1611         fix_partition_sd(ldbs.sam, names)
1612         rebuild_sd(ldbs.sam, names)
1613
1614     # We calculate the max USN before recalculating the SD because we might
1615     # touch object that have been modified after a provision and we do not
1616     # want that the next upgradeprovision thinks that it has a green light
1617     # to modify them
1618
1619     # 17)
1620     maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1621
1622     # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1623     # But in fact we should do it also if one object has its SD modified as
1624     # child might need rebuild
1625     if defSDmodified == 1:
1626         message(SIMPLE, "Updating SD")
1627         ldbs.sam.set_session_info(adm_session)
1628         # Alpha10 was a bit broken still
1629         if re.match(r'.*alpha(\d|10)', str(oem)):
1630             fix_partition_sd(ldbs.sam, names)
1631         rebuild_sd(ldbs.sam, names)
1632
1633     # 19)
1634     # Now we are quite confident in the recalculate process of the SD, we make
1635     # it optional.
1636     # Also the check must be done in a clever way as for the moment we just
1637     # compare SDDL
1638     if opts.debugchangesd:
1639         check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1640
1641     # 20)
1642     updateOEMInfo(ldbs.sam, names)
1643     # 21)
1644     check_for_DNS(newpaths.private_dir, paths.private_dir)
1645     # 22)
1646     if lastProvisionUSNs != None:
1647         updateProvisionUSN(ldbs.sam, minUSN, maxUSN)
1648     ldbs.groupedCommit()
1649     new_ldbs.groupedCommit()
1650     message(SIMPLE, "Upgrade finished !")
1651     # remove reference provision now that everything is done !
1652     shutil.rmtree(provisiondir)