]> git.sur5r.net Git - openldap/blob - servers/slapd/schema_check.c
Patch slapadd(8) to provide a structuralObjectClass if missing
[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 {
32         int i;
33         ObjectClass *oc;
34         ObjectClass *sc = NULL;
35         int scn = 0;
36
37         *text = "structural object error";
38         scbv->bv_len = 0;
39
40         for( i=0; ocs[i]; i++ ) {
41                 oc = oc_find( ocs[i]->bv_val );
42
43                 if( oc == NULL ) {
44                         *text = "unrecongized objectClass attribute";
45                         return LDAP_OBJECT_CLASS_VIOLATION;
46                 }
47
48                 if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
49                         if( sc == NULL || is_object_subclass( sc, oc ) ) {
50                                 sc = oc;
51                                 scn = i;
52
53                         } else if ( !is_object_subclass( oc, sc ) ) {
54                                 /* FIXME: multiple inheritance possible! */
55                                 *text = "invalid strucutural object class chain";
56                                 return LDAP_OBJECT_CLASS_VIOLATION;
57                         }
58                 }
59         }
60
61         if( sc == NULL ) {
62                 *text = "no strucutural object classes";
63                 return LDAP_OBJECT_CLASS_VIOLATION;
64         }
65
66         *scbv = *ocs[scn];
67         return LDAP_SUCCESS;
68 }
69
70 /*
71  * Return structural object class from list of modifications
72  */
73 int mods_structural_class(
74         Modifications *mods,
75         struct berval *sc,
76         const char **text )
77 {
78         Modifications *ocmod = NULL;
79
80         for( ; mods != NULL; mods = mods->sml_next ) {
81                 if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
82                         if( ocmod != NULL ) {
83                                 *text = "entry has multiple objectClass attributes";
84                                 return LDAP_OBJECT_CLASS_VIOLATION;
85                         }
86                         ocmod = mods;
87                 }
88         }
89
90         if( ocmod == NULL ) {
91                 *text = "entry has no objectClass attribute";
92                 return LDAP_OBJECT_CLASS_VIOLATION;
93         }
94
95         if( ocmod->sml_bvalues == NULL || ocmod->sml_bvalues[0] == NULL ) {
96                 *text = "objectClass attribute has no values";
97                 return LDAP_OBJECT_CLASS_VIOLATION;
98         }
99
100         return structural_class( ocmod->sml_bvalues, sc, text );
101 }
102
103 /*
104  * entry_schema_check - check that entry e conforms to the schema required
105  * by its object class(es).
106  *
107  * returns 0 if so, non-zero otherwise.
108  */
109
110 int
111 entry_schema_check( 
112         Entry *e, Attribute *oldattrs,
113         const char** text,
114         char *textbuf, size_t textlen )
115 {
116         Attribute       *a, *asc, *aoc;
117         ObjectClass *sc, *oc;
118         int     rc, i;
119         struct berval nsc;
120         AttributeDescription *ad_structuralObjectClass
121                 = slap_schema.si_ad_structuralObjectClass;
122         AttributeDescription *ad_objectClass
123                 = slap_schema.si_ad_objectClass;
124         int extensible = 0;
125
126         *text = textbuf;
127
128         /* check single-valued attrs for multiple values */
129         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
130                 /* there should be at least one value */
131                 assert( a->a_vals );
132                 assert( a->a_vals[0] != NULL ); 
133
134                 /* if single value type, check for multiple values */
135                 if( is_at_single_value( a->a_desc->ad_type ) &&
136                         a->a_vals[1] != NULL )
137                 {
138                         char *type = a->a_desc->ad_cname.bv_val;
139
140                         snprintf( textbuf, textlen, 
141                                 "attribute '%s' cannot have multiple values",
142                                 type );
143
144 #ifdef NEW_LOGGING
145                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
146                                 "entry_schema_check: dn=\"%s\" %s\n",
147                                 e->e_dn, textbuf ));
148 #else
149                         Debug( LDAP_DEBUG_ANY,
150                             "Entry (%s), %s\n",
151                             e->e_dn, textbuf, 0 );
152 #endif
153
154                         return LDAP_CONSTRAINT_VIOLATION;
155                 }
156         }
157
158         if( !global_schemacheck ) return LDAP_SUCCESS;
159
160         /* find the object class attribute - could error out here */
161         asc = attr_find( e->e_attrs, ad_structuralObjectClass );
162         if ( asc == NULL ) {
163 #ifdef NEW_LOGGING
164                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO, "entry_schema_check: "
165                         "No structuralObjectClass for entry (%s)\n",
166                         e->e_dn ));
167 #else
168                 Debug( LDAP_DEBUG_ANY,
169                         "No structuralObjectClass for entry (%s)\n",
170                     e->e_dn, 0, 0 );
171 #endif
172
173                 *text = "no structuralObjectClass operational attribute";
174                 return LDAP_OBJECT_CLASS_VIOLATION;
175         }
176
177         assert( asc->a_vals != NULL );
178         assert( asc->a_vals[0] != NULL );
179         assert( asc->a_vals[1] == NULL );
180
181         sc = oc_find( asc->a_vals[0]->bv_val );
182         if( sc == NULL ) {
183                 snprintf( textbuf, textlen, 
184                         "unrecognized structuralObjectClass '%s'",
185                         aoc->a_vals[0]->bv_val );
186
187 #ifdef NEW_LOGGING
188                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
189                         "entry_schema_check: dn (%s), %s\n",
190                         e->e_dn, textbuf ));
191 #else
192                 Debug( LDAP_DEBUG_ANY,
193                         "entry_check_schema(%s): %s\n",
194                         e->e_dn, textbuf, 0 );
195 #endif
196
197                 return LDAP_OBJECT_CLASS_VIOLATION;
198         }
199
200         if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
201                 snprintf( textbuf, textlen, 
202                         "structuralObjectClass '%s' is not STRUCTURAL",
203                         aoc->a_vals[0]->bv_val );
204
205 #ifdef NEW_LOGGING
206                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
207                         "entry_schema_check: dn (%s), %s\n",
208                         e->e_dn, textbuf ));
209 #else
210                 Debug( LDAP_DEBUG_ANY,
211                         "entry_check_schema(%s): %s\n",
212                         e->e_dn, textbuf, 0 );
213 #endif
214
215                 return LDAP_OBJECT_CLASS_VIOLATION;
216         }
217
218         /* find the object class attribute */
219         aoc = attr_find( e->e_attrs, ad_objectClass );
220         if ( aoc == NULL ) {
221 #ifdef NEW_LOGGING
222                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
223                         "entry_schema_check: No objectClass for entry (%s).\n", e->e_dn ));
224 #else
225                 Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
226                     e->e_dn, 0, 0 );
227 #endif
228
229                 *text = "no objectClass attribute";
230                 return LDAP_OBJECT_CLASS_VIOLATION;
231         }
232
233         assert( aoc->a_vals != NULL );
234         assert( aoc->a_vals[0] != NULL );
235
236         rc = structural_class( aoc->a_vals, &nsc, text );
237         if( rc != LDAP_SUCCESS ) {
238                 return rc;
239         } else if ( nsc.bv_len == 0 ) {
240                 return LDAP_OBJECT_CLASS_VIOLATION;
241         }
242
243         oc = oc_find( nsc.bv_val );
244         if ( oc == NULL ) {
245                 snprintf( textbuf, textlen, 
246                         "unrecognized objectClass '%s'",
247                         aoc->a_vals[i]->bv_val );
248                 return LDAP_OBJECT_CLASS_VIOLATION;
249
250         } else if ( sc != oc ) {
251                 snprintf( textbuf, textlen, 
252                         "structuralObjectClass modification from '%s' to '%s' not allowed",
253                         asc->a_vals[0]->bv_val, nsc.bv_val );
254                 return LDAP_NO_OBJECT_CLASS_MODS;
255         }
256
257         /* check that the entry has required attrs for each oc */
258         for ( i = 0; aoc->a_vals[i] != NULL; i++ ) {
259                 if ( (oc = oc_find( aoc->a_vals[i]->bv_val )) == NULL ) {
260                         snprintf( textbuf, textlen, 
261                                 "unrecognized objectClass '%s'",
262                                 aoc->a_vals[i]->bv_val );
263
264 #ifdef NEW_LOGGING
265                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
266                                 "entry_schema_check: dn (%s), %s\n",
267                                 e->e_dn, textbuf ));
268 #else
269                         Debug( LDAP_DEBUG_ANY,
270                                 "entry_check_schema(%s): \"%s\" not recognized\n",
271                                 e->e_dn, textbuf, 0 );
272 #endif
273
274                         return LDAP_OBJECT_CLASS_VIOLATION;
275
276                 } else if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
277                         /* object class is abstract */
278                         /* FIXME: need to check that is is a superclass of something */
279
280                 } else if ( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL && oc != sc ) {
281                         /* object class is a superclass of the structural class */
282                         /* nothing in particular to check */
283
284                 } else {
285                         char *s = oc_check_required( e, oc, aoc->a_vals[i] );
286
287                         if (s != NULL) {
288                                 snprintf( textbuf, textlen, 
289                                         "object class '%s' requires attribute '%s'",
290                                         aoc->a_vals[i]->bv_val, s );
291
292 #ifdef NEW_LOGGING
293                                 LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
294                                         "entry_schema_check: dn=\"%s\" %s",
295                                         e->e_dn, textbuf ));
296 #else
297                                 Debug( LDAP_DEBUG_ANY,
298                                         "Entry (%s): %s\n",
299                                         e->e_dn, textbuf, 0 );
300 #endif
301
302                                 return LDAP_OBJECT_CLASS_VIOLATION;
303                         }
304
305                         if( oc == slap_schema.si_oc_extensibleObject ) {
306                                 extensible=1;
307                         }
308                 }
309         }
310
311         if( extensible ) {
312                 return LDAP_SUCCESS;
313         }
314
315         /* check that each attr in the entry is allowed by some oc */
316         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
317                 int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals );
318                 if ( ret != LDAP_SUCCESS ) {
319                         char *type = a->a_desc->ad_cname.bv_val;
320
321                         snprintf( textbuf, textlen, 
322                                 "attribute '%s' not allowed",
323                                 type );
324
325 #ifdef NEW_LOGGING
326                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
327                                 "entry_schema_check: dn=\"%s\" %s\n",
328                                 e->e_dn, textbuf ));
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(( "schema", LDAP_LEVEL_ENTRY,
354                 "oc_check_required: dn (%s), objectClass \"%s\"\n",
355         e->e_dn, ocname->bv_val ));
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         struct berval **ocl )
389 {
390         ObjectClass     *oc;
391         int             i, j;
392
393 #ifdef NEW_LOGGING
394         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
395                 "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
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 that the type appears as req or opt in at least one oc */
415         for ( i = 0; ocl[i] != NULL; i++ ) {
416                 /* if we know about the oc */
417                 if ( (oc = oc_find( ocl[i]->bv_val )) != NULL ) {
418                         /* does it require the type? */
419                         for ( j = 0; oc->soc_required != NULL && 
420                                 oc->soc_required[j] != NULL; j++ )
421                         {
422                                 if( at == oc->soc_required[j] ) {
423                                         return LDAP_SUCCESS;
424                                 }
425                         }
426                         /* does it allow the type? */
427                         for ( j = 0; oc->soc_allowed != NULL && 
428                                 oc->soc_allowed[j] != NULL; j++ )
429                         {
430                                 if( at == oc->soc_allowed[j] ) {
431                                         return LDAP_SUCCESS;
432                                 }
433                         }
434                         /* maybe the next oc allows it */
435                 }
436         }
437
438         /* not allowed by any oc */
439         return LDAP_OBJECT_CLASS_VIOLATION;
440 }