]> git.sur5r.net Git - openldap/blob - servers/slapd/schema_check.c
Another round of operational attribute changes... more to follow.
[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                 } else if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
209                         /* object class is abstract */
210                         if ( oc != slap_schema.si_oc_top &&
211                                 !is_object_subclass( oc, sc ))
212                         {
213                                 int j;
214                                 ObjectClass *xc = NULL;
215                                 for( j=0; aoc->a_vals[j].bv_val; j++ ) {
216                                         if( i != j ) {
217                                                 xc = oc_bvfind( &aoc->a_vals[i] );
218                                                 if( xc == NULL ) {
219                                                         snprintf( textbuf, textlen, 
220                                                                 "unrecognized objectClass '%s'",
221                                                                 aoc->a_vals[i].bv_val );
222
223 #ifdef NEW_LOGGING
224                                                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
225                                                                 "entry_schema_check: dn (%s), %s\n",
226                                                                 e->e_dn, textbuf ));
227 #else
228                                                         Debug( LDAP_DEBUG_ANY,
229                                                                 "entry_check_schema(%s): %s\n",
230                                                                 e->e_dn, textbuf, 0 );
231 #endif
232
233                                                         return LDAP_OBJECT_CLASS_VIOLATION;
234                                                 }
235
236                                                 /* since we previous check against the
237                                                  * structural object of this entry, the
238                                                  * abstract class must be a (direct or indirect)
239                                                  * superclass of one of the auxiliary classes of
240                                                  * the entry.
241                                                  */
242                                                 if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY &&
243                                                         is_object_subclass( oc, xc ) )
244                                                 {
245                                                         break;;
246                                                 }
247
248                                                 xc = NULL;
249                                         }
250                                 }
251
252                                 if( xc == NULL ) {
253                                         snprintf( textbuf, textlen, "instanstantiation of "
254                                                 "abstract objectClass '%s' not allowed",
255                                                 aoc->a_vals[i].bv_val );
256
257 #ifdef NEW_LOGGING
258                                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
259                                                 "entry_schema_check: dn (%s), %s\n",
260                                                 e->e_dn, textbuf ));
261 #else
262                                         Debug( LDAP_DEBUG_ANY,
263                                                 "entry_check_schema(%s): %s\n",
264                                                 e->e_dn, textbuf, 0 );
265 #endif
266
267                                         return LDAP_OBJECT_CLASS_VIOLATION;
268                                 }
269                         }
270
271                 } else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) {
272                         char *s = oc_check_required( e, oc, &aoc->a_vals[i] );
273
274                         if (s != NULL) {
275                                 snprintf( textbuf, textlen, 
276                                         "object class '%s' requires attribute '%s'",
277                                         aoc->a_vals[i].bv_val, s );
278
279 #ifdef NEW_LOGGING
280                                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
281                                         "entry_schema_check: dn=\"%s\" %s",
282                                         e->e_dn, textbuf ));
283 #else
284                                 Debug( LDAP_DEBUG_ANY,
285                                         "Entry (%s): %s\n",
286                                         e->e_dn, textbuf, 0 );
287 #endif
288
289                                 return LDAP_OBJECT_CLASS_VIOLATION;
290                         }
291
292                         if( oc == slap_schema.si_oc_extensibleObject ) {
293                                 extensible=1;
294                         }
295                 }
296         }
297
298         if( extensible ) {
299                 return LDAP_SUCCESS;
300         }
301
302         /* check that each attr in the entry is allowed by some oc */
303         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
304                 int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals, sc );
305                 if ( ret != LDAP_SUCCESS ) {
306                         char *type = a->a_desc->ad_cname.bv_val;
307
308                         snprintf( textbuf, textlen, 
309                                 "attribute '%s' not allowed",
310                                 type );
311
312 #ifdef NEW_LOGGING
313                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
314                                 "entry_schema_check: dn=\"%s\" %s\n",
315                                 e->e_dn, textbuf ));
316 #else
317                         Debug( LDAP_DEBUG_ANY,
318                             "Entry (%s), %s\n",
319                             e->e_dn, textbuf, 0 );
320 #endif
321
322                         return ret;
323                 }
324         }
325
326         return LDAP_SUCCESS;
327 }
328
329 static char *
330 oc_check_required(
331         Entry *e,
332         ObjectClass *oc,
333         struct berval *ocname )
334 {
335         AttributeType   *at;
336         int             i;
337         Attribute       *a;
338
339 #ifdef NEW_LOGGING
340         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
341                 "oc_check_required: dn (%s), objectClass \"%s\"\n",
342         e->e_dn, ocname->bv_val ));
343 #else
344         Debug( LDAP_DEBUG_TRACE,
345                 "oc_check_required entry (%s), objectClass \"%s\"\n",
346                 e->e_dn, ocname->bv_val, 0 );
347 #endif
348
349
350         /* check for empty oc_required */
351         if(oc->soc_required == NULL) {
352                 return NULL;
353         }
354
355         /* for each required attribute */
356         for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
357                 at = oc->soc_required[i];
358                 /* see if it's in the entry */
359                 for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
360                         if( a->a_desc->ad_type == at ) {
361                                 break;
362                         }
363                 }
364                 /* not there => schema violation */
365                 if ( a == NULL ) {
366                         return at->sat_cname.bv_val;
367                 }
368         }
369
370         return( NULL );
371 }
372
373 int oc_check_allowed(
374         AttributeType *at,
375         BVarray ocl,
376         ObjectClass *sc )
377 {
378         int             i, j;
379
380 #ifdef NEW_LOGGING
381         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
382                 "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
383 #else
384         Debug( LDAP_DEBUG_TRACE,
385                 "oc_check_allowed type \"%s\"\n",
386                 at->sat_cname.bv_val, 0, 0 );
387 #endif
388
389         /* always allow objectClass attribute */
390         if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
391                 return LDAP_SUCCESS;
392         }
393
394         /*
395          * All operational attributions are allowed by schema rules.
396          */
397         if( is_at_operational(at) ) {
398                 return LDAP_SUCCESS;
399         }
400
401         /* check to see if its allowed by the structuralObjectClass */
402         if( sc ) {
403                 /* does it require the type? */
404                 for ( j = 0; sc->soc_required != NULL && 
405                         sc->soc_required[j] != NULL; j++ )
406                 {
407                         if( at == sc->soc_required[j] ) {
408                                 return LDAP_SUCCESS;
409                         }
410                 }
411
412                 /* does it allow the type? */
413                 for ( j = 0; sc->soc_allowed != NULL && 
414                         sc->soc_allowed[j] != NULL; j++ )
415                 {
416                         if( at == sc->soc_allowed[j] ) {
417                                 return LDAP_SUCCESS;
418                         }
419                 }
420         }
421
422         /* check that the type appears as req or opt in at least one oc */
423         for ( i = 0; ocl[i].bv_val != NULL; i++ ) {
424                 /* if we know about the oc */
425                 ObjectClass     *oc = oc_bvfind( &ocl[i] );
426                 if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT &&
427                         ( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY ))
428                 {
429                         /* does it require the type? */
430                         for ( j = 0; oc->soc_required != NULL && 
431                                 oc->soc_required[j] != NULL; j++ )
432                         {
433                                 if( at == oc->soc_required[j] ) {
434                                         return LDAP_SUCCESS;
435                                 }
436                         }
437                         /* does it allow the type? */
438                         for ( j = 0; oc->soc_allowed != NULL && 
439                                 oc->soc_allowed[j] != NULL; j++ )
440                         {
441                                 if( at == oc->soc_allowed[j] ) {
442                                         return LDAP_SUCCESS;
443                                 }
444                         }
445                 }
446         }
447
448         /* not allowed by any oc */
449         return LDAP_OBJECT_CLASS_VIOLATION;
450 }
451
452 /*
453  * Determine the structural object class from a set of OIDs
454  */
455 int structural_class(
456         BVarray ocs,
457         struct berval *scbv,
458         ObjectClass **scp,
459         const char **text,
460         char *textbuf, size_t textlen )
461 {
462         int i;
463         ObjectClass *oc;
464         ObjectClass *sc = NULL;
465         int scn = -1;
466
467         *text = "structural_class: internal error";
468         scbv->bv_len = 0;
469
470         for( i=0; ocs[i].bv_val; i++ ) {
471                 oc = oc_bvfind( &ocs[i] );
472
473                 if( oc == NULL ) {
474                         snprintf( textbuf, textlen,
475                                 "unrecongized objectClass '%s'",
476                                 ocs[i].bv_val );
477                         *text = textbuf;
478                         return LDAP_OBJECT_CLASS_VIOLATION;
479                 }
480
481                 if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
482                         if( sc == NULL || is_object_subclass( sc, oc ) ) {
483                                 sc = oc;
484                                 scn = i;
485
486                         } else if ( !is_object_subclass( oc, sc ) ) {
487                                 int j;
488                                 ObjectClass *xc = NULL;
489
490                                 /* find common superior */
491                                 for( j=i+1; ocs[j].bv_val; j++ ) {
492                                         xc = oc_bvfind( &ocs[j] );
493
494                                         if( xc == NULL ) {
495                                                 snprintf( textbuf, textlen,
496                                                         "unrecongized objectClass '%s'",
497                                                         ocs[i].bv_val );
498                                                 *text = textbuf;
499                                                 return LDAP_OBJECT_CLASS_VIOLATION;
500                                         }
501
502                                         if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
503                                                 xc = NULL;
504                                                 continue;
505                                         }
506
507                                         if( is_object_subclass( sc, xc ) &&
508                                                 is_object_subclass( oc, xc ) )
509                                         {
510                                                 /* found common subclass */
511                                                 break;
512                                         }
513
514                                         xc = NULL;
515                                 }
516
517                                 if( xc == NULL ) {
518                                         /* no common subclass */
519                                         snprintf( textbuf, textlen,
520                                                 "invalid structural object class chain (%s/%s)",
521                                                 ocs[scn].bv_val, ocs[i].bv_val );
522                                         *text = textbuf;
523                                         return LDAP_OBJECT_CLASS_VIOLATION;
524                                 }
525                         }
526                 }
527         }
528
529         if( scp )
530                 *scp = sc;
531
532         if( sc == NULL ) {
533                 *text = "no structural object classes provided";
534                 return LDAP_OBJECT_CLASS_VIOLATION;
535         }
536
537         *scbv = ocs[scn];
538         return LDAP_SUCCESS;
539 }
540
541 /*
542  * Return structural object class from list of modifications
543  */
544 int mods_structural_class(
545         Modifications *mods,
546         struct berval *sc,
547         const char **text,
548         char *textbuf, size_t textlen )
549 {
550         Modifications *ocmod = NULL;
551
552         for( ; mods != NULL; mods = mods->sml_next ) {
553                 if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
554                         if( ocmod != NULL ) {
555                                 *text = "entry has multiple objectClass attributes";
556                                 return LDAP_OBJECT_CLASS_VIOLATION;
557                         }
558                         ocmod = mods;
559                 }
560         }
561
562         if( ocmod == NULL ) {
563                 *text = "entry has no objectClass attribute";
564                 return LDAP_OBJECT_CLASS_VIOLATION;
565         }
566
567         if( ocmod->sml_bvalues == NULL || ocmod->sml_bvalues[0].bv_val == NULL ) {
568                 *text = "objectClass attribute has no values";
569                 return LDAP_OBJECT_CLASS_VIOLATION;
570         }
571
572         return structural_class( ocmod->sml_bvalues, sc, NULL,
573                 text, textbuf, textlen );
574 }