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