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