[BACK]Return to modify.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / ldapd

File: [local] / src / usr.sbin / ldapd / modify.c (download)

Revision 1.24, Mon Dec 20 13:26:11 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.23: +3 -2 lines

When removing the last value from an attribute in ldap_del_values()
the actuall attribute needs to removed instead of leaving back an
empty attribute. Empty attributes are not valid and fail later on
in ldap_modify(). By calling ldap_del_attribute() in this case
properly removes the attribute and with that validate_entry() no
longer fails later on.
OK jmatthew@

/*	$OpenBSD: modify.c,v 1.24 2021/12/20 13:26:11 claudio Exp $ */

/*
 * Copyright (c) 2009, 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 <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "ldapd.h"
#include "log.h"
#include "uuid.h"

int
ldap_delete(struct request *req)
{
	struct btval		 key;
	char			*dn, *s;
	struct namespace	*ns;
	struct referrals	*refs;
	struct cursor		*cursor;
	struct ber_element	*entry, *elm, *a;
	int			 rc = LDAP_OTHER;

	++stats.req_mod;

	if (ober_scanf_elements(req->op, "s", &dn) != 0)
		return ldap_respond(req, LDAP_PROTOCOL_ERROR);

	normalize_dn(dn);
	log_debug("deleting entry %s", dn);

	if ((ns = namespace_for_base(dn)) == NULL) {
		refs = namespace_referrals(dn);
		if (refs == NULL)
			return ldap_respond(req, LDAP_NAMING_VIOLATION);
		else
			return ldap_refer(req, dn, NULL, refs);
	}

	if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
		return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);

	if (namespace_begin(ns) != 0) {
		if (errno == EBUSY) {
			if (namespace_queue_request(ns, req) != 0)
				return ldap_respond(req, LDAP_BUSY);
			return LDAP_BUSY;
		}
		return ldap_respond(req, LDAP_OTHER);
	}

	/* Check that this is a leaf node by getting a cursor to the DN
	 * we're about to delete. If there is a next entry with the DN
	 * as suffix (ie, a child node), the DN can't be deleted.
	 */
	if ((cursor = btree_txn_cursor_open(NULL, ns->data_txn)) == NULL)
		goto done;

	memset(&key, 0, sizeof(key));
	key.data = dn;
	key.size = strlen(dn);
	if (btree_cursor_get(cursor, &key, NULL, BT_CURSOR_EXACT) != 0) {
		if (errno == ENOENT)
			rc = LDAP_NO_SUCH_OBJECT;
		goto done;
	}

	btval_reset(&key);
	if (btree_cursor_get(cursor, &key, NULL, BT_NEXT) != 0) {
		if (errno != ENOENT)
			goto done;
	} else if (has_suffix(&key, dn)) {
		rc = LDAP_NOT_ALLOWED_ON_NONLEAF;
		goto done;
	}

	if ((entry = namespace_get(ns, dn)) == NULL) {
		rc = LDAP_NO_SUCH_OBJECT;
		goto done;
	}

	/* Fail if this leaf node includes non-writeable attributes */
	if (entry->be_encoding != BER_TYPE_SEQUENCE)
		goto done;
	for (elm = entry->be_sub; elm != NULL; elm = elm->be_next) {
		a = elm->be_sub;
		if (a && ober_get_string(a, &s) == 0 &&
		    !authorized(req->conn, ns, ACI_WRITE, dn, s,
		    LDAP_SCOPE_BASE)) {
			rc = LDAP_INSUFFICIENT_ACCESS;
			goto done;
		}
	}

	if (namespace_del(ns, dn) == 0 && namespace_commit(ns) == 0)
		rc = LDAP_SUCCESS;

done:
	btree_cursor_close(cursor);
	btval_reset(&key);
	namespace_abort(ns);
	return ldap_respond(req, rc);
}

