3 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
5 # Based on provision a Samba4 server by
6 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
7 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # 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
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
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":replace, "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)
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 # Create an array of attributes with a dn synthax (2.5.5.1)
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"])
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))
237 def handle_security_desc(ischema, att, msgElt, hashallSD, old, new):
238 """Check if the security descriptor has been modified.
240 This function also populate a hash used for the upgrade process.
241 :param ischema: Boolean that indicate if it's the schema that is updated
242 :param att: Name of the attribute
243 :param msgElt: MessageElement object
244 :param hashallSD: Hash table with DN as key and the old SD as value
245 :param old: The updated LDAP object
246 :param new: The reference LDAP object
247 :return: 1 to indicate that the attribute should be kept, 0 for discarding it
249 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
251 hashSD["oldSD"] = old[0][att]
252 hashSD["newSD"] = new[0][att]
253 hashallSD[str(old[0].dn)] = hashSD
255 if att == "nTSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
258 hashSD["oldSD"] = ndr_unpack(security.descriptor, str(old[0][att]))
259 hashSD["newSD"] = ndr_unpack(security.descriptor, str(new[0][att]))
260 hashallSD[str(old[0].dn)] = hashSD
264 def handle_special_case(att, delta, new, old, ischema):
265 """Define more complicate update rules for some attributes
267 :param att: The attribute to be updated
268 :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
269 :param new: The reference object
270 :param old: The Updated object
271 :param ischema: A boolean that indicate that the attribute is part of a schema object
272 :return: 1 to indicate that the attribute should be kept, 0 for discarding it
274 flag = delta.get(att).flags()
275 if (att == "gPLink" or att == "gPCFileSysPath") and \
276 flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
279 if att == "forceLogoff":
280 ref=0x8000000000000000
281 oldval=int(old[0][att][0])
282 newval=int(new[0][att][0])
283 ref == old and ref == abs(new)
285 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
288 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn))\
289 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
292 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
295 if ( (att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
299 for elem in old[0][att]:
301 newval.append(str(elem))
303 for elem in new[0][att]:
304 if not hash.has_key(str(elem)):
306 newval.append(str(elem))
308 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
313 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
315 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
319 def update_secrets(newpaths, paths, creds, session):
320 """Update secrets.ldb
322 :param newpaths: a list of paths for different provision objects from the reference provision
323 :param paths: a list of paths for different provision objects from the upgraded provision
324 :param creds: credential for the authentification
325 :param session: session for connexion"""
327 message(SIMPLE,"update secrets.ldb")
328 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
329 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
330 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
331 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
332 delta = secrets_ldb.msg_diff(current[0],reference[0])
333 delta.dn = current[0].dn
334 secrets_ldb.modify(delta)
336 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
337 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
338 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
339 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
346 for i in range(0,len(reference)):
347 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
349 # Create a hash for speeding the search of existing object in the
351 for i in range(0,len(current)):
352 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
354 for k in hash_new.keys():
355 if not hash.has_key(k):
356 listMissing.append(hash_new[k])
358 listPresent.append(hash_new[k])
360 for entry in listMissing:
361 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
362 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
363 delta = secrets_ldb.msg_diff(empty,reference[0])
364 for att in hashAttrNotCopied.keys():
366 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
368 message(CHANGE," Adding attribute %s"%att)
369 delta.dn = reference[0].dn
370 secrets_ldb.add(delta)
372 for entry in listPresent:
373 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
374 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
375 delta = secrets_ldb.msg_diff(current[0],reference[0])
377 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])
393 for att in hashAttrNotCopied.keys():
398 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
400 delta.dn = current[0].dn
401 secrets_ldb.modify(delta)
403 def dump_denied_change(dn,att,flagtxt,current,reference):
404 """Print detailed information about why a changed is denied
406 :param dn: DN of the object which attribute is denied
407 :param att: Attribute that was supposed to be upgraded
408 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
409 :param current: Value(s) of the current attribute
410 :param reference: Value(s) of the reference attribute"""
412 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
413 if att != "objectSid" :
415 for e in range(0,len(current)):
416 message(CHANGE,"old %d : %s"%(i,str(current[e])))
418 if reference != None:
420 for e in range(0,len(reference)):
421 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
424 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
425 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 #Check if the one of the dn in the listdn will be created after the current dn
454 #hash is indexed by dn to be created, with each key is associated the creation order
455 #First dn to be created has the creation order 0, second has 1, ...
456 #Index contain the current creation order
457 def check_dn_nottobecreated(hash,index,listdn):
458 """Check if one of the DN present in the list has a creation order greater than the current.
460 Hash is indexed by dn to be created, with each key is associated the creation order
461 First dn to be created has the creation order 0, second has 1, ...
462 Index contain the current creation order
463 :param hash: Hash holding the different DN of the object to be created as key
464 :param index: Current creation order
465 :param listdn: List of DNs on which the current DN depends on
466 :return: None if the current object do not depend on other object or if all object have been
471 key = str(dn).lower()
472 if hash.has_key(key) and hash[key] > index:
476 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
477 """Add a new object if the dependencies are satisfied
479 The function add the object if the object on which it depends are already created
480 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
481 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
482 :param dn: DN of the object to be added
483 :param names: List of key provision parameters
484 :param basedn: DN of the partition to be updated
485 :param hash: Hash holding the different DN of the object to be created as key
486 :param index: Current creation order
487 :return: 1 if the object was created 0 otherwise"""
488 handle_special_add(sam_ldb,dn,names)
489 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
490 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
492 delta = sam_ldb.msg_diff(empty,reference[0])
493 for att in hashAttrNotCopied.keys():
495 for att in backlinked:
497 depend_on_yettobecreated = None
498 for att in dn_syntax_att:
499 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
500 if depend_on_yet_tobecreated != None:
501 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
502 %(str(dn),depend_on_yet_tobecreated,str(att)))
505 message(CHANGE,"Object %s will be added"%dn)
506 sam_ldb.add(delta,["relax:0"])
509 def gen_dn_index_hash(listMissing):
510 """Generate a hash associating the DN to its creation order
512 :param listMissing: List of DN
513 :return: Hash with DN as keys and creation order as values"""
515 for i in range(0,len(listMissing)):
516 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")
549 # Check difference between the current provision and the reference provision.
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
553 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
554 """Check differences between the reference provision and the upgraded one.
556 This function will also add the missing object and update existing object to add
557 or remove attributes that were missing.
558 :param newpaths: List of paths for different provision objects from the reference provision
559 :param paths: List of paths for different provision objects from the upgraded provision
560 :param creds: Credential for the authentification
561 :param session: Session for connexion
562 :param basedn: DN of the partition to update
563 :param names: List of key provision parameters
564 :param ischema: Boolean indicating if the update is about the schema only
565 :return: Hash of security descriptor to update"""
574 # Connect to the reference provision and get all the attribute in the
575 # partition referred by name
576 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
577 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
578 sam_ldb.transaction_start()
580 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
581 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
583 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
584 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
586 sam_ldb.transaction_commit()
587 # Create a hash for speeding the search of new object
588 for i in range(0,len(reference)):
589 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
591 # Create a hash for speeding the search of existing object in the
593 for i in range(0,len(current)):
594 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
596 for k in hash_new.keys():
597 if not hash.has_key(k):
599 listMissing.append(hash_new[k])
601 listPresent.append(hash_new[k])
603 # Sort the missing object in order to have object of the lowest level
604 # first (which can be containers for higher level objects)
605 listMissing.sort(dn_sort)
606 listPresent.sort(dn_sort)
609 # The following lines (up to the for loop) is to load the up to
610 # date schema into our current LDB
611 # a complete schema is needed as the insertion of attributes
612 # and class is done against it
613 # and the schema is self validated
614 # The double ldb open and schema validation is taken from the
615 # initial provision script
616 # it's not certain that it is really needed ....
617 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
618 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
619 # Load the schema from the one we computed earlier
620 sam_ldb.set_schema_from_ldb(schema.ldb)
621 # And now we can connect to the DB - the schema won't be loaded
623 sam_ldb.connect(paths.samdb)
625 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
627 sam_ldb.transaction_start()
629 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
630 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
632 for dn in listPresent:
633 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
634 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
635 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
636 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
637 identic_rename(sam_ldb,reference[0].dn)
638 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
640 delta = sam_ldb.msg_diff(current[0],reference[0])
641 for att in hashAttrNotCopied.keys():
643 for att in backlinked:
645 delta.remove("parentGUID")
649 msgElt = delta.get(att)
655 if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference) == 0:
658 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
659 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
662 if handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=FLAG_MOD_ADD:
664 if opts.debugchange or opts.debugall:
666 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
668 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
671 if len(delta.items()) >1:
672 attributes=",".join(delta.keys())
673 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
674 changed = changed + 1
675 sam_ldb.modify(delta)
677 sam_ldb.transaction_commit()
678 message(SIMPLE,"There are %d changed objects"%(changed))
681 def check_updated_sd(newpaths, paths, creds, session, names):
682 """Check if the security descriptor in the upgraded provision are the same as the reference
684 :param newpaths: List of paths for different provision objects from the reference provision
685 :param paths: List of paths for different provision objects from the upgraded provision
686 :param creds: Credential for the authentification
687 :param session: Session for connexion
688 :param basedn: DN of the partition to update
689 :param names: List of key provision parameters"""
690 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
691 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
692 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
693 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
695 for i in range(0,len(reference)):
696 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
698 for i in range(0,len(current)):
699 key = str(current[i]["dn"]).lower()
700 if hash_new.has_key(key):
701 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
702 if sddl != hash_new[key]:
703 print "%s new sddl/sddl in ref"%key
704 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 sam_ldb.transaction_cancel()
764 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
765 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
766 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
768 sam_ldb.transaction_commit()
771 def update_basesamdb(newpaths, paths, names):
772 """Update the provision container db: sam.ldb
774 :param newpaths: List of paths for different provision objects from the reference provision
775 :param paths: List of paths for different provision objects from the upgraded provision
776 :param names: List of key provision parameters"""
778 message(SIMPLE,"Copy samdb")
779 shutil.copy(newpaths.samdb,paths.samdb)
781 message(SIMPLE,"Update partitions filename if needed")
782 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
783 configldb=os.path.join(paths.private_dir,"configuration.ldb")
784 usersldb=os.path.join(paths.private_dir,"users.ldb")
785 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
787 if not os.path.isdir(samldbdir):
789 os.chmod(samldbdir,0700)
790 if os.path.isfile(schemaldb):
791 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
793 if os.path.isfile(usersldb):
794 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
796 if os.path.isfile(configldb):
797 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
800 def update_privilege(newpaths, paths):
801 """Update the privilege database
803 :param newpaths: List of paths for different provision objects from the reference provision
804 :param paths: List of paths for different provision objects from the upgraded provision"""
805 message(SIMPLE,"Copy privilege")
806 shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
808 # For each partition check the differences
809 def update_samdb(newpaths, paths, creds, session, names):
810 """Upgrade the SAM DB contents for all the provision
812 :param newpaths: List of paths for different provision objects from the reference provision
813 :param paths: List of paths for different provision objects from the upgraded provision
814 :param creds: Credential for the authentification
815 :param session: Session for connexion
816 :param names: List of key provision parameters"""
818 message(SIMPLE, "Doing schema update")
819 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
820 message(SIMPLE,"Done with schema update")
821 message(SIMPLE,"Scanning whole provision for updates and additions")
822 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
823 message(SIMPLE,"Done with scanning")
825 def update_machine_account_password(paths, creds, session, names):
826 """Update (change) the password of the current DC both in the SAM db and in secret one
828 :param paths: List of paths for different provision objects from the upgraded provision
829 :param creds: Credential for the authentification
830 :param session: Session for connexion
831 :param names: List of key provision parameters"""
833 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
834 secrets_ldb.transaction_start()
835 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
836 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
837 sam_ldb.transaction_start()
838 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
839 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
840 assert(len(res) == 1)
842 msg = Message(res[0].dn)
843 machinepass = glue.generate_random_password(128, 255)
844 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
847 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
848 attrs=["msDs-keyVersionNumber"])
849 assert(len(res) == 1)
850 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
852 secretsdb_self_join(secrets_ldb, domain=names.domain,
854 domainsid=names.domainsid,
855 dnsdomain=names.dnsdomain,
856 netbiosname=names.netbiosname,
857 machinepass=machinepass,
858 key_version_number=kvno,
859 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
860 sam_ldb.transaction_prepare_commit()
861 secrets_ldb.transaction_prepare_commit()
862 sam_ldb.transaction_commit()
863 secrets_ldb.transaction_commit()
865 secrets_ldb.transaction_cancel()
867 def setup_path(file):
868 return os.path.join(setup_dir, file)
870 cmd = os.environ["_"]
871 m = re.match('(^|.*/)pydoc$',cmd)
873 # From here start the big steps of the program
874 # First get files paths
875 paths=get_paths(param,smbconf=smbconf)
876 paths.setup = setup_dir
877 # Guess all the needed names (variables in fact) from the current
880 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
881 if not sanitychecks(creds,session,names,paths):
882 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
885 print_provision_key_parameters(names)
886 # With all this information let's create a fresh new provision used as reference
887 message(SIMPLE,"Creating a reference provision")
888 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
889 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
890 # Get file paths of this new provision
891 newpaths = get_paths(param, targetdir=provisiondir)
892 populate_backlink(newpaths, creds, session,names.schemadn)
893 populate_dnsyntax(newpaths, creds, session,names.schemadn)
894 # Check the difference
895 update_basesamdb(newpaths, paths,names)
898 update_samdb(newpaths, paths, creds, session, names)
899 update_secrets(newpaths, paths, creds, session)
900 update_privilege(newpaths, paths)
901 update_machine_account_password(paths, creds, session, names)
902 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
903 # to recreate them with the good form but with system account and then give the ownership to admin ...
904 admin_session_info = admin_session(lp, str(names.domainsid))
905 message(SIMPLE,"Updating SD")
906 update_sd(paths, creds, session,names)
907 update_sd(paths, creds, admin_session_info, names)
908 check_updated_sd(newpaths, paths, creds, session, names)
909 message(SIMPLE,"Upgrade finished !")
910 # remove reference provision now that everything is done !
911 shutil.rmtree(provisiondir)