Return per-entry controls in ldb_module_send_entry()
[abartlet/samba.git/.git] / source4 / dsdb / samdb / ldb_modules / kludge_acl.c
1 /* 
2    ldb database library
3
4    Copyright (C) Andrew Bartlett 2005
5    Copyright (C) Simo Sorce 2006-2008
6
7     This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22  *  Name: ldb
23  *
24  *  Component: ldb kludge ACL module
25  *
26  *  Description: Simple module to enforce a simple form of access
27  *               control, sufficient for securing a default Samba4 
28  *               installation.
29  *
30  *  Author: Andrew Bartlett
31  */
32
33 #include "includes.h"
34 #include "ldb/include/ldb.h"
35 #include "ldb/include/ldb_errors.h"
36 #include "ldb/include/ldb_private.h"
37 #include "auth/auth.h"
38 #include "libcli/security/security.h"
39 #include "dsdb/samdb/samdb.h"
40
41 /* Kludge ACL rules:
42  *
43  * - System can read passwords
44  * - Administrators can write anything
45  * - Users can read anything that is not a password
46  *
47  */
48
49 struct kludge_private_data {
50         const char **password_attrs;
51 };
52
53 static enum security_user_level what_is_user(struct ldb_module *module) 
54 {
55         struct auth_session_info *session_info
56                 = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
57         return security_session_user_level(session_info);
58 }
59
60 static const char *user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module) 
61 {
62         struct auth_session_info *session_info
63                 = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
64         if (!session_info) {
65                 return "UNKNOWN (NULL)";
66         }
67         
68         return talloc_asprintf(mem_ctx, "%s\\%s",
69                                session_info->server_info->domain_name,
70                                session_info->server_info->account_name);
71 }
72
73 /* search */
74 struct kludge_acl_context {
75
76         struct ldb_module *module;
77         struct ldb_request *req;
78
79         enum security_user_level user_type;
80         bool allowedAttributes;
81         bool allowedAttributesEffective;
82         bool allowedChildClasses;
83         bool allowedChildClassesEffective;
84         const char * const *attrs;
85 };
86
87 /* read all objectClasses */
88
89 static int kludge_acl_allowedAttributes(struct ldb_context *ldb, struct ldb_message *msg,
90                                         const char *attrName) 
91 {
92         struct ldb_message_element *oc_el;
93         struct ldb_message_element *allowedAttributes;
94         const struct dsdb_schema *schema = dsdb_get_schema(ldb);
95         TALLOC_CTX *mem_ctx;
96         char **objectclass_list, **attr_list;
97         int i, ret;
98
99         /* If we don't have a schema yet, we can't do anything... */
100         if (schema == NULL) {
101                 return LDB_SUCCESS;
102         }
103
104         /* Must remove any existing attribute, or else confusion reins */
105         ldb_msg_remove_attr(msg, attrName);
106         ret = ldb_msg_add_empty(msg, attrName, 0, &allowedAttributes);
107         if (ret != LDB_SUCCESS) {
108                 return ret;
109         }
110         
111         mem_ctx = talloc_new(msg);
112         if (!mem_ctx) {
113                 ldb_oom(ldb);
114                 return LDB_ERR_OPERATIONS_ERROR;
115         }
116
117         /* To ensure that oc_el is valid, we must look for it after 
118            we alter the element array in ldb_msg_add_empty() */
119         oc_el = ldb_msg_find_element(msg, "objectClass");
120         
121         objectclass_list = talloc_array(mem_ctx, char *, oc_el->num_values + 1);
122         if (!objectclass_list) {
123                 ldb_oom(ldb);
124                 talloc_free(mem_ctx);
125                 return LDB_ERR_OPERATIONS_ERROR;
126         }
127
128         for (i=0; oc_el && i < oc_el->num_values; i++) {
129                 objectclass_list[i] = (char *)oc_el->values[i].data;
130         }
131         objectclass_list[i] = NULL;
132
133         attr_list = dsdb_full_attribute_list(mem_ctx, schema, (const char **)objectclass_list, DSDB_SCHEMA_ALL);
134         if (!attr_list) {
135                 ldb_asprintf_errstring(ldb, "kludge_acl: Failed to get list of attributes create %s attribute", attrName);
136                 talloc_free(mem_ctx);
137                 return LDB_ERR_OPERATIONS_ERROR;
138         }
139
140         for (i=0; attr_list && attr_list[i]; i++) {
141                 ldb_msg_add_string(msg, attrName, attr_list[i]);
142         }
143         talloc_free(mem_ctx);
144         return LDB_SUCCESS;
145
146 }
147 /* read all objectClasses */
148
149 static int kludge_acl_childClasses(struct ldb_context *ldb, struct ldb_message *msg,
150                                    const char *attrName) 
151 {
152         struct ldb_message_element *oc_el;
153         struct ldb_message_element *allowedClasses;
154         const struct dsdb_schema *schema = dsdb_get_schema(ldb);
155         const struct dsdb_class *class;
156         int i, j, ret;
157
158         /* If we don't have a schema yet, we can't do anything... */
159         if (schema == NULL) {
160                 return LDB_SUCCESS;
161         }
162
163         /* Must remove any existing attribute, or else confusion reins */
164         ldb_msg_remove_attr(msg, attrName);
165         ret = ldb_msg_add_empty(msg, attrName, 0, &allowedClasses);
166         if (ret != LDB_SUCCESS) {
167                 return ret;
168         }
169         
170         /* To ensure that oc_el is valid, we must look for it after 
171            we alter the element array in ldb_msg_add_empty() */
172         oc_el = ldb_msg_find_element(msg, "objectClass");
173
174         for (i=0; oc_el && i < oc_el->num_values; i++) {
175                 class = dsdb_class_by_lDAPDisplayName(schema, (const char *)oc_el->values[i].data);
176                 if (!class) {
177                         /* We don't know this class?  what is going on? */
178                         continue;
179                 }
180
181                 for (j=0; class->possibleInferiors && class->possibleInferiors[j]; j++) {
182                         ldb_msg_add_string(msg, attrName, class->possibleInferiors[j]);
183                 }
184         }
185                 
186         if (allowedClasses->num_values > 1) {
187                 qsort(allowedClasses->values, 
188                       allowedClasses->num_values, 
189                       sizeof(*allowedClasses->values),
190                       (comparison_fn_t)data_blob_cmp);
191         
192                 for (i=1 ; i < allowedClasses->num_values; i++) {
193
194                         struct ldb_val *val1 = &allowedClasses->values[i-1];
195                         struct ldb_val *val2 = &allowedClasses->values[i];
196                         if (data_blob_cmp(val1, val2) == 0) {
197                                 memmove(val1, val2, (allowedClasses->num_values - i) * sizeof( struct ldb_val)); 
198                                 allowedClasses->num_values--;
199                                 i--;
200                         }
201                 }
202         }
203
204         return LDB_SUCCESS;
205
206 }
207
208 /* find all attributes allowed by all these objectClasses */
209
210 static int kludge_acl_callback(struct ldb_request *req, struct ldb_reply *ares)
211 {
212         struct kludge_acl_context *ac;
213         struct kludge_private_data *data;
214         int i, ret;
215
216         ac = talloc_get_type(req->context, struct kludge_acl_context);
217         data = talloc_get_type(ac->module->private_data, struct kludge_private_data);
218
219         if (!ares) {
220                 return ldb_module_done(ac->req, NULL, NULL,
221                                         LDB_ERR_OPERATIONS_ERROR);
222         }
223         if (ares->error != LDB_SUCCESS) {
224                 return ldb_module_done(ac->req, ares->controls,
225                                         ares->response, ares->error);
226         }
227
228         switch (ares->type) {
229         case LDB_REPLY_ENTRY:
230                 if (ac->allowedAttributes) {
231                         ret = kludge_acl_allowedAttributes(ac->module->ldb,
232                                                    ares->message,
233                                                    "allowedAttributes");
234                         if (ret != LDB_SUCCESS) {
235                                 return ldb_module_done(ac->req, NULL, NULL, ret);
236                         }
237                 }
238                 if (ac->allowedChildClasses) {
239                         ret = kludge_acl_childClasses(ac->module->ldb,
240                                                 ares->message,
241                                                 "allowedChildClasses");
242                         if (ret != LDB_SUCCESS) {
243                                 return ldb_module_done(ac->req, NULL, NULL, ret);
244                         }
245                 }
246
247                 if (data && data->password_attrs) /* if we are not initialized just get through */
248                 {
249                         switch (ac->user_type) {
250                         case SECURITY_SYSTEM:
251                                 if (ac->allowedAttributesEffective) {
252                                         ret = kludge_acl_allowedAttributes(ac->module->ldb, ares->message,
253                                                                         "allowedAttributesEffective");
254                                         if (ret != LDB_SUCCESS) {
255                                                 return ldb_module_done(ac->req, NULL, NULL, ret);
256                                         }
257                                 }
258                                 if (ac->allowedChildClassesEffective) {
259                                         ret = kludge_acl_childClasses(ac->module->ldb, ares->message,
260                                                                         "allowedChildClassesEffective");
261                                         if (ret != LDB_SUCCESS) {
262                                                 return ldb_module_done(ac->req, NULL, NULL, ret);
263                                         }
264                                 }
265                                 break;
266
267                         case SECURITY_ADMINISTRATOR:
268                                 if (ac->allowedAttributesEffective) {
269                                         ret = kludge_acl_allowedAttributes(ac->module->ldb, ares->message,
270                                                                         "allowedAttributesEffective");
271                                         if (ret != LDB_SUCCESS) {
272                                                 return ldb_module_done(ac->req, NULL, NULL, ret);
273                                         }
274                                 }
275                                 if (ac->allowedChildClassesEffective) {
276                                         ret = kludge_acl_childClasses(ac->module->ldb, ares->message,
277                                                                         "allowedChildClassesEffective");
278                                         if (ret != LDB_SUCCESS) {
279                                                 return ldb_module_done(ac->req, NULL, NULL, ret);
280                                         }
281                                 }
282                                 /* fall through */
283                         default:
284                                 /* remove password attributes */
285                                 for (i = 0; data->password_attrs[i]; i++) {
286                                         ldb_msg_remove_attr(ares->message, data->password_attrs[i]);
287                                 }
288                         }
289                 }
290
291                 if (ac->allowedAttributes ||
292                     ac->allowedAttributesEffective ||
293                     ac->allowedChildClasses ||
294                     ac->allowedChildClassesEffective) {
295
296                         if (!ldb_attr_in_list(ac->attrs, "objectClass") &&
297                             !ldb_attr_in_list(ac->attrs, "*")) {
298
299                                 ldb_msg_remove_attr(ares->message,
300                                                     "objectClass");
301                         }
302                 }
303
304                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
305
306         case LDB_REPLY_REFERRAL:
307                 return ldb_module_send_referral(ac->req, ares->referral);
308
309         case LDB_REPLY_DONE:
310                 return ldb_module_done(ac->req, ares->controls,
311                                         ares->response, LDB_SUCCESS);
312
313         }
314         return LDB_SUCCESS;
315 }
316
317 static int kludge_acl_search(struct ldb_module *module, struct ldb_request *req)
318 {
319         struct kludge_acl_context *ac;
320         struct ldb_request *down_req;
321         struct kludge_private_data *data;
322         const char * const *attrs;
323         int ret, i;
324         struct ldb_control *sd_control;
325         struct ldb_control **sd_saved_controls;
326
327         ac = talloc(req, struct kludge_acl_context);
328         if (ac == NULL) {
329                 ldb_oom(module->ldb);
330                 return LDB_ERR_OPERATIONS_ERROR;
331         }
332
333         data = talloc_get_type(module->private_data, struct kludge_private_data);
334
335         ac->module = module;
336         ac->req = req;
337         ac->user_type = what_is_user(module);
338         ac->attrs = req->op.search.attrs;
339
340         ac->allowedAttributes = ldb_attr_in_list(req->op.search.attrs, "allowedAttributes");
341
342         ac->allowedAttributesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedAttributesEffective");
343
344         ac->allowedChildClasses = ldb_attr_in_list(req->op.search.attrs, "allowedChildClasses");
345
346         ac->allowedChildClassesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedChildClassesEffective");
347
348         if (ac->allowedAttributes || ac->allowedAttributesEffective || ac->allowedChildClasses || ac->allowedChildClassesEffective) {
349                 attrs = ldb_attr_list_copy_add(ac, req->op.search.attrs, "objectClass");
350         } else {
351                 attrs = req->op.search.attrs;
352         }
353
354         /* replace any attributes in the parse tree that are private,
355            so we don't allow a search for 'userPassword=penguin',
356            just as we would not allow that attribute to be returned */
357         switch (ac->user_type) {
358         case SECURITY_SYSTEM:
359                 break;
360         default:
361         /* FIXME: We should copy the tree and keep the original unmodified. */
362                 /* remove password attributes */
363
364                 if (!data || !data->password_attrs) {
365                         break;
366                 }
367                 for (i = 0; data->password_attrs[i]; i++) {
368                         ldb_parse_tree_attr_replace(req->op.search.tree,
369                                                     data->password_attrs[i],
370                                                     "kludgeACLredactedattribute");
371                 }
372         }
373
374         ret = ldb_build_search_req_ex(&down_req,
375                                         module->ldb, ac,
376                                         req->op.search.base,
377                                         req->op.search.scope,
378                                         req->op.search.tree,
379                                         attrs,
380                                         req->controls,
381                                         ac, kludge_acl_callback,
382                                         req);
383         if (ret != LDB_SUCCESS) {
384                 return LDB_ERR_OPERATIONS_ERROR;
385         }
386
387         /* check if there's an SD_FLAGS control */
388         sd_control = ldb_request_get_control(down_req, LDB_CONTROL_SD_FLAGS_OID);
389         if (sd_control) {
390                 /* save it locally and remove it from the list */
391                 /* we do not need to replace them later as we
392                  * are keeping the original req intact */
393                 if (!save_controls(sd_control, down_req, &sd_saved_controls)) {
394                         return LDB_ERR_OPERATIONS_ERROR;
395                 }
396         }
397
398         /* perform the search */
399         return ldb_next_request(module, down_req);
400 }
401
402 /* ANY change type */
403 static int kludge_acl_change(struct ldb_module *module, struct ldb_request *req)
404 {
405         enum security_user_level user_type = what_is_user(module);
406         switch (user_type) {
407         case SECURITY_SYSTEM:
408         case SECURITY_ADMINISTRATOR:
409                 return ldb_next_request(module, req);
410         default:
411                 ldb_asprintf_errstring(module->ldb,
412                                        "kludge_acl_change: "
413                                        "attempted database modify not permitted. "
414                                        "User %s is not SYSTEM or an administrator",
415                                        user_name(req, module));
416                 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
417         }
418 }
419
420 static int kludge_acl_extended(struct ldb_module *module, struct ldb_request *req)
421 {
422         enum security_user_level user_type;
423
424         /* allow everybody to read the sequence number */
425         if (strcmp(req->op.extended.oid,
426                    LDB_EXTENDED_SEQUENCE_NUMBER) == 0) {
427                 return ldb_next_request(module, req);
428         }
429
430         user_type = what_is_user(module);
431
432         switch (user_type) {
433         case SECURITY_SYSTEM:
434         case SECURITY_ADMINISTRATOR:
435                 return ldb_next_request(module, req);
436         default:
437                 ldb_asprintf_errstring(module->ldb,
438                                        "kludge_acl_change: "
439                                        "attempted database modify not permitted. "
440                                        "User %s is not SYSTEM or an administrator",
441                                        user_name(req, module));
442                 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
443         }
444 }
445
446 static int kludge_acl_init(struct ldb_module *module)
447 {
448         int ret, i;
449         TALLOC_CTX *mem_ctx = talloc_new(module);
450         static const char *attrs[] = { "passwordAttribute", NULL };
451         struct ldb_result *res;
452         struct ldb_message *msg;
453         struct ldb_message_element *password_attributes;
454
455         struct kludge_private_data *data;
456
457         data = talloc(module, struct kludge_private_data);
458         if (data == NULL) {
459                 ldb_oom(module->ldb);
460                 return LDB_ERR_OPERATIONS_ERROR;
461         }
462
463         data->password_attrs = NULL;
464         module->private_data = data;
465
466         if (!mem_ctx) {
467                 ldb_oom(module->ldb);
468                 return LDB_ERR_OPERATIONS_ERROR;
469         }
470
471         ret = ldb_search(module->ldb, mem_ctx, &res,
472                          ldb_dn_new(mem_ctx, module->ldb, "@KLUDGEACL"),
473                          LDB_SCOPE_BASE, attrs, NULL);
474         if (ret != LDB_SUCCESS) {
475                 goto done;
476         }
477         if (res->count == 0) {
478                 goto done;
479         }
480
481         if (res->count > 1) {
482                 talloc_free(mem_ctx);
483                 return LDB_ERR_CONSTRAINT_VIOLATION;
484         }
485
486         msg = res->msgs[0];
487
488         password_attributes = ldb_msg_find_element(msg, "passwordAttribute");
489         if (!password_attributes) {
490                 goto done;
491         }
492         data->password_attrs = talloc_array(data, const char *, password_attributes->num_values + 1);
493         if (!data->password_attrs) {
494                 talloc_free(mem_ctx);
495                 ldb_oom(module->ldb);
496                 return LDB_ERR_OPERATIONS_ERROR;
497         }
498         for (i=0; i < password_attributes->num_values; i++) {
499                 data->password_attrs[i] = (const char *)password_attributes->values[i].data;    
500                 talloc_steal(data->password_attrs, password_attributes->values[i].data);
501         }
502         data->password_attrs[i] = NULL;
503
504         ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID);
505         if (ret != LDB_SUCCESS) {
506                 ldb_debug(module->ldb, LDB_DEBUG_ERROR,
507                         "partition: Unable to register control with rootdse!\n");
508                 return LDB_ERR_OPERATIONS_ERROR;
509         }
510
511 done:
512         talloc_free(mem_ctx);
513         return ldb_next_init(module);
514 }
515
516 _PUBLIC_ const struct ldb_module_ops ldb_kludge_acl_module_ops = {
517         .name              = "kludge_acl",
518         .search            = kludge_acl_search,
519         .add               = kludge_acl_change,
520         .modify            = kludge_acl_change,
521         .del               = kludge_acl_change,
522         .rename            = kludge_acl_change,
523         .extended          = kludge_acl_extended,
524         .init_context      = kludge_acl_init
525 };