int
ldap_add(struct request *req)
{
	char			 uuid_str[64];
	struct uuid		 uuid;
	char			*dn, *s;
	struct attr_type	*at;
	struct ber_element	*attrs, *attr, *elm, *set = NULL;
	struct namespace	*ns;
	struct referrals	*refs;
	int			 rc;

	++stats.req_mod;

	if (ober_scanf_elements(req->op, "{se", &dn, &attrs) != 0)
		return ldap_respond(req, LDAP_PROTOCOL_ERROR);

	normalize_dn(dn);
	log_debug("adding entry %s", dn);

	if (*dn == '\0')
		return ldap_respond(req, LDAP_INVALID_DN_SYNTAX);

	if ((ns = namespace_for_base(dn)) == NULL) {
		refs = namespace_referrals(dn);
		if (refs == NULL)
			return ldap_respond(req, LDAP_NAMING_VIOLATION);
		else
			return ldap_refer(req, dn, NULL, refs);
	}

	if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
		return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);

	/* Check that we're not adding immutable attributes.
	 */
	for (elm = attrs->be_sub; elm != NULL; elm = elm->be_next) {
		attr = elm->be_sub;
		if (attr == NULL || ober_get_string(attr, &s) != 0)
			return ldap_respond(req, LDAP_PROTOCOL_ERROR);
		if (!authorized(req->conn, ns, ACI_WRITE, dn, s,
		    LDAP_SCOPE_BASE))
			return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
		if (!ns->relax) {
			at = lookup_attribute(conf->schema, s);
			if (at == NULL) {
				log_debug("unknown attribute type %s", s);
				return ldap_respond(req,
				    LDAP_NO_SUCH_ATTRIBUTE);
			}
			if (at->immutable) {
				log_debug("attempt to add immutable"
				    " attribute %s", s);
				return ldap_respond(req,
				    LDAP_CONSTRAINT_VIOLATION);
			}
		}
	}

	if (namespace_begin(ns) == -1) {
		if (errno == EBUSY) {
			if (namespace_queue_request(ns, req) != 0)
				return ldap_respond(req, LDAP_BUSY);
			return LDAP_BUSY;
		}
		return ldap_respond(req, LDAP_OTHER);
	}

	/* add operational attributes
	 */
	if ((set = ober_add_set(NULL)) == NULL)
		goto fail;
	if (ober_add_string(set, req->conn->binddn ? req->conn->binddn : "") == NULL)
		goto fail;
	if (ldap_add_attribute(attrs, "creatorsName", set) == NULL)
		goto fail;

	if ((set = ober_add_set(NULL)) == NULL)
		goto fail;
	if (ober_add_string(set, ldap_now()) == NULL)
		goto fail;
	if (ldap_add_attribute(attrs, "createTimestamp", set) == NULL)
		goto fail;

	uuid_create(&uuid);
	uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
	if ((set = ober_add_set(NULL)) == NULL)
		goto fail;
	if (ober_add_string(set, uuid_str) == NULL)
		goto fail;
	if (ldap_add_attribute(attrs, "entryUUID", set) == NULL)
		goto fail;

	if ((rc = validate_entry(dn, attrs, ns->relax)) != LDAP_SUCCESS ||
	    namespace_add(ns, dn, attrs) != 0) {
		namespace_abort(ns);
		if (rc == LDAP_SUCCESS && errno == EEXIST)
			rc = LDAP_ALREADY_EXISTS;
		else if (rc == LDAP_SUCCESS)
			rc = LDAP_OTHER;
	} else if (namespace_commit(ns) != 0)
		rc = LDAP_OTHER;

	return ldap_respond(req, rc);

fail:
	if (set != NULL)
		ober_free_elements(set);
	namespace_abort(ns);
	return ldap_respond(req, LDAP_OTHER);
}

