Introduce system MIT krb5 build with --with-system-mitkrb5 option.
[sfrench/samba-autobuild/.git] / source4 / dsdb / samdb / ldb_modules / objectclass_attrs.c
1 /*
2    ldb database library
3
4    Copyright (C) Simo Sorce  2006-2008
5    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
6    Copyright (C) Stefan Metzmacher 2009
7    Copyright (C) Matthias Dieter Wallnöfer 2010
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU Lesser General Public
20    License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /*
24  *  Name: ldb
25  *
26  *  Component: objectclass attribute checking module
27  *
28  *  Description: this checks the attributes on a directory entry (if they're
29  *    allowed, if the syntax is correct, if mandatory ones are missing,
30  *    denies the deletion of mandatory ones...). The module contains portions
31  *    of the "objectclass" and the "validate_update" LDB module.
32  *
33  *  Author: Matthias Dieter Wallnöfer
34  */
35
36 #include "includes.h"
37 #include "ldb_module.h"
38 #include "dsdb/samdb/samdb.h"
39
40 struct oc_context {
41
42         struct ldb_module *module;
43         struct ldb_request *req;
44         const struct dsdb_schema *schema;
45
46         struct ldb_message *msg;
47
48         struct ldb_reply *search_res;
49         struct ldb_reply *mod_ares;
50 };
51
52 static struct oc_context *oc_init_context(struct ldb_module *module,
53                                           struct ldb_request *req)
54 {
55         struct ldb_context *ldb;
56         struct oc_context *ac;
57
58         ldb = ldb_module_get_ctx(module);
59
60         ac = talloc_zero(req, struct oc_context);
61         if (ac == NULL) {
62                 ldb_oom(ldb);
63                 return NULL;
64         }
65
66         ac->module = module;
67         ac->req = req;
68         ac->schema = dsdb_get_schema(ldb, ac);
69
70         return ac;
71 }
72
73 static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares);
74
75 /*
76  * Checks the correctness of the "dSHeuristics" attribute as described in both
77  * MS-ADTS 7.1.1.2.4.1.2 dSHeuristics and MS-ADTS 3.1.1.5.3.2 Constraints
78  */
79 static int oc_validate_dsheuristics(struct ldb_message_element *el)
80 {
81         if (el->num_values > 0) {
82                 if ((el->values[0].length >= DS_HR_NINETIETH_CHAR) &&
83                     (el->values[0].data[DS_HR_NINETIETH_CHAR-1] != '9')) {
84                         return LDB_ERR_CONSTRAINT_VIOLATION;
85                 }
86                 if ((el->values[0].length >= DS_HR_EIGHTIETH_CHAR) &&
87                     (el->values[0].data[DS_HR_EIGHTIETH_CHAR-1] != '8')) {
88                         return LDB_ERR_CONSTRAINT_VIOLATION;
89                 }
90                 if ((el->values[0].length >= DS_HR_SEVENTIETH_CHAR) &&
91                     (el->values[0].data[DS_HR_SEVENTIETH_CHAR-1] != '7')) {
92                         return LDB_ERR_CONSTRAINT_VIOLATION;
93                 }
94                 if ((el->values[0].length >= DS_HR_SIXTIETH_CHAR) &&
95                     (el->values[0].data[DS_HR_SIXTIETH_CHAR-1] != '6')) {
96                         return LDB_ERR_CONSTRAINT_VIOLATION;
97                 }
98                 if ((el->values[0].length >= DS_HR_FIFTIETH_CHAR) &&
99                     (el->values[0].data[DS_HR_FIFTIETH_CHAR-1] != '5')) {
100                         return LDB_ERR_CONSTRAINT_VIOLATION;
101                 }
102                 if ((el->values[0].length >= DS_HR_FOURTIETH_CHAR) &&
103                     (el->values[0].data[DS_HR_FOURTIETH_CHAR-1] != '4')) {
104                         return LDB_ERR_CONSTRAINT_VIOLATION;
105                 }
106                 if ((el->values[0].length >= DS_HR_THIRTIETH_CHAR) &&
107                     (el->values[0].data[DS_HR_THIRTIETH_CHAR-1] != '3')) {
108                         return LDB_ERR_CONSTRAINT_VIOLATION;
109                 }
110                 if ((el->values[0].length >= DS_HR_TWENTIETH_CHAR) &&
111                     (el->values[0].data[DS_HR_TWENTIETH_CHAR-1] != '2')) {
112                         return LDB_ERR_CONSTRAINT_VIOLATION;
113                 }
114                 if ((el->values[0].length >= DS_HR_TENTH_CHAR) &&
115                     (el->values[0].data[DS_HR_TENTH_CHAR-1] != '1')) {
116                         return LDB_ERR_CONSTRAINT_VIOLATION;
117                 }
118         }
119
120         return LDB_SUCCESS;
121 }
122
123 /*
124   auto normalise values on input
125  */
126 static int oc_auto_normalise(struct ldb_context *ldb, const struct dsdb_attribute *attr,
127                              struct ldb_message *msg, struct ldb_message_element *el)
128 {
129         int i;
130         bool values_copied = false;
131
132         for (i=0; i<el->num_values; i++) {
133                 struct ldb_val v;
134                 int ret;
135                 ret = attr->ldb_schema_attribute->syntax->canonicalise_fn(ldb, el->values, &el->values[i], &v);
136                 if (ret != LDB_SUCCESS) {
137                         return ret;
138                 }
139                 if (data_blob_cmp(&v, &el->values[i]) == 0) {
140                         /* no need to replace it */
141                         talloc_free(v.data);
142                         continue;
143                 }
144
145                 /* we need to copy the values array on the first change */
146                 if (!values_copied) {
147                         struct ldb_val *v2;
148                         v2 = talloc_array(msg->elements, struct ldb_val, el->num_values);
149                         if (v2 == NULL) {
150                                 return ldb_oom(ldb);
151                         }
152                         memcpy(v2, el->values, sizeof(struct ldb_val) * el->num_values);
153                         el->values = v2;
154                         values_copied = true;
155                 }
156
157                 el->values[i] = v;
158         }
159         return LDB_SUCCESS;
160 }
161
162 static int attr_handler(struct oc_context *ac)
163 {
164         struct ldb_context *ldb;
165         struct ldb_message *msg;
166         struct ldb_request *child_req;
167         const struct dsdb_attribute *attr;
168         unsigned int i;
169         int ret;
170         WERROR werr;
171         struct dsdb_syntax_ctx syntax_ctx;
172
173         ldb = ldb_module_get_ctx(ac->module);
174
175         if (ac->req->operation == LDB_ADD) {
176                 msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
177         } else {
178                 msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message);
179         }
180         if (msg == NULL) {
181                 return ldb_oom(ldb);
182         }
183         ac->msg = msg;
184
185         /* initialize syntax checking context */
186         dsdb_syntax_ctx_init(&syntax_ctx, ldb, ac->schema);
187
188         /* Check if attributes exist in the schema, if the values match,
189          * if they're not operational and fix the names to the match the schema
190          * case */
191         for (i = 0; i < msg->num_elements; i++) {
192                 attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
193                                                          msg->elements[i].name);
194                 if (attr == NULL) {
195                         if (ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK) &&
196                             ac->req->operation != LDB_ADD) {
197                                 /* we allow this for dbcheck to fix
198                                    broken attributes */
199                                 goto no_attribute;
200                         }
201                         ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' was not found in the schema!",
202                                                msg->elements[i].name,
203                                                ldb_dn_get_linearized(msg->dn));
204                         return LDB_ERR_NO_SUCH_ATTRIBUTE;
205                 }
206
207                 if ((attr->linkID & 1) == 1 &&
208                     !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
209                     !ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
210                         /* Odd is for the target.  Illegal to modify */
211                         ldb_asprintf_errstring(ldb, 
212                                                "objectclass_attrs: attribute '%s' on entry '%s' must not be modified directly, it is a linked attribute", 
213                                                msg->elements[i].name,
214                                                ldb_dn_get_linearized(msg->dn));
215                         return LDB_ERR_UNWILLING_TO_PERFORM;
216                 }
217                 
218                 if (!(msg->elements[i].flags & LDB_FLAG_INTERNAL_DISABLE_VALIDATION)) {
219                         werr = attr->syntax->validate_ldb(&syntax_ctx, attr,
220                                                           &msg->elements[i]);
221                         if (!W_ERROR_IS_OK(werr) &&
222                             !ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
223                                 ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' contains at least one invalid value!",
224                                                        msg->elements[i].name,
225                                                        ldb_dn_get_linearized(msg->dn));
226                                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
227                         }
228                 }
229
230                 if ((attr->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED) != 0) {
231                         ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' is constructed!",
232                                                msg->elements[i].name,
233                                                ldb_dn_get_linearized(msg->dn));
234                         if (ac->req->operation == LDB_ADD) {
235                                 return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
236                         } else {
237                                 return LDB_ERR_CONSTRAINT_VIOLATION;
238                         }
239                 }
240
241                 /* "dSHeuristics" syntax check */
242                 if (ldb_attr_cmp(attr->lDAPDisplayName, "dSHeuristics") == 0) {
243                         ret = oc_validate_dsheuristics(&(msg->elements[i]));
244                         if (ret != LDB_SUCCESS) {
245                                 return ret;
246                         }
247                 }
248
249                 /* auto normalise some attribute values */
250                 if (attr->syntax->auto_normalise) {
251                         ret = oc_auto_normalise(ldb, attr, msg, &msg->elements[i]);
252                         if (ret != LDB_SUCCESS) {
253                                 return ret;
254                         }
255                 }
256
257                 /* Substitute the attribute name to match in case */
258                 msg->elements[i].name = attr->lDAPDisplayName;
259         }
260
261 no_attribute:
262         if (ac->req->operation == LDB_ADD) {
263                 ret = ldb_build_add_req(&child_req, ldb, ac,
264                                         msg, ac->req->controls,
265                                         ac, oc_op_callback, ac->req);
266                 LDB_REQ_SET_LOCATION(child_req);
267         } else {
268                 ret = ldb_build_mod_req(&child_req, ldb, ac,
269                                         msg, ac->req->controls,
270                                         ac, oc_op_callback, ac->req);
271                 LDB_REQ_SET_LOCATION(child_req);
272         }
273         if (ret != LDB_SUCCESS) {
274                 return ret;
275         }
276
277         return ldb_next_request(ac->module, child_req);
278 }
279
280 /*
281   these are attributes which are left over from old ways of doing
282   things in ldb, and are harmless
283  */
284 static const char *harmless_attrs[] = { "parentGUID", NULL };
285
286 static int attr_handler2(struct oc_context *ac)
287 {
288         struct ldb_context *ldb;
289         struct ldb_message_element *oc_element;
290         struct ldb_message *msg;
291         const char **must_contain, **may_contain, **found_must_contain;
292         /* There exists a hardcoded delete-protected attributes list in AD */
293         const char *del_prot_attributes[] = { "nTSecurityDescriptor",
294                 "objectSid", "sAMAccountType", "sAMAccountName", "groupType",
295                 "primaryGroupID", "userAccountControl", "accountExpires",
296                 "badPasswordTime", "badPwdCount", "codePage", "countryCode",
297                 "lastLogoff", "lastLogon", "logonCount", "pwdLastSet", NULL },
298                 **l;
299         const struct dsdb_attribute *attr;
300         unsigned int i;
301         bool found;
302         bool isSchemaAttr = false;
303
304         ldb = ldb_module_get_ctx(ac->module);
305
306         if (ac->search_res == NULL) {
307                 return ldb_operr(ldb);
308         }
309
310         /* We rely here on the preceeding "objectclass" LDB module which did
311          * already fix up the objectclass list (inheritance, order...). */
312         oc_element = ldb_msg_find_element(ac->search_res->message,
313                                           "objectClass");
314         if (oc_element == NULL) {
315                 return ldb_operr(ldb);
316         }
317
318         /* LSA-specific object classes are not allowed to be created over LDAP,
319          * so we need to tell if this connection is internal (trusted) or not
320          * (untrusted).
321          *
322          * Hongwei Sun from Microsoft explains:
323          * The constraint in 3.1.1.5.2.2 MS-ADTS means that LSA objects cannot
324          * be added or modified through the LDAP interface, instead they can
325          * only be handled through LSA Policy API.  This is also explained in
326          * 7.1.6.9.7 MS-ADTS as follows:
327          * "Despite being replicated normally between peer DCs in a domain,
328          * the process of creating or manipulating TDOs is specifically
329          * restricted to the LSA Policy APIs, as detailed in [MS-LSAD] section
330          * 3.1.1.5. Unlike other objects in the DS, TDOs may not be created or
331          *  manipulated by client machines over the LDAPv3 transport."
332          */
333         for (i = 0; i < oc_element->num_values; i++) {
334                 char * attname = (char *)oc_element->values[i].data;
335                 if (ldb_req_is_untrusted(ac->req)) {
336                         if (strcmp(attname, "secret") == 0 ||
337                             strcmp(attname, "trustedDomain") == 0) {
338                                 ldb_asprintf_errstring(ldb, "objectclass_attrs: LSA objectclasses (entry '%s') cannot be created or changed over LDAP!",
339                                                        ldb_dn_get_linearized(ac->search_res->message->dn));
340                                 return LDB_ERR_UNWILLING_TO_PERFORM;
341                         }
342                 }
343                 if (strcmp(attname, "attributeSchema") == 0) {
344                         isSchemaAttr = true;
345                 }
346         }
347
348         must_contain = dsdb_full_attribute_list(ac, ac->schema, oc_element,
349                                                 DSDB_SCHEMA_ALL_MUST);
350         may_contain =  dsdb_full_attribute_list(ac, ac->schema, oc_element,
351                                                 DSDB_SCHEMA_ALL_MAY);
352         found_must_contain = const_str_list(str_list_copy(ac, must_contain));
353         if ((must_contain == NULL) || (may_contain == NULL)
354             || (found_must_contain == NULL)) {
355                 return ldb_operr(ldb);
356         }
357
358         /* Check the delete-protected attributes list */
359         msg = ac->search_res->message;
360         for (l = del_prot_attributes; *l != NULL; l++) {
361                 struct ldb_message_element *el;
362
363                 el = ldb_msg_find_element(ac->msg, *l);
364                 if (el == NULL) {
365                         /*
366                          * It was not specified in the add or modify,
367                          * so it doesn't need to be in the stored record
368                          */
369                         continue;
370                 }
371
372                 found = str_list_check_ci(must_contain, *l);
373                 if (!found) {
374                         found = str_list_check_ci(may_contain, *l);
375                 }
376                 if (found && (ldb_msg_find_element(msg, *l) == NULL)) {
377                         ldb_asprintf_errstring(ldb, "objectclass_attrs: delete protected attribute '%s' on entry '%s' missing!",
378                                                *l,
379                                                ldb_dn_get_linearized(msg->dn));
380                         return LDB_ERR_UNWILLING_TO_PERFORM;
381                 }
382         }
383
384         /* Check if all specified attributes are valid in the given
385          * objectclasses and if they meet additional schema restrictions. */
386         for (i = 0; i < msg->num_elements; i++) {
387                 attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
388                                                          msg->elements[i].name);
389                 if (attr == NULL) {
390                         if (ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
391                                 /* allow this to make it possible for dbcheck
392                                    to remove bad attributes */
393                                 continue;
394                         }
395                         return ldb_operr(ldb);
396                 }
397
398                 /* We can use "str_list_check" with "strcmp" here since the
399                  * attribute information from the schema are always equal
400                  * up-down-cased. */
401                 found = str_list_check(must_contain, attr->lDAPDisplayName);
402                 if (found) {
403                         str_list_remove(found_must_contain, attr->lDAPDisplayName);
404                 } else {
405                         found = str_list_check(may_contain, attr->lDAPDisplayName);
406                 }
407                 if (!found) {
408                         found = str_list_check(harmless_attrs, attr->lDAPDisplayName);
409                 }
410                 if (!found) {
411                         ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' does not exist in the specified objectclasses!",
412                                                msg->elements[i].name,
413                                                ldb_dn_get_linearized(msg->dn));
414                         return LDB_ERR_OBJECT_CLASS_VIOLATION;
415                 }
416         }
417
418         if (found_must_contain[0] != NULL &&
419             ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE") == 0) {
420                 ldb_asprintf_errstring(ldb, "objectclass_attrs: at least one mandatory attribute ('%s') on entry '%s' wasn't specified!",
421                                        found_must_contain[0],
422                                        ldb_dn_get_linearized(msg->dn));
423                 return LDB_ERR_OBJECT_CLASS_VIOLATION;
424         }
425
426         if (isSchemaAttr) {
427                 /* Before really adding an attribute in the database,
428                         * let's check that we can translate it into a dbsd_attribute and
429                         * that we can find a valid syntax object.
430                         * If not it's better to reject this attribute than not be able
431                         * to start samba next time due to schema being unloadable.
432                         */
433                 struct dsdb_attribute *att = talloc(ac, struct dsdb_attribute);
434                 const struct dsdb_syntax *attrSyntax;
435                 WERROR status;
436
437                 status= dsdb_attribute_from_ldb(ac->schema, msg, att);
438                 if (!W_ERROR_IS_OK(status)) {
439                         ldb_set_errstring(ldb,
440                                                 "objectclass: failed to translate the schemaAttribute to a dsdb_attribute");
441                         return LDB_ERR_UNWILLING_TO_PERFORM;
442                 }
443
444                 attrSyntax = dsdb_syntax_for_attribute(att);
445                 if (!attrSyntax) {
446                         ldb_set_errstring(ldb,
447                                                 "objectclass: unknown attribute syntax");
448                         return LDB_ERR_UNWILLING_TO_PERFORM;
449                 }
450         }
451         return ldb_module_done(ac->req, ac->mod_ares->controls,
452                                ac->mod_ares->response, LDB_SUCCESS);
453 }
454
455 static int get_search_callback(struct ldb_request *req, struct ldb_reply *ares)
456 {
457         struct ldb_context *ldb;
458         struct oc_context *ac;
459         int ret;
460
461         ac = talloc_get_type(req->context, struct oc_context);
462         ldb = ldb_module_get_ctx(ac->module);
463
464         if (!ares) {
465                 return ldb_module_done(ac->req, NULL, NULL,
466                                        LDB_ERR_OPERATIONS_ERROR);
467         }
468         if (ares->error != LDB_SUCCESS) {
469                 return ldb_module_done(ac->req, ares->controls,
470                                        ares->response, ares->error);
471         }
472
473         ldb_reset_err_string(ldb);
474
475         switch (ares->type) {
476         case LDB_REPLY_ENTRY:
477                 if (ac->search_res != NULL) {
478                         ldb_set_errstring(ldb, "Too many results");
479                         talloc_free(ares);
480                         return ldb_module_done(ac->req, NULL, NULL,
481                                                LDB_ERR_OPERATIONS_ERROR);
482                 }
483
484                 ac->search_res = talloc_steal(ac, ares);
485                 break;
486
487         case LDB_REPLY_REFERRAL:
488                 /* ignore */
489                 talloc_free(ares);
490                 break;
491
492         case LDB_REPLY_DONE:
493                 talloc_free(ares);
494                 ret = attr_handler2(ac);
495                 if (ret != LDB_SUCCESS) {
496                         return ldb_module_done(ac->req, NULL, NULL, ret);
497                 }
498                 break;
499         }
500
501         return LDB_SUCCESS;
502 }
503
504 static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares)
505 {
506         struct oc_context *ac;
507         struct ldb_context *ldb;
508         struct ldb_request *search_req;
509         struct ldb_dn *base_dn;
510         int ret;
511
512         ac = talloc_get_type(req->context, struct oc_context);
513         ldb = ldb_module_get_ctx(ac->module);
514
515         if (!ares) {
516                 return ldb_module_done(ac->req, NULL, NULL,
517                                        LDB_ERR_OPERATIONS_ERROR);
518         }
519
520         if (ares->type == LDB_REPLY_REFERRAL) {
521                 return ldb_module_send_referral(ac->req, ares->referral);
522         }
523
524         if (ares->error != LDB_SUCCESS) {
525                 return ldb_module_done(ac->req, ares->controls, ares->response,
526                                        ares->error);
527         }
528
529         if (ares->type != LDB_REPLY_DONE) {
530                 talloc_free(ares);
531                 return ldb_module_done(ac->req, NULL, NULL,
532                                        LDB_ERR_OPERATIONS_ERROR);
533         }
534
535         ac->search_res = NULL;
536         ac->mod_ares = talloc_steal(ac, ares);
537
538         /* This looks up all attributes of our just added/modified entry */
539         base_dn = ac->req->operation == LDB_ADD ? ac->req->op.add.message->dn
540                 : ac->req->op.mod.message->dn;
541         ret = ldb_build_search_req(&search_req, ldb, ac, base_dn,
542                                    LDB_SCOPE_BASE, "(objectClass=*)",
543                                    NULL, NULL, ac,
544                                    get_search_callback, ac->req);
545         LDB_REQ_SET_LOCATION(search_req);
546         if (ret != LDB_SUCCESS) {
547                 return ldb_module_done(ac->req, NULL, NULL, ret);
548         }
549
550         ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
551                                       true, NULL);
552         if (ret != LDB_SUCCESS) {
553                 return ldb_module_done(ac->req, NULL, NULL, ret);
554         }
555
556         ret = ldb_next_request(ac->module, search_req);
557         if (ret != LDB_SUCCESS) {
558                 return ldb_module_done(ac->req, NULL, NULL, ret);
559         }
560
561         /* "ldb_module_done" isn't called here since we need to do additional
562          * checks. It is called at the end of "attr_handler2". */
563         return LDB_SUCCESS;
564 }
565
566 static int objectclass_attrs_add(struct ldb_module *module,
567                                  struct ldb_request *req)
568 {
569         struct ldb_context *ldb;
570         struct oc_context *ac;
571
572         ldb = ldb_module_get_ctx(module);
573
574         ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_attrs_add\n");
575
576         /* do not manipulate our control entries */
577         if (ldb_dn_is_special(req->op.add.message->dn)) {
578                 return ldb_next_request(module, req);
579         }
580
581         ac = oc_init_context(module, req);
582         if (ac == NULL) {
583                 return ldb_operr(ldb);
584         }
585
586         /* without schema, there isn't much to do here */
587         if (ac->schema == NULL) {
588                 talloc_free(ac);
589                 return ldb_next_request(module, req);
590         }
591
592         return attr_handler(ac);
593 }
594
595 static int objectclass_attrs_modify(struct ldb_module *module,
596                                     struct ldb_request *req)
597 {
598         struct ldb_context *ldb;
599         struct oc_context *ac;
600
601         ldb = ldb_module_get_ctx(module);
602
603         ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_attrs_modify\n");
604
605         /* do not manipulate our control entries */
606         if (ldb_dn_is_special(req->op.mod.message->dn)) {
607                 return ldb_next_request(module, req);
608         }
609
610         ac = oc_init_context(module, req);
611         if (ac == NULL) {
612                 return ldb_operr(ldb);
613         }
614
615         /* without schema, there isn't much to do here */
616         if (ac->schema == NULL) {
617                 talloc_free(ac);
618                 return ldb_next_request(module, req);
619         }
620
621         return attr_handler(ac);
622 }
623
624 static const struct ldb_module_ops ldb_objectclass_attrs_module_ops = {
625         .name              = "objectclass_attrs",
626         .add               = objectclass_attrs_add,
627         .modify            = objectclass_attrs_modify
628 };
629
630 int ldb_objectclass_attrs_module_init(const char *version)
631 {
632         LDB_MODULE_CHECK_VERSION(version);
633         return ldb_register_module(&ldb_objectclass_attrs_module_ops);
634 }