xref: /openbsd-src/usr.sbin/ldapd/modify.c (revision 5387241f8527ff37ae50356167c4e1c0a1937311)
1*5387241fSclaudio /*	$OpenBSD: modify.c,v 1.24 2021/12/20 13:26:11 claudio Exp $ */
25d465952Smartinh 
35d465952Smartinh /*
45d465952Smartinh  * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
55d465952Smartinh  *
65d465952Smartinh  * Permission to use, copy, modify, and distribute this software for any
75d465952Smartinh  * purpose with or without fee is hereby granted, provided that the above
85d465952Smartinh  * copyright notice and this permission notice appear in all copies.
95d465952Smartinh  *
105d465952Smartinh  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
115d465952Smartinh  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
125d465952Smartinh  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
135d465952Smartinh  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
145d465952Smartinh  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
155d465952Smartinh  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
165d465952Smartinh  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
175d465952Smartinh  */
185d465952Smartinh 
195d465952Smartinh #include <sys/types.h>
205d465952Smartinh #include <sys/queue.h>
215d465952Smartinh 
225d465952Smartinh #include <assert.h>
230279e526Smartinh #include <errno.h>
245d465952Smartinh #include <stdlib.h>
255d465952Smartinh #include <string.h>
265d465952Smartinh 
275d465952Smartinh #include "ldapd.h"
28fdd30f56Sbenno #include "log.h"
295d465952Smartinh #include "uuid.h"
305d465952Smartinh 
315d465952Smartinh int
ldap_delete(struct request * req)325d465952Smartinh ldap_delete(struct request *req)
335d465952Smartinh {
3486f37e34Smartinh 	struct btval		 key;
35841a002bSreyk 	char			*dn, *s;
365d465952Smartinh 	struct namespace	*ns;
3738c09006Smartinh 	struct referrals	*refs;
3886f37e34Smartinh 	struct cursor		*cursor;
39841a002bSreyk 	struct ber_element	*entry, *elm, *a;
40653ab0a7Smartinh 	int			 rc = LDAP_OTHER;
415d465952Smartinh 
425d465952Smartinh 	++stats.req_mod;
435d465952Smartinh 
44696b5899Stb 	if (ober_scanf_elements(req->op, "s", &dn) != 0)
455d465952Smartinh 		return ldap_respond(req, LDAP_PROTOCOL_ERROR);
465d465952Smartinh 
475d465952Smartinh 	normalize_dn(dn);
485d465952Smartinh 	log_debug("deleting entry %s", dn);
495d465952Smartinh 
5038c09006Smartinh 	if ((ns = namespace_for_base(dn)) == NULL) {
5138c09006Smartinh 		refs = namespace_referrals(dn);
5238c09006Smartinh 		if (refs == NULL)
535d465952Smartinh 			return ldap_respond(req, LDAP_NAMING_VIOLATION);
5438c09006Smartinh 		else
5538c09006Smartinh 			return ldap_refer(req, dn, NULL, refs);
5638c09006Smartinh 	}
575d465952Smartinh 
58841a002bSreyk 	if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
595d465952Smartinh 		return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
605d465952Smartinh 
610279e526Smartinh 	if (namespace_begin(ns) != 0) {
620279e526Smartinh 		if (errno == EBUSY) {
635d465952Smartinh 			if (namespace_queue_request(ns, req) != 0)
645d465952Smartinh 				return ldap_respond(req, LDAP_BUSY);
655d465952Smartinh 			return LDAP_BUSY;
660279e526Smartinh 		}
67a9ac9ba1Smartinh 		return ldap_respond(req, LDAP_OTHER);
685d465952Smartinh 	}
69a9ac9ba1Smartinh 
7086f37e34Smartinh 	/* Check that this is a leaf node by getting a cursor to the DN
7186f37e34Smartinh 	 * we're about to delete. If there is a next entry with the DN
7286f37e34Smartinh 	 * as suffix (ie, a child node), the DN can't be deleted.
7386f37e34Smartinh 	 */
7486f37e34Smartinh 	if ((cursor = btree_txn_cursor_open(NULL, ns->data_txn)) == NULL)
75653ab0a7Smartinh 		goto done;
7686f37e34Smartinh 
7737f4b933Smmcc 	memset(&key, 0, sizeof(key));
7886f37e34Smartinh 	key.data = dn;
7986f37e34Smartinh 	key.size = strlen(dn);
80653ab0a7Smartinh 	if (btree_cursor_get(cursor, &key, NULL, BT_CURSOR_EXACT) != 0) {
81653ab0a7Smartinh 		if (errno == ENOENT)
82653ab0a7Smartinh 			rc = LDAP_NO_SUCH_OBJECT;
83653ab0a7Smartinh 		goto done;
84653ab0a7Smartinh 	}
85653ab0a7Smartinh 
866a906d33Smartinh 	btval_reset(&key);
8786f37e34Smartinh 	if (btree_cursor_get(cursor, &key, NULL, BT_NEXT) != 0) {
8886f37e34Smartinh 		if (errno != ENOENT)
89653ab0a7Smartinh 			goto done;
9086f37e34Smartinh 	} else if (has_suffix(&key, dn)) {
91653ab0a7Smartinh 		rc = LDAP_NOT_ALLOWED_ON_NONLEAF;
92653ab0a7Smartinh 		goto done;
9386f37e34Smartinh 	}
9486f37e34Smartinh 
95841a002bSreyk 	if ((entry = namespace_get(ns, dn)) == NULL) {
96841a002bSreyk 		rc = LDAP_NO_SUCH_OBJECT;
97841a002bSreyk 		goto done;
98841a002bSreyk 	}
99841a002bSreyk 
100841a002bSreyk 	/* Fail if this leaf node includes non-writeable attributes */
101841a002bSreyk 	if (entry->be_encoding != BER_TYPE_SEQUENCE)
102841a002bSreyk 		goto done;
103841a002bSreyk 	for (elm = entry->be_sub; elm != NULL; elm = elm->be_next) {
104841a002bSreyk 		a = elm->be_sub;
105696b5899Stb 		if (a && ober_get_string(a, &s) == 0 &&
106841a002bSreyk 		    !authorized(req->conn, ns, ACI_WRITE, dn, s,
107841a002bSreyk 		    LDAP_SCOPE_BASE)) {
108841a002bSreyk 			rc = LDAP_INSUFFICIENT_ACCESS;
109841a002bSreyk 			goto done;
110841a002bSreyk 		}
111841a002bSreyk 	}
112841a002bSreyk 
1131761b184Smartinh 	if (namespace_del(ns, dn) == 0 && namespace_commit(ns) == 0)
114653ab0a7Smartinh 		rc = LDAP_SUCCESS;
11586f37e34Smartinh 
116653ab0a7Smartinh done:
117653ab0a7Smartinh 	btree_cursor_close(cursor);
1186a906d33Smartinh 	btval_reset(&key);
1190279e526Smartinh 	namespace_abort(ns);
120653ab0a7Smartinh 	return ldap_respond(req, rc);
1210279e526Smartinh }
1225d465952Smartinh 
1235d465952Smartinh int
ldap_add(struct request * req)1245d465952Smartinh ldap_add(struct request *req)
1255d465952Smartinh {
1265d465952Smartinh 	char			 uuid_str[64];
1275d465952Smartinh 	struct uuid		 uuid;
128a2c61c8bSmartinh 	char			*dn, *s;
129a2c61c8bSmartinh 	struct attr_type	*at;
130a2c61c8bSmartinh 	struct ber_element	*attrs, *attr, *elm, *set = NULL;
1315d465952Smartinh 	struct namespace	*ns;
13238c09006Smartinh 	struct referrals	*refs;
1335d465952Smartinh 	int			 rc;
1345d465952Smartinh 
1355d465952Smartinh 	++stats.req_mod;
1365d465952Smartinh 
137696b5899Stb 	if (ober_scanf_elements(req->op, "{se", &dn, &attrs) != 0)
1385d465952Smartinh 		return ldap_respond(req, LDAP_PROTOCOL_ERROR);
1395d465952Smartinh 
1405d465952Smartinh 	normalize_dn(dn);
1415d465952Smartinh 	log_debug("adding entry %s", dn);
1425d465952Smartinh 
1435d465952Smartinh 	if (*dn == '\0')
1445d465952Smartinh 		return ldap_respond(req, LDAP_INVALID_DN_SYNTAX);
1455d465952Smartinh 
14638c09006Smartinh 	if ((ns = namespace_for_base(dn)) == NULL) {
14738c09006Smartinh 		refs = namespace_referrals(dn);
14838c09006Smartinh 		if (refs == NULL)
1495d465952Smartinh 			return ldap_respond(req, LDAP_NAMING_VIOLATION);
15038c09006Smartinh 		else
15138c09006Smartinh 			return ldap_refer(req, dn, NULL, refs);
15238c09006Smartinh 	}
1535d465952Smartinh 
154841a002bSreyk 	if (!authorized(req->conn, ns, ACI_WRITE, dn, NULL, LDAP_SCOPE_BASE))
1555d465952Smartinh 		return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
1565d465952Smartinh 
157a2c61c8bSmartinh 	/* Check that we're not adding immutable attributes.
158a2c61c8bSmartinh 	 */
159a2c61c8bSmartinh 	for (elm = attrs->be_sub; elm != NULL; elm = elm->be_next) {
160a2c61c8bSmartinh 		attr = elm->be_sub;
161696b5899Stb 		if (attr == NULL || ober_get_string(attr, &s) != 0)
162a2c61c8bSmartinh 			return ldap_respond(req, LDAP_PROTOCOL_ERROR);
163841a002bSreyk 		if (!authorized(req->conn, ns, ACI_WRITE, dn, s,
164841a002bSreyk 		    LDAP_SCOPE_BASE))
165841a002bSreyk 			return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
1660354f496Smartinh 		if (!ns->relax) {
167a2c61c8bSmartinh 			at = lookup_attribute(conf->schema, s);
168a2c61c8bSmartinh 			if (at == NULL) {
169a2c61c8bSmartinh 				log_debug("unknown attribute type %s", s);
1700354f496Smartinh 				return ldap_respond(req,
1710354f496Smartinh 				    LDAP_NO_SUCH_ATTRIBUTE);
172a2c61c8bSmartinh 			}
173a2c61c8bSmartinh 			if (at->immutable) {
1740354f496Smartinh 				log_debug("attempt to add immutable"
1750354f496Smartinh 				    " attribute %s", s);
1760354f496Smartinh 				return ldap_respond(req,
1770354f496Smartinh 				    LDAP_CONSTRAINT_VIOLATION);
1780354f496Smartinh 			}
179a2c61c8bSmartinh 		}
180a2c61c8bSmartinh 	}
181a2c61c8bSmartinh 
1820279e526Smartinh 	if (namespace_begin(ns) == -1) {
1830279e526Smartinh 		if (errno == EBUSY) {
1845d465952Smartinh 			if (namespace_queue_request(ns, req) != 0)
1855d465952Smartinh 				return ldap_respond(req, LDAP_BUSY);
1865d465952Smartinh 			return LDAP_BUSY;
1870279e526Smartinh 		}
188a9ac9ba1Smartinh 		return ldap_respond(req, LDAP_OTHER);
1890279e526Smartinh 	}
1905d465952Smartinh 
1915d465952Smartinh 	/* add operational attributes
1925d465952Smartinh 	 */
193696b5899Stb 	if ((set = ober_add_set(NULL)) == NULL)
194a2c61c8bSmartinh 		goto fail;
195696b5899Stb 	if (ober_add_string(set, req->conn->binddn ? req->conn->binddn : "") == NULL)
196a2c61c8bSmartinh 		goto fail;
197a2c61c8bSmartinh 	if (ldap_add_attribute(attrs, "creatorsName", set) == NULL)
198a2c61c8bSmartinh 		goto fail;
1995d465952Smartinh 
200696b5899Stb 	if ((set = ober_add_set(NULL)) == NULL)
201a2c61c8bSmartinh 		goto fail;
202696b5899Stb 	if (ober_add_string(set, ldap_now()) == NULL)
203a2c61c8bSmartinh 		goto fail;
204a2c61c8bSmartinh 	if (ldap_add_attribute(attrs, "createTimestamp", set) == NULL)
205a2c61c8bSmartinh 		goto fail;
2065d465952Smartinh 
2075d465952Smartinh 	uuid_create(&uuid);
2085d465952Smartinh 	uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
209696b5899Stb 	if ((set = ober_add_set(NULL)) == NULL)
210a2c61c8bSmartinh 		goto fail;
211696b5899Stb 	if (ober_add_string(set, uuid_str) == NULL)
212a2c61c8bSmartinh 		goto fail;
213a2c61c8bSmartinh 	if (ldap_add_attribute(attrs, "entryUUID", set) == NULL)
214a2c61c8bSmartinh 		goto fail;
2155d465952Smartinh 
2160279e526Smartinh 	if ((rc = validate_entry(dn, attrs, ns->relax)) != LDAP_SUCCESS ||
2170279e526Smartinh 	    namespace_add(ns, dn, attrs) != 0) {
2180279e526Smartinh 		namespace_abort(ns);
2190279e526Smartinh 		if (rc == LDAP_SUCCESS && errno == EEXIST)
220a9ac9ba1Smartinh 			rc = LDAP_ALREADY_EXISTS;
221639fdb7dSmartinh 		else if (rc == LDAP_SUCCESS)
222a9ac9ba1Smartinh 			rc = LDAP_OTHER;
2230279e526Smartinh 	} else if (namespace_commit(ns) != 0)
2240279e526Smartinh 		rc = LDAP_OTHER;
225a9ac9ba1Smartinh 
2260279e526Smartinh 	return ldap_respond(req, rc);
227a2c61c8bSmartinh 
228a2c61c8bSmartinh fail:
229a2c61c8bSmartinh 	if (set != NULL)
230696b5899Stb 		ober_free_elements(set);
231a2c61c8bSmartinh 	namespace_abort(ns);
232a2c61c8bSmartinh 	return ldap_respond(req, LDAP_OTHER);
2335d465952Smartinh }
2345d465952Smartinh 
2355d465952Smartinh int
ldap_modify(struct request * req)2365d465952Smartinh ldap_modify(struct request *req)
2375d465952Smartinh {
2381761b184Smartinh 	int			 rc = LDAP_OTHER;
2395d465952Smartinh 	char			*dn;
2405d465952Smartinh 	long long		 op;
241a2a43363Smartinh 	char			*attr;
242de677adcSpelikan 	struct ber_element	*mods, *entry, *mod, *a, *set;
243de677adcSpelikan 	struct ber_element	*vals = NULL, *prev = NULL;
2445d465952Smartinh 	struct namespace	*ns;
2455d465952Smartinh 	struct attr_type	*at;
24638c09006Smartinh 	struct referrals	*refs;
2475d465952Smartinh 
2485d465952Smartinh 	++stats.req_mod;
2495d465952Smartinh 
250696b5899Stb 	if (ober_scanf_elements(req->op, "{se", &dn, &mods) != 0)
2515d465952Smartinh 		return ldap_respond(req, LDAP_PROTOCOL_ERROR);
2525d465952Smartinh 
2535d465952Smartinh 	normalize_dn(dn);
2545d465952Smartinh 	log_debug("modifying dn %s", dn);
2555d465952Smartinh 
2565d465952Smartinh 	if (*dn == 0)
2575d465952Smartinh 		return ldap_respond(req, LDAP_INVALID_DN_SYNTAX);
2585d465952Smartinh 
25938c09006Smartinh 	if ((ns = namespace_for_base(dn)) == NULL) {
26038c09006Smartinh 		refs = namespace_referrals(dn);
26138c09006Smartinh 		if (refs == NULL)
2625d465952Smartinh 			return ldap_respond(req, LDAP_NAMING_VIOLATION);
26338c09006Smartinh 		else
26438c09006Smartinh 			return ldap_refer(req, dn, NULL, refs);
26538c09006Smartinh 	}
2665d465952Smartinh 
267841a002bSreyk 	/* Check authorization for each mod to consider attributes */
268841a002bSreyk 	for (mod = mods->be_sub; mod; mod = mod->be_next) {
269696b5899Stb 		if (ober_scanf_elements(mod, "{E{es", &op, &prev, &attr) != 0)
270841a002bSreyk 			return ldap_respond(req, LDAP_PROTOCOL_ERROR);
271841a002bSreyk 		if (!authorized(req->conn, ns, ACI_WRITE, dn, attr,
272841a002bSreyk 		    LDAP_SCOPE_BASE))
2735d465952Smartinh 			return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
274841a002bSreyk 	}
2755d465952Smartinh 
2760279e526Smartinh 	if (namespace_begin(ns) == -1) {
2770279e526Smartinh 		if (errno == EBUSY) {
2785d465952Smartinh 			if (namespace_queue_request(ns, req) != 0)
2795d465952Smartinh 				return ldap_respond(req, LDAP_BUSY);
2805d465952Smartinh 			return LDAP_BUSY;
2810279e526Smartinh 		}
282a9ac9ba1Smartinh 		return ldap_respond(req, LDAP_OTHER);
2830279e526Smartinh 	}
284a9ac9ba1Smartinh 
285a9ac9ba1Smartinh 	if ((entry = namespace_get(ns, dn)) == NULL) {
286a9ac9ba1Smartinh 		rc = LDAP_NO_SUCH_OBJECT;
287a9ac9ba1Smartinh 		goto done;
2885d465952Smartinh 	}
2895d465952Smartinh 
2905d465952Smartinh 	for (mod = mods->be_sub; mod; mod = mod->be_next) {
291696b5899Stb 		if (ober_scanf_elements(mod, "{E{ese(", &op, &prev, &attr, &vals) != 0) {
292a9ac9ba1Smartinh 			rc = LDAP_PROTOCOL_ERROR;
293f12396bdSmartinh 			vals = NULL;
294a9ac9ba1Smartinh 			goto done;
295a9ac9ba1Smartinh 		}
2965d465952Smartinh 
297f12396bdSmartinh 		prev->be_next = NULL;
298f12396bdSmartinh 
2990354f496Smartinh 		if (!ns->relax) {
3000354f496Smartinh 			at = lookup_attribute(conf->schema, attr);
3010354f496Smartinh 			if (at == NULL) {
3025d465952Smartinh 				log_debug("unknown attribute type %s", attr);
303a9ac9ba1Smartinh 				rc = LDAP_NO_SUCH_ATTRIBUTE;
304a9ac9ba1Smartinh 				goto done;
3055d465952Smartinh 			}
3060354f496Smartinh 			if (at->immutable) {
3070354f496Smartinh 				log_debug("attempt to modify immutable"
3080354f496Smartinh 				    " attribute %s", attr);
309a9ac9ba1Smartinh 				rc = LDAP_CONSTRAINT_VIOLATION;
310a9ac9ba1Smartinh 				goto done;
3115d465952Smartinh 			}
3120354f496Smartinh 		}
3135d465952Smartinh 
3145d465952Smartinh 		a = ldap_get_attribute(entry, attr);
3155d465952Smartinh 
3165d465952Smartinh 		switch (op) {
3175d465952Smartinh 		case LDAP_MOD_ADD:
318f12396bdSmartinh 			if (a == NULL) {
319f12396bdSmartinh 				if (ldap_add_attribute(entry, attr, vals) != NULL)
320f12396bdSmartinh 					vals = NULL;
321f12396bdSmartinh 			} else {
322ad54cec9Smartinh 				if (ldap_merge_values(a, vals) == 0)
323f12396bdSmartinh 					vals = NULL;
324f12396bdSmartinh 			}
3255d465952Smartinh 			break;
3265d465952Smartinh 		case LDAP_MOD_DELETE:
3277c698af4Sguenther 			/*
3287c698af4Sguenther 			 * We're already in the "SET OF value
3297e500a57Srob 			 * AttributeValue" (see RFC4511 section
3307e500a57Srob 			 * 4.1.7) have either no content, so all values
3317c698af4Sguenther 			 * for the attribute gets deleted, or we
3327c698af4Sguenther 			 * have a (first) octetstring (there is one
3337c698af4Sguenther 			 * for each AttributeValue to be deleted)
3347c698af4Sguenther 			 */
3355d465952Smartinh 			if (vals->be_sub &&
3367c698af4Sguenther 			    vals->be_sub->be_type == BER_TYPE_OCTETSTRING) {
337*5387241fSclaudio 				if (ldap_del_values(a, vals) == 1)
338*5387241fSclaudio 					ldap_del_attribute(entry, attr);
3397c698af4Sguenther 			} else {
3405d465952Smartinh 				ldap_del_attribute(entry, attr);
3417c698af4Sguenther 			}
3425d465952Smartinh 			break;
3435d465952Smartinh 		case LDAP_MOD_REPLACE:
344ad54cec9Smartinh 			if (vals->be_sub != NULL &&
345ad54cec9Smartinh 			    vals->be_sub->be_type != BER_TYPE_EOC) {
346f12396bdSmartinh 				if (a == NULL) {
347f12396bdSmartinh 					if (ldap_add_attribute(entry, attr, vals) != NULL)
348f12396bdSmartinh 						vals = NULL;
349f12396bdSmartinh 				} else {
350ad54cec9Smartinh 					if (ldap_set_values(a, vals) == 0)
351f12396bdSmartinh 						vals = NULL;
352f12396bdSmartinh 				}
353ad54cec9Smartinh 			} else if (a != NULL)
3545d465952Smartinh 				ldap_del_attribute(entry, attr);
3555d465952Smartinh 			break;
3565d465952Smartinh 		}
357f12396bdSmartinh 
358f12396bdSmartinh 		if (vals != NULL) {
359696b5899Stb 			ober_free_elements(vals);
360f12396bdSmartinh 			vals = NULL;
361f12396bdSmartinh 		}
3625d465952Smartinh 	}
3635d465952Smartinh 
3645d465952Smartinh 	if ((rc = validate_entry(dn, entry, ns->relax)) != LDAP_SUCCESS)
365a9ac9ba1Smartinh 		goto done;
3665d465952Smartinh 
367696b5899Stb 	set = ober_add_set(NULL);
368696b5899Stb 	ober_add_string(set, req->conn->binddn ? req->conn->binddn : "");
3695d465952Smartinh 	if ((a = ldap_get_attribute(entry, "modifiersName")) != NULL)
3705d465952Smartinh 		ldap_set_values(a, set);
3715d465952Smartinh 	else
3725d465952Smartinh 		ldap_add_attribute(entry, "modifiersName", set);
3735d465952Smartinh 
374696b5899Stb 	set = ober_add_set(NULL);
375696b5899Stb 	ober_add_string(set, ldap_now());
3765d465952Smartinh 	if ((a = ldap_get_attribute(entry, "modifyTimestamp")) != NULL)
3775d465952Smartinh 		ldap_set_values(a, set);
3785d465952Smartinh 	else
3795d465952Smartinh 		ldap_add_attribute(entry, "modifyTimestamp", set);
3805d465952Smartinh 
3811761b184Smartinh 	if (namespace_update(ns, dn, entry) == 0 && namespace_commit(ns) == 0)
382a9ac9ba1Smartinh 		rc = LDAP_SUCCESS;
3830279e526Smartinh 	else
384a9ac9ba1Smartinh 		rc = LDAP_OTHER;
385a9ac9ba1Smartinh 
386a9ac9ba1Smartinh done:
387f12396bdSmartinh 	if (vals != NULL)
388696b5899Stb 		ober_free_elements(vals);
3890279e526Smartinh 	namespace_abort(ns);
3901761b184Smartinh 	return ldap_respond(req, rc);
3915d465952Smartinh }
3925d465952Smartinh 
393