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