diff options
| author | William Brown <william@blackhats.net.au> | 2018-04-28 15:22:29 +1000 |
|---|---|---|
| committer | Andrew Bartlett <abartlet@samba.org> | 2018-05-29 05:34:08 +0200 |
| commit | 74d85d4cc246458f51ec9b264310959055f0a0b9 (patch) | |
| tree | 1c20131709a86f89f49df7be2b3988f7b25c32de /python | |
| parent | 289ae87c3bb81b2e1cd30a876a3b694b7264edc5 (diff) | |
| download | samba-74d85d4cc246458f51ec9b264310959055f0a0b9.tar.gz samba-74d85d4cc246458f51ec9b264310959055f0a0b9.tar.bz2 samba-74d85d4cc246458f51ec9b264310959055f0a0b9.zip | |
python/samba/netcmd/schema.py: add schema query and management.
Schema management in active directory is complex and dangerous. Having
a tool that safely wraps administrative tasks as well as allowing query
of the schema will make this complex topic more accessible to administrators.
Signed-off-by: William Brown <william@blackhats.net.au>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Diffstat (limited to 'python')
| -rw-r--r-- | python/samba/ms_schema.py | 9 | ||||
| -rw-r--r-- | python/samba/netcmd/main.py | 1 | ||||
| -rw-r--r-- | python/samba/netcmd/schema.py | 262 | ||||
| -rw-r--r-- | python/samba/samdb.py | 4 | ||||
| -rw-r--r-- | python/samba/tests/samba_tool/schema.py | 88 |
5 files changed, 361 insertions, 3 deletions
diff --git a/python/samba/ms_schema.py b/python/samba/ms_schema.py index de6e4b28cdc..e8363754281 100644 --- a/python/samba/ms_schema.py +++ b/python/samba/ms_schema.py @@ -36,14 +36,17 @@ bitFields["searchflags"] = { 'fTUPLEINDEX': 26, # TP 'fSUBTREEATTINDEX': 25, # ST 'fCONFIDENTIAL': 24, # CF + 'fCONFIDENTAIL': 24, # typo 'fNEVERVALUEAUDIT': 23, # NV 'fRODCAttribute': 22, # RO # missing in ADTS but required by LDIF - 'fRODCFilteredAttribute': 22, # RO ? - 'fCONFIDENTAIL': 24, # typo - 'fRODCFILTEREDATTRIBUTE': 22 # case + 'fRODCFilteredAttribute': 22, # RO + 'fRODCFILTEREDATTRIBUTE': 22, # case + 'fEXTENDEDLINKTRACKING': 21, # XL + 'fBASEONLY': 20, # BO + 'fPARTITIONSECRET': 19, # SE } # ADTS: 2.2.10 diff --git a/python/samba/netcmd/main.py b/python/samba/netcmd/main.py index 56720801199..83797662083 100644 --- a/python/samba/netcmd/main.py +++ b/python/samba/netcmd/main.py @@ -70,6 +70,7 @@ class cmd_sambatool(SuperCommand): subcommands["ldapcmp"] = None subcommands["ntacl"] = None subcommands["rodc"] = None + subcommands["schema"] = None subcommands["sites"] = None subcommands["spn"] = None subcommands["testparm"] = None diff --git a/python/samba/netcmd/schema.py b/python/samba/netcmd/schema.py new file mode 100644 index 00000000000..dbefe7999cb --- /dev/null +++ b/python/samba/netcmd/schema.py @@ -0,0 +1,262 @@ +# Manipulate ACLs on directory objects +# +# Copyright (C) William Brown <william@blackhats.net.au> 2018 +# +# 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 ldb +import samba.getopt as options +from samba.ms_schema import bitFields +from samba.auth import system_session +from samba.samdb import SamDB +from samba.netcmd import ( + Command, + CommandError, + SuperCommand, + Option + ) + +class cmd_schema_attribute_modify(Command): + """Modify attribute settings in the schema partition. + + This commands allows minor modifications to attributes in the schema. Active + Directory does not allow many changes to schema, but important modifications + are related to indexing. This command overwrites the value of searchflags, + so be sure to view the current content before making changes. + + Example1: + samba-tool schema attribute modify uid \ + --searchflags="fATTINDEX,fPRESERVEONDELETE" + + This alters the uid attribute to be indexed and to be preserved when + converted to a tombstone. + + Important search flag values are: + + fATTINDEX: create an equality index for this attribute. + fPDNTATTINDEX: create a container index for this attribute (ie OU). + fANR: specify that this attribute is a member of the ambiguous name + resolution set. + fPRESERVEONDELETE: indicate that the value of this attribute should be + preserved when the object is converted to a tombstone (deleted). + fCOPY: hint to clients that this attribute should be copied. + fTUPLEINDEX: create a tuple index for this attribute. This is used in + substring queries. + fSUBTREEATTINDEX: create a browsing index for this attribute. VLV searches + require this. + fCONFIDENTIAL: indicate that the attribute is confidental and requires + special access checks. + fNEVERVALUEAUDIT: indicate that changes to this value should NOT be audited. + fRODCFILTEREDATTRIBUTE: indicate that this value should not be replicated to + RODCs. + fEXTENDEDLINKTRACKING: indicate to the DC to perform extra link tracking. + fBASEONLY: indicate that this attribute should only be displayed when the + search scope of the query is SCOPE_BASE or a single object result. + fPARTITIONSECRET: indicate that this attribute is a partition secret and + requires special access checks. + + The authoritative source of this information is the MS-ADTS. + """ + synopsis = "%prog attribute [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("--searchflags", help="Search Flags for the attribute", type=str), + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["attribute"] + + def run(self, attribute, H=None, credopts=None, sambaopts=None, + versionopts=None, searchflags=None): + + if searchflags is None: + raise CommandError('A value to modify must be provided.') + + # Parse the search flags to a set of bits to modify. + + searchflags_int = None + if searchflags is not None: + searchflags_int = 0 + flags = searchflags.split(',') + # We have to normalise all the values. To achieve this predictably + # we title case (Fattrindex), then swapcase (fATTINDEX) + flags = [ x.capitalize().swapcase() for x in flags ] + for flag in flags: + if flag not in bitFields['searchflags'].keys(): + raise CommandError("Unknown flag '%s', please see --help" % flag) + bit_loc = 31 - bitFields['searchflags'][flag] + # Now apply the bit. + searchflags_int = searchflags_int | (1 << bit_loc) + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + schema_dn = samdb.schema_dn() + # For now we make assumptions about the CN + attr_dn = 'cn=%s,%s' % (attribute, schema_dn) + + m = ldb.Message() + m.dn = ldb.Dn(samdb, attr_dn) + + if searchflags_int is not None: + m['searchFlags'] = ldb.MessageElement( + str(searchflags_int), ldb.FLAG_MOD_REPLACE, 'searchFlags') + + samdb.modify(m) + print("modified %s" % attr_dn) + +class cmd_schema_attribute_show(Command): + """Show details about an attribute from the schema. + + Schema attribute definitions define and control the behaviour of directory + attributes on objects. This displays the details of a single attribute. + """ + synopsis = "%prog attribute [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["attribute"] + + def run(self, attribute, H=None, credopts=None, sambaopts=None, versionopts=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + schema_dn = samdb.schema_dn() + + filt = '(&(objectClass=attributeSchema)(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(attribute) + + res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE, + expression=filt) + + if len(res) == 0: + raise CommandError('No schema objects matched "%s"' % attribute) + if len(res) > 1: + raise CommandError('Multiple schema objects matched "%s": this is a serious issue you should report!' % attribute) + + # Get the content of searchFlags (if any) and manipulate them to + # show our friendly names. + + # WARNING: If you are reading this in the future trying to change an + # ldb message dynamically, and wondering why you get an operations + # error, it's related to talloc references. + # + # When you create *any* python reference, IE: + # flags = res[0]['attr'] + # this creates a talloc_reference that may live forever due to pythons + # memory management model. However, when you create this reference it + # blocks talloc_realloc from functions in msg.add(element). + # + # As a result, you MUST avoid ALL new variable references UNTIL you have + # modified the message as required, even if it makes your code more + # verbose. + + if 'searchFlags' in res[0].keys(): + flags_i = None + try: + # See above + flags_i = int(str(res[0]['searchFlags'])) + except ValueError: + raise CommandError('Invalid schemaFlags value "%s": this is a serious issue you should report!' % res[0]['searchFlags']) + # Work out what keys we have. + out = [] + for flag in bitFields['searchflags'].keys(): + if flags_i & (1 << (31 - bitFields['searchflags'][flag])) != 0: + out.append(flag) + if len(out) > 0: + res[0].add(ldb.MessageElement(out, ldb.FLAG_MOD_ADD, 'searchFlagsDecoded')) + + user_ldif = samdb.write_ldif(res[0], ldb.CHANGETYPE_NONE) + self.outf.write(user_ldif) + +class cmd_schema_objectclass_show(Command): + """Show details about an objectClass from the schema. + + Schema objectClass definitions define and control the behaviour of directory + objects including what attributes they may contain. This displays the + details of an objectClass. + """ + synopsis = "%prog objectclass [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["objectclass"] + + def run(self, objectclass, H=None, credopts=None, sambaopts=None, versionopts=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + schema_dn = samdb.schema_dn() + + filt = '(&(objectClass=classSchema)' \ + '(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(objectclass) + + res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE, + expression=filt) + + for msg in res: + user_ldif = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE) + self.outf.write(user_ldif) + +class cmd_schema_attribute(SuperCommand): + """Query and manage attributes in the schema partition.""" + subcommands = {} + subcommands["modify"] = cmd_schema_attribute_modify() + subcommands["show"] = cmd_schema_attribute_show() + +class cmd_schema_objectclass(SuperCommand): + """Query and manage objectclasses in the schema partition.""" + subcommands = {} + subcommands["show"] = cmd_schema_objectclass_show() + +class cmd_schema(SuperCommand): + """Schema querying and management.""" + + subcommands = {} + subcommands["attribute"] = cmd_schema_attribute() + subcommands["objectclass"] = cmd_schema_objectclass() diff --git a/python/samba/samdb.py b/python/samba/samdb.py index abe434c8578..2b5c43faa54 100644 --- a/python/samba/samdb.py +++ b/python/samba/samdb.py @@ -90,6 +90,10 @@ class SamDB(samba.Ldb): '''return the domain DN''' return str(self.get_default_basedn()) + def schema_dn(self): + '''return the schema partition dn''' + return str(self.get_schema_basedn()) + def disable_account(self, search_filter): """Disables an account diff --git a/python/samba/tests/samba_tool/schema.py b/python/samba/tests/samba_tool/schema.py new file mode 100644 index 00000000000..373ae16aad9 --- /dev/null +++ b/python/samba/tests/samba_tool/schema.py @@ -0,0 +1,88 @@ +# Unix SMB/CIFS implementation. +# Copyright (C) William Brown <william@blackhats.net.au> 2018 +# +# 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 os +import ldb +from samba.tests.samba_tool.base import SambaToolCmdTest + +class SchemaCmdTestCase(SambaToolCmdTest): + """Tests for samba-tool dsacl subcommands""" + samdb = None + + def setUp(self): + super(SchemaCmdTestCase, self).setUp() + self.samdb = self.getSamDB("-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + + def tearDown(self): + super(SchemaCmdTestCase, self).tearDown() + + def test_display_attribute(self): + """Tests that we can display schema attributes""" + (result, out, err) = self.runsubcmd("schema", "attribute", + "show", "uid", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdSuccess(result, out, err) + + def test_modify_attribute_searchflags(self): + """Tests that we can modify searchFlags of an attribute""" + (result, out, err) = self.runsubcmd("schema", "attribute", + "modify", "uid", "--searchflags=9", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdFail(result, 'Unknown flag 9, please see --help') + + (result, out, err) = self.runsubcmd("schema", "attribute", + "modify", "uid", "--searchflags=fATTINDEX", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdSuccess(result, out, err) + + (result, out, err) = self.runsubcmd("schema", "attribute", + "modify", "uid", + "--searchflags=fATTINDEX,fSUBTREEATTINDEX", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdSuccess(result, out, err) + + (result, out, err) = self.runsubcmd("schema", "attribute", + "modify", "uid", + "--searchflags=fAtTiNdEx,fPRESERVEONDELETE", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdSuccess(result, out, err) + + def test_display_objectclass(self): + """Tests that we can display schema objectclasses""" + (result, out, err) = self.runsubcmd("schema", "objectclass", + "show", "person", + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + self.assertCmdSuccess(result, out, err) |
