PEP8: fix E127: continuation line over-indented for visual indent
[metze/samba/wip.git] / python / samba / netcmd / schema.py
1 # Manipulate ACLs on directory objects
2 #
3 # Copyright (C) William Brown <william@blackhats.net.au> 2018
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import ldb
20 import samba.getopt as options
21 from samba.ms_schema import bitFields
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba.netcmd import (
25     Command,
26     CommandError,
27     SuperCommand,
28     Option
29 )
30
31 class cmd_schema_attribute_modify(Command):
32     """Modify attribute settings in the schema partition.
33
34     This commands allows minor modifications to attributes in the schema. Active
35     Directory does not allow many changes to schema, but important modifications
36     are related to indexing. This command overwrites the value of searchflags,
37     so be sure to view the current content before making changes.
38
39     Example1:
40     samba-tool schema attribute modify uid \
41         --searchflags="fATTINDEX,fPRESERVEONDELETE"
42
43     This alters the uid attribute to be indexed and to be preserved when
44     converted to a tombstone.
45
46     Important search flag values are:
47
48     fATTINDEX: create an equality index for this attribute.
49     fPDNTATTINDEX: create a container index for this attribute (ie OU).
50     fANR: specify that this attribute is a member of the ambiguous name
51          resolution set.
52     fPRESERVEONDELETE: indicate that the value of this attribute should be
53          preserved when the object is converted to a tombstone (deleted).
54     fCOPY: hint to clients that this attribute should be copied.
55     fTUPLEINDEX: create a tuple index for this attribute. This is used in
56           substring queries.
57     fSUBTREEATTINDEX: create a browsing index for this attribute. VLV searches
58           require this.
59     fCONFIDENTIAL: indicate that the attribute is confidental and requires
60           special access checks.
61     fNEVERVALUEAUDIT: indicate that changes to this value should NOT be audited.
62     fRODCFILTEREDATTRIBUTE: indicate that this value should not be replicated to
63           RODCs.
64     fEXTENDEDLINKTRACKING: indicate to the DC to perform extra link tracking.
65     fBASEONLY: indicate that this attribute should only be displayed when the
66            search scope of the query is SCOPE_BASE or a single object result.
67     fPARTITIONSECRET: indicate that this attribute is a partition secret and
68            requires special access checks.
69
70     The authoritative source of this information is the MS-ADTS.
71     """
72     synopsis = "%prog attribute [options]"
73
74     takes_optiongroups = {
75         "sambaopts": options.SambaOptions,
76         "versionopts": options.VersionOptions,
77         "credopts": options.CredentialsOptions,
78     }
79
80     takes_options = [
81         Option("--searchflags", help="Search Flags for the attribute", type=str),
82         Option("-H", "--URL", help="LDB URL for database or target server",
83                type=str, metavar="URL", dest="H"),
84     ]
85
86     takes_args = ["attribute"]
87
88     def run(self, attribute, H=None, credopts=None, sambaopts=None,
89             versionopts=None, searchflags=None):
90
91         if searchflags is None:
92             raise CommandError('A value to modify must be provided.')
93
94         # Parse the search flags to a set of bits to modify.
95
96         searchflags_int = None
97         if searchflags is not None:
98             searchflags_int = 0
99             flags = searchflags.split(',')
100             # We have to normalise all the values. To achieve this predictably
101             # we title case (Fattrindex), then swapcase (fATTINDEX)
102             flags = [ x.capitalize().swapcase() for x in flags ]
103             for flag in flags:
104                 if flag not in bitFields['searchflags'].keys():
105                     raise CommandError("Unknown flag '%s', please see --help" % flag)
106                 bit_loc = 31 - bitFields['searchflags'][flag]
107                 # Now apply the bit.
108                 searchflags_int = searchflags_int | (1 << bit_loc)
109
110         lp = sambaopts.get_loadparm()
111         creds = credopts.get_credentials(lp)
112
113         samdb = SamDB(url=H, session_info=system_session(),
114             credentials=creds, lp=lp)
115
116         schema_dn = samdb.schema_dn()
117         # For now we make assumptions about the CN
118         attr_dn = 'cn=%s,%s' % (attribute, schema_dn)
119
120         m = ldb.Message()
121         m.dn = ldb.Dn(samdb, attr_dn)
122
123         if searchflags_int is not None:
124             m['searchFlags'] = ldb.MessageElement(
125                 str(searchflags_int), ldb.FLAG_MOD_REPLACE, 'searchFlags')
126
127         samdb.modify(m)
128         samdb.set_schema_update_now()
129         self.outf.write("modified %s" % attr_dn)
130
131 class cmd_schema_attribute_show(Command):
132     """Show details about an attribute from the schema.
133
134     Schema attribute definitions define and control the behaviour of directory
135     attributes on objects. This displays the details of a single attribute.
136     """
137     synopsis = "%prog attribute [options]"
138
139     takes_optiongroups = {
140         "sambaopts": options.SambaOptions,
141         "versionopts": options.VersionOptions,
142         "credopts": options.CredentialsOptions,
143     }
144
145     takes_options = [
146         Option("-H", "--URL", help="LDB URL for database or target server",
147                type=str, metavar="URL", dest="H"),
148     ]
149
150     takes_args = ["attribute"]
151
152     def run(self, attribute, H=None, credopts=None, sambaopts=None, versionopts=None):
153         lp = sambaopts.get_loadparm()
154         creds = credopts.get_credentials(lp)
155
156         samdb = SamDB(url=H, session_info=system_session(),
157             credentials=creds, lp=lp)
158
159         schema_dn = samdb.schema_dn()
160
161         filt = '(&(objectClass=attributeSchema)(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(attribute)
162
163         res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
164                            expression=filt)
165
166         if len(res) == 0:
167             raise CommandError('No schema objects matched "%s"' % attribute)
168         if len(res) > 1:
169             raise CommandError('Multiple schema objects matched "%s": this is a serious issue you should report!' % attribute)
170
171         # Get the content of searchFlags (if any) and manipulate them to
172         # show our friendly names.
173
174         # WARNING: If you are reading this in the future trying to change an
175         # ldb message dynamically, and wondering why you get an operations
176         # error, it's related to talloc references.
177         #
178         # When you create *any* python reference, IE:
179         # flags = res[0]['attr']
180         # this creates a talloc_reference that may live forever due to pythons
181         # memory management model. However, when you create this reference it
182         # blocks talloc_realloc from functions in msg.add(element).
183         #
184         # As a result, you MUST avoid ALL new variable references UNTIL you have
185         # modified the message as required, even if it makes your code more
186         # verbose.
187
188         if 'searchFlags' in res[0].keys():
189             flags_i = None
190             try:
191                 # See above
192                 flags_i = int(str(res[0]['searchFlags']))
193             except ValueError:
194                 raise CommandError('Invalid schemaFlags value "%s": this is a serious issue you should report!' % res[0]['searchFlags'])
195             # Work out what keys we have.
196             out = []
197             for flag in bitFields['searchflags'].keys():
198                 if flags_i & (1 << (31 - bitFields['searchflags'][flag])) != 0:
199                     out.append(flag)
200             if len(out) > 0:
201                 res[0].add(ldb.MessageElement(out, ldb.FLAG_MOD_ADD, 'searchFlagsDecoded'))
202
203         user_ldif = samdb.write_ldif(res[0], ldb.CHANGETYPE_NONE)
204         self.outf.write(user_ldif)
205
206 class cmd_schema_attribute_show_oc(Command):
207     """Show what objectclasses MAY or MUST contain an attribute.
208
209     This is useful to determine "if I need uid, what objectclasses could be
210     applied to achieve this."
211     """
212     synopsis = "%prog attribute [options]"
213
214     takes_optiongroups = {
215         "sambaopts": options.SambaOptions,
216         "versionopts": options.VersionOptions,
217         "credopts": options.CredentialsOptions,
218     }
219
220     takes_options = [
221         Option("-H", "--URL", help="LDB URL for database or target server",
222                type=str, metavar="URL", dest="H"),
223     ]
224
225     takes_args = ["attribute"]
226
227     def run(self, attribute, H=None, credopts=None, sambaopts=None, versionopts=None):
228         lp = sambaopts.get_loadparm()
229         creds = credopts.get_credentials(lp)
230
231         samdb = SamDB(url=H, session_info=system_session(),
232             credentials=creds, lp=lp)
233
234         schema_dn = samdb.schema_dn()
235
236         may_filt = '(&(objectClass=classSchema)' \
237         '(|(mayContain={0})(systemMayContain={0})))'.format(attribute)
238         must_filt = '(&(objectClass=classSchema)' \
239         '(|(mustContain={0})(systemMustContain={0})))'.format(attribute)
240
241         may_res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
242                            expression=may_filt, attrs=['cn'])
243         must_res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
244                            expression=must_filt, attrs=['cn'])
245
246         self.outf.write('--- MAY contain ---\n')
247         for msg in may_res:
248             self.outf.write('%s\n' % msg['cn'][0])
249
250         self.outf.write('--- MUST contain ---\n')
251         for msg in must_res:
252             self.outf.write('%s\n' % msg['cn'][0])
253
254
255 class cmd_schema_objectclass_show(Command):
256     """Show details about an objectClass from the schema.
257
258     Schema objectClass definitions define and control the behaviour of directory
259     objects including what attributes they may contain. This displays the
260     details of an objectClass.
261     """
262     synopsis = "%prog objectclass [options]"
263
264     takes_optiongroups = {
265         "sambaopts": options.SambaOptions,
266         "versionopts": options.VersionOptions,
267         "credopts": options.CredentialsOptions,
268     }
269
270     takes_options = [
271         Option("-H", "--URL", help="LDB URL for database or target server",
272                type=str, metavar="URL", dest="H"),
273     ]
274
275     takes_args = ["objectclass"]
276
277     def run(self, objectclass, H=None, credopts=None, sambaopts=None, versionopts=None):
278         lp = sambaopts.get_loadparm()
279         creds = credopts.get_credentials(lp)
280
281         samdb = SamDB(url=H, session_info=system_session(),
282             credentials=creds, lp=lp)
283
284         schema_dn = samdb.schema_dn()
285
286         filt = '(&(objectClass=classSchema)' \
287                '(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(objectclass)
288
289         res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
290                            expression=filt)
291
292         for msg in res:
293             user_ldif = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE)
294             self.outf.write(user_ldif)
295
296 class cmd_schema_attribute(SuperCommand):
297     """Query and manage attributes in the schema partition."""
298     subcommands = {}
299     subcommands["modify"] = cmd_schema_attribute_modify()
300     subcommands["show"] = cmd_schema_attribute_show()
301     subcommands["show_oc"] = cmd_schema_attribute_show_oc()
302
303 class cmd_schema_objectclass(SuperCommand):
304     """Query and manage objectclasses in the schema partition."""
305     subcommands = {}
306     subcommands["show"] = cmd_schema_objectclass_show()
307
308 class cmd_schema(SuperCommand):
309     """Schema querying and management."""
310
311     subcommands = {}
312     subcommands["attribute"] = cmd_schema_attribute()
313     subcommands["objectclass"] = cmd_schema_objectclass()