xref: /openbsd-src/usr.sbin/ldapd/validate.c (revision 7c686fcd9cf377b5ee91139b18e7fcc653fa77fd)
1 /*	$OpenBSD: validate.c,v 1.8 2010/09/03 09:39:17 martinh Exp $ */
2 
3 /*
4  * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "ldapd.h"
26 
27 static int
28 validate_required_attributes(struct ber_element *entry, struct object *obj)
29 {
30 	struct attr_ptr		*ap;
31 	struct attr_type	*at;
32 
33 	if (obj->must == NULL)
34 		return LDAP_SUCCESS;
35 
36 	SLIST_FOREACH(ap, obj->must, next) {
37 		at = ap->attr_type;
38 
39 		if (ldap_find_attribute(entry, at) == NULL) {
40 			log_debug("missing required attribute %s",
41 			    ATTR_NAME(at));
42 			return LDAP_OBJECT_CLASS_VIOLATION;
43 		}
44 	}
45 
46 	return LDAP_SUCCESS;
47 }
48 
49 static int
50 validate_attribute(struct attr_type *at, struct ber_element *vals)
51 {
52 	int			 nvals = 0;
53 	struct ber_element	*elm;
54 	char			*val;
55 
56 	if (vals == NULL) {
57 		log_debug("missing values");
58 		return LDAP_OTHER;
59 	}
60 
61 	if (vals->be_type != BER_TYPE_SET) {
62 		log_debug("values should be a set");
63 		return LDAP_OTHER;
64 	}
65 
66 	for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) {
67 		if (ber_get_string(elm, &val) == -1) {
68 			log_debug("attribute value not an octet-string");
69 			return LDAP_PROTOCOL_ERROR;
70 		}
71 
72 		if (++nvals > 1 && at->single) {
73 			log_debug("multiple values for single-valued"
74 			    " attribute %s", ATTR_NAME(at));
75 			return LDAP_CONSTRAINT_VIOLATION;
76 		}
77 
78 		if (at->syntax->is_valid != NULL &&
79 		    !at->syntax->is_valid(conf->schema, val, elm->be_len)) {
80 			log_debug("%s: invalid syntax", ATTR_NAME(at));
81 			log_debug("syntax = %s", at->syntax->desc);
82 			log_debug("value: [%.*s]", elm->be_len, val);
83 			return LDAP_INVALID_SYNTAX;
84 		}
85 	}
86 
87 	/* There must be at least one value in an attribute. */
88 	if (nvals == 0) {
89 		log_debug("missing value in attribute %s", ATTR_NAME(at));
90 		return LDAP_CONSTRAINT_VIOLATION;
91 	}
92 
93 	/* FIXME: validate that values are unique */
94 
95 	return LDAP_SUCCESS;
96 }
97 
98 static const char *
99 attribute_equality(struct attr_type *at)
100 {
101 	if (at == NULL)
102 		return NULL;
103 	if (at->equality != NULL)
104 		return at->equality;
105 	return attribute_equality(at->sup);
106 }
107 
108 /* FIXME: doesn't handle escaped characters.
109  */
110 static int
111 validate_dn(const char *dn, struct ber_element *entry)
112 {
113 	char			*copy;
114 	char			*sup_dn, *na, *dv, *p;
115 	struct namespace	*ns;
116 	struct attr_type	*at;
117 	struct ber_element	*vals;
118 
119 	if ((copy = strdup(dn)) == NULL)
120 		return LDAP_OTHER;
121 
122 	sup_dn = strchr(copy, ',');
123 	if (sup_dn++ == NULL)
124 		sup_dn = strrchr(copy, '\0');
125 
126 	/* Validate naming attributes and distinguished values in the RDN.
127 	 */
128 	p = copy;
129 	for (;p < sup_dn;) {
130 		na = p;
131 		p = na + strcspn(na, "=");
132 		if (p == na || p >= sup_dn) {
133 			free(copy);
134 			return LDAP_INVALID_DN_SYNTAX;
135 		}
136 		*p = '\0';
137 		dv = p + 1;
138 		p = dv + strcspn(dv, "+,");
139 		if (p == dv) {
140 			free(copy);
141 			return LDAP_INVALID_DN_SYNTAX;
142 		}
143 		*p++ = '\0';
144 
145 		if ((at = lookup_attribute(conf->schema, na)) == NULL) {
146 			log_debug("attribute %s not defined in schema", na);
147 			goto fail;
148 		}
149 		if (at->usage != USAGE_USER_APP) {
150 			log_debug("naming attribute %s is operational", na);
151 			goto fail;
152 		}
153 		if (at->collective) {
154 			log_debug("naming attribute %s is collective", na);
155 			goto fail;
156 		}
157 		if (at->obsolete) {
158 			log_debug("naming attribute %s is obsolete", na);
159 			goto fail;
160 		}
161 		if (attribute_equality(at) == NULL) {
162 			log_debug("naming attribute %s doesn't define equality",
163 			    na);
164 			goto fail;
165 		}
166 		if ((vals = ldap_find_attribute(entry, at)) == NULL) {
167 			log_debug("missing distinguished value for %s", na);
168 			goto fail;
169 		}
170 		if (ldap_find_value(vals->be_next, dv) == NULL) {
171 			log_debug("missing distinguished value %s"
172 			    " in naming attribute %s", dv, na);
173 			goto fail;
174 		}
175 	}
176 
177 	/* Check that the RDN immediate superior exists, or it is a
178 	 * top-level namespace.
179 	 */
180 	if (*sup_dn != '\0') {
181 		TAILQ_FOREACH(ns, &conf->namespaces, next) {
182 			if (strcmp(dn, ns->suffix) == 0)
183 				goto done;
184 		}
185 		ns = namespace_for_base(sup_dn);
186 		if (ns == NULL || !namespace_exists(ns, sup_dn)) {
187 			free(copy);
188 			return LDAP_NO_SUCH_OBJECT;
189 		}
190 	}
191 
192 done:
193 	free(copy);
194 	return LDAP_SUCCESS;
195 fail:
196 	free(copy);
197 	return LDAP_NAMING_VIOLATION;
198 }
199 
200 static int
201 has_attribute(struct attr_type *at, struct attr_list *alist)
202 {
203 	struct attr_ptr		*ap;
204 
205 	if (alist == NULL)
206 		return 0;
207 
208 	SLIST_FOREACH(ap, alist, next) {
209 		if (at == ap->attr_type)
210 			return 1;
211 	}
212 	return 0;
213 }
214 
215 /* Validate that the attribute type is allowed by any object class.
216  */
217 static int
218 validate_allowed_attribute(struct attr_type *at, struct obj_list *olist)
219 {
220 	struct object		*obj;
221 	struct obj_ptr		*optr;
222 
223 	if (olist == NULL)
224 		return LDAP_OBJECT_CLASS_VIOLATION;
225 
226 	SLIST_FOREACH(optr, olist, next) {
227 		obj = optr->object;
228 
229 		if (has_attribute(at, obj->may) ||
230 		    has_attribute(at, obj->must))
231 			return LDAP_SUCCESS;
232 
233 		if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS)
234 			return LDAP_SUCCESS;
235 	}
236 
237 	return LDAP_OBJECT_CLASS_VIOLATION;
238 }
239 
240 static void
241 olist_push(struct obj_list *olist, struct object *obj)
242 {
243 	struct obj_ptr		*optr, *sup;
244 
245 	SLIST_FOREACH(optr, olist, next)
246 		if (optr->object == obj)
247 			return;
248 
249 	if ((optr = calloc(1, sizeof(*optr))) == NULL)
250 		return;
251 	optr->object = obj;
252 	SLIST_INSERT_HEAD(olist, optr, next);
253 
254 	/* Expand the list of object classes along the superclass chain.
255 	 */
256 	if (obj->sup != NULL)
257 		SLIST_FOREACH(sup, obj->sup, next)
258 			olist_push(olist, sup->object);
259 }
260 
261 static void
262 olist_free(struct obj_list *olist)
263 {
264 	struct obj_ptr		*optr;
265 
266 	if (olist == NULL)
267 		return;
268 
269 	while ((optr = SLIST_FIRST(olist)) != NULL) {
270 		SLIST_REMOVE_HEAD(olist, next);
271 		free(optr);
272 	}
273 
274 	free(olist);
275 }
276 
277 /* Check if sup is a superior object class to obj.
278  */
279 static int
280 is_super(struct object *sup, struct object *obj)
281 {
282 	struct obj_ptr	*optr;
283 
284 	if (sup == NULL || obj->sup == NULL)
285 		return 0;
286 
287 	SLIST_FOREACH(optr, obj->sup, next)
288 		if (optr->object == sup || is_super(sup, optr->object))
289 			return 1;
290 
291 	return 0;
292 }
293 
294 int
295 validate_entry(const char *dn, struct ber_element *entry, int relax)
296 {
297 	int			 rc, extensible = 0;
298 	char			*s;
299 	struct ber_element	*objclass, *a, *vals;
300 	struct object		*obj, *structural_obj = NULL;
301 	struct attr_type	*at;
302 	struct obj_list		*olist = NULL;
303 	struct obj_ptr		*optr, *optr2;
304 
305 	if (relax)
306 		goto rdn;
307 
308 	/* There must be an objectClass attribute.
309 	 */
310 	objclass = ldap_get_attribute(entry, "objectClass");
311 	if (objclass == NULL) {
312 		log_debug("missing objectClass attribute");
313 		return LDAP_OBJECT_CLASS_VIOLATION;
314 	}
315 
316 	if ((olist = calloc(1, sizeof(*olist))) == NULL)
317 		return LDAP_OTHER;
318 	SLIST_INIT(olist);
319 
320 	/* Check objectClass(es) against schema.
321 	 */
322 	objclass = objclass->be_next;		/* skip attribute description */
323 	for (a = objclass->be_sub; a != NULL; a = a->be_next) {
324 		if (ber_get_string(a, &s) != 0) {
325 			rc = LDAP_INVALID_SYNTAX;
326 			goto done;
327 		}
328 
329 		if ((obj = lookup_object(conf->schema, s)) == NULL) {
330 			log_debug("objectClass %s not defined in schema", s);
331 			rc = LDAP_NAMING_VIOLATION;
332 			goto done;
333 		}
334 
335 		if (obj->kind == KIND_STRUCTURAL) {
336 			if (structural_obj != NULL) {
337 				if (is_super(structural_obj, obj))
338 					structural_obj = obj;
339 				else if (!is_super(obj, structural_obj)) {
340 					log_debug("multiple structural"
341 					    " object classes");
342 					rc = LDAP_OBJECT_CLASS_VIOLATION;
343 					goto done;
344 				}
345 			} else
346 				structural_obj = obj;
347 		}
348 
349 		olist_push(olist, obj);
350 
351                 /* RFC4512, section 4.3:
352 		 * "The 'extensibleObject' auxiliary object class allows
353 		 * entries that belong to it to hold any user attribute."
354                  */
355                 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
356                         extensible = 1;
357         }
358 
359 	/* Must have exactly one structural object class.
360 	 */
361 	if (structural_obj == NULL) {
362 		log_debug("no structural object class defined");
363 		rc = LDAP_OBJECT_CLASS_VIOLATION;
364 		goto done;
365 	}
366 
367 	/* "An entry cannot belong to an abstract object class
368 	 *  unless it belongs to a structural or auxiliary class that
369 	 *  inherits from that abstract class."
370 	 */
371         SLIST_FOREACH(optr, olist, next) {
372 		if (optr->object->kind != KIND_ABSTRACT)
373 			continue;
374 
375 		/* Check the structural object class. */
376 		if (is_super(optr->object, structural_obj))
377 			continue;
378 
379 		/* Check all auxiliary object classes. */
380 		SLIST_FOREACH(optr2, olist, next) {
381 			if (optr2->object->kind != KIND_AUXILIARY)
382 				continue;
383 			if (is_super(optr->object, optr2->object))
384 				break;
385 		}
386 
387 		if (optr2 == NULL) {
388 			/* No subclassed object class found. */
389 			log_debug("abstract class '%s' not subclassed",
390 			    OBJ_NAME(optr->object));
391 			rc = LDAP_OBJECT_CLASS_VIOLATION;
392 			goto done;
393 		}
394 	}
395 
396 	/* Check all required attributes.
397 	 */
398 	SLIST_FOREACH(optr, olist, next) {
399 		rc = validate_required_attributes(entry, optr->object);
400 		if (rc != LDAP_SUCCESS)
401 			goto done;
402 	}
403 
404 	/* Check all attributes against schema.
405 	 */
406 	for (a = entry->be_sub; a != NULL; a = a->be_next) {
407 		if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) {
408 			rc = LDAP_INVALID_SYNTAX;
409 			goto done;
410 		}
411 		if ((at = lookup_attribute(conf->schema, s)) == NULL) {
412 			log_debug("attribute %s not defined in schema", s);
413 			rc = LDAP_NAMING_VIOLATION;
414 			goto done;
415 		}
416 		if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
417 			goto done;
418 		if (!extensible && at->usage == USAGE_USER_APP &&
419 		    (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) {
420 			log_debug("%s not allowed by any object class",
421 			    ATTR_NAME(at));
422 			goto done;
423 		}
424 	}
425 
426 rdn:
427 	rc = validate_dn(dn, entry);
428 
429 done:
430 	olist_free(olist);
431 	return rc;
432 }
433 
434