GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/ldapd/validate.c Lines: 0 179 0.0 %
Date: 2017-11-13 Branches: 0 168 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: validate.c,v 1.10 2017/01/20 11:55:08 benno 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 (ber_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]", 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 (ber_get_string(a, &s) != 0) {
316
			rc = LDAP_INVALID_SYNTAX;
317
			goto done;
318
		}
319
320
		if ((obj = lookup_object(conf->schema, s)) == NULL) {
321
			log_debug("objectClass %s not defined in schema", s);
322
			rc = LDAP_NAMING_VIOLATION;
323
			goto done;
324
		}
325
326
		if (obj->kind == KIND_STRUCTURAL) {
327
			if (structural_obj != NULL) {
328
				if (is_super(structural_obj, obj))
329
					structural_obj = obj;
330
				else if (!is_super(obj, structural_obj)) {
331
					log_debug("multiple structural"
332
					    " object classes");
333
					rc = LDAP_OBJECT_CLASS_VIOLATION;
334
					goto done;
335
				}
336
			} else
337
				structural_obj = obj;
338
		}
339
340
		olist_push(olist, obj);
341
342
                /* RFC4512, section 4.3:
343
		 * "The 'extensibleObject' auxiliary object class allows
344
		 * entries that belong to it to hold any user attribute."
345
                 */
346
                if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
347
                        extensible = 1;
348
        }
349
350
	/* Must have exactly one structural object class.
351
	 */
352
	if (structural_obj == NULL) {
353
		log_debug("no structural object class defined");
354
		rc = LDAP_OBJECT_CLASS_VIOLATION;
355
		goto done;
356
	}
357
358
	/* "An entry cannot belong to an abstract object class
359
	 *  unless it belongs to a structural or auxiliary class that
360
	 *  inherits from that abstract class."
361
	 */
362
        SLIST_FOREACH(optr, olist, next) {
363
		if (optr->object->kind != KIND_ABSTRACT)
364
			continue;
365
366
		/* Check the structural object class. */
367
		if (is_super(optr->object, structural_obj))
368
			continue;
369
370
		/* Check all auxiliary object classes. */
371
		SLIST_FOREACH(optr2, olist, next) {
372
			if (optr2->object->kind != KIND_AUXILIARY)
373
				continue;
374
			if (is_super(optr->object, optr2->object))
375
				break;
376
		}
377
378
		if (optr2 == NULL) {
379
			/* No subclassed object class found. */
380
			log_debug("abstract class '%s' not subclassed",
381
			    OBJ_NAME(optr->object));
382
			rc = LDAP_OBJECT_CLASS_VIOLATION;
383
			goto done;
384
		}
385
	}
386
387
	/* Check all required attributes.
388
	 */
389
	SLIST_FOREACH(optr, olist, next) {
390
		rc = validate_required_attributes(entry, optr->object);
391
		if (rc != LDAP_SUCCESS)
392
			goto done;
393
	}
394
395
	/* Check all attributes against schema.
396
	 */
397
	for (a = entry->be_sub; a != NULL; a = a->be_next) {
398
		if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) {
399
			rc = LDAP_INVALID_SYNTAX;
400
			goto done;
401
		}
402
		if ((at = lookup_attribute(conf->schema, s)) == NULL) {
403
			log_debug("attribute %s not defined in schema", s);
404
			rc = LDAP_NAMING_VIOLATION;
405
			goto done;
406
		}
407
		if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
408
			goto done;
409
		if (!extensible && at->usage == USAGE_USER_APP &&
410
		    (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) {
411
			log_debug("%s not allowed by any object class",
412
			    ATTR_NAME(at));
413
			goto done;
414
		}
415
	}
416
417
rdn:
418
	rc = validate_dn(dn, entry);
419
420
done:
421
	olist_free(olist);
422
	return rc;
423
}
424