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 import glue
43 from samba.misc import messageEltFlagToString
44 from samba.provision import find_setup_dir, get_domain_descriptor, get_config_descriptor, secretsdb_self_join,set_gpo_acl,getpolicypath,create_gpo_struct
45 from samba.provisionexceptions import 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 whatToLog = define_what_to_log(opts)
126 def messageprovision(text):
127 """Print a message if quiet is not set
129 :param text: Message to print """
130 if opts.debugprovision or opts.debugall:
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:
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 probalby 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 reference provision
326 :param paths: a list of paths for different provision objects from the upgraded provision
327 :param creds: credential for the authentification
328 :param session: session for connexion"""
330 message(SIMPLE,"update secrets.ldb")
331 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
332 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
333 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
334 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
335 delta = secrets_ldb.msg_diff(current[0],reference[0])
336 delta.dn = current[0].dn
337 secrets_ldb.modify(delta)
339 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
340 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
341 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
342 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
349 for i in range(0,len(reference)):
350 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
352 # Create a hash for speeding the search of existing object in the
354 for i in range(0,len(current)):
355 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
357 for k in hash_new.keys():
358 if not hash.has_key(k):
359 listMissing.append(hash_new[k])
361 listPresent.append(hash_new[k])
363 for entry in listMissing:
364 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
365 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
366 delta = secrets_ldb.msg_diff(empty,reference[0])
367 for att in hashAttrNotCopied.keys():
369 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
371 message(CHANGE," Adding attribute %s"%att)
372 delta.dn = reference[0].dn
373 secrets_ldb.add(delta)
375 for entry in listPresent:
376 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
377 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
378 delta = secrets_ldb.msg_diff(current[0],reference[0])
379 for att in hashAttrNotCopied.keys():
383 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
384 identic_rename(secrets_ldb,reference[0].dn)
388 for entry in listPresent:
389 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
390 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
391 delta = secrets_ldb.msg_diff(current[0],reference[0])
392 for att in hashAttrNotCopied.keys():
396 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
398 delta.dn = current[0].dn
399 secrets_ldb.modify(delta)
402 def dump_denied_change(dn,att,flagtxt,current,reference):
403 """Print detailed information about why a changed is denied
405 :param dn: DN of the object which attribute is denied
406 :param att: Attribute that was supposed to be upgraded
407 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
408 :param current: Value(s) of the current attribute
409 :param reference: Value(s) of the reference attribute"""
411 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
412 if att != "objectSid" :
414 for e in range(0,len(current)):
415 message(CHANGE,"old %d : %s"%(i,str(current[e])))
417 if reference != None:
419 for e in range(0,len(reference)):
420 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
423 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
424 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
427 def handle_special_add(sam_ldb,dn,names):
428 """Handle special operation (like remove) on some object needed during upgrade
430 This is mostly due to wrong creation of the object in previous provision.
431 :param sam_ldb: An Ldb object representing the SAM database
432 :param dn: DN of the object to inspect
433 :param names: list of key provision parameters"""
435 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
436 #This entry was misplaced lets remove it if it exists
437 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
439 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
440 #This entry was misplaced lets remove it if it exists
441 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
443 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
444 #This entry was misplaced lets remove it if it exists
445 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
447 if dntoremove != None:
448 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
450 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
451 sam_ldb.delete(res[0]["dn"])
453 def check_dn_nottobecreated(hash,index,listdn):
454 """Check if one of the DN present in the list has a creation order greater than the current.
456 Hash is indexed by dn to be created, with each key is associated the creation order
457 First dn to be created has the creation order 0, second has 1, ...
458 Index contain the current creation order
460 :param hash: Hash holding the different DN of the object to be created as key
461 :param index: Current creation order
462 :param listdn: List of DNs on which the current DN depends on
463 :return: None if the current object do not depend on other object or if all object have been
468 key = str(dn).lower()
469 if hash.has_key(key) and hash[key] > index:
474 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
475 """Add a new object if the dependencies are satisfied
477 The function add the object if the object on which it depends are already created
478 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
479 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
480 :param dn: DN of the object to be added
481 :param names: List of key provision parameters
482 :param basedn: DN of the partition to be updated
483 :param hash: Hash holding the different DN of the object to be created as key
484 :param index: Current creation order
485 :return: True if the object was created False otherwise"""
486 handle_special_add(sam_ldb,dn,names)
487 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
488 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
490 delta = sam_ldb.msg_diff(empty,reference[0])
491 for att in hashAttrNotCopied.keys():
493 for att in backlinked:
495 depend_on_yettobecreated = None
496 for att in dn_syntax_att:
497 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
498 if depend_on_yet_tobecreated != None:
499 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
500 %(str(dn),depend_on_yet_tobecreated,str(att)))
503 message(CHANGE,"Object %s will be added"%dn)
504 sam_ldb.add(delta,["relax:0"])
508 def gen_dn_index_hash(listMissing):
509 """Generate a hash associating the DN to its creation order
511 :param listMissing: List of DN
512 :return: Hash with DN as keys and creation order as values"""
514 for i in range(0,len(listMissing)):
515 hash[str(listMissing[i]).lower()] = i
519 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
520 """Add the missing object whose DN is the list
522 The function add the object if the object on which it depends are already created
523 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
524 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
525 :param dn: DN of the object to be added
526 :param names: List of key provision parameters
527 :param basedn: DN of the partition to be updated
528 :param list: List of DN to be added in the upgraded provision"""
532 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
534 listMissing = listDefered
536 hashMissing = gen_dn_index_hash(listMissing)
537 for dn in listMissing:
538 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
541 #DN can't be created because it depends on some other DN in the list
542 listDefered.append(dn)
543 if len(listDefered) != 0:
544 raise ProvisioningError("Unable to insert missing elements: circular references")
547 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
548 """Check differences between the reference provision and the upgraded one.
550 It looks for all objects which base DN is name. If ischema is "false" then
551 the scan is done in cross partition mode.
552 If "ischema" is true, then special handling is done for dealing with schema
554 This function will also add the missing object and update existing object to add
555 or remove attributes that were missing.
556 :param newpaths: List of paths for different provision objects from the reference provision
557 :param paths: List of paths for different provision objects from the upgraded provision
558 :param creds: Credential for the authentification
559 :param session: Session for connexion
560 :param basedn: DN of the partition to update
561 :param names: List of key provision parameters
562 :param ischema: Boolean indicating if the update is about the schema only
563 :return: Hash of security descriptor to update"""
572 # Connect to the reference provision and get all the attribute in the
573 # partition referred by name
574 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
575 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
576 sam_ldb.transaction_start()
578 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
579 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
581 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
582 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
584 sam_ldb.transaction_commit()
585 # Create a hash for speeding the search of new object
586 for i in range(0,len(reference)):
587 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
589 # Create a hash for speeding the search of existing object in the
591 for i in range(0,len(current)):
592 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
594 for k in hash_new.keys():
595 if not hash.has_key(k):
597 listMissing.append(hash_new[k])
599 listPresent.append(hash_new[k])
601 # Sort the missing object in order to have object of the lowest level
602 # first (which can be containers for higher level objects)
603 listMissing.sort(dn_sort)
604 listPresent.sort(dn_sort)
607 # The following lines (up to the for loop) is to load the up to
608 # date schema into our current LDB
609 # a complete schema is needed as the insertion of attributes
610 # and class is done against it
611 # and the schema is self validated
612 # The double ldb open and schema validation is taken from the
613 # initial provision script
614 # it's not certain that it is really needed ....
615 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
616 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
617 # Load the schema from the one we computed earlier
618 sam_ldb.set_schema_from_ldb(schema.ldb)
619 # And now we can connect to the DB - the schema won't be loaded
621 sam_ldb.connect(paths.samdb)
623 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
625 sam_ldb.transaction_start()
626 # XXX: This needs to be wrapped in try/except so we
627 # abort on exceptions.
628 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
629 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
631 for dn in listPresent:
632 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
633 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
634 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
635 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
636 identic_rename(sam_ldb,reference[0].dn)
637 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
639 delta = sam_ldb.msg_diff(current[0],reference[0])
640 for att in hashAttrNotCopied.keys():
642 for att in backlinked:
644 delta.remove("parentGUID")
648 msgElt = delta.get(att)
654 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
657 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
658 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
661 if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
662 if opts.debugchange or opts.debugall:
664 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
666 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
669 if len(delta.items()) >1:
670 attributes=",".join(delta.keys())
671 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
672 changed = changed + 1
673 sam_ldb.modify(delta)
675 sam_ldb.transaction_commit()
676 message(SIMPLE,"There are %d changed objects"%(changed))
680 def check_updated_sd(newpaths, paths, creds, session, names):
681 """Check if the security descriptor in the upgraded provision are the same as the reference
683 :param newpaths: List of paths for different provision objects from the reference provision
684 :param paths: List of paths for different provision objects from the upgraded provision
685 :param creds: Credential for the authentification
686 :param session: Session for connexion
687 :param basedn: DN of the partition to update
688 :param names: List of key provision parameters"""
689 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
690 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
691 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
692 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
694 for i in range(0,len(reference)):
695 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
697 for i in range(0,len(current)):
698 key = str(current[i]["dn"]).lower()
699 if hash_new.has_key(key):
700 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
701 if sddl != hash_new[key]:
702 print "%s new sddl/sddl in ref"%key
703 print "%s\n%s"%(sddl,hash_new[key])
706 def update_sd(paths, creds, session, names):
707 """Update security descriptor of the current provision
709 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
710 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
711 and so SD can be safely recalculated from scratch to get them right.
713 :param paths: List of paths for different provision objects from the upgraded provision
714 :param creds: Credential for the authentification
715 :param session: Session for connexion
716 :param names: List of key provision parameters"""
718 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
719 sam_ldb.transaction_start()
720 # First update the SD for the rootdn
721 sam_ldb.set_session_info(session)
722 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
723 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
725 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
726 descr = get_domain_descriptor(names.domainsid)
727 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
728 sam_ldb.modify(delta,["recalculate_sd:0"])
730 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
732 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
733 descr = get_config_descriptor(names.domainsid)
734 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
735 sam_ldb.modify(delta,["recalculate_sd:0"])
737 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
739 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
740 descr = get_schema_descriptor(names.domainsid)
741 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
742 sam_ldb.modify(delta,["recalculate_sd:0"])
746 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
748 if not (str(obj["dn"]) == str(names.rootdn) or
749 str(obj["dn"]) == str(names.configdn) or \
750 str(obj["dn"]) == str(names.schemadn)):
751 hash[str(obj["dn"])] = obj["whenCreated"]
753 listkeys = hash.keys()
754 listkeys.sort(dn_sort)
759 delta.dn = Dn(sam_ldb,key)
760 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
761 sam_ldb.modify(delta,["recalculate_sd:0"])
763 # XXX: We should always catch an explicit exception.
764 # What could go wrong here?
765 sam_ldb.transaction_cancel()
766 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
767 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
768 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
770 sam_ldb.transaction_commit()
773 def update_basesamdb(newpaths, paths, names):
774 """Update the provision container db: sam.ldb
776 :param newpaths: List of paths for different provision objects from the reference provision
777 :param paths: List of paths for different provision objects from the upgraded provision
778 :param names: List of key provision parameters"""
780 message(SIMPLE,"Copy samdb")
781 shutil.copy(newpaths.samdb,paths.samdb)
783 message(SIMPLE,"Update partitions filename if needed")
784 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
785 configldb=os.path.join(paths.private_dir,"configuration.ldb")
786 usersldb=os.path.join(paths.private_dir,"users.ldb")
787 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
789 if not os.path.isdir(samldbdir):
791 os.chmod(samldbdir,0700)
792 if os.path.isfile(schemaldb):
793 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb"%str(names.schemadn).upper()))
795 if os.path.isfile(usersldb):
796 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb"%str(names.rootdn).upper()))
798 if os.path.isfile(configldb):
799 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb"%str(names.configdn).upper()))
803 def update_privilege(newpaths, paths):
804 """Update the privilege database
806 :param newpaths: List of paths for different provision objects from the reference provision
807 :param paths: List of paths for different provision objects from the upgraded provision"""
808 message(SIMPLE, "Copy privilege")
809 shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"),
810 os.path.join(paths.private_dir, "privilege.ldb"))
813 def update_samdb(newpaths, paths, creds, session, names):
814 """Upgrade the SAM DB contents for all the provision
816 :param newpaths: List of paths for different provision objects from the reference provision
817 :param paths: List of paths for different provision objects from the upgraded provision
818 :param creds: Credential for the authentification
819 :param session: Session for connexion
820 :param names: List of key provision parameters"""
822 message(SIMPLE, "Doing schema update")
823 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
824 message(SIMPLE,"Done with schema update")
825 message(SIMPLE,"Scanning whole provision for updates and additions")
826 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
827 message(SIMPLE,"Done with scanning")
830 def update_machine_account_password(paths, creds, session, names):
831 """Update (change) the password of the current DC both in the SAM db and in secret one
833 :param paths: List of paths for different provision objects from the upgraded provision
834 :param creds: Credential for the authentification
835 :param session: Session for connexion
836 :param names: List of key provision parameters"""
838 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
839 secrets_ldb.transaction_start()
840 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
841 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
842 sam_ldb.transaction_start()
843 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
844 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
845 assert(len(res) == 1)
847 msg = Message(res[0].dn)
848 machinepass = glue.generate_random_password(128, 255)
849 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
852 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
853 attrs=["msDs-keyVersionNumber"])
854 assert(len(res) == 1)
855 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
857 secretsdb_self_join(secrets_ldb, domain=names.domain,
859 domainsid=names.domainsid,
860 dnsdomain=names.dnsdomain,
861 netbiosname=names.netbiosname,
862 machinepass=machinepass,
863 key_version_number=kvno,
864 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
865 sam_ldb.transaction_prepare_commit()
866 secrets_ldb.transaction_prepare_commit()
867 sam_ldb.transaction_commit()
868 secrets_ldb.transaction_commit()
870 secrets_ldb.transaction_cancel()
873 def update_gpo(paths,creds,session,names):
874 """Create missing GPO file object if needed
876 Set ACL correctly also.
878 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
879 if not os.path.isdir(dir):
880 create_gpo_struct(dir)
882 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
883 if not os.path.isdir(dir):
884 create_gpo_struct(dir)
885 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
886 set_gpo_acl(path.sysvol,names.dnsdomain,names.domainsid,names.domaindn,samdb,lp)
888 def updateOEMInfo(paths,creds,session,names):
889 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
890 res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
891 scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
893 info = res[0]["oEMInformation"]
894 info = "%s, upgrade to %s"%(info,version)
896 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
897 descr = get_schema_descriptor(names.domainsid)
898 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE, "oEMInformation" )
899 sam_ldb.modify(delta)
902 def setup_path(file):
903 return os.path.join(setup_dir, file)
906 if __name__ == '__main__':
907 # From here start the big steps of the program
908 # First get files paths
909 paths=get_paths(param,smbconf=smbconf)
910 paths.setup = setup_dir
911 # Guess all the needed names (variables in fact) from the current
914 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
915 if not sanitychecks(creds,session,names,paths):
916 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
919 print_provision_key_parameters(names)
920 # With all this information let's create a fresh new provision used as reference
921 message(SIMPLE,"Creating a reference provision")
922 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
923 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
924 # Get file paths of this new provision
925 newpaths = get_paths(param, targetdir=provisiondir)
926 populate_backlink(newpaths, creds, session,names.schemadn)
927 populate_dnsyntax(newpaths, creds, session,names.schemadn)
928 # Check the difference
929 update_basesamdb(newpaths, paths, names)
932 update_samdb(newpaths, paths, creds, session, names)
933 update_secrets(newpaths, paths, creds, session)
934 update_privilege(newpaths, paths)
935 update_machine_account_password(paths, creds, session, names)
936 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
937 # to recreate them with the good form but with system account and then give the ownership to admin ...
938 admin_session_info = admin_session(lp, str(names.domainsid))
939 message(SIMPLE, "Updating SD")
940 update_sd(paths, creds, session,names)
941 update_sd(paths, creds, admin_session_info, names)
942 check_updated_sd(newpaths, paths, creds, session, names)
943 updateOEMInfo(paths,creds,session,names)
944 message(SIMPLE, "Upgrade finished !")
945 # remove reference provision now that everything is done !
946 shutil.rmtree(provisiondir)