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