int
ldap_modify(struct request *req)
{
	int			 rc = LDAP_OTHER;
	char			*dn;
	long long		 op;
	char			*attr;
	struct ber_element	*mods, *entry, *mod, *a, *set;
	struct ber_element	*vals = NULL, *prev = NULL;
	struct namespace	*ns;
	struct attr_type	*at;
	struct referrals	*refs;

	++stats.req_mod;

	if (ober_scanf_elements(req->op, "{se", &dn, &mods) != 0)
		return ldap_respond(req, LDAP_PROTOCOL_ERROR);

	normalize_dn(dn);
	log_debug("modifying dn %s", dn);

	if (*dn == 0)
		return ldap_respond(req, LDAP_INVALID_DN_SYNTAX);

	if ((ns = namespace_for_base(dn)) == NULL) {
		refs = namespace_referrals(dn);
		if (refs == NULL)
			return ldap_respond(req, LDAP_NAMING_VIOLATION);
		else
			return ldap_refer(req, dn, NULL, refs);
	}

	/* Check authorization for each mod to consider attributes */
	for (mod = mods->be_sub; mod; mod = mod->be_next) {
		if (ober_scanf_elements(mod, "{E{es", &op, &prev, &attr) != 0)
			return ldap_respond(req, LDAP_PROTOCOL_ERROR);
		if (!authorized(req->conn, ns, ACI_WRITE, dn, attr,
		    LDAP_SCOPE_BASE))
			return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
	}

	if (namespace_begin(ns) == -1) {
		if (errno == EBUSY) {
			if (namespace_queue_request(ns, req) != 0)
				return ldap_respond(req, LDAP_BUSY);
			return LDAP_BUSY;
		}
		return ldap_respond(req, LDAP_OTHER);
	}

	if ((entry = namespace_get(ns, dn)) == NULL) {
		rc = LDAP_NO_SUCH_OBJECT;
		goto done;
	}

	for (mod = mods->be_sub; mod; mod = mod->be_next) {
		if (ober_scanf_elements(mod, "{E{ese(", &op, &prev, &attr, &vals) != 0) {
			rc = LDAP_PROTOCOL_ERROR;
			vals = NULL;
			goto done;
		}

		prev->be_next = NULL;

		if (!ns->relax) {
			at = lookup_attribute(conf->schema, attr);
			if (at == NULL) {
				log_debug("unknown attribute type %s", attr);
				rc = LDAP_NO_SUCH_ATTRIBUTE;
				goto done;
			}
			if (at->immutable) {
				log_debug("attempt to modify immutable"
				    " attribute %s", attr);
				rc = LDAP_CONSTRAINT_VIOLATION;
				goto done;
			}
		}

		a = ldap_get_attribute(entry, attr);

		switch (op) {
		case LDAP_MOD_ADD:
			if (a == NULL) {
				if (ldap_add_attribute(entry, attr, vals) != NULL)
					vals = NULL;
			} else {
				if (ldap_merge_values(a, vals) == 0)
					vals = NULL;
			}
			break;
		case LDAP_MOD_DELETE:
			/*
			 * We're already in the "SET OF value
			 * AttributeValue" (see RFC4511 section
			 * 4.1.7) have either no content, so all values
			 * for the attribute gets deleted, or we
			 * have a (first) octetstring (there is one
			 * for each AttributeValue to be deleted)
			 */
			if (vals->be_sub &&
			    vals->be_sub->be_type == BER_TYPE_OCTETSTRING) {
				if (ldap_del_values(a, vals) == 1)
					ldap_del_attribute(entry, attr);
			} else {
				ldap_del_attribute(entry, attr);
			}
			break;
		case LDAP_MOD_REPLACE:
			if (vals->be_sub != NULL &&
			    vals->be_sub->be_type != BER_TYPE_EOC) {
				if (a == NULL) {
					if (ldap_add_attribute(entry, attr, vals) != NULL)
						vals = NULL;
				} else {
					if (ldap_set_values(a, vals) == 0)
						vals = NULL;
				}
			} else if (a != NULL)
				ldap_del_attribute(entry, attr);
			break;
		}

		if (vals != NULL) {
			ober_free_elements(vals);
			vals = NULL;
		}
	}

	if ((rc = validate_entry(dn, entry, ns->relax)) != LDAP_SUCCESS)
		goto done;

	set = ober_add_set(NULL);
	ober_add_string(set, req->conn->binddn ? req->conn->binddn : "");
	if ((a = ldap_get_attribute(entry, "modifiersName")) != NULL)
		ldap_set_values(a, set);
	else
		ldap_add_attribute(entry, "modifiersName", set);

	set = ober_add_set(NULL);
	ober_add_string(set, ldap_now());
	if ((a = ldap_get_attribute(entry, "modifyTimestamp")) != NULL)
		ldap_set_values(a, set);
	else
		ldap_add_attribute(entry, "modifyTimestamp", set);

	if (namespace_update(ns, dn, entry) == 0 && namespace_commit(ns) == 0)
		rc = LDAP_SUCCESS;
	else
		rc = LDAP_OTHER;

done:
	if (vals != NULL)
		ober_free_elements(vals);
	namespace_abort(ns);
	return ldap_respond(req, rc);
}