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, FLAG_MOD_REPLACE,
39 FLAG_MOD_ADD, FLAG_MOD_DELETE, MessageElement, Message, Dn)
40 from samba import param
41 from samba.misc import messageEltFlagToString
42 from samba.provision import (find_setup_dir, get_domain_descriptor,
43 get_config_descriptor, secretsdb_self_join, set_gpo_acl,
44 getpolicypath, create_gpo_struct, 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
326 :param paths: a list of paths for different provision objects from the
328 :param creds: credential for the authentification
329 :param session: session for connexion"""
331 message(SIMPLE,"update secrets.ldb")
332 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session,
333 credentials=creds,lp=lp)
334 secrets_ldb = Ldb(paths.secrets, session_info=session,
335 credentials=creds,lp=lp, options=["modules:samba_secrets"])
336 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="",
338 current = secrets_ldb.search(expression="dn=@MODULES",base="",
340 delta = secrets_ldb.msg_diff(current[0],reference[0])
341 delta.dn = current[0].dn
342 secrets_ldb.modify(delta)
344 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
345 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
346 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
347 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
354 for i in range(0,len(reference)):
355 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
357 # Create a hash for speeding the search of existing object in the
359 for i in range(0,len(current)):
360 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
362 for k in hash_new.keys():
363 if not hash.has_key(k):
364 listMissing.append(hash_new[k])
366 listPresent.append(hash_new[k])
368 for entry in listMissing:
369 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
370 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
371 delta = secrets_ldb.msg_diff(empty,reference[0])
372 for att in hashAttrNotCopied.keys():
374 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
376 message(CHANGE," Adding attribute %s"%att)
377 delta.dn = reference[0].dn
378 secrets_ldb.add(delta)
380 for entry in listPresent:
381 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
382 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
383 delta = secrets_ldb.msg_diff(current[0],reference[0])
384 for att in hashAttrNotCopied.keys():
388 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
389 identic_rename(secrets_ldb,reference[0].dn)
393 for entry in listPresent:
394 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
395 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
396 delta = secrets_ldb.msg_diff(current[0],reference[0])
397 for att in hashAttrNotCopied.keys():
401 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
403 delta.dn = current[0].dn
404 secrets_ldb.modify(delta)
407 def dump_denied_change(dn,att,flagtxt,current,reference):
408 """Print detailed information about why a changed is denied
410 :param dn: DN of the object which attribute is denied
411 :param att: Attribute that was supposed to be upgraded
412 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
413 :param current: Value(s) of the current attribute
414 :param reference: Value(s) of the reference attribute"""
416 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
417 if att != "objectSid" :
419 for e in range(0,len(current)):
420 message(CHANGE,"old %d : %s"%(i,str(current[e])))
422 if reference != None:
424 for e in range(0,len(reference)):
425 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
428 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
429 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
432 def handle_special_add(sam_ldb,dn,names):
433 """Handle special operation (like remove) on some object needed during upgrade
435 This is mostly due to wrong creation of the object in previous provision.
436 :param sam_ldb: An Ldb object representing the SAM database
437 :param dn: DN of the object to inspect
438 :param names: list of key provision parameters"""
440 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
441 #This entry was misplaced lets remove it if it exists
442 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
444 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
445 #This entry was misplaced lets remove it if it exists
446 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
448 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
449 #This entry was misplaced lets remove it if it exists
450 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
452 if dntoremove != None:
453 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
455 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
456 sam_ldb.delete(res[0]["dn"])
459 def check_dn_nottobecreated(hash, index, listdn):
460 """Check if one of the DN present in the list has a creation order greater than the current.
462 Hash is indexed by dn to be created, with each key is associated the creation order
463 First dn to be created has the creation order 0, second has 1, ...
464 Index contain the current creation order
466 :param hash: Hash holding the different DN of the object to be created as key
467 :param index: Current creation order
468 :param listdn: List of DNs on which the current DN depends on
469 :return: None if the current object do not depend on other object or if all object have been
474 key = str(dn).lower()
475 if hash.has_key(key) and hash[key] > index:
480 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
481 """Add a new object if the dependencies are satisfied
483 The function add the object if the object on which it depends are already created
484 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
485 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
486 :param dn: DN of the object to be added
487 :param names: List of key provision parameters
488 :param basedn: DN of the partition to be updated
489 :param hash: Hash holding the different DN of the object to be created as key
490 :param index: Current creation order
491 :return: True if the object was created False otherwise"""
492 handle_special_add(sam_ldb,dn,names)
493 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
494 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
496 delta = sam_ldb.msg_diff(empty,reference[0])
497 for att in hashAttrNotCopied.keys():
499 for att in backlinked:
501 depend_on_yettobecreated = None
502 for att in dn_syntax_att:
503 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
504 if depend_on_yet_tobecreated != None:
505 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
506 %(str(dn),depend_on_yet_tobecreated,str(att)))
509 message(CHANGE,"Object %s will be added"%dn)
510 sam_ldb.add(delta,["relax:0"])
514 def gen_dn_index_hash(listMissing):
515 """Generate a hash associating the DN to its creation order
517 :param listMissing: List of DN
518 :return: Hash with DN as keys and creation order as values"""
520 for i in range(0,len(listMissing)):
521 hash[str(listMissing[i]).lower()] = i
525 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
526 """Add the missing object whose DN is the list
528 The function add the object if the object on which it depends are already created
529 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
530 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
531 :param dn: DN of the object to be added
532 :param names: List of key provision parameters
533 :param basedn: DN of the partition to be updated
534 :param list: List of DN to be added in the upgraded provision"""
538 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
540 listMissing = listDefered
542 hashMissing = gen_dn_index_hash(listMissing)
543 for dn in listMissing:
544 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
547 #DN can't be created because it depends on some other DN in the list
548 listDefered.append(dn)
549 if len(listDefered) != 0:
550 raise ProvisioningError("Unable to insert missing elements: circular references")
553 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
554 """Check differences between the reference provision and the upgraded one.
556 It looks for all objects which base DN is name. If ischema is "false" then
557 the scan is done in cross partition mode.
558 If "ischema" is true, then special handling is done for dealing with schema
560 This function will also add the missing object and update existing object to add
561 or remove attributes that were missing.
562 :param newpaths: List of paths for different provision objects from the reference provision
563 :param paths: List of paths for different provision objects from the upgraded provision
564 :param creds: Credential for the authentification
565 :param session: Session for connexion
566 :param basedn: DN of the partition to update
567 :param names: List of key provision parameters
568 :param ischema: Boolean indicating if the update is about the schema only
569 :return: Hash of security descriptor to update"""
578 # Connect to the reference provision and get all the attribute in the
579 # partition referred by name
580 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
581 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
582 sam_ldb.transaction_start()
584 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
585 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
587 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
588 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
590 sam_ldb.transaction_commit()
591 # Create a hash for speeding the search of new object
592 for i in range(0,len(reference)):
593 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
595 # Create a hash for speeding the search of existing object in the
597 for i in range(0,len(current)):
598 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
600 for k in hash_new.keys():
601 if not hash.has_key(k):
603 listMissing.append(hash_new[k])
605 listPresent.append(hash_new[k])
607 # Sort the missing object in order to have object of the lowest level
608 # first (which can be containers for higher level objects)
609 listMissing.sort(dn_sort)
610 listPresent.sort(dn_sort)
613 # The following lines (up to the for loop) is to load the up to
614 # date schema into our current LDB
615 # a complete schema is needed as the insertion of attributes
616 # and class is done against it
617 # and the schema is self validated
618 # The double ldb open and schema validation is taken from the
619 # initial provision script
620 # it's not certain that it is really needed ....
621 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
622 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
623 # Load the schema from the one we computed earlier
624 sam_ldb.set_schema_from_ldb(schema.ldb)
625 # And now we can connect to the DB - the schema won't be loaded
627 sam_ldb.connect(paths.samdb)
629 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
631 sam_ldb.transaction_start()
632 # XXX: This needs to be wrapped in try/except so we
633 # abort on exceptions.
634 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
635 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
637 for dn in listPresent:
638 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
639 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
640 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
641 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
642 identic_rename(sam_ldb,reference[0].dn)
643 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
645 delta = sam_ldb.msg_diff(current[0],reference[0])
646 for att in hashAttrNotCopied.keys():
648 for att in backlinked:
650 delta.remove("parentGUID")
654 msgElt = delta.get(att)
660 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
663 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
664 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
667 if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
668 if opts.debugchange or opts.debugall:
670 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
672 # FIXME: Should catch an explicit exception here
673 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
676 if len(delta.items()) >1:
677 attributes=",".join(delta.keys())
678 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
679 changed = changed + 1
680 sam_ldb.modify(delta)
682 sam_ldb.transaction_commit()
683 message(SIMPLE,"There are %d changed objects"%(changed))
687 def check_updated_sd(newpaths, paths, creds, session, names):
688 """Check if the security descriptor in the upgraded provision are the same as the reference
690 :param newpaths: List of paths for different provision objects from the reference provision
691 :param paths: List of paths for different provision objects from the upgraded provision
692 :param creds: Credential for the authentification
693 :param session: Session for connexion
694 :param basedn: DN of the partition to update
695 :param names: List of key provision parameters"""
696 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
697 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
698 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
699 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
701 for i in range(0,len(reference)):
702 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
704 for i in range(0,len(current)):
705 key = str(current[i]["dn"]).lower()
706 if hash_new.has_key(key):
707 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
708 if sddl != hash_new[key]:
709 print "%s new sddl/sddl in ref"%key
710 print "%s\n%s"%(sddl,hash_new[key])
713 def update_sd(paths, creds, session, names):
714 """Update security descriptor of the current provision
716 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
717 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
718 and so SD can be safely recalculated from scratch to get them right.
720 :param paths: List of paths for different provision objects from the upgraded provision
721 :param creds: Credential for the authentification
722 :param session: Session for connexion
723 :param names: List of key provision parameters"""
725 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
726 sam_ldb.transaction_start()
727 # First update the SD for the rootdn
728 sam_ldb.set_session_info(session)
729 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
730 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
732 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
733 descr = get_domain_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.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
739 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
740 descr = get_config_descriptor(names.domainsid)
741 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
742 sam_ldb.modify(delta,["recalculate_sd:0"])
744 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
746 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
747 descr = get_schema_descriptor(names.domainsid)
748 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
749 sam_ldb.modify(delta,["recalculate_sd:0"])
753 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
755 if not (str(obj["dn"]) == str(names.rootdn) or
756 str(obj["dn"]) == str(names.configdn) or \
757 str(obj["dn"]) == str(names.schemadn)):
758 hash[str(obj["dn"])] = obj["whenCreated"]
760 listkeys = hash.keys()
761 listkeys.sort(dn_sort)
766 delta.dn = Dn(sam_ldb,key)
767 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
768 sam_ldb.modify(delta,["recalculate_sd:0"])
770 # XXX: We should always catch an explicit exception.
771 # What could go wrong here?
772 sam_ldb.transaction_cancel()
773 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
774 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
775 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
777 sam_ldb.transaction_commit()
780 def update_basesamdb(newpaths, paths, names):
781 """Update the provision container db: sam.ldb
783 :param newpaths: List of paths for different provision objects from the reference provision
784 :param paths: List of paths for different provision objects from the upgraded provision
785 :param names: List of key provision parameters"""
787 message(SIMPLE,"Copy samdb")
788 shutil.copy(newpaths.samdb,paths.samdb)
790 message(SIMPLE,"Update partitions filename if needed")
791 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
792 configldb = os.path.join(paths.private_dir, "configuration.ldb")
793 usersldb = os.path.join(paths.private_dir, "users.ldb")
794 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
796 if not os.path.isdir(samldbdir):
798 os.chmod(samldbdir,0700)
799 if os.path.isfile(schemaldb):
800 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb" % str(names.schemadn).upper()))
802 if os.path.isfile(usersldb):
803 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb" % str(names.rootdn).upper()))
805 if os.path.isfile(configldb):
806 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb" % str(names.configdn).upper()))
810 def update_privilege(newpaths, paths):
811 """Update the privilege database
813 :param newpaths: List of paths for different provision objects from the reference provision
814 :param paths: List of paths for different provision objects from the upgraded provision"""
815 message(SIMPLE, "Copy privilege")
816 shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"),
817 os.path.join(paths.private_dir, "privilege.ldb"))
820 def update_samdb(newpaths, paths, creds, session, names):
821 """Upgrade the SAM DB contents for all the provision
823 :param newpaths: List of paths for different provision objects from the reference provision
824 :param paths: List of paths for different provision objects from the upgraded provision
825 :param creds: Credential for the authentification
826 :param session: Session for connexion
827 :param names: List of key provision parameters"""
829 message(SIMPLE, "Doing schema update")
830 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
831 message(SIMPLE,"Done with schema update")
832 message(SIMPLE,"Scanning whole provision for updates and additions")
833 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
834 message(SIMPLE,"Done with scanning")
837 def update_machine_account_password(paths, creds, session, names):
838 """Update (change) the password of the current DC both in the SAM db and in secret one
840 :param paths: List of paths for different provision objects from the upgraded provision
841 :param creds: Credential for the authentification
842 :param session: Session for connexion
843 :param names: List of key provision parameters"""
845 secrets_ldb = Ldb(paths.secrets, session_info=session,
846 credentials=creds,lp=lp)
847 secrets_ldb.transaction_start()
848 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
849 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
850 sam_ldb.transaction_start()
851 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
852 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
853 assert(len(res) == 1)
855 msg = Message(res[0].dn)
856 machinepass = samba.generate_random_password(128, 255)
857 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
860 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
861 attrs=["msDs-keyVersionNumber"])
862 assert(len(res) == 1)
863 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
865 secretsdb_self_join(secrets_ldb, domain=names.domain,
866 realm=names.realm or sambaopts._lp.get('realm'),
867 domainsid=names.domainsid,
868 dnsdomain=names.dnsdomain,
869 netbiosname=names.netbiosname,
870 machinepass=machinepass,
871 key_version_number=kvno,
872 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
873 sam_ldb.transaction_prepare_commit()
874 secrets_ldb.transaction_prepare_commit()
875 sam_ldb.transaction_commit()
876 secrets_ldb.transaction_commit()
878 secrets_ldb.transaction_cancel()
881 def update_gpo(paths,creds,session,names):
882 """Create missing GPO file object if needed
884 Set ACL correctly also.
886 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
887 if not os.path.isdir(dir):
888 create_gpo_struct(dir)
890 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
891 if not os.path.isdir(dir):
892 create_gpo_struct(dir)
893 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
894 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
895 names.domaindn, samdb, lp)
898 def updateOEMInfo(paths, creds, session,names):
899 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
900 options=["modules:samba_dsdb"])
901 res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
902 scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
904 info = res[0]["oEMInformation"]
905 info = "%s, upgrade to %s"%(info,version)
907 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
908 descr = get_schema_descriptor(names.domainsid)
909 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
911 sam_ldb.modify(delta)
914 def setup_path(file):
915 return os.path.join(setup_dir, file)
918 if __name__ == '__main__':
919 # From here start the big steps of the program
920 # First get files paths
921 paths=get_paths(param,smbconf=smbconf)
922 paths.setup = setup_dir
923 # Guess all the needed names (variables in fact) from the current
926 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
927 if not sanitychecks(creds,session,names,paths):
928 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
931 print_provision_key_parameters(names)
932 # With all this information let's create a fresh new provision used as reference
933 message(SIMPLE,"Creating a reference provision")
934 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
935 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
936 # Get file paths of this new provision
937 newpaths = get_paths(param, targetdir=provisiondir)
938 populate_backlink(newpaths, creds, session,names.schemadn)
939 populate_dnsyntax(newpaths, creds, session,names.schemadn)
940 # Check the difference
941 update_basesamdb(newpaths, paths, names)
944 update_samdb(newpaths, paths, creds, session, names)
945 update_secrets(newpaths, paths, creds, session)
946 update_privilege(newpaths, paths)
947 update_machine_account_password(paths, creds, session, names)
948 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
949 # to recreate them with the good form but with system account and then give the ownership to admin ...
950 admin_session_info = admin_session(lp, str(names.domainsid))
951 message(SIMPLE, "Updating SD")
952 update_sd(paths, creds, session,names)
953 update_sd(paths, creds, admin_session_info, names)
954 check_updated_sd(newpaths, paths, creds, session, names)
955 updateOEMInfo(paths,creds,session,names)
956 message(SIMPLE, "Upgrade finished !")
957 # remove reference provision now that everything is done !
958 shutil.rmtree(provisiondir)