]> git.sur5r.net Git - openldap/blob - servers/slapd/schema_check.c
1f1ed839fe7bcb4fb32e1f14a2ed21ccb5d8dbf0
[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_class: internal 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         *text = textbuf;
244
245         oc = oc_find( nsc.bv_val );
246         if ( oc == NULL ) {
247                 snprintf( textbuf, textlen, 
248                         "unrecognized objectClass '%s'",
249                         aoc->a_vals[i]->bv_val );
250                 return LDAP_OBJECT_CLASS_VIOLATION;
251
252         } else if ( sc != oc ) {
253                 snprintf( textbuf, textlen, 
254                         "structuralObjectClass modification from '%s' to '%s' not allowed",
255                         asc->a_vals[0]->bv_val, nsc.bv_val );
256                 return LDAP_NO_OBJECT_CLASS_MODS;
257         }
258
259         /* check that the entry has required attrs for each oc */
260         for ( i = 0; aoc->a_vals[i] != NULL; i++ ) {
261                 if ( (oc = oc_find( aoc->a_vals[i]->bv_val )) == NULL ) {
262                         snprintf( textbuf, textlen, 
263                                 "unrecognized objectClass '%s'",
264                                 aoc->a_vals[i]->bv_val );
265
266 #ifdef NEW_LOGGING
267                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
268                                 "entry_schema_check: dn (%s), %s\n",
269                                 e->e_dn, textbuf ));
270 #else
271                         Debug( LDAP_DEBUG_ANY,
272                                 "entry_check_schema(%s): \"%s\" not recognized\n",
273                                 e->e_dn, textbuf, 0 );
274 #endif
275
276                         return LDAP_OBJECT_CLASS_VIOLATION;
277
278                 } else if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
279                         /* object class is abstract */
280                         /* FIXME: need to check that is is a superclass of something */
281
282                 } else if ( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL && oc != sc ) {
283                         /* object class is a superclass of the structural class */
284                         /* nothing in particular to check */
285
286                 } else {
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(( "schema", LDAP_LEVEL_INFO,
296                                         "entry_schema_check: dn=\"%s\" %s",
297                                         e->e_dn, textbuf ));
298 #else
299                                 Debug( LDAP_DEBUG_ANY,
300                                         "Entry (%s): %s\n",
301                                         e->e_dn, textbuf, 0 );
302 #endif
303
304                                 return LDAP_OBJECT_CLASS_VIOLATION;
305                         }
306
307                         if( oc == slap_schema.si_oc_extensibleObject ) {
308                                 extensible=1;
309                         }
310                 }
311         }
312
313         if( extensible ) {
314                 return LDAP_SUCCESS;
315         }
316
317         /* check that each attr in the entry is allowed by some oc */
318         for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
319                 int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals );
320                 if ( ret != LDAP_SUCCESS ) {
321                         char *type = a->a_desc->ad_cname.bv_val;
322
323                         snprintf( textbuf, textlen, 
324                                 "attribute '%s' not allowed",
325                                 type );
326
327 #ifdef NEW_LOGGING
328                         LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
329                                 "entry_schema_check: dn=\"%s\" %s\n",
330                                 e->e_dn, textbuf ));
331 #else
332                         Debug( LDAP_DEBUG_ANY,
333                             "Entry (%s), %s\n",
334                             e->e_dn, textbuf, 0 );
335 #endif
336
337                         return ret;
338                 }
339         }
340
341         return LDAP_SUCCESS;
342 }
343
344 static char *
345 oc_check_required(
346         Entry *e,
347         ObjectClass *oc,
348         struct berval *ocname )
349 {
350         AttributeType   *at;
351         int             i;
352         Attribute       *a;
353
354 #ifdef NEW_LOGGING
355         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
356                 "oc_check_required: dn (%s), objectClass \"%s\"\n",
357         e->e_dn, ocname->bv_val ));
358 #else
359         Debug( LDAP_DEBUG_TRACE,
360                 "oc_check_required entry (%s), objectClass \"%s\"\n",
361                 e->e_dn, ocname->bv_val, 0 );
362 #endif
363
364
365         /* check for empty oc_required */
366         if(oc->soc_required == NULL) {
367                 return NULL;
368         }
369
370         /* for each required attribute */
371         for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
372                 at = oc->soc_required[i];
373                 /* see if it's in the entry */
374                 for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
375                         if( a->a_desc->ad_type == at ) {
376                                 break;
377                         }
378                 }
379                 /* not there => schema violation */
380                 if ( a == NULL ) {
381                         return at->sat_cname.bv_val;
382                 }
383         }
384
385         return( NULL );
386 }
387
388 int oc_check_allowed(
389         AttributeType *at,
390         struct berval **ocl )
391 {
392         ObjectClass     *oc;
393         int             i, j;
394
395 #ifdef NEW_LOGGING
396         LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
397                 "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
398 #else
399         Debug( LDAP_DEBUG_TRACE,
400                 "oc_check_allowed type \"%s\"\n",
401                 at->sat_cname.bv_val, 0, 0 );
402 #endif
403
404         /* always allow objectClass attribute */
405         if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
406                 return LDAP_SUCCESS;
407         }
408
409         /*
410          * All operational attributions are allowed by schema rules.
411          */
412         if( is_at_operational(at) ) {
413                 return LDAP_SUCCESS;
414         }
415
416         /* check that the type appears as req or opt in at least one oc */
417         for ( i = 0; ocl[i] != NULL; i++ ) {
418                 /* if we know about the oc */
419                 if ( (oc = oc_find( ocl[i]->bv_val )) != NULL ) {
420                         /* does it require the type? */
421                         for ( j = 0; oc->soc_required != NULL && 
422                                 oc->soc_required[j] != NULL; j++ )
423                         {
424                                 if( at == oc->soc_required[j] ) {
425                                         return LDAP_SUCCESS;
426                                 }
427                         }
428                         /* does it allow the type? */
429                         for ( j = 0; oc->soc_allowed != NULL && 
430                                 oc->soc_allowed[j] != NULL; j++ )
431                         {
432                                 if( at == oc->soc_allowed[j] ) {
433                                         return LDAP_SUCCESS;
434                                 }
435                         }
436                         /* maybe the next oc allows it */
437                 }
438         }
439
440         /* not allowed by any oc */
441         return LDAP_OBJECT_CLASS_VIOLATION;
442 }