#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;
}
}
if (nvals == 0) {
log_debug("missing value in attribute %s", ATTR_NAME(at));
return LDAP_CONSTRAINT_VIOLATION;
}
return LDAP_SUCCESS;
}
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');
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;
}
}
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;
}
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);
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);
}
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;
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);
objclass = objclass->be_next;
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);
if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0)
extensible = 1;
}
if (structural_obj == NULL) {
log_debug("no structural object class defined");
rc = LDAP_OBJECT_CLASS_VIOLATION;
goto done;
}
SLIST_FOREACH(optr, olist, next) {
if (optr->object->kind != KIND_ABSTRACT)
continue;
if (is_super(optr->object, structural_obj))
continue;
SLIST_FOREACH(optr2, olist, next) {
if (optr2->object->kind != KIND_AUXILIARY)
continue;
if (is_super(optr->object, optr2->object))
break;
}
if (optr2 == NULL) {
log_debug("abstract class '%s' not subclassed",
OBJ_NAME(optr->object));
rc = LDAP_OBJECT_CLASS_VIOLATION;
goto done;
}
}
SLIST_FOREACH(optr, olist, next) {
rc = validate_required_attributes(entry, optr->object);
if (rc != LDAP_SUCCESS)
goto done;
}
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;
}