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