The Samba-Bugzilla – Attachment 4744 Details for
Bug 6760
Samba4 fails returns empty SACL/DACL in LDAP although being not empty in the LDB
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
script to show difference between a good and a bad provision
diffprov.py (text/x-python), 20.46 KB, created by
Matthieu Patou
on 2009-09-25 03:16:59 UTC
(
hide
)
Description:
script to show difference between a good and a bad provision
Filename:
MIME Type:
Creator:
Matthieu Patou
Created:
2009-09-25 03:16:59 UTC
Size:
20.46 KB
patch
obsolete
>#!/usr/bin/python ># ># Copyright (C) Matthieu Patou <mat@matws.net> 2009 ># ># Based on provision a Samba4 server by ># Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008 ># Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008 ># ># ># This program is free software; you can redistribute it and/or modify ># it under the terms of the GNU General Public License as published by ># the Free Software Foundation; either version 3 of the License, or ># (at your option) any later version. ># ># This program is distributed in the hope that it will be useful, ># but WITHOUT ANY WARRANTY; without even the implied warranty of ># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ># GNU General Public License for more details. ># ># You should have received a copy of the GNU General Public License ># along with this program. If not, see <http://www.gnu.org/licenses/>. > > >import getopt >import optparse >import os >import sys >import random >import string >import re >import base64 ># Find right directory when running from source tree >sys.path.insert(0, "bin/python") > >from base64 import b64encode > >import samba >from samba.credentials import DONT_USE_KERBEROS >from samba.auth import system_session, admin_session >from samba import Ldb >from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError >import ldb >import samba.getopt as options >from samba.samdb import SamDB >from samba import param >from samba.provision import ProvisionNames,provision_paths_from_lp,find_setup_dir,Schema,get_linked_attributes,FILL_FULL,provision >from samba.dcerpc import misc, security >from samba.ndr import ndr_pack, ndr_unpack > >replace=2^ldb.FLAG_MOD_REPLACE >add=2^ldb.FLAG_MOD_ADD >delete=2^ldb.FLAG_MOD_DELETE > >CHANGE = 0x01 >CHANGESD = 0x02 >GUESS = 0x04 >CHANGEALL = 0xff > ># Attributes that not copied from the reference provision even if they do not exists in the destination object ># This is most probably because they are populated automatcally when object is created >hhashAttrNotCopied = { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\ > "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\ > "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\ > "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\ > "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\ > "maxPwdAge":1, "mail":1, } >hashAttrNotCopied = {"uSNChanged": 1,"whenChanged": 1,"replPropertyMetaData": 1} ># Usually for an object that already exists we do not overwrite attributes as they might have been changed for good ># reasons. Anyway for a few of thems it's mandatory to replace them otherwise the provision will be broken somehow. >hhashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,"systemOnly":replace, "searchFlags":replace,\ > "mayContain":replace, "systemFlags":replace, > "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":1,"possibleInferiors":replace+delete} >hashOverwrittenAtt = {} >backlinked = [] > >def define_what_to_log(opts): > what = 0 > if opts.debugchange: > what = what | CHANGE > if opts.debugchangesd: > what = what | CHANGESD > if opts.debugguess: > what = what | GUESS > if opts.debugall: > what = what | CHANGEALL > return what > > > >parser = optparse.OptionParser("diffprov [options]") >sambaopts = options.SambaOptions(parser) >parser.add_option_group(sambaopts) >parser.add_option_group(options.VersionOptions(parser)) >credopts = options.CredentialsOptions(parser) >parser.add_option_group(credopts) >parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true") >parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true") >parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true") >parser.add_option("--debugall", help="Print information security descriptors differences", action="store_true") >parser.add_option("--bad", type="string", metavar="DIR", help="To be analyzed provision dir") >parser.add_option("--good", type="string", metavar="DIR", help="Reference provision dir") > >opts = parser.parse_args()[0] > >whatToLog = define_what_to_log(opts) > >def messageprovision(text): > """print a message if quiet is not set.""" > if opts.debugprovision or opts.debugall: > print text > >def message(what,text): > """print a message if quiet is not set.""" > if whatToLog & what: > print text > >if len(sys.argv) == 1: > opts.interactive = True >lp = sambaopts.get_loadparm() >smbconf = lp.configfile > >creds = credopts.get_credentials(lp) >creds.set_kerberos_state(DONT_USE_KERBEROS) >session = system_session() > ># Create an array of backlinked attributes >def populate_backlink(newpaths,creds,session,schemadn): > newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp) > backlinked.extend(get_linked_attributes(ldb.Dn(newsam_ldb,str(schemadn)),newsam_ldb).values()) > ># Get Paths for important objects (ldb, keytabs ...) >def get_paths(targetdir=None,smbconf=None): > if targetdir is not None: > if (not os.path.exists(os.path.join(targetdir, "etc"))): > os.makedirs(os.path.join(targetdir, "etc")) > smbconf = os.path.join(targetdir, "etc", "smb.conf") > if smbconf is None: > smbconf = param.default_path() > > if not os.path.exists(smbconf): > print >>sys.stderr, "Unable to find smb.conf .." > parser.print_usage() > sys.exit(1) > > lp = param.LoadParm() > lp.load(smbconf) ># Normaly we need the domain name for this function but for our needs it's pointless > paths = provision_paths_from_lp(lp,"foo") > return paths > ># This function guess(fetch) informations needed to make a fresh provision from the current provision ># It includes: realm, workgroup, partitions, netbiosname, domain guid, ... >def guess_names_from_current_provision(credentials,session_info,paths): > lp = param.LoadParm() > lp.load(paths.smbconf) > names = ProvisionNames() > # NT domain, kerberos realm, root dn, domain dn, domain dns name > names.domain = string.upper(lp.get("workgroup")) > names.realm = lp.get("realm") > rootdn = "DC=" + names.realm.replace(".",",DC=") > names.domaindn = rootdn > names.dnsdomain = names.realm > names.realm = string.upper(names.realm) > # netbiosname > secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials,lp=lp) > # Get the netbiosname first (could be obtained from smb.conf in theory) > attrs = ["sAMAccountName"] > res = secrets_ldb.search(expression="(flatname=%s)"%names.domain,base="CN=Primary Domains", scope=SCOPE_SUBTREE, attrs=attrs) > names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","") > > #partitions = get_partitions(credentials,session_info,paths,lp) > names.smbconf = smbconf > samdb = SamDB(paths.samdb, session_info=session_info, > credentials=credentials, lp=lp) > > # partitions (schema,config,root) > # That's a bit simplistic but it's ok as long as we have only 3 partitions > attrs2 = ["schemaNamingContext","configurationNamingContext","rootDomainNamingContext"] > res2 = samdb.search(expression="(objectClass=*)",base="", scope=SCOPE_BASE, attrs=attrs2) > > names.configdn = res2[0]["configurationNamingContext"] > configdn = str(names.configdn) > names.schemadn = res2[0]["schemaNamingContext"] > if not (rootdn == str(res2[0]["rootDomainNamingContext"])): > print >>sys.stderr, "rootdn in sam.ldb and smb.conf is not the same ..." > else: > names.rootdn=res2[0]["rootDomainNamingContext"] > # default site name > attrs3 = ["cn"] > res3= samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_ONELEVEL, attrs=attrs3) > names.sitename = str(res3[0]["cn"]) > > # dns hostname and server dn > attrs4 = ["dNSHostName"] > res4= samdb.search(expression="(CN=%s)"%names.netbiosname,base="OU=Domain Controllers,"+rootdn, \ > scope=SCOPE_ONELEVEL, attrs=attrs4) > names.hostname = str(res4[0]["dNSHostName"]).replace("."+names.dnsdomain,"") > > names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (names.netbiosname, names.sitename, configdn) > > # invocation id > attrs5 = ["invocationId"] > res5 = samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_SUBTREE, attrs=attrs5) > for i in range(0,len(res5)): > if ( len(res5[i]) > 0): > names.invocation = str(ndr_unpack( misc.GUID,res5[i]["invocationId"][0])) > break > # domain guid/sid > attrs6 = ["objectGUID", "objectSid", ] > res6 = samdb.search(expression="(objectClass=*)",base=rootdn, scope=SCOPE_BASE, attrs=attrs6) > names.domainguid = str(ndr_unpack( misc.GUID,res6[0]["objectGUID"][0])) > names.domainsid = str(ndr_unpack( security.dom_sid,res6[0]["objectSid"][0])) > > # policy guid > attrs7 = ["cn","displayName"] > res7 = samdb.search(expression="(displayName=Default Domain Policy)",base="CN=Policies,CN=System,"+rootdn, \ > scope=SCOPE_ONELEVEL, attrs=attrs7) > names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","") > # dc policy guid > attrs8 = ["cn","displayName"] > res8 = samdb.search(expression="(displayName=Default Domain Controllers Policy)",base="CN=Policies,CN=System,"+rootdn, \ > scope=SCOPE_ONELEVEL, attrs=attrs7) > if len(res8) == 1: > names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","") > else: > names.policyid_dc = None > # ntds guid > attrs9 = ["objectGUID" ] > exp = "(dn=CN=NTDS Settings,%s)"%(names.serverdn) > print exp > res9 = samdb.search(expression="(dn=CN=NTDS Settings,%s)"%(names.serverdn),base=str(names.configdn), scope=SCOPE_SUBTREE, attrs=attrs9) > names.ntdsguid = str(ndr_unpack( misc.GUID,res9[0]["objectGUID"][0])) > > > return names > ># Debug a little bit >def print_names(names): > message(GUESS, "rootdn :"+str(names.rootdn)) > message(GUESS, "configdn :"+str(names.configdn)) > message(GUESS, "schemadn :"+str(names.schemadn)) > message(GUESS, "serverdn :"+names.serverdn) > message(GUESS, "netbiosname :"+names.netbiosname) > message(GUESS, "defaultsite :"+names.sitename) > message(GUESS, "dnsdomain :"+names.dnsdomain) > message(GUESS, "hostname :"+names.hostname) > message(GUESS, "domain :"+names.domain) > message(GUESS, "realm :"+names.realm) > message(GUESS, "invocationid:"+names.invocation) > message(GUESS, "policyguid :"+names.policyid) > message(GUESS, "policyguiddc:"+str(names.policyid_dc)) > message(GUESS, "domainsid :"+names.domainsid) > message(GUESS, "domainguid :"+names.domainguid) > message(GUESS, "ntdsguid :"+names.ntdsguid) > ># Create a fresh new reference provision ># This provision will be the reference for knowing what has changed in the ># since the latest upgrade in the current provision > ># This function sorts two dn in the lexicographical order and put higher level DN before ># So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller (-1) as it has less ># level >def dn_sort(x,y): > p = re.compile(r'(?<!\\),') > tab1 = p.split(str(x)) > tab2 = p.split(str(y)) > min = 0 > if (len(tab1) > len(tab2)): > min = len(tab2) > elif (len(tab1) < len(tab2)): > min = len(tab1) > else: > min = len(tab1) > len1=len(tab1)-1 > len2=len(tab2)-1 > space = " " > # Note: python range go up to upper limit but do not include it > for i in range(0,min): > ret=cmp(tab1[len1-i],tab2[len2-i]) > if(ret != 0): > return ret > else: > if(i==min-1): > if(len1==len2): > print >>sys.stderr,"PB PB PB"+space.join(tab1)+" / "+space.join(tab2) > if(len1>len2): > return 1 > else: > return -1 > return ret >def printSD(sd): > print "revision %s"%sd.revision > print "type %s"%sd.type > print "owner sid %s"%sd.owner_sid > print "group sid %s"%sd.group_sid > print "sacl revision:%s"%str(sd.sacl.revision) > print "sacl size:%s"%str(sd.sacl.size) > print "sacl #ace:%s"%str(sd.sacl.num_aces) > print "dacl revision:%s"%str(sd.dacl.revision) > print "dacl size:%s"%str(sd.dacl.size) > print "dacl #ace:%s"%str(sd.dacl.num_aces) > ># check from security descriptors modifications return 1 if it is 0 otherwise ># it also populate hash structure for later use in the upgrade process >def handle_security_desc(ischema,att,msgElt,hashallSD,old,new): > if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE: > hashSD = {} > hashSD["oldSD"] = old[0][att] > hashSD["newSD"] = new[0][att] > hashallSD[str(old[0].dn)] = hashSD > return 1 > if att == "nTSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE: > if ischema == 0: > hashSD = {} > hashSD["oldSD"] = ndr_unpack(security.descriptor,str(old[0][att])) > hashSD["newSD"] = ndr_unpack(security.descriptor,str(new[0][att])) > > s1=hashSD["oldSD"].as_sddl(security.dom_sid("S-1-5-21-718463037-488991067-1482376005")) > s2=hashSD["newSD"].as_sddl(security.dom_sid("S-1-5-21-718463037-488991067-1482376005")) > if ( s1 == s2 ): > print "Same sddl for bad and good" > print "SD for bad" > printSD(hashSD["oldSD"]) > print "\nSD for good" > printSD(hashSD["newSD"]) > else: > print "oldSD %s"%s1 > print "newSD %s"%s2 > hashallSD[str(old[0].dn)] = hashSD > return 1 > return 0 > ># Hangle special cases ... That's when we want to update an attribute only ># if it has a certain value or if it's for a certain object or ># a class of object. ># It can be also if we want to do a merge of value instead of a simple replace > >def handle_special_case(att,delta,new,old,ischema): > flag = delta.get(att).flags() > if (att == "gPLink" or att == "gPCFileSysPath") and flag == ldb.FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower(): > delta.remove(att) > return 1 > if att == "forceLogoff": > ref=0x8000000000000000 > oldval=int(old[0][att][0]) > newval=int(new[0][att][0]) > ref == old and ref == abs(new) > return 1 > if (att == "adminDisplayName" or att == "adminDescription") and ischema: > return 1 > if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag == ldb.FLAG_MOD_REPLACE): > return 1 > if (str(old[0].dn) == "CN=S-1-5-11,CN=ForeignSecurityPrincipals,%s"%(str(names.rootdn)) and att == "description" and flag == ldb.FLAG_MOD_DELETE): > return 1 > if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == ldb.FLAG_MOD_REPLACE): > return 1 > if ( (att == "member" or att == "servicePrincipalName") and flag == ldb.FLAG_MOD_REPLACE): > > hash = {} > newval = [] > changeDelta=0 > for elem in old[0][att]: > hash[str(elem)]=1 > newval.append(str(elem)) > > for elem in new[0][att]: > if not hash.has_key(str(elem)): > changeDelta=1 > newval.append(str(elem)) > if changeDelta == 1: > delta[att] = ldb.MessageElement(newval, ldb.FLAG_MOD_REPLACE, att) > else: > delta.remove(att) > return 1 > if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == ldb.FLAG_MOD_REPLACE): > return 1 > if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn): > return 1 > return 0 > ># Check difference between the current provision and the reference provision. ># It looks for all object which base DN is name if ischema is false then scan is done in ># cross partition mode. ># If ischema is true, then special handling is done for dealing with schema >def check_diff_name(newpaths,paths,creds,session,name,serverdn,ischema): > hash_new = {} > hash = {} > hashallSD = {} > listMissing = [] > listPresent = [] > res = [] > res2 = [] > # Connect to the reference provision and get all the attribute in the partition referred by name > newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp) > sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp) > if ischema: > res = newsam_ldb.search(expression="objectClass=*",base=name, scope=SCOPE_SUBTREE,attrs=["dn"]) > res2 = sam_ldb.search(expression="objectClass=*",base=name, scope=SCOPE_SUBTREE,attrs=["dn"]) > else: > res = newsam_ldb.search(expression="objectClass=*",base=name, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"]) > res2 = sam_ldb.search(expression="objectClass=*",base=name, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"]) > > # Create a hash for speeding the search of new object > for i in range(0,len(res)): > hash_new[str(res[i]["dn"]).lower()] = res[i]["dn"] > > # Create a hash for speeding the search of existing object in the current provision > for i in range(0,len(res2)): > hash[str(res2[i]["dn"]).lower()] = res2[i]["dn"] > > for k in hash_new.keys(): > if not hash.has_key(k): > listMissing.append(hash_new[k]) > else: > listPresent.append(hash_new[k]) > > # Sort the missing object in order to have object of the lowest level first (which can be > # containers for higher level objects) > listMissing.sort(dn_sort) > listPresent.sort(dn_sort) > > if 1 and ischema: > # The following lines (up to the for loop) is to load the up to date schema into our current LDB > # a complete schema is needed as the insertion of attributes and class is done against it > # and the schema is self validated > # The double ldb open and schema validation is taken from the initial provision script > # it's not certain that it is really needed .... > sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp) > schema = Schema(setup_path, schemadn=name, serverdn=serverdn) > # Load the schema from the one we computed earlier > sam_ldb.set_schema_from_ldb(schema.ldb) > # And now we can connect to the DB - the schema won't be loaded from the DB > sam_ldb.connect(paths.samdb) > sam_ldb.transaction_start() > else: > sam_ldb.transaction_start() > > empty = ldb.Message() > print "There are %d missing objects"%(len(listMissing)) > for dn in listMissing: > res = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=name, scope=SCOPE_SUBTREE,controls=["search_options:1:2"]) > #print >>sys.stderr, "@@@"+str(dn) > delta = sam_ldb.msg_diff(empty,res[0]) > for att in hashAttrNotCopied.keys(): > delta.remove(att) > for att in backlinked: > delta.remove(att) > delta.dn = dn > sam_ldb.add(delta) > > changed = 0 > for dn in listPresent: > res = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=name, scope=SCOPE_SUBTREE,controls=["search_options:1:2"]) > res2 = sam_ldb.search(expression="dn=%s"%(str(dn)),base=name, scope=SCOPE_SUBTREE,controls=["search_options:1:2"]) > delta = sam_ldb.msg_diff(res2[0],res[0]) > for att in hashAttrNotCopied.keys(): > delta.remove(att) > # for att in backlinked: > # delta.remove(att) > delta.remove("parentGUID") > nb = 0 > dnprinted = 0 > for att in delta: > msgElt = delta.get(att) > if att == "dn": > continue > if not dnprinted: > print "\n%s"%str(dn) > dnprinted = 1 > print att > if handle_security_desc(ischema,att,msgElt,hashallSD,res2,res): > delta.remove(att) > continue > if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())): > if handle_special_case(att,delta,res,res2,ischema)==0 and msgElt.flags()!=ldb.FLAG_MOD_ADD: > i = 0 > if opts.debugchange: > message(CHANGE, "dn= "+str(dn)+ " "+att + " with flag "+str(msgElt.flags())+ " is not allowed to be changed/removed, I discard this change ...") > for e in range(0,len(res2[0][att])): > message(CHANGE,"old %d : %s"%(i,str(res2[0][att][e]))) > if msgElt.flags() == 2: > i = 0 > for e in range(0,len(res[0][att])): > message(CHANGE,"new %d : %s"%(i,str(res[0][att][e]))) > delta.remove(att) > delta.dn = dn > if len(delta.items()) >1: > attributes=",".join(delta.keys()) > message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes)) > changed = changed + 1 > sam_ldb.modify(delta) > > sam_ldb.transaction_commit() > print "There are %d changed objects"%(changed) > return hashallSD > > > >def rmall(topdir): > for root, dirs, files in os.walk(topdir, topdown=False): > for name in files: > os.remove(os.path.join(root, name)) > for name in dirs: > os.rmdir(os.path.join(root, name)) > os.rmdir(topdir) > ># For each partition check the differences > >def check_diff(newpaths,paths,creds,session,names): > #for name in [str(names.schemadn), str(names.configdn), str(names.rootdn)] : > #for name in [str(names.configdn)] : > #os.copy("newpaths.samdb","paths.samdb") > print "Scanning whole provision for updates and additions" > hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names.serverdn,0) > print "Done with scanning" > ># From here start the big steps of the program ># First get files paths >paths=get_paths(targetdir=opts.bad,smbconf=smbconf) ># Guess all the needed names (variables in fact) from the current ># provision. >names = guess_names_from_current_provision(creds,session,paths) ># Let's see them ># Get file paths of this new provision >newpaths = get_paths(targetdir=opts.good) >populate_backlink(newpaths,creds,session,names.schemadn) ># Check the difference >check_diff(newpaths,paths,creds,session,names) ># remove reference provision now that everything is done ! >#rmall(provisiondir)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 6760
:
4741
|
4742
|
4743
| 4744 |
4745