xref: /openbsd-src/usr.sbin/ldapd/validate.c (revision a2a433637a48b3f957c19d9c3f4e8cca08dba220)
1 /*	$OpenBSD: validate.c,v 1.2 2010/06/29 02:45:46 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 #define OBJ_NAME(obj)	 ((obj)->names ? SLIST_FIRST((obj)->names)->name : \
28 				(obj)->oid)
29 #define ATTR_NAME(at)	 OBJ_NAME(at)
30 
31 static int
32 validate_required_attributes(struct ber_element *entry, struct object *obj)
33 {
34 	struct attr_ptr		*ap;
35 	struct attr_type	*at;
36 
37 	log_debug("validating required attributes for object %s",
38 	    OBJ_NAME(obj));
39 
40 	if (obj->must == NULL)
41 		return LDAP_SUCCESS;
42 
43 	SLIST_FOREACH(ap, obj->must, next) {
44 		at = ap->attr_type;
45 
46 		if (ldap_find_attribute(entry, at) == NULL) {
47 			log_debug("missing required attribute %s",
48 			    ATTR_NAME(at));
49 			return LDAP_OBJECT_CLASS_VIOLATION;
50 		}
51 	}
52 
53 	return LDAP_SUCCESS;
54 }
55 
56 static int
57 validate_attribute(struct attr_type *at, struct ber_element *vals)
58 {
59 	int			 nvals = 0;
60 	struct ber_element	*elm;
61 
62 	if (vals == NULL) {
63 		log_debug("missing values");
64 		return LDAP_OTHER;
65 	}
66 
67 	if (vals->be_type != BER_TYPE_SET) {
68 		log_debug("values should be a set");
69 		return LDAP_OTHER;
70 	}
71 
72 	for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) {
73 		if (elm->be_type != BER_TYPE_OCTETSTRING) {
74 			log_debug("attribute value not an octet-string");
75 			return LDAP_PROTOCOL_ERROR;
76 		}
77 
78 		if (++nvals > 1 && at->single) {
79 			log_debug("multiple values for single-valued"
80 			    " attribute %s", ATTR_NAME(at));
81 			return LDAP_CONSTRAINT_VIOLATION;
82 		}
83 	}
84 
85 	/* There must be at least one value in an attribute. */
86 	if (nvals == 0) {
87 		log_debug("missing value in attribute %s", ATTR_NAME(at));
88 		return LDAP_CONSTRAINT_VIOLATION;
89 	}
90 
91 	return LDAP_SUCCESS;
92 }
93 
94 static const char *
95 attribute_equality(struct attr_type *at)
96 {
97 	if (at == NULL)
98 		return NULL;
99 	if (at->equality != NULL)
100 		return at->equality;
101 	return attribute_equality(at->sup);
102 }
103 
104 /* FIXME: doesn't handle escaped characters.
105  */
106 static int
107 validate_dn(const char *dn, struct ber_element *entry)
108 {
109 	char			*copy;
110 	char			*sup_dn, *na, *dv, *p;
111 	struct namespace	*ns;
112 	struct attr_type	*at;
113 	struct ber_element	*vals;
114 
115 	if ((copy = strdup(dn)) == NULL)
116 		return LDAP_OTHER;
117 
118 	sup_dn = strchr(copy, ',');
119 	if (sup_dn++ == NULL)
120 		sup_dn = strrchr(copy, '\0');
121 
122 	/* Validate naming attributes and distinguished values in the RDN.
123 	 */
124 	p = copy;
125 	for (;p < sup_dn;) {
126 		na = p;
127 		p = na + strcspn(na, "=");
128 		if (p == na || p >= sup_dn) {
129 			free(copy);
130 			return LDAP_INVALID_DN_SYNTAX;
131 		}
132 		*p = '\0';
133 		dv = p + 1;
134 		p = dv + strcspn(dv, "+,");
135 		if (p == dv) {
136 			free(copy);
137 			return LDAP_INVALID_DN_SYNTAX;
138 		}
139 		*p++ = '\0';
140 
141 		log_debug("got naming attribute %s", na);
142 		log_debug("got distinguished value %s", dv);
143 		if ((at = lookup_attribute(conf->schema, na)) == NULL) {
144 			log_debug("attribute %s not defined in schema", na);
145 			goto fail;
146 		}
147 		if (at->usage != USAGE_USER_APP) {
148 			log_debug("naming attribute %s is operational", na);
149 			goto fail;
150 		}
151 		if (at->collective) {
152 			log_debug("naming attribute %s is collective", na);
153 			goto fail;
154 		}
155 		if (at->obsolete) {
156 			log_debug("naming attribute %s is obsolete", na);
157 			goto fail;
158 		}
159 		if (attribute_equality(at) == NULL) {
160 			log_debug("naming attribute %s doesn't define equality",
161 			    na);
162 			goto fail;
163 		}
164 		if ((vals = ldap_find_attribute(entry, at)) == NULL) {
165 			log_debug("missing distinguished value for %s", na);
166 			goto fail;
167 		}
168 		if (ldap_find_value(vals->be_next, dv) == NULL) {
169 			log_debug("missing distinguished value %s"
170 			    " in naming attribute %s", dv, na);
171 			goto fail;
172 		}
173 	}
174 
175 	/* Check that the RDN immediate superior exists, or it is a
176 	 * top-level namespace.
177 	 */
178 	if (*sup_dn != '\0') {
179 		TAILQ_FOREACH(ns, &conf->namespaces, next) {
180 			if (strcmp(dn, ns->suffix) == 0)
181 				goto done;
182 		}
183 		log_debug("checking for presence of superior dn %s", sup_dn);
184 		ns = namespace_for_base(sup_dn);
185 		if (ns == NULL || !namespace_exists(ns, sup_dn)) {
186 			free(copy);
187 			return LDAP_NO_SUCH_OBJECT;
188 		}
189 	}
190 
191 done:
192 	free(copy);
193 	return LDAP_SUCCESS;
194 fail:
195 	free(copy);
196 	return LDAP_NAMING_VIOLATION;
197 }
198 
199 static int
200 validate_object_class(struct ber_element *entry, struct object *obj)
201 {
202 	struct obj_ptr		*sup;
203 	int			 rc;
204 
205 	rc = validate_required_attributes(entry, obj);
206 	if (rc == LDAP_SUCCESS && obj->sup != NULL) {
207 		SLIST_FOREACH(sup, obj->sup, next) {
208 			rc = validate_object_class(entry, sup->object);
209 			if (rc != LDAP_SUCCESS)
210 				break;
211 		}
212 	}
213 
214 	return rc;
215 }
216 
217 int
218 validate_entry(const char *dn, struct ber_element *entry, int relax)
219 {
220 	int			 rc;
221 	char			*s;
222 	struct ber_element	*objclass, *a, *vals;
223 	struct object		*obj, *structural_obj = NULL;
224 	struct attr_type	*at;
225 
226 	if (relax)
227 		goto rdn;
228 
229 	/* There must be an objectClass attribute.
230 	 */
231 	objclass = ldap_get_attribute(entry, "objectClass");
232 	if (objclass == NULL) {
233 		log_debug("missing objectClass attribute");
234 		return LDAP_OBJECT_CLASS_VIOLATION;
235 	}
236 
237 	/* Check objectClass(es) against schema.
238 	 */
239 	objclass = objclass->be_next;		/* skip attribute description */
240 	for (a = objclass->be_sub; a != NULL; a = a->be_next) {
241 		if (ber_get_string(a, &s) != 0)
242 			return LDAP_INVALID_SYNTAX;
243 		if ((obj = lookup_object(conf->schema, s)) == NULL) {
244 			log_debug("objectClass %s not defined in schema", s);
245 			return LDAP_NAMING_VIOLATION;
246 		}
247 		log_debug("object class %s has kind %d", s, obj->kind);
248 		if (obj->kind == KIND_STRUCTURAL)
249 			structural_obj = obj;
250 
251 		rc = validate_object_class(entry, obj);
252 		if (rc != LDAP_SUCCESS)
253 			return rc;
254 	}
255 
256 	/* Must have at least one structural object class.
257 	 */
258 	if (structural_obj == NULL) {
259 		log_debug("no structural object class defined");
260 		return LDAP_OBJECT_CLASS_VIOLATION;
261 	}
262 
263 	/* Check all attributes against schema.
264 	 */
265 	for (a = entry->be_sub; a != NULL; a = a->be_next) {
266 		if (ber_scanf_elements(a, "{se{", &s, &vals) != 0)
267 			return LDAP_INVALID_SYNTAX;
268 		if ((at = lookup_attribute(conf->schema, s)) == NULL) {
269 			log_debug("attribute %s not defined in schema", s);
270 			return LDAP_NAMING_VIOLATION;
271 		}
272 		if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
273 			return rc;
274 	}
275 
276 rdn:
277 	if ((rc = validate_dn(dn, entry)) != LDAP_SUCCESS)
278 		return rc;
279 
280 	return LDAP_SUCCESS;
281 }
282 
283