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