]> git.sur5r.net Git - openldap/blob - servers/slapd/schema_check.c
Add start of subentry support... still need to sort out
[openldap] / servers / slapd / schema_check.c
1 /* schema_check.c - routines to enforce schema definitions */
2 /* $OpenLDAP$ */
3 /*
4  * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
5  * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
6  */
7
8 #include "portable.h"
9
10 #include <stdio.h>
11
12 #include <ac/ctype.h>
13 #include <ac/string.h>
14 #include <ac/socket.h>
15
16 #include "slap.h"
17 #include "ldap_pvt.h"
18
19 static char * oc_check_required(
20         Entry *e,
21         ObjectClass *oc,
22         struct berval *ocname );
23
24 /*
25  * entry_schema_check - check that entry e conforms to the schema required
26  * by its object class(es).
27  *
28  * returns 0 if so, non-zero otherwise.
29  */
30
31 int
32 entry_schema_check( 
33         Entry *e, Attribute *oldattrs,
34         const char** text,
35         char *textbuf, size_t textlen )
36 {
37         Attribute       *a, *asc, *aoc;
38         ObjectClass *sc, *oc;
39         int     rc, i;
40         struct berval nsc;
41         AttributeDescription *ad_structuralObjectClass
42                 = slap_schema.si_ad_structuralObjectClass;
43         AttributeDescription *ad_objectClass
44                 = slap_schema.si_ad_objectClass;
45         int extensible = 0;
46
47         *text = textbuf;
48
49         /* misc attribute checks */
50         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
51                 /* there should be at least one value */
52                 assert( a->a_vals );
53                 assert( a->a_vals[0].bv_val != NULL ); 
54
55                 if( a->a_desc->ad_type->sat_check ) {
56                         int rc = (a->a_desc->ad_type->sat_check)(
57                                 e, a, text, textbuf, textlen );
58                         if( rc != LDAP_SUCCESS ) {
59                                 return rc;
60                         }
61                 }
62
63                 /* if single value type, check for multiple values */
64                 if( is_at_single_value( a->a_desc->ad_type ) &&
65                         a->a_vals[1].bv_val != NULL )
66                 {
67                         char *type = a->a_desc->ad_cname.bv_val;
68
69                         snprintf( textbuf, textlen, 
70                                 "attribute '%s' cannot have multiple values",
71                                 type );
72
73 #ifdef NEW_LOGGING
74                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
75                                 "entry_schema_check: dn=\"%s\" %s\n",
76                                 e->e_dn, textbuf ));
77 #else
78                         Debug( LDAP_DEBUG_ANY,
79                             "Entry (%s), %s\n",
80                             e->e_dn, textbuf, 0 );
81 #endif
82
83                         return LDAP_CONSTRAINT_VIOLATION;
84                 }
85         }
86
87         /* it's a REALLY bad idea to disable schema checks */
88         if( !global_schemacheck ) return LDAP_SUCCESS;
89
90         /* find the object class attribute - could error out here */
91         asc = attr_find( e->e_attrs, ad_structuralObjectClass );
92         if ( asc == NULL ) {
93 #ifdef NEW_LOGGING
94                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO, "entry_schema_check: "
95                         "No structuralObjectClass for entry (%s)\n",
96                         e->e_dn ));
97 #else
98                 Debug( LDAP_DEBUG_ANY,
99                         "No structuralObjectClass for entry (%s)\n",
100                     e->e_dn, 0, 0 );
101 #endif
102
103                 *text = "no structuralObjectClass operational attribute";
104                 return LDAP_OBJECT_CLASS_VIOLATION;
105         }
106
107         assert( asc->a_vals != NULL );
108         assert( asc->a_vals[0].bv_val != NULL );
109         assert( asc->a_vals[1].bv_val == NULL );
110
111         sc = oc_bvfind( &asc->a_vals[0] );
112         if( sc == NULL ) {
113                 snprintf( textbuf, textlen, 
114                         "unrecognized structuralObjectClass '%s'",
115                         asc->a_vals[0].bv_val );
116
117 #ifdef NEW_LOGGING
118                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
119                         "entry_schema_check: dn (%s), %s\n",
120                         e->e_dn, textbuf ));
121 #else
122                 Debug( LDAP_DEBUG_ANY,
123                         "entry_check_schema(%s): %s\n",
124                         e->e_dn, textbuf, 0 );
125 #endif
126
127                 return LDAP_OBJECT_CLASS_VIOLATION;
128         }
129
130         if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
131                 snprintf( textbuf, textlen, 
132                         "structuralObjectClass '%s' is not STRUCTURAL",
133                         asc->a_vals[0].bv_val );
134
135 #ifdef NEW_LOGGING
136                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
137                         "entry_schema_check: dn (%s), %s\n",
138                         e->e_dn, textbuf ));
139 #else
140                 Debug( LDAP_DEBUG_ANY,
141                         "entry_check_schema(%s): %s\n",
142                         e->e_dn, textbuf, 0 );
143 #endif
144
145                 return LDAP_OBJECT_CLASS_VIOLATION;
146         }
147
148         /* find the object class attribute */
149         aoc = attr_find( e->e_attrs, ad_objectClass );
150         if ( aoc == NULL ) {
151 #ifdef NEW_LOGGING
152                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
153                         "entry_schema_check: No objectClass for entry (%s).\n"
154                         e->e_dn ));
155 #else
156                 Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
157                     e->e_dn, 0, 0 );
158 #endif
159
160                 *text = "no objectClass attribute";
161                 return LDAP_OBJECT_CLASS_VIOLATION;
162         }
163
164         assert( aoc->a_vals != NULL );
165         assert( aoc->a_vals[0].bv_val != NULL );
166
167         rc = structural_class( aoc->a_vals, &nsc, &oc, text, textbuf, textlen );
168         if( rc != LDAP_SUCCESS ) {
169                 return rc;
170         } else if ( nsc.bv_len == 0 ) {
171                 return LDAP_OBJECT_CLASS_VIOLATION;
172         }
173
174         *text = textbuf;
175
176         if ( oc == NULL ) {
177                 snprintf( textbuf, textlen, 
178                         "unrecognized objectClass '%s'",
179                         aoc->a_vals[0].bv_val );
180                 return LDAP_OBJECT_CLASS_VIOLATION;
181
182         } else if ( sc != oc ) {
183                 snprintf( textbuf, textlen, 
184                         "structuralObjectClass modification from '%s' to '%s' not allowed",
185                         asc->a_vals[0].bv_val, nsc.bv_val );
186                 return LDAP_NO_OBJECT_CLASS_MODS;
187         }
188
189         /* check that the entry has required attrs for each oc */
190         for ( i = 0; aoc->a_vals[i].bv_val != NULL; i++ ) {
191                 if ( (oc = oc_bvfind( &aoc->a_vals[i] )) == NULL ) {
192                         snprintf( textbuf, textlen, 
193                                 "unrecognized objectClass '%s'",
194                                 aoc->a_vals[i].bv_val );
195
196 #ifdef NEW_LOGGING
197                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
198                                 "entry_schema_check: dn (%s), %s\n",
199                                 e->e_dn, textbuf ));
200 #else
201                         Debug( LDAP_DEBUG_ANY,
202                                 "entry_check_schema(%s): %s\n",
203                                 e->e_dn, textbuf, 0 );
204 #endif
205
206                         return LDAP_OBJECT_CLASS_VIOLATION;
207                 }
208
209                 if ( oc->sco_check ) {
210                         int rc = (oc->sco_check)( e, oc,
211                                 text, textbuf, textlen );
212                         if( rc != LDAP_SUCCESS ) {
213                                 return rc;
214                         }
215                 }
216                 if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
217                         /* object class is abstract */
218                         if ( oc != slap_schema.si_oc_top &&
219                                 !is_object_subclass( oc, sc ))
220                         {
221                                 int j;
222                                 ObjectClass *xc = NULL;
223                                 for( j=0; aoc->a_vals[j].bv_val; j++ ) {
224                                         if( i != j ) {
225                                                 xc = oc_bvfind( &aoc->a_vals[i] );
226                                                 if( xc == NULL ) {
227                                                         snprintf( textbuf, textlen, 
228                                                                 "unrecognized objectClass '%s'",
229                                                                 aoc->a_vals[i].bv_val );
230
231 #ifdef NEW_LOGGING
232                                                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
233                                                                 "entry_schema_check: dn (%s), %s\n",
234                                                                 e->e_dn, textbuf ));
235 #else
236                                                         Debug( LDAP_DEBUG_ANY,
237                                                                 "entry_check_schema(%s): %s\n",
238                                                                 e->e_dn, textbuf, 0 );
239 #endif
240
241                                                         return LDAP_OBJECT_CLASS_VIOLATION;
242                                                 }
243
244                                                 /* since we previous check against the
245                                                  * structural object of this entry, the
246                                                  * abstract class must be a (direct or indirect)
247                                                  * superclass of one of the auxiliary classes of
248                                                  * the entry.
249                                                  */
250                                                 if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY &&
251                                                         is_object_subclass( oc, xc ) )
252                                                 {
253                                                         break;;
254                                                 }
255
256                                                 xc = NULL;
257                                         }
258                                 }
259
260                                 if( xc == NULL ) {
261                                         snprintf( textbuf, textlen, "instanstantiation of "
262                                                 "abstract objectClass '%s' not allowed",
263                                                 aoc->a_vals[i].bv_val );
264
265 #ifdef NEW_LOGGING
266                                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
267                                                 "entry_schema_check: dn (%s), %s\n",
268                                                 e->e_dn, textbuf ));
269 #else
270                                         Debug( LDAP_DEBUG_ANY,
271                                                 "entry_check_schema(%s): %s\n",
272                                                 e->e_dn, textbuf, 0 );
273 #endif
274
275                                         return LDAP_OBJECT_CLASS_VIOLATION;
276                                 }
277                         }
278
279                 } else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) {
280                         char *s = oc_check_required( e, oc, &aoc->a_vals[i] );
281
282                         if (s != NULL) {
283                                 snprintf( textbuf, textlen, 
284                                         "object class '%s' requires attribute '%s'",
285                                         aoc->a_vals[i].bv_val, s );
286
287 #ifdef NEW_LOGGING
288                                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
289                                         "entry_schema_check: dn=\"%s\" %s",
290                                         e->e_dn, textbuf ));
291 #else
292                                 Debug( LDAP_DEBUG_ANY,
293                                         "Entry (%s): %s\n",
294                                         e->e_dn, textbuf, 0 );
295 #endif
296
297                                 return LDAP_OBJECT_CLASS_VIOLATION;
298                         }
299
300                         if( oc == slap_schema.si_oc_extensibleObject ) {
301                                 extensible=1;
302                         }
303                 }
304         }
305
306         if( extensible ) {
307                 return LDAP_SUCCESS;
308         }
309
310         /* check that each attr in the entry is allowed by some oc */
311         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
312                 int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals, sc );
313                 if ( ret != LDAP_SUCCESS ) {
314                         char *type = a->a_desc->ad_cname.bv_val;
315
316                         snprintf( textbuf, textlen, 
317                                 "attribute '%s' not allowed",
318                                 type );
319
320 #ifdef NEW_LOGGING
321                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
322                                 "entry_schema_check: dn=\"%s\" %s\n",
323                                 e->e_dn, textbuf ));
324 #else
325                         Debug( LDAP_DEBUG_ANY,
326                             "Entry (%s), %s\n",
327                             e->e_dn, textbuf, 0 );
328 #endif
329
330                         return ret;
331                 }
332         }
333
334         return LDAP_SUCCESS;
335 }
336
337 static char *
338 oc_check_required(
339         Entry *e,
340         ObjectClass *oc,
341         struct berval *ocname )
342 {
343         AttributeType   *at;
344         int             i;
345         Attribute       *a;
346
347 #ifdef NEW_LOGGING
348         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
349                 "oc_check_required: dn (%s), objectClass \"%s\"\n",
350         e->e_dn, ocname->bv_val ));
351 #else
352         Debug( LDAP_DEBUG_TRACE,
353                 "oc_check_required entry (%s), objectClass \"%s\"\n",
354                 e->e_dn, ocname->bv_val, 0 );
355 #endif
356
357
358         /* check for empty oc_required */
359         if(oc->soc_required == NULL) {
360                 return NULL;
361         }
362
363         /* for each required attribute */
364         for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
365                 at = oc->soc_required[i];
366                 /* see if it's in the entry */
367                 for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
368                         if( a->a_desc->ad_type == at ) {
369                                 break;
370                         }
371                 }
372                 /* not there => schema violation */
373                 if ( a == NULL ) {
374                         return at->sat_cname.bv_val;
375                 }
376         }
377
378         return( NULL );
379 }
380
381 int oc_check_allowed(
382         AttributeType *at,
383         BVarray ocl,
384         ObjectClass *sc )
385 {
386         int             i, j;
387
388 #ifdef NEW_LOGGING
389         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
390                 "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
391 #else
392         Debug( LDAP_DEBUG_TRACE,
393                 "oc_check_allowed type \"%s\"\n",
394                 at->sat_cname.bv_val, 0, 0 );
395 #endif
396
397         /* always allow objectClass attribute */
398         if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
399                 return LDAP_SUCCESS;
400         }
401
402         /*
403          * All operational attributions are allowed by schema rules.
404          */
405         if( is_at_operational(at) ) {
406                 return LDAP_SUCCESS;
407         }
408
409         /* check to see if its allowed by the structuralObjectClass */
410         if( sc ) {
411                 /* does it require the type? */
412                 for ( j = 0; sc->soc_required != NULL && 
413                         sc->soc_required[j] != NULL; j++ )
414                 {
415                         if( at == sc->soc_required[j] ) {
416                                 return LDAP_SUCCESS;
417                         }
418                 }
419
420                 /* does it allow the type? */
421                 for ( j = 0; sc->soc_allowed != NULL && 
422                         sc->soc_allowed[j] != NULL; j++ )
423                 {
424                         if( at == sc->soc_allowed[j] ) {
425                                 return LDAP_SUCCESS;
426                         }
427                 }
428         }
429
430         /* check that the type appears as req or opt in at least one oc */
431         for ( i = 0; ocl[i].bv_val != NULL; i++ ) {
432                 /* if we know about the oc */
433                 ObjectClass     *oc = oc_bvfind( &ocl[i] );
434                 if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT &&
435                         ( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY ))
436                 {
437                         /* does it require the type? */
438                         for ( j = 0; oc->soc_required != NULL && 
439                                 oc->soc_required[j] != NULL; j++ )
440                         {
441                                 if( at == oc->soc_required[j] ) {
442                                         return LDAP_SUCCESS;
443                                 }
444                         }
445                         /* does it allow the type? */
446                         for ( j = 0; oc->soc_allowed != NULL && 
447                                 oc->soc_allowed[j] != NULL; j++ )
448                         {
449                                 if( at == oc->soc_allowed[j] ) {
450                                         return LDAP_SUCCESS;
451                                 }
452                         }
453                 }
454         }
455
456         /* not allowed by any oc */
457         return LDAP_OBJECT_CLASS_VIOLATION;
458 }
459
460 /*
461  * Determine the structural object class from a set of OIDs
462  */
463 int structural_class(
464         BVarray ocs,
465         struct berval *scbv,
466         ObjectClass **scp,
467         const char **text,
468         char *textbuf, size_t textlen )
469 {
470         int i;
471         ObjectClass *oc;
472         ObjectClass *sc = NULL;
473         int scn = -1;
474
475         *text = "structural_class: internal error";
476         scbv->bv_len = 0;
477
478         for( i=0; ocs[i].bv_val; i++ ) {
479                 oc = oc_bvfind( &ocs[i] );
480
481                 if( oc == NULL ) {
482                         snprintf( textbuf, textlen,
483                                 "unrecongized objectClass '%s'",
484                                 ocs[i].bv_val );
485                         *text = textbuf;
486                         return LDAP_OBJECT_CLASS_VIOLATION;
487                 }
488
489                 if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
490                         if( sc == NULL || is_object_subclass( sc, oc ) ) {
491                                 sc = oc;
492                                 scn = i;
493
494                         } else if ( !is_object_subclass( oc, sc ) ) {
495                                 int j;
496                                 ObjectClass *xc = NULL;
497
498                                 /* find common superior */
499                                 for( j=i+1; ocs[j].bv_val; j++ ) {
500                                         xc = oc_bvfind( &ocs[j] );
501
502                                         if( xc == NULL ) {
503                                                 snprintf( textbuf, textlen,
504                                                         "unrecongized objectClass '%s'",
505                                                         ocs[i].bv_val );
506                                                 *text = textbuf;
507                                                 return LDAP_OBJECT_CLASS_VIOLATION;
508                                         }
509
510                                         if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
511                                                 xc = NULL;
512                                                 continue;
513                                         }
514
515                                         if( is_object_subclass( sc, xc ) &&
516                                                 is_object_subclass( oc, xc ) )
517                                         {
518                                                 /* found common subclass */
519                                                 break;
520                                         }
521
522                                         xc = NULL;
523                                 }
524
525                                 if( xc == NULL ) {
526                                         /* no common subclass */
527                                         snprintf( textbuf, textlen,
528                                                 "invalid structural object class chain (%s/%s)",
529                                                 ocs[scn].bv_val, ocs[i].bv_val );
530                                         *text = textbuf;
531                                         return LDAP_OBJECT_CLASS_VIOLATION;
532                                 }
533                         }
534                 }
535         }
536
537         if( scp )
538                 *scp = sc;
539
540         if( sc == NULL ) {
541                 *text = "no structural object classes provided";
542                 return LDAP_OBJECT_CLASS_VIOLATION;
543         }
544
545         *scbv = ocs[scn];
546         return LDAP_SUCCESS;
547 }
548
549 /*
550  * Return structural object class from list of modifications
551  */
552 int mods_structural_class(
553         Modifications *mods,
554         struct berval *sc,
555         const char **text,
556         char *textbuf, size_t textlen )
557 {
558         Modifications *ocmod = NULL;
559
560         for( ; mods != NULL; mods = mods->sml_next ) {
561                 if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
562                         if( ocmod != NULL ) {
563                                 *text = "entry has multiple objectClass attributes";
564                                 return LDAP_OBJECT_CLASS_VIOLATION;
565                         }
566                         ocmod = mods;
567                 }
568         }
569
570         if( ocmod == NULL ) {
571                 *text = "entry has no objectClass attribute";
572                 return LDAP_OBJECT_CLASS_VIOLATION;
573         }
574
575         if( ocmod->sml_bvalues == NULL || ocmod->sml_bvalues[0].bv_val == NULL ) {
576                 *text = "objectClass attribute has no values";
577                 return LDAP_OBJECT_CLASS_VIOLATION;
578         }
579
580         return structural_class( ocmod->sml_bvalues, sc, NULL,
581                 text, textbuf, textlen );
582 }