File: [local] / src / usr.sbin / ldapd / validate.c (download)
Revision 1.13, Mon Dec 20 13:18:29 2021 UTC (2 years, 5 months ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, HEAD Changes since 1.12: +3 -1 lines
Add some debug messages in validate_entry() that explain why
LDAP_INVALID_SYNTAX is returned.
OK jmatthew@
|
/* $OpenBSD: validate.c,v 1.13 2021/12/20 13:18:29 claudio Exp $ */
/*
* Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se>
*
* 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 <sys/types.h>
#include <sys/queue.h>
#include <stdlib.h>
#include <string.h>
#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) {
log_debug("invalid ObjectClass encoding");
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) {
log_debug("invalid attribute encoding");
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;
}