]> git.sur5r.net Git - openldap/blob - servers/slapd/schema_check.c
aa3341d079ea5dd3a803d513a9b6d82af73770e2
[openldap] / servers / slapd / schema_check.c
1 /* schema_check.c - routines to enforce schema definitions */
2 /* $OpenLDAP$ */
3 /*
4  * Copyright 1998-2000 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  * Determine the structural object class from a set of OIDs
26  */
27 int structural_class(
28         struct berval **ocs,
29         struct berval *scbv,
30         const char **text,
31         char *textbuf, size_t textlen )
32 {
33         int i;
34         ObjectClass *oc;
35         ObjectClass *sc = NULL;
36         int scn = -1;
37
38         *text = "structural_class: internal error";
39         scbv->bv_len = 0;
40
41         for( i=0; ocs[i]; i++ ) {
42                 oc = oc_find( ocs[i]->bv_val );
43
44                 if( oc == NULL ) {
45                         snprintf( textbuf, textlen,
46                                 "unrecongized objectClass '%s'",
47                                 ocs[i]->bv_val );
48                         *text = textbuf;
49                         return LDAP_OBJECT_CLASS_VIOLATION;
50                 }
51
52                 if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
53                         if( sc == NULL || is_object_subclass( sc, oc ) ) {
54                                 sc = oc;
55                                 scn = i;
56
57                         } else if ( !is_object_subclass( oc, sc ) ) {
58                                 int j;
59                                 ObjectClass *xc = NULL;
60
61                                 /* find common superior */
62                                 for( j=i+1; ocs[j]; j++ ) {
63                                         xc = oc_find( ocs[j]->bv_val );
64
65                                         if( xc == NULL ) {
66                                                 snprintf( textbuf, textlen,
67                                                         "unrecongized objectClass '%s'",
68                                                         ocs[i]->bv_val );
69                                                 *text = textbuf;
70                                                 return LDAP_OBJECT_CLASS_VIOLATION;
71                                         }
72
73                                         if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
74                                                 xc = NULL;
75                                                 continue;
76                                         }
77
78                                         if( is_object_subclass( sc, xc ) &&
79                                                 is_object_subclass( oc, xc ) )
80                                         {
81                                                 /* found common subclass */
82                                                 break;
83                                         }
84
85                                         xc = NULL;
86                                 }
87
88                                 if( xc == NULL ) {
89                                         /* no common subclass */
90                                         snprintf( textbuf, textlen,
91                                                 "invalid structural object class chain (%s/%s)",
92                                                 ocs[scn]->bv_val, ocs[i]->bv_val );
93                                         *text = textbuf;
94                                         return LDAP_OBJECT_CLASS_VIOLATION;
95                                 }
96                         }
97                 }
98         }
99
100         if( sc == NULL ) {
101                 *text = "no structural object classes provided";
102                 return LDAP_OBJECT_CLASS_VIOLATION;
103         }
104
105         *scbv = *ocs[scn];
106         return LDAP_SUCCESS;
107 }
108
109 /*
110  * Return structural object class from list of modifications
111  */
112 int mods_structural_class(
113         Modifications *mods,
114         struct berval *sc,
115         const char **text,
116         char *textbuf, size_t textlen )
117 {
118         Modifications *ocmod = NULL;
119
120         for( ; mods != NULL; mods = mods->sml_next ) {
121                 if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
122                         if( ocmod != NULL ) {
123                                 *text = "entry has multiple objectClass attributes";
124                                 return LDAP_OBJECT_CLASS_VIOLATION;
125                         }
126                         ocmod = mods;
127                 }
128         }
129
130         if( ocmod == NULL ) {
131                 *text = "entry has no objectClass attribute";
132                 return LDAP_OBJECT_CLASS_VIOLATION;
133         }
134
135         if( ocmod->sml_bvalues == NULL || ocmod->sml_bvalues[0] == NULL ) {
136                 *text = "objectClass attribute has no values";
137                 return LDAP_OBJECT_CLASS_VIOLATION;
138         }
139
140         return structural_class( ocmod->sml_bvalues, sc,
141                 text, textbuf, textlen );
142 }
143
144 /*
145  * entry_schema_check - check that entry e conforms to the schema required
146  * by its object class(es).
147  *
148  * returns 0 if so, non-zero otherwise.
149  */
150
151 int
152 entry_schema_check( 
153         Entry *e, Attribute *oldattrs,
154         const char** text,
155         char *textbuf, size_t textlen )
156 {
157         Attribute       *a, *asc, *aoc;
158         ObjectClass *sc, *oc;
159         int     rc, i;
160         struct berval nsc;
161         AttributeDescription *ad_structuralObjectClass
162                 = slap_schema.si_ad_structuralObjectClass;
163         AttributeDescription *ad_objectClass
164                 = slap_schema.si_ad_objectClass;
165         int extensible = 0;
166
167         *text = textbuf;
168
169         /* check single-valued attrs for multiple values */
170         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
171                 /* there should be at least one value */
172                 assert( a->a_vals );
173                 assert( a->a_vals[0] != NULL ); 
174
175                 /* if single value type, check for multiple values */
176                 if( is_at_single_value( a->a_desc->ad_type ) &&
177                         a->a_vals[1] != NULL )
178                 {
179                         char *type = a->a_desc->ad_cname.bv_val;
180
181                         snprintf( textbuf, textlen, 
182                                 "attribute '%s' cannot have multiple values",
183                                 type );
184
185 #ifdef NEW_LOGGING
186                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
187                                 "entry_schema_check: dn=\"%s\" %s\n",
188                                 e->e_dn, textbuf ));
189 #else
190                         Debug( LDAP_DEBUG_ANY,
191                             "Entry (%s), %s\n",
192                             e->e_dn, textbuf, 0 );
193 #endif
194
195                         return LDAP_CONSTRAINT_VIOLATION;
196                 }
197         }
198
199         if( !global_schemacheck ) return LDAP_SUCCESS;
200
201         /* find the object class attribute - could error out here */
202         asc = attr_find( e->e_attrs, ad_structuralObjectClass );
203         if ( asc == NULL ) {
204 #ifdef NEW_LOGGING
205                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO, "entry_schema_check: "
206                         "No structuralObjectClass for entry (%s)\n",
207                         e->e_dn ));
208 #else
209                 Debug( LDAP_DEBUG_ANY,
210                         "No structuralObjectClass for entry (%s)\n",
211                     e->e_dn, 0, 0 );
212 #endif
213
214                 *text = "no structuralObjectClass operational attribute";
215                 return LDAP_OBJECT_CLASS_VIOLATION;
216         }
217
218         assert( asc->a_vals != NULL );
219         assert( asc->a_vals[0] != NULL );
220         assert( asc->a_vals[1] == NULL );
221
222         sc = oc_find( asc->a_vals[0]->bv_val );
223         if( sc == NULL ) {
224                 snprintf( textbuf, textlen, 
225                         "unrecognized structuralObjectClass '%s'",
226                         aoc->a_vals[0]->bv_val );
227
228 #ifdef NEW_LOGGING
229                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
230                         "entry_schema_check: dn (%s), %s\n",
231                         e->e_dn, textbuf ));
232 #else
233                 Debug( LDAP_DEBUG_ANY,
234                         "entry_check_schema(%s): %s\n",
235                         e->e_dn, textbuf, 0 );
236 #endif
237
238                 return LDAP_OBJECT_CLASS_VIOLATION;
239         }
240
241         if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
242                 snprintf( textbuf, textlen, 
243                         "structuralObjectClass '%s' is not STRUCTURAL",
244                         aoc->a_vals[0]->bv_val );
245
246 #ifdef NEW_LOGGING
247                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
248                         "entry_schema_check: dn (%s), %s\n",
249                         e->e_dn, textbuf ));
250 #else
251                 Debug( LDAP_DEBUG_ANY,
252                         "entry_check_schema(%s): %s\n",
253                         e->e_dn, textbuf, 0 );
254 #endif
255
256                 return LDAP_OBJECT_CLASS_VIOLATION;
257         }
258
259         /* find the object class attribute */
260         aoc = attr_find( e->e_attrs, ad_objectClass );
261         if ( aoc == NULL ) {
262 #ifdef NEW_LOGGING
263                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
264                         "entry_schema_check: No objectClass for entry (%s).\n", e->e_dn ));
265 #else
266                 Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
267                     e->e_dn, 0, 0 );
268 #endif
269
270                 *text = "no objectClass attribute";
271                 return LDAP_OBJECT_CLASS_VIOLATION;
272         }
273
274         assert( aoc->a_vals != NULL );
275         assert( aoc->a_vals[0] != NULL );
276
277         rc = structural_class( aoc->a_vals, &nsc, text, textbuf, textlen );
278         if( rc != LDAP_SUCCESS ) {
279                 return rc;
280         } else if ( nsc.bv_len == 0 ) {
281                 return LDAP_OBJECT_CLASS_VIOLATION;
282         }
283
284         *text = textbuf;
285
286         oc = oc_find( nsc.bv_val );
287         if ( oc == NULL ) {
288                 snprintf( textbuf, textlen, 
289                         "unrecognized objectClass '%s'",
290                         aoc->a_vals[i]->bv_val );
291                 return LDAP_OBJECT_CLASS_VIOLATION;
292
293         } else if ( sc != oc ) {
294                 snprintf( textbuf, textlen, 
295                         "structuralObjectClass modification from '%s' to '%s' not allowed",
296                         asc->a_vals[0]->bv_val, nsc.bv_val );
297                 return LDAP_NO_OBJECT_CLASS_MODS;
298         }
299
300         /* check that the entry has required attrs for each oc */
301         for ( i = 0; aoc->a_vals[i] != NULL; i++ ) {
302                 if ( (oc = oc_find( aoc->a_vals[i]->bv_val )) == NULL ) {
303                         snprintf( textbuf, textlen, 
304                                 "unrecognized objectClass '%s'",
305                                 aoc->a_vals[i]->bv_val );
306
307 #ifdef NEW_LOGGING
308                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
309                                 "entry_schema_check: dn (%s), %s\n",
310                                 e->e_dn, textbuf ));
311 #else
312                         Debug( LDAP_DEBUG_ANY,
313                                 "entry_check_schema(%s): \"%s\" not recognized\n",
314                                 e->e_dn, textbuf, 0 );
315 #endif
316
317                         return LDAP_OBJECT_CLASS_VIOLATION;
318
319                 } else if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
320                         /* object class is abstract */
321                         /* FIXME: need to check that is is a superclass of something */
322
323                 } else if ( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL && oc != sc ) {
324                         /* object class is a superclass of the structural class */
325                         /* nothing in particular to check */
326
327                 } else {
328                         char *s = oc_check_required( e, oc, aoc->a_vals[i] );
329
330                         if (s != NULL) {
331                                 snprintf( textbuf, textlen, 
332                                         "object class '%s' requires attribute '%s'",
333                                         aoc->a_vals[i]->bv_val, s );
334
335 #ifdef NEW_LOGGING
336                                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
337                                         "entry_schema_check: dn=\"%s\" %s",
338                                         e->e_dn, textbuf ));
339 #else
340                                 Debug( LDAP_DEBUG_ANY,
341                                         "Entry (%s): %s\n",
342                                         e->e_dn, textbuf, 0 );
343 #endif
344
345                                 return LDAP_OBJECT_CLASS_VIOLATION;
346                         }
347
348                         if( oc == slap_schema.si_oc_extensibleObject ) {
349                                 extensible=1;
350                         }
351                 }
352         }
353
354         if( extensible ) {
355                 return LDAP_SUCCESS;
356         }
357
358         /* check that each attr in the entry is allowed by some oc */
359         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
360                 int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals );
361                 if ( ret != LDAP_SUCCESS ) {
362                         char *type = a->a_desc->ad_cname.bv_val;
363
364                         snprintf( textbuf, textlen, 
365                                 "attribute '%s' not allowed",
366                                 type );
367
368 #ifdef NEW_LOGGING
369                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
370                                 "entry_schema_check: dn=\"%s\" %s\n",
371                                 e->e_dn, textbuf ));
372 #else
373                         Debug( LDAP_DEBUG_ANY,
374                             "Entry (%s), %s\n",
375                             e->e_dn, textbuf, 0 );
376 #endif
377
378                         return ret;
379                 }
380         }
381
382         return LDAP_SUCCESS;
383 }
384
385 static char *
386 oc_check_required(
387         Entry *e,
388         ObjectClass *oc,
389         struct berval *ocname )
390 {
391         AttributeType   *at;
392         int             i;
393         Attribute       *a;
394
395 #ifdef NEW_LOGGING
396         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
397                 "oc_check_required: dn (%s), objectClass \"%s\"\n",
398         e->e_dn, ocname->bv_val ));
399 #else
400         Debug( LDAP_DEBUG_TRACE,
401                 "oc_check_required entry (%s), objectClass \"%s\"\n",
402                 e->e_dn, ocname->bv_val, 0 );
403 #endif
404
405
406         /* check for empty oc_required */
407         if(oc->soc_required == NULL) {
408                 return NULL;
409         }
410
411         /* for each required attribute */
412         for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
413                 at = oc->soc_required[i];
414                 /* see if it's in the entry */
415                 for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
416                         if( a->a_desc->ad_type == at ) {
417                                 break;
418                         }
419                 }
420                 /* not there => schema violation */
421                 if ( a == NULL ) {
422                         return at->sat_cname.bv_val;
423                 }
424         }
425
426         return( NULL );
427 }
428
429 int oc_check_allowed(
430         AttributeType *at,
431         struct berval **ocl )
432 {
433         ObjectClass     *oc;
434         int             i, j;
435
436 #ifdef NEW_LOGGING
437         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
438                 "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
439 #else
440         Debug( LDAP_DEBUG_TRACE,
441                 "oc_check_allowed type \"%s\"\n",
442                 at->sat_cname.bv_val, 0, 0 );
443 #endif
444
445         /* always allow objectClass attribute */
446         if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
447                 return LDAP_SUCCESS;
448         }
449
450         /*
451          * All operational attributions are allowed by schema rules.
452          */
453         if( is_at_operational(at) ) {
454                 return LDAP_SUCCESS;
455         }
456
457         /* check that the type appears as req or opt in at least one oc */
458         for ( i = 0; ocl[i] != NULL; i++ ) {
459                 /* if we know about the oc */
460                 if ( (oc = oc_find( ocl[i]->bv_val )) != NULL ) {
461                         /* does it require the type? */
462                         for ( j = 0; oc->soc_required != NULL && 
463                                 oc->soc_required[j] != NULL; j++ )
464                         {
465                                 if( at == oc->soc_required[j] ) {
466                                         return LDAP_SUCCESS;
467                                 }
468                         }
469                         /* does it allow the type? */
470                         for ( j = 0; oc->soc_allowed != NULL && 
471                                 oc->soc_allowed[j] != NULL; j++ )
472                         {
473                                 if( at == oc->soc_allowed[j] ) {
474                                         return LDAP_SUCCESS;
475                                 }
476                         }
477                         /* maybe the next oc allows it */
478                 }
479         }
480
481         /* not allowed by any oc */
482         return LDAP_OBJECT_CLASS_VIOLATION;
483 }