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