/* $OpenBSD: validate.c,v 1.12 2019/10/24 12:39:26 tb Exp $ */ /* * Copyright (c) 2010 Martin Hedenfalk * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "ldapd.h" #include "log.h" static int validate_required_attributes(struct ber_element *entry, struct object *obj) { struct attr_ptr *ap; struct attr_type *at; if (obj->must == NULL) return LDAP_SUCCESS; SLIST_FOREACH(ap, obj->must, next) { at = ap->attr_type; if (ldap_find_attribute(entry, at) == NULL) { log_debug("missing required attribute %s", ATTR_NAME(at)); return LDAP_OBJECT_CLASS_VIOLATION; } } return LDAP_SUCCESS; } static int validate_attribute(struct attr_type *at, struct ber_element *vals) { int nvals = 0; struct ber_element *elm; char *val; if (vals == NULL) { log_debug("missing values"); return LDAP_OTHER; } if (vals->be_type != BER_TYPE_SET) { log_debug("values should be a set"); return LDAP_OTHER; } for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) { if (ober_get_string(elm, &val) == -1) { log_debug("attribute value not an octet-string"); return LDAP_PROTOCOL_ERROR; } if (++nvals > 1 && at->single) { log_debug("multiple values for single-valued" " attribute %s", ATTR_NAME(at)); return LDAP_CONSTRAINT_VIOLATION; } if (at->syntax->is_valid != NULL && !at->syntax->is_valid(conf->schema, val, elm->be_len)) { log_debug("%s: invalid syntax", ATTR_NAME(at)); log_debug("syntax = %s", at->syntax->desc); log_debug("value: [%.*s]", (int)elm->be_len, val); return LDAP_INVALID_SYNTAX; } } /* There must be at least one value in an attribute. */ if (nvals == 0) { log_debug("missing value in attribute %s", ATTR_NAME(at)); return LDAP_CONSTRAINT_VIOLATION; } /* FIXME: validate that values are unique */ return LDAP_SUCCESS; } /* FIXME: doesn't handle escaped characters. */ static int validate_dn(const char *dn, struct ber_element *entry) { char *copy; char *sup_dn, *na, *dv, *p; struct namespace *ns; struct attr_type *at; struct ber_element *vals; if ((copy = strdup(dn)) == NULL) return LDAP_OTHER; sup_dn = strchr(copy, ','); if (sup_dn++ == NULL) sup_dn = strrchr(copy, '\0'); /* Validate naming attributes and distinguished values in the RDN. */ p = copy; for (;p < sup_dn;) { na = p; p = na + strcspn(na, "="); if (p == na || p >= sup_dn) { free(copy); return LDAP_INVALID_DN_SYNTAX; } *p = '\0'; dv = p + 1; p = dv + strcspn(dv, "+,"); if (p == dv) { free(copy); return LDAP_INVALID_DN_SYNTAX; } *p++ = '\0'; if ((at = lookup_attribute(conf->schema, na)) == NULL) { log_debug("attribute %s not defined in schema", na); goto fail; } if (at->usage != USAGE_USER_APP) { log_debug("naming attribute %s is operational", na); goto fail; } if (at->collective) { log_debug("naming attribute %s is collective", na); goto fail; } if (at->obsolete) { log_debug("naming attribute %s is obsolete", na); goto fail; } if (at->equality == NULL) { log_debug("naming attribute %s doesn't define equality", na); goto fail; } if ((vals = ldap_find_attribute(entry, at)) == NULL) { log_debug("missing distinguished value for %s", na); goto fail; } if (ldap_find_value(vals->be_next, dv) == NULL) { log_debug("missing distinguished value %s" " in naming attribute %s", dv, na); goto fail; } } /* Check that the RDN immediate superior exists, or it is a * top-level namespace. */ if (*sup_dn != '\0') { TAILQ_FOREACH(ns, &conf->namespaces, next) { if (strcmp(dn, ns->suffix) == 0) goto done; } ns = namespace_for_base(sup_dn); if (ns == NULL || !namespace_exists(ns, sup_dn)) { free(copy); return LDAP_NO_SUCH_OBJECT; } } done: free(copy); return LDAP_SUCCESS; fail: free(copy); return LDAP_NAMING_VIOLATION; } static int has_attribute(struct attr_type *at, struct attr_list *alist) { struct attr_ptr *ap; if (alist == NULL) return 0; SLIST_FOREACH(ap, alist, next) { if (at == ap->attr_type) return 1; } return 0; } /* Validate that the attribute type is allowed by any object class. */ static int validate_allowed_attribute(struct attr_type *at, struct obj_list *olist) { struct object *obj; struct obj_ptr *optr; if (olist == NULL) return LDAP_OBJECT_CLASS_VIOLATION; SLIST_FOREACH(optr, olist, next) { obj = optr->object; if (has_attribute(at, obj->may) || has_attribute(at, obj->must)) return LDAP_SUCCESS; if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS) return LDAP_SUCCESS; } return LDAP_OBJECT_CLASS_VIOLATION; } static void olist_push(struct obj_list *olist, struct object *obj) { struct obj_ptr *optr, *sup; SLIST_FOREACH(optr, olist, next) if (optr->object == obj) return; if ((optr = calloc(1, sizeof(*optr))) == NULL) return; optr->object = obj; SLIST_INSERT_HEAD(olist, optr, next); /* Expand the list of object classes along the superclass chain. */ if (obj->sup != NULL) SLIST_FOREACH(sup, obj->sup, next) olist_push(olist, sup->object); } static void olist_free(struct obj_list *olist) { struct obj_ptr *optr; if (olist == NULL) return; while ((optr = SLIST_FIRST(olist)) != NULL) { SLIST_REMOVE_HEAD(olist, next); free(optr); } free(olist); } /* Check if sup is a superior object class to obj. */ static int is_super(struct object *sup, struct object *obj) { struct obj_ptr *optr; if (sup == NULL || obj->sup == NULL) return 0; SLIST_FOREACH(optr, obj->sup, next) if (optr->object == sup || is_super(sup, optr->object)) return 1; return 0; } int validate_entry(const char *dn, struct ber_element *entry, int relax) { int rc, extensible = 0; char *s; struct ber_element *objclass, *a, *vals; struct object *obj, *structural_obj = NULL; struct attr_type *at; struct obj_list *olist = NULL; struct obj_ptr *optr, *optr2; if (relax) goto rdn; /* There must be an objectClass attribute. */ objclass = ldap_get_attribute(entry, "objectClass"); if (objclass == NULL) { log_debug("missing objectClass attribute"); return LDAP_OBJECT_CLASS_VIOLATION; } if ((olist = calloc(1, sizeof(*olist))) == NULL) return LDAP_OTHER; SLIST_INIT(olist); /* Check objectClass(es) against schema. */ objclass = objclass->be_next; /* skip attribute description */ for (a = objclass->be_sub; a != NULL; a = a->be_next) { if (ober_get_string(a, &s) != 0) { rc = LDAP_INVALID_SYNTAX; goto done; } if ((obj = lookup_object(conf->schema, s)) == NULL) { log_debug("objectClass %s not defined in schema", s); rc = LDAP_NAMING_VIOLATION; goto done; } if (obj->kind == KIND_STRUCTURAL) { if (structural_obj != NULL) { if (is_super(structural_obj, obj)) structural_obj = obj; else if (!is_super(obj, structural_obj)) { log_debug("multiple structural" " object classes"); rc = LDAP_OBJECT_CLASS_VIOLATION; goto done; } } else structural_obj = obj; } olist_push(olist, obj); /* RFC4512, section 4.3: * "The 'extensibleObject' auxiliary object class allows * entries that belong to it to hold any user attribute." */ if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0) extensible = 1; } /* Must have exactly one structural object class. */ if (structural_obj == NULL) { log_debug("no structural object class defined"); rc = LDAP_OBJECT_CLASS_VIOLATION; goto done; } /* "An entry cannot belong to an abstract object class * unless it belongs to a structural or auxiliary class that * inherits from that abstract class." */ SLIST_FOREACH(optr, olist, next) { if (optr->object->kind != KIND_ABSTRACT) continue; /* Check the structural object class. */ if (is_super(optr->object, structural_obj)) continue; /* Check all auxiliary object classes. */ SLIST_FOREACH(optr2, olist, next) { if (optr2->object->kind != KIND_AUXILIARY) continue; if (is_super(optr->object, optr2->object)) break; } if (optr2 == NULL) { /* No subclassed object class found. */ log_debug("abstract class '%s' not subclassed", OBJ_NAME(optr->object)); rc = LDAP_OBJECT_CLASS_VIOLATION; goto done; } } /* Check all required attributes. */ SLIST_FOREACH(optr, olist, next) { rc = validate_required_attributes(entry, optr->object); if (rc != LDAP_SUCCESS) goto done; } /* Check all attributes against schema. */ for (a = entry->be_sub; a != NULL; a = a->be_next) { if (ober_scanf_elements(a, "{se{", &s, &vals) != 0) { rc = LDAP_INVALID_SYNTAX; goto done; } if ((at = lookup_attribute(conf->schema, s)) == NULL) { log_debug("attribute %s not defined in schema", s); rc = LDAP_NAMING_VIOLATION; goto done; } if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS) goto done; if (!extensible && at->usage == USAGE_USER_APP && (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) { log_debug("%s not allowed by any object class", ATTR_NAME(at)); goto done; } } rdn: rc = validate_dn(dn, entry); done: olist_free(olist); return rc; }