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
validate_required_attributes(struct ber_element * entry,struct object * obj)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
validate_attribute(struct attr_type * at,struct ber_element * vals)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
validate_dn(const char * dn,struct ber_element * entry)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
has_attribute(struct attr_type * at,struct attr_list * alist)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
validate_allowed_attribute(struct attr_type * at,struct obj_list * olist)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
olist_push(struct obj_list * olist,struct object * obj)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
olist_free(struct obj_list * olist)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
is_super(struct object * sup,struct object * obj)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
validate_entry(const char * dn,struct ber_element * entry,int relax)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