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/>.
31 # Allow to run from s4 source directory (without installing samba)
32 sys.path.insert(0, "bin/python")
35 import samba.getopt as options
36 from samba.credentials import DONT_USE_KERBEROS
37 from samba.auth import system_session, admin_session
38 from samba import Ldb, version
39 from ldb import (SCOPE_SUBTREE, SCOPE_BASE, FLAG_MOD_REPLACE,
40 FLAG_MOD_ADD, FLAG_MOD_DELETE, MessageElement, Message, Dn)
41 from samba import param
42 from samba.misc import messageEltFlagToString
43 from samba.provision import (find_setup_dir, get_domain_descriptor,
44 get_config_descriptor, secretsdb_self_join, set_gpo_acl,
45 getpolicypath, create_gpo_struct, ProvisioningError)
46 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
47 from samba.dcerpc import security
48 from samba.ndr import ndr_unpack
49 from samba.dcerpc.misc import SEC_CHAN_BDC
50 from samba.upgradehelpers import dn_sort, get_paths, newprovision, find_provision_key_parameters
53 replace=2^FLAG_MOD_REPLACE
55 delete=2^FLAG_MOD_DELETE
57 #Errors are always logged
66 __docformat__ = "restructuredText"
68 # Attributes that are never copied from the reference provision (even if they
69 # do not exist in the destination object).
70 # This is most probably because they are populated automatcally when object is
72 # This also apply to imported object from reference provision
73 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1, "objectGUID": 1, "replPropertyMetaData": 1, "uSNChanged": 1,
74 "uSNCreated": 1, "parentGUID": 1, "objectCategory": 1, "distinguishedName": 1,
75 "showInAdvancedViewOnly": 1, "instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
76 "nTMixedDomain": 1, "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
77 "dBCSPwd":1, "supplementalCredentials":1, "gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
78 "maxPwdAge":1, "mail":1, "secret":1, "possibleInferiors":1, "sAMAccountType":1}
80 # Usually for an object that already exists we do not overwrite attributes as
81 # they might have been changed for good reasons. Anyway for a few of them it's
82 # mandatory to replace them otherwise the provision will be broken somehow.
83 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace, "systemOnly":replace, "searchFlags":replace,
84 "mayContain":replace, "systemFlags":replace, "description":replace,
85 "oEMInformation":never, "operatingSystemVersion":replace, "adminPropertyPages":replace,
86 "defaultSecurityDescriptor": replace, "wellKnownObjects":replace, "privilege":delete, "groupType":replace,
87 "rIDAvailablePool": never}
92 def define_what_to_log(opts):
96 if opts.debugchangesd:
97 what = what | CHANGESD
100 if opts.debugprovision:
101 what = what | PROVISION
103 what = what | CHANGEALL
107 parser = optparse.OptionParser("provision [options]")
108 sambaopts = options.SambaOptions(parser)
109 parser.add_option_group(sambaopts)
110 parser.add_option_group(options.VersionOptions(parser))
111 credopts = options.CredentialsOptions(parser)
112 parser.add_option_group(credopts)
113 parser.add_option("--setupdir", type="string", metavar="DIR",
114 help="directory with setup files")
115 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
116 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
117 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
118 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
119 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
120 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
122 opts = parser.parse_args()[0]
124 handler = logging.StreamHandler(sys.stdout)
125 upgrade_logger = logging.getLogger("upgradeprovision")
126 upgrade_logger.addHandler(handler)
128 provision_logger = logging.getLogger("provision")
129 provision_logger.addHandler(handler)
131 whatToLog = define_what_to_log(opts)
133 def message(what, text):
134 """Print a message if this message type has been selected to be printed
136 :param what: Category of the message
137 :param text: Message to print """
138 if (whatToLog & what) or what <= 0:
139 upgrade_logger.info("%s", text)
141 if len(sys.argv) == 1:
142 opts.interactive = True
143 lp = sambaopts.get_loadparm()
144 smbconf = lp.configfile
146 creds = credopts.get_credentials(lp)
147 creds.set_kerberos_state(DONT_USE_KERBEROS)
148 setup_dir = opts.setupdir
149 if setup_dir is None:
150 setup_dir = find_setup_dir()
152 session = system_session()
154 def identic_rename(ldbobj,dn):
155 """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified.
157 :param lbdobj: An Ldb Object
158 :param dn: DN of the object to manipulate """
159 (before,sep,after)=str(dn).partition('=')
160 ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
161 ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
164 def populate_backlink(newpaths,creds,session,schemadn):
165 """Populate an array with all the back linked attributes
167 This attributes that are modified automaticaly when
168 front attibutes are changed
170 :param newpaths: a list of paths for different provision objects
171 :param creds: credential for the authentification
172 :param session: session for connexion
173 :param schemadn: DN of the schema for the partition"""
174 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
175 linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
176 backlinked.extend(linkedAttHash.values())
178 def populate_dnsyntax(newpaths,creds,session,schemadn):
179 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
181 :param newpaths: a list of paths for different provision objects
182 :param creds: credential for the authentification
183 :param session: session for connexion
184 :param schemadn: DN of the schema for the partition"""
185 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
186 res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)),
187 scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
189 dn_syntax_att.append(elem["lDAPDisplayName"])
192 def sanitychecks(credentials,session_info,names,paths):
193 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
195 :param creds: credential for the authentification
196 :param session_info: session for connexion
197 :param names: list of key provision parameters
198 :param paths: list of path to provision object
199 :return: Status of check (1 for Ok, 0 for not Ok) """
200 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
202 sam_ldb.set_session_info(session)
203 res = sam_ldb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
204 scope=SCOPE_SUBTREE, attrs=["dn"], controls=["search_options:1:2"])
206 print "No DC found, your provision is most probably hardly broken !"
209 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
210 domain with more than one DC, please demote the other(s) DC(s) before upgrading"%len(res)
216 def print_provision_key_parameters(names):
217 """Do a a pretty print of provision parameters
219 :param names: list of key provision parameters """
220 message(GUESS, "rootdn :"+str(names.rootdn))
221 message(GUESS, "configdn :"+str(names.configdn))
222 message(GUESS, "schemadn :"+str(names.schemadn))
223 message(GUESS, "serverdn :"+str(names.serverdn))
224 message(GUESS, "netbiosname :"+names.netbiosname)
225 message(GUESS, "defaultsite :"+names.sitename)
226 message(GUESS, "dnsdomain :"+names.dnsdomain)
227 message(GUESS, "hostname :"+names.hostname)
228 message(GUESS, "domain :"+names.domain)
229 message(GUESS, "realm :"+names.realm)
230 message(GUESS, "invocationid:"+names.invocation)
231 message(GUESS, "policyguid :"+names.policyid)
232 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
233 message(GUESS, "domainsid :"+str(names.domainsid))
234 message(GUESS, "domainguid :"+names.domainguid)
235 message(GUESS, "ntdsguid :"+names.ntdsguid)
236 message(GUESS, "domainlevel :"+str(names.domainlevel))
239 def handle_security_desc(ischema, att, msgElt, hashallSD, old, new):
240 """Check if the security descriptor has been modified.
242 This function also populate a hash used for the upgrade process.
243 :param ischema: Boolean that indicate if it's the schema that is updated
244 :param att: Name of the attribute
245 :param msgElt: MessageElement object
246 :param hashallSD: Hash table with DN as key and the old SD as value
247 :param old: The updated LDAP object
248 :param new: The reference LDAP object
249 :return: 1 to indicate that the attribute should be kept, 0 for discarding it
251 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
253 hashSD["oldSD"] = old[0][att]
254 hashSD["newSD"] = new[0][att]
255 hashallSD[str(old[0].dn)] = hashSD
257 if att == "nTSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
260 hashSD["oldSD"] = ndr_unpack(security.descriptor, str(old[0][att]))
261 hashSD["newSD"] = ndr_unpack(security.descriptor, str(new[0][att]))
262 hashallSD[str(old[0].dn)] = hashSD
267 def handle_special_case(att, delta, new, old, ischema):
268 """Define more complicate update rules for some attributes
270 :param att: The attribute to be updated
271 :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
272 :param new: The reference object
273 :param old: The Updated object
274 :param ischema: A boolean that indicate that the attribute is part of a schema object
275 :return: Tru to indicate that the attribute should be kept, False for discarding it
277 flag = delta.get(att).flags()
278 if (att == "gPLink" or att == "gPCFileSysPath") and \
279 flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
282 if att == "forceLogoff":
283 ref=0x8000000000000000
284 oldval=int(old[0][att][0])
285 newval=int(new[0][att][0])
286 ref == old and ref == abs(new)
288 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
291 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s" % (str(names.schemadn))\
292 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
295 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
298 if ((att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
302 for elem in old[0][att]:
304 newval.append(str(elem))
306 for elem in new[0][att]:
307 if not hash.has_key(str(elem)):
309 newval.append(str(elem))
311 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
316 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
318 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
322 def update_secrets(newpaths, paths, creds, session):
323 """Update secrets.ldb
325 :param newpaths: a list of paths for different provision objects from the
327 :param paths: a list of paths for different provision objects from the
329 :param creds: credential for the authentification
330 :param session: session for connexion"""
332 message(SIMPLE, "update secrets.ldb")
333 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session,
334 credentials=creds,lp=lp)
335 secrets_ldb = Ldb(paths.secrets, session_info=session,
336 credentials=creds,lp=lp, options=["modules:samba_secrets"])
337 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="",
339 current = secrets_ldb.search(expression="dn=@MODULES",base="",
341 delta = secrets_ldb.msg_diff(current[0],reference[0])
342 delta.dn = current[0].dn
343 secrets_ldb.modify(delta)
345 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
346 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
347 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
348 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
355 for i in range(0,len(reference)):
356 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
358 # Create a hash for speeding the search of existing object in the
360 for i in range(0,len(current)):
361 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
363 for k in hash_new.keys():
364 if not hash.has_key(k):
365 listMissing.append(hash_new[k])
367 listPresent.append(hash_new[k])
369 for entry in listMissing:
370 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
371 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
372 delta = secrets_ldb.msg_diff(empty,reference[0])
373 for att in hashAttrNotCopied.keys():
375 message(CHANGE, "Entry %s is missing from secrets.ldb"%reference[0].dn)
377 message(CHANGE, " Adding attribute %s"%att)
378 delta.dn = reference[0].dn
379 secrets_ldb.add(delta)
381 for entry in listPresent:
382 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
383 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
384 delta = secrets_ldb.msg_diff(current[0],reference[0])
385 for att in hashAttrNotCopied.keys():
389 message(CHANGE, "Found attribute name on %s, must rename the DN "%(current[0].dn))
390 identic_rename(secrets_ldb,reference[0].dn)
394 for entry in listPresent:
395 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
396 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
397 delta = secrets_ldb.msg_diff(current[0],reference[0])
398 for att in hashAttrNotCopied.keys():
402 message(CHANGE, " Adding/Changing attribute %s to %s"%(att,current[0].dn))
404 delta.dn = current[0].dn
405 secrets_ldb.modify(delta)
408 def dump_denied_change(dn,att,flagtxt,current,reference):
409 """Print detailed information about why a changed is denied
411 :param dn: DN of the object which attribute is denied
412 :param att: Attribute that was supposed to be upgraded
413 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
414 :param current: Value(s) of the current attribute
415 :param reference: Value(s) of the reference attribute"""
417 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
418 if att != "objectSid" :
420 for e in range(0,len(current)):
421 message(CHANGE, "old %d : %s"%(i,str(current[e])))
423 if reference != None:
425 for e in range(0,len(reference)):
426 message(CHANGE, "new %d : %s"%(i,str(reference[e])))
429 message(CHANGE, "old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
430 message(CHANGE, "new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
433 def handle_special_add(sam_ldb,dn,names):
434 """Handle special operation (like remove) on some object needed during upgrade
436 This is mostly due to wrong creation of the object in previous provision.
437 :param sam_ldb: An Ldb object representing the SAM database
438 :param dn: DN of the object to inspect
439 :param names: list of key provision parameters"""
441 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
442 #This entry was misplaced lets remove it if it exists
443 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
445 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
446 #This entry was misplaced lets remove it if it exists
447 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
449 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
450 #This entry was misplaced lets remove it if it exists
451 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
453 if dntoremove != None:
454 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
456 message(CHANGE, "Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
457 sam_ldb.delete(res[0]["dn"])
460 def check_dn_nottobecreated(hash, index, listdn):
461 """Check if one of the DN present in the list has a creation order greater than the current.
463 Hash is indexed by dn to be created, with each key is associated the creation order
464 First dn to be created has the creation order 0, second has 1, ...
465 Index contain the current creation order
467 :param hash: Hash holding the different DN of the object to be created as key
468 :param index: Current creation order
469 :param listdn: List of DNs on which the current DN depends on
470 :return: None if the current object do not depend on other object or if all object have been
475 key = str(dn).lower()
476 if hash.has_key(key) and hash[key] > index:
481 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
482 """Add a new object if the dependencies are satisfied
484 The function add the object if the object on which it depends are already created
485 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
486 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
487 :param dn: DN of the object to be added
488 :param names: List of key provision parameters
489 :param basedn: DN of the partition to be updated
490 :param hash: Hash holding the different DN of the object to be created as key
491 :param index: Current creation order
492 :return: True if the object was created False otherwise"""
493 handle_special_add(sam_ldb,dn,names)
494 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
495 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
497 delta = sam_ldb.msg_diff(empty,reference[0])
498 for att in hashAttrNotCopied.keys():
500 for att in backlinked:
502 depend_on_yettobecreated = None
503 for att in dn_syntax_att:
504 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
505 if depend_on_yet_tobecreated != None:
506 message(CHANGE, "Object %s depends on %s in attribute %s, delaying the creation"
507 %(str(dn),depend_on_yet_tobecreated,str(att)))
510 message(CHANGE, "Object %s will be added"%dn)
511 sam_ldb.add(delta,["relax:0"])
515 def gen_dn_index_hash(listMissing):
516 """Generate a hash associating the DN to its creation order
518 :param listMissing: List of DN
519 :return: Hash with DN as keys and creation order as values"""
521 for i in range(0,len(listMissing)):
522 hash[str(listMissing[i]).lower()] = i
526 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
527 """Add the missing object whose DN is the list
529 The function add the object if the object on which it depends are already created
530 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
531 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
532 :param dn: DN of the object to be added
533 :param names: List of key provision parameters
534 :param basedn: DN of the partition to be updated
535 :param list: List of DN to be added in the upgraded provision"""
539 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
541 listMissing = listDefered
543 hashMissing = gen_dn_index_hash(listMissing)
544 for dn in listMissing:
545 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
548 #DN can't be created because it depends on some other DN in the list
549 listDefered.append(dn)
550 if len(listDefered) != 0:
551 raise ProvisioningError("Unable to insert missing elements: circular references")
554 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
555 """Check differences between the reference provision and the upgraded one.
557 It looks for all objects which base DN is name. If ischema is "false" then
558 the scan is done in cross partition mode.
559 If "ischema" is true, then special handling is done for dealing with schema
561 This function will also add the missing object and update existing object to add
562 or remove attributes that were missing.
563 :param newpaths: List of paths for different provision objects from the reference provision
564 :param paths: List of paths for different provision objects from the upgraded provision
565 :param creds: Credential for the authentification
566 :param session: Session for connexion
567 :param basedn: DN of the partition to update
568 :param names: List of key provision parameters
569 :param ischema: Boolean indicating if the update is about the schema only
570 :return: Hash of security descriptor to update"""
579 # Connect to the reference provision and get all the attribute in the
580 # partition referred by name
581 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
582 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
583 sam_ldb.transaction_start()
586 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
587 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
589 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
590 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
592 sam_ldb.transaction_cancel()
595 sam_ldb.transaction_commit()
596 # Create a hash for speeding the search of new object
597 for i in range(0,len(reference)):
598 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
600 # Create a hash for speeding the search of existing object in the
602 for i in range(0,len(current)):
603 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
605 for k in hash_new.keys():
606 if not hash.has_key(k):
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)
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 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
627 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
628 # Load the schema from the one we computed earlier
629 sam_ldb.set_schema_from_ldb(schema.ldb)
630 # And now we can connect to the DB - the schema won't be loaded
632 sam_ldb.connect(paths.samdb)
634 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
636 sam_ldb.transaction_start()
638 # XXX: This needs to be wrapped in try/except so we
639 # abort on exceptions.
640 message(SIMPLE, "There are %d missing objects"%(len(listMissing)))
641 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
643 for dn in listPresent:
644 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
645 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
646 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
647 message(CHANGE, "Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
648 identic_rename(sam_ldb,reference[0].dn)
649 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
651 delta = sam_ldb.msg_diff(current[0],reference[0])
652 for att in hashAttrNotCopied.keys():
654 for att in backlinked:
656 delta.remove("parentGUID")
660 msgElt = delta.get(att)
666 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
669 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
670 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
673 if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
674 if opts.debugchange or opts.debugall:
676 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
678 # FIXME: Should catch an explicit exception here
679 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
682 if len(delta.items()) >1:
683 attributes=",".join(delta.keys())
684 message(CHANGE, "%s is different from the reference one, changed attributes: %s"%(dn,attributes))
685 changed = changed + 1
686 sam_ldb.modify(delta)
688 sam_ldb.transaction_cancel()
691 sam_ldb.transaction_commit()
692 message(SIMPLE, "There are %d changed objects"%(changed))
696 def check_updated_sd(newpaths, paths, creds, session, names):
697 """Check if the security descriptor in the upgraded provision are the same as the reference
699 :param newpaths: List of paths for different provision objects from the reference provision
700 :param paths: List of paths for different provision objects from the upgraded provision
701 :param creds: Credential for the authentification
702 :param session: Session for connexion
703 :param basedn: DN of the partition to update
704 :param names: List of key provision parameters"""
705 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
706 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
707 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
708 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
710 for i in range(0,len(reference)):
711 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
713 for i in range(0,len(current)):
714 key = str(current[i]["dn"]).lower()
715 if hash_new.has_key(key):
716 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
717 if sddl != hash_new[key]:
718 print "%s new sddl/sddl in ref"%key
719 print "%s\n%s"%(sddl,hash_new[key])
722 def update_sd(paths, creds, session, names):
723 """Update security descriptor of the current provision
725 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
726 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
727 and so SD can be safely recalculated from scratch to get them right.
729 :param paths: List of paths for different provision objects from the upgraded provision
730 :param creds: Credential for the authentification
731 :param session: Session for connexion
732 :param names: List of key provision parameters"""
734 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
735 sam_ldb.transaction_start()
737 # First update the SD for the rootdn
738 sam_ldb.set_session_info(session)
739 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
740 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
742 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
743 descr = get_domain_descriptor(names.domainsid)
744 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
745 sam_ldb.modify(delta,["recalculate_sd:0"])
747 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
749 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
750 descr = get_config_descriptor(names.domainsid)
751 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
752 sam_ldb.modify(delta,["recalculate_sd:0"])
754 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
756 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
757 descr = get_schema_descriptor(names.domainsid)
758 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
759 sam_ldb.modify(delta,["recalculate_sd:0"])
763 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
765 if not (str(obj["dn"]) == str(names.rootdn) or
766 str(obj["dn"]) == str(names.configdn) or \
767 str(obj["dn"]) == str(names.schemadn)):
768 hash[str(obj["dn"])] = obj["whenCreated"]
770 listkeys = hash.keys()
771 listkeys.sort(dn_sort)
776 delta.dn = Dn(sam_ldb,key)
777 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
778 sam_ldb.modify(delta,["recalculate_sd:0"])
780 # XXX: We should always catch an explicit exception.
781 # What could go wrong here?
782 sam_ldb.transaction_cancel()
783 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
784 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
785 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
788 sam_ldb.transaction_cancel()
791 sam_ldb.transaction_commit()
794 def update_basesamdb(newpaths, paths, names):
795 """Update the provision container db: sam.ldb
797 :param newpaths: List of paths for different provision objects from the reference provision
798 :param paths: List of paths for different provision objects from the upgraded provision
799 :param names: List of key provision parameters"""
801 message(SIMPLE, "Copy samdb")
802 shutil.copy(newpaths.samdb,paths.samdb)
804 message(SIMPLE, "Update partitions filename if needed")
805 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
806 configldb = os.path.join(paths.private_dir, "configuration.ldb")
807 usersldb = os.path.join(paths.private_dir, "users.ldb")
808 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
810 if not os.path.isdir(samldbdir):
812 os.chmod(samldbdir,0700)
813 if os.path.isfile(schemaldb):
814 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb" % str(names.schemadn).upper()))
816 if os.path.isfile(usersldb):
817 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb" % str(names.rootdn).upper()))
819 if os.path.isfile(configldb):
820 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb" % str(names.configdn).upper()))
824 def update_privilege(newpaths, paths):
825 """Update the privilege database
827 :param newpaths: List of paths for different provision objects from the reference provision
828 :param paths: List of paths for different provision objects from the upgraded provision"""
829 message(SIMPLE, "Copy privilege")
830 shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"),
831 os.path.join(paths.private_dir, "privilege.ldb"))
834 def update_samdb(newpaths, paths, creds, session, names):
835 """Upgrade the SAM DB contents for all the provision
837 :param newpaths: List of paths for different provision objects from the reference provision
838 :param paths: List of paths for different provision objects from the upgraded provision
839 :param creds: Credential for the authentification
840 :param session: Session for connexion
841 :param names: List of key provision parameters"""
843 message(SIMPLE, "Doing schema update")
844 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
845 message(SIMPLE, "Done with schema update")
846 message(SIMPLE, "Scanning whole provision for updates and additions")
847 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
848 message(SIMPLE, "Done with scanning")
851 def update_machine_account_password(paths, creds, session, names):
852 """Update (change) the password of the current DC both in the SAM db and in secret one
854 :param paths: List of paths for different provision objects from the upgraded provision
855 :param creds: Credential for the authentification
856 :param session: Session for connexion
857 :param names: List of key provision parameters"""
859 secrets_ldb = Ldb(paths.secrets, session_info=session,
860 credentials=creds,lp=lp)
861 secrets_ldb.transaction_start()
862 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
863 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
864 sam_ldb.transaction_start()
865 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
866 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
867 assert(len(res) == 1)
869 msg = Message(res[0].dn)
870 machinepass = samba.generate_random_password(128, 255)
871 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
874 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
875 attrs=["msDs-keyVersionNumber"])
876 assert(len(res) == 1)
877 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
879 secretsdb_self_join(secrets_ldb, domain=names.domain,
880 realm=names.realm or sambaopts._lp.get('realm'),
881 domainsid=names.domainsid,
882 dnsdomain=names.dnsdomain,
883 netbiosname=names.netbiosname,
884 machinepass=machinepass,
885 key_version_number=kvno,
886 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
887 sam_ldb.transaction_prepare_commit()
888 secrets_ldb.transaction_prepare_commit()
889 sam_ldb.transaction_commit()
890 secrets_ldb.transaction_commit()
892 secrets_ldb.transaction_cancel()
895 def update_gpo(paths,creds,session,names):
896 """Create missing GPO file object if needed
898 Set ACL correctly also.
900 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
901 if not os.path.isdir(dir):
902 create_gpo_struct(dir)
904 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
905 if not os.path.isdir(dir):
906 create_gpo_struct(dir)
907 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
908 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
909 names.domaindn, samdb, lp)
912 def updateOEMInfo(paths, creds, session,names):
913 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
914 options=["modules:samba_dsdb"])
915 res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
916 scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
918 info = res[0]["oEMInformation"]
919 info = "%s, upgrade to %s"%(info,version)
921 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
922 descr = get_schema_descriptor(names.domainsid)
923 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
925 sam_ldb.modify(delta)
928 def setup_path(file):
929 return os.path.join(setup_dir, file)
932 if __name__ == '__main__':
933 # From here start the big steps of the program
934 # First get files paths
935 paths=get_paths(param,smbconf=smbconf)
936 paths.setup = setup_dir
937 # Guess all the needed names (variables in fact) from the current
940 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
941 if not sanitychecks(creds,session,names,paths):
942 message(SIMPLE, "Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
945 print_provision_key_parameters(names)
946 # With all this information let's create a fresh new provision used as reference
947 message(SIMPLE, "Creating a reference provision")
948 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
949 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, provision_logger)
950 # Get file paths of this new provision
951 newpaths = get_paths(param, targetdir=provisiondir)
952 populate_backlink(newpaths, creds, session,names.schemadn)
953 populate_dnsyntax(newpaths, creds, session,names.schemadn)
954 # Check the difference
955 update_basesamdb(newpaths, paths, names)
958 update_samdb(newpaths, paths, creds, session, names)
959 update_secrets(newpaths, paths, creds, session)
960 update_privilege(newpaths, paths)
961 update_machine_account_password(paths, creds, session, names)
962 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
963 # to recreate them with the good form but with system account and then give the ownership to admin ...
964 admin_session_info = admin_session(lp, str(names.domainsid))
965 message(SIMPLE, "Updating SD")
966 update_sd(paths, creds, session,names)
967 update_sd(paths, creds, admin_session_info, names)
968 check_updated_sd(newpaths, paths, creds, session, names)
969 updateOEMInfo(paths,creds,session,names)
970 message(SIMPLE, "Upgrade finished !")
971 # remove reference provision now that everything is done !
972 shutil.rmtree(provisiondir)