s4:scripting/python/modules.c - fix "asprintf" calls
[Samba/ekacnet.git] / source4 / scripting / bin / upgradeprovision
blob066fbe469ceb3223be4d5ee0baa408f805bc16e8
1 #!/usr/bin/env python
2 # vim: expandtab
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/>.
25 import shutil
26 import optparse
27 import os
28 import sys
29 import tempfile
30 # Allow to run from s4 source directory (without installing samba)
31 sys.path.insert(0, "bin/python")
33 import samba
34 import samba.getopt as options
35 from samba.credentials import DONT_USE_KERBEROS
36 from samba.auth import system_session, admin_session
37 from samba import Ldb, version
38 from ldb import SCOPE_SUBTREE, SCOPE_BASE, \
39 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,\
40 MessageElement, Message, Dn
41 from samba import param
42 from samba.misc import messageEltFlagToString
43 from samba.provision import find_setup_dir, get_domain_descriptor, get_config_descriptor, secretsdb_self_join,set_gpo_acl,getpolicypath,create_gpo_struct, ProvisioningError
44 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
45 from samba.dcerpc import security
46 from samba.ndr import ndr_unpack
47 from samba.dcerpc.misc import SEC_CHAN_BDC
48 from samba.upgradehelpers import dn_sort, get_paths, newprovision, find_provision_key_parameters
50 never=0
51 replace=2^FLAG_MOD_REPLACE
52 add=2^FLAG_MOD_ADD
53 delete=2^FLAG_MOD_DELETE
55 #Errors are always logged
56 ERROR = -1
57 SIMPLE = 0x00
58 CHANGE = 0x01
59 CHANGESD = 0x02
60 GUESS = 0x04
61 PROVISION = 0x08
62 CHANGEALL = 0xff
64 __docformat__ = "restructuredText"
66 # Attributes that are never copied from the reference provision (even if they
67 # do not exist in the destination object).
68 # This is most probably because they are populated automatcally when object is
69 # created
70 # This also apply to imported object from reference provision
71 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1, "objectGUID": 1, "replPropertyMetaData": 1, "uSNChanged": 1,
72 "uSNCreated": 1, "parentGUID": 1, "objectCategory": 1, "distinguishedName": 1,
73 "showInAdvancedViewOnly": 1, "instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
74 "nTMixedDomain": 1, "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
75 "dBCSPwd":1, "supplementalCredentials":1, "gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
76 "maxPwdAge":1, "mail":1, "secret":1, "possibleInferiors":1, "sAMAccountType":1}
78 # Usually for an object that already exists we do not overwrite attributes as
79 # they might have been changed for good reasons. Anyway for a few of them it's
80 # mandatory to replace them otherwise the provision will be broken somehow.
81 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace, "systemOnly":replace, "searchFlags":replace,
82 "mayContain":replace, "systemFlags":replace, "description":replace,
83 "oEMInformation":never, "operatingSystemVersion":replace, "adminPropertyPages":replace,
84 "defaultSecurityDescriptor": replace, "wellKnownObjects":replace, "privilege":delete, "groupType":replace,
85 "rIDAvailablePool": never}
88 backlinked = []
89 dn_syntax_att = []
90 def define_what_to_log(opts):
91 what = 0
92 if opts.debugchange:
93 what = what | CHANGE
94 if opts.debugchangesd:
95 what = what | CHANGESD
96 if opts.debugguess:
97 what = what | GUESS
98 if opts.debugprovision:
99 what = what | PROVISION
100 if opts.debugall:
101 what = what | CHANGEALL
102 return what
105 parser = optparse.OptionParser("provision [options]")
106 sambaopts = options.SambaOptions(parser)
107 parser.add_option_group(sambaopts)
108 parser.add_option_group(options.VersionOptions(parser))
109 credopts = options.CredentialsOptions(parser)
110 parser.add_option_group(credopts)
111 parser.add_option("--setupdir", type="string", metavar="DIR",
112 help="directory with setup files")
113 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
114 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
115 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
116 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
117 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
118 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
120 opts = parser.parse_args()[0]
122 whatToLog = define_what_to_log(opts)
124 def messageprovision(text):
125 """Print a message if quiet is not set
127 :param text: Message to print """
128 if opts.debugprovision or opts.debugall:
129 print text
131 def message(what,text):
132 """Print a message if this message type has been selected to be printed
134 :param what: Category of the message
135 :param text: Message to print """
136 if (whatToLog & what) or what <= 0:
137 print text
139 if len(sys.argv) == 1:
140 opts.interactive = True
141 lp = sambaopts.get_loadparm()
142 smbconf = lp.configfile
144 creds = credopts.get_credentials(lp)
145 creds.set_kerberos_state(DONT_USE_KERBEROS)
146 setup_dir = opts.setupdir
147 if setup_dir is None:
148 setup_dir = find_setup_dir()
150 session = system_session()
152 def identic_rename(ldbobj,dn):
153 """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified.
155 :param lbdobj: An Ldb Object
156 :param dn: DN of the object to manipulate """
157 (before,sep,after)=str(dn).partition('=')
158 ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
159 ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
162 def populate_backlink(newpaths,creds,session,schemadn):
163 """Populate an array with all the back linked attributes
165 This attributes that are modified automaticaly when
166 front attibutes are changed
168 :param newpaths: a list of paths for different provision objects
169 :param creds: credential for the authentification
170 :param session: session for connexion
171 :param schemadn: DN of the schema for the partition"""
172 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
173 linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
174 backlinked.extend(linkedAttHash.values())
176 def populate_dnsyntax(newpaths,creds,session,schemadn):
177 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
179 :param newpaths: a list of paths for different provision objects
180 :param creds: credential for the authentification
181 :param session: session for connexion
182 :param schemadn: DN of the schema for the partition"""
183 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
184 res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)),
185 scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
186 for elem in res:
187 dn_syntax_att.append(elem["lDAPDisplayName"])
190 def sanitychecks(credentials,session_info,names,paths):
191 """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
193 :param creds: credential for the authentification
194 :param session_info: session for connexion
195 :param names: list of key provision parameters
196 :param paths: list of path to provision object
197 :return: Status of check (1 for Ok, 0 for not Ok) """
198 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
200 sam_ldb.set_session_info(session)
201 res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn),
202 scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
203 if len(res) == 0:
204 print "No DC found, your provision is most probalby hardly broken !"
205 return False
206 elif len(res) != 1:
207 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
208 domain with more than one DC, please demote the other(s) DC(s) before upgrading"%len(res)
209 return False
210 else:
211 return True
214 def print_provision_key_parameters(names):
215 """Do a a pretty print of provision parameters
217 :param names: list of key provision parameters """
218 message(GUESS, "rootdn :"+str(names.rootdn))
219 message(GUESS, "configdn :"+str(names.configdn))
220 message(GUESS, "schemadn :"+str(names.schemadn))
221 message(GUESS, "serverdn :"+str(names.serverdn))
222 message(GUESS, "netbiosname :"+names.netbiosname)
223 message(GUESS, "defaultsite :"+names.sitename)
224 message(GUESS, "dnsdomain :"+names.dnsdomain)
225 message(GUESS, "hostname :"+names.hostname)
226 message(GUESS, "domain :"+names.domain)
227 message(GUESS, "realm :"+names.realm)
228 message(GUESS, "invocationid:"+names.invocation)
229 message(GUESS, "policyguid :"+names.policyid)
230 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
231 message(GUESS, "domainsid :"+str(names.domainsid))
232 message(GUESS, "domainguid :"+names.domainguid)
233 message(GUESS, "ntdsguid :"+names.ntdsguid)
234 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:
250 hashSD = {}
251 hashSD["oldSD"] = old[0][att]
252 hashSD["newSD"] = new[0][att]
253 hashallSD[str(old[0].dn)] = hashSD
254 return True
255 if att == "nTSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE:
256 if ischema == 0:
257 hashSD = {}
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
261 return False
262 return False
265 def handle_special_case(att, delta, new, old, ischema):
266 """Define more complicate update rules for some attributes
268 :param att: The attribute to be updated
269 :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
270 :param new: The reference object
271 :param old: The Updated object
272 :param ischema: A boolean that indicate that the attribute is part of a schema object
273 :return: Tru to indicate that the attribute should be kept, False for discarding it
275 flag = delta.get(att).flags()
276 if (att == "gPLink" or att == "gPCFileSysPath") and \
277 flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
278 delta.remove(att)
279 return True
280 if att == "forceLogoff":
281 ref=0x8000000000000000
282 oldval=int(old[0][att][0])
283 newval=int(new[0][att][0])
284 ref == old and ref == abs(new)
285 return True
286 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
287 return True
289 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s" % (str(names.schemadn))\
290 and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
291 return True
293 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
294 return True
296 if ((att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
297 hash = {}
298 newval = []
299 changeDelta=0
300 for elem in old[0][att]:
301 hash[str(elem)]=1
302 newval.append(str(elem))
304 for elem in new[0][att]:
305 if not hash.has_key(str(elem)):
306 changeDelta=1
307 newval.append(str(elem))
308 if changeDelta == 1:
309 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
310 else:
311 delta.remove(att)
312 return True
314 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
315 return True
316 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
317 return True
318 return False
320 def update_secrets(newpaths, paths, creds, session):
321 """Update secrets.ldb
323 :param newpaths: a list of paths for different provision objects from the
324 reference provision
325 :param paths: a list of paths for different provision objects from the
326 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,
332 credentials=creds,lp=lp)
333 secrets_ldb = Ldb(paths.secrets, session_info=session,
334 credentials=creds,lp=lp, options=["modules:samba_secrets"])
335 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="",
336 scope=SCOPE_SUBTREE)
337 current = secrets_ldb.search(expression="dn=@MODULES",base="",
338 scope=SCOPE_SUBTREE)
339 delta = secrets_ldb.msg_diff(current[0],reference[0])
340 delta.dn = current[0].dn
341 secrets_ldb.modify(delta)
343 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
344 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
345 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
346 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
347 hash_new = {}
348 hash = {}
349 listMissing = []
350 listPresent = []
352 empty = Message()
353 for i in range(0,len(reference)):
354 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
356 # Create a hash for speeding the search of existing object in the
357 # current provision
358 for i in range(0,len(current)):
359 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
361 for k in hash_new.keys():
362 if not hash.has_key(k):
363 listMissing.append(hash_new[k])
364 else:
365 listPresent.append(hash_new[k])
367 for entry in listMissing:
368 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
369 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
370 delta = secrets_ldb.msg_diff(empty,reference[0])
371 for att in hashAttrNotCopied.keys():
372 delta.remove(att)
373 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
374 for att in delta:
375 message(CHANGE," Adding attribute %s"%att)
376 delta.dn = reference[0].dn
377 secrets_ldb.add(delta)
379 for entry in listPresent:
380 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
381 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
382 delta = secrets_ldb.msg_diff(current[0],reference[0])
383 for att in hashAttrNotCopied.keys():
384 delta.remove(att)
385 for att in delta:
386 if att == "name":
387 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
388 identic_rename(secrets_ldb,reference[0].dn)
389 else:
390 delta.remove(att)
392 for entry in listPresent:
393 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
394 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
395 delta = secrets_ldb.msg_diff(current[0],reference[0])
396 for att in hashAttrNotCopied.keys():
397 delta.remove(att)
398 for att in delta:
399 if att != "dn":
400 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
402 delta.dn = current[0].dn
403 secrets_ldb.modify(delta)
406 def dump_denied_change(dn,att,flagtxt,current,reference):
407 """Print detailed information about why a changed is denied
409 :param dn: DN of the object which attribute is denied
410 :param att: Attribute that was supposed to be upgraded
411 :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
412 :param current: Value(s) of the current attribute
413 :param reference: Value(s) of the reference attribute"""
415 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
416 if att != "objectSid" :
417 i = 0
418 for e in range(0,len(current)):
419 message(CHANGE,"old %d : %s"%(i,str(current[e])))
420 i+=1
421 if reference != None:
422 i = 0
423 for e in range(0,len(reference)):
424 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
425 i+=1
426 else:
427 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
428 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
431 def handle_special_add(sam_ldb,dn,names):
432 """Handle special operation (like remove) on some object needed during upgrade
434 This is mostly due to wrong creation of the object in previous provision.
435 :param sam_ldb: An Ldb object representing the SAM database
436 :param dn: DN of the object to inspect
437 :param names: list of key provision parameters"""
438 dntoremove = None
439 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
440 #This entry was misplaced lets remove it if it exists
441 dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
443 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
444 #This entry was misplaced lets remove it if it exists
445 dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
447 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
448 #This entry was misplaced lets remove it if it exists
449 dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
451 if dntoremove != None:
452 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
453 if len(res) > 0:
454 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
455 sam_ldb.delete(res[0]["dn"])
458 def check_dn_nottobecreated(hash, index, listdn):
459 """Check if one of the DN present in the list has a creation order greater than the current.
461 Hash is indexed by dn to be created, with each key is associated the creation order
462 First dn to be created has the creation order 0, second has 1, ...
463 Index contain the current creation order
465 :param hash: Hash holding the different DN of the object to be created as key
466 :param index: Current creation order
467 :param listdn: List of DNs on which the current DN depends on
468 :return: None if the current object do not depend on other object or if all object have been
469 created before."""
470 if listdn == None:
471 return None
472 for dn in listdn:
473 key = str(dn).lower()
474 if hash.has_key(key) and hash[key] > index:
475 return str(dn)
476 return None
479 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
480 """Add a new object if the dependencies are satisfied
482 The function add the object if the object on which it depends are already created
483 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
484 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
485 :param dn: DN of the object to be added
486 :param names: List of key provision parameters
487 :param basedn: DN of the partition to be updated
488 :param hash: Hash holding the different DN of the object to be created as key
489 :param index: Current creation order
490 :return: True if the object was created False otherwise"""
491 handle_special_add(sam_ldb,dn,names)
492 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
493 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
494 empty = Message()
495 delta = sam_ldb.msg_diff(empty,reference[0])
496 for att in hashAttrNotCopied.keys():
497 delta.remove(att)
498 for att in backlinked:
499 delta.remove(att)
500 depend_on_yettobecreated = None
501 for att in dn_syntax_att:
502 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
503 if depend_on_yet_tobecreated != None:
504 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
505 %(str(dn),depend_on_yet_tobecreated,str(att)))
506 return False
507 delta.dn = dn
508 message(CHANGE,"Object %s will be added"%dn)
509 sam_ldb.add(delta,["relax:0"])
510 return True
513 def gen_dn_index_hash(listMissing):
514 """Generate a hash associating the DN to its creation order
516 :param listMissing: List of DN
517 :return: Hash with DN as keys and creation order as values"""
518 hash = {}
519 for i in range(0,len(listMissing)):
520 hash[str(listMissing[i]).lower()] = i
521 return hash
524 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
525 """Add the missing object whose DN is the list
527 The function add the object if the object on which it depends are already created
528 :param newsam_ldb: Ldb object representing the SAM db of the reference provision
529 :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
530 :param dn: DN of the object to be added
531 :param names: List of key provision parameters
532 :param basedn: DN of the partition to be updated
533 :param list: List of DN to be added in the upgraded provision"""
534 listMissing = []
535 listDefered = list
537 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
538 index = 0
539 listMissing = listDefered
540 listDefered = []
541 hashMissing = gen_dn_index_hash(listMissing)
542 for dn in listMissing:
543 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
544 index = index + 1
545 if ret == 0:
546 #DN can't be created because it depends on some other DN in the list
547 listDefered.append(dn)
548 if len(listDefered) != 0:
549 raise ProvisioningError("Unable to insert missing elements: circular references")
552 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
553 """Check differences between the reference provision and the upgraded one.
555 It looks for all objects which base DN is name. If ischema is "false" then
556 the scan is done in cross partition mode.
557 If "ischema" is true, then special handling is done for dealing with schema
559 This function will also add the missing object and update existing object to add
560 or remove attributes that were missing.
561 :param newpaths: List of paths for different provision objects from the reference provision
562 :param paths: List of paths for different provision objects from the upgraded provision
563 :param creds: Credential for the authentification
564 :param session: Session for connexion
565 :param basedn: DN of the partition to update
566 :param names: List of key provision parameters
567 :param ischema: Boolean indicating if the update is about the schema only
568 :return: Hash of security descriptor to update"""
570 hash_new = {}
571 hash = {}
572 hashallSD = {}
573 listMissing = []
574 listPresent = []
575 reference = []
576 current = []
577 # Connect to the reference provision and get all the attribute in the
578 # partition referred by name
579 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
580 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
581 sam_ldb.transaction_start()
582 if ischema:
583 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
584 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
585 else:
586 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
587 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
589 sam_ldb.transaction_commit()
590 # Create a hash for speeding the search of new object
591 for i in range(0,len(reference)):
592 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
594 # Create a hash for speeding the search of existing object in the
595 # current provision
596 for i in range(0,len(current)):
597 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
599 for k in hash_new.keys():
600 if not hash.has_key(k):
601 print hash_new[k]
602 listMissing.append(hash_new[k])
603 else:
604 listPresent.append(hash_new[k])
606 # Sort the missing object in order to have object of the lowest level
607 # first (which can be containers for higher level objects)
608 listMissing.sort(dn_sort)
609 listPresent.sort(dn_sort)
611 if ischema:
612 # The following lines (up to the for loop) is to load the up to
613 # date schema into our current LDB
614 # a complete schema is needed as the insertion of attributes
615 # and class is done against it
616 # and the schema is self validated
617 # The double ldb open and schema validation is taken from the
618 # initial provision script
619 # it's not certain that it is really needed ....
620 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
621 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
622 # Load the schema from the one we computed earlier
623 sam_ldb.set_schema_from_ldb(schema.ldb)
624 # And now we can connect to the DB - the schema won't be loaded
625 # from the DB
626 sam_ldb.connect(paths.samdb)
627 else:
628 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
630 sam_ldb.transaction_start()
631 # XXX: This needs to be wrapped in try/except so we
632 # abort on exceptions.
633 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
634 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
635 changed = 0
636 for dn in listPresent:
637 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
638 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
639 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
640 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
641 identic_rename(sam_ldb,reference[0].dn)
642 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
644 delta = sam_ldb.msg_diff(current[0],reference[0])
645 for att in hashAttrNotCopied.keys():
646 delta.remove(att)
647 for att in backlinked:
648 delta.remove(att)
649 delta.remove("parentGUID")
650 nb = 0
652 for att in delta:
653 msgElt = delta.get(att)
654 if att == "dn":
655 continue
656 if att == "name":
657 delta.remove(att)
658 continue
659 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
660 delta.remove(att)
661 continue
662 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
663 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
664 delta.remove(att)
665 continue
666 if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
667 if opts.debugchange or opts.debugall:
668 try:
669 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
670 except:
671 # FIXME: Should catch an explicit exception here
672 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
673 delta.remove(att)
674 delta.dn = dn
675 if len(delta.items()) >1:
676 attributes=",".join(delta.keys())
677 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
678 changed = changed + 1
679 sam_ldb.modify(delta)
681 sam_ldb.transaction_commit()
682 message(SIMPLE,"There are %d changed objects"%(changed))
683 return hashallSD
686 def check_updated_sd(newpaths, paths, creds, session, names):
687 """Check if the security descriptor in the upgraded provision are the same as the reference
689 :param newpaths: List of paths for different provision objects from the reference provision
690 :param paths: List of paths for different provision objects from the upgraded provision
691 :param creds: Credential for the authentification
692 :param session: Session for connexion
693 :param basedn: DN of the partition to update
694 :param names: List of key provision parameters"""
695 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
696 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
697 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
698 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
699 hash_new = {}
700 for i in range(0,len(reference)):
701 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
703 for i in range(0,len(current)):
704 key = str(current[i]["dn"]).lower()
705 if hash_new.has_key(key):
706 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
707 if sddl != hash_new[key]:
708 print "%s new sddl/sddl in ref"%key
709 print "%s\n%s"%(sddl,hash_new[key])
712 def update_sd(paths, creds, session, names):
713 """Update security descriptor of the current provision
715 During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
716 This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
717 and so SD can be safely recalculated from scratch to get them right.
719 :param paths: List of paths for different provision objects from the upgraded provision
720 :param creds: Credential for the authentification
721 :param session: Session for connexion
722 :param names: List of key provision parameters"""
724 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
725 sam_ldb.transaction_start()
726 # First update the SD for the rootdn
727 sam_ldb.set_session_info(session)
728 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
729 attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
730 delta = Message()
731 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
732 descr = get_domain_descriptor(names.domainsid)
733 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
734 sam_ldb.modify(delta,["recalculate_sd:0"])
735 # Then the config dn
736 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
737 delta = Message()
738 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
739 descr = get_config_descriptor(names.domainsid)
740 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
741 sam_ldb.modify(delta,["recalculate_sd:0"])
742 # Then the schema dn
743 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
744 delta = Message()
745 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
746 descr = get_schema_descriptor(names.domainsid)
747 delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
748 sam_ldb.modify(delta,["recalculate_sd:0"])
750 # Then the rest
751 hash = {}
752 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
753 for obj in res:
754 if not (str(obj["dn"]) == str(names.rootdn) or
755 str(obj["dn"]) == str(names.configdn) or \
756 str(obj["dn"]) == str(names.schemadn)):
757 hash[str(obj["dn"])] = obj["whenCreated"]
759 listkeys = hash.keys()
760 listkeys.sort(dn_sort)
762 for key in listkeys:
763 try:
764 delta = Message()
765 delta.dn = Dn(sam_ldb,key)
766 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
767 sam_ldb.modify(delta,["recalculate_sd:0"])
768 except:
769 # XXX: We should always catch an explicit exception.
770 # What could go wrong here?
771 sam_ldb.transaction_cancel()
772 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
773 attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
774 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
775 return
776 sam_ldb.transaction_commit()
779 def update_basesamdb(newpaths, paths, names):
780 """Update the provision container db: sam.ldb
782 :param newpaths: List of paths for different provision objects from the reference provision
783 :param paths: List of paths for different provision objects from the upgraded provision
784 :param names: List of key provision parameters"""
786 message(SIMPLE,"Copy samdb")
787 shutil.copy(newpaths.samdb,paths.samdb)
789 message(SIMPLE,"Update partitions filename if needed")
790 schemaldb = os.path.join(paths.private_dir, "schema.ldb")
791 configldb = os.path.join(paths.private_dir, "configuration.ldb")
792 usersldb = os.path.join(paths.private_dir, "users.ldb")
793 samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
795 if not os.path.isdir(samldbdir):
796 os.mkdir(samldbdir)
797 os.chmod(samldbdir,0700)
798 if os.path.isfile(schemaldb):
799 shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb" % str(names.schemadn).upper()))
800 os.remove(schemaldb)
801 if os.path.isfile(usersldb):
802 shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb" % str(names.rootdn).upper()))
803 os.remove(usersldb)
804 if os.path.isfile(configldb):
805 shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb" % str(names.configdn).upper()))
806 os.remove(configldb)
809 def update_privilege(newpaths, paths):
810 """Update the privilege database
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 message(SIMPLE, "Copy privilege")
815 shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"),
816 os.path.join(paths.private_dir, "privilege.ldb"))
819 def update_samdb(newpaths, paths, creds, session, names):
820 """Upgrade the SAM DB contents for all the provision
822 :param newpaths: List of paths for different provision objects from the reference provision
823 :param paths: List of paths for different provision objects from the upgraded provision
824 :param creds: Credential for the authentification
825 :param session: Session for connexion
826 :param names: List of key provision parameters"""
828 message(SIMPLE, "Doing schema update")
829 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
830 message(SIMPLE,"Done with schema update")
831 message(SIMPLE,"Scanning whole provision for updates and additions")
832 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
833 message(SIMPLE,"Done with scanning")
836 def update_machine_account_password(paths, creds, session, names):
837 """Update (change) the password of the current DC both in the SAM db and in secret one
839 :param paths: List of paths for different provision objects from the upgraded provision
840 :param creds: Credential for the authentification
841 :param session: Session for connexion
842 :param names: List of key provision parameters"""
844 secrets_ldb = Ldb(paths.secrets, session_info=session,
845 credentials=creds,lp=lp)
846 secrets_ldb.transaction_start()
847 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
848 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
849 sam_ldb.transaction_start()
850 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
851 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
852 assert(len(res) == 1)
854 msg = Message(res[0].dn)
855 machinepass = samba.generate_random_password(128, 255)
856 msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
857 sam_ldb.modify(msg)
859 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
860 attrs=["msDs-keyVersionNumber"])
861 assert(len(res) == 1)
862 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
864 secretsdb_self_join(secrets_ldb, domain=names.domain,
865 realm=names.realm or sambaopts._lp.get('realm'),
866 domainsid=names.domainsid,
867 dnsdomain=names.dnsdomain,
868 netbiosname=names.netbiosname,
869 machinepass=machinepass,
870 key_version_number=kvno,
871 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
872 sam_ldb.transaction_prepare_commit()
873 secrets_ldb.transaction_prepare_commit()
874 sam_ldb.transaction_commit()
875 secrets_ldb.transaction_commit()
876 else:
877 secrets_ldb.transaction_cancel()
880 def update_gpo(paths,creds,session,names):
881 """Create missing GPO file object if needed
883 Set ACL correctly also.
885 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
886 if not os.path.isdir(dir):
887 create_gpo_struct(dir)
889 dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
890 if not os.path.isdir(dir):
891 create_gpo_struct(dir)
892 samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
893 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
894 names.domaindn, samdb, lp)
897 def updateOEMInfo(paths, creds, session,names):
898 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
899 options=["modules:samba_dsdb"])
900 res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
901 scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
902 if len(res) > 0:
903 info = res[0]["oEMInformation"]
904 info = "%s, upgrade to %s"%(info,version)
905 delta = Message()
906 delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
907 descr = get_schema_descriptor(names.domainsid)
908 delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
909 "oEMInformation" )
910 sam_ldb.modify(delta)
913 def setup_path(file):
914 return os.path.join(setup_dir, file)
917 if __name__ == '__main__':
918 # From here start the big steps of the program
919 # First get files paths
920 paths=get_paths(param,smbconf=smbconf)
921 paths.setup = setup_dir
922 # Guess all the needed names (variables in fact) from the current
923 # provision.
925 names = find_provision_key_parameters(param, creds, session, paths, smbconf)
926 if not sanitychecks(creds,session,names,paths):
927 message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
928 sys.exit(1)
929 # Let's see them
930 print_provision_key_parameters(names)
931 # With all this information let's create a fresh new provision used as reference
932 message(SIMPLE,"Creating a reference provision")
933 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
934 newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
935 # Get file paths of this new provision
936 newpaths = get_paths(param, targetdir=provisiondir)
937 populate_backlink(newpaths, creds, session,names.schemadn)
938 populate_dnsyntax(newpaths, creds, session,names.schemadn)
939 # Check the difference
940 update_basesamdb(newpaths, paths, names)
942 if opts.full:
943 update_samdb(newpaths, paths, creds, session, names)
944 update_secrets(newpaths, paths, creds, session)
945 update_privilege(newpaths, paths)
946 update_machine_account_password(paths, creds, session, names)
947 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
948 # to recreate them with the good form but with system account and then give the ownership to admin ...
949 admin_session_info = admin_session(lp, str(names.domainsid))
950 message(SIMPLE, "Updating SD")
951 update_sd(paths, creds, session,names)
952 update_sd(paths, creds, admin_session_info, names)
953 check_updated_sd(newpaths, paths, creds, session, names)
954 updateOEMInfo(paths,creds,session,names)
955 message(SIMPLE, "Upgrade finished !")
956 # remove reference provision now that everything is done !
957 shutil.rmtree(provisiondir)