summaryrefslogtreecommitdiff
path: root/usr.sbin/ldapd/validate.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ldapd/validate.c')
-rw-r--r--usr.sbin/ldapd/validate.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/usr.sbin/ldapd/validate.c b/usr.sbin/ldapd/validate.c
new file mode 100644
index 00000000000..b7a3358aadd
--- /dev/null
+++ b/usr.sbin/ldapd/validate.c
@@ -0,0 +1,282 @@
+/* $OpenBSD: validate.c,v 1.1 2010/05/31 17:36:31 martinh 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"
+
+#define OBJ_NAME(obj) ((obj)->names ? SLIST_FIRST((obj)->names)->name : \
+ (obj)->oid)
+#define ATTR_NAME(at) OBJ_NAME(at)
+
+static int
+validate_required_attributes(struct ber_element *entry, struct object *obj)
+{
+ struct attr_ptr *ap;
+ struct attr_type *at;
+
+ log_debug("validating required attributes for object %s",
+ OBJ_NAME(obj));
+
+ 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;
+
+ 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 (elm->be_type != BER_TYPE_OCTETSTRING) {
+ 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;
+ }
+ }
+
+ /* 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;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static const char *
+attribute_equality(struct attr_type *at)
+{
+ if (at == NULL)
+ return NULL;
+ if (at->equality != NULL)
+ return at->equality;
+ return attribute_equality(at->sup);
+}
+
+/* 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';
+
+ log_debug("got naming attribute %s", na);
+ log_debug("got distinguished value %s", dv);
+ if ((at = lookup_attribute(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 (attribute_equality(at) == 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;
+ }
+ log_debug("checking for presence of superior dn %s", sup_dn);
+ 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
+validate_object_class(struct ber_element *entry, struct object *obj)
+{
+ struct obj_ptr *sup;
+ int rc;
+
+ rc = validate_required_attributes(entry, obj);
+ if (rc == LDAP_SUCCESS && obj->sup != NULL) {
+ SLIST_FOREACH(sup, obj->sup, next) {
+ rc = validate_object_class(entry, sup->object);
+ if (rc != LDAP_SUCCESS)
+ break;
+ }
+ }
+
+ return rc;
+}
+
+int
+validate_entry(const char *dn, struct ber_element *entry, int relax)
+{
+ int rc;
+ char *s;
+ struct ber_element *objclass, *a, *vals;
+ struct object *obj, *structural_obj = NULL;
+ struct attr_type *at;
+
+ 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;
+ }
+
+ /* Check objectClass(es) against schema.
+ */
+ objclass = objclass->be_next; /* skip attribute description */
+ for (a = objclass->be_sub; a != NULL; a = a->be_next) {
+ if (ber_get_string(a, &s) != 0)
+ return LDAP_INVALID_SYNTAX;
+ if ((obj = lookup_object(s)) == NULL) {
+ log_debug("objectClass %s not defined in schema", s);
+ return LDAP_NAMING_VIOLATION;
+ }
+ log_debug("object class %s has kind %d", s, obj->kind);
+ if (obj->kind == KIND_STRUCTURAL)
+ structural_obj = obj;
+
+ rc = validate_object_class(entry, obj);
+ if (rc != LDAP_SUCCESS)
+ return rc;
+ }
+
+ /* Must have at least one structural object class.
+ */
+ if (structural_obj == NULL) {
+ log_debug("no structural object class defined");
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* Check all attributes against schema.
+ */
+ for (a = entry->be_sub; a != NULL; a = a->be_next) {
+ if (ber_scanf_elements(a, "{se{", &s, &vals) != 0)
+ return LDAP_INVALID_SYNTAX;
+ if ((at = lookup_attribute(s)) == NULL) {
+ log_debug("attribute %s not defined in schema", s);
+ return LDAP_NAMING_VIOLATION;
+ }
+ if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS)
+ return rc;
+ }
+
+rdn:
+ if ((rc = validate_dn(dn, entry)) != LDAP_SUCCESS)
+ return rc;
+
+ return LDAP_SUCCESS;
+}
+