xref: /openbsd-src/usr.sbin/ldapd/validate.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: validate.c,v 1.9 2010/09/03 09:53:24 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 /* FIXME: doesn't handle escaped characters.
99  */
100 static int
101 validate_dn(const char *dn, struct ber_element *entry)
102 {
103 	char			*copy;
104 	char			*sup_dn, *na, *dv, *p;
105 	struct namespace	*ns;
106 	struct attr_type	*at;
107 	struct ber_element	*vals;
108 
109 	if ((copy = strdup(dn)) == NULL)
110 		return LDAP_OTHER;
111 
112 	sup_dn = strchr(copy, ',');
113 	if (sup_dn++ == NULL)
114 		sup_dn = strrchr(copy, '\0');
115 
116 	/* Validate naming attributes and distinguished values in the RDN.
117 	 */
118 	p = copy;
119 	for (;p < sup_dn;) {
120 		na = p;
121 		p = na + strcspn(na, "=");
122 		if (p == na || p >= sup_dn) {
123 			free(copy);
124 			return LDAP_INVALID_DN_SYNTAX;
125 		}
126 		*p = '\0';
127 		dv = p + 1;
128 		p = dv + strcspn(dv, "+,");
129 		if (p == dv) {
130 			free(copy);
131 			return LDAP_INVALID_DN_SYNTAX;
132 		}
133 		*p++ = '\0';
134 
135 		if ((at = lookup_attribute(conf->schema, na)) == NULL) {
136 			log_debug("attribute %s not defined in schema", na);
137 			goto fail;
138 		}
139 		if (at->usage != USAGE_USER_APP) {
140 			log_debug("naming attribute %s is operational", na);
141 			goto fail;
142 		}
143 		if (at->collective) {
144 			log_debug("naming attribute %s is collective", na);
145 			goto fail;
146 		}
147 		if (at->obsolete) {
148 			log_debug("naming attribute %s is obsolete", na);
149 			goto fail;
150 		}
151 		if (at->equality == NULL) {
152 			log_debug("naming attribute %s doesn't define equality",
153 			    na);
154 			goto fail;
155 		}
156 		if ((vals = ldap_find_attribute(entry, at)) == NULL) {
157 			log_debug("missing distinguished value for %s", na);
158 			goto fail;
159 		}
160 		if (ldap_find_value(vals->be_next, dv) == NULL) {
161 			log_debug("missing distinguished value %s"
162 			    " in naming attribute %s", dv, na);
163 			goto fail;
164 		}
165 	}
166 
167 	/* Check that the RDN immediate superior exists, or it is a
168 	 * top-level namespace.
169 	 */
170 	if (*sup_dn != '\0') {
171 		TAILQ_FOREACH(ns, &conf->namespaces, next) {
172 			if (strcmp(dn, ns->suffix) == 0)
173 				goto done;
174 		}
175 		ns = namespace_for_base(sup_dn);
176 		if (ns == NULL || !namespace_exists(ns, sup_dn)) {
177 			free(copy);
178 			return LDAP_NO_SUCH_OBJECT;
179 		}
180 	}
181 
182 done:
183 	free(copy);
184 	return LDAP_SUCCESS;
185 fail:
186 	free(copy);
187 	return LDAP_NAMING_VIOLATION;
188 }
189 
190 static int
191 has_attribute(struct attr_type *at, struct attr_list *alist)
192 {
193 	struct attr_ptr		*ap;
194 
195 	if (alist == NULL)
196 		return 0;
197 
198 	SLIST_FOREACH(ap, alist, next) {
199 		if (at == ap->attr_type)
200 			return 1;
201 	}
202 	return 0;
203 }
204 
205 /* Validate that the attribute type is allowed by any object class.
206  */
207 static int
208 validate_allowed_attribute(struct attr_type *at, struct obj_list *olist)
209 {
210 	struct object		*obj;
211 	struct obj_ptr		*optr;
212 
213 	if (olist == NULL)
214 		return LDAP_OBJECT_CLASS_VIOLATION;
215 
216 	SLIST_FOREACH(optr, olist, next) {
217 		obj = optr->object;
218 
219 		if (has_attribute(at, obj->may) ||
220 		    has_attribute(at, obj->must))
221 			return LDAP_SUCCESS;
222 
223 		if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS)
224 			return LDAP_SUCCESS;
225 	}
226 
227 	return LDAP_OBJECT_CLASS_VIOLATION;
228 }
229 
230 static void
231 olist_push(struct obj_list *olist, struct object *obj)
232 {
233 	struct obj_ptr		*optr, *sup;
234 
235 	SLIST_FOREACH(optr, olist, next)
236 		if (optr->object == obj)
237 			return;
238 
239 	if ((optr = calloc(1, sizeof(*optr))) == NULL)
240 		return;
241 	optr->object = obj;
242 	SLIST_INSERT_HEAD(olist, optr, next);
243 
244 	/* Expand the list of object classes along the superclass chain.
245 	 */
246 	if (obj->sup != NULL)
247 		SLIST_FOREACH(sup, obj->sup, next)
248 			olist_push(olist, sup->object);
249 }
250 
251 static void
252 olist_free(struct obj_list *olist)
253 {
254 	struct obj_ptr		*optr;
255 
256 	if (olist == NULL)
257 		return;
258 
259 	while ((optr = SLIST_FIRST(olist)) != NULL) {
260 		SLIST_REMOVE_HEAD(olist, next);
261 		free(optr);
262 	}
263 
264 	free(olist);
265 }
266 
267 /* Check if sup is a superior object class to obj.
268  */
269 static int
270 is_super(struct object *sup, struct object *obj)
271 {
272 	struct obj_ptr	*optr;
273 
274 	if (sup == NULL || obj->sup == NULL)
275 		return 0;
276 
277 	SLIST_FOREACH(optr, obj->sup, next)
278 		if (optr->object == sup || is_super(sup, optr->object))
279 			return 1;
280 
281 	return 0;
282 }
283 
284 int
285 validate_entry(const char *dn, struct ber_element *entry, int relax)
286 {
287 	int			 rc, extensible = 0;
288 	char			*s;
289 	struct ber_element	*objclass, *a, *vals;
290 	struct object		*obj, *structural_obj = NULL;
291 	struct attr_type	*at;
292 	struct obj_list		*olist = NULL;
293 	struct obj_ptr		*optr, *optr2;
294 
295 	if (relax)
296 		goto rdn;
297 
298 	/* There must be an objectClass attribute.
299 	 */
300 	objclass = ldap_get_attribute(entry, "objectClass");
301 	if (objclass == NULL) {
302 		log_debug("missing objectClass attribute");
303 		return LDAP_OBJECT_CLASS_VIOLATION;
304 	}
305 
306 	if ((olist = calloc(1, sizeof(*olist))) == NULL)
307 		return LDAP_OTHER;
308 	SLIST_INIT(olist);
309 
310 	/* Check objectClass(es) against schema.
311 	 */
312 	objclass = objclass->be_next;		/* skip attribute description */
313 	for (a = objclass->be_sub; a != NULL; a = a->be_next) {
314 		if (ber_get_string(a, &s) != 0) {
315 			rc = LDAP_INVALID_SYNTAX;
316 			goto done;
317 		}
318 
319 		if ((obj = lookup_object(conf->schema, s)) == NULL) {
320 			log_debug("objectClass %s not defined in schema", s);
321 			rc = LDAP_NAMING_VIOLATION;
322 			goto done;
323 		}
324 
325 		if (obj->kind == KIND_STRUCTURAL) {
326 			if (structural_obj != NULL) {
327 				if (is_super(structural_obj, obj))
328 					structural_obj = obj;
329 				else if (!is_super(obj, structural_obj)) {
330 					log_debug("multiple structural"
331 					    " object classes");
332 					rc = LDAP_OBJECT_CLASS_VIOLATION;
333 					goto done;
334 				}
335 			} else
336 				structural_obj = obj;
337 		}
338 
339 		olist_push(olist, obj);
340 
341                 /* RFC4512, section 4.3:
342 		 * "The 'extensibleObject' auxiliary object class allows
343 		 * entries that belong to it to hold any user attribute."
344                  */
345                 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
346                         extensible = 1;
347         }
348 
349 	/* Must have exactly one structural object class.
350 	 */
351 	if (structural_obj == NULL) {
352 		log_debug("no structural object class defined");
353 		rc = LDAP_OBJECT_CLASS_VIOLATION;
354 		goto done;
355 	}
356 
357 	/* "An entry cannot belong to an abstract object class
358 	 *  unless it belongs to a structural or auxiliary class that
359 	 *  inherits from that abstract class."
360 	 */
361         SLIST_FOREACH(optr, olist, next) {
362 		if (optr->object->kind != KIND_ABSTRACT)
363 			continue;
364 
365 		/* Check the structural object class. */
366 		if (is_super(optr->object, structural_obj))
367 			continue;
368 
369 		/* Check all auxiliary object classes. */
370 		SLIST_FOREACH(optr2, olist, next) {
371 			if (optr2->object->kind != KIND_AUXILIARY)
372 				continue;
373 			if (is_super(optr->object, optr2->object))
374 				break;
375 		}
376 
377 		if (optr2 == NULL) {
378 			/* No subclassed object class found. */
379 			log_debug("abstract class '%s' not subclassed",
380 			    OBJ_NAME(optr->object));
381 			rc = LDAP_OBJECT_CLASS_VIOLATION;
382 			goto done;
383 		}
384 	}
385 
386 	/* Check all required attributes.
387 	 */
388 	SLIST_FOREACH(optr, olist, next) {
389 		rc = validate_required_attributes(entry, optr->object);
390 		if (rc != LDAP_SUCCESS)
391 			goto done;
392 	}
393 
394 	/* Check all attributes against schema.
395 	 */
396 	for (a = entry->be_sub; a != NULL; a = a->be_next) {
397 		if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) {
398 			rc = LDAP_INVALID_SYNTAX;
399 			goto done;
400 		}
401 		if ((at = lookup_attribute(conf->schema, s)) == NULL) {
402 			log_debug("attribute %s not defined in schema", s);
403 			rc = LDAP_NAMING_VIOLATION;
404 			goto done;
405 		}
406 		if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
407 			goto done;
408 		if (!extensible && at->usage == USAGE_USER_APP &&
409 		    (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) {
410 			log_debug("%s not allowed by any object class",
411 			    ATTR_NAME(at));
412 			goto done;
413 		}
414 	}
415 
416 rdn:
417 	rc = validate_dn(dn, entry);
418 
419 done:
420 	olist_free(olist);
421 	return rc;
422 }
423 
424