1*684a6a8cSclaudio /* $OpenBSD: validate.c,v 1.13 2021/12/20 13:18:29 claudio Exp $ */
25d465952Smartinh
35d465952Smartinh /*
45d465952Smartinh * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se>
55d465952Smartinh *
65d465952Smartinh * Permission to use, copy, modify, and distribute this software for any
75d465952Smartinh * purpose with or without fee is hereby granted, provided that the above
85d465952Smartinh * copyright notice and this permission notice appear in all copies.
95d465952Smartinh *
105d465952Smartinh * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
115d465952Smartinh * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
125d465952Smartinh * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
135d465952Smartinh * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
145d465952Smartinh * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
155d465952Smartinh * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
165d465952Smartinh * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
175d465952Smartinh */
185d465952Smartinh
195d465952Smartinh #include <sys/types.h>
205d465952Smartinh #include <sys/queue.h>
215d465952Smartinh
225d465952Smartinh #include <stdlib.h>
235d465952Smartinh #include <string.h>
245d465952Smartinh
255d465952Smartinh #include "ldapd.h"
26fdd30f56Sbenno #include "log.h"
275d465952Smartinh
285d465952Smartinh static int
validate_required_attributes(struct ber_element * entry,struct object * obj)295d465952Smartinh validate_required_attributes(struct ber_element *entry, struct object *obj)
305d465952Smartinh {
315d465952Smartinh struct attr_ptr *ap;
325d465952Smartinh struct attr_type *at;
335d465952Smartinh
345d465952Smartinh if (obj->must == NULL)
355d465952Smartinh return LDAP_SUCCESS;
365d465952Smartinh
375d465952Smartinh SLIST_FOREACH(ap, obj->must, next) {
385d465952Smartinh at = ap->attr_type;
395d465952Smartinh
405d465952Smartinh if (ldap_find_attribute(entry, at) == NULL) {
415d465952Smartinh log_debug("missing required attribute %s",
425d465952Smartinh ATTR_NAME(at));
435d465952Smartinh return LDAP_OBJECT_CLASS_VIOLATION;
445d465952Smartinh }
455d465952Smartinh }
465d465952Smartinh
475d465952Smartinh return LDAP_SUCCESS;
485d465952Smartinh }
495d465952Smartinh
505d465952Smartinh static int
validate_attribute(struct attr_type * at,struct ber_element * vals)515d465952Smartinh validate_attribute(struct attr_type *at, struct ber_element *vals)
525d465952Smartinh {
535d465952Smartinh int nvals = 0;
545d465952Smartinh struct ber_element *elm;
557c686fcdSmartinh char *val;
565d465952Smartinh
575d465952Smartinh if (vals == NULL) {
585d465952Smartinh log_debug("missing values");
595d465952Smartinh return LDAP_OTHER;
605d465952Smartinh }
615d465952Smartinh
625d465952Smartinh if (vals->be_type != BER_TYPE_SET) {
635d465952Smartinh log_debug("values should be a set");
645d465952Smartinh return LDAP_OTHER;
655d465952Smartinh }
665d465952Smartinh
675d465952Smartinh for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) {
68696b5899Stb if (ober_get_string(elm, &val) == -1) {
695d465952Smartinh log_debug("attribute value not an octet-string");
705d465952Smartinh return LDAP_PROTOCOL_ERROR;
715d465952Smartinh }
725d465952Smartinh
735d465952Smartinh if (++nvals > 1 && at->single) {
745d465952Smartinh log_debug("multiple values for single-valued"
755d465952Smartinh " attribute %s", ATTR_NAME(at));
765d465952Smartinh return LDAP_CONSTRAINT_VIOLATION;
775d465952Smartinh }
787c686fcdSmartinh
797c686fcdSmartinh if (at->syntax->is_valid != NULL &&
807c686fcdSmartinh !at->syntax->is_valid(conf->schema, val, elm->be_len)) {
817c686fcdSmartinh log_debug("%s: invalid syntax", ATTR_NAME(at));
827c686fcdSmartinh log_debug("syntax = %s", at->syntax->desc);
83c0785a05Sreyk log_debug("value: [%.*s]", (int)elm->be_len, val);
847c686fcdSmartinh return LDAP_INVALID_SYNTAX;
857c686fcdSmartinh }
865d465952Smartinh }
875d465952Smartinh
885d465952Smartinh /* There must be at least one value in an attribute. */
895d465952Smartinh if (nvals == 0) {
905d465952Smartinh log_debug("missing value in attribute %s", ATTR_NAME(at));
915d465952Smartinh return LDAP_CONSTRAINT_VIOLATION;
925d465952Smartinh }
935d465952Smartinh
94561be2e9Smartinh /* FIXME: validate that values are unique */
95561be2e9Smartinh
965d465952Smartinh return LDAP_SUCCESS;
975d465952Smartinh }
985d465952Smartinh
995d465952Smartinh /* FIXME: doesn't handle escaped characters.
1005d465952Smartinh */
1015d465952Smartinh static int
validate_dn(const char * dn,struct ber_element * entry)1025d465952Smartinh validate_dn(const char *dn, struct ber_element *entry)
1035d465952Smartinh {
1045d465952Smartinh char *copy;
1055d465952Smartinh char *sup_dn, *na, *dv, *p;
1065d465952Smartinh struct namespace *ns;
1075d465952Smartinh struct attr_type *at;
1085d465952Smartinh struct ber_element *vals;
1095d465952Smartinh
1105d465952Smartinh if ((copy = strdup(dn)) == NULL)
1115d465952Smartinh return LDAP_OTHER;
1125d465952Smartinh
1135d465952Smartinh sup_dn = strchr(copy, ',');
1145d465952Smartinh if (sup_dn++ == NULL)
1155d465952Smartinh sup_dn = strrchr(copy, '\0');
1165d465952Smartinh
1175d465952Smartinh /* Validate naming attributes and distinguished values in the RDN.
1185d465952Smartinh */
1195d465952Smartinh p = copy;
1205d465952Smartinh for (;p < sup_dn;) {
1215d465952Smartinh na = p;
1225d465952Smartinh p = na + strcspn(na, "=");
1235d465952Smartinh if (p == na || p >= sup_dn) {
1245d465952Smartinh free(copy);
1255d465952Smartinh return LDAP_INVALID_DN_SYNTAX;
1265d465952Smartinh }
1275d465952Smartinh *p = '\0';
1285d465952Smartinh dv = p + 1;
1295d465952Smartinh p = dv + strcspn(dv, "+,");
1305d465952Smartinh if (p == dv) {
1315d465952Smartinh free(copy);
1325d465952Smartinh return LDAP_INVALID_DN_SYNTAX;
1335d465952Smartinh }
1345d465952Smartinh *p++ = '\0';
1355d465952Smartinh
136a2a43363Smartinh if ((at = lookup_attribute(conf->schema, na)) == NULL) {
1375d465952Smartinh log_debug("attribute %s not defined in schema", na);
1385d465952Smartinh goto fail;
1395d465952Smartinh }
1405d465952Smartinh if (at->usage != USAGE_USER_APP) {
1415d465952Smartinh log_debug("naming attribute %s is operational", na);
1425d465952Smartinh goto fail;
1435d465952Smartinh }
1445d465952Smartinh if (at->collective) {
1455d465952Smartinh log_debug("naming attribute %s is collective", na);
1465d465952Smartinh goto fail;
1475d465952Smartinh }
1485d465952Smartinh if (at->obsolete) {
1495d465952Smartinh log_debug("naming attribute %s is obsolete", na);
1505d465952Smartinh goto fail;
1515d465952Smartinh }
15278a17c8cSmartinh if (at->equality == NULL) {
1535d465952Smartinh log_debug("naming attribute %s doesn't define equality",
1545d465952Smartinh na);
1555d465952Smartinh goto fail;
1565d465952Smartinh }
1575d465952Smartinh if ((vals = ldap_find_attribute(entry, at)) == NULL) {
1585d465952Smartinh log_debug("missing distinguished value for %s", na);
1595d465952Smartinh goto fail;
1605d465952Smartinh }
1615d465952Smartinh if (ldap_find_value(vals->be_next, dv) == NULL) {
1625d465952Smartinh log_debug("missing distinguished value %s"
1635d465952Smartinh " in naming attribute %s", dv, na);
1645d465952Smartinh goto fail;
1655d465952Smartinh }
1665d465952Smartinh }
1675d465952Smartinh
1685d465952Smartinh /* Check that the RDN immediate superior exists, or it is a
1695d465952Smartinh * top-level namespace.
1705d465952Smartinh */
1715d465952Smartinh if (*sup_dn != '\0') {
1725d465952Smartinh TAILQ_FOREACH(ns, &conf->namespaces, next) {
1735d465952Smartinh if (strcmp(dn, ns->suffix) == 0)
1745d465952Smartinh goto done;
1755d465952Smartinh }
1765d465952Smartinh ns = namespace_for_base(sup_dn);
1775d465952Smartinh if (ns == NULL || !namespace_exists(ns, sup_dn)) {
1785d465952Smartinh free(copy);
1795d465952Smartinh return LDAP_NO_SUCH_OBJECT;
1805d465952Smartinh }
1815d465952Smartinh }
1825d465952Smartinh
1835d465952Smartinh done:
1845d465952Smartinh free(copy);
1855d465952Smartinh return LDAP_SUCCESS;
1865d465952Smartinh fail:
1875d465952Smartinh free(copy);
1885d465952Smartinh return LDAP_NAMING_VIOLATION;
1895d465952Smartinh }
1905d465952Smartinh
1915d465952Smartinh static int
has_attribute(struct attr_type * at,struct attr_list * alist)192561be2e9Smartinh has_attribute(struct attr_type *at, struct attr_list *alist)
1935d465952Smartinh {
194561be2e9Smartinh struct attr_ptr *ap;
1955d465952Smartinh
196561be2e9Smartinh if (alist == NULL)
197561be2e9Smartinh return 0;
198561be2e9Smartinh
199561be2e9Smartinh SLIST_FOREACH(ap, alist, next) {
200561be2e9Smartinh if (at == ap->attr_type)
201561be2e9Smartinh return 1;
2025d465952Smartinh }
203561be2e9Smartinh return 0;
2045d465952Smartinh }
2055d465952Smartinh
206561be2e9Smartinh /* Validate that the attribute type is allowed by any object class.
207561be2e9Smartinh */
208561be2e9Smartinh static int
validate_allowed_attribute(struct attr_type * at,struct obj_list * olist)209561be2e9Smartinh validate_allowed_attribute(struct attr_type *at, struct obj_list *olist)
210561be2e9Smartinh {
211561be2e9Smartinh struct object *obj;
212561be2e9Smartinh struct obj_ptr *optr;
213561be2e9Smartinh
214561be2e9Smartinh if (olist == NULL)
215561be2e9Smartinh return LDAP_OBJECT_CLASS_VIOLATION;
216561be2e9Smartinh
217561be2e9Smartinh SLIST_FOREACH(optr, olist, next) {
218561be2e9Smartinh obj = optr->object;
219561be2e9Smartinh
220561be2e9Smartinh if (has_attribute(at, obj->may) ||
221561be2e9Smartinh has_attribute(at, obj->must))
222561be2e9Smartinh return LDAP_SUCCESS;
223561be2e9Smartinh
224561be2e9Smartinh if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS)
225561be2e9Smartinh return LDAP_SUCCESS;
226561be2e9Smartinh }
227561be2e9Smartinh
228561be2e9Smartinh return LDAP_OBJECT_CLASS_VIOLATION;
229561be2e9Smartinh }
230561be2e9Smartinh
231561be2e9Smartinh static void
olist_push(struct obj_list * olist,struct object * obj)232561be2e9Smartinh olist_push(struct obj_list *olist, struct object *obj)
233561be2e9Smartinh {
234561be2e9Smartinh struct obj_ptr *optr, *sup;
235561be2e9Smartinh
236561be2e9Smartinh SLIST_FOREACH(optr, olist, next)
237561be2e9Smartinh if (optr->object == obj)
238561be2e9Smartinh return;
239561be2e9Smartinh
240561be2e9Smartinh if ((optr = calloc(1, sizeof(*optr))) == NULL)
241561be2e9Smartinh return;
242561be2e9Smartinh optr->object = obj;
243561be2e9Smartinh SLIST_INSERT_HEAD(olist, optr, next);
244561be2e9Smartinh
2455d98f3b0Smartinh /* Expand the list of object classes along the superclass chain.
246561be2e9Smartinh */
247561be2e9Smartinh if (obj->sup != NULL)
248561be2e9Smartinh SLIST_FOREACH(sup, obj->sup, next)
249561be2e9Smartinh olist_push(olist, sup->object);
250561be2e9Smartinh }
251561be2e9Smartinh
252241e5f5bSmartinh static void
olist_free(struct obj_list * olist)253241e5f5bSmartinh olist_free(struct obj_list *olist)
254241e5f5bSmartinh {
255241e5f5bSmartinh struct obj_ptr *optr;
256241e5f5bSmartinh
257241e5f5bSmartinh if (olist == NULL)
258241e5f5bSmartinh return;
259241e5f5bSmartinh
260241e5f5bSmartinh while ((optr = SLIST_FIRST(olist)) != NULL) {
261241e5f5bSmartinh SLIST_REMOVE_HEAD(olist, next);
262241e5f5bSmartinh free(optr);
263241e5f5bSmartinh }
264241e5f5bSmartinh
265241e5f5bSmartinh free(olist);
266241e5f5bSmartinh }
267241e5f5bSmartinh
268561be2e9Smartinh /* Check if sup is a superior object class to obj.
269561be2e9Smartinh */
270561be2e9Smartinh static int
is_super(struct object * sup,struct object * obj)271561be2e9Smartinh is_super(struct object *sup, struct object *obj)
272561be2e9Smartinh {
273561be2e9Smartinh struct obj_ptr *optr;
274561be2e9Smartinh
275561be2e9Smartinh if (sup == NULL || obj->sup == NULL)
2765d98f3b0Smartinh return 0;
277561be2e9Smartinh
278561be2e9Smartinh SLIST_FOREACH(optr, obj->sup, next)
279561be2e9Smartinh if (optr->object == sup || is_super(sup, optr->object))
280561be2e9Smartinh return 1;
281561be2e9Smartinh
282561be2e9Smartinh return 0;
2835d465952Smartinh }
2845d465952Smartinh
2855d465952Smartinh int
validate_entry(const char * dn,struct ber_element * entry,int relax)2865d465952Smartinh validate_entry(const char *dn, struct ber_element *entry, int relax)
2875d465952Smartinh {
288561be2e9Smartinh int rc, extensible = 0;
2895d465952Smartinh char *s;
2905d465952Smartinh struct ber_element *objclass, *a, *vals;
2915d465952Smartinh struct object *obj, *structural_obj = NULL;
2925d465952Smartinh struct attr_type *at;
293241e5f5bSmartinh struct obj_list *olist = NULL;
294d5900b0fSmartinh struct obj_ptr *optr, *optr2;
2955d465952Smartinh
2965d465952Smartinh if (relax)
2975d465952Smartinh goto rdn;
2985d465952Smartinh
2995d465952Smartinh /* There must be an objectClass attribute.
3005d465952Smartinh */
3015d465952Smartinh objclass = ldap_get_attribute(entry, "objectClass");
3025d465952Smartinh if (objclass == NULL) {
3035d465952Smartinh log_debug("missing objectClass attribute");
3045d465952Smartinh return LDAP_OBJECT_CLASS_VIOLATION;
3055d465952Smartinh }
3065d465952Smartinh
307561be2e9Smartinh if ((olist = calloc(1, sizeof(*olist))) == NULL)
308561be2e9Smartinh return LDAP_OTHER;
309561be2e9Smartinh SLIST_INIT(olist);
310561be2e9Smartinh
3115d465952Smartinh /* Check objectClass(es) against schema.
3125d465952Smartinh */
3135d465952Smartinh objclass = objclass->be_next; /* skip attribute description */
3145d465952Smartinh for (a = objclass->be_sub; a != NULL; a = a->be_next) {
315696b5899Stb if (ober_get_string(a, &s) != 0) {
316*684a6a8cSclaudio log_debug("invalid ObjectClass encoding");
317241e5f5bSmartinh rc = LDAP_INVALID_SYNTAX;
318241e5f5bSmartinh goto done;
319241e5f5bSmartinh }
320561be2e9Smartinh
321a2a43363Smartinh if ((obj = lookup_object(conf->schema, s)) == NULL) {
3225d465952Smartinh log_debug("objectClass %s not defined in schema", s);
323241e5f5bSmartinh rc = LDAP_NAMING_VIOLATION;
324241e5f5bSmartinh goto done;
3255d465952Smartinh }
3265d465952Smartinh
327561be2e9Smartinh if (obj->kind == KIND_STRUCTURAL) {
328561be2e9Smartinh if (structural_obj != NULL) {
329561be2e9Smartinh if (is_super(structural_obj, obj))
330561be2e9Smartinh structural_obj = obj;
331561be2e9Smartinh else if (!is_super(obj, structural_obj)) {
332561be2e9Smartinh log_debug("multiple structural"
333561be2e9Smartinh " object classes");
334241e5f5bSmartinh rc = LDAP_OBJECT_CLASS_VIOLATION;
335241e5f5bSmartinh goto done;
336561be2e9Smartinh }
337561be2e9Smartinh } else
338561be2e9Smartinh structural_obj = obj;
3395d465952Smartinh }
3405d465952Smartinh
341561be2e9Smartinh olist_push(olist, obj);
342561be2e9Smartinh
343561be2e9Smartinh /* RFC4512, section 4.3:
344561be2e9Smartinh * "The 'extensibleObject' auxiliary object class allows
345561be2e9Smartinh * entries that belong to it to hold any user attribute."
346561be2e9Smartinh */
347561be2e9Smartinh if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
348561be2e9Smartinh extensible = 1;
349561be2e9Smartinh }
350561be2e9Smartinh
351561be2e9Smartinh /* Must have exactly one structural object class.
3525d465952Smartinh */
3535d465952Smartinh if (structural_obj == NULL) {
3545d465952Smartinh log_debug("no structural object class defined");
355241e5f5bSmartinh rc = LDAP_OBJECT_CLASS_VIOLATION;
356241e5f5bSmartinh goto done;
3575d465952Smartinh }
3585d465952Smartinh
359d5900b0fSmartinh /* "An entry cannot belong to an abstract object class
360d5900b0fSmartinh * unless it belongs to a structural or auxiliary class that
361d5900b0fSmartinh * inherits from that abstract class."
362d5900b0fSmartinh */
363d5900b0fSmartinh SLIST_FOREACH(optr, olist, next) {
364d5900b0fSmartinh if (optr->object->kind != KIND_ABSTRACT)
365d5900b0fSmartinh continue;
366d5900b0fSmartinh
367d5900b0fSmartinh /* Check the structural object class. */
368d5900b0fSmartinh if (is_super(optr->object, structural_obj))
369d5900b0fSmartinh continue;
370d5900b0fSmartinh
371d5900b0fSmartinh /* Check all auxiliary object classes. */
372d5900b0fSmartinh SLIST_FOREACH(optr2, olist, next) {
373d5900b0fSmartinh if (optr2->object->kind != KIND_AUXILIARY)
374d5900b0fSmartinh continue;
375d5900b0fSmartinh if (is_super(optr->object, optr2->object))
376d5900b0fSmartinh break;
377d5900b0fSmartinh }
378d5900b0fSmartinh
379d5900b0fSmartinh if (optr2 == NULL) {
380d5900b0fSmartinh /* No subclassed object class found. */
381d5900b0fSmartinh log_debug("abstract class '%s' not subclassed",
382d5900b0fSmartinh OBJ_NAME(optr->object));
383241e5f5bSmartinh rc = LDAP_OBJECT_CLASS_VIOLATION;
384241e5f5bSmartinh goto done;
385d5900b0fSmartinh }
386d5900b0fSmartinh }
387d5900b0fSmartinh
388561be2e9Smartinh /* Check all required attributes.
389561be2e9Smartinh */
390561be2e9Smartinh SLIST_FOREACH(optr, olist, next) {
391241e5f5bSmartinh rc = validate_required_attributes(entry, optr->object);
392241e5f5bSmartinh if (rc != LDAP_SUCCESS)
393241e5f5bSmartinh goto done;
394561be2e9Smartinh }
395561be2e9Smartinh
3965d465952Smartinh /* Check all attributes against schema.
3975d465952Smartinh */
3985d465952Smartinh for (a = entry->be_sub; a != NULL; a = a->be_next) {
399696b5899Stb if (ober_scanf_elements(a, "{se{", &s, &vals) != 0) {
400*684a6a8cSclaudio log_debug("invalid attribute encoding");
401241e5f5bSmartinh rc = LDAP_INVALID_SYNTAX;
402241e5f5bSmartinh goto done;
403241e5f5bSmartinh }
404a2a43363Smartinh if ((at = lookup_attribute(conf->schema, s)) == NULL) {
4055d465952Smartinh log_debug("attribute %s not defined in schema", s);
406241e5f5bSmartinh rc = LDAP_NAMING_VIOLATION;
407241e5f5bSmartinh goto done;
4085d465952Smartinh }
4095d465952Smartinh if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
410241e5f5bSmartinh goto done;
411561be2e9Smartinh if (!extensible && at->usage == USAGE_USER_APP &&
412561be2e9Smartinh (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) {
413561be2e9Smartinh log_debug("%s not allowed by any object class",
414561be2e9Smartinh ATTR_NAME(at));
415241e5f5bSmartinh goto done;
416561be2e9Smartinh }
4175d465952Smartinh }
4185d465952Smartinh
4195d465952Smartinh rdn:
420241e5f5bSmartinh rc = validate_dn(dn, entry);
4215d465952Smartinh
422241e5f5bSmartinh done:
423241e5f5bSmartinh olist_free(olist);
424241e5f5bSmartinh return rc;
4255d465952Smartinh }
4265d465952Smartinh
427