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