4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
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
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.
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.
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/>.
32 # Allow to run from s4 source directory (without installing samba)
33 sys.path.insert(0, "bin/python")
36 import samba.getopt as options
37 from samba.credentials import DONT_USE_KERBEROS
38 from samba.auth import system_session, admin_session
39 from samba import Ldb, version
40 from ldb import SCOPE_ONELEVEL, 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, set_gpo_acl,
47 getpolicypath, create_gpo_struct, ProvisioningError)
48 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
49 from samba.dcerpc import security
50 from samba.ndr import ndr_unpack
51 from samba.dcerpc.misc import SEC_CHAN_BDC
52 from samba.upgradehelpers import dn_sort, get_paths, newprovision,\
53 find_provision_key_parameters, get_ldbs
55 replace=2**FLAG_MOD_REPLACE
57 delete=2**FLAG_MOD_DELETE
60 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
62 #Errors are always logged
71 __docformat__ = "restructuredText"
73 # Attributes that are never copied from the reference provision (even if they
74 # do not exist in the destination object).
75 # This is most probably because they are populated automatcally when object is
77 # This also apply to imported object from reference provision
78 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1, "objectGUID": 1, "replPropertyMetaData": 1, "uSNChanged": 1,
79 "uSNCreated": 1, "parentGUID": 1, "objectCategory": 1, "distinguishedName": 1,
80 "showInAdvancedViewOnly": 1, "instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
81 "nTMixedDomain": 1, "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
82 "dBCSPwd":1, "supplementalCredentials":1, "gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
83 "maxPwdAge":1, "secret":1, "possibleInferiors":1, "privilege":1, "sAMAccountType":1 }
85 # Usually for an object that already exists we do not overwrite attributes as
86 # they might have been changed for good reasons. Anyway for a few of them it's
87 # mandatory to replace them otherwise the provision will be broken somehow.
88 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace, "systemOnly":replace, "searchFlags":replace,
89 "mayContain":replace, "systemFlags":replace, "description":replace,
90 "operatingSystemVersion":replace, "adminPropertyPages":replace,
91 "defaultSecurityDescriptor": replace, "wellKnownObjects":replace, "privilege":never, "groupType":replace,
92 "rIDAvailablePool": never, "defaultSecurityDescriptor": replace + add}
97 def define_what_to_log(opts):
101 if opts.debugchangesd:
102 what = what | CHANGESD
105 if opts.debugprovision:
106 what = what | PROVISION
108 what = what | CHANGEALL
112 parser = optparse.OptionParser("provision [options]")
113 sambaopts = options.SambaOptions(parser)
114 parser.add_option_group(sambaopts)
115 parser.add_option_group(options.VersionOptions(parser))
116 credopts = options.CredentialsOptions(parser)
117 parser.add_option_group(credopts)
118 parser.add_option("--setupdir", type="string", metavar="DIR",
119 help="directory with setup files")
120 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
121 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
122 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
123 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
124 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
125 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
127 opts = parser.parse_args()[0]
129 handler = logging.StreamHandler(sys.stdout)
130 upgrade_logger = logging.getLogger("upgradeprovision")
131 upgrade_logger.addHandler(handler)
133 provision_logger = logging.getLogger("provision")
134 provision_logger.addHandler(handler)
136 whatToLog = define_what_to_log(opts)
138 def message(what, text):
139 """Print a message if this message type has been selected to be printed
141 :param what: Category of the message
142 :param text: Message to print """
143 if (whatToLog & what) or what <= 0:
144 upgrade_logger.info("%s", text)
146 if len(sys.argv) == 1:
147 opts.interactive = True
148 lp = sambaopts.get_loadparm()
149 smbconf = lp.configfile
151 creds = credopts.get_credentials(lp)
152 creds.set_kerberos_state(DONT_USE_KERBEROS)
153 setup_dir = opts.setupdir
154 if setup_dir is None:
155 setup_dir = find_setup_dir()
158 def identic_rename(ldbobj,dn):
159 """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified.
161 :param lbdobj: An Ldb Object
162 :param dn: DN of the object to manipulate """
163 (before,sep,after)=str(dn).partition('=')
164 ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
165 ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
168 def populate_backlink(samdb, schemadn):
169 """Populate an array with all the back linked attributes
171 This attributes that are modified automaticaly when
172 front attibutes are changed
174 :param samdb: A LDB object for sam.ldb file
175 :param schemadn: DN of the schema for the partition"""
176 linkedAttHash = get_linked_attributes(Dn(samdb,str(schemadn)), samdb)
177 backlinked.extend(linkedAttHash.values())
179 def populate_dnsyntax(samdb, schemadn):
180 """Populate an array with all the attributes that have DN synthax
183 :param samdb: A LDB object for sam.ldb file
184 :param schemadn: DN of the schema for the partition"""
185 res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
186 str(schemadn)), scope=SCOPE_SUBTREE,
187 attrs=["lDAPDisplayName"])
189 dn_syntax_att.append(elem["lDAPDisplayName"])
192 def sanitychecks(samdb, names):
193 """Make some checks before trying to update
195 :param samdb: An LDB object opened on sam.ldb
196 :param names: list of key provision parameters
197 :return: Status of check (1 for Ok, 0 for not Ok) """
198 res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
199 scope=SCOPE_SUBTREE, attrs=["dn"],
200 controls=["search_options:1:2"])
202 print "No DC found, your provision is most probably hardly broken !"
205 print "Found %d domain controllers, for the moment upgradeprovision" \
206 "is not able to handle upgrade on domain with more than one DC, please demote" \
207 " the other(s) DC(s) before upgrading" % len(res)
213 def print_provision_key_parameters(names):
214 """Do a a pretty print of provision parameters
216 :param names: list of key provision parameters """
217 message(GUESS, "rootdn :"+str(names.rootdn))
218 message(GUESS, "configdn :"+str(names.configdn))
219 message(GUESS, "schemadn :"+str(names.schemadn))
220 message(GUESS, "serverdn :"+str(names.serverdn))
221 message(GUESS, "netbiosname :"+names.netbiosname)
222 message(GUESS, "defaultsite :"+names.sitename)
223 message(GUESS, "dnsdomain :"+names.dnsdomain)
224 message(GUESS, "hostname :"+names.hostname)
225 message(GUESS, "domain :"+names.domain)
226 message(GUESS, "realm :"+names.realm)
227 message(GUESS, "invocationid:"+names.invocation)
228 message(GUESS, "policyguid :"+names.policyid)
229 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
230 message(GUESS, "domainsid :"+str(names.domainsid))
231 message(GUESS, "domainguid :"+names.domainguid)
232 message(GUESS, "ntdsguid :"+names.ntdsguid)
233 message(GUESS, "domainlevel :"+str(names.domainlevel))
236 def handle_special_case(att, delta, new, old):
237 """Define more complicate update rules for some attributes
239 :param att: The attribute to be updated
240 :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
241 :param new: The reference object
242 :param old: The Updated object
243 :return: Tru to indicate that the attribute should be kept, False for discarding it
245 flag = delta.get(att).flags()
246 if (att == "gPLink" or att == "gPCFileSysPath") and \
247 flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
250 if att == "forceLogoff":
251 ref=0x8000000000000000
252 oldval=int(old[0][att][0])
253 newval=int(new[0][att][0])
254 ref == old and ref == abs(new)
256 if (att == "adminDisplayName" or att == "adminDescription"):
259 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s" % (str(names.schemadn))\
260 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
263 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
266 if ((att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
270 for elem in old[0][att]:
272 newval.append(str(elem))
274 for elem in new[0][att]:
275 if not hash.has_key(str(elem)):
277 newval.append(str(elem))
279 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
284 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
286 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
290 def update_secrets(newsecrets_ldb, secrets_ldb):
291 """Update secrets.ldb
293 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
294 of the reference provision
295 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
296 of the updated provision"""
298 message(SIMPLE, "update secrets.ldb")
299 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
301 current = secrets_ldb.search(expression="dn=@MODULES", base="",
303 delta = secrets_ldb.msg_diff(current[0], reference[0])
304 delta.dn = current[0].dn
305 secrets_ldb.modify(delta)
307 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
308 scope=SCOPE_SUBTREE, attrs=["dn"])
309 current = secrets_ldb.search(expression="objectClass=top", base="",
310 scope=SCOPE_SUBTREE, attrs=["dn"])
317 for i in range(0, len(reference)):
318 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
320 # Create a hash for speeding the search of existing object in the
322 for i in range(0, len(current)):
323 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
325 for k in hash_new.keys():
326 if not hash.has_key(k):
327 listMissing.append(hash_new[k])
329 listPresent.append(hash_new[k])
331 for entry in listMissing:
332 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
333 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
334 delta = secrets_ldb.msg_diff(empty,reference[0])
335 for att in hashAttrNotCopied.keys():
337 message(CHANGE, "Entry %s is missing from secrets.ldb"%reference[0].dn)
339 message(CHANGE, " Adding attribute %s"%att)
340 delta.dn = reference[0].dn
341 secrets_ldb.add(delta)
343 for entry in listPresent:
344 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
345 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
346 delta = secrets_ldb.msg_diff(current[0],reference[0])
347 for att in hashAttrNotCopied.keys():
351 message(CHANGE, "Found attribute name on %s, must rename the DN "%(current[0].dn))
352 identic_rename(secrets_ldb,reference[0].dn)
356 for entry in listPresent:
357 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
358 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
359 delta = secrets_ldb.msg_diff(current[0],reference[0])
360 for att in hashAttrNotCopied.keys():
364 message(CHANGE, " Adding/Changing attribute %s to %s"%(att,current[0].dn))
366 delta.dn = current[0].dn
367 secrets_ldb.modify(delta)
370 def dump_denied_change(dn,att,flagtxt,current,reference):
371 """Print detailed information about why a changed is denied
373 :param dn: DN of the object which attribute is denied
374 :param att: Attribute that was supposed to be upgraded
375 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
376 :param current: Value(s) of the current attribute
377 :param reference: Value(s) of the reference attribute"""
379 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
380 if att != "objectSid" :
382 for e in range(0,len(current)):
383 message(CHANGE, "old %d : %s"%(i,str(current[e])))
385 if reference != None:
387 for e in range(0,len(reference)):
388 message(CHANGE, "new %d : %s"%(i,str(reference[e])))
391 message(CHANGE, "old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
392 message(CHANGE, "new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
395 def handle_special_add(samdb,dn,names):
396 """Handle special operation (like remove) on some object needed during upgrade
398 This is mostly due to wrong creation of the object in previous provision.
399 :param samdb: An Ldb object representing the SAM database
400 :param dn: DN of the object to inspect
401 :param names: list of key provision parameters"""
403 if str(dn).lower() == ("CN=IIS_IUSRS,CN=Builtin,%s" % names.rootdn).lower():
404 #This entry was misplaced lets remove it if it exists
405 dntoremove = "CN=IIS_IUSRS,CN=Users,%s" % names.rootdn
407 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
408 #This entry was misplaced lets remove it if it exists
409 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
411 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
412 #This entry was misplaced lets remove it if it exists
413 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
415 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
416 #This entry was misplaced lets remove it if it exists
417 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
419 if dntoremove != None:
420 res = samdb.search(expression="(dn=%s)" % dntoremove, base=str(names.rootdn), scope=SCOPE_SUBTREE, attrs=["dn"], controls=["search_options:1:2"])
422 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
423 samdb.delete(res[0]["dn"])
426 def check_dn_nottobecreated(hash, index, listdn):
427 """Check if one of the DN present in the list has a creation order greater than the current.
429 Hash is indexed by dn to be created, with each key is associated the creation order
430 First dn to be created has the creation order 0, second has 1, ...
431 Index contain the current creation order
433 :param hash: Hash holding the different DN of the object to be created as key
434 :param index: Current creation order
435 :param listdn: List of DNs on which the current DN depends on
436 :return: None if the current object do not depend on other object or if all object have been
441 key = str(dn).lower()
442 if hash.has_key(key) and hash[key] > index:
447 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
448 """Add a new object if the dependencies are satisfied
450 The function add the object if the object on which it depends are already created
451 :param ref_samdb: Ldb object representing the SAM db of the reference provision
452 :param samdb: Ldb object representing the SAM db of the upgraded provision
453 :param dn: DN of the object to be added
454 :param names: List of key provision parameters
455 :param basedn: DN of the partition to be updated
456 :param hash: Hash holding the different DN of the object to be created as key
457 :param index: Current creation order
458 :return: True if the object was created False otherwise"""
459 handle_special_add(samdb,dn,names)
460 reference = ref_samdb.search(expression="dn=%s" % (str(dn)),base=basedn,
461 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
463 delta = samdb.msg_diff(empty,reference[0])
465 for att in hashAttrNotCopied.keys():
467 for att in backlinked:
469 depend_on_yettobecreated = None
470 for att in dn_syntax_att:
471 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
472 if depend_on_yet_tobecreated != None:
473 message(CHANGE, "Object %s depends on %s in attribute %s, delaying the creation"
474 %(str(dn),depend_on_yet_tobecreated,str(att)))
477 message(CHANGE,"Object %s will be added"%dn)
478 samdb.add(delta, ["relax:0"])
481 def gen_dn_index_hash(listMissing):
482 """Generate a hash associating the DN to its creation order
484 :param listMissing: List of DN
485 :return: Hash with DN as keys and creation order as values"""
487 for i in range(0, len(listMissing)):
488 hash[str(listMissing[i]).lower()] = i
491 def add_deletedobj_containers(ref_samdb, samdb, names):
492 """Add the object containter: CN=Deleted Objects
494 This function create the container for each partition that need one and then reference the object into
495 the root of the partition
496 :param ref_samdb: Ldb object representing the SAM db of the reference provision
497 :param samdb: Ldb object representing the SAM db of the upgraded provision
498 :param names: List of key provision parameters"""
500 partitions = [str(names.rootdn),str(names.configdn)]
501 for part in partitions:
502 ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",base=part, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["show_deleted:0"])
503 delObjCnt = samdb.search(expression="(cn=Deleted Objects)",base=part, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["show_deleted:0"])
504 if len(ref_delObjCnt) > len(delObjCnt):
505 reference = ref_samdb.search(expression="cn=Deleted Objects",base=part,
506 scope=SCOPE_SUBTREE,controls=["show_deleted:0"])
508 delta = samdb.msg_diff(empty,reference[0])
510 delta.dn = Dn(samdb,str(reference[0]["dn"]))
511 for att in hashAttrNotCopied.keys():
516 res = samdb.search(expression="(objectClass=*)",base=part,
517 scope=SCOPE_BASE, attrs=["dn","wellKnownObjects"])
519 targetWKO = "B:32:18E2EA80684F11D2B9AA00C04F79F805:%s" % str(reference[0]["dn"])
523 wko = res[0]["wellKnownObjects"]
525 # The wellKnownObject that we want to add.
528 if str(o) == targetWKO:
530 listwko.append(str(o))
532 listwko.append(targetWKO)
535 delta.dn = Dn(samdb,str(res[0]["dn"]))
536 delta["wellKnownObjects"] = MessageElement(listwko, FLAG_MOD_REPLACE, "wellKnownObjects" )
539 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
540 """Add the missing object whose DN is the list
542 The function add the object if the object on which it depends are already created
543 :param ref_samdb: Ldb object representing the SAM db of the reference provision
544 :param samdb: Ldb object representing the SAM db of the upgraded provision
545 :param dn: DN of the object to be added
546 :param names: List of key provision parameters
547 :param basedn: DN of the partition to be updated
548 :param list: List of DN to be added in the upgraded provision"""
552 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
554 listMissing = listDefered
556 hashMissing = gen_dn_index_hash(listMissing)
557 for dn in listMissing:
558 ret = add_missing_object(ref_samdb,samdb,dn,names,basedn,hashMissing,index)
561 #DN can't be created because it depends on some other DN in the list
562 listDefered.append(dn)
563 if len(listDefered) != 0:
564 raise ProvisioningError("Unable to insert missing elements: circular references")
567 def update_partition(ref_samdb, samdb, basedn, names, use_ref_schema, highestUSN):
568 """Check differences between the reference provision and the upgraded one.
570 It looks for all objects which base DN is name. If ischema is "false" then
571 the scan is done in cross partition mode.
572 If "use_ref_schema" is true, then special handling is done for dealing with schema
574 This function will also add the missing object and update existing object to add
575 or remove attributes that were missing.
576 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference provision
577 :param samdb: An LDB object connected to the sam.ldb of the update provision
578 :param basedn: String value of the DN of the partition
579 :param names: List of key provision parameters
580 :param use_ref_schema: A flag to indicate if we should use the shema of the reference provision
581 :param highestUSN: The highest USN modified by provision/upgradeprovision last time"""
590 # Connect to the reference provision and get all the attribute in the
591 # partition referred by name
592 reference = ref_samdb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
593 current = samdb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
595 # Create a hash for speeding the search of new object
596 for i in range(0,len(reference)):
597 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
599 # Create a hash for speeding the search of existing object in the
601 for i in range(0,len(current)):
602 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
605 for k in hash_new.keys():
606 if not hash.has_key(k):
607 if not str(hash_new[k]) == "CN=Deleted Objects,%s" % names.rootdn:
608 listMissing.append(hash_new[k])
610 listPresent.append(hash_new[k])
612 # Sort the missing object in order to have object of the lowest level
613 # first (which can be containers for higher level objects)
614 listMissing.sort(dn_sort)
615 listPresent.sort(dn_sort)
617 if use_ref_schema == 1:
618 # The following lines (up to the for loop) is to load the up to
619 # date schema into our current LDB
620 # a complete schema is needed as the insertion of attributes
621 # and class is done against it
622 # and the schema is self validated
623 # The double ldb open and schema validation is taken from the
624 # initial provision script
625 # it's not certain that it is really needed ....
626 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
627 # Load the schema from the one we computed earlier
628 samdb.set_schema_from_ldb(schema.ldb)
631 message(SIMPLE,"There are %d missing objects" % (len(listMissing)))
632 add_deletedobj_containers(ref_samdb, samdb, names)
634 add_missing_entries(ref_samdb,samdb,names,basedn,listMissing)
637 for dn in listPresent:
638 reference = ref_samdb.search(expression="dn=%s" % (str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
639 current = samdb.search(expression="dn=%s" % (str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
640 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
641 message(CHANGE,"Name are the same but case change, let's rename %s to %s" % (str(current[0].dn),str(reference[0].dn)))
642 identic_rename(samdb,reference[0].dn)
643 current = samdb.search(expression="dn=%s" % (str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
645 delta = samdb.msg_diff(current[0],reference[0])
646 for att in hashAttrNotCopied.keys():
648 for att in backlinked:
650 delta.remove("parentGUID")
654 msgElt = delta.get(att)
660 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
661 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
664 if not handle_special_case(att,delta,reference,current) and msgElt.flags()!=FLAG_MOD_ADD:
665 if opts.debugchange or opts.debugall:
667 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
669 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
673 if len(delta.items()) >1:
674 attributes=",".join(delta.keys())
675 message(CHANGE,"%s is different from the reference one, changed attributes: %s" % (dn,attributes))
676 changed = changed + 1
679 message(SIMPLE,"There are %d changed objects" % (changed))
682 except Exception, err:
683 message(ERROR,"Exception during upgrade of samdb: %s" % str(err))
687 def check_updated_sd(ref_sam,cur_sam, names):
688 """Check if the security descriptor in the upgraded provision are the same as the reference
690 :param ref_sam: A LDB object connected to the sam.ldb file used as the reference provision
691 :param cur_sam: A LDB object connected to the sam.ldb file used as upgraded provision
692 :param names: List of key provision parameters"""
693 reference = ref_sam.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
694 current = cur_sam.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
696 for i in range(0,len(reference)):
697 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
699 for i in range(0,len(current)):
700 key = str(current[i]["dn"]).lower()
701 if hash_new.has_key(key):
702 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
703 if sddl != hash_new[key]:
704 print "%s \nnew sddl / sddl in ref" % key
705 print "%s\n%s\n" % (sddl,hash_new[key])
708 def rebuild_sd(samdb, names):
709 """Rebuild security descriptor of the current provision from scratch
711 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
712 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
713 and so SD can be safely recalculated from scratch to get them right.
715 :param names: List of key provision parameters"""
717 # First update the SD for the rootdn
718 res = samdb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
719 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
721 delta.dn = Dn(samdb,str(res[0]["dn"]))
722 descr = get_domain_descriptor(names.domainsid)
723 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
724 samdb.modify(delta,["recalculate_sd:0"])
726 res = samdb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
728 delta.dn = Dn(samdb,str(res[0]["dn"]))
729 descr = get_config_descriptor(names.domainsid)
730 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
731 samdb.modify(delta,["recalculate_sd:0"])
733 res = samdb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
735 delta.dn = Dn(samdb,str(res[0]["dn"]))
736 descr = get_schema_descriptor(names.domainsid)
737 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
738 samdb.modify(delta,["recalculate_sd:0"])
742 res = samdb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
744 if not (str(obj["dn"]) == str(names.rootdn) or
745 str(obj["dn"]) == str(names.configdn) or \
746 str(obj["dn"]) == str(names.schemadn)):
747 hash[str(obj["dn"])] = obj["whenCreated"]
749 listkeys = hash.keys()
750 listkeys.sort(dn_sort)
755 delta.dn = Dn(samdb,key)
756 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
757 samdb.modify(delta,["recalculate_sd:0"])
759 # XXX: We should always catch an explicit exception.
760 # What could go wrong here?
761 samdb.transaction_cancel()
762 res = samdb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
763 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
764 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
768 def getLastProvisionUSN(paths, creds, session, lp):
769 """Get the lastest USN modified by a provision or an upgradeprovision
771 :param paths: An object holding the different importants paths for upgraded provision object
772 :param creds: Credential used for openning LDB files
773 :param session: Session to use for openning LDB files
774 :param lp: A loadparam object
775 :return an integer corresponding to the highest USN modified by (upgrade)provision, 0 is this value is unknown"""
777 sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:"] )
778 entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" % LAST_PROVISION_USN_ATTRIBUTE, scope=SCOPE_SUBTREE,attrs=[LAST_PROVISION_USN_ATTRIBUTE])
780 message(CHANGE,"Find a last provision USN: %d" % entry[0][LAST_PROVISION_USN_ATTRIBUTE])
781 return entry[0][LAST_PROVISION_USN_ATTRIBUTE]
788 def delta_update_basesamdb(refpaths, paths, creds, session, lp):
789 """Update the provision container db: sam.ldb
790 This function is aimed for alpha9 and newer;
792 :param refpaths: An object holding the different importants paths for reference provision object
793 :param paths: An object holding the different importants paths for upgraded provision object
794 :param creds: Credential used for openning LDB files
795 :param session: Session to use for openning LDB files
796 :param lp: A loadparam object"""
798 message(SIMPLE,"Update base samdb by searching difference with reference one")
799 refsam = Ldb(refpaths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:"] )
800 sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:"] )
804 reference = refsam.search(expression="")
806 for refentry in reference:
807 entry = sam.search(expression="dn=%s" % refentry["dn"],scope=SCOPE_SUBTREE)
808 if not len(entry[0]):
809 message(CHANGE,"Adding %s to sam db" % str(delta.dn))
810 delta = sam.msg_diff(empty,refentry)
811 delta.dn = refentry.dn
814 delta = sam.msg_diff(entry[0],refentry)
815 if refentry.dn == "@PARTITION" and delta.get(LAST_PROVISION_USN_ATTRIBUTE):
816 delta.remove(LAST_PROVISION_USN_ATTRIBUTE)
817 if len(delta.items()) > 1:
818 delta.dn = refentry.dn
822 def simple_update_basesamdb(newpaths, paths, names):
823 """Update the provision container db: sam.ldb
824 This function is aimed at very old provision (before alpha9)
826 :param newpaths: List of paths for different provision objects from the reference provision
827 :param paths: List of paths for different provision objects from the upgraded provision
828 :param names: List of key provision parameters"""
830 message(SIMPLE, "Copy samdb")
831 shutil.copy(newpaths.samdb,paths.samdb)
833 message(SIMPLE, "Update partitions filename if needed")
834 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
835 configldb = os.path.join(paths.private_dir, "configuration.ldb")
836 usersldb = os.path.join(paths.private_dir, "users.ldb")
837 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
839 if not os.path.isdir(samldbdir):
841 os.chmod(samldbdir,0700)
842 if os.path.isfile(schemaldb):
843 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb" % str(names.schemadn).upper()))
845 if os.path.isfile(usersldb):
846 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb" % str(names.rootdn).upper()))
848 if os.path.isfile(configldb):
849 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb" % str(names.configdn).upper()))
853 def update_privilege(ref_private_path, cur_private_path):
854 """Update the privilege database
856 :param ref_private_path: Path to the private directory of the reference provision.
857 :param cur_private_path: Path to the private directory of the current (and to be updated) provision."""
858 message(SIMPLE, "Copy privilege")
859 shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
860 os.path.join(cur_private_path, "privilege.ldb"))
863 def update_samdb(ref_samdb, samdb, names, highestUSN):
864 """Upgrade the SAM DB contents for all the provision partitions
866 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference provision
867 :param samdb: An LDB object connected to the sam.ldb of the update provision
868 :param names: List of key provision parameters
869 :param highestUSN: The highest USN modified by provision/upgradeprovision last time"""
871 message(SIMPLE, "Starting update of samdb")
872 ret = update_partition(ref_samdb, samdb, str(names.rootdn), names, 1, highestUSN)
874 message(SIMPLE,"Update of samdb finished")
877 message(SIMPLE,"Update failed")
881 def update_machine_account_password(samdb, secrets_ldb, names):
882 """Update (change) the password of the current DC both in the SAM db and in secret one
884 :param samdb: An LDB object related to the sam.ldb file of a given provision
885 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given provision
886 :param names: List of key provision parameters"""
888 message(SIMPLE,"Update machine account")
889 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
890 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
891 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
892 assert(len(res) == 1)
894 msg = Message(res[0].dn)
895 machinepass = samba.generate_random_password(128, 255)
896 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
899 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
900 attrs=["msDs-keyVersionNumber"])
901 assert(len(res) == 1)
902 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
904 secretsdb_self_join(secrets_ldb, domain=names.domain,
905 realm=names.realm or sambaopts._lp.get('realm'),
906 domainsid=names.domainsid,
907 dnsdomain=names.dnsdomain,
908 netbiosname=names.netbiosname,
909 machinepass=machinepass,
910 key_version_number=kvno,
911 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
913 raise ProvisioningError("Unable to find a Secure Channel of type SEC_CHAN_BDC")
916 def update_gpo(paths,creds,session,names):
917 """Create missing GPO file object if needed
919 Set ACL correctly also.
921 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
922 if not os.path.isdir(dir):
923 create_gpo_struct(dir)
925 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
926 if not os.path.isdir(dir):
927 create_gpo_struct(dir)
928 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
929 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
930 names.domaindn, samdb, lp)
933 def getOEMInfo(samdb, rootdn):
934 """Return OEM Information on the top level
935 Samba4 use to store version info in this field
937 :param samdb: An LDB object connect to sam.ldb
938 :param rootdn: Root DN of the domain
939 :return: The content of the field oEMInformation (if any)"""
940 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
941 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
943 info = res[0]["oEMInformation"]
950 def updateProvisionUSN(samdb, names):
951 """Update the field provisionUSN in sam.ldb
952 This field is used to track the highest USN of a modified or created object.
953 This value is used afterward by next provision to figure out if the field have been
954 modified since last provision.
956 :param samdb: An LDB object connect to sam.ldb
957 :param names: Key provision parameters"""
958 message(SIMPLE,"Updating the highest USN modified by upgrade: This is a stub function")
962 def updateOEMInfo(samdb, names):
963 res = samdb.search(expression="(objectClass=*)", base=str(names.rootdn),
964 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
966 info = res[0]["oEMInformation"]
967 info = "%s, upgrade to %s" % (info, version)
969 delta.dn = Dn(samdb, str(res[0]["dn"]))
970 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
975 def setup_path(file):
976 return os.path.join(setup_dir, file)
979 if __name__ == '__main__':
980 # From here start the big steps of the program
981 # First get files paths
982 paths = get_paths(param, smbconf=smbconf)
983 paths.setup = setup_dir
984 # Get ldbs with the system session, it is needed for searching provision parameters
985 session = system_session()
987 # This variable will hold the last provision USN once if it exists.
988 lastProvisionUSN = getLastProvisionUSN(paths, creds, session, lp)
990 ldbs = get_ldbs(paths, creds, session, lp)
991 ldbs.startTransactions()
993 # Guess all the needed names (variables in fact) from the current
995 names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, paths, smbconf, lp)
997 # Objects will be created with the admin session (not anymore system session)
998 adm_session = admin_session(lp, str(names.domainsid))
999 # So we reget handle on objects
1000 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1003 if not sanitychecks(ldbs.sam, names):
1004 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct them before rerunning upgradeprovision")
1007 # Let's see provision parameters
1008 print_provision_key_parameters(names)
1010 # With all this information let's create a fresh new provision used as reference
1011 message(SIMPLE, "Creating a reference provision")
1012 provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1013 prefix="referenceprovision")
1014 newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1018 # We need to get a list of object which SD is directly computed from
1019 # defaultSecurityDescriptor.
1020 # This will allow us to know which object we can rebuild the SD in case
1021 # of change of the parent's SD or of the defaultSD.
1022 # Get file paths of this new provision
1023 newpaths = get_paths(param, targetdir=provisiondir)
1024 new_ldbs = get_ldbs(newpaths, creds, session, lp)
1025 new_ldbs.startTransactions()
1027 # Populate some associative array to ease the update process
1028 populate_backlink(new_ldbs.sam, names.schemadn) # List of attribute which are backlink
1029 populate_dnsyntax(new_ldbs.sam, names.schemadn) # List of attribute with ASN DN synthax)
1031 update_privilege(newpaths.private_dir,paths.private_dir)
1032 oem = getOEMInfo(ldbs.sam, names.rootdn)
1033 # Do some modification on sam.ldb
1034 ldbs.groupedCommit()
1035 if re.match(".*alpha((9)|(\d\d+)).*",str(oem)):
1036 # Starting from alpha9 we can consider that the structure is quite ok and that we should do only dela
1037 new_ldbs.groupedCommit()
1038 delta_update_basesamdb(newpaths, paths, creds, session, lp)
1039 ldbs.startTransactions()
1040 new_ldbs.startTransactions()
1042 simple_update_basesamdb(newpaths, paths, names)
1043 ldbs = get_ldbs(paths, creds, session, lp)
1044 ldbs.startTransactions()
1047 if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSN):
1048 message(SIMPLE,"Rollbacking every changes. Check the reason of the problem")
1049 message(SIMPLE,"In any case your system as it was before the upgrade")
1050 ldbs.groupedRollback()
1051 new_ldbs.groupedRollback()
1052 shutil.rmtree(provisiondir)
1055 update_secrets(new_ldbs.secrets,ldbs.secrets)
1056 update_machine_account_password(ldbs.sam,ldbs.secrets, names)
1058 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
1059 # to recreate them with the good form but with system account and then give the ownership to admin ...
1060 message(SIMPLE, "Updating SD")
1061 if not re.match(r'alpha(9|\d\d+)',str(oem)):
1062 rebuild_sd(ldbs.sam,names)
1064 # We rebuild SD only when we do not have a lastProvisionUSN because otherwise SD have been already updated if needed
1065 if lastProvisionUSN == 0:
1066 ldbs.sam.set_session_info(adm_session)
1067 rebuild_sd(ldbs.sam, names)
1068 check_updated_sd(new_ldbs.sam,ldbs.sam, names)
1070 updateOEMInfo(ldbs.sam,names)
1071 updateProvisionUSN(ldbs.sam,names)
1072 ldbs.groupedCommit()
1073 new_ldbs.groupedCommit()
1074 message(SIMPLE, "Upgrade finished !")
1075 # remove reference provision now that everything is done !
1076 shutil.rmtree(provisiondir)