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