Edit

IABSD.fr/src/usr.sbin/ldapd/namespace.c

Branch :

  • Show log

    Commit

  • Author : martijn
    Date : 2020-03-05 07:39:25
    Hash : 403185e4
    Message : Fix ldapd datadir location. Diff from roklein <at> roklein <dot> de OK claudio@

  • usr.sbin/ldapd/namespace.c
  • /*	$OpenBSD: namespace.c,v 1.20 2020/03/05 07:39:25 martijn 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 <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <zlib.h>
    
    #include "ldapd.h"
    #include "log.h"
    
    extern char		*datadir;
    
    /* Maximum number of requests to queue per namespace during compaction.
     * After this many requests, we return LDAP_BUSY.
     */
    #define MAX_REQUEST_QUEUE	 10000
    
    static struct btval	*namespace_find(struct namespace *ns, char *dn);
    static void		 namespace_queue_replay(int fd, short event, void *arg);
    static int		 namespace_set_fd(struct namespace *ns,
    			    struct btree **bt, int fd, unsigned int flags);
    
    int
    namespace_begin_txn(struct namespace *ns, struct btree_txn **data_txn,
        struct btree_txn **indx_txn, int rdonly)
    {
    	if (ns->data_db == NULL || ns->indx_db == NULL) {
    		errno = EBUSY;	/* namespace is being reopened */
    		return -1;
    	}
    
    	if ((*data_txn = btree_txn_begin(ns->data_db, rdonly)) == NULL ||
    	    (*indx_txn = btree_txn_begin(ns->indx_db, rdonly)) == NULL) {
    		if (errno == ESTALE) {
    			if (*data_txn == NULL)
    				namespace_reopen_data(ns);
    			else
    				namespace_reopen_indx(ns);
    			errno = EBUSY;
    		}
    		log_warn("failed to open transaction");
    		btree_txn_abort(*data_txn);
    		*data_txn = NULL;
    		return -1;
    	}
    
    	return 0;
    }
    
    int
    namespace_begin(struct namespace *ns)
    {
    	return namespace_begin_txn(ns, &ns->data_txn, &ns->indx_txn, 0);
    }
    
    int
    namespace_commit(struct namespace *ns)
    {
    	if (ns->indx_txn != NULL &&
    	    btree_txn_commit(ns->indx_txn) != BT_SUCCESS) {
    		log_warn("%s(indx): commit failed", ns->suffix);
    		btree_txn_abort(ns->data_txn);
    		ns->indx_txn = ns->data_txn = NULL;
    		return -1;
    	}
    	ns->indx_txn = NULL;
    
    	if (ns->data_txn != NULL &&
    	    btree_txn_commit(ns->data_txn) != BT_SUCCESS) {
    		log_warn("%s(data): commit failed", ns->suffix);
    		ns->data_txn = NULL;
    		return -1;
    	}
    	ns->data_txn = NULL;
    
    	return 0;
    }
    
    void
    namespace_abort(struct namespace *ns)
    {
    	btree_txn_abort(ns->data_txn);
    	ns->data_txn = NULL;
    
    	btree_txn_abort(ns->indx_txn);
    	ns->indx_txn = NULL;
    }
    
    int
    namespace_open(struct namespace *ns)
    {
    	unsigned int	 db_flags = 0;
    
    	assert(ns);
    	assert(ns->suffix);
    
    	if (ns->sync == 0)
    		db_flags |= BT_NOSYNC;
    
    	if (asprintf(&ns->data_path, "%s/%s_data.db", datadir, ns->suffix) == -1)
    		return -1;
    	log_info("opening namespace %s", ns->suffix);
    	ns->data_db = btree_open(ns->data_path, db_flags | BT_REVERSEKEY, 0644);
    	if (ns->data_db == NULL)
    		return -1;
    
    	btree_set_cache_size(ns->data_db, ns->cache_size);
    
    	if (asprintf(&ns->indx_path, "%s/%s_indx.db", datadir, ns->suffix) == -1)
    		return -1;
    	ns->indx_db = btree_open(ns->indx_path, db_flags, 0644);
    	if (ns->indx_db == NULL)
    		return -1;
    
    	btree_set_cache_size(ns->indx_db, ns->index_cache_size);
    
    	/* prepare request queue scheduler */
    	evtimer_set(&ns->ev_queue, namespace_queue_replay, ns);
    
    	return 0;
    }
    
    static int
    namespace_reopen(const char *path)
    {
    	struct open_req		 req;
    
    	log_debug("asking parent to open %s", path);
    
    	memset(&req, 0, sizeof(req));
    	if (strlcpy(req.path, path, sizeof(req.path)) >= sizeof(req.path)) {
    		log_warnx("%s: path truncated", __func__);
    		return -1;
    	}
    
    	return imsgev_compose(iev_ldapd, IMSG_LDAPD_OPEN, 0, 0, -1, &req,
    	    sizeof(req));
    }
    
    int
    namespace_reopen_data(struct namespace *ns)
    {
    	if (ns->data_db != NULL) {
    		btree_close(ns->data_db);
    		ns->data_db = NULL;
    		return namespace_reopen(ns->data_path);
    	}
    	return 1;
    }
    
    int
    namespace_reopen_indx(struct namespace *ns)
    {
    	if (ns->indx_db != NULL) {
    		btree_close(ns->indx_db);
    		ns->indx_db = NULL;
    		return namespace_reopen(ns->indx_path);
    	}
    	return 1;
    }
    
    static int
    namespace_set_fd(struct namespace *ns, struct btree **bt, int fd,
        unsigned int flags)
    {
    	log_info("reopening namespace %s (entries)", ns->suffix);
    	btree_close(*bt);
    	if (ns->sync == 0)
    		flags |= BT_NOSYNC;
    	*bt = btree_open_fd(fd, flags);
    	if (*bt == NULL)
    		return -1;
    	return 0;
    }
    
    int
    namespace_set_data_fd(struct namespace *ns, int fd)
    {
    	return namespace_set_fd(ns, &ns->data_db, fd, BT_REVERSEKEY);
    }
    
    int
    namespace_set_indx_fd(struct namespace *ns, int fd)
    {
    	return namespace_set_fd(ns, &ns->indx_db, fd, 0);
    }
    
    void
    namespace_close(struct namespace *ns)
    {
    	struct conn		*conn;
    	struct search		*search, *next;
    	struct request		*req;
    
    	/* Cancel any queued requests for this namespace.
    	 */
    	if (ns->queued_requests > 0) {
    		log_warnx("cancelling %u queued requests on namespace %s",
    		    ns->queued_requests, ns->suffix);
    		while ((req = TAILQ_FIRST(&ns->request_queue)) != NULL) {
    			TAILQ_REMOVE(&ns->request_queue, req, next);
    			ldap_respond(req, LDAP_UNAVAILABLE);
    		}
    	}
    
    	/* Cancel any searches on this namespace.
    	 */
    	TAILQ_FOREACH(conn, &conn_list, next) {
    		for (search = TAILQ_FIRST(&conn->searches); search != NULL;
    		    search = next) {
    			next = TAILQ_NEXT(search, next);
    			if (search->ns == ns)
    				search_close(search);
    		}
    	}
    
    	free(ns->suffix);
    	btree_close(ns->data_db);
    	btree_close(ns->indx_db);
    	if (evtimer_pending(&ns->ev_queue, NULL))
    		evtimer_del(&ns->ev_queue);
    	free(ns->data_path);
    	free(ns->indx_path);
    	free(ns);
    }
    
    void
    namespace_remove(struct namespace *ns)
    {
    	TAILQ_REMOVE(&conf->namespaces, ns, next);
    	namespace_close(ns);
    }
    
    static struct btval *
    namespace_find(struct namespace *ns, char *dn)
    {
    	struct btval		 key;
    	static struct btval	 val;
    
    	if (ns->data_db == NULL) {
    		errno = EBUSY;	/* namespace is being reopened */
    		return NULL;
    	}
    
    	memset(&key, 0, sizeof(key));
    	memset(&val, 0, sizeof(val));
    
    	key.data = dn;
    	key.size = strlen(dn);
    
    	if (btree_txn_get(ns->data_db, ns->data_txn, &key, &val) != 0) {
    		if (errno == ENOENT)
    			log_debug("%s: dn not found", dn);
    		else
    			log_warn("%s", dn);
    
    		if (errno == ESTALE)
    			namespace_reopen_data(ns);
    
    		return NULL;
    	}
    
    	return &val;
    }
    
    struct ber_element *
    namespace_get(struct namespace *ns, char *dn)
    {
    	struct ber_element	*elm;
    	struct btval		*val;
    
    	if ((val = namespace_find(ns, dn)) == NULL)
    		return NULL;
    
    	elm = namespace_db2ber(ns, val);
    	btval_reset(val);
    	return elm;
    }
    
    int
    namespace_exists(struct namespace *ns, char *dn)
    {
    	struct btval		*val;
    
    	if ((val = namespace_find(ns, dn)) == NULL)
    		return 0;
    	btval_reset(val);
    	return 1;
    }
    
    int
    namespace_ber2db(struct namespace *ns, struct ber_element *root,
        struct btval *val)
    {
    	return ber2db(root, val, ns->compression_level);
    }
    
    struct ber_element *
    namespace_db2ber(struct namespace *ns, struct btval *val)
    {
    	return db2ber(val, ns->compression_level);
    }
    
    static int
    namespace_put(struct namespace *ns, char *dn, struct ber_element *root,
        int update)
    {
    	int			 rc;
    	struct btval		 key, val;
    
    	assert(ns != NULL);
    	assert(ns->data_txn != NULL);
    	assert(ns->indx_txn != NULL);
    
    	memset(&key, 0, sizeof(key));
    	key.data = dn;
    	key.size = strlen(dn);
    
    	if (namespace_ber2db(ns, root, &val) != 0)
    		return BT_FAIL;
    
    	rc = btree_txn_put(NULL, ns->data_txn, &key, &val,
    	    update ? 0 : BT_NOOVERWRITE);
    	if (rc != BT_SUCCESS) {
    		if (errno == EEXIST)
    			log_debug("%s: already exists", dn);
    		else
    			log_warn("%s", dn);
    		goto done;
    	}
    
    	/* FIXME: if updating, try harder to just update changed indices.
    	 */
    	if (update && (rc = unindex_entry(ns, &key, root)) != BT_SUCCESS)
    		goto done;
    
    	rc = index_entry(ns, &key, root);
    
    done:
    	btval_reset(&val);
    	return rc;
    }
    
    int
    namespace_add(struct namespace *ns, char *dn, struct ber_element *root)
    {
    	return namespace_put(ns, dn, root, 0);
    }
    
    int
    namespace_update(struct namespace *ns, char *dn, struct ber_element *root)
    {
    	return namespace_put(ns, dn, root, 1);
    }
    
    int
    namespace_del(struct namespace *ns, char *dn)
    {
    	int			 rc;
    	struct ber_element	*root;
    	struct btval		 key, data;
    
    	assert(ns != NULL);
    	assert(ns->indx_txn != NULL);
    	assert(ns->data_txn != NULL);
    
    	memset(&key, 0, sizeof(key));
    	memset(&data, 0, sizeof(data));
    
    	key.data = dn;
    	key.size = strlen(key.data);
    
    	rc = btree_txn_del(NULL, ns->data_txn, &key, &data);
    	if (rc == BT_SUCCESS && (root = namespace_db2ber(ns, &data)) != NULL)
    		rc = unindex_entry(ns, &key, root);
    
    	btval_reset(&data);
    	return rc;
    }
    
    int
    namespace_has_referrals(struct namespace *ns)
    {
    	return !SLIST_EMPTY(&ns->referrals);
    }
    
    struct namespace *
    namespace_lookup_base(const char *basedn, int include_referrals)
    {
    	size_t			 blen, slen;
    	struct namespace	*ns, *matched_ns = NULL;
    
    	assert(basedn);
    	blen = strlen(basedn);
    
    	TAILQ_FOREACH(ns, &conf->namespaces, next) {
    		slen = strlen(ns->suffix);
    		if ((include_referrals || !namespace_has_referrals(ns)) &&
    		    blen >= slen &&
    		    bcmp(basedn + blen - slen, ns->suffix, slen) == 0) {
    			/* Match the longest namespace suffix. */
    			if (matched_ns == NULL ||
    			    strlen(ns->suffix) > strlen(matched_ns->suffix))
    				matched_ns = ns;
    		}
    	}
    
    	return matched_ns;
    }
    
    struct namespace *
    namespace_for_base(const char *basedn)
    {
    	return namespace_lookup_base(basedn, 0);
    }
    
    struct referrals *
    namespace_referrals(const char *basedn)
    {
    	struct namespace	*ns;
    
    	if ((ns = namespace_lookup_base(basedn, 1)) != NULL &&
    	    namespace_has_referrals(ns))
    		return &ns->referrals;
    
    	if (!SLIST_EMPTY(&conf->referrals))
    		return &conf->referrals;
    
    	return NULL;
    }
    
    int
    namespace_has_index(struct namespace *ns, const char *attr,
        enum index_type type)
    {
    	struct attr_index	*ai;
    
    	assert(ns);
    	assert(attr);
    	TAILQ_FOREACH(ai, &ns->indices, next) {
    		if (strcasecmp(attr, ai->attr) == 0 && ai->type == type)
    			return 1;
    	}
    
    	return 0;
    }
    
    /* Queues modification requests while the namespace is being reopened.
     */
    int
    namespace_queue_request(struct namespace *ns, struct request *req)
    {
    	if (ns->queued_requests > MAX_REQUEST_QUEUE) {
    		log_warn("%u requests already queued, sorry",
    		    ns->queued_requests);
    		return -1;
    	}
    
    	TAILQ_INSERT_TAIL(&ns->request_queue, req, next);
    	ns->queued_requests++;
    
    	if (!evtimer_pending(&ns->ev_queue, NULL))
    		namespace_queue_schedule(ns, 250000);
    
    	return 0;
    }
    
    static void
    namespace_queue_replay(int fd, short event, void *data)
    {
    	struct namespace	*ns = data;
    	struct request		*req;
    
    	if (ns->data_db == NULL || ns->indx_db == NULL) {
    		log_debug("%s: database is being reopened", ns->suffix);
    		return;		/* Database is being reopened. */
    	}
    
    	if ((req = TAILQ_FIRST(&ns->request_queue)) == NULL)
    		return;
    	TAILQ_REMOVE(&ns->request_queue, req, next);
    
    	log_debug("replaying queued request");
    	req->replayed = 1;
    	request_dispatch(req);
    	ns->queued_requests--;
    
    	if (!evtimer_pending(&ns->ev_queue, NULL))
    		namespace_queue_schedule(ns, 0);
    }
    
    void
    namespace_queue_schedule(struct namespace *ns, unsigned int usec)
    {
    	struct timeval	 tv;
    
    	tv.tv_sec = 0;
    	tv.tv_usec = usec;
    	evtimer_add(&ns->ev_queue, &tv);
    }
    
    /* Cancel all queued requests from the given connection. Drops matching
     * requests from all namespaces without sending a response.
     */
    void
    namespace_cancel_conn(struct conn *conn)
    {
    	struct namespace	*ns;
    	struct request		*req, *next;
    
    	TAILQ_FOREACH(ns, &conf->namespaces, next) {
    		for (req = TAILQ_FIRST(&ns->request_queue); req != NULL;
    		    req = next) {
    			next = TAILQ_NEXT(req, next);
    
    			if (req->conn == conn) {
    				TAILQ_REMOVE(&ns->request_queue, req, next);
    				request_free(req);
    			}
    		}
    	}
    }
    
    int
    namespace_conn_queue_count(struct conn *conn)
    {
    	struct namespace	*ns;
    	struct request		*req;
    	int			 count = 0;
    
    	TAILQ_FOREACH(ns, &conf->namespaces, next) {
    		TAILQ_FOREACH(req, &ns->request_queue, next) {
    			if (req->conn == conn)
    				count++;
    		}
    	}
    
    	return count;
    }