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);
}