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