xref: /openbsd/usr.sbin/ldapd/validate.c (revision d415bd75)
1 /*	$OpenBSD: validate.c,v 1.13 2021/12/20 13:18:29 claudio 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 #include "log.h"
27 
28 static int
29 validate_required_attributes(struct ber_element *entry, struct object *obj)
30 {
31 	struct attr_ptr		*ap;
32 	struct attr_type	*at;
33 
34 	if (obj->must == NULL)
35 		return LDAP_SUCCESS;
36 
37 	SLIST_FOREACH(ap, obj->must, next) {
38 		at = ap->attr_type;
39 
40 		if (ldap_find_attribute(entry, at) == NULL) {
41 			log_debug("missing required attribute %s",
42 			    ATTR_NAME(at));
43 			return LDAP_OBJECT_CLASS_VIOLATION;
44 		}
45 	}
46 
47 	return LDAP_SUCCESS;
48 }
49 
50 static int
51 validate_attribute(struct attr_type *at, struct ber_element *vals)
52 {
53 	int			 nvals = 0;
54 	struct ber_element	*elm;
55 	char			*val;
56 
57 	if (vals == NULL) {
58 		log_debug("missing values");
59 		return LDAP_OTHER;
60 	}
61 
62 	if (vals->be_type != BER_TYPE_SET) {
63 		log_debug("values should be a set");
64 		return LDAP_OTHER;
65 	}
66 
67 	for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) {
68 		if (ober_get_string(elm, &val) == -1) {
69 			log_debug("attribute value not an octet-string");
70 			return LDAP_PROTOCOL_ERROR;
71 		}
72 
73 		if (++nvals > 1 && at->single) {
74 			log_debug("multiple values for single-valued"
75 			    " attribute %s", ATTR_NAME(at));
76 			return LDAP_CONSTRAINT_VIOLATION;
77 		}
78 
79 		if (at->syntax->is_valid != NULL &&
80 		    !at->syntax->is_valid(conf->schema, val, elm->be_len)) {
81 			log_debug("%s: invalid syntax", ATTR_NAME(at));
82 			log_debug("syntax = %s", at->syntax->desc);
83 			log_debug("value: [%.*s]", (int)elm->be_len, val);
84 			return LDAP_INVALID_SYNTAX;
85 		}
86 	}
87 
88 	/* There must be at least one value in an attribute. */
89 	if (nvals == 0) {
90 		log_debug("missing value in attribute %s", ATTR_NAME(at));
91 		return LDAP_CONSTRAINT_VIOLATION;
92 	}
93 
94 	/* FIXME: validate that values are unique */
95 
96 	return LDAP_SUCCESS;
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 (at->equality == 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 static void
253 olist_free(struct obj_list *olist)
254 {
255 	struct obj_ptr		*optr;
256 
257 	if (olist == NULL)
258 		return;
259 
260 	while ((optr = SLIST_FIRST(olist)) != NULL) {
261 		SLIST_REMOVE_HEAD(olist, next);
262 		free(optr);
263 	}
264 
265 	free(olist);
266 }
267 
268 /* Check if sup is a superior object class to obj.
269  */
270 static int
271 is_super(struct object *sup, struct object *obj)
272 {
273 	struct obj_ptr	*optr;
274 
275 	if (sup == NULL || obj->sup == NULL)
276 		return 0;
277 
278 	SLIST_FOREACH(optr, obj->sup, next)
279 		if (optr->object == sup || is_super(sup, optr->object))
280 			return 1;
281 
282 	return 0;
283 }
284 
285 int
286 validate_entry(const char *dn, struct ber_element *entry, int relax)
287 {
288 	int			 rc, extensible = 0;
289 	char			*s;
290 	struct ber_element	*objclass, *a, *vals;
291 	struct object		*obj, *structural_obj = NULL;
292 	struct attr_type	*at;
293 	struct obj_list		*olist = NULL;
294 	struct obj_ptr		*optr, *optr2;
295 
296 	if (relax)
297 		goto rdn;
298 
299 	/* There must be an objectClass attribute.
300 	 */
301 	objclass = ldap_get_attribute(entry, "objectClass");
302 	if (objclass == NULL) {
303 		log_debug("missing objectClass attribute");
304 		return LDAP_OBJECT_CLASS_VIOLATION;
305 	}
306 
307 	if ((olist = calloc(1, sizeof(*olist))) == NULL)
308 		return LDAP_OTHER;
309 	SLIST_INIT(olist);
310 
311 	/* Check objectClass(es) against schema.
312 	 */
313 	objclass = objclass->be_next;		/* skip attribute description */
314 	for (a = objclass->be_sub; a != NULL; a = a->be_next) {
315 		if (ober_get_string(a, &s) != 0) {
316 			log_debug("invalid ObjectClass encoding");
317 			rc = LDAP_INVALID_SYNTAX;
318 			goto done;
319 		}
320 
321 		if ((obj = lookup_object(conf->schema, s)) == NULL) {
322 			log_debug("objectClass %s not defined in schema", s);
323 			rc = LDAP_NAMING_VIOLATION;
324 			goto done;
325 		}
326 
327 		if (obj->kind == KIND_STRUCTURAL) {
328 			if (structural_obj != NULL) {
329 				if (is_super(structural_obj, obj))
330 					structural_obj = obj;
331 				else if (!is_super(obj, structural_obj)) {
332 					log_debug("multiple structural"
333 					    " object classes");
334 					rc = LDAP_OBJECT_CLASS_VIOLATION;
335 					goto done;
336 				}
337 			} else
338 				structural_obj = obj;
339 		}
340 
341 		olist_push(olist, obj);
342 
343                 /* RFC4512, section 4.3:
344 		 * "The 'extensibleObject' auxiliary object class allows
345 		 * entries that belong to it to hold any user attribute."
346                  */
347                 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
348                         extensible = 1;
349         }
350 
351 	/* Must have exactly one structural object class.
352 	 */
353 	if (structural_obj == NULL) {
354 		log_debug("no structural object class defined");
355 		rc = LDAP_OBJECT_CLASS_VIOLATION;
356 		goto done;
357 	}
358 
359 	/* "An entry cannot belong to an abstract object class
360 	 *  unless it belongs to a structural or auxiliary class that
361 	 *  inherits from that abstract class."
362 	 */
363         SLIST_FOREACH(optr, olist, next) {
364 		if (optr->object->kind != KIND_ABSTRACT)
365 			continue;
366 
367 		/* Check the structural object class. */
368 		if (is_super(optr->object, structural_obj))
369 			continue;
370 
371 		/* Check all auxiliary object classes. */
372 		SLIST_FOREACH(optr2, olist, next) {
373 			if (optr2->object->kind != KIND_AUXILIARY)
374 				continue;
375 			if (is_super(optr->object, optr2->object))
376 				break;
377 		}
378 
379 		if (optr2 == NULL) {
380 			/* No subclassed object class found. */
381 			log_debug("abstract class '%s' not subclassed",
382 			    OBJ_NAME(optr->object));
383 			rc = LDAP_OBJECT_CLASS_VIOLATION;
384 			goto done;
385 		}
386 	}
387 
388 	/* Check all required attributes.
389 	 */
390 	SLIST_FOREACH(optr, olist, next) {
391 		rc = validate_required_attributes(entry, optr->object);
392 		if (rc != LDAP_SUCCESS)
393 			goto done;
394 	}
395 
396 	/* Check all attributes against schema.
397 	 */
398 	for (a = entry->be_sub; a != NULL; a = a->be_next) {
399 		if (ober_scanf_elements(a, "{se{", &s, &vals) != 0) {
400 			log_debug("invalid attribute encoding");
401 			rc = LDAP_INVALID_SYNTAX;
402 			goto done;
403 		}
404 		if ((at = lookup_attribute(conf->schema, s)) == NULL) {
405 			log_debug("attribute %s not defined in schema", s);
406 			rc = LDAP_NAMING_VIOLATION;
407 			goto done;
408 		}
409 		if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
410 			goto done;
411 		if (!extensible && at->usage == USAGE_USER_APP &&
412 		    (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) {
413 			log_debug("%s not allowed by any object class",
414 			    ATTR_NAME(at));
415 			goto done;
416 		}
417 	}
418 
419 rdn:
420 	rc = validate_dn(dn, entry);
421 
422 done:
423 	olist_free(olist);
424 	return rc;
425 }
426 
427