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/>.
30 # Allow to run from s4 source directory (without installing samba)
31 sys.path.insert(0, "bin/python")
34 import samba.getopt as options
35 from samba.credentials import DONT_USE_KERBEROS
36 from samba.auth import system_session, admin_session
37 from samba import Ldb, version
38 from ldb import SCOPE_SUBTREE, SCOPE_BASE, \
39 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,\
40 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, get_config_descriptor, secretsdb_self_join,set_gpo_acl,getpolicypath,create_gpo_struct
44 from samba.provisionexceptions import ProvisioningError
45 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
46 from samba.dcerpc import security
47 from samba.ndr import ndr_unpack
48 from samba.dcerpc.misc import SEC_CHAN_BDC
49 from samba.upgradehelpers import dn_sort, get_paths, newprovision, find_provision_key_parameters
52 replace=2^FLAG_MOD_REPLACE
54 delete=2^FLAG_MOD_DELETE
56 #Errors are always logged
65 __docformat__ = "restructuredText"
67 # Attributes that are never copied from the reference provision (even if they
68 # do not exist in the destination object).
69 # This is most probably because they are populated automatcally when object is
71 # This also apply to imported object from reference provision
72 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1, "objectGUID": 1, "replPropertyMetaData": 1, "uSNChanged": 1,
73 "uSNCreated": 1, "parentGUID": 1, "objectCategory": 1, "distinguishedName": 1,
74 "showInAdvancedViewOnly": 1, "instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
75 "nTMixedDomain": 1, "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
76 "dBCSPwd":1, "supplementalCredentials":1, "gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
77 "maxPwdAge":1, "mail":1, "secret":1, "possibleInferiors":1, "sAMAccountType":1}
79 # Usually for an object that already exists we do not overwrite attributes as
80 # they might have been changed for good reasons. Anyway for a few of them it's
81 # mandatory to replace them otherwise the provision will be broken somehow.
82 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace, "systemOnly":replace, "searchFlags":replace,
83 "mayContain":replace, "systemFlags":replace, "description":replace,
84 "oEMInformation":never, "operatingSystemVersion":replace, "adminPropertyPages":replace,
85 "defaultSecurityDescriptor": replace, "wellKnownObjects":replace, "privilege":delete, "groupType":replace,
86 "rIDAvailablePool": never}
91 def define_what_to_log(opts):
95 if opts.debugchangesd:
96 what = what | CHANGESD
99 if opts.debugprovision:
100 what = what | PROVISION
102 what = what | CHANGEALL
106 parser = optparse.OptionParser("provision [options]")
107 sambaopts = options.SambaOptions(parser)
108 parser.add_option_group(sambaopts)
109 parser.add_option_group(options.VersionOptions(parser))
110 credopts = options.CredentialsOptions(parser)
111 parser.add_option_group(credopts)
112 parser.add_option("--setupdir", type="string", metavar="DIR",
113 help="directory with setup files")
114 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
115 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
116 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
117 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
118 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
119 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
121 opts = parser.parse_args()[0]
123 whatToLog = define_what_to_log(opts)
125 def messageprovision(text):
126 """Print a message if quiet is not set
128 :param text: Message to print """
129 if opts.debugprovision or opts.debugall:
132 def message(what,text):
133 """Print a message if this message type has been selected to be printed
135 :param what: Category of the message
136 :param text: Message to print """
137 if (whatToLog & what) or what <= 0:
140 if len(sys.argv) == 1:
141 opts.interactive = True
142 lp = sambaopts.get_loadparm()
143 smbconf = lp.configfile
145 creds = credopts.get_credentials(lp)
146 creds.set_kerberos_state(DONT_USE_KERBEROS)
147 setup_dir = opts.setupdir
148 if setup_dir is None:
149 setup_dir = find_setup_dir()
151 session = system_session()
153 def identic_rename(ldbobj,dn):
154 """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified.
156 :param lbdobj: An Ldb Object
157 :param dn: DN of the object to manipulate """
158 (before,sep,after)=str(dn).partition('=')
159 ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
160 ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
163 def populate_backlink(newpaths,creds,session,schemadn):
164 """Populate an array with all the back linked attributes
166 This attributes that are modified automaticaly when
167 front attibutes are changed
169 :param newpaths: a list of paths for different provision objects
170 :param creds: credential for the authentification
171 :param session: session for connexion
172 :param schemadn: DN of the schema for the partition"""
173 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
174 linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
175 backlinked.extend(linkedAttHash.values())
177 def populate_dnsyntax(newpaths,creds,session,schemadn):
178 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
180 :param newpaths: a list of paths for different provision objects
181 :param creds: credential for the authentification
182 :param session: session for connexion
183 :param schemadn: DN of the schema for the partition"""
184 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
185 res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)),
186 scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
188 dn_syntax_att.append(elem["lDAPDisplayName"])
191 def sanitychecks(credentials,session_info,names,paths):
192 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
194 :param creds: credential for the authentification
195 :param session_info: session for connexion
196 :param names: list of key provision parameters
197 :param paths: list of path to provision object
198 :return: Status of check (1 for Ok, 0 for not Ok) """
199 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
201 sam_ldb.set_session_info(session)
202 res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn),
203 scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
205 print "No DC found, your provision is most probalby hardly broken !"
208 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
209 domain with more than one DC, please demote the other(s) DC(s) before upgrading"%len(res)
215 def print_provision_key_parameters(names):
216 """Do a a pretty print of provision parameters
218 :param names: list of key provision parameters """
219 message(GUESS, "rootdn :"+str(names.rootdn))
220 message(GUESS, "configdn :"+str(names.configdn))
221 message(GUESS, "schemadn :"+str(names.schemadn))
222 message(GUESS, "serverdn :"+str(names.serverdn))
223 message(GUESS, "netbiosname :"+names.netbiosname)
224 message(GUESS, "defaultsite :"+names.sitename)
225 message(GUESS, "dnsdomain :"+names.dnsdomain)
226 message(GUESS, "hostname :"+names.hostname)
227 message(GUESS, "domain :"+names.domain)
228 message(GUESS, "realm :"+names.realm)
229 message(GUESS, "invocationid:"+names.invocation)
230 message(GUESS, "policyguid :"+names.policyid)
231 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
232 message(GUESS, "domainsid :"+str(names.domainsid))
233 message(GUESS, "domainguid :"+names.domainguid)
234 message(GUESS, "ntdsguid :"+names.ntdsguid)
235 message(GUESS, "domainlevel :"+str(names.domainlevel))
238 def handle_security_desc(ischema, att, msgElt, hashallSD, old, new):
239 """Check if the security descriptor has been modified.
241 This function also populate a hash used for the upgrade process.
242 :param ischema: Boolean that indicate if it's the schema that is updated
243 :param att: Name of the attribute
244 :param msgElt: MessageElement object
245 :param hashallSD: Hash table with DN as key and the old SD as value
246 :param old: The updated LDAP object
247 :param new: The reference LDAP object
248 :return: 1 to indicate that the attribute should be kept, 0 for discarding it
250 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
252 hashSD["oldSD"] = old[0][att]
253 hashSD["newSD"] = new[0][att]
254 hashallSD[str(old[0].dn)] = hashSD
256 if att == "nTSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
259 hashSD["oldSD"] = ndr_unpack(security.descriptor, str(old[0][att]))
260 hashSD["newSD"] = ndr_unpack(security.descriptor, str(new[0][att]))
261 hashallSD[str(old[0].dn)] = hashSD
266 def handle_special_case(att, delta, new, old, ischema):
267 """Define more complicate update rules for some attributes
269 :param att: The attribute to be updated
270 :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
271 :param new: The reference object
272 :param old: The Updated object
273 :param ischema: A boolean that indicate that the attribute is part of a schema object
274 :return: Tru to indicate that the attribute should be kept, False for discarding it
276 flag = delta.get(att).flags()
277 if (att == "gPLink" or att == "gPCFileSysPath") and \
278 flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
281 if att == "forceLogoff":
282 ref=0x8000000000000000
283 oldval=int(old[0][att][0])
284 newval=int(new[0][att][0])
285 ref == old and ref == abs(new)
287 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
290 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s" % (str(names.schemadn))\
291 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
294 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
297 if ((att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
301 for elem in old[0][att]:
303 newval.append(str(elem))
305 for elem in new[0][att]:
306 if not hash.has_key(str(elem)):
308 newval.append(str(elem))
310 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
315 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
317 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
321 def update_secrets(newpaths, paths, creds, session):
322 """Update secrets.ldb
324 :param newpaths: a list of paths for different provision objects from the reference provision
325 :param paths: a list of paths for different provision objects from the upgraded provision
326 :param creds: credential for the authentification
327 :param session: session for connexion"""
329 message(SIMPLE,"update secrets.ldb")
330 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
331 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
332 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
333 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
334 delta = secrets_ldb.msg_diff(current[0],reference[0])
335 delta.dn = current[0].dn
336 secrets_ldb.modify(delta)
338 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
339 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
340 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
341 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
348 for i in range(0,len(reference)):
349 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
351 # Create a hash for speeding the search of existing object in the
353 for i in range(0,len(current)):
354 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
356 for k in hash_new.keys():
357 if not hash.has_key(k):
358 listMissing.append(hash_new[k])
360 listPresent.append(hash_new[k])
362 for entry in listMissing:
363 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
364 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
365 delta = secrets_ldb.msg_diff(empty,reference[0])
366 for att in hashAttrNotCopied.keys():
368 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
370 message(CHANGE," Adding attribute %s"%att)
371 delta.dn = reference[0].dn
372 secrets_ldb.add(delta)
374 for entry in listPresent:
375 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
376 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
377 delta = secrets_ldb.msg_diff(current[0],reference[0])
378 for att in hashAttrNotCopied.keys():
382 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
383 identic_rename(secrets_ldb,reference[0].dn)
387 for entry in listPresent:
388 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
389 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
390 delta = secrets_ldb.msg_diff(current[0],reference[0])
391 for att in hashAttrNotCopied.keys():
395 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
397 delta.dn = current[0].dn
398 secrets_ldb.modify(delta)
401 def dump_denied_change(dn,att,flagtxt,current,reference):
402 """Print detailed information about why a changed is denied
404 :param dn: DN of the object which attribute is denied
405 :param att: Attribute that was supposed to be upgraded
406 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
407 :param current: Value(s) of the current attribute
408 :param reference: Value(s) of the reference attribute"""
410 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
411 if att != "objectSid" :
413 for e in range(0,len(current)):
414 message(CHANGE,"old %d : %s"%(i,str(current[e])))
416 if reference != None:
418 for e in range(0,len(reference)):
419 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
422 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
423 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
426 def handle_special_add(sam_ldb,dn,names):
427 """Handle special operation (like remove) on some object needed during upgrade
429 This is mostly due to wrong creation of the object in previous provision.
430 :param sam_ldb: An Ldb object representing the SAM database
431 :param dn: DN of the object to inspect
432 :param names: list of key provision parameters"""
434 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
435 #This entry was misplaced lets remove it if it exists
436 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
438 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
439 #This entry was misplaced lets remove it if it exists
440 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
442 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
443 #This entry was misplaced lets remove it if it exists
444 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
446 if dntoremove != None:
447 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
449 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
450 sam_ldb.delete(res[0]["dn"])
452 def check_dn_nottobecreated(hash,index,listdn):
453 """Check if one of the DN present in the list has a creation order greater than the current.
455 Hash is indexed by dn to be created, with each key is associated the creation order
456 First dn to be created has the creation order 0, second has 1, ...
457 Index contain the current creation order
459 :param hash: Hash holding the different DN of the object to be created as key
460 :param index: Current creation order
461 :param listdn: List of DNs on which the current DN depends on
462 :return: None if the current object do not depend on other object or if all object have been
467 key = str(dn).lower()
468 if hash.has_key(key) and hash[key] > index:
473 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
474 """Add a new object if the dependencies are satisfied
476 The function add the object if the object on which it depends are already created
477 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
478 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
479 :param dn: DN of the object to be added
480 :param names: List of key provision parameters
481 :param basedn: DN of the partition to be updated
482 :param hash: Hash holding the different DN of the object to be created as key
483 :param index: Current creation order
484 :return: True if the object was created False otherwise"""
485 handle_special_add(sam_ldb,dn,names)
486 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
487 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
489 delta = sam_ldb.msg_diff(empty,reference[0])
490 for att in hashAttrNotCopied.keys():
492 for att in backlinked:
494 depend_on_yettobecreated = None
495 for att in dn_syntax_att:
496 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
497 if depend_on_yet_tobecreated != None:
498 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
499 %(str(dn),depend_on_yet_tobecreated,str(att)))
502 message(CHANGE,"Object %s will be added"%dn)
503 sam_ldb.add(delta,["relax:0"])
507 def gen_dn_index_hash(listMissing):
508 """Generate a hash associating the DN to its creation order
510 :param listMissing: List of DN
511 :return: Hash with DN as keys and creation order as values"""
513 for i in range(0,len(listMissing)):
514 hash[str(listMissing[i]).lower()] = i
518 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
519 """Add the missing object whose DN is the list
521 The function add the object if the object on which it depends are already created
522 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
523 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
524 :param dn: DN of the object to be added
525 :param names: List of key provision parameters
526 :param basedn: DN of the partition to be updated
527 :param list: List of DN to be added in the upgraded provision"""
531 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
533 listMissing = listDefered
535 hashMissing = gen_dn_index_hash(listMissing)
536 for dn in listMissing:
537 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
540 #DN can't be created because it depends on some other DN in the list
541 listDefered.append(dn)
542 if len(listDefered) != 0:
543 raise ProvisioningError("Unable to insert missing elements: circular references")
546 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
547 """Check differences between the reference provision and the upgraded one.
549 It looks for all objects which base DN is name. If ischema is "false" then
550 the scan is done in cross partition mode.
551 If "ischema" is true, then special handling is done for dealing with schema
553 This function will also add the missing object and update existing object to add
554 or remove attributes that were missing.
555 :param newpaths: List of paths for different provision objects from the reference provision
556 :param paths: List of paths for different provision objects from the upgraded provision
557 :param creds: Credential for the authentification
558 :param session: Session for connexion
559 :param basedn: DN of the partition to update
560 :param names: List of key provision parameters
561 :param ischema: Boolean indicating if the update is about the schema only
562 :return: Hash of security descriptor to update"""
571 # Connect to the reference provision and get all the attribute in the
572 # partition referred by name
573 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
574 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
575 sam_ldb.transaction_start()
577 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
578 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
580 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
581 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
583 sam_ldb.transaction_commit()
584 # Create a hash for speeding the search of new object
585 for i in range(0,len(reference)):
586 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
588 # Create a hash for speeding the search of existing object in the
590 for i in range(0,len(current)):
591 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
593 for k in hash_new.keys():
594 if not hash.has_key(k):
596 listMissing.append(hash_new[k])
598 listPresent.append(hash_new[k])
600 # Sort the missing object in order to have object of the lowest level
601 # first (which can be containers for higher level objects)
602 listMissing.sort(dn_sort)
603 listPresent.sort(dn_sort)
606 # The following lines (up to the for loop) is to load the up to
607 # date schema into our current LDB
608 # a complete schema is needed as the insertion of attributes
609 # and class is done against it
610 # and the schema is self validated
611 # The double ldb open and schema validation is taken from the
612 # initial provision script
613 # it's not certain that it is really needed ....
614 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
615 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
616 # Load the schema from the one we computed earlier
617 sam_ldb.set_schema_from_ldb(schema.ldb)
618 # And now we can connect to the DB - the schema won't be loaded
620 sam_ldb.connect(paths.samdb)
622 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
624 sam_ldb.transaction_start()
625 # XXX: This needs to be wrapped in try/except so we
626 # abort on exceptions.
627 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
628 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
630 for dn in listPresent:
631 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
632 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
633 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
634 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
635 identic_rename(sam_ldb,reference[0].dn)
636 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
638 delta = sam_ldb.msg_diff(current[0],reference[0])
639 for att in hashAttrNotCopied.keys():
641 for att in backlinked:
643 delta.remove("parentGUID")
647 msgElt = delta.get(att)
653 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
656 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
657 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
660 if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
661 if opts.debugchange or opts.debugall:
663 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
665 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
668 if len(delta.items()) >1:
669 attributes=",".join(delta.keys())
670 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
671 changed = changed + 1
672 sam_ldb.modify(delta)
674 sam_ldb.transaction_commit()
675 message(SIMPLE,"There are %d changed objects"%(changed))
679 def check_updated_sd(newpaths, paths, creds, session, names):
680 """Check if the security descriptor in the upgraded provision are the same as the reference
682 :param newpaths: List of paths for different provision objects from the reference provision
683 :param paths: List of paths for different provision objects from the upgraded provision
684 :param creds: Credential for the authentification
685 :param session: Session for connexion
686 :param basedn: DN of the partition to update
687 :param names: List of key provision parameters"""
688 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
689 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
690 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
691 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
693 for i in range(0,len(reference)):
694 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
696 for i in range(0,len(current)):
697 key = str(current[i]["dn"]).lower()
698 if hash_new.has_key(key):
699 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
700 if sddl != hash_new[key]:
701 print "%s new sddl/sddl in ref"%key
702 print "%s\n%s"%(sddl,hash_new[key])
705 def update_sd(paths, creds, session, names):
706 """Update security descriptor of the current provision
708 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
709 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
710 and so SD can be safely recalculated from scratch to get them right.
712 :param paths: List of paths for different provision objects from the upgraded provision
713 :param creds: Credential for the authentification
714 :param session: Session for connexion
715 :param names: List of key provision parameters"""
717 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
718 sam_ldb.transaction_start()
719 # First update the SD for the rootdn
720 sam_ldb.set_session_info(session)
721 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
722 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
724 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
725 descr = get_domain_descriptor(names.domainsid)
726 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
727 sam_ldb.modify(delta,["recalculate_sd:0"])
729 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
731 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
732 descr = get_config_descriptor(names.domainsid)
733 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
734 sam_ldb.modify(delta,["recalculate_sd:0"])
736 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
738 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
739 descr = get_schema_descriptor(names.domainsid)
740 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
741 sam_ldb.modify(delta,["recalculate_sd:0"])
745 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
747 if not (str(obj["dn"]) == str(names.rootdn) or
748 str(obj["dn"]) == str(names.configdn) or \
749 str(obj["dn"]) == str(names.schemadn)):
750 hash[str(obj["dn"])] = obj["whenCreated"]
752 listkeys = hash.keys()
753 listkeys.sort(dn_sort)
758 delta.dn = Dn(sam_ldb,key)
759 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
760 sam_ldb.modify(delta,["recalculate_sd:0"])
762 # XXX: We should always catch an explicit exception.
763 # What could go wrong here?
764 sam_ldb.transaction_cancel()
765 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
766 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
767 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
769 sam_ldb.transaction_commit()
772 def update_basesamdb(newpaths, paths, names):
773 """Update the provision container db: sam.ldb
775 :param newpaths: List of paths for different provision objects from the reference provision
776 :param paths: List of paths for different provision objects from the upgraded provision
777 :param names: List of key provision parameters"""
779 message(SIMPLE,"Copy samdb")
780 shutil.copy(newpaths.samdb,paths.samdb)
782 message(SIMPLE,"Update partitions filename if needed")
783 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
784 configldb=os.path.join(paths.private_dir,"configuration.ldb")
785 usersldb=os.path.join(paths.private_dir,"users.ldb")
786 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
788 if not os.path.isdir(samldbdir):
790 os.chmod(samldbdir,0700)
791 if os.path.isfile(schemaldb):
792 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb"%str(names.schemadn).upper()))
794 if os.path.isfile(usersldb):
795 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb"%str(names.rootdn).upper()))
797 if os.path.isfile(configldb):
798 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb"%str(names.configdn).upper()))
802 def update_privilege(newpaths, paths):
803 """Update the privilege database
805 :param newpaths: List of paths for different provision objects from the reference provision
806 :param paths: List of paths for different provision objects from the upgraded provision"""
807 message(SIMPLE, "Copy privilege")
808 shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"),
809 os.path.join(paths.private_dir, "privilege.ldb"))
812 def update_samdb(newpaths, paths, creds, session, names):
813 """Upgrade the SAM DB contents for all the provision
815 :param newpaths: List of paths for different provision objects from the reference provision
816 :param paths: List of paths for different provision objects from the upgraded provision
817 :param creds: Credential for the authentification
818 :param session: Session for connexion
819 :param names: List of key provision parameters"""
821 message(SIMPLE, "Doing schema update")
822 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
823 message(SIMPLE,"Done with schema update")
824 message(SIMPLE,"Scanning whole provision for updates and additions")
825 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
826 message(SIMPLE,"Done with scanning")
829 def update_machine_account_password(paths, creds, session, names):
830 """Update (change) the password of the current DC both in the SAM db and in secret one
832 :param paths: List of paths for different provision objects from the upgraded provision
833 :param creds: Credential for the authentification
834 :param session: Session for connexion
835 :param names: List of key provision parameters"""
837 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
838 secrets_ldb.transaction_start()
839 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
840 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
841 sam_ldb.transaction_start()
842 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
843 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
844 assert(len(res) == 1)
846 msg = Message(res[0].dn)
847 machinepass = samba.generate_random_password(128, 255)
848 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
851 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
852 attrs=["msDs-keyVersionNumber"])
853 assert(len(res) == 1)
854 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
856 secretsdb_self_join(secrets_ldb, domain=names.domain,
858 domainsid=names.domainsid,
859 dnsdomain=names.dnsdomain,
860 netbiosname=names.netbiosname,
861 machinepass=machinepass,
862 key_version_number=kvno,
863 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
864 sam_ldb.transaction_prepare_commit()
865 secrets_ldb.transaction_prepare_commit()
866 sam_ldb.transaction_commit()
867 secrets_ldb.transaction_commit()
869 secrets_ldb.transaction_cancel()
872 def update_gpo(paths,creds,session,names):
873 """Create missing GPO file object if needed
875 Set ACL correctly also.
877 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
878 if not os.path.isdir(dir):
879 create_gpo_struct(dir)
881 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
882 if not os.path.isdir(dir):
883 create_gpo_struct(dir)
884 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
885 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
886 names.domaindn, samdb, lp)
888 def updateOEMInfo(paths, creds, session,names):
889 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
890 options=["modules:samba_dsdb"])
891 res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
892 scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
894 info = res[0]["oEMInformation"]
895 info = "%s, upgrade to %s"%(info,version)
897 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
898 descr = get_schema_descriptor(names.domainsid)
899 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
901 sam_ldb.modify(delta)
904 def setup_path(file):
905 return os.path.join(setup_dir, file)
908 if __name__ == '__main__':
909 # From here start the big steps of the program
910 # First get files paths
911 paths=get_paths(param,smbconf=smbconf)
912 paths.setup = setup_dir
913 # Guess all the needed names (variables in fact) from the current
916 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
917 if not sanitychecks(creds,session,names,paths):
918 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
921 print_provision_key_parameters(names)
922 # With all this information let's create a fresh new provision used as reference
923 message(SIMPLE,"Creating a reference provision")
924 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
925 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
926 # Get file paths of this new provision
927 newpaths = get_paths(param, targetdir=provisiondir)
928 populate_backlink(newpaths, creds, session,names.schemadn)
929 populate_dnsyntax(newpaths, creds, session,names.schemadn)
930 # Check the difference
931 update_basesamdb(newpaths, paths, names)
934 update_samdb(newpaths, paths, creds, session, names)
935 update_secrets(newpaths, paths, creds, session)
936 update_privilege(newpaths, paths)
937 update_machine_account_password(paths, creds, session, names)
938 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
939 # to recreate them with the good form but with system account and then give the ownership to admin ...
940 admin_session_info = admin_session(lp, str(names.domainsid))
941 message(SIMPLE, "Updating SD")
942 update_sd(paths, creds, session,names)
943 update_sd(paths, creds, admin_session_info, names)
944 check_updated_sd(newpaths, paths, creds, session, names)
945 updateOEMInfo(paths,creds,session,names)
946 message(SIMPLE, "Upgrade finished !")
947 # remove reference provision now that everything is done !
948 shutil.rmtree(provisiondir)