summaryrefslogtreecommitdiff
path: root/usr.sbin/ldapd
diff options
context:
space:
mode:
authorMartin Hedenfal <martinh@cvs.openbsd.org>2010-05-31 17:36:32 +0000
committerMartin Hedenfal <martinh@cvs.openbsd.org>2010-05-31 17:36:32 +0000
commit28c2c79b07ed71b5906389cdda8588b4b5ddb055 (patch)
tree8eb48a984a6919ca495b6f04acc0ff17b126923f /usr.sbin/ldapd
parent4351d86e27905803bc2e0db9b38ad6075b8ceee1 (diff)
Initial import of ldapd, a small LDAP daemon. Work in progress.
ok deraadt@ jacekm@ gilles@ back@ henning@ blambert@
Diffstat (limited to 'usr.sbin/ldapd')
-rw-r--r--usr.sbin/ldapd/Makefile20
-rw-r--r--usr.sbin/ldapd/aldap.h137
-rw-r--r--usr.sbin/ldapd/attributes.c247
-rw-r--r--usr.sbin/ldapd/auth.c372
-rw-r--r--usr.sbin/ldapd/ber.c1247
-rw-r--r--usr.sbin/ldapd/ber.h128
-rw-r--r--usr.sbin/ldapd/btest.c133
-rw-r--r--usr.sbin/ldapd/btree.3230
-rw-r--r--usr.sbin/ldapd/btree.c2905
-rw-r--r--usr.sbin/ldapd/btree.h128
-rw-r--r--usr.sbin/ldapd/compact.c185
-rw-r--r--usr.sbin/ldapd/conn.c307
-rw-r--r--usr.sbin/ldapd/control.c379
-rw-r--r--usr.sbin/ldapd/filter.c253
-rw-r--r--usr.sbin/ldapd/index.c491
-rw-r--r--usr.sbin/ldapd/ldapd.894
-rw-r--r--usr.sbin/ldapd/ldapd.c331
-rw-r--r--usr.sbin/ldapd/ldapd.conf.5230
-rw-r--r--usr.sbin/ldapd/ldapd.h617
-rw-r--r--usr.sbin/ldapd/ldape.c360
-rw-r--r--usr.sbin/ldapd/log.c172
-rw-r--r--usr.sbin/ldapd/modify.c237
-rw-r--r--usr.sbin/ldapd/namespace.c594
-rw-r--r--usr.sbin/ldapd/parse.y1529
-rw-r--r--usr.sbin/ldapd/schema/core.schema678
-rw-r--r--usr.sbin/ldapd/schema/inetorgperson.schema81
-rw-r--r--usr.sbin/ldapd/schema/nis.schema250
-rw-r--r--usr.sbin/ldapd/search.c827
-rw-r--r--usr.sbin/ldapd/ssl.c710
-rw-r--r--usr.sbin/ldapd/ssl_privsep.c252
-rw-r--r--usr.sbin/ldapd/util.c98
-rw-r--r--usr.sbin/ldapd/uuid.c361
-rw-r--r--usr.sbin/ldapd/uuid.h61
-rw-r--r--usr.sbin/ldapd/validate.c282
34 files changed, 14926 insertions, 0 deletions
diff --git a/usr.sbin/ldapd/Makefile b/usr.sbin/ldapd/Makefile
new file mode 100644
index 00000000000..3c056b44ad1
--- /dev/null
+++ b/usr.sbin/ldapd/Makefile
@@ -0,0 +1,20 @@
+# $OpenBSD: Makefile,v 1.1 2010/05/31 17:36:31 martinh Exp $
+
+PROG= ldapd
+MAN= ldapd.8 ldapd.conf.5
+SRCS= ber.c log.c control.c \
+ util.c ldapd.c ldape.c conn.c attributes.c namespace.c \
+ btree.c filter.c search.c parse.y \
+ auth.c modify.c index.c ssl.c ssl_privsep.c compact.c \
+ validate.c uuid.c
+
+LDADD= -levent -lcrypto -lssl -lz -lutil
+DPADD= ${LIBEVENT} ${LIBCRYPTO} ${LIBSSL} ${LIBZ} ${LIBUTIL}
+CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare
+CLEANFILES+= y.tab.h parse.c
+
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/ldapd/aldap.h b/usr.sbin/ldapd/aldap.h
new file mode 100644
index 00000000000..22a01bd5452
--- /dev/null
+++ b/usr.sbin/ldapd/aldap.h
@@ -0,0 +1,137 @@
+/* $OpenBSD: aldap.h,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
+ * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
+ *
+ * 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 <stdio.h>
+#include "ber.h"
+
+enum protocol_op {
+ LDAP_REQ_BIND = 0,
+ LDAP_RES_BIND = 1,
+ LDAP_REQ_UNBIND_30 = 2,
+ LDAP_REQ_SEARCH = 3,
+ LDAP_RES_SEARCH_ENTRY = 4,
+ LDAP_RES_SEARCH_RESULT = 5,
+ LDAP_REQ_MODIFY = 6,
+ LDAP_RES_MODIFY = 7,
+ LDAP_REQ_ADD = 8,
+ LDAP_RES_ADD = 9,
+ LDAP_REQ_DELETE_30 = 10,
+ LDAP_RES_DELETE = 11,
+ LDAP_REQ_MODRDN = 12,
+ LDAP_RES_MODRDN = 13,
+ LDAP_REQ_COMPARE = 14,
+ LDAP_RES_COMPARE = 15,
+ LDAP_REQ_ABANDON_30 = 16,
+
+ LDAP_RES_SEARCH_REFERENCE = 19,
+ LDAP_REQ_EXTENDED = 23,
+ LDAP_RES_EXTENDED = 24,
+};
+
+enum deref_aliases {
+ LDAP_DEREF_NEVER = 0,
+ LDAP_DEREF_SEARCHING = 1,
+ LDAP_DEREF_FINDING = 2,
+ LDAP_DEREF_ALWAYS = 3,
+};
+
+enum authentication_choice {
+ LDAP_AUTH_SIMPLE = 0,
+ LDAP_AUTH_SASL = 3,
+};
+
+enum scope {
+ LDAP_SCOPE_BASE = 0,
+ LDAP_SCOPE_ONELEVEL = 1,
+ LDAP_SCOPE_SUBTREE = 2,
+};
+
+enum result_code {
+ LDAP_SUCCESS = 0,
+ LDAP_OPERATIONS_ERROR = 1,
+ LDAP_PROTOCOL_ERROR = 2,
+ LDAP_TIMELIMIT_EXCEEDED = 3,
+ LDAP_SIZELIMIT_EXCEEDED = 4,
+ LDAP_COMPARE_FALSE = 5,
+ LDAP_COMPARE_TRUE = 6,
+ LDAP_STRONG_AUTH_NOT_SUPPORTED = 7,
+ LDAP_STRONG_AUTH_REQUIRED = 8,
+
+ LDAP_REFERRAL = 10,
+ LDAP_ADMINLIMIT_EXCEEDED = 11,
+ LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12,
+ LDAP_CONFIDENTIALITY_REQUIRED = 13,
+ LDAP_SASL_BIND_IN_PROGRESS = 14,
+ LDAP_NO_SUCH_ATTRIBUTE = 16,
+ LDAP_UNDEFINED_TYPE = 17,
+ LDAP_INAPPROPRIATE_MATCHING = 18,
+ LDAP_CONSTRAINT_VIOLATION = 19,
+ LDAP_TYPE_OR_VALUE_EXISTS = 20,
+ LDAP_INVALID_SYNTAX = 21,
+
+ LDAP_NO_SUCH_OBJECT = 32,
+ LDAP_ALIAS_PROBLEM = 33,
+ LDAP_INVALID_DN_SYNTAX = 34,
+
+ LDAP_ALIAS_DEREF_PROBLEM = 36,
+
+ LDAP_INAPPROPRIATE_AUTH = 48,
+ LDAP_INVALID_CREDENTIALS = 49,
+ LDAP_INSUFFICIENT_ACCESS = 50,
+ LDAP_BUSY = 51,
+ LDAP_UNAVAILABLE = 52,
+ LDAP_UNWILLING_TO_PERFORM = 53,
+ LDAP_LOOP_DETECT = 54,
+
+ LDAP_NAMING_VIOLATION = 64,
+ LDAP_OBJECT_CLASS_VIOLATION = 65,
+ LDAP_NOT_ALLOWED_ON_NONLEAF = 66,
+ LDAP_NOT_ALLOWED_ON_RDN = 67,
+ LDAP_ALREADY_EXISTS = 68,
+ LDAP_NO_OBJECT_CLASS_MODS = 69,
+
+ LDAP_AFFECTS_MULTIPLE_DSAS = 71,
+
+ LDAP_OTHER = 80,
+};
+
+enum filter {
+ LDAP_FILT_AND = 0,
+ LDAP_FILT_OR = 1,
+ LDAP_FILT_NOT = 2,
+ LDAP_FILT_EQ = 3,
+ LDAP_FILT_SUBS = 4,
+ LDAP_FILT_GE = 5,
+ LDAP_FILT_LE = 6,
+ LDAP_FILT_PRES = 7,
+ LDAP_FILT_APPR = 8,
+};
+
+enum subfilter {
+ LDAP_FILT_SUBS_INIT = 0,
+ LDAP_FILT_SUBS_ANY = 1,
+ LDAP_FILT_SUBS_FIN = 2,
+};
+
+enum modify_op {
+ LDAP_MOD_ADD = 0,
+ LDAP_MOD_DELETE = 1,
+ LDAP_MOD_REPLACE = 2,
+};
+
diff --git a/usr.sbin/ldapd/attributes.c b/usr.sbin/ldapd/attributes.c
new file mode 100644
index 00000000000..c6c724ea200
--- /dev/null
+++ b/usr.sbin/ldapd/attributes.c
@@ -0,0 +1,247 @@
+/* $OpenBSD: attributes.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 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/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#include "ldapd.h"
+
+struct ber_element *
+ldap_get_attribute(struct ber_element *entry, const char *attr)
+{
+ char *s;
+ struct ber_element *elm, *a;
+
+ assert(entry);
+ assert(attr);
+ if (entry->be_encoding != BER_TYPE_SEQUENCE)
+ return NULL;
+
+ for (elm = entry->be_sub; elm != NULL; elm = elm->be_next) {
+ a = elm->be_sub;
+ if (a && ber_get_string(a, &s) == 0 && strcasecmp(s, attr) == 0)
+ return a;
+ }
+
+ return NULL;
+}
+
+struct ber_element *
+ldap_find_attribute(struct ber_element *entry, struct attr_type *at)
+{
+ struct ber_element *elm = NULL;
+ struct name *an;
+
+ SLIST_FOREACH(an, at->names, next) {
+ if ((elm = ldap_get_attribute(entry, an->name)) != NULL)
+ return elm;
+ }
+ if (an == NULL)
+ elm = ldap_get_attribute(entry, at->oid);
+
+ return elm;
+}
+
+struct ber_element *
+ldap_find_value(struct ber_element *elm, const char *value)
+{
+ char *s;
+ struct ber_element *a;
+
+ if (elm == NULL)
+ return NULL;
+
+ for (a = elm->be_sub; a != NULL; a = a->be_next) {
+ if (ber_get_string(a, &s) == 0 && strcasecmp(s, value) == 0)
+ return a;
+ }
+
+ return NULL;
+}
+
+struct ber_element *
+ldap_add_attribute(struct ber_element *entry, const char *attr,
+ struct ber_element *value_set)
+{
+ struct ber_element *elm, *a, *last;
+
+ assert(entry);
+ assert(attr);
+ assert(value_set);
+
+ if (entry->be_encoding != BER_TYPE_SEQUENCE) {
+ log_warnx("entries should be a sequence");
+ return NULL;
+ }
+
+ if (value_set->be_type != BER_TYPE_SET) {
+ log_warnx("values should be a set");
+ return NULL;
+ }
+
+ last = entry->be_sub;
+ while (last && last->be_next != NULL)
+ last = last->be_next;
+
+ if ((elm = ber_add_sequence(last)) == NULL)
+ return NULL;
+ if ((a = ber_add_string(elm, attr)) == NULL) {
+ ber_free_elements(elm);
+ return NULL;
+ }
+ ber_link_elements(a, value_set);
+
+ return elm;
+}
+
+int
+ldap_set_values(struct ber_element *elm, struct ber_element *vals)
+{
+ char *attr;
+ struct ber_element *old_vals;
+
+ assert(elm);
+ assert(vals);
+ assert(vals->be_sub);
+
+ if (ber_scanf_elements(elm, "se(", &attr, &old_vals) != 0) {
+ log_warnx("failed to parse element");
+ return -1;
+ }
+
+ ber_free_elements(old_vals->be_sub);
+ old_vals->be_sub = NULL;
+ ber_link_elements(old_vals, vals->be_sub);
+
+ return 0;
+}
+
+int
+ldap_merge_values(struct ber_element *elm, struct ber_element *vals)
+{
+ char *attr;
+ struct ber_element *old_vals, *last;
+
+ assert(elm);
+ assert(vals);
+ assert(vals->be_type == BER_TYPE_SET);
+ assert(vals->be_sub);
+
+ if (ber_scanf_elements(elm, "se(", &attr, &old_vals) != 0) {
+ log_warnx("failed to parse element");
+ return -1;
+ }
+
+ last = old_vals->be_sub;
+ while (last && last->be_next)
+ last = last->be_next;
+
+ ber_link_elements(last, vals->be_sub);
+
+ return 0;
+}
+
+
+int
+ldap_del_attribute(struct ber_element *entry, const char *attrdesc)
+{
+ struct ber_element *attr, *prev = NULL;
+ char *s;
+
+ assert(entry);
+ assert(attrdesc);
+
+ attr = entry->be_sub;
+ while (attr) {
+ if (ber_scanf_elements(attr, "{s(", &s) != 0) {
+ log_warnx("failed to parse attribute");
+ return -1;
+ }
+
+ if (strcasecmp(s, attrdesc) == 0) {
+ if (prev == NULL)
+ entry->be_sub = attr->be_next;
+ else
+ prev->be_next = attr->be_next;
+ attr->be_next = NULL;
+ ber_free_elements(attr);
+ break;
+ }
+
+ prev = attr;
+ attr = attr->be_next;
+ }
+
+ return 0;
+}
+
+int
+ldap_del_values(struct ber_element *elm, struct ber_element *vals)
+{
+ char *attr;
+ struct ber_element *old_vals, *v, *x, *vk, *xk, *prev;
+ struct ber_element *removed;
+
+ assert(elm);
+ assert(vals);
+ assert(vals->be_sub);
+
+ if (ber_scanf_elements(elm, "se(", &attr, &old_vals) != 0) {
+ log_warnx("failed to parse element");
+ return -1;
+ }
+
+ prev = old_vals;
+ for (v = old_vals->be_sub; v; v = v->be_next) {
+ vk = v->be_sub;
+ for (x = vals->be_sub; x; x = x->be_next) {
+ xk = x->be_sub;
+ if (xk && vk->be_len == xk->be_len &&
+ memcmp(vk->be_val, xk->be_val, xk->be_len) == 0) {
+ removed = ber_unlink_elements(prev);
+ ber_link_elements(prev, removed->be_next);
+ ber_free_elements(removed);
+ break;
+ }
+ }
+ prev = v;
+ }
+
+ return 0;
+}
+
+char *
+ldap_strftime(time_t tm)
+{
+ static char tmbuf[16];
+ struct tm *gmt = gmtime(&tm);
+
+ strftime(tmbuf, sizeof(tmbuf), "%Y%m%d%H%M%SZ", gmt);
+ return tmbuf;
+}
+
+char *
+ldap_now(void)
+{
+ return ldap_strftime(time(0));
+}
+
diff --git a/usr.sbin/ldapd/auth.c b/usr.sbin/ldapd/auth.c
new file mode 100644
index 00000000000..2d461ef20ec
--- /dev/null
+++ b/usr.sbin/ldapd/auth.c
@@ -0,0 +1,372 @@
+/* $OpenBSD: auth.c,v 1.1 2010/05/31 17:36:31 martinh 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 <netinet/in.h>
+
+#include <openssl/sha.h>
+#include <pwd.h>
+#include <resolv.h> /* for b64_pton */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+static int
+aci_matches(struct aci *aci, struct conn *conn, struct namespace *ns,
+ char *dn, int rights, enum scope scope)
+{
+ struct btval key;
+
+ if ((rights & aci->rights) != rights)
+ return 0;
+
+ if (dn == NULL)
+ return 0;
+
+ if (aci->target != NULL) {
+ key.size = strlen(dn);
+ key.data = dn;
+
+ if (scope == LDAP_SCOPE_BASE) {
+ switch (aci->scope) {
+ case LDAP_SCOPE_BASE:
+ if (strcmp(dn, aci->target) != 0)
+ return 0;
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ if (!is_child_of(&key, aci->target))
+ return 0;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if (!has_suffix(&key, aci->target))
+ return 0;
+ break;
+ }
+ } else if (scope == LDAP_SCOPE_ONELEVEL) {
+ switch (aci->scope) {
+ case LDAP_SCOPE_BASE:
+ return 0;
+ case LDAP_SCOPE_ONELEVEL:
+ if (strcmp(dn, aci->target) != 0)
+ return 0;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if (!has_suffix(&key, aci->target))
+ return 0;
+ break;
+ }
+ } else if (scope == LDAP_SCOPE_SUBTREE) {
+ switch (aci->scope) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ return 0;
+ case LDAP_SCOPE_SUBTREE:
+ if (!has_suffix(&key, aci->target))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ if (aci->subject != NULL) {
+ if (conn->binddn == NULL)
+ return 0;
+ if (strcmp(aci->subject, "@") == 0) {
+ if (strcmp(dn, conn->binddn) != 0)
+ return 0;
+ } else if (strcmp(aci->subject, conn->binddn) != 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Returns true (1) if conn is authorized for op on dn in namespace.
+ */
+int
+authorized(struct conn *conn, struct namespace *ns, int rights, char *dn,
+ int scope)
+{
+ struct aci *aci;
+ int type = ACI_ALLOW;
+
+ /* Root DN is always allowed. */
+ if (conn->binddn && ns && strcasecmp(conn->binddn, ns->rootdn) == 0)
+ return 1;
+
+ /* Default to deny for write access. */
+ if ((rights & (ACI_WRITE | ACI_CREATE)) != 0)
+ type = ACI_DENY;
+
+ log_debug("requesting %02X access to %s by %s, in namespace %s",
+ rights,
+ dn ?: "any",
+ conn->binddn ?: "any",
+ ns ? ns->suffix : "global");
+
+ SIMPLEQ_FOREACH(aci, &conf->acl, entry) {
+ if (aci_matches(aci, conn, ns, dn, rights, scope)) {
+ type = aci->type;
+ log_debug("%s by: %s %02X access to %s by %s",
+ type == ACI_ALLOW ? "allowed" : "denied",
+ aci->type == ACI_ALLOW ? "allow" : "deny",
+ aci->rights,
+ aci->target ?: "any",
+ aci->subject ?: "any");
+ }
+ }
+
+ if (ns != NULL) {
+ SIMPLEQ_FOREACH(aci, &ns->acl, entry) {
+ if (aci_matches(aci, conn, ns, dn, rights, scope)) {
+ type = aci->type;
+ log_debug("%s by: %s %02X access to %s by %s",
+ type == ACI_ALLOW ? "allowed" : "denied",
+ aci->type == ACI_ALLOW ? "allow" : "deny",
+ aci->rights,
+ aci->target ?: "any",
+ aci->subject ?: "any");
+ }
+ }
+ }
+
+ return type == ACI_ALLOW ? 1 : 0;
+}
+
+static int
+check_password(const char *stored_passwd, const char *passwd)
+{
+ int sz;
+ char *encpw;
+ unsigned char *salt;
+ unsigned char md[SHA_DIGEST_LENGTH];
+ unsigned char tmp[128];
+ SHA_CTX ctx;
+
+ if (strncmp(stored_passwd, "{SHA}", 5) == 0) {
+ sz = b64_pton(stored_passwd + 5, tmp, sizeof(tmp));
+ if (sz != SHA_DIGEST_LENGTH)
+ return -1;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, passwd, strlen(passwd));
+ SHA1_Final(md, &ctx);
+ return memcmp(md, tmp, SHA_DIGEST_LENGTH);
+ }
+ else if (strncmp(stored_passwd, "{SSHA}", 6) == 0) {
+ sz = b64_pton(stored_passwd + 6, tmp, sizeof(tmp));
+ if (sz <= SHA_DIGEST_LENGTH)
+ return -1;
+ salt = tmp + SHA_DIGEST_LENGTH;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, passwd, strlen(passwd));
+ SHA1_Update(&ctx, salt, sz - SHA_DIGEST_LENGTH);
+ SHA1_Final(md, &ctx);
+ return memcmp(md, tmp, SHA_DIGEST_LENGTH);
+ }
+ else if (strncmp(stored_passwd, "{CRYPT}", 7) == 0) {
+ encpw = crypt(passwd, stored_passwd + 7);
+ if (encpw == NULL)
+ return -1;
+ return strcmp(encpw, stored_passwd + 7);
+ }
+ else
+ return strcmp(stored_passwd, passwd);
+}
+
+static int
+ldap_auth_sasl(struct request *req, char *binddn, struct ber_element *params)
+{
+ size_t len;
+ char *method;
+ char *authzid, *authcid, *password;
+ char *creds;
+ struct auth_req auth_req;
+
+ if (ber_scanf_elements(params, "{sx", &method, &creds, &len) != 0)
+ return LDAP_PROTOCOL_ERROR;
+
+ if (strcmp(method, "PLAIN") != 0)
+ return LDAP_STRONG_AUTH_NOT_SUPPORTED;
+
+ if ((req->conn->s_flags & F_SECURE) == 0) {
+ log_info("refusing plain sasl bind on insecure connection");
+ return LDAP_CONFIDENTIALITY_REQUIRED;
+ }
+
+ authzid = creds;
+ authcid = memchr(creds, '\0', len);
+ if (authcid == NULL || authcid + 2 >= creds + len)
+ return LDAP_PROTOCOL_ERROR;
+ authcid++;
+ password = memchr(authcid, '\0', len - (authcid - creds));
+ if (password == NULL || password + 2 >= creds + len)
+ return LDAP_PROTOCOL_ERROR;
+ password++;
+
+ log_debug("sasl authorization id = [%s]", authzid);
+ log_debug("sasl authentication id = [%s]", authcid);
+
+ bzero(&auth_req, sizeof(auth_req));
+ if (strlcpy(auth_req.name, authcid, sizeof(auth_req.name)) >=
+ sizeof(auth_req.name))
+ return LDAP_OPERATIONS_ERROR;
+ if (strlcpy(auth_req.password, password, sizeof(auth_req.password)) >=
+ sizeof(auth_req.password))
+ return LDAP_OPERATIONS_ERROR;
+ auth_req.fd = req->conn->fd;
+ auth_req.msgid = req->msgid;
+ bzero(password, strlen(password));
+
+ if (imsg_compose(&iev_ldapd->ibuf, IMSG_LDAPD_AUTH, 0, 0, -1, &auth_req,
+ sizeof(auth_req)) == -1)
+ return LDAP_OPERATIONS_ERROR;
+ imsg_event_add(iev_ldapd);
+
+ req->conn->bind_req = req;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+ldap_auth_simple(struct request *req, char *binddn, struct ber_element *auth)
+{
+ int ok = 0;
+ char *password;
+ char *user_password;
+ struct namespace *ns;
+ struct ber_element *elm = NULL, *pw = NULL;
+
+ if (*binddn == '\0') {
+ free(req->conn->binddn); /* anonymous bind */
+ req->conn->binddn = NULL;
+ log_debug("anonymous bind");
+ return LDAP_SUCCESS;
+ }
+
+ if ((req->conn->s_flags & F_SECURE) == 0) {
+ log_info("refusing non-anonymous bind on insecure connection");
+ return LDAP_CONFIDENTIALITY_REQUIRED;
+ }
+
+ if (ber_scanf_elements(auth, "s", &password) != 0)
+ return LDAP_PROTOCOL_ERROR;
+
+ if (*password == '\0') {
+ /* Unauthenticated Authentication Mechanism of Simple Bind */
+ log_debug("refusing unauthenticated bind");
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ if ((ns = namespace_for_base(binddn)) == NULL)
+ return LDAP_INVALID_CREDENTIALS;
+
+ if (strcmp(ns->rootdn, binddn) == 0) {
+ if (check_password(ns->rootpw, password) == 0)
+ ok = 1;
+ } else {
+ if (!authorized(req->conn, ns, ACI_BIND, binddn,
+ LDAP_SCOPE_BASE))
+ return LDAP_INSUFFICIENT_ACCESS;
+
+ elm = namespace_get(ns, binddn);
+ if (elm != NULL)
+ pw = ldap_get_attribute(elm, "userPassword");
+ if (pw != NULL) {
+ for (elm = pw->be_next->be_sub; elm;
+ elm = elm->be_next) {
+ if (ber_get_string(elm, &user_password) != 0)
+ continue;
+ if (check_password(user_password,
+ password) == 0) {
+ ok = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (ok) {
+ free(req->conn->binddn);
+ if ((req->conn->binddn = strdup(binddn)) == NULL)
+ return LDAP_OTHER;
+ log_debug("successfully authenticated as %s",
+ req->conn->binddn);
+ return LDAP_SUCCESS;
+ } else
+ return LDAP_INVALID_CREDENTIALS;
+}
+
+void
+ldap_bind_continue(struct conn *conn, int ok)
+{
+ int rc;
+
+ if (ok)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_INVALID_CREDENTIALS;
+
+ ldap_respond(conn->bind_req, rc);
+ conn->bind_req = NULL;
+}
+
+int
+ldap_bind(struct request *req)
+{
+ int rc = LDAP_OTHER;
+ long long ver;
+ char *binddn;
+ struct ber_element *auth;
+
+ ++stats.req_bind;
+
+ if (ber_scanf_elements(req->op, "{ise", &ver, &binddn, &auth) != 0) {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ if (req->conn->bind_req) {
+ log_debug("aborting bind in progress with msgid %lld",
+ req->conn->bind_req->msgid);
+ request_free(req->conn->bind_req);
+ req->conn->bind_req = NULL;
+ }
+
+ normalize_dn(binddn);
+ log_debug("bind dn = %s", binddn);
+
+ switch (auth->be_type) {
+ case LDAP_AUTH_SIMPLE:
+ rc = ldap_auth_simple(req, binddn, auth);
+ break;
+ case LDAP_AUTH_SASL:
+ if ((rc = ldap_auth_sasl(req, binddn, auth)) == LDAP_SUCCESS)
+ return LDAP_SUCCESS;
+ break;
+ default:
+ rc = LDAP_STRONG_AUTH_NOT_SUPPORTED;
+ break;
+ }
+
+done:
+ return ldap_respond(req, rc);
+}
+
diff --git a/usr.sbin/ldapd/ber.c b/usr.sbin/ldapd/ber.c
new file mode 100644
index 00000000000..602bbe7074d
--- /dev/null
+++ b/usr.sbin/ldapd/ber.c
@@ -0,0 +1,1247 @@
+/* $OpenBSD: ber.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
+ *
+ * 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/param.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <err.h> /* XXX for debug output */
+#include <stdio.h> /* XXX for debug output */
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "ber.h"
+
+#define BER_TYPE_CONSTRUCTED 0x20 /* otherwise primitive */
+#define BER_TYPE_SINGLE_MAX 30
+#define BER_TAG_MASK 0x1f
+#define BER_TAG_MORE 0x80 /* more subsequent octets */
+#define BER_TAG_TYPE_MASK 0x7f
+#define BER_CLASS_SHIFT 6
+
+static int ber_dump_element(struct ber *ber, struct ber_element *root);
+static void ber_dump_header(struct ber *ber, struct ber_element *root);
+static void ber_putc(struct ber *ber, u_char c);
+static void ber_write(struct ber *ber, void *buf, size_t len);
+static ssize_t get_id(struct ber *b, unsigned long *tag, int *class,
+ int *cstruct);
+static ssize_t get_len(struct ber *b, ssize_t *len);
+static ssize_t ber_read_element(struct ber *ber, struct ber_element *elm);
+static ssize_t ber_readbuf(struct ber *b, void *buf, size_t nbytes);
+static ssize_t ber_getc(struct ber *b, u_char *c);
+static ssize_t ber_read(struct ber *ber, void *buf, size_t len);
+
+#ifdef DEBUG
+#define DPRINTF(...) printf(__VA_ARGS__)
+#else
+#define DPRINTF(...) do { } while (0)
+#endif
+
+struct ber_element *
+ber_get_element(unsigned long encoding)
+{
+ struct ber_element *elm;
+
+ if ((elm = calloc(1, sizeof(*elm))) == NULL)
+ return NULL;
+
+ elm->be_encoding = encoding;
+ ber_set_header(elm, BER_CLASS_UNIVERSAL, BER_TYPE_DEFAULT);
+
+ return elm;
+}
+
+void
+ber_set_header(struct ber_element *elm, int class, unsigned long type)
+{
+ elm->be_class = class & BER_CLASS_MASK;
+ if (type == BER_TYPE_DEFAULT)
+ type = elm->be_encoding;
+ elm->be_type = type;
+}
+
+void
+ber_link_elements(struct ber_element *prev, struct ber_element *elm)
+{
+ if (prev != NULL) {
+ if ((prev->be_encoding == BER_TYPE_SEQUENCE ||
+ prev->be_encoding == BER_TYPE_SET) &&
+ prev->be_sub == NULL)
+ prev->be_sub = elm;
+ else
+ prev->be_next = elm;
+ }
+}
+
+struct ber_element *
+ber_unlink_elements(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((prev->be_encoding == BER_TYPE_SEQUENCE ||
+ prev->be_encoding == BER_TYPE_SET) &&
+ prev->be_sub != NULL) {
+ elm = prev->be_sub;
+ prev->be_sub = NULL;
+ } else {
+ elm = prev->be_next;
+ prev->be_next = NULL;
+ }
+
+ return (elm);
+}
+
+void
+ber_replace_elements(struct ber_element *prev, struct ber_element *new)
+{
+ struct ber_element *ber, *next;
+
+ ber = ber_unlink_elements(prev);
+ next = ber_unlink_elements(ber);
+ ber_link_elements(new, next);
+ ber_link_elements(prev, new);
+
+ /* cleanup old element */
+ ber_free_elements(ber);
+}
+
+struct ber_element *
+ber_add_sequence(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_SEQUENCE)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_set(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_SET)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_enumerated(struct ber_element *prev, long long val)
+{
+ struct ber_element *elm;
+ u_int i, len = 0;
+ u_char cur, last = 0;
+
+ if ((elm = ber_get_element(BER_TYPE_ENUMERATED)) == NULL)
+ return NULL;
+
+ elm->be_numeric = val;
+
+ for (i = 0; i < sizeof(long long); i++) {
+ cur = val & 0xff;
+ if (cur != 0 && cur != 0xff)
+ len = i;
+ if ((cur == 0 && last & 0x80) ||
+ (cur == 0xff && (last & 0x80) == 0))
+ len = i;
+ val >>= 8;
+ last = cur;
+ }
+ elm->be_len = len + 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_integer(struct ber_element *prev, long long val)
+{
+ struct ber_element *elm;
+ u_int i, len = 0;
+ u_char cur, last = 0;
+
+ if ((elm = ber_get_element(BER_TYPE_INTEGER)) == NULL)
+ return NULL;
+
+ elm->be_numeric = val;
+
+ for (i = 0; i < sizeof(long long); i++) {
+ cur = val & 0xff;
+ if (cur != 0 && cur != 0xff)
+ len = i;
+ if ((cur == 0 && last & 0x80) ||
+ (cur == 0xff && (last & 0x80) == 0))
+ len = i;
+ val >>= 8;
+ last = cur;
+ }
+ elm->be_len = len + 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_integer(struct ber_element *elm, long long *n)
+{
+ if (elm->be_encoding != BER_TYPE_INTEGER)
+ return -1;
+
+ *n = elm->be_numeric;
+ return 0;
+}
+
+int
+ber_get_enumerated(struct ber_element *elm, long long *n)
+{
+ if (elm->be_encoding != BER_TYPE_ENUMERATED)
+ return -1;
+
+ *n = elm->be_numeric;
+ return 0;
+}
+
+
+struct ber_element *
+ber_add_boolean(struct ber_element *prev, int bool)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_BOOLEAN)) == NULL)
+ return NULL;
+
+ elm->be_numeric = bool ? 0xff : 0;
+ elm->be_len = 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_boolean(struct ber_element *elm, int *b)
+{
+ if (elm->be_encoding != BER_TYPE_BOOLEAN)
+ return -1;
+
+ *b = !(elm->be_numeric == 0);
+ return 0;
+}
+
+struct ber_element *
+ber_add_string(struct ber_element *prev, const char *string)
+{
+ return ber_add_nstring(prev, string, strlen(string));
+}
+
+struct ber_element *
+ber_add_nstring(struct ber_element *prev, const char *string0, size_t len)
+{
+ struct ber_element *elm;
+ char *string;
+
+ if ((string = calloc(1, len + 1)) == NULL)
+ return NULL;
+ if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) {
+ free(string);
+ return NULL;
+ }
+
+ bcopy(string0, string, len);
+ elm->be_val = string;
+ elm->be_len = len;
+ elm->be_free = 1; /* free string on cleanup */
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_string(struct ber_element *elm, char **s)
+{
+ if (elm->be_encoding != BER_TYPE_OCTETSTRING)
+ return -1;
+
+ *s = elm->be_val;
+ return 0;
+}
+
+int
+ber_get_nstring(struct ber_element *elm, void **p, size_t *len)
+{
+ if (elm->be_encoding != BER_TYPE_OCTETSTRING)
+ return -1;
+
+ *p = elm->be_val;
+ *len = elm->be_len;
+ return 0;
+}
+
+struct ber_element *
+ber_add_bitstring(struct ber_element *prev, const void *v0, size_t len)
+{
+ struct ber_element *elm;
+ void *v;
+
+ if ((v = calloc(1, len)) == NULL)
+ return NULL;
+ if ((elm = ber_get_element(BER_TYPE_BITSTRING)) == NULL) {
+ free(v);
+ return NULL;
+ }
+
+ bcopy(v0, v, len);
+ elm->be_val = v;
+ elm->be_len = len;
+ elm->be_free = 1; /* free string on cleanup */
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_bitstring(struct ber_element *elm, void **v, size_t *len)
+{
+ if (elm->be_encoding != BER_TYPE_BITSTRING)
+ return -1;
+
+ *v = elm->be_val;
+ *len = elm->be_len;
+ return 0;
+}
+
+struct ber_element *
+ber_add_null(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_NULL)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_null(struct ber_element *elm)
+{
+ if (elm->be_encoding != BER_TYPE_NULL)
+ return -1;
+
+ return 0;
+}
+
+struct ber_element *
+ber_add_eoc(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_EOC)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_eoc(struct ber_element *elm)
+{
+ if (elm->be_encoding != BER_TYPE_EOC)
+ return -1;
+
+ return 0;
+}
+
+size_t
+ber_oid2ber(struct ber_oid *o, u_int8_t *buf, size_t len)
+{
+ u_int32_t v;
+ u_int i, j = 0, k;
+
+ if (o->bo_n < BER_MIN_OID_LEN || o->bo_n > BER_MAX_OID_LEN ||
+ o->bo_id[0] > 2 || o->bo_id[1] > 40)
+ return (0);
+
+ v = (o->bo_id[0] * 40) + o->bo_id[1];
+ for (i = 2, j = 0; i <= o->bo_n; v = o->bo_id[i], i++) {
+ for (k = 28; k >= 7; k -= 7) {
+ if (v >= (u_int)(1 << k)) {
+ if (len)
+ buf[j] = v >> k | BER_TAG_MORE;
+ j++;
+ }
+ }
+ if (len)
+ buf[j] = v & BER_TAG_TYPE_MASK;
+ j++;
+ }
+
+ return (j);
+}
+
+int
+ber_string2oid(const char *oidstr, struct ber_oid *o)
+{
+ char *sp, *p, str[BUFSIZ];
+ const char *errstr;
+
+ if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str))
+ return (-1);
+ bzero(o, sizeof(*o));
+
+ /* Parse OID strings in the common forms n.n.n, n_n_n_n, or n-n-n */
+ for (p = sp = str; p != NULL; sp = p) {
+ if ((p = strpbrk(p, "._-")) != NULL)
+ *p++ = '\0';
+ o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr);
+ if (errstr || o->bo_n > BER_MAX_OID_LEN)
+ return (-1);
+ }
+
+ return (0);
+}
+
+struct ber_element *
+ber_add_oid(struct ber_element *prev, struct ber_oid *o)
+{
+ struct ber_element *elm;
+ u_int8_t *buf;
+ size_t len;
+
+ if ((elm = ber_get_element(BER_TYPE_OBJECT)) == NULL)
+ return (NULL);
+
+ if ((len = ber_oid2ber(o, NULL, 0)) == 0)
+ goto fail;
+
+ if ((buf = calloc(1, len)) == NULL)
+ goto fail;
+
+ elm->be_val = buf;
+ elm->be_len = len;
+ elm->be_free = 1;
+
+ if (ber_oid2ber(o, buf, len) != len)
+ goto fail;
+
+ ber_link_elements(prev, elm);
+
+ return (elm);
+
+ fail:
+ ber_free_elements(elm);
+ return (NULL);
+}
+
+struct ber_element *
+ber_add_noid(struct ber_element *prev, struct ber_oid *o, int n)
+{
+ struct ber_oid no;
+
+ if (n > BER_MAX_OID_LEN)
+ return (NULL);
+ no.bo_n = n;
+ bcopy(&o->bo_id, &no.bo_id, sizeof(no.bo_id));
+
+ return (ber_add_oid(prev, &no));
+}
+
+struct ber_element *
+ber_add_oidstring(struct ber_element *prev, const char *oidstr)
+{
+ struct ber_oid o;
+
+ if (ber_string2oid(oidstr, &o) == -1)
+ return (NULL);
+
+ return (ber_add_oid(prev, &o));
+}
+
+int
+ber_get_oid(struct ber_element *elm, struct ber_oid *o)
+{
+ u_int8_t *buf;
+ size_t len, i = 0, j = 0;
+
+ if (elm->be_encoding != BER_TYPE_OBJECT)
+ return (-1);
+
+ buf = elm->be_val;
+ len = elm->be_len;
+
+ if (!buf[i])
+ return (-1);
+
+ bzero(o, sizeof(*o));
+ o->bo_id[j++] = buf[i] / 40;
+ o->bo_id[j++] = buf[i++] % 40;
+ for (; i < len && j < BER_MAX_OID_LEN; i++) {
+ o->bo_id[j] = (o->bo_id[j] << 7) + (buf[i] & ~0x80);
+ if (buf[i] & 0x80)
+ continue;
+ j++;
+ }
+ o->bo_n = j;
+
+ return (0);
+}
+
+struct ber_element *
+ber_printf_elements(struct ber_element *ber, char *fmt, ...)
+{
+ va_list ap;
+ int d, class;
+ size_t len;
+ unsigned long type;
+ long long i;
+ char *s;
+ void *p;
+ struct ber_oid *o;
+ struct ber_element *sub = ber, *e;
+
+ va_start(ap, fmt);
+ while (*fmt) {
+ switch (*fmt++) {
+ case 'B':
+ p = va_arg(ap, void *);
+ len = va_arg(ap, size_t);
+ ber = ber_add_bitstring(ber, p, len);
+ break;
+ case 'b':
+ d = va_arg(ap, int);
+ ber = ber_add_boolean(ber, d);
+ break;
+ case 'd':
+ d = va_arg(ap, int);
+ ber = ber_add_integer(ber, d);
+ break;
+ case 'e':
+ e = va_arg(ap, struct ber_element *);
+ ber_link_elements(ber, e);
+ break;
+ case 'E':
+ i = va_arg(ap, long long);
+ ber = ber_add_enumerated(ber, i);
+ break;
+ case 'i':
+ i = va_arg(ap, long long);
+ ber = ber_add_integer(ber, i);
+ break;
+ case 'O':
+ o = va_arg(ap, struct ber_oid *);
+ ber = ber_add_oid(ber, o);
+ break;
+ case 'o':
+ s = va_arg(ap, char *);
+ ber = ber_add_oidstring(ber, s);
+ break;
+ case 's':
+ s = va_arg(ap, char *);
+ ber = ber_add_string(ber, s);
+ break;
+ case 't':
+ class = va_arg(ap, int);
+ type = va_arg(ap, unsigned long);
+ ber_set_header(ber, class, type);
+ break;
+ case 'x':
+ s = va_arg(ap, char *);
+ len = va_arg(ap, size_t);
+ ber = ber_add_nstring(ber, s, len);
+ break;
+ case '0':
+ ber = ber_add_null(ber);
+ break;
+ case '{':
+ ber = sub = ber_add_sequence(ber);
+ break;
+ case '(':
+ ber = sub = ber_add_set(ber);
+ break;
+ case '}':
+ case ')':
+ ber = sub;
+ break;
+ case '.':
+ ber = ber_add_eoc(ber);
+ break;
+ default:
+ break;
+ }
+ }
+ va_end(ap);
+
+ return (ber);
+}
+
+int
+ber_scanf_elements(struct ber_element *ber, char *fmt, ...)
+{
+#define _MAX_SEQ 128
+ va_list ap;
+ int *d, level = -1;
+ unsigned long *t;
+ long long *i;
+ void **ptr;
+ size_t *len, ret = 0, n = strlen(fmt);
+ char **s;
+ struct ber_oid *o;
+ struct ber_element *parent[_MAX_SEQ], **e;
+
+ bzero(parent, sizeof(struct ber_element *) * _MAX_SEQ);
+
+ va_start(ap, fmt);
+ while (*fmt != '\0' && ber != NULL) {
+ switch (*fmt++) {
+ case 'B':
+ ptr = va_arg(ap, void **);
+ len = va_arg(ap, size_t *);
+ if (ber_get_bitstring(ber, ptr, len) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'b':
+ d = va_arg(ap, int *);
+ if (ber_get_boolean(ber, d) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'e':
+ e = va_arg(ap, struct ber_element **);
+ *e = ber;
+ ret++;
+ continue;
+ case 'E':
+ i = va_arg(ap, long long *);
+ if (ber_get_enumerated(ber, i) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'i':
+ i = va_arg(ap, long long *);
+ if (ber_get_integer(ber, i) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'o':
+ o = va_arg(ap, struct ber_oid *);
+ if (ber_get_oid(ber, o) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'S':
+ ret++;
+ break;
+ case 's':
+ s = va_arg(ap, char **);
+ if (ber_get_string(ber, s) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 't':
+ d = va_arg(ap, int *);
+ t = va_arg(ap, unsigned long *);
+ *d = ber->be_class;
+ *t = ber->be_type;
+ ret++;
+ continue;
+ case 'x':
+ ptr = va_arg(ap, void **);
+ len = va_arg(ap, size_t *);
+ if (ber_get_nstring(ber, ptr, len) == -1)
+ goto fail;
+ ret++;
+ break;
+ case '0':
+ if (ber->be_encoding != BER_TYPE_NULL)
+ goto fail;
+ ret++;
+ break;
+ case '.':
+ if (ber->be_encoding != BER_TYPE_EOC)
+ goto fail;
+ ret++;
+ break;
+ case '{':
+ case '(':
+ if (ber->be_encoding != BER_TYPE_SEQUENCE &&
+ ber->be_encoding != BER_TYPE_SET)
+ goto fail;
+ if (ber->be_sub == NULL || level >= _MAX_SEQ-1)
+ goto fail;
+ parent[++level] = ber;
+ ber = ber->be_sub;
+ ret++;
+ continue;
+ case '}':
+ case ')':
+ if (parent[level] == NULL)
+ goto fail;
+ ber = parent[level--];
+ ret++;
+ continue;
+ default:
+ goto fail;
+ }
+
+ ber = ber->be_next;
+ }
+ va_end(ap);
+ return (ret == n ? 0 : -1);
+
+ fail:
+ va_end(ap);
+ return (-1);
+
+}
+
+/*
+ * write ber elements to the socket
+ *
+ * params:
+ * ber holds the socket
+ * root fully populated element tree
+ *
+ * returns number of bytes written, or -1 on error and sets errno
+ */
+int
+ber_write_elements(struct ber *ber, struct ber_element *root)
+{
+ size_t len;
+
+ /* calculate length because only the definite form is required */
+ len = ber_calc_len(root);
+ DPRINTF("write ber element of %zd bytes length\n", len);
+
+ if (ber->br_wbuf != NULL && ber->br_wbuf + len > ber->br_wend) {
+ free(ber->br_wbuf);
+ ber->br_wbuf = NULL;
+ }
+ if (ber->br_wbuf == NULL) {
+ if ((ber->br_wbuf = malloc(len)) == NULL)
+ return -1;
+ ber->br_wend = ber->br_wbuf + len;
+ }
+
+ /* reset write pointer */
+ ber->br_wptr = ber->br_wbuf;
+
+ if (ber_dump_element(ber, root) == -1)
+ return -1;
+
+ /* XXX this should be moved to a different function */
+ if (ber->fd != -1)
+ return write(ber->fd, ber->br_wbuf, len);
+
+ return (len);
+}
+
+/*
+ * read ber elements from the socket
+ *
+ * params:
+ * ber holds the socket and lot more
+ * root if NULL, build up an element tree from what we receive on
+ * the wire. If not null, use the specified encoding for the
+ * elements received.
+ *
+ * returns:
+ * !=NULL, elements read and store in the ber_element tree
+ * NULL, type mismatch or read error
+ */
+struct ber_element *
+ber_read_elements(struct ber *ber, struct ber_element *elm)
+{
+ struct ber_element *root = elm;
+
+ if (root == NULL) {
+ if ((root = ber_get_element(0)) == NULL)
+ return NULL;
+ }
+
+ DPRINTF("read ber elements, root %p\n", root);
+
+ if (ber_read_element(ber, root) == -1) {
+ /* Cleanup if root was allocated by us */
+ if (elm == NULL)
+ ber_free_elements(root);
+ return NULL;
+ }
+
+ return root;
+}
+
+void
+ber_free_elements(struct ber_element *root)
+{
+ if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET))
+ ber_free_elements(root->be_sub);
+ if (root->be_next)
+ ber_free_elements(root->be_next);
+ if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING ||
+ root->be_encoding == BER_TYPE_BITSTRING ||
+ root->be_encoding == BER_TYPE_OBJECT))
+ free(root->be_val);
+ free(root);
+}
+
+/*
+ * internal functions
+ */
+
+size_t
+ber_calc_len(struct ber_element *root)
+{
+ unsigned long t;
+ size_t s;
+ size_t size = 2; /* minimum 1 byte head and 1 byte size */
+
+ /* calculate the real length of a sequence or set */
+ if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET))
+ root->be_len = ber_calc_len(root->be_sub);
+
+ /* fix header length for extended types */
+ if (root->be_type > BER_TYPE_SINGLE_MAX)
+ for (t = root->be_type; t > 0; t >>= 7)
+ size++;
+ if (root->be_len >= BER_TAG_MORE)
+ for (s = root->be_len; s > 0; s >>= 8)
+ size++;
+
+ /* calculate the length of the following elements */
+ if (root->be_next)
+ size += ber_calc_len(root->be_next);
+
+ /* This is an empty element, do not use a minimal size */
+ if (root->be_type == BER_TYPE_EOC && root->be_len == 0)
+ return (0);
+
+ return (root->be_len + size);
+}
+
+static int
+ber_dump_element(struct ber *ber, struct ber_element *root)
+{
+ unsigned long long l;
+ int i;
+ uint8_t u;
+
+ ber_dump_header(ber, root);
+
+ switch (root->be_encoding) {
+ case BER_TYPE_BOOLEAN:
+ case BER_TYPE_INTEGER:
+ case BER_TYPE_ENUMERATED:
+ l = (unsigned long long)root->be_numeric;
+ for (i = root->be_len; i > 0; i--) {
+ u = (l >> ((i - 1) * 8)) & 0xff;
+ ber_putc(ber, u);
+ }
+ break;
+ case BER_TYPE_BITSTRING:
+ return -1;
+ case BER_TYPE_OCTETSTRING:
+ case BER_TYPE_OBJECT:
+ ber_write(ber, root->be_val, root->be_len);
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ case BER_TYPE_EOC:
+ break;
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ if (root->be_sub && ber_dump_element(ber, root->be_sub) == -1)
+ return -1;
+ break;
+ }
+
+ if (root->be_next == NULL)
+ return 0;
+ return ber_dump_element(ber, root->be_next);
+}
+
+static void
+ber_dump_header(struct ber *ber, struct ber_element *root)
+{
+ u_char id = 0, t, buf[8];
+ unsigned long type;
+ size_t size;
+
+ /* class universal, type encoding depending on type value */
+ /* length encoding */
+ if (root->be_type <= BER_TYPE_SINGLE_MAX) {
+ id = root->be_type | (root->be_class << BER_CLASS_SHIFT);
+ if (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET)
+ id |= BER_TYPE_CONSTRUCTED;
+
+ ber_putc(ber, id);
+ } else {
+ id = BER_TAG_MASK | (root->be_class << BER_CLASS_SHIFT);
+ if (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET)
+ id |= BER_TYPE_CONSTRUCTED;
+
+ ber_putc(ber, id);
+
+ for (t = 0, type = root->be_type; type > 0; type >>= 7)
+ buf[t++] = type & ~BER_TAG_MORE;
+
+ while (t-- > 0) {
+ if (t > 0)
+ buf[t] |= BER_TAG_MORE;
+ ber_putc(ber, buf[t]);
+ }
+ }
+
+ if (root->be_len < BER_TAG_MORE) {
+ /* short form */
+ ber_putc(ber, root->be_len);
+ } else {
+ for (t = 0, size = root->be_len; size > 0; size >>= 8)
+ buf[t++] = size & 0xff;
+
+ ber_putc(ber, t | BER_TAG_MORE);
+
+ while (t > 0)
+ ber_putc(ber, buf[--t]);
+ }
+}
+
+static void
+ber_putc(struct ber *ber, u_char c)
+{
+ if (ber->br_wptr + 1 <= ber->br_wend)
+ *ber->br_wptr = c;
+ ber->br_wptr++;
+}
+
+static void
+ber_write(struct ber *ber, void *buf, size_t len)
+{
+ if (ber->br_wptr + len <= ber->br_wend)
+ bcopy(buf, ber->br_wptr, len);
+ ber->br_wptr += len;
+}
+
+/*
+ * extract a BER encoded tag. There are two types, a short and long form.
+ */
+static ssize_t
+get_id(struct ber *b, unsigned long *tag, int *class, int *cstruct)
+{
+ u_char u;
+ size_t i = 0;
+ unsigned long t = 0;
+
+ if (ber_getc(b, &u) == -1)
+ return -1;
+
+ *class = (u >> BER_CLASS_SHIFT) & BER_CLASS_MASK;
+ *cstruct = (u & BER_TYPE_CONSTRUCTED) == BER_TYPE_CONSTRUCTED;
+
+ if ((u & BER_TAG_MASK) != BER_TAG_MASK) {
+ *tag = u & BER_TAG_MASK;
+ return 1;
+ }
+
+ do {
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ t = (t << 7) | (u & ~BER_TAG_MORE);
+ i++;
+ } while (u & BER_TAG_MORE);
+
+ if (i > sizeof(unsigned long)) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *tag = t;
+ return i + 1;
+}
+
+/*
+ * extract length of a ber object -- if length is unknown an error is returned.
+ */
+static ssize_t
+get_len(struct ber *b, ssize_t *len)
+{
+ u_char u, n;
+ ssize_t s, r;
+
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ if ((u & BER_TAG_MORE) == 0) {
+ /* short form */
+ *len = u;
+ return 1;
+ }
+
+ n = u & ~BER_TAG_MORE;
+ if (sizeof(ssize_t) < n) {
+ errno = ERANGE;
+ return -1;
+ }
+ r = n + 1;
+
+ for (s = 0; n > 0; n--) {
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ s = (s << 8) | u;
+ }
+
+ if (s < 0) {
+ /* overflow */
+ errno = ERANGE;
+ return -1;
+ }
+
+ if (s == 0) {
+ /* invalid encoding */
+ errno = EINVAL;
+ return -1;
+ }
+
+ *len = s;
+ return r;
+}
+
+static ssize_t
+ber_read_element(struct ber *ber, struct ber_element *elm)
+{
+ long long val = 0;
+ struct ber_element *next;
+ unsigned long type;
+ int i, class, cstruct;
+ ssize_t len, r, totlen = 0;
+ u_char c;
+
+ if ((r = get_id(ber, &type, &class, &cstruct)) == -1)
+ return -1;
+ DPRINTF("ber read got class %d type %lu, %s\n",
+ class, type, cstruct ? "constructive" : "primitive");
+ totlen += r;
+ if ((r = get_len(ber, &len)) == -1)
+ return -1;
+ DPRINTF("ber read element size %zd\n", len);
+ totlen += r + len;
+
+ elm->be_type = type;
+ elm->be_len = len;
+ elm->be_class = class;
+
+ if (elm->be_encoding == 0) {
+ /* try to figure out the encoding via class, type and cstruct */
+ if (cstruct)
+ elm->be_encoding = BER_TYPE_SEQUENCE;
+ else if (class == BER_CLASS_UNIVERSAL)
+ elm->be_encoding = type;
+ else if (ber->br_application != NULL) {
+ /*
+ * Ask the application to map the encoding to a
+ * universal type. For example, a SMI IpAddress
+ * type is defined as 4 byte OCTET STRING.
+ */
+ elm->be_encoding = (*ber->br_application)(elm);
+ } else
+ /* last resort option */
+ elm->be_encoding = BER_TYPE_NULL;
+ }
+
+ switch (elm->be_encoding) {
+ case BER_TYPE_EOC: /* End-Of-Content */
+ break;
+ case BER_TYPE_BOOLEAN:
+ case BER_TYPE_INTEGER:
+ case BER_TYPE_ENUMERATED:
+ if (len > (ssize_t)sizeof(long long))
+ return -1;
+ for (i = 0; i < len; i++) {
+ if (ber_getc(ber, &c) != 1)
+ return -1;
+ val <<= 8;
+ val |= c;
+ }
+
+ /* sign extend if MSB is set */
+ if (val >> ((i - 1) * 8) & 0x80)
+ val |= ULLONG_MAX << (i * 8);
+ elm->be_numeric = val;
+ break;
+ case BER_TYPE_BITSTRING:
+ elm->be_val = malloc(len);
+ if (elm->be_val == NULL)
+ return -1;
+ elm->be_free = 1;
+ elm->be_len = len;
+ ber_read(ber, elm->be_val, len);
+ break;
+ case BER_TYPE_OCTETSTRING:
+ case BER_TYPE_OBJECT:
+ elm->be_val = malloc(len + 1);
+ if (elm->be_val == NULL)
+ return -1;
+ elm->be_free = 1;
+ elm->be_len = len;
+ ber_read(ber, elm->be_val, len);
+ ((u_char *)elm->be_val)[len] = '\0';
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ if (len != 0)
+ return -1;
+ break;
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ if (elm->be_sub == NULL) {
+ if ((elm->be_sub = ber_get_element(0)) == NULL)
+ return -1;
+ }
+ next = elm->be_sub;
+ while (len > 0) {
+ r = ber_read_element(ber, next);
+ if (r == -1)
+ return -1;
+ len -= r;
+ if (len > 0 && next->be_next == NULL) {
+ if ((next->be_next = ber_get_element(0)) ==
+ NULL)
+ return -1;
+ }
+ next = next->be_next;
+ }
+ break;
+ }
+ return totlen;
+}
+
+static ssize_t
+ber_readbuf(struct ber *b, void *buf, size_t nbytes)
+{
+ size_t sz;
+ size_t len;
+
+ if (b->br_rbuf == NULL)
+ return -1;
+
+ sz = b->br_rend - b->br_rptr;
+ len = MIN(nbytes, sz);
+ if (len == 0) {
+ errno = ECANCELED;
+ return (-1); /* end of buffer and parser wants more data */
+ }
+
+ bcopy(b->br_rptr, buf, len);
+ b->br_rptr += len;
+
+ return (len);
+}
+
+void
+ber_set_readbuf(struct ber *b, void *buf, size_t len)
+{
+ b->br_rbuf = b->br_rptr = buf;
+ b->br_rend = (u_int8_t *)buf + len;
+}
+
+ssize_t
+ber_get_writebuf(struct ber *b, void **buf)
+{
+ if (b->br_wbuf == NULL)
+ return -1;
+ *buf = b->br_wbuf;
+ return (b->br_wend - b->br_wbuf);
+}
+
+void
+ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *))
+{
+ b->br_application = cb;
+}
+
+void
+ber_free(struct ber *b)
+{
+ if (b->br_wbuf != NULL)
+ free (b->br_wbuf);
+}
+
+static ssize_t
+ber_getc(struct ber *b, u_char *c)
+{
+ ssize_t r;
+ /*
+ * XXX calling read here is wrong in many ways. The most obvious one
+ * being that we will block till data arrives.
+ * But for now it is _good enough_ *gulp*
+ */
+ if (b->fd == -1)
+ r = ber_readbuf(b, c, 1);
+ else
+ r = read(b->fd, c, 1);
+ return r;
+}
+
+static ssize_t
+ber_read(struct ber *ber, void *buf, size_t len)
+{
+ u_char *b = buf;
+ ssize_t r, remain = len;
+
+ /*
+ * XXX calling read here is wrong in many ways. The most obvious one
+ * being that we will block till data arrives.
+ * But for now it is _good enough_ *gulp*
+ */
+
+ while (remain > 0) {
+ if (ber->fd == -1)
+ r = ber_readbuf(ber, b, remain);
+ else
+ r = read(ber->fd, b, remain);
+ if (r == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ if (r == 0)
+ return (b - (u_char *)buf);
+ b += r;
+ remain -= r;
+ }
+ return (b - (u_char *)buf);
+}
diff --git a/usr.sbin/ldapd/ber.h b/usr.sbin/ldapd/ber.h
new file mode 100644
index 00000000000..4051e3f23f6
--- /dev/null
+++ b/usr.sbin/ldapd/ber.h
@@ -0,0 +1,128 @@
+/* $OpenBSD: ber.h,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org>
+ *
+ * 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.
+ */
+
+struct ber_element {
+ struct ber_element *be_next;
+ unsigned long be_type;
+ unsigned long be_encoding;
+ size_t be_len;
+ int be_free;
+ u_int8_t be_class;
+ union {
+ struct ber_element *bv_sub;
+ void *bv_val;
+ long long bv_numeric;
+ } be_union;
+#define be_sub be_union.bv_sub
+#define be_val be_union.bv_val
+#define be_numeric be_union.bv_numeric
+};
+
+struct ber {
+ int fd;
+ u_char *br_wbuf;
+ u_char *br_wptr;
+ u_char *br_wend;
+ u_char *br_rbuf;
+ u_char *br_rptr;
+ u_char *br_rend;
+
+ unsigned long (*br_application)(struct ber_element *);
+};
+
+/* well-known ber_element types */
+#define BER_TYPE_DEFAULT ((unsigned long)-1)
+#define BER_TYPE_EOC 0
+#define BER_TYPE_BOOLEAN 1
+#define BER_TYPE_INTEGER 2
+#define BER_TYPE_BITSTRING 3
+#define BER_TYPE_OCTETSTRING 4
+#define BER_TYPE_NULL 5
+#define BER_TYPE_OBJECT 6
+#define BER_TYPE_ENUMERATED 10
+#define BER_TYPE_SEQUENCE 16
+#define BER_TYPE_SET 17
+
+/* ber classes */
+#define BER_CLASS_UNIVERSAL 0x0
+#define BER_CLASS_UNIV BER_CLASS_UNIVERSAL
+#define BER_CLASS_APPLICATION 0x1
+#define BER_CLASS_APP BER_CLASS_APPLICATION
+#define BER_CLASS_CONTEXT 0x2
+#define BER_CLASS_PRIVATE 0x3
+#define BER_CLASS_MASK 0x3
+
+/* common definitions */
+#define BER_MIN_OID_LEN 2 /* OBJECT */
+#define BER_MAX_OID_LEN 32 /* OBJECT */
+
+struct ber_oid {
+ u_int32_t bo_id[BER_MAX_OID_LEN + 1];
+ size_t bo_n;
+};
+
+__BEGIN_DECLS
+struct ber_element *ber_get_element(unsigned long);
+void ber_set_header(struct ber_element *, int,
+ unsigned long);
+void ber_link_elements(struct ber_element *,
+ struct ber_element *);
+struct ber_element *ber_unlink_elements(struct ber_element *);
+void ber_replace_elements(struct ber_element *,
+ struct ber_element *);
+struct ber_element *ber_add_sequence(struct ber_element *);
+struct ber_element *ber_add_set(struct ber_element *);
+struct ber_element *ber_add_integer(struct ber_element *, long long);
+int ber_get_integer(struct ber_element *, long long *);
+struct ber_element *ber_add_enumerated(struct ber_element *, long long);
+int ber_get_enumerated(struct ber_element *, long long *);
+struct ber_element *ber_add_boolean(struct ber_element *, int);
+int ber_get_boolean(struct ber_element *, int *);
+struct ber_element *ber_add_string(struct ber_element *, const char *);
+struct ber_element *ber_add_nstring(struct ber_element *, const char *,
+ size_t);
+int ber_get_string(struct ber_element *, char **);
+int ber_get_nstring(struct ber_element *, void **,
+ size_t *);
+struct ber_element *ber_add_bitstring(struct ber_element *, const void *,
+ size_t);
+int ber_get_bitstring(struct ber_element *, void **,
+ size_t *);
+struct ber_element *ber_add_null(struct ber_element *);
+int ber_get_null(struct ber_element *);
+struct ber_element *ber_add_eoc(struct ber_element *);
+int ber_get_eoc(struct ber_element *);
+struct ber_element *ber_add_oid(struct ber_element *, struct ber_oid *);
+struct ber_element *ber_add_noid(struct ber_element *, struct ber_oid *, int);
+struct ber_element *ber_add_oidstring(struct ber_element *, const char *);
+int ber_get_oid(struct ber_element *, struct ber_oid *);
+size_t ber_oid2ber(struct ber_oid *, u_int8_t *, size_t);
+int ber_string2oid(const char *, struct ber_oid *);
+struct ber_element *ber_printf_elements(struct ber_element *, char *, ...);
+int ber_scanf_elements(struct ber_element *, char *, ...);
+ssize_t ber_get_writebuf(struct ber *, void **);
+int ber_write_elements(struct ber *, struct ber_element *);
+void ber_set_readbuf(struct ber *, void *, size_t);
+struct ber_element *ber_read_elements(struct ber *, struct ber_element *);
+void ber_free_elements(struct ber_element *);
+size_t ber_calc_len(struct ber_element *);
+void ber_set_application(struct ber *,
+ unsigned long (*)(struct ber_element *));
+void ber_free(struct ber *);
+__END_DECLS
diff --git a/usr.sbin/ldapd/btest.c b/usr.sbin/ldapd/btest.c
new file mode 100644
index 00000000000..a16d00f7e84
--- /dev/null
+++ b/usr.sbin/ldapd/btest.c
@@ -0,0 +1,133 @@
+/* $OpenBSD: btest.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/* Simple test program for the btree database. */
+/*
+ * Copyright (c) 2009 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 <err.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "btree.h"
+
+int
+main(int argc, char **argv)
+{
+ int c, rc = BT_FAIL;
+ unsigned int flags = 0;
+ struct btree *bt;
+ struct cursor *cursor;
+ const char *filename = "test.db";
+ struct btval key, data, maxkey;
+
+ while ((c = getopt(argc, argv, "rf:")) != -1) {
+ switch (c) {
+ case 'r':
+ flags |= BT_REVERSEKEY;
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ errx(1, "missing command");
+
+ bt = btree_open(filename, flags | BT_NOSYNC, 0644);
+ if (bt == NULL)
+ err(1, filename);
+
+ bzero(&key, sizeof(key));
+ bzero(&data, sizeof(data));
+ bzero(&maxkey, sizeof(maxkey));
+
+ if (strcmp(argv[0], "put") == 0) {
+ if (argc < 3)
+ errx(1, "missing arguments");
+ key.data = argv[1];
+ key.size = strlen(key.data);
+ data.data = argv[2];
+ data.size = strlen(data.data);
+ rc = btree_put(bt, &key, &data, 0);
+ if (rc == BT_SUCCESS)
+ printf("OK\n");
+ else
+ printf("FAIL\n");
+ } else if (strcmp(argv[0], "del") == 0) {
+ if (argc < 1)
+ errx(1, "missing argument");
+ key.data = argv[1];
+ key.size = strlen(key.data);
+ rc = btree_del(bt, &key, NULL);
+ if (rc == BT_SUCCESS)
+ printf("OK\n");
+ else
+ printf("FAIL\n");
+ } else if (strcmp(argv[0], "get") == 0) {
+ if (argc < 2)
+ errx(1, "missing arguments");
+ key.data = argv[1];
+ key.size = strlen(key.data);
+ rc = btree_get(bt, &key, &data);
+ if (rc == BT_SUCCESS) {
+ printf("OK %.*s\n", (int)data.size, (char *)data.data);
+ } else {
+ printf("FAIL\n");
+ }
+ } else if (strcmp(argv[0], "scan") == 0) {
+ if (argc > 1) {
+ key.data = argv[1];
+ key.size = strlen(key.data);
+ flags = BT_CURSOR;
+ }
+ else
+ flags = BT_FIRST;
+ if (argc > 2) {
+ maxkey.data = argv[2];
+ maxkey.size = strlen(key.data);
+ }
+
+ cursor = btree_cursor_open(bt);
+ while ((rc = btree_cursor_get(cursor, &key, &data,
+ flags)) == BT_SUCCESS) {
+ if (argc > 2 && btree_cmp(bt, &key, &maxkey) > 0)
+ break;
+ printf("OK %zi %.*s\n",
+ key.size, (int)key.size, (char *)key.data);
+ flags = BT_NEXT;
+ }
+ btree_cursor_close(cursor);
+ } else if (strcmp(argv[0], "compact") == 0) {
+ if ((rc = btree_compact(bt)) != BT_SUCCESS)
+ warn("compact");
+ } else if (strcmp(argv[0], "revert") == 0) {
+ if ((rc = btree_revert(bt)) != BT_SUCCESS)
+ warn("revert");
+ } else
+ errx(1, "%s: invalid command", argv[0]);
+
+ btree_close(bt);
+
+ return rc;
+}
+
diff --git a/usr.sbin/ldapd/btree.3 b/usr.sbin/ldapd/btree.3
new file mode 100644
index 00000000000..b88e8f35233
--- /dev/null
+++ b/usr.sbin/ldapd/btree.3
@@ -0,0 +1,230 @@
+.\" $$
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate: May 31 2010 $
+.Dt BTREE 3
+.Os
+.Sh NAME
+.Nm btree_open ,
+.Nm btree_open_fd ,
+.Nm btree_close ,
+.Nm btree_txn_begin ,
+.Nm btree_txn_get ,
+.Nm btree_txn_put ,
+.Nm btree_txn_del ,
+.Nm btree_txn_commit ,
+.Nm btree_txn_abort ,
+.Nm btree_get ,
+.Nm btree_put ,
+.Nm btree_del ,
+.Nm btree_txn_cursor_open ,
+.Nm btree_cursor_open ,
+.Nm btree_cursor_close ,
+.Nm btree_cursor_get ,
+.Nm btree_stat ,
+.Nm btree_compact ,
+.Nm btree_revert ,
+.Nm btree_sync ,
+.Nm btree_set_cache_size ,
+.Nm btree_get_flags ,
+.Nm btree_get_path ,
+.Nm btree_cmp ,
+.Nm btval_reset
+.Nd Append-only prefix B+Tree database library.
+.Sh SYNOPSIS
+.Fd #include <btree.h>
+.Ft "struct btree *"
+.Fn "btree_open_fd" "int fd" "uint32_t flags"
+.Ft "struct btree *"
+.Fn "btree_open" "const char *path" "uint32_t flags" "mode_t mode"
+.Ft "void"
+.Fn "btree_close" "struct btree *bt"
+.Ft "struct btree_txn *"
+.Fn "btree_txn_begin" "struct btree *bt" "int rdonly"
+.Ft "int"
+.Fn "btree_txn_get" "struct btree *bt" "struct btree_txn *" "struct btval *key" "struct btval *data"
+.Ft "int"
+.Fn "btree_txn_put" "struct btree *bt" "struct btree_txn *" "struct btval *key" "struct btval *data" "unsigned flags"
+.Ft "int"
+.Fn "btree_txn_del" "struct btree *bt" "struct btree_txn *" "struct btval *key" "struct btval *data"
+.Ft "int"
+.Fn "btree_txn_commit" "struct btree_txn *txn"
+.Ft "void"
+.Fn "btree_txn_abort" "struct btree_txn *txn"
+.Ft "int"
+.Fn "btree_get" "struct btree *bt" "struct btval *key" "struct btval *data"
+.Ft "int"
+.Fn "btree_put" "struct btree *bt" "struct btval *key" "struct btval *data" "unsigned flags"
+.Ft "int"
+.Fn "btree_del" "struct btree *bt" "struct btval *key" "struct btval *data"
+.Ft "struct cursor *"
+.Fn "btree_txn_cursor_open" "struct btree *bt" "struct btree_txn *txn"
+.Ft "struct cursor *"
+.Fn "btree_cursor_open" "struct btree *bt"
+.Ft "void"
+.Fn "btree_cursor_close" "struct cursor *cursor"
+.Ft "int"
+.Fn "btree_cursor_get" "struct cursor *cursor" "struct btval *key" "struct btval *data" "enum cursor_op op"
+.Ft "struct btree_stat *"
+.Fn "btree_stat" "struct btree *bt"
+.Ft "int"
+.Fn "btree_compact" "struct btree *bt"
+.Ft "int"
+.Fn "btree_revert" "struct btree *bt"
+.Ft "int"
+.Fn "btree_sync" "struct btree *bt"
+.Ft "void"
+.Fn "btree_set_cache_size" "struct btree *bt" "unsigned int cache_size"
+.Ft "unsigned int"
+.Fn "btree_get_flags" "struct btree *bt"
+.Ft "const char *"
+.Fn "btree_get_path" "struct btree *bt"
+.Ft "int"
+.Fn "btree_cmp" "struct btree *bt" "const struct btval *a" "const struct btval *b"
+.Ft "void"
+.Fn "btval_reset" "struct btval *btv"
+.Sh DESCRIPTION
+The database is implemented as a modified prefix B+tree.
+Instead of modifying the database file inplace,
+each update appends any modified pages at the end of the file.
+The last block of the file contains metadata and a pointer to the root page.
+.Pp
+Append-only writing gives the following properties:
+.Bl -enum
+.It
+No locks.
+Since all writes are appended to the end of the file, multiple
+readers can continue reading from the tree as it was when they
+started.
+This snapshot view might contain outdated versions of entries.
+.It
+Resistance to corruption.
+The file content is never modified.
+When opening a database file, the last good meta-data page is searched
+by scanning backwards.
+If there is trailing garbage in the file, it will be skipped.
+.It
+Hot backups.
+Backups can be made on a running server simply by copying the files.
+There is no risk for inconsistencies.
+.El
+.Pp
+The drawback is that it wastes space.
+A 4-level B+tree database will write at least 5 new pages on each update,
+including the meta-data page.
+With 4 KiB pagesize, the file would grow by 20 KiB on each update.
+.Pp
+To reclaim the wasted space, the database can be compacted.
+The compaction process opens the database and traverses the tree.
+Each active page is then written to a new file.
+When complete, the old file is replaced.
+Any updates committed after the file was opened will be lost.
+All processes using the file should re-open it.
+.Sh CURSORS
+A new cursor may be opened with a call to
+.Fn btree_txn_cursor_open
+or
+.Fn btree_cursor_open .
+The latter is implemented as a macro to the former with a NULL
+.Ar txn
+argument.
+Multiple cursors may be open simultaneously.
+The cursor must be closed with
+.Fn btree_cursor_close
+after use.
+.Pp
+The cursor can be placed at a specific key by setting
+.Ar op
+to BT_CURSOR and filling in the
+.Ar key
+argument.
+The cursor will be placed at the smallest key greater or equal to
+the specified key.
+.Pp
+The database may be traversed from the first key to the last by calling
+.Fn btree_cursor_get
+with
+.Ar op
+initially set to BT_FIRST and then set to BT_NEXT.
+If the cursor is not yet initialized, ie
+.Fn btree_cursor_get
+has not yet been called with
+.Ar op
+set to BT_FIRST or BT_CURSOR, then BT_NEXT behaves as BT_FIRST.
+.Sh TRANSACTIONS
+There are two types of transactions: write and read-only transactions.
+Only one write transaction is allowed at a time.
+A read-only transaction allows the grouping of several read operations
+to see a consistent state of the database.
+.Pp
+A transaction is started with
+.Fn btree_txn_begin .
+If the
+.Ar rdonly
+parameter is 0, a write transaction is started and an exclusive lock
+is taken on the file.
+No lock is taken for read-only transactions.
+.Pp
+The transaction is ended either with
+.Fn btree_txn_commit
+or
+.Fn btree_txn_abort .
+The
+.Ft btree_txn
+pointer must not be accessed afterwards.
+.Sh RETURN VALUES
+The
+.Fn btree_txn_get ,
+.Fn btree_txn_put ,
+.Fn btree_txn_del ,
+.Fn btree_txn_commit ,
+.Fn btree_get ,
+.Fn btree_put ,
+.Fn btree_del ,
+.Fn btree_cursor_get ,
+.Fn btree_compact
+and
+.Fn btree_revert
+functions all return BT_SUCCESS on success and BT_FAIL on failure.
+.Pp
+.Fn btree_txn_put
+and
+.Fn btree_put
+returns BT_EXISTS if the key already exists and BT_NOOVERWRITE was not
+passed in the
+.Ar flags
+argument.
+.Pp
+.Fn btree_txn_get ,
+.Fn btree_txn_del ,
+.Fn btree_get ,
+.Fn btree_del
+and
+.Fn btree_cursor_get
+returns BT_NOTFOUND if the specified key was not found.
+.Pp
+All functions returning pointers to structs returns NULL on error.
+.Sh SEE ALSO
+.Pp
+.Sh AUTHORS
+The
+.Nm btree
+library was written by
+.An Martin Hedenfalk Aq martin@bzero.se
+.Sh BUGS
+This manpage is incomplete.
+The page size can't be changed.
+Byte order is assumed never to change.
diff --git a/usr.sbin/ldapd/btree.c b/usr.sbin/ldapd/btree.c
new file mode 100644
index 00000000000..45a1317142c
--- /dev/null
+++ b/usr.sbin/ldapd/btree.c
@@ -0,0 +1,2905 @@
+/* $OpenBSD: btree.c,v 1.1 2010/05/31 17:36:31 martinh 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/tree.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "btree.h"
+
+/* #define DEBUG */
+
+#ifdef DEBUG
+# define DPRINTF(...) do { fprintf(stderr, "%s:%d: ", __func__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); } while(0)
+#else
+# define DPRINTF(...)
+#endif
+
+#define PAGESIZE 4096
+#define BT_MINKEYS 4
+#define BT_MAGIC 0xB3DBB3DB
+#define BT_VERSION 3
+#define MAXKEYSIZE 255
+
+#define P_INVALID 0xFFFFFFFF
+
+#define F_ISSET(w, f) (((w) & (f)) == (f))
+
+/* There are four page types: meta, index, leaf and overflow.
+ * They all share the same page header.
+ */
+struct page { /* represents an on-disk page */
+ pgno_t pgno;
+#define P_BRANCH 0x01 /* branch page */
+#define P_LEAF 0x02 /* leaf page */
+#define P_OVERFLOW 0x04 /* overflow page */
+#define P_META 0x08 /* meta page */
+ uint32_t flags;
+#define lower b.fb.fb_lower
+#define upper b.fb.fb_upper
+#define p_next_pgno b.pb_next_pgno
+ union page_bounds {
+ struct {
+ indx_t fb_lower; /* lower bound of free space */
+ indx_t fb_upper; /* upper bound of free space */
+ } fb;
+ pgno_t pb_next_pgno; /* overflow page linked list */
+ } b;
+#define PAGEHDRSZ (sizeof(pgno_t) + sizeof(uint32_t) + \
+ sizeof(union page_bounds))
+ indx_t ptrs[(PAGESIZE - PAGEHDRSZ) / sizeof(indx_t)];
+};
+
+#define NUMKEYSP(p) (((p)->lower - PAGEHDRSZ) >> 1)
+#define NUMKEYS(mp) (((mp)->page.lower - PAGEHDRSZ) >> 1)
+#define SIZELEFT(mp) (indx_t)((mp)->page.upper - (mp)->page.lower)
+#define PAGEFILL(mp) ((float)(PAGESIZE - PAGEHDRSZ - SIZELEFT(mp)) / \
+ (PAGESIZE - PAGEHDRSZ))
+#define IS_LEAF(mp) F_ISSET((mp)->page.flags, P_LEAF)
+#define IS_BRANCH(mp) F_ISSET((mp)->page.flags, P_BRANCH)
+#define IS_OVERFLOW(mp) F_ISSET((mp)->page.flags, P_OVERFLOW)
+
+struct bt_meta { /* meta (footer) page content */
+ uint32_t magic; /* really needed? */
+ uint32_t version;
+ uint32_t flags;
+ uint32_t psize; /* page size */
+ pgno_t root; /* page number of root page */
+ pgno_t prev_meta; /* previous meta page number */
+ time_t created_at;
+ uint32_t branch_pages;
+ uint32_t leaf_pages;
+ uint32_t overflow_pages;
+ uint32_t revisions;
+ uint32_t depth;
+ uint64_t entries;
+ unsigned char hash[SHA_DIGEST_LENGTH];
+};
+
+struct btkey {
+ size_t len;
+ char str[MAXKEYSIZE];
+};
+
+struct mpage { /* an in-memory cached page */
+ RB_ENTRY(mpage) entry; /* page cache entry */
+ SIMPLEQ_ENTRY(mpage) next; /* queue of dirty pages */
+ TAILQ_ENTRY(mpage) lru_next; /* LRU queue */
+ struct mpage *parent; /* NULL if root */
+ unsigned int parent_index; /* keep track of node index */
+ struct btkey prefix;
+ struct page page;
+ pgno_t pgno; /* copy of page->pgno */
+ short ref; /* increased by cursors */
+ short dirty; /* 1 if on dirty queue */
+};
+RB_HEAD(page_cache, mpage);
+SIMPLEQ_HEAD(dirty_queue, mpage);
+TAILQ_HEAD(lru_queue, mpage);
+
+static int mpage_cmp(struct mpage *a, struct mpage *b);
+static struct mpage *mpage_lookup(struct btree *bt, pgno_t pgno);
+static void mpage_add(struct btree *bt, struct mpage *mp);
+static void mpage_del(struct btree *bt, struct mpage *mp);
+static struct mpage *mpage_copy(struct mpage *mp);
+static void mpage_prune(struct btree *bt);
+static void mpage_dirty(struct btree *bt, struct mpage *mp);
+static void mpage_touch(struct btree *bt, struct mpage *mp);
+
+RB_PROTOTYPE(page_cache, mpage, entry, mpage_cmp);
+RB_GENERATE(page_cache, mpage, entry, mpage_cmp);
+
+struct ppage { /* ordered list of pages */
+ SLIST_ENTRY(ppage) entry;
+ struct mpage *mpage;
+ unsigned int ki; /* cursor index on page */
+};
+SLIST_HEAD(page_stack, ppage);
+
+#define CURSOR_EMPTY(c) SLIST_EMPTY(&(c)->stack)
+#define CURSOR_TOP(c) SLIST_FIRST(&(c)->stack)
+#define CURSOR_POP(c) SLIST_REMOVE_HEAD(&(c)->stack, entry)
+#define CURSOR_PUSH(c,p) SLIST_INSERT_HEAD(&(c)->stack, p, entry)
+
+struct cursor {
+ struct btree *bt;
+ struct btree_txn *txn;
+ struct page_stack stack; /* stack of parent pages */
+ short initialized; /* 1 if initialized */
+ short eof; /* 1 if end is reached */
+};
+
+#define METAHASHLEN offsetof(struct bt_meta, hash)
+#define METADATA(p) ((struct bt_meta *)((char *)p + PAGEHDRSZ))
+
+struct node {
+#define n_pgno p.np_pgno
+#define n_dsize p.np_dsize
+ union {
+ pgno_t np_pgno; /* child page number */
+ uint32_t np_dsize; /* leaf data size */
+ } p;
+ uint16_t ksize; /* key size */
+#define F_BIGDATA 0x01 /* data put on overflow page */
+ uint8_t flags;
+ char data[];
+};
+
+struct btree_txn {
+ pgno_t root; /* current / new root page */
+ pgno_t next_pgno; /* next unallocated page */
+ struct btree *bt; /* btree is ref'd */
+ struct dirty_queue *dirty_queue; /* modified pages */
+#define BT_TXN_RDONLY 0x01 /* read-only transaction */
+#define BT_TXN_ERROR 0x02 /* an error has occurred */
+ unsigned int flags;
+};
+
+struct btree {
+ int fd;
+ char *path;
+#define BT_FIXPADDING 0x01 /* internal */
+ unsigned int flags;
+ bt_cmp_func cmp; /* user compare function */
+ struct page *metapage; /* current last meta page */
+ struct bt_meta *meta;
+ struct page_cache *page_cache;
+ struct lru_queue *lru_queue;
+ struct btree_txn *txn; /* current write transaction */
+ int ref; /* increased by cursors */
+ struct btree_stat stat;
+ off_t size; /* current file size */
+};
+
+#define NODESIZE offsetof(struct node, data)
+
+#define INDXSIZE(k) (NODESIZE + ((k) == NULL ? 0 : (k)->size))
+#define LEAFSIZE(k, d) (NODESIZE + (k)->size + (d)->size)
+#define NODEPTRP(p, i) ((struct node *)((char *)(p) + (p)->ptrs[i]))
+#define NODEPTR(mp, i) NODEPTRP(&(mp)->page, i)
+#define NODEKEY(node) (void *)((node)->data)
+#define NODEDATA(node) (void *)((char *)(node)->data + (node)->ksize)
+#define NODEPGNO(node) ((node)->p.np_pgno)
+#define NODEDSZ(node) ((node)->p.np_dsize)
+
+#define BT_COMMIT_PAGES 64 /* max number of pages to write in one commit */
+#define BT_MAXCACHE_DEF 1024 /* max number of pages to keep in cache */
+
+static int btree_read_page(struct btree *bt, pgno_t pgno,
+ struct page *page);
+static int btree_get_mpage(struct btree *bt, pgno_t pgno,
+ struct mpage **mpp);
+static int btree_search_page_root(struct btree *bt,
+ struct mpage *root, struct btval *key,
+ struct cursor *cursor, int modify,
+ struct mpage **mpp);
+static int btree_search_page(struct btree *bt,
+ struct btree_txn *txn, struct btval *key,
+ struct cursor *cursor, int modify,
+ struct mpage **mpp);
+
+static void btree_init_meta(struct btree *bt);
+static int btree_is_meta_page(struct page *p);
+static int btree_read_meta(struct btree *bt, pgno_t *p_next);
+static int btree_write_meta(struct btree *bt, pgno_t root);
+
+static struct node *btree_search_node(struct btree *bt, struct mpage *mp,
+ struct btval *key, int *exactp, unsigned int *kip);
+static int btree_add_node(struct btree *bt, struct mpage *mp,
+ indx_t indx, struct btval *key, struct btval *data,
+ pgno_t pgno, uint8_t flags);
+static void btree_del_node(struct btree *bt, struct mpage *mp,
+ indx_t indx);
+static int btree_read_data(struct btree *bt, struct mpage *mp,
+ struct node *leaf, struct btval *data);
+
+static int btree_rebalance(struct btree *bt, struct mpage *mp);
+static int btree_update_key(struct btree *bt, struct mpage *mp,
+ indx_t indx, struct btval *key);
+static int btree_adjust_prefix(struct btree *bt,
+ struct mpage *src, int delta);
+static int btree_move_node(struct btree *bt, struct mpage *src,
+ indx_t srcindx, struct mpage *dst, indx_t dstindx);
+static int btree_merge(struct btree *bt, struct mpage *src,
+ struct mpage *dst);
+static int btree_split(struct btree *bt, struct mpage **mpp,
+ unsigned int *newindxp, struct btval *newkey,
+ struct btval *newdata, pgno_t newpgno);
+static struct mpage *btree_new_page(struct btree *bt, uint32_t flags);
+static int btree_write_overflow_data(struct btree *bt,
+ struct page *p, struct btval *data);
+
+static void cursor_pop_page(struct cursor *cursor);
+static struct ppage *cursor_push_page(struct cursor *cursor,
+ struct mpage *mp);
+
+static int bt_set_key(struct btree *bt, struct mpage *mp,
+ struct node *node, struct btval *key);
+static int btree_sibling(struct cursor *cursor, int move_right);
+static int btree_cursor_next(struct cursor *cursor,
+ struct btval *key, struct btval *data);
+static int btree_cursor_set(struct cursor *cursor,
+ struct btval *key, struct btval *data);
+static int btree_cursor_first(struct cursor *cursor,
+ struct btval *key, struct btval *data);
+
+static void bt_reduce_separator(struct btree *bt, struct node *min,
+ struct btval *sep);
+static void remove_prefix(struct btree *bt, struct btval *key,
+ size_t pfxlen);
+static void expand_prefix(struct btree *bt, struct mpage *mp,
+ indx_t indx, struct btkey *expkey);
+static void concat_prefix(struct btree *bt, char *s1, size_t n1,
+ char *s2, size_t n2, char *cs, size_t *cn);
+static void common_prefix(struct btree *bt, struct btkey *min,
+ struct btkey *max, struct btkey *pfx);
+static void find_common_prefix(struct btree *bt, struct mpage *mp);
+
+static size_t bt_leaf_size(struct btval *key, struct btval *data);
+static size_t bt_branch_size(struct btval *key);
+
+static pgno_t btree_compact_tree(struct btree *bt, pgno_t pgno,
+ int fd);
+
+static int memncmp(const void *s1, size_t n1,
+ const void *s2, size_t n2);
+static int memnrcmp(const void *s1, size_t n1,
+ const void *s2, size_t n2);
+
+static int
+memncmp(const void *s1, size_t n1, const void *s2, size_t n2)
+{
+ if (n1 < n2) {
+ if (memcmp(s1, s2, n1) == 0)
+ return -1;
+ }
+ else if (n1 > n2) {
+ if (memcmp(s1, s2, n2) == 0)
+ return 1;
+ }
+ return memcmp(s1, s2, n1);
+}
+
+static int
+memnrcmp(const void *s1, size_t n1, const void *s2, size_t n2)
+{
+ const unsigned char *p1;
+ const unsigned char *p2;
+
+ if (n1 == 0)
+ return n2 == 0 ? 0 : -1;
+
+ if (n2 == 0)
+ return n1 == 0 ? 0 : 1;
+
+ p1 = (const unsigned char *)s1 + n1 - 1;
+ p2 = (const unsigned char *)s2 + n2 - 1;
+
+ while (*p1 == *p2) {
+ if (p1 == s1)
+ return (p2 == s2) ? 0 : -1;
+ if (p2 == s2)
+ return (p1 == p2) ? 0 : 1;
+ p1--;
+ p2--;
+ }
+ return *p1 - *p2;
+}
+
+int
+btree_cmp(struct btree *bt, const struct btval *a, const struct btval *b)
+{
+ return bt->cmp(a, b);
+}
+
+static void
+common_prefix(struct btree *bt, struct btkey *min, struct btkey *max,
+ struct btkey *pfx)
+{
+ size_t n = 0;
+ char *p1;
+ char *p2;
+
+ if (min->len == 0 || max->len == 0) {
+ pfx->len = 0;
+ return;
+ }
+
+ if (F_ISSET(bt->flags, BT_REVERSEKEY)) {
+ p1 = min->str + min->len - 1;
+ p2 = max->str + max->len - 1;
+
+ while (*p1 == *p2) {
+ if (p1 < min->str || p2 < max->str)
+ break;
+ p1--;
+ p2--;
+ n++;
+ }
+
+ assert(n <= (int)sizeof(pfx->str));
+ pfx->len = n;
+ bcopy(p2 + 1, pfx->str, n);
+ } else {
+ p1 = min->str;
+ p2 = max->str;
+
+ while (*p1 == *p2) {
+ if (n == min->len || n == max->len)
+ break;
+ p1++;
+ p2++;
+ n++;
+ }
+
+ assert(n <= (int)sizeof(pfx->str));
+ pfx->len = n;
+ bcopy(max->str, pfx->str, n);
+ }
+}
+
+static void
+remove_prefix(struct btree *bt, struct btval *key, size_t pfxlen)
+{
+ if (pfxlen == 0 || bt->cmp != NULL)
+ return;
+
+ DPRINTF("removing %zu bytes of prefix from key [%.*s]", pfxlen,
+ (int)key->size, (char *)key->data);
+ assert(pfxlen <= key->size);
+ key->size -= pfxlen;
+ if (!F_ISSET(bt->flags, BT_REVERSEKEY))
+ key->data = (char *)key->data + pfxlen;
+}
+
+static void
+expand_prefix(struct btree *bt, struct mpage *mp, indx_t indx,
+ struct btkey *expkey)
+{
+ struct node *node;
+
+ node = NODEPTR(mp, indx);
+ expkey->len = sizeof(expkey->str);
+ concat_prefix(bt, mp->prefix.str, mp->prefix.len,
+ NODEKEY(node), node->ksize, expkey->str, &expkey->len);
+}
+
+static int
+bt_cmp(struct btree *bt, const struct btval *key1, const struct btval *key2,
+ struct btkey *pfx)
+{
+ if (F_ISSET(bt->flags, BT_REVERSEKEY))
+ return memnrcmp(key1->data, key1->size - pfx->len,
+ key2->data, key2->size);
+ else
+ return memncmp((char *)key1->data + pfx->len, key1->size - pfx->len,
+ key2->data, key2->size);
+}
+
+void
+btval_reset(struct btval *btv)
+{
+ if (btv) {
+ if (btv->mp)
+ btv->mp->ref--;
+ if (btv->free_data)
+ free(btv->data);
+ bzero(btv, sizeof(*btv));
+ }
+}
+
+static int
+mpage_cmp(struct mpage *a, struct mpage *b)
+{
+ if (a->pgno > b->pgno)
+ return 1;
+ if (a->pgno < b->pgno)
+ return -1;
+ return 0;
+}
+
+static struct mpage *
+mpage_lookup(struct btree *bt, pgno_t pgno)
+{
+ struct mpage find, *mp;
+
+ find.pgno = pgno;
+ mp = RB_FIND(page_cache, bt->page_cache, &find);
+ if (mp) {
+ bt->stat.hits++;
+ /* Update LRU queue. Move page to the end. */
+ TAILQ_REMOVE(bt->lru_queue, mp, lru_next);
+ TAILQ_INSERT_TAIL(bt->lru_queue, mp, lru_next);
+ }
+ return mp;
+}
+
+static void
+mpage_add(struct btree *bt, struct mpage *mp)
+{
+ assert(RB_INSERT(page_cache, bt->page_cache, mp) == NULL);
+ bt->stat.cache_size++;
+ TAILQ_INSERT_TAIL(bt->lru_queue, mp, lru_next);
+}
+
+static void
+mpage_del(struct btree *bt, struct mpage *mp)
+{
+ assert(RB_REMOVE(page_cache, bt->page_cache, mp) == mp);
+ assert(bt->stat.cache_size > 0);
+ bt->stat.cache_size--;
+ TAILQ_REMOVE(bt->lru_queue, mp, lru_next);
+}
+
+static struct mpage *
+mpage_copy(struct mpage *mp)
+{
+ struct mpage *copy;
+
+ if ((copy = calloc(1, sizeof(*copy))) == NULL)
+ return NULL;
+ bcopy(&mp->page, &copy->page, sizeof(mp->page));
+ bcopy(&mp->prefix, &copy->prefix, sizeof(mp->prefix));
+ copy->parent = mp->parent;
+ copy->parent_index = mp->parent_index;
+ copy->pgno = mp->pgno;
+ copy->pgno = mp->pgno;
+
+ return copy;
+}
+
+/* Remove the least recently used memory pages until the cache size is
+ * within the configured bounds. Pages referenced by cursors or returned
+ * key/data are not pruned.
+ */
+static void
+mpage_prune(struct btree *bt)
+{
+ struct mpage *mp, *next;
+
+ for (mp = TAILQ_FIRST(bt->lru_queue); mp; mp = next) {
+ if (bt->stat.cache_size <= bt->stat.max_cache)
+ break;
+ next = TAILQ_NEXT(mp, lru_next);
+ if (!mp->dirty && mp->ref <= 0) {
+ mpage_del(bt, mp);
+ free(mp);
+ }
+ }
+}
+
+/* Mark a page as dirty and push it on the dirty queue.
+ */
+static void
+mpage_dirty(struct btree *bt, struct mpage *mp)
+{
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+
+ if (!mp->dirty) {
+ mp->dirty = 1;
+ SIMPLEQ_INSERT_TAIL(bt->txn->dirty_queue, mp, next);
+ }
+}
+
+/* Touch a page: make it dirty and re-insert into tree with updated pgno.
+ */
+static void
+mpage_touch(struct btree *bt, struct mpage *mp)
+{
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+ assert(mp != NULL);
+
+ if (!mp->dirty) {
+ DPRINTF("touching page %u -> %u", mp->pgno, bt->txn->next_pgno);
+ if (mp->ref == 0)
+ mpage_del(bt, mp);
+ else
+ mp = mpage_copy(mp);
+ mp->pgno = mp->page.pgno = bt->txn->next_pgno++;
+ mpage_dirty(bt, mp);
+ mpage_add(bt, mp);
+
+ /* Update the page number to new touched page. */
+ if (mp->parent != NULL)
+ NODEPGNO(NODEPTR(mp->parent,
+ mp->parent_index)) = mp->pgno;
+ }
+}
+
+static int
+btree_read_page(struct btree *bt, pgno_t pgno, struct page *page)
+{
+ ssize_t rc;
+
+ DPRINTF("reading page %u", pgno);
+ bt->stat.reads++;
+ if ((rc = pread(bt->fd, page, PAGESIZE, (off_t)pgno*PAGESIZE)) == 0) {
+ DPRINTF("page %u doesn't exist", pgno);
+ return BT_NOTFOUND;
+ }
+ else if (rc != PAGESIZE) {
+ DPRINTF("read: %s", strerror(errno));
+ return BT_FAIL;
+ }
+
+ if (page->pgno != pgno) {
+ DPRINTF("page numbers don't match: %u != %u", pgno, page->pgno);
+ errno = EINVAL;
+ return BT_FAIL;
+ }
+
+ return BT_SUCCESS;
+}
+
+int
+btree_sync(struct btree *bt)
+{
+ if (!F_ISSET(bt->flags, BT_NOSYNC))
+ return fsync(bt->fd);
+ return 0;
+}
+
+struct btree_txn *
+btree_txn_begin(struct btree *bt, int rdonly)
+{
+ int rc;
+ struct btree_txn *txn;
+
+ if (!rdonly && bt->txn != NULL) {
+ DPRINTF("write transaction already begun");
+ return NULL;
+ }
+
+ if ((txn = calloc(1, sizeof(*txn))) == NULL) {
+ DPRINTF("calloc: %s", strerror(errno));
+ return NULL;
+ }
+
+ if (rdonly) {
+ txn->flags |= BT_TXN_RDONLY;
+ } else {
+ txn->dirty_queue = calloc(1, sizeof(*txn->dirty_queue));
+ if (txn->dirty_queue == NULL) {
+ free(txn);
+ return NULL;
+ }
+ SIMPLEQ_INIT(txn->dirty_queue);
+
+ DPRINTF("taking write lock on txn %p", txn);
+ if (flock(bt->fd, LOCK_EX | LOCK_NB) != 0) {
+ free(txn->dirty_queue);
+ free(txn);
+ return NULL;
+ }
+ bt->txn = txn;
+ }
+
+ txn->bt = bt;
+ bt->ref++;
+
+ if ((rc = btree_read_meta(bt, &txn->next_pgno)) == BT_FAIL) {
+ btree_txn_abort(txn);
+ return NULL;
+ }
+
+ txn->root = bt->meta->root;
+ DPRINTF("begin transaction on btree %p, root page %u", bt, txn->root);
+
+ return txn;
+}
+
+void
+btree_txn_abort(struct btree_txn *txn)
+{
+ struct mpage *mp;
+ struct btree *bt;
+
+ if (txn == NULL)
+ return;
+
+ bt = txn->bt;
+ DPRINTF("abort transaction on btree %p, root page %u", bt, txn->root);
+
+ /* Discard all dirty pages.
+ */
+ while (!SIMPLEQ_EMPTY(txn->dirty_queue)) {
+ mp = SIMPLEQ_FIRST(txn->dirty_queue);
+ assert(mp->ref == 0); /* cursors should be closed */
+ mpage_del(bt, mp);
+ SIMPLEQ_REMOVE_HEAD(txn->dirty_queue, next);
+ }
+
+ if (!F_ISSET(txn->flags, BT_TXN_RDONLY)) {
+ DPRINTF("releasing write lock on txn %p", txn);
+ txn->bt->txn = NULL;
+ if (flock(txn->bt->fd, LOCK_UN) != 0) {
+ DPRINTF("failed to unlock fd %d: %s",
+ txn->bt->fd, strerror(errno));
+ }
+ free(txn->dirty_queue);
+ }
+
+ btree_close(txn->bt);
+ free(txn);
+}
+
+int
+btree_txn_commit(struct btree_txn *txn)
+{
+ int n, done;
+ ssize_t rc;
+ off_t size;
+ struct mpage *mp;
+ struct btree *bt;
+ struct iovec iov[BT_COMMIT_PAGES];
+
+ assert(txn != NULL);
+ assert(txn->bt != NULL);
+
+ bt = txn->bt;
+
+ if (F_ISSET(txn->flags, BT_TXN_RDONLY)) {
+ DPRINTF("attempt to commit read-only transaction");
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+
+ if (txn != bt->txn) {
+ DPRINTF("attempt to commit unknown transaction");
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+
+ if (F_ISSET(txn->flags, BT_TXN_ERROR)) {
+ DPRINTF("error flag is set, can't commit");
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+
+ if (SIMPLEQ_EMPTY(txn->dirty_queue))
+ goto done;
+
+ if (F_ISSET(bt->flags, BT_FIXPADDING)) {
+ size = lseek(bt->fd, 0, SEEK_END);
+ size += PAGESIZE - (size % PAGESIZE);
+ DPRINTF("extending to multiple of page size: %llu", size);
+ if (ftruncate(bt->fd, size) != 0) {
+ DPRINTF("ftruncate: %s", strerror(errno));
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+ bt->flags &= ~BT_FIXPADDING;
+ }
+
+ DPRINTF("committing transaction on btree %p, root page %u",
+ bt, txn->root);
+
+ /* Commit up to BT_COMMIT_PAGES dirty pages to disk until done.
+ */
+ do {
+ n = 0;
+ done = 1;
+ SIMPLEQ_FOREACH(mp, txn->dirty_queue, next) {
+ DPRINTF("commiting page %u", mp->pgno);
+ iov[n].iov_len = PAGESIZE;
+ iov[n].iov_base = &mp->page;
+ if (++n >= BT_COMMIT_PAGES) {
+ done = 0;
+ break;
+ }
+ }
+
+ if (n == 0)
+ break;
+
+ DPRINTF("commiting %u dirty pages", n);
+ rc = writev(bt->fd, iov, n);
+ if (rc != PAGESIZE*n) {
+ if (rc > 0)
+ DPRINTF("short write, filesystem full?");
+ else
+ DPRINTF("writev: %s", strerror(errno));
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+
+ /* Remove the dirty flag from the written pages.
+ */
+ while (!SIMPLEQ_EMPTY(txn->dirty_queue)) {
+ mp = SIMPLEQ_FIRST(txn->dirty_queue);
+ mp->dirty = 0;
+ SIMPLEQ_REMOVE_HEAD(txn->dirty_queue, next);
+ if (--n == 0)
+ break;
+ }
+ } while (!done);
+
+ if (btree_sync(bt) != 0 ||
+ btree_write_meta(bt, txn->root) != BT_SUCCESS ||
+ btree_sync(bt) != 0) {
+ btree_txn_abort(txn);
+ return BT_FAIL;
+ }
+
+done:
+ mpage_prune(bt);
+ btree_txn_abort(txn);
+
+ return BT_SUCCESS;
+}
+
+static int
+btree_write_meta(struct btree *bt, pgno_t root)
+{
+ ssize_t rc;
+
+ DPRINTF("writing meta page for root page %u", root);
+
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+ assert(bt->metapage != NULL);
+ assert(bt->meta != NULL);
+
+ bt->meta->prev_meta = bt->metapage->pgno;
+ bt->metapage->pgno = bt->txn->next_pgno++;
+
+ bt->meta->root = root;
+ bt->meta->created_at = time(0);
+ bt->meta->revisions++;
+ SHA1((unsigned char *)bt->meta, METAHASHLEN, bt->meta->hash);
+
+ if ((rc = write(bt->fd, bt->metapage, PAGESIZE)) != PAGESIZE) {
+ if (rc > 0)
+ DPRINTF("short write, filesystem full?");
+ return BT_FAIL;
+ }
+
+ if ((bt->size = lseek(bt->fd, 0, SEEK_END)) == -1) {
+ DPRINTF("failed to update file size: %s", strerror(errno));
+ bt->size = 0;
+ }
+
+ return BT_SUCCESS;
+}
+
+/* Returns true if page p is a valid meta page, false otherwise.
+ */
+static int
+btree_is_meta_page(struct page *p)
+{
+ struct bt_meta *m;
+ unsigned char hash[SHA_DIGEST_LENGTH];
+
+ m = METADATA(p);
+ if (!F_ISSET(p->flags, P_META)) {
+ DPRINTF("page %d not a meta page", p->pgno);
+ return 0;
+ }
+
+ if (m->magic != BT_MAGIC) {
+ DPRINTF("page %d has invalid magic", p->pgno);
+ return 0;
+ }
+
+ if (m->root >= p->pgno && m->root != P_INVALID) {
+ DPRINTF("page %d points to an invalid root page", p->pgno);
+ return 0;
+ }
+
+ SHA1((unsigned char *)m, METAHASHLEN, hash);
+ if (bcmp(hash, m->hash, SHA_DIGEST_LENGTH) != 0) {
+ DPRINTF("page %d has an invalid digest", p->pgno);
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+btree_init_meta(struct btree *bt)
+{
+ bt->metapage->flags = P_META;
+ bt->meta = METADATA(bt->metapage);
+ bt->meta->magic = BT_MAGIC;
+ bt->meta->version = BT_VERSION;
+ bt->meta->flags = 0;
+ bt->meta->psize = PAGESIZE;
+ bt->meta->root = P_INVALID;
+}
+
+static int
+btree_read_meta(struct btree *bt, pgno_t *p_next)
+{
+ pgno_t meta_pgno, next_pgno;
+ off_t size;
+ int rc;
+
+ assert(bt != NULL);
+
+ if ((size = lseek(bt->fd, 0, SEEK_END)) == -1)
+ goto fail;
+
+ DPRINTF("btree_read_meta: size = %llu", size);
+
+ if (size < bt->size) {
+ DPRINTF("file has shrunk!");
+ errno = EIO;
+ goto fail;
+ }
+
+ if (size == 0) {
+ if (p_next != NULL)
+ *p_next = 0;
+ return BT_NOTFOUND; /* new file */
+ }
+
+ next_pgno = size / PAGESIZE;
+ if (next_pgno == 0) {
+ DPRINTF("corrupt file");
+ errno = EIO;
+ goto fail;
+ }
+
+ meta_pgno = next_pgno - 1;
+
+ if (size % PAGESIZE != 0) {
+ DPRINTF("filesize not a multiple of the page size!");
+ bt->flags |= BT_FIXPADDING;
+ next_pgno++;
+ }
+
+ if (p_next != NULL)
+ *p_next = next_pgno;
+
+ if (size == bt->size) {
+ DPRINTF("size unchanged, keeping current meta page");
+ return BT_SUCCESS;
+ }
+ bt->size = size;
+
+ while (meta_pgno > 0) {
+ rc = btree_read_page(bt, meta_pgno, bt->metapage);
+ if (rc == BT_NOTFOUND)
+ break; /* no meta page found */
+ if (rc == BT_SUCCESS && btree_is_meta_page(bt->metapage)) {
+ bt->meta = METADATA(bt->metapage);
+ return BT_SUCCESS;
+ }
+ --meta_pgno; /* scan backwards to first valid meta page */
+ }
+
+ errno = EIO;
+fail:
+ if (p_next != NULL)
+ *p_next = P_INVALID;
+ return BT_FAIL;
+}
+
+struct btree *
+btree_open_fd(int fd, uint32_t flags)
+{
+ int fl;
+ struct btree *bt;
+
+ fl = fcntl(fd, F_GETFL, 0);
+ if (fcntl(fd, F_SETFL, fl | O_APPEND) == -1)
+ return NULL;
+
+ if ((bt = calloc(1, sizeof(*bt))) == NULL)
+ return NULL;
+ bt->fd = fd;
+ bt->flags = flags;
+ bt->flags &= ~BT_FIXPADDING;
+
+ if ((bt->page_cache = calloc(1, sizeof(*bt->page_cache))) == NULL)
+ goto fail;
+ bt->stat.max_cache = BT_MAXCACHE_DEF;
+ RB_INIT(bt->page_cache);
+
+ if ((bt->lru_queue = calloc(1, sizeof(*bt->lru_queue))) == NULL)
+ goto fail;
+ TAILQ_INIT(bt->lru_queue);
+
+ if ((bt->metapage = calloc(1, PAGESIZE)) == NULL)
+ goto fail;
+
+ if (btree_read_meta(bt, NULL) == BT_FAIL)
+ goto fail;
+
+ if (bt->meta == NULL) {
+ DPRINTF("new database");
+ btree_init_meta(bt);
+ } else if (bt->meta->version != BT_VERSION) {
+ DPRINTF("database is version %u, expected version %u",
+ bt->meta->version, BT_VERSION);
+ errno = EINVAL;
+ goto fail;
+ } else {
+ DPRINTF("opened database version %u, pagesize %u",
+ bt->meta->version, bt->meta->psize);
+ DPRINTF("timestamp: %s", ctime(&bt->meta->created_at));
+ DPRINTF("depth: %u", bt->meta->depth);
+ DPRINTF("entries: %llu", bt->meta->entries);
+ DPRINTF("revisions: %u", bt->meta->revisions);
+ DPRINTF("branch pages: %u", bt->meta->branch_pages);
+ DPRINTF("leaf pages: %u", bt->meta->leaf_pages);
+ DPRINTF("overflow pages: %u", bt->meta->overflow_pages);
+ DPRINTF("root: %u", bt->meta->root);
+ DPRINTF("previous meta page: %u", bt->meta->prev_meta);
+ }
+
+ return bt;
+
+fail:
+ free(bt->lru_queue);
+ free(bt->page_cache);
+ free(bt->metapage);
+ free(bt);
+ return NULL;
+}
+
+struct btree *
+btree_open(const char *path, uint32_t flags, mode_t mode)
+{
+ int fd, oflags;
+ struct btree *bt;
+
+ if (F_ISSET(flags, BT_RDONLY))
+ oflags = O_RDONLY;
+ else
+ oflags = O_RDWR | O_CREAT | O_APPEND;
+
+ if ((fd = open(path, oflags, mode)) == -1)
+ return NULL;
+
+ if ((bt = btree_open_fd(fd, flags)) == NULL)
+ close(fd);
+ else {
+ bt->path = strdup(path);
+ bt->ref = 1;
+ DPRINTF("opened btree %p", bt);
+ }
+
+ return bt;
+}
+
+void
+btree_close(struct btree *bt)
+{
+ struct mpage *mp;
+
+ if (bt != NULL && --bt->ref == 0) {
+ DPRINTF("ref is zero, closing btree %p", bt);
+ close(bt->fd);
+
+ /* Free page_cache. */
+ while ((mp = RB_MIN(page_cache, bt->page_cache)) != NULL) {
+ mpage_del(bt, mp);
+ free(mp);
+ }
+ free(bt->page_cache);
+ free(bt);
+ }
+}
+
+/* Search for key within a leaf page, using binary search.
+ * Returns the smallest entry larger or equal to the key.
+ * If exactp is non-null, stores whether the found entry was an exact match
+ * in *exactp (1 or 0).
+ * If kip is non-null, stores the index of the found entry in *kip.
+ * If no entry larger of equal to the key is found, returns NULL.
+ */
+static struct node *
+btree_search_node(struct btree *bt, struct mpage *mp, struct btval *key,
+ int *exactp, unsigned int *kip)
+{
+ unsigned int i = 0;
+ int low, high;
+ int rc = 0;
+ struct node *node;
+ struct btval nodekey;
+
+ DPRINTF("searching %lu keys in %s page %u with prefix [%.*s]",
+ NUMKEYS(mp),
+ IS_LEAF(mp) ? "leaf" : "branch",
+ mp->pgno, (int)mp->prefix.len, (char *)mp->prefix.str);
+
+ assert(NUMKEYS(mp) > 0);
+
+ bzero(&nodekey, sizeof(nodekey));
+
+ low = IS_LEAF(mp) ? 0 : 1;
+ high = NUMKEYS(mp) - 1;
+ while (low <= high) {
+ i = (low + high) >> 1;
+ node = NODEPTR(mp, i);
+
+ nodekey.size = node->ksize;
+ nodekey.data = NODEKEY(node);
+
+ if (bt->cmp)
+ rc = bt->cmp(key, &nodekey);
+ else
+ rc = bt_cmp(bt, key, &nodekey, &mp->prefix);
+
+ if (IS_LEAF(mp))
+ DPRINTF("found leaf index %u [%.*s], rc = %i",
+ i, (int)nodekey.size, (char *)nodekey.data, rc);
+ else
+ DPRINTF("found branch index %u [%.*s -> %u], rc = %i",
+ i, (int)node->ksize, (char *)NODEKEY(node),
+ node->n_pgno, rc);
+
+ if (rc == 0)
+ break;
+ if (rc > 0)
+ low = i + 1;
+ else
+ high = i - 1;
+ }
+
+ if (rc > 0) { /* Found entry is less than the key. */
+ i++; /* Skip to get the smallest entry larger than key. */
+ if (i >= NUMKEYS(mp))
+ /* There is no entry larger or equal to the key. */
+ return NULL;
+ }
+ if (exactp)
+ *exactp = (rc == 0);
+ if (kip) /* Store the key index if requested. */
+ *kip = i;
+
+ return NODEPTR(mp, i);
+}
+
+static void
+cursor_pop_page(struct cursor *cursor)
+{
+ struct ppage *top;
+
+ top = CURSOR_TOP(cursor);
+ CURSOR_POP(cursor);
+ top->mpage->ref--;
+
+ DPRINTF("popped page %u off cursor %p", top->mpage->pgno, cursor);
+
+ free(top);
+}
+
+static struct ppage *
+cursor_push_page(struct cursor *cursor, struct mpage *mp)
+{
+ struct ppage *ppage;
+
+ DPRINTF("pushing page %u on cursor %p", mp->pgno, cursor);
+
+ if ((ppage = calloc(1, sizeof(*ppage))) == NULL)
+ return NULL;
+ ppage->mpage = mp;
+ mp->ref++;
+ CURSOR_PUSH(cursor, ppage);
+ return ppage;
+}
+
+static int
+btree_get_mpage(struct btree *bt, pgno_t pgno, struct mpage **mpp)
+{
+ int rc;
+ struct mpage *mp;
+
+ mp = mpage_lookup(bt, pgno);
+ if (mp == NULL) {
+ if ((mp = calloc(1, sizeof(*mp))) == NULL)
+ return BT_FAIL;
+ if ((rc = btree_read_page(bt, pgno, &mp->page)) != BT_SUCCESS) {
+ free(mp);
+ return rc;
+ }
+ mp->pgno = pgno;
+ mpage_add(bt, mp);
+ } else
+ DPRINTF("returning page %u from cache", pgno);
+
+ *mpp = mp;
+ return BT_SUCCESS;
+}
+
+static void
+concat_prefix(struct btree *bt, char *s1, size_t n1, char *s2, size_t n2,
+ char *cs, size_t *cn)
+{
+ assert(*cn >= n1 + n2);
+ if (F_ISSET(bt->flags, BT_REVERSEKEY)) {
+ bcopy(s2, cs, n2);
+ bcopy(s1, cs + n2, n1);
+ } else {
+ bcopy(s1, cs, n1);
+ bcopy(s2, cs + n1, n2);
+ }
+ *cn = n1 + n2;
+}
+
+static void
+find_common_prefix(struct btree *bt, struct mpage *mp)
+{
+ indx_t lbound = 0, ubound = 0;
+ struct mpage *lp, *up;
+ struct btkey lprefix, uprefix;
+
+ mp->prefix.len = 0;
+ if (bt->cmp != NULL)
+ return;
+
+ lp = mp;
+ while (lp->parent != NULL) {
+ if (lp->parent_index > 0) {
+ lbound = lp->parent_index;
+ break;
+ }
+ lp = lp->parent;
+ }
+
+ up = mp;
+ while (up->parent != NULL) {
+ if (up->parent_index + 1 < (indx_t)NUMKEYS(up->parent)) {
+ ubound = up->parent_index + 1;
+ break;
+ }
+ up = up->parent;
+ }
+
+ if (lp->parent != NULL && up->parent != NULL) {
+ expand_prefix(bt, lp->parent, lbound, &lprefix);
+ expand_prefix(bt, up->parent, ubound, &uprefix);
+ common_prefix(bt, &lprefix, &uprefix, &mp->prefix);
+ }
+ else if (mp->parent)
+ bcopy(&mp->parent->prefix, &mp->prefix, sizeof(mp->prefix));
+
+ DPRINTF("found common prefix [%.*s] (len %zu) for page %u",
+ (int)mp->prefix.len, mp->prefix.str, mp->prefix.len, mp->pgno);
+}
+
+static int
+btree_search_page_root(struct btree *bt, struct mpage *root, struct btval *key,
+ struct cursor *cursor, int modify, struct mpage **mpp)
+{
+ struct mpage *mp, *parent;
+
+ if (cursor && cursor_push_page(cursor, root) == NULL)
+ return BT_FAIL;
+
+ mp = root;
+ while (IS_BRANCH(mp)) {
+ unsigned int i = 0;
+ struct node *node;
+
+ DPRINTF("branch page %u has %lu keys", mp->pgno, NUMKEYS(mp));
+ assert(NUMKEYS(mp) > 1);
+ node = NODEPTR(mp, 0);
+ DPRINTF("found index 0 to page %u", NODEPGNO(node));
+
+ if (key == NULL) /* Initialize cursor to first page. */
+ i = 0;
+ else {
+ int exact;
+ node = btree_search_node(bt, mp, key, &exact, &i);
+ if (node == NULL)
+ i = NUMKEYS(mp) - 1;
+ else if (!exact) {
+ assert(i > 0);
+ i--;
+ }
+ }
+
+ if (key)
+ DPRINTF("following index %u for key %.*s",
+ i, (int)key->size, (char *)key->data);
+ assert(i >= 0 && i < NUMKEYS(mp));
+ node = NODEPTR(mp, i);
+
+ if (cursor)
+ CURSOR_TOP(cursor)->ki = i;
+
+ parent = mp;
+ if (btree_get_mpage(bt, NODEPGNO(node), &mp) != BT_SUCCESS)
+ return BT_FAIL;
+ mp->parent = parent;
+ mp->parent_index = i;
+ find_common_prefix(bt, mp);
+
+ if (cursor && cursor_push_page(cursor, mp) == NULL)
+ return BT_FAIL;
+
+ if (modify)
+ mpage_touch(bt, mp);
+ }
+
+ if (!IS_LEAF(mp)) {
+ DPRINTF("internal error, index points to a %02X page!?",
+ mp->page.flags);
+ return BT_FAIL;
+ }
+
+ DPRINTF("found leaf page %u for key %.*s", mp->pgno,
+ key ? (int)key->size : 0, key ? (char *)key->data : NULL);
+
+ *mpp = mp;
+ return BT_SUCCESS;
+}
+
+/* Search for the page a given key should be in.
+ * Stores a pointer to the found page in *mpp.
+ * If key is NULL, search for the lowest page (used by btree_cursor_first).
+ * If cursor is non-null, pushes parent pages on the cursor stack.
+ * If modify is true, visited pages are updated with new page numbers.
+ */
+static int
+btree_search_page(struct btree *bt, struct btree_txn *txn, struct btval *key,
+ struct cursor *cursor, int modify, struct mpage **mpp)
+{
+ int rc;
+ pgno_t root;
+ struct mpage *mp;
+
+ /* Can't modify pages outside a transaction. */
+ if (txn == NULL && modify) {
+ errno = EINVAL;
+ return BT_FAIL;
+ }
+
+ /* Choose which root page to start with. If a transaction is given
+ * use the root page from the transaction, otherwise read the last
+ * committed root page.
+ */
+ if (txn == NULL) {
+ if ((rc = btree_read_meta(bt, NULL)) != BT_SUCCESS)
+ return rc;
+ root = bt->meta->root;
+ } else if (F_ISSET(txn->flags, BT_TXN_ERROR)) {
+ DPRINTF("transaction has failed, must abort");
+ return BT_FAIL;
+ } else
+ root = txn->root;
+
+ if (root == P_INVALID) /* Tree is empty. */
+ return BT_NOTFOUND;
+
+ if ((rc = btree_get_mpage(bt, root, &mp)) != BT_SUCCESS)
+ return rc;
+
+ assert(mp->parent == NULL);
+ assert(mp->prefix.len == 0);
+
+ if (modify && !mp->dirty) {
+ mpage_touch(bt, mp);
+ txn->root = mp->pgno;
+ }
+
+ return btree_search_page_root(bt, mp, key, cursor, modify, mpp);
+}
+
+static int
+btree_read_data(struct btree *bt, struct mpage *mp, struct node *leaf,
+ struct btval *data)
+{
+ size_t psz;
+ size_t max;
+ size_t sz = 0;
+ pgno_t pgno;
+ struct page p;
+
+ bzero(data, sizeof(*data));
+ max = PAGESIZE - PAGEHDRSZ;
+
+ if (!F_ISSET(leaf->flags, F_BIGDATA)) {
+ data->size = leaf->n_dsize;
+ if (data->size > 0) {
+ if (mp == NULL) {
+ if ((data->data = malloc(data->size)) == NULL)
+ return BT_FAIL;
+ bcopy(NODEDATA(leaf), data->data, data->size);
+ data->free_data = 1;
+ data->mp = NULL;
+ } else {
+ data->data = NODEDATA(leaf);
+ data->free_data = 0;
+ data->mp = mp;
+ mp->ref++;
+ }
+ }
+ return BT_SUCCESS;
+ }
+
+ /* Read overflow data.
+ */
+ DPRINTF("allocating %u byte for overflow data", leaf->n_dsize);
+ if ((data->data = malloc(leaf->n_dsize)) == NULL)
+ return BT_FAIL;
+ data->size = leaf->n_dsize;
+ data->free_data = 1;
+ data->mp = NULL;
+ pgno = *(pgno_t *)NODEDATA(leaf); /* XXX: alignment? */
+ for (sz = 0; sz < data->size; ) {
+ if (btree_read_page(bt, pgno, &p) != 0 ||
+ !F_ISSET(p.flags, P_OVERFLOW)) {
+ DPRINTF("read overflow page failed (%02x)", p.flags);
+ free(data->data);
+ return BT_FAIL;
+ }
+ psz = data->size - sz;
+ if (psz > max)
+ psz = max;
+ bcopy(p.ptrs, (char *)data->data + sz, psz);
+ sz += psz;
+ pgno = p.p_next_pgno;
+ }
+
+ return BT_SUCCESS;
+}
+
+int
+btree_txn_get(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data)
+{
+ int rc, exact;
+ struct node *leaf;
+ struct mpage *mp;
+
+ assert(bt);
+ assert(key);
+ assert(data);
+ DPRINTF("===> get key [%.*s]", (int)key->size, (char *)key->data);
+
+ if (key->size == 0 || key->size > MAXKEYSIZE) {
+ errno = EINVAL;
+ return BT_FAIL;
+ }
+
+ if ((rc = btree_search_page(bt, txn, key, NULL, 0, &mp)) != BT_SUCCESS)
+ return rc;
+
+ leaf = btree_search_node(bt, mp, key, &exact, NULL);
+ if (leaf && exact)
+ rc = btree_read_data(bt, mp, leaf, data);
+ else
+ rc = BT_NOTFOUND;
+
+ mpage_prune(bt);
+ return rc;
+}
+
+static int
+btree_sibling(struct cursor *cursor, int move_right)
+{
+ int rc;
+ struct node *indx;
+ struct ppage *parent, *top;
+ struct mpage *mp;
+
+ top = CURSOR_TOP(cursor);
+ if ((parent = SLIST_NEXT(top, entry)) == NULL)
+ return BT_NOTFOUND; /* root has no siblings */
+
+ DPRINTF("parent page is page %u, index %u",
+ parent->mpage->pgno, parent->ki);
+
+ cursor_pop_page(cursor);
+ if (move_right ? (parent->ki + 1 >= NUMKEYS(parent->mpage))
+ : (parent->ki == 0)) {
+ DPRINTF("no more keys left, moving to %s sibling",
+ move_right ? "right" : "left");
+ if ((rc = btree_sibling(cursor, move_right)) != BT_SUCCESS)
+ return rc;
+ parent = CURSOR_TOP(cursor);
+ } else {
+ if (move_right)
+ parent->ki++;
+ else
+ parent->ki--;
+ DPRINTF("just moving to %s index key %u",
+ move_right ? "right" : "left", parent->ki);
+ }
+ assert(IS_BRANCH(parent->mpage));
+
+ indx = NODEPTR(parent->mpage, parent->ki);
+ if (btree_get_mpage(cursor->bt, indx->n_pgno, &mp) != BT_SUCCESS)
+ return BT_FAIL;
+ mp->parent = parent->mpage;
+ mp->parent_index = parent->ki;
+
+ cursor_push_page(cursor, mp);
+ find_common_prefix(cursor->bt, mp);
+
+ return BT_SUCCESS;
+}
+
+static int
+bt_set_key(struct btree *bt, struct mpage *mp, struct node *node,
+ struct btval *key)
+{
+ if (key == NULL)
+ return 0;
+
+ if (mp->prefix.len > 0) {
+ key->size = node->ksize + mp->prefix.len;
+ key->data = malloc(key->size);
+ if (key->data == NULL)
+ return -1;
+ concat_prefix(bt,
+ mp->prefix.str, mp->prefix.len,
+ NODEKEY(node), node->ksize,
+ key->data, &key->size);
+ key->free_data = 1;
+ } else {
+ key->size = node->ksize;
+ key->data = NODEKEY(node);
+ key->free_data = 0;
+ key->mp = mp;
+ mp->ref++;
+ }
+
+ return 0;
+}
+
+static int
+btree_cursor_next(struct cursor *cursor, struct btval *key, struct btval *data)
+{
+ struct ppage *top;
+ struct mpage *mp;
+ struct node *leaf;
+
+ if (cursor->eof)
+ return BT_NOTFOUND;
+
+ assert(cursor->initialized);
+
+ top = CURSOR_TOP(cursor);
+ mp = top->mpage;
+
+ DPRINTF("cursor_next: top page is %u in cursor %p", mp->pgno, cursor);
+
+ if (top->ki + 1 >= NUMKEYS(mp)) {
+ DPRINTF("=====> move to next sibling page");
+ if (btree_sibling(cursor, 1) != BT_SUCCESS) {
+ cursor->eof = 1;
+ return BT_NOTFOUND;
+ }
+ top = CURSOR_TOP(cursor);
+ mp = top->mpage;
+ DPRINTF("next page is %u, key index %u", mp->pgno, top->ki);
+ } else
+ top->ki++;
+
+ DPRINTF("==> cursor points to page %u with %lu keys, key index %u",
+ mp->pgno, NUMKEYS(mp), top->ki);
+
+ assert(IS_LEAF(mp));
+ leaf = NODEPTR(mp, top->ki);
+
+ if (data && btree_read_data(cursor->bt, mp, leaf, data) != BT_SUCCESS)
+ return BT_FAIL;
+
+ if (bt_set_key(cursor->bt, mp, leaf, key) != 0)
+ return BT_FAIL;
+
+ return BT_SUCCESS;
+}
+
+static int
+btree_cursor_set(struct cursor *cursor, struct btval *key, struct btval *data)
+{
+ int rc;
+ struct node *leaf;
+ struct mpage *mp;
+ struct ppage *top;
+
+ assert(cursor);
+ assert(key);
+ assert(key->size > 0);
+
+ rc = btree_search_page(cursor->bt, cursor->txn, key, cursor, 0, &mp);
+ if (rc != BT_SUCCESS)
+ return rc;
+ assert(IS_LEAF(mp));
+
+ top = CURSOR_TOP(cursor);
+ leaf = btree_search_node(cursor->bt, mp, key, NULL, &top->ki);
+ if (leaf == NULL) {
+ DPRINTF("===> inexact leaf not found, goto sibling");
+ if (btree_sibling(cursor, 1) != BT_SUCCESS)
+ return BT_NOTFOUND; /* no entries matched */
+ top = CURSOR_TOP(cursor);
+ top->ki = 0;
+ mp = top->mpage;
+ assert(IS_LEAF(mp));
+ leaf = NODEPTR(mp, 0);
+ }
+
+ cursor->initialized = 1;
+ cursor->eof = 0;
+
+ if (data && btree_read_data(cursor->bt, mp, leaf, data) != BT_SUCCESS)
+ return BT_FAIL;
+
+ if (bt_set_key(cursor->bt, mp, leaf, key) != 0)
+ return BT_FAIL;
+ DPRINTF("==> cursor placed on key %.*s",
+ (int)key->size, (char *)key->data);
+
+ return BT_SUCCESS;
+}
+
+static int
+btree_cursor_first(struct cursor *cursor, struct btval *key, struct btval *data)
+{
+ int rc;
+ struct mpage *mp;
+ struct node *leaf;
+
+ rc = btree_search_page(cursor->bt, cursor->txn, NULL, cursor, 0, &mp);
+ if (rc != BT_SUCCESS)
+ return rc;
+ assert(IS_LEAF(mp));
+
+ leaf = NODEPTR(mp, 0);
+ cursor->initialized = 1;
+ cursor->eof = 0;
+
+ if (data && btree_read_data(cursor->bt, mp, leaf, data) != BT_SUCCESS)
+ return BT_FAIL;
+
+ if (bt_set_key(cursor->bt, mp, leaf, key) != 0)
+ return BT_FAIL;
+
+ return BT_SUCCESS;
+}
+
+int
+btree_cursor_get(struct cursor *cursor, struct btval *key, struct btval *data,
+ enum cursor_op op)
+{
+ int rc;
+
+ assert(cursor);
+
+ switch (op) {
+ case BT_CURSOR:
+ while (CURSOR_TOP(cursor) != NULL)
+ cursor_pop_page(cursor);
+ if (key == NULL || key->size == 0 || key->size > MAXKEYSIZE) {
+ errno = EINVAL;
+ rc = BT_FAIL;
+ } else
+ rc = btree_cursor_set(cursor, key, data);
+ break;
+ case BT_NEXT:
+ if (!cursor->initialized)
+ rc = btree_cursor_first(cursor, key, data);
+ else
+ rc = btree_cursor_next(cursor, key, data);
+ break;
+ case BT_FIRST:
+ while (CURSOR_TOP(cursor) != NULL)
+ cursor_pop_page(cursor);
+ rc = btree_cursor_first(cursor, key, data);
+ break;
+ default:
+ DPRINTF("unhandled/unimplemented cursor operation %u", op);
+ rc = BT_FAIL;
+ break;
+ }
+
+ mpage_prune(cursor->bt);
+
+ return rc;
+}
+
+static struct mpage *
+btree_new_page(struct btree *bt, uint32_t flags)
+{
+ struct mpage *mp;
+
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+
+ DPRINTF("allocating new mpage %u", bt->txn->next_pgno);
+ if ((mp = calloc(1, sizeof(*mp))) == NULL)
+ return NULL;
+ mp->pgno = mp->page.pgno = bt->txn->next_pgno++;
+ mp->page.flags = flags;
+ mp->page.lower = PAGEHDRSZ;
+ mp->page.upper = PAGESIZE;
+
+ if (IS_BRANCH(mp))
+ bt->meta->branch_pages++;
+ else if (IS_LEAF(mp))
+ bt->meta->leaf_pages++;
+ else if (IS_OVERFLOW(mp))
+ bt->meta->overflow_pages++;
+
+ mpage_add(bt, mp);
+ mpage_dirty(bt, mp);
+
+ return mp;
+}
+
+static size_t
+bt_leaf_size(struct btval *key, struct btval *data)
+{
+ size_t sz;
+
+ sz = LEAFSIZE(key, data);
+ if (data->size >= PAGESIZE / BT_MINKEYS) {
+ /* put on overflow page */
+ sz -= data->size - sizeof(pgno_t);
+ }
+
+ return sz + sizeof(indx_t);
+}
+
+static size_t
+bt_branch_size(struct btval *key)
+{
+ size_t sz;
+
+ sz = INDXSIZE(key);
+ if (sz >= PAGESIZE / BT_MINKEYS) {
+ /* put on overflow page */
+ /* not implemented */
+ /* sz -= key->size - sizeof(pgno_t); */
+ }
+
+ return sz + sizeof(indx_t);
+}
+
+static int
+btree_write_overflow_data(struct btree *bt, struct page *p, struct btval *data)
+{
+ size_t done = 0;
+ size_t sz;
+ size_t max;
+ pgno_t *linkp; /* linked page stored here */
+ struct mpage *next = NULL;
+
+ max = PAGESIZE - PAGEHDRSZ;
+
+ while (done < data->size) {
+ linkp = &p->p_next_pgno;
+ if (data->size - done > max) {
+ /* need another overflow page */
+ if ((next = btree_new_page(bt, P_OVERFLOW)) == NULL)
+ return BT_FAIL;
+ *linkp = next->pgno;
+ DPRINTF("linking overflow page %u", next->pgno);
+ } else
+ *linkp = 0; /* indicates end of list */
+ sz = data->size - done;
+ if (sz > max)
+ sz = max;
+ DPRINTF("copying %zu bytes to overflow page %u", sz, p->pgno);
+ bcopy((char *)data->data + done, p->ptrs, sz);
+ done += sz;
+ p = &next->page;
+ }
+
+ return BT_SUCCESS;
+}
+
+/* Key prefix should already be stripped.
+ */
+static int
+btree_add_node(struct btree *bt, struct mpage *mp, indx_t indx,
+ struct btval *key, struct btval *data, pgno_t pgno, uint8_t flags)
+{
+ unsigned int i;
+ size_t node_size = NODESIZE;
+ indx_t ofs;
+ struct node *node;
+ struct page *p;
+ struct mpage *ofp = NULL; /* overflow page */
+
+ p = &mp->page;
+ assert(p->upper >= p->lower);
+
+ DPRINTF("add node [%.*s] to %s page %u at index %i",
+ key ? (int)key->size : 0, key ? (char *)key->data : NULL,
+ IS_LEAF(mp) ? "leaf" : "branch",
+ mp->pgno, indx);
+
+ if (key != NULL)
+ node_size += key->size;
+
+ if (IS_LEAF(mp)) {
+ assert(data);
+ node_size += data->size;
+ if (F_ISSET(flags, F_BIGDATA)) {
+ /* Data already on overflow page. */
+ node_size -= data->size - sizeof(pgno_t);
+ } else if (data->size >= PAGESIZE / BT_MINKEYS) {
+ /* Put data on overflow page. */
+ DPRINTF("data size is %zu, put on overflow page",
+ data->size);
+ node_size -= data->size - sizeof(pgno_t);
+ if ((ofp = btree_new_page(bt, P_OVERFLOW)) == NULL)
+ return BT_FAIL;
+ DPRINTF("allocated overflow page %u", ofp->pgno);
+ flags |= F_BIGDATA;
+ }
+ }
+
+ if (node_size + sizeof(indx_t) > SIZELEFT(mp)) {
+ DPRINTF("not enough room in page %u, got %lu ptrs",
+ mp->pgno, NUMKEYS(mp));
+ DPRINTF("upper - lower = %u - %u = %u", p->upper, p->lower,
+ p->upper - p->lower);
+ DPRINTF("node size = %lu", node_size);
+ return BT_FAIL;
+ }
+
+ /* Move higher pointers up one slot. */
+ for (i = NUMKEYS(mp); i > indx; i--)
+ p->ptrs[i] = p->ptrs[i - 1];
+
+ /* Adjust free space offsets. */
+ ofs = p->upper - node_size;
+ assert(ofs >= p->lower + sizeof(indx_t));
+ p->ptrs[indx] = ofs;
+ p->upper = ofs;
+ p->lower += sizeof(indx_t);
+
+ /* Write the node data. */
+ node = NODEPTR(mp, indx);
+ node->ksize = (key == NULL) ? 0 : key->size;
+ node->flags = flags;
+ if (IS_LEAF(mp))
+ node->n_dsize = data->size;
+ else
+ node->n_pgno = pgno;
+
+ if (key)
+ bcopy(key->data, NODEKEY(node), key->size);
+
+ if (IS_LEAF(mp)) {
+ if (ofp == NULL) {
+ if (F_ISSET(flags, F_BIGDATA))
+ bcopy(data->data, node->data + key->size,
+ sizeof(pgno_t));
+ else
+ bcopy(data->data, node->data + key->size,
+ data->size);
+ } else {
+ bcopy(&ofp->pgno, node->data + key->size,
+ sizeof(pgno_t));
+ if (btree_write_overflow_data(bt, &ofp->page,
+ data) == BT_FAIL)
+ return BT_FAIL;
+ }
+ }
+
+ return BT_SUCCESS;
+}
+
+static void
+btree_del_node(struct btree *bt, struct mpage *mp, indx_t indx)
+{
+ unsigned int sz;
+ indx_t i, j, numkeys, ptr;
+ struct node *node;
+ char *base;
+
+ DPRINTF("delete node %u on %s page %u", indx,
+ IS_LEAF(mp) ? "leaf" : "branch", mp->pgno);
+ assert(indx < NUMKEYS(mp));
+
+ node = NODEPTR(mp, indx);
+ sz = NODESIZE + node->ksize;
+ if (IS_LEAF(mp)) {
+ if (F_ISSET(node->flags, F_BIGDATA))
+ sz += sizeof(pgno_t);
+ else
+ sz += NODEDSZ(node);
+ }
+
+ ptr = mp->page.ptrs[indx];
+ numkeys = NUMKEYS(mp);
+ for (i = j = 0; i < numkeys; i++) {
+ if (i != indx) {
+ mp->page.ptrs[j] = mp->page.ptrs[i];
+ if (mp->page.ptrs[i] < ptr)
+ mp->page.ptrs[j] += sz;
+ j++;
+ }
+ }
+
+ base = (char *)&mp->page + mp->page.upper;
+ bcopy(base, base + sz, ptr - mp->page.upper);
+
+ mp->page.lower -= sizeof(indx_t);
+ mp->page.upper += sz;
+}
+
+struct cursor *
+btree_txn_cursor_open(struct btree *bt, struct btree_txn *txn)
+{
+ struct cursor *cursor;
+
+ if ((cursor = calloc(1, sizeof(*cursor))) != NULL) {
+ SLIST_INIT(&cursor->stack);
+ cursor->bt = bt;
+ cursor->txn = txn;
+ bt->ref++;
+ }
+
+ return cursor;
+}
+
+void
+btree_cursor_close(struct cursor *cursor)
+{
+ if (cursor != NULL) {
+ while (!CURSOR_EMPTY(cursor))
+ cursor_pop_page(cursor);
+
+ btree_close(cursor->bt);
+ free(cursor);
+ }
+}
+
+static int
+btree_update_key(struct btree *bt, struct mpage *mp, indx_t indx,
+ struct btval *key)
+{
+ indx_t ptr, i, numkeys;
+ int delta;
+ size_t len;
+ struct node *node;
+ char *base;
+
+ node = NODEPTR(mp, indx);
+ ptr = mp->page.ptrs[indx];
+ DPRINTF("update key %u (ofs %u) [%.*s] to [%.*s] on page %u",
+ indx, ptr,
+ (int)node->ksize, (char *)NODEKEY(node),
+ (int)key->size, (char *)key->data,
+ mp->pgno);
+
+ if (key->size != node->ksize) {
+ delta = key->size - node->ksize;
+ if (delta > 0 && SIZELEFT(mp) < delta) {
+ DPRINTF("OUCH! Not enough room, delta = %d", delta);
+ return BT_FAIL;
+ }
+
+ numkeys = NUMKEYS(mp);
+ for (i = 0; i < numkeys; i++) {
+ if (mp->page.ptrs[i] <= ptr)
+ mp->page.ptrs[i] -= delta;
+ }
+
+ base = (char *)&mp->page + mp->page.upper;
+ len = ptr - mp->page.upper + NODESIZE;
+ bcopy(base, base - delta, len);
+ mp->page.upper -= delta;
+
+ node = NODEPTR(mp, indx);
+ node->ksize = key->size;
+ }
+
+ bcopy(key->data, NODEKEY(node), key->size);
+
+ return BT_SUCCESS;
+}
+
+static int
+btree_adjust_prefix(struct btree *bt, struct mpage *src, int delta)
+{
+ indx_t i;
+ struct node *node;
+ struct btkey tmpkey;
+ struct btval key;
+
+ DPRINTF("adjusting prefix lengths on page %u with delta %d",
+ src->pgno, delta);
+ assert(delta != 0);
+
+ for (i = 0; i < NUMKEYS(src); i++) {
+ node = NODEPTR(src, i);
+ tmpkey.len = node->ksize - delta;
+ if (delta > 0) {
+ if (F_ISSET(bt->flags, BT_REVERSEKEY))
+ bcopy(NODEKEY(node), tmpkey.str, tmpkey.len);
+ else
+ bcopy((char *)NODEKEY(node) + delta, tmpkey.str,
+ tmpkey.len);
+ } else {
+ if (F_ISSET(bt->flags, BT_REVERSEKEY)) {
+ bcopy(NODEKEY(node), tmpkey.str, node->ksize);
+ bcopy(src->prefix.str, tmpkey.str + node->ksize,
+ -delta);
+ } else {
+ bcopy(src->prefix.str + src->prefix.len + delta,
+ tmpkey.str, -delta);
+ bcopy(NODEKEY(node), tmpkey.str - delta,
+ node->ksize);
+ }
+ }
+ key.size = tmpkey.len;
+ key.data = tmpkey.str;
+ if (btree_update_key(bt, src, i, &key) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ return BT_SUCCESS;
+}
+
+/* Move a node from src to dst.
+ */
+static int
+btree_move_node(struct btree *bt, struct mpage *src, indx_t srcindx,
+ struct mpage *dst, indx_t dstindx)
+{
+ int rc;
+ unsigned int pfxlen, mp_pfxlen = 0;
+ struct node *node, *srcnode;
+ struct mpage *mp;
+ struct btkey tmpkey, srckey;
+ struct btval key, data;
+
+ assert(src->parent);
+ assert(dst->parent);
+
+ srcnode = NODEPTR(src, srcindx);
+ DPRINTF("moving %s node %u [%.*s] on page %u to node %u on page %u",
+ IS_LEAF(src) ? "leaf" : "branch",
+ srcindx,
+ (int)srcnode->ksize, (char *)NODEKEY(srcnode),
+ src->pgno,
+ dstindx, dst->pgno);
+
+ if (IS_BRANCH(src)) {
+ /* Need to check if the page the moved node points to
+ * changes prefix.
+ */
+ btree_get_mpage(bt, NODEPGNO(srcnode), &mp);
+ mp->parent = src;
+ mp->parent_index = srcindx;
+ find_common_prefix(bt, mp);
+ mp_pfxlen = mp->prefix.len;
+ }
+
+ /* Mark src and dst as dirty. */
+ mpage_touch(bt, src);
+ mpage_touch(bt, dst);
+
+ find_common_prefix(bt, dst);
+
+ /* Check if src node has destination page prefix. Otherwise the
+ * destination page must expand its prefix on all its nodes.
+ */
+ srckey.len = srcnode->ksize;
+ bcopy(NODEKEY(srcnode), srckey.str, srckey.len);
+ common_prefix(bt, &srckey, &dst->prefix, &tmpkey);
+ if (tmpkey.len != dst->prefix.len) {
+ if (btree_adjust_prefix(bt, dst,
+ tmpkey.len - dst->prefix.len) != BT_SUCCESS)
+ return BT_FAIL;
+ bcopy(&tmpkey, &dst->prefix, sizeof(tmpkey));
+ }
+
+ if (srcindx == 0 && IS_BRANCH(src)) {
+ struct mpage *low;
+
+ /* must find the lowest key below src
+ */
+ assert(btree_search_page_root(bt, src, NULL, NULL, 0,
+ &low) == BT_SUCCESS);
+ expand_prefix(bt, low, 0, &srckey);
+ DPRINTF("found lowest key [%.*s] on leaf page %u",
+ (int)srckey.len, srckey.str, low->pgno);
+ } else {
+ srckey.len = srcnode->ksize;
+ bcopy(NODEKEY(srcnode), srckey.str, srcnode->ksize);
+ }
+ find_common_prefix(bt, src);
+
+ /* expand the prefix */
+ tmpkey.len = sizeof(tmpkey.str);
+ concat_prefix(bt, src->prefix.str, src->prefix.len,
+ srckey.str, srckey.len, tmpkey.str, &tmpkey.len);
+
+ /* Add the node to the destination page. Adjust prefix for
+ * destination page.
+ */
+ key.size = tmpkey.len;
+ key.data = tmpkey.str;
+ remove_prefix(bt, &key, dst->prefix.len);
+ data.size = NODEDSZ(srcnode);
+ data.data = NODEDATA(srcnode);
+ rc = btree_add_node(bt, dst, dstindx, &key, &data, NODEPGNO(srcnode),
+ srcnode->flags);
+ if (rc != BT_SUCCESS)
+ return rc;
+
+ /* Delete the node from the source page.
+ */
+ btree_del_node(bt, src, srcindx);
+
+ /* Update the parent separators.
+ */
+ if (srcindx == 0 && src->parent_index != 0) {
+ node = NODEPTR(src->parent, src->parent_index);
+ DPRINTF("current parent separator for source page %u is [%.*s]",
+ src->pgno,
+ (int)node->ksize, (char *)NODEKEY(node));
+
+ expand_prefix(bt, src, 0, &tmpkey);
+ key.size = tmpkey.len;
+ key.data = tmpkey.str;
+ remove_prefix(bt, &key, src->parent->prefix.len);
+
+ DPRINTF("update separator for source page %u to [%.*s]",
+ src->pgno, (int)key.size, (char *)key.data);
+// ? bt_reduce_separator(bt, node, &key);
+ if (btree_update_key(bt, src->parent, src->parent_index,
+ &key) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ if (srcindx == 0 && IS_BRANCH(src)) {
+ struct btval nullkey;
+ nullkey.size = 0;
+ assert(btree_update_key(bt, src, 0, &nullkey) == BT_SUCCESS);
+ }
+
+ if (dstindx == 0 && dst->parent_index != 0) {
+ node = NODEPTR(dst->parent, dst->parent_index);
+ DPRINTF("current parent separator for destination page %u is [%.*s]",
+ dst->pgno,
+ (int)node->ksize, (char *)NODEKEY(node));
+
+ expand_prefix(bt, dst, 0, &tmpkey);
+ key.size = tmpkey.len;
+ key.data = tmpkey.str;
+ remove_prefix(bt, &key, dst->parent->prefix.len);
+
+ DPRINTF("update separator for destination page %u to [%.*s]",
+ dst->pgno, (int)key.size, (char *)key.data);
+// ? bt_reduce_separator(bt, node, &key);
+ if (btree_update_key(bt, dst->parent, dst->parent_index,
+ &key) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ if (dstindx == 0 && IS_BRANCH(dst)) {
+ struct btval nullkey;
+ nullkey.size = 0;
+ assert(btree_update_key(bt, dst, 0, &nullkey) == BT_SUCCESS);
+ }
+
+ /* We can get a new page prefix here!
+ * Must update keys in all nodes of this page!
+ */
+ pfxlen = src->prefix.len;
+ find_common_prefix(bt, src);
+ if (src->prefix.len != pfxlen) {
+ if (btree_adjust_prefix(bt, src,
+ src->prefix.len - pfxlen) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ pfxlen = dst->prefix.len;
+ find_common_prefix(bt, dst);
+ if (dst->prefix.len != pfxlen) {
+ if (btree_adjust_prefix(bt, dst,
+ dst->prefix.len - pfxlen) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ if (IS_BRANCH(dst)) {
+ mp->parent = dst;
+ mp->parent_index = dstindx;
+ find_common_prefix(bt, mp);
+ if (mp->prefix.len != mp_pfxlen) {
+ DPRINTF("moved branch node has changed prefix");
+ mpage_touch(bt, mp);
+ if (btree_adjust_prefix(bt, mp,
+ mp->prefix.len - mp_pfxlen) != BT_SUCCESS)
+ return BT_FAIL;
+ }
+ }
+
+ return BT_SUCCESS;
+}
+
+static int
+btree_merge(struct btree *bt, struct mpage *src, struct mpage *dst)
+{
+ int rc;
+ indx_t i;
+ struct node *srcnode;
+ struct btkey tmpkey, dstpfx;
+ struct btval key, data;
+
+ DPRINTF("merging page %u and %u", src->pgno, dst->pgno);
+
+ assert(src->parent); /* can't merge root page */
+ assert(dst->parent);
+ assert(bt->txn != NULL);
+
+ /* Mark src and dst as dirty. */
+ mpage_touch(bt, src);
+ mpage_touch(bt, dst);
+
+ find_common_prefix(bt, src);
+ find_common_prefix(bt, dst);
+
+ /* Check if source nodes has destination page prefix. Otherwise
+ * the destination page must expand its prefix on all its nodes.
+ */
+ common_prefix(bt, &src->prefix, &dst->prefix, &dstpfx);
+ if (dstpfx.len != dst->prefix.len) {
+ if (btree_adjust_prefix(bt, dst,
+ dstpfx.len - dst->prefix.len) != BT_SUCCESS)
+ return BT_FAIL;
+ bcopy(&dstpfx, &dst->prefix, sizeof(dstpfx));
+ }
+
+ /* Move all nodes from src to dst.
+ */
+ for (i = 0; i < NUMKEYS(src); i++) {
+ srcnode = NODEPTR(src, i);
+
+ /* If branch node 0 (implicit key), find the real key.
+ */
+ if (i == 0 && IS_BRANCH(src)) {
+ struct mpage *low;
+
+ /* must find the lowest key below src
+ */
+ assert(btree_search_page_root(bt, src, NULL, NULL, 0,
+ &low) == BT_SUCCESS);
+ expand_prefix(bt, low, 0, &tmpkey);
+ DPRINTF("found lowest key [%.*s] on leaf page %u",
+ (int)tmpkey.len, tmpkey.str, low->pgno);
+ } else {
+ expand_prefix(bt, src, i, &tmpkey);
+ }
+
+ key.size = tmpkey.len;
+ key.data = tmpkey.str;
+
+ remove_prefix(bt, &key, dst->prefix.len);
+ data.size = NODEDSZ(srcnode);
+ data.data = NODEDATA(srcnode);
+ rc = btree_add_node(bt, dst, NUMKEYS(dst), &key,
+ &data, NODEPGNO(srcnode), srcnode->flags);
+ if (rc != BT_SUCCESS)
+ return rc;
+ }
+
+ DPRINTF("dst page %u now has %lu keys (%.1f%% filled)",
+ dst->pgno, NUMKEYS(dst), PAGEFILL(dst) * 100);
+
+ /* Unlink the src page from parent.
+ */
+ btree_del_node(bt, src->parent, src->parent_index);
+ if (src->parent_index == 0) {
+ key.size = 0;
+ if (btree_update_key(bt, src->parent, 0, &key) != BT_SUCCESS)
+ return BT_FAIL;
+
+ unsigned int pfxlen = src->prefix.len;
+ find_common_prefix(bt, src);
+ assert (src->prefix.len == pfxlen);
+ }
+
+ if (IS_LEAF(src))
+ bt->meta->leaf_pages--;
+ else
+ bt->meta->branch_pages--;
+
+ return btree_rebalance(bt, src->parent);
+}
+
+#define FILL_THRESHOLD 0.25
+
+static int
+btree_rebalance(struct btree *bt, struct mpage *mp)
+{
+ indx_t si = 0, di = 0;
+ struct mpage *parent;
+ struct mpage *neighbor = NULL;
+
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+ assert(mp != NULL);
+
+ DPRINTF("rebalancing %s page %u (has %lu keys, %.1f%% full)",
+ IS_LEAF(mp) ? "leaf" : "branch",
+ mp->pgno, NUMKEYS(mp), PAGEFILL(mp) * 100);
+
+ if (PAGEFILL(mp) >= FILL_THRESHOLD) {
+ DPRINTF("no need to rebalance page %u, above fill threshold",
+ mp->pgno);
+ return BT_SUCCESS;
+ }
+
+ parent = mp->parent;
+
+ if (parent == NULL) {
+ if (NUMKEYS(mp) == 0) {
+ DPRINTF("tree is completely empty");
+ bt->txn->root = P_INVALID;
+ bt->meta->depth--;
+ bt->meta->leaf_pages--;
+ } else if (IS_BRANCH(mp) && NUMKEYS(mp) == 1) {
+ DPRINTF("collapsing root page!");
+ bt->txn->root = NODEPGNO(NODEPTR(mp, 0));
+ bt->meta->depth--;
+ bt->meta->branch_pages--;
+ } else
+ DPRINTF("root page doesn't need rebalancing");
+ return BT_SUCCESS;
+ }
+
+ /* The parent (branch page) must have at least 2 pointers,
+ * otherwise the tree is invalid.
+ */
+ assert(NUMKEYS(parent) > 1);
+
+ /* Leaf page fill factor is below the threshold.
+ * Try to move keys from left or right neighbor, or
+ * merge with a neighbor page.
+ */
+
+ /* Find neighbors.
+ */
+ if (mp->parent_index == 0) {
+ /* We're the leftmost leaf in our parent.
+ */
+ DPRINTF("reading right neighbor");
+ if (btree_get_mpage(bt,
+ NODEPGNO(NODEPTR(parent, mp->parent_index + 1)),
+ &neighbor) != BT_SUCCESS) {
+ return BT_FAIL;
+ }
+ neighbor->parent_index = mp->parent_index + 1;
+ si = 0;
+ di = NUMKEYS(mp);
+ } else {
+ /* There is at least one neighbor to the left.
+ */
+ DPRINTF("reading left neighbor");
+ if (btree_get_mpage(bt,
+ NODEPGNO(NODEPTR(parent, mp->parent_index - 1)),
+ &neighbor) != BT_SUCCESS) {
+ return BT_FAIL;
+ }
+ neighbor->parent_index = mp->parent_index - 1;
+ si = NUMKEYS(neighbor) - 1;
+ di = 0;
+ }
+ neighbor->parent = parent;
+
+ DPRINTF("found neighbor page %u (%lu keys, %.1f%% full)",
+ neighbor->pgno, NUMKEYS(neighbor), PAGEFILL(neighbor) * 100);
+
+ /* If the neighbor page is above threshold and has at least two
+ * keys, move one key from it.
+ *
+ * Otherwise we should try to merge them, but that might not be
+ * possible, even if both are below threshold, as prefix expansion
+ * might make keys larger. FIXME: detect this
+ */
+ if (PAGEFILL(neighbor) >= FILL_THRESHOLD &&
+ NUMKEYS(neighbor) >= NUMKEYS(mp) + 2)
+ return btree_move_node(bt, neighbor, si, mp, di);
+ else { /* FIXME: if (has_enough_room()) */
+ if (mp->parent_index == 0)
+ return btree_merge(bt, neighbor, mp);
+ else
+ return btree_merge(bt, mp, neighbor);
+ }
+}
+
+int
+btree_txn_del(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data)
+{
+ int rc, exact, close_txn = 0;
+ unsigned int ki;
+ struct node *leaf;
+ struct mpage *mp;
+
+ DPRINTF("========> delete key %.*s", (int)key->size, (char *)key->data);
+
+ assert(bt != NULL);
+ assert(key != NULL);
+
+ if (key->size == 0 || key->size > MAXKEYSIZE) {
+ errno = EINVAL;
+ return BT_FAIL;
+ }
+
+ if (txn == NULL) {
+ close_txn = 1;
+ if ((txn = btree_txn_begin(bt, 0)) == NULL)
+ return BT_FAIL;
+ }
+
+ if ((rc = btree_search_page(bt, txn, key, NULL, 1, &mp)) != BT_SUCCESS)
+ goto done;
+
+ leaf = btree_search_node(bt, mp, key, &exact, &ki);
+ if (leaf == NULL || !exact) {
+ rc = BT_NOTFOUND;
+ goto done;
+ }
+
+ if (data && (rc = btree_read_data(bt, NULL, leaf, data)) != BT_SUCCESS)
+ goto done;
+
+ btree_del_node(bt, mp, ki);
+ bt->meta->entries--;
+ rc = btree_rebalance(bt, mp);
+ if (rc != BT_SUCCESS)
+ txn->flags |= BT_TXN_ERROR;
+
+done:
+ if (close_txn) {
+ if (rc == BT_SUCCESS)
+ rc = btree_txn_commit(txn);
+ else
+ btree_txn_abort(txn);
+ }
+ mpage_prune(bt);
+ return rc;
+}
+
+/* Reduce the length of the prefix separator <sep> to the minimum length that
+ * still makes it uniquely distinguishable from <min>.
+ *
+ * <min> is guaranteed to be sorted less than <sep>
+ *
+ * On return, <sep> is modified to the minimum length.
+ */
+static void
+bt_reduce_separator(struct btree *bt, struct node *min, struct btval *sep)
+{
+ size_t n = 0;
+ char *p1;
+ char *p2;
+
+ if (F_ISSET(bt->flags, BT_REVERSEKEY)) {
+
+ assert(sep->size > 0);
+
+ p1 = (char *)NODEKEY(min) + min->ksize - 1;
+ p2 = (char *)sep->data + sep->size - 1;
+
+ while (p1 >= (char *)NODEKEY(min) && *p1 == *p2) {
+ assert(p2 > (char *)sep->data);
+ p1--;
+ p2--;
+ n++;
+ }
+
+ sep->data = p2;
+ sep->size = n + 1;
+ } else {
+
+ assert(min->ksize > 0);
+ assert(sep->size > 0);
+
+ p1 = (char *)NODEKEY(min);
+ p2 = (char *)sep->data;
+
+ while (*p1 == *p2) {
+ p1++;
+ p2++;
+ n++;
+ if (n == min->ksize || n == sep->size)
+ break;
+ }
+
+ sep->size = n + 1;
+ }
+
+ DPRINTF("reduced separator to [%.*s] > [%.*s]",
+ (int)sep->size, (char *)sep->data,
+ (int)min->ksize, (char *)NODEKEY(min));
+}
+
+/* Split page <*mpp>, and insert <key,(data|newpgno)> in either left or
+ * right sibling, at index <*newindxp> (as if unsplit). Updates *mpp and
+ * *newindxp with the actual values after split, ie if *mpp and *newindxp
+ * refer to a node in the new right sibling page.
+ */
+static int
+btree_split(struct btree *bt, struct mpage **mpp, unsigned int *newindxp,
+ struct btval *newkey, struct btval *newdata, pgno_t newpgno)
+{
+ uint8_t flags;
+ int rc = BT_SUCCESS, ins_new = 0;
+ indx_t newindx;
+ pgno_t pgno = 0;
+ size_t orig_pfx_len, left_pfx_diff, right_pfx_diff, pfx_diff;
+ unsigned int i, j, split_indx;
+ struct node *node;
+ struct mpage *pright, *p, *mp;
+ struct btval sepkey, rkey, rdata;
+ struct btkey tmpkey;
+ struct page copy;
+
+ assert(bt != NULL);
+ assert(bt->txn != NULL);
+
+ mp = *mpp;
+ newindx = *newindxp;
+
+ DPRINTF("-----> splitting %s page %u and adding [%.*s] at index %i",
+ IS_LEAF(mp) ? "leaf" : "branch", mp->pgno,
+ (int)newkey->size, (char *)newkey->data, *newindxp);
+ DPRINTF("page %u has prefix [%.*s]", mp->pgno,
+ (int)mp->prefix.len, (char *)mp->prefix.str);
+ orig_pfx_len = mp->prefix.len;
+
+ if (mp->parent == NULL) {
+ if ((mp->parent = btree_new_page(bt, P_BRANCH)) == NULL)
+ return BT_FAIL;
+ mp->parent_index = 0;
+ bt->txn->root = mp->parent->pgno;
+ DPRINTF("root split! new root = %u", mp->parent->pgno);
+ bt->meta->depth++;
+
+ /* Add left (implicit) pointer. */
+ if (btree_add_node(bt, mp->parent, 0, NULL, NULL,
+ mp->pgno, 0) != BT_SUCCESS)
+ return BT_FAIL;
+ } else {
+ DPRINTF("parent branch page is %u", mp->parent->pgno);
+ }
+
+ /* Create a right sibling. */
+ if ((pright = btree_new_page(bt, mp->page.flags)) == NULL)
+ return BT_FAIL;
+ pright->parent = mp->parent;
+ pright->parent_index = mp->parent_index + 1;
+ DPRINTF("new right sibling: page %u", pright->pgno);
+
+ /* Move half of the keys to the right sibling. */
+ bcopy(&mp->page, &copy, PAGESIZE);
+ assert(mp->ref == 0); /* XXX */
+ bzero(&mp->page.ptrs, PAGESIZE - PAGEHDRSZ);
+ mp->page.lower = PAGEHDRSZ;
+ mp->page.upper = PAGESIZE;
+
+ split_indx = NUMKEYSP(&copy) / 2 + 1;
+
+ /* First find the separating key between the split pages.
+ */
+ bzero(&sepkey, sizeof(sepkey));
+ if (newindx == split_indx) {
+ sepkey.size = newkey->size;
+ sepkey.data = newkey->data;
+ remove_prefix(bt, &sepkey, mp->prefix.len);
+ } else {
+ node = NODEPTRP(&copy, split_indx);
+ sepkey.size = node->ksize;
+ sepkey.data = NODEKEY(node);
+ }
+
+ if (IS_LEAF(mp) && bt->cmp == NULL) {
+ /* Find the smallest separator. */
+ /* Ref: Prefix B-trees, R. Bayer, K. Unterauer, 1977 */
+ node = NODEPTRP(&copy, split_indx - 1);
+ bt_reduce_separator(bt, node, &sepkey);
+ }
+
+ /* Fix separator wrt parent prefix. */
+ if (bt->cmp == NULL) {
+ tmpkey.len = sizeof(tmpkey.str);
+ concat_prefix(bt, mp->prefix.str, mp->prefix.len,
+ sepkey.data, sepkey.size, tmpkey.str, &tmpkey.len);
+ sepkey.data = tmpkey.str;
+ sepkey.size = tmpkey.len;
+ }
+
+ DPRINTF("separator is [%.*s]", (int)sepkey.size, (char *)sepkey.data);
+
+ /* Copy separator key to the parent.
+ */
+ if (SIZELEFT(pright->parent) < bt_branch_size(&sepkey)) {
+ rc = btree_split(bt, &pright->parent, &pright->parent_index,
+ &sepkey, NULL, pright->pgno);
+
+ /* Right page might now have changed parent.
+ * Check if left page also changed parent.
+ */
+ if (pright->parent != mp->parent &&
+ mp->parent_index >= NUMKEYS(mp->parent)) {
+ mp->parent = pright->parent;
+ mp->parent_index = pright->parent_index - 1;
+ }
+ } else {
+ remove_prefix(bt, &sepkey, pright->parent->prefix.len);
+ rc = btree_add_node(bt, pright->parent, pright->parent_index,
+ &sepkey, NULL, pright->pgno, 0);
+ }
+ if (rc != BT_SUCCESS)
+ return BT_FAIL;
+
+ /* Update prefix for right and left page, if the parent was split.
+ */
+ find_common_prefix(bt, pright);
+ assert(orig_pfx_len <= pright->prefix.len);
+ right_pfx_diff = pright->prefix.len - orig_pfx_len;
+
+ find_common_prefix(bt, mp);
+ assert(orig_pfx_len <= mp->prefix.len);
+ left_pfx_diff = mp->prefix.len - orig_pfx_len;
+
+ for (i = j = 0; i <= NUMKEYSP(&copy); j++) {
+ if (i < split_indx) {
+ /* Re-insert in left sibling. */
+ p = mp;
+ pfx_diff = left_pfx_diff;
+ } else {
+ /* Insert in right sibling. */
+ if (i == split_indx)
+ /* Reset insert index for right sibling. */
+ j = (i == newindx && ins_new);
+ p = pright;
+ pfx_diff = right_pfx_diff;
+ }
+
+ if (i == newindx && !ins_new) {
+ /* Insert the original entry that caused the split. */
+ rkey.data = newkey->data;
+ rkey.size = newkey->size;
+ if (IS_LEAF(mp)) {
+ rdata.data = newdata->data;
+ rdata.size = newdata->size;
+ } else
+ pgno = newpgno;
+ flags = 0;
+ pfx_diff = p->prefix.len;
+
+ ins_new = 1;
+
+ /* Update page and index for the new key. */
+ *newindxp = j;
+ *mpp = p;
+ } else if (i == NUMKEYSP(&copy)) {
+ break;
+ } else {
+ node = NODEPTRP(&copy, i);
+ rkey.data = NODEKEY(node);
+ rkey.size = node->ksize;
+ if (IS_LEAF(mp)) {
+ rdata.data = NODEDATA(node);
+ rdata.size = node->n_dsize;
+ } else
+ pgno = node->n_pgno;
+ flags = node->flags;
+
+ i++;
+ }
+
+ if (!IS_LEAF(mp) && j == 0) {
+ /* First branch index doesn't need key data. */
+ rkey.size = 0;
+ } else
+ remove_prefix(bt, &rkey, pfx_diff);
+
+ rc = btree_add_node(bt, p, j, &rkey, &rdata, pgno,flags);
+ if (rc != BT_SUCCESS)
+ return BT_FAIL;
+ }
+
+ return rc;
+}
+
+int
+btree_txn_put(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data, unsigned int flags)
+{
+ int rc = BT_SUCCESS, exact, close_txn = 0;
+ unsigned int ki;
+ struct node *leaf;
+ struct mpage *mp;
+ struct btval xkey;
+
+ assert(bt != NULL);
+ assert(key != NULL);
+ assert(data != NULL);
+
+ if (key->size == 0 || key->size > MAXKEYSIZE) {
+ errno = EINVAL;
+ return BT_FAIL;
+ }
+
+ DPRINTF("==> put key %.*s, size %zu, data size %zu",
+ (int)key->size, (char *)key->data, key->size, data->size);
+
+ if (txn == NULL) {
+ close_txn = 1;
+ if ((txn = btree_txn_begin(bt, 0)) == NULL)
+ return BT_FAIL;
+ }
+
+ rc = btree_search_page(bt, txn, key, NULL, 1, &mp);
+ if (rc == BT_SUCCESS) {
+ leaf = btree_search_node(bt, mp, key, &exact, &ki);
+ if (leaf && exact) {
+ if (F_ISSET(flags, BT_NOOVERWRITE)) {
+ DPRINTF("duplicate key %.*s",
+ (int)key->size, (char *)key->data);
+ rc = BT_EXISTS;
+ goto done;
+ }
+ btree_del_node(bt, mp, ki);
+ }
+ if (leaf == NULL) { /* append if not found */
+ ki = NUMKEYS(mp);
+ DPRINTF("appending key at index %i", ki);
+ }
+ } else if (rc == BT_NOTFOUND) {
+ /* new file, just write a root leaf page */
+ DPRINTF("allocating new root leaf page");
+ if ((mp = btree_new_page(bt, P_LEAF)) == NULL) {
+ rc = BT_FAIL;
+ goto done;
+ }
+ txn->root = mp->pgno;
+ bt->meta->depth++;
+ ki = 0;
+ }
+ else
+ goto done;
+
+ assert(IS_LEAF(mp));
+ DPRINTF("there are %lu keys, should insert new key at index %i",
+ NUMKEYS(mp), ki);
+
+ /* Copy the key pointer as it is modified by the prefix code. The
+ * caller might have malloc'ed the data.
+ */
+ xkey.data = key->data;
+ xkey.size = key->size;
+
+ if (SIZELEFT(mp) < bt_leaf_size(key, data)) {
+ rc = btree_split(bt, &mp, &ki, &xkey, data, P_INVALID);
+ } else {
+ /* There is room already in this leaf page. */
+ remove_prefix(bt, &xkey, mp->prefix.len);
+ rc = btree_add_node(bt, mp, ki, &xkey, data, 0, 0);
+ }
+
+ if (rc != BT_SUCCESS)
+ txn->flags |= BT_TXN_ERROR;
+ else
+ bt->meta->entries++;
+
+done:
+ if (close_txn) {
+ if (rc == BT_SUCCESS)
+ rc = btree_txn_commit(txn);
+ else
+ btree_txn_abort(txn);
+ }
+ mpage_prune(bt);
+ return rc;
+}
+
+static pgno_t
+btree_compact_tree(struct btree *bt, pgno_t pgno, int fd)
+{
+ indx_t i;
+ pgno_t *pnext;
+ struct node *node;
+ struct page *p;
+ struct mpage *mp;
+ char page[PAGESIZE];
+
+ p = (struct page *)page;
+ if ((mp = mpage_lookup(bt, pgno)) != NULL)
+ bcopy(&mp->page, p, PAGESIZE);
+ else if (btree_read_page(bt, pgno, p) != BT_SUCCESS)
+ return P_INVALID;
+
+ if (F_ISSET(p->flags, P_BRANCH)) {
+ for (i = 0; i < NUMKEYSP(p); i++) {
+ node = NODEPTRP(p, i);
+ node->n_pgno = btree_compact_tree(bt, node->n_pgno, fd);
+ if (node->n_pgno == P_INVALID)
+ return P_INVALID;
+ }
+ } else if (F_ISSET(p->flags, P_LEAF)) {
+ for (i = 0; i < NUMKEYSP(p); i++) {
+ node = NODEPTRP(p, i);
+ if (F_ISSET(node->flags, F_BIGDATA)) {
+ pnext = NODEDATA(node);
+ *pnext = btree_compact_tree(bt, *pnext, fd);
+ if (*pnext == P_INVALID)
+ return P_INVALID;
+ }
+ }
+ } else if (F_ISSET(p->flags, P_OVERFLOW)) {
+ pnext = &p->p_next_pgno;
+ if (*pnext > 0) {
+ *pnext = btree_compact_tree(bt, *pnext, fd);
+ if (*pnext == P_INVALID)
+ return P_INVALID;
+ }
+ } else
+ assert(0);
+
+ p->pgno = bt->txn->next_pgno++;
+ if (write(fd, page, PAGESIZE) != PAGESIZE)
+ return P_INVALID;
+ return p->pgno;
+}
+
+int
+btree_compact(struct btree *bt)
+{
+ int rc, fd, old_fd;
+ char *compact_path = NULL;
+ struct btree_txn *txn;
+ pgno_t root;
+
+ assert(bt != NULL);
+
+ if (bt->path == NULL)
+ return BT_FAIL;
+
+ if ((rc = btree_read_meta(bt, NULL)) == BT_FAIL)
+ return BT_FAIL;
+ else if (rc == BT_NOTFOUND)
+ return BT_SUCCESS;
+
+ asprintf(&compact_path, "%s.compact.XXXXXX", bt->path);
+ fd = mkstemp(compact_path);
+ if (fd == -1) {
+ free(compact_path);
+ return BT_FAIL;
+ }
+
+ old_fd = bt->fd;
+
+ if ((txn = btree_txn_begin(bt, 0)) == NULL)
+ goto failed;
+
+ bt->txn->next_pgno = 0;
+ root = btree_compact_tree(bt, bt->meta->root, fd);
+ if (root == P_INVALID)
+ goto failed;
+ bt->fd = fd;
+ bt->meta->revisions = 0;
+ if (btree_write_meta(bt, root) != BT_SUCCESS)
+ goto failed;
+
+ fsync(fd);
+
+ DPRINTF("renaming %s to %s", compact_path, bt->path);
+ if (rename(compact_path, bt->path) != 0)
+ goto failed;
+
+ // XXX: write a "reopen me" meta page for other processes to see
+ btree_txn_abort(txn);
+ close(old_fd);
+
+ free(compact_path);
+ return BT_SUCCESS;
+
+failed:
+ bt->fd = old_fd;
+ btree_txn_abort(txn);
+ unlink(compact_path);
+ free(compact_path);
+ return BT_FAIL;
+}
+
+/* Reverts the last change. Truncates the file at the last root page.
+ */
+int
+btree_revert(struct btree *bt)
+{
+ if (btree_read_meta(bt, NULL) == BT_FAIL)
+ return BT_FAIL;
+
+ if (bt->meta == NULL)
+ return BT_SUCCESS;
+
+ DPRINTF("truncating file at page %u", bt->meta->root);
+ return ftruncate(bt->fd, PAGESIZE * bt->meta->root);
+}
+
+void
+btree_set_cache_size(struct btree *bt, unsigned int cache_size)
+{
+ bt->stat.max_cache = cache_size;
+}
+
+unsigned int
+btree_get_flags(struct btree *bt)
+{
+ return (bt->flags & ~BT_FIXPADDING);
+}
+
+const char *
+btree_get_path(struct btree *bt)
+{
+ return bt->path;
+}
+
+const struct btree_stat *
+btree_stat(struct btree *bt)
+{
+ bt->stat.branch_pages = bt->meta->branch_pages;
+ bt->stat.leaf_pages = bt->meta->leaf_pages;
+ bt->stat.overflow_pages = bt->meta->overflow_pages;
+ bt->stat.revisions = bt->meta->revisions;
+ bt->stat.depth = bt->meta->depth;
+ bt->stat.entries = bt->meta->entries;
+ bt->stat.psize = bt->meta->psize;
+ bt->stat.created_at = bt->meta->created_at;
+
+ return &bt->stat;
+}
+
diff --git a/usr.sbin/ldapd/btree.h b/usr.sbin/ldapd/btree.h
new file mode 100644
index 00000000000..926cea6b0f4
--- /dev/null
+++ b/usr.sbin/ldapd/btree.h
@@ -0,0 +1,128 @@
+/* $OpenBSD: btree.h,v 1.1 2010/05/31 17:36:31 martinh 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.
+ */
+
+#ifndef _btree_h_
+#define _btree_h_
+
+#include <openssl/sha.h>
+#include <stdint.h>
+
+struct mpage;
+struct cursor;
+struct btree_txn;
+
+struct btval {
+ void *data;
+ size_t size;
+ int free_data; /* true if data malloc'd */
+ struct mpage *mp; /* ref'd memory page */
+};
+
+typedef uint32_t pgno_t;
+typedef uint16_t indx_t;
+typedef int (*bt_cmp_func)(const struct btval *a,
+ const struct btval *b);
+typedef void (*bt_prefix_func)(const struct btval *a,
+ const struct btval *b,
+ struct btval *sep);
+
+#define BT_NOOVERWRITE 1
+
+enum cursor_op { /* cursor operations */
+ BT_CURSOR, /* position at given key */
+ BT_FIRST,
+ BT_NEXT,
+ BT_LAST, /* not implemented */
+ BT_PREV /* not implemented */
+};
+
+/* return codes */
+#define BT_FAIL -1
+#define BT_SUCCESS 0
+#define BT_NOTFOUND 1
+#define BT_EXISTS 2
+
+/* btree flags */
+#define BT_NOSYNC 0x01 /* don't fsync after commit */
+#define BT_RDONLY 0x08 /* read only */
+#define BT_REVERSEKEY 0x10 /* use reverse string keys */
+
+struct btree_stat {
+ unsigned long long int hits; /* cache hits */
+ unsigned long long int reads; /* page reads */
+ unsigned int max_cache; /* max cached pages */
+ unsigned int cache_size; /* current cache size */
+ unsigned int branch_pages;
+ unsigned int leaf_pages;
+ unsigned int overflow_pages;
+ unsigned int revisions;
+ unsigned int depth;
+ unsigned long long int entries;
+ unsigned int psize;
+ time_t created_at;
+};
+
+struct btree *btree_open_fd(int fd, uint32_t flags);
+struct btree *btree_open(const char *path, uint32_t flags,
+ mode_t mode);
+void btree_close(struct btree *bt);
+const struct btree_stat *btree_stat(struct btree *bt);
+
+struct btree_txn *btree_txn_begin(struct btree *bt, int rdonly);
+int btree_txn_commit(struct btree_txn *txn);
+void btree_txn_abort(struct btree_txn *txn);
+
+int btree_txn_get(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data);
+int btree_txn_put(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data,
+ unsigned int flags);
+int btree_txn_del(struct btree *bt, struct btree_txn *txn,
+ struct btval *key, struct btval *data);
+
+#define btree_get(bt, key, data) \
+ btree_txn_get(bt, NULL, key, data)
+#define btree_put(bt, key, data, flags) \
+ btree_txn_put(bt, NULL, key, data, flags)
+#define btree_del(bt, key, data) \
+ btree_txn_del(bt, NULL, key, data)
+
+void btree_set_cache_size(struct btree *bt,
+ unsigned int cache_size);
+unsigned int btree_get_flags(struct btree *bt);
+const char *btree_get_path(struct btree *bt);
+
+#define btree_cursor_open(bt) \
+ btree_txn_cursor_open(bt, NULL)
+struct cursor *btree_txn_cursor_open(struct btree *bt,
+ struct btree_txn *txn);
+void btree_cursor_close(struct cursor *cursor);
+int btree_cursor_get(struct cursor *cursor,
+ struct btval *key, struct btval *data,
+ enum cursor_op op);
+
+int btree_sync(struct btree *bt);
+int btree_compact(struct btree *bt);
+int btree_revert(struct btree *bt);
+
+int btree_cmp(struct btree *bt, const struct btval *a,
+ const struct btval *b);
+void btval_reset(struct btval *btv);
+
+#endif
+
diff --git a/usr.sbin/ldapd/compact.c b/usr.sbin/ldapd/compact.c
new file mode 100644
index 00000000000..700da0ba8be
--- /dev/null
+++ b/usr.sbin/ldapd/compact.c
@@ -0,0 +1,185 @@
+/* $OpenBSD: compact.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/queue.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+static int continue_compaction(struct ctl_conn *c);
+static void stop_compaction(struct ctl_conn *c);
+
+void
+check_compaction(pid_t pid, int status)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbypid(pid)) == NULL)
+ return;
+
+ if (WIFEXITED(status)) {
+ log_debug("compaction process %d exited with status %d",
+ pid, WEXITSTATUS(status));
+ if (WEXITSTATUS(status) == 0) {
+ control_report_compaction(c, 0);
+ continue_compaction(c);
+ return;
+ }
+ } else if (WIFSIGNALED(status))
+ log_warn("compaction process %d exited due to signal %d",
+ pid, WTERMSIG(status));
+ else
+ log_debug("compaction process %d exited", pid);
+
+ /* Compaction failed, no need to continue (disk might be full).
+ */
+ control_report_compaction(c, 1);
+ if (c->ns != NULL)
+ c->ns->compacting = 0;
+ c->ns = NULL;
+}
+
+static pid_t
+compact(struct btree *bt)
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ log_warn("compaction monitor: fork");
+ return -1;
+ }
+ if (pid > 0)
+ return pid;
+
+ signal(SIGINT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ setproctitle("compacting %s", btree_get_path(bt));
+ _exit(btree_compact(bt));
+}
+
+static int
+continue_compaction(struct ctl_conn *c)
+{
+again:
+ switch (c->state) {
+ case COMPACT_DATA:
+ log_info("compacting namespace %s (entries)", c->ns->suffix);
+ if (namespace_commit(c->ns) != 0) {
+ control_report_compaction(c, 1);
+ goto fail;
+ }
+ c->ns->compacting = 1;
+ if ((c->pid = compact(c->ns->data_db)) < 0)
+ goto fail;
+ c->state = COMPACT_INDX;
+ break;
+ case COMPACT_INDX:
+ if (namespace_reopen_data(c->ns) != 0)
+ goto fail;
+ log_info("compacting namespace %s (index)", c->ns->suffix);
+ if ((c->pid = compact(c->ns->indx_db)) < 0)
+ goto fail;
+ c->state = COMPACT_DONE;
+ break;
+ case COMPACT_DONE:
+ if (namespace_reopen_indx(c->ns) != 0)
+ goto fail;
+ c->ns->compacting = 0;
+ c->pid = 0;
+ namespace_queue_schedule(c->ns);
+
+ if (c->all) {
+ /* Proceed with the next namespace that isn't
+ * already being compacted or indexed.
+ */
+ while ((c->ns = TAILQ_NEXT(c->ns, next)) != NULL) {
+ if (!c->ns->compacting && !c->ns->indexing)
+ break;
+ }
+ } else
+ c->ns = NULL;
+
+ if (c->ns == NULL) {
+ control_end(c);
+ return 0;
+ }
+ c->state = COMPACT_DATA;
+ goto again;
+ break;
+ default:
+ assert(0);
+ }
+
+ return 0;
+
+fail:
+ control_end(c);
+ namespace_remove(c->ns);
+ c->ns = NULL;
+ return -1;
+}
+
+/* Run compaction for the given namespace, or all namespaces if ns is NULL.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int
+run_compaction(struct ctl_conn *c, struct namespace *ns)
+{
+ if (ns == NULL) {
+ c->all = 1;
+ c->ns = TAILQ_FIRST(&conf->namespaces);
+ } else {
+ c->all = 0;
+ c->ns = ns;
+ }
+
+ c->closecb = stop_compaction;
+ c->state = COMPACT_DATA;
+ return continue_compaction(c);
+}
+
+static void
+stop_compaction(struct ctl_conn *c)
+{
+ if (c->pid != 0) {
+ log_info("stopping compaction process %i", c->pid);
+ if (kill(c->pid, SIGKILL) != 0)
+ log_warn("failed to stop compaction process");
+ c->pid = 0;
+ }
+
+ if (c->ns != NULL) {
+ log_info("stopped compacting namespace %s", c->ns->suffix);
+ c->ns->compacting = 0;
+ namespace_queue_schedule(c->ns);
+ }
+ c->ns = NULL;
+}
+
diff --git a/usr.sbin/ldapd/conn.c b/usr.sbin/ldapd/conn.c
new file mode 100644
index 00000000000..f21a04e5c4b
--- /dev/null
+++ b/usr.sbin/ldapd/conn.c
@@ -0,0 +1,307 @@
+/* $OpenBSD: conn.c,v 1.1 2010/05/31 17:36:31 martinh 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/queue.h>
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+int conn_dispatch(struct conn *conn);
+unsigned long ldap_application(struct ber_element *elm);
+
+struct conn_list conn_list;
+
+unsigned long
+ldap_application(struct ber_element *elm)
+{
+ return BER_TYPE_OCTETSTRING;
+}
+
+void
+request_free(struct request *req)
+{
+ if (req->root != NULL)
+ ber_free_elements(req->root);
+ free(req);
+}
+
+void
+conn_close(struct conn *conn)
+{
+ struct search *search, *next;
+
+ log_debug("closing connection %d", conn->fd);
+
+ /* Cancel any ongoing searches on this connection. */
+ for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
+ next = TAILQ_NEXT(search, next);
+ search_close(search);
+ }
+
+ /* Cancel any queued requests on this connection. */
+ namespace_cancel_conn(conn);
+
+ ssl_session_destroy(conn);
+
+ TAILQ_REMOVE(&conn_list, conn, next);
+ ber_free(&conn->ber);
+ if (conn->bev != NULL)
+ bufferevent_free(conn->bev);
+ close(conn->fd);
+ free(conn->binddn);
+ free(conn);
+
+ --stats.conns;
+}
+
+/* Marks a connection for disconnect. The connection will be closed when
+ * any remaining data has been flushed to the socket.
+ */
+void
+conn_disconnect(struct conn *conn)
+{
+ conn->disconnect = 1;
+ bufferevent_enable(conn->bev, EV_WRITE);
+}
+
+int
+request_dispatch(struct request *req)
+{
+ unsigned long i;
+ struct {
+ unsigned long type;
+ int (*fn)(struct request *);
+ } requests[] = {
+ { LDAP_REQ_SEARCH, ldap_search },
+ { LDAP_REQ_BIND, ldap_bind },
+ { LDAP_REQ_ADD, ldap_add },
+ { LDAP_REQ_UNBIND_30, ldap_unbind },
+ { LDAP_REQ_MODIFY, ldap_modify },
+ { LDAP_REQ_ABANDON_30, ldap_abandon },
+ { LDAP_REQ_DELETE_30, ldap_delete },
+ { LDAP_REQ_EXTENDED, ldap_extended },
+ { 0, NULL }
+ };
+
+ /* RFC4511, section 4.2.1 says we shouldn't process other requests
+ * while binding. A bind operation can, however, be aborted by sending
+ * another bind operation.
+ */
+ if (req->conn->bind_req != NULL && req->type != LDAP_REQ_BIND) {
+ log_warnx("got request while bind in progress");
+ ldap_respond(req, LDAP_SASL_BIND_IN_PROGRESS);
+ return 0;
+ }
+
+ for (i = 0; requests[i].fn != NULL; i++) {
+ if (requests[i].type == req->type) {
+ requests[i].fn(req);
+ break;
+ }
+ }
+
+ if (requests[i].fn == NULL) {
+ log_warnx("unhandled request %d (not implemented)", req->type);
+ ldap_respond(req, LDAP_PROTOCOL_ERROR);
+ }
+
+ return 0;
+}
+
+int
+conn_dispatch(struct conn *conn)
+{
+ int class;
+ struct request *req;
+
+ ++stats.requests;
+
+ if ((req = calloc(1, sizeof(*req))) == NULL) {
+ log_warn("calloc");
+ conn_disconnect(conn);
+ return -1;
+ }
+
+ req->conn = conn;
+
+ if ((req->root = ber_read_elements(&conn->ber, NULL)) == NULL) {
+ if (errno != ECANCELED) {
+ log_warnx("protocol error");
+ conn_disconnect(conn);
+ }
+ request_free(req);
+ return -1;
+ }
+
+ /* Read message id and request type.
+ */
+ if (ber_scanf_elements(req->root, "{ite",
+ &req->msgid, &class, &req->type, &req->op) != 0) {
+ log_warnx("protocol error");
+ conn_disconnect(conn);
+ request_free(req);
+ return -1;
+ }
+
+ log_debug("got request type %d, id %lld", req->type, req->msgid);
+ request_dispatch(req);
+ return 0;
+}
+
+void
+conn_read(struct bufferevent *bev, void *data)
+{
+ size_t nused = 0;
+ struct conn *conn = data;
+ struct evbuffer *input;
+
+ input = EVBUFFER_INPUT(bev);
+ ber_set_readbuf(&conn->ber,
+ EVBUFFER_DATA(input), EVBUFFER_LENGTH(input));
+
+ while (conn->ber.br_rend - conn->ber.br_rptr > 0) {
+ if (conn_dispatch(conn) == 0)
+ nused += conn->ber.br_rptr - conn->ber.br_rbuf;
+ else
+ break;
+ }
+
+ evbuffer_drain(input, nused);
+}
+
+void
+conn_write(struct bufferevent *bev, void *data)
+{
+ struct search *search, *next;
+ struct conn *conn = data;
+
+ /* Continue any ongoing searches.
+ * Note that the search may be unlinked and freed by conn_search.
+ */
+ for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
+ next = TAILQ_NEXT(search, next);
+ conn_search(search);
+ }
+
+ if (conn->disconnect)
+ conn_close(conn);
+ else if (conn->s_flags & F_STARTTLS) {
+ conn->s_flags &= ~F_STARTTLS;
+ bufferevent_free(conn->bev);
+ conn->bev = NULL;
+ ssl_session_init(conn);
+ }
+}
+
+void
+conn_err(struct bufferevent *bev, short why, void *data)
+{
+ struct conn *conn = data;
+
+ if ((why & EVBUFFER_EOF) == EVBUFFER_EOF)
+ log_debug("end-of-file on connection %i", conn->fd);
+ else if ((why & EVBUFFER_TIMEOUT) == EVBUFFER_TIMEOUT)
+ log_debug("timeout on connection %i", conn->fd);
+ else
+ log_warnx("error 0x%02X on connection %i", why, conn->fd);
+
+ conn_close(conn);
+}
+
+void
+conn_accept(int fd, short why, void *data)
+{
+ int afd;
+ socklen_t addrlen;
+ struct conn *conn;
+ struct listener *l = data;
+ struct sockaddr_storage remote_addr;
+ char host[128];
+
+ addrlen = sizeof(remote_addr);
+ afd = accept(fd, (struct sockaddr *)&remote_addr, &addrlen);
+ if (afd == -1) {
+ log_warn("accept");
+ return;
+ }
+
+ if (l->ss.ss_family == AF_UNIX) {
+ uid_t euid;
+ gid_t egid;
+
+ if (getpeereid(afd, &euid, &egid) == -1)
+ log_warnx("conn_accept: getpeereid");
+ else
+ log_debug("accepted local connection by uid %d", euid);
+ } else {
+ print_host(&remote_addr, host, sizeof(host));
+ log_debug("accepted connection from %s on fd %d", host, afd);
+ }
+
+ fd_nonblock(afd);
+
+ if ((conn = calloc(1, sizeof(*conn))) == NULL) {
+ log_warn("malloc");
+ close(afd);
+ return;
+ }
+ conn->ber.fd = -1;
+ conn->s_l = l;
+ ber_set_application(&conn->ber, ldap_application);
+ conn->fd = afd;
+
+ if (l->flags & F_LDAPS) {
+ ssl_session_init(conn);
+ } else {
+ conn->bev = bufferevent_new(afd, conn_read, conn_write,
+ conn_err, conn);
+ if (conn->bev == NULL) {
+ log_warn("conn_accept: bufferevent_new");
+ close(afd);
+ free(conn);
+ return;
+ }
+ bufferevent_enable(conn->bev, EV_READ);
+ bufferevent_settimeout(conn->bev, 0, 60);
+ }
+
+ TAILQ_INIT(&conn->searches);
+ TAILQ_INSERT_HEAD(&conn_list, conn, next);
+
+ if (l->flags & F_SECURE)
+ conn->s_flags |= F_SECURE;
+
+ ++stats.conns;
+}
+
+struct conn *
+conn_by_fd(int fd)
+{
+ struct conn *conn;
+
+ TAILQ_FOREACH(conn, &conn_list, next) {
+ if (conn->fd == fd)
+ return conn;
+ }
+ return NULL;
+}
+
diff --git a/usr.sbin/ldapd/control.c b/usr.sbin/ldapd/control.c
new file mode 100644
index 00000000000..db77ffdda43
--- /dev/null
+++ b/usr.sbin/ldapd/control.c
@@ -0,0 +1,379 @@
+/* $OpenBSD: control.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+void
+control_init(struct control_sock *cs)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask, mode;
+
+ if (cs->cs_name == NULL)
+ return;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ fatal("control_init: socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ if (strlcpy(sun.sun_path, cs->cs_name,
+ sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
+ fatalx("control_init: name too long");
+
+ if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == 0)
+ fatalx("control socket already listening");
+
+ if (unlink(cs->cs_name) == -1 && errno != ENOENT)
+ fatal("control_init: unlink");
+
+ if (cs->cs_restricted) {
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ } else {
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
+ }
+
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ (void)umask(old_umask);
+ fatal("control_init: bind");
+ }
+ (void)umask(old_umask);
+
+ if (chmod(cs->cs_name, mode) == -1) {
+ (void)unlink(cs->cs_name);
+ fatal("control_init: chmod");
+ }
+
+ fd_nonblock(fd);
+ cs->cs_fd = fd;
+}
+
+void
+control_listen(struct control_sock *cs)
+{
+ if (cs->cs_name == NULL)
+ return;
+
+ if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1)
+ fatal("control_listen: listen");
+
+ event_set(&cs->cs_ev, cs->cs_fd, EV_READ | EV_PERSIST,
+ control_accept, cs);
+ event_add(&cs->cs_ev, NULL);
+}
+
+void
+control_cleanup(struct control_sock *cs)
+{
+ if (cs->cs_name == NULL)
+ return;
+ (void)unlink(cs->cs_name);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ struct control_sock *cs = arg;
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ fd_nonblock(connfd);
+
+ if ((c = calloc(1, sizeof(*c))) == NULL) {
+ log_warn("control_accept");
+ close(connfd);
+ return;
+ }
+
+ imsg_init(&c->iev.ibuf, connfd);
+ c->iev.handler = control_dispatch_imsg;
+ c->iev.events = EV_READ;
+ event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+ c->iev.handler, cs);
+ event_add(&c->iev.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->pid != pid;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warnx("control_close: fd %d: not found", fd);
+ return;
+ }
+
+ msgbuf_clear(&c->iev.ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ if (c->closecb)
+ c->closecb(c);
+
+ event_del(&c->iev.ev);
+ close(c->iev.ibuf.fd);
+ free(c);
+}
+
+static int
+send_stats(struct imsgev *iev)
+{
+ struct namespace *ns;
+ const struct btree_stat *st;
+ struct ns_stat nss;
+
+ imsg_compose(&iev->ibuf, IMSG_CTL_STATS, 0, iev->ibuf.pid, -1,
+ &stats, sizeof(stats));
+
+ TAILQ_FOREACH(ns, &conf->namespaces, next) {
+ strlcpy(nss.suffix, ns->suffix, sizeof(nss.suffix));
+ st = btree_stat(ns->data_db);
+ bcopy(st, &nss.data_stat, sizeof(nss.data_stat));
+
+ st = btree_stat(ns->indx_db);
+ bcopy(st, &nss.indx_stat, sizeof(nss.indx_stat));
+
+ imsg_compose(&iev->ibuf, IMSG_CTL_NSSTATS, 0, iev->ibuf.pid, -1,
+ &nss, sizeof(nss));
+ }
+
+ imsg_compose(&iev->ibuf, IMSG_CTL_END, 0, iev->ibuf.pid, -1, NULL, 0);
+
+ return 0;
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+ int n, verbose;
+ struct control_sock *cs = arg;
+ struct ctl_conn *c;
+ struct imsg imsg;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warnx("control_dispatch_imsg: fd %d: not found", fd);
+ return;
+ }
+
+ if (event & EV_WRITE) {
+ if (msgbuf_write(&c->iev.ibuf.w) < 0) {
+ control_close(fd);
+ return;
+ }
+ imsg_event_add(&c->iev);
+ }
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) {
+ control_close(fd);
+ return;
+ }
+ } else
+ return;
+
+ for (;;) {
+ if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ log_debug("control_dispatch_imsg: imsg type %u", imsg.hdr.type);
+
+ if (cs->cs_restricted || (c->flags & CTL_CONN_LOCKED)) {
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_STATS:
+ break;
+ default:
+ log_debug("control_dispatch_imsg: "
+ "client requested restricted command");
+ imsg_free(&imsg);
+ control_close(fd);
+ return;
+ }
+ }
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_STATS:
+ if (send_stats(&c->iev) == -1) {
+ log_debug("control_dispatch_imsg: "
+ "failed to send statistics");
+ imsg_free(&imsg);
+ control_close(fd);
+ return;
+ }
+ break;
+ case IMSG_CTL_LOG_VERBOSE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(verbose))
+ break;
+
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+
+ imsg_compose_event(iev_ldapd, IMSG_CTL_LOG_VERBOSE,
+ 0, 0, -1, &verbose, sizeof(verbose));
+ memcpy(imsg.data, &verbose, sizeof(verbose));
+ control_imsg_forward(&imsg);
+
+ log_verbose(verbose);
+ break;
+ case IMSG_CTL_COMPACT:
+ run_compaction(c, NULL);
+ break;
+ case IMSG_CTL_INDEX:
+ run_indexer(c, NULL);
+ break;
+ default:
+ log_debug("control_dispatch_imsg: "
+ "error handling imsg %d", imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->iev);
+}
+
+void
+control_imsg_forward(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ TAILQ_FOREACH(c, &ctl_conns, entry)
+ if (c->flags & CTL_CONN_NOTIFY)
+ imsg_compose(&c->iev.ibuf, imsg->hdr.type, 0,
+ imsg->hdr.pid, -1, imsg->data,
+ imsg->hdr.len - IMSG_HEADER_SIZE);
+}
+
+void
+control_end(struct ctl_conn *c)
+{
+ imsg_compose(&c->iev.ibuf, IMSG_CTL_END, 0, c->iev.ibuf.pid,
+ -1, NULL, 0);
+ imsg_event_add(&c->iev);
+}
+
+void
+control_report_compaction(struct ctl_conn *c, int status)
+{
+ struct compaction_status cs;
+
+ /* Report compaction status to the requesting control conn.
+ */
+ bzero(&cs, sizeof(cs));
+ strlcpy(cs.suffix, c->ns->suffix, sizeof(cs.suffix));
+ cs.db = c->state;
+ cs.status = status;
+ imsg_compose(&c->iev.ibuf, IMSG_CTL_COMPACT_STATUS, 0,
+ c->iev.ibuf.pid, -1, &cs, sizeof(cs));
+
+ imsg_event_add(&c->iev);
+}
+
+
+void
+control_report_indexer(struct ctl_conn *c, int status)
+{
+ const struct btree_stat *st;
+ struct indexer_status is;
+
+ /* Report indexer status to the requesting control conn.
+ */
+ bzero(&is, sizeof(is));
+ strlcpy(is.suffix, c->ns->suffix, sizeof(is.suffix));
+ st = btree_stat(c->ns->data_db);
+ is.entries = st->entries;
+ is.ncomplete = c->ncomplete;
+ is.status = status;
+ log_debug("reporting indexer status %ju/%ju for ns %s",
+ (intmax_t)is.ncomplete, (intmax_t)is.entries, c->ns->suffix);
+ imsg_compose(&c->iev.ibuf, IMSG_CTL_INDEX_STATUS, 0,
+ c->iev.ibuf.pid, -1, &is, sizeof(is));
+
+ imsg_event_add(&c->iev);
+}
+
diff --git a/usr.sbin/ldapd/filter.c b/usr.sbin/ldapd/filter.c
new file mode 100644
index 00000000000..7725ca9cdf1
--- /dev/null
+++ b/usr.sbin/ldapd/filter.c
@@ -0,0 +1,253 @@
+/* $OpenBSD: filter.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 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/queue.h>
+#include <sys/types.h>
+
+#include <string.h>
+#include <stdint.h>
+
+#include "ldapd.h"
+
+static int ldap_filt_eq(struct ber_element *root,
+ struct ber_element *filter);
+static int ldap_filt_subs(struct ber_element *root,
+ struct ber_element *filter);
+static int ldap_filt_and(struct ber_element *root,
+ struct ber_element *filter);
+static int ldap_filt_or(struct ber_element *root,
+ struct ber_element *filter);
+static int ldap_filt_not(struct ber_element *root,
+ struct ber_element *filter);
+
+static int
+ldap_filt_eq(struct ber_element *root, struct ber_element *filter)
+{
+ const char *key, *cmp;
+ struct ber_element *a, *vals, *v;
+
+ if (ber_scanf_elements(filter, "{ss", &key, &cmp) != 0)
+ return -1;
+
+ a = ldap_get_attribute(root, key);
+ if (a == NULL) {
+ log_debug("no attribute [%s] found", key);
+ return -1;
+ }
+
+ vals = a->be_next;
+ if (vals == NULL)
+ return -1;
+
+ for (v = vals->be_sub; v; v = v->be_next) {
+ char *vs;
+ if (ber_get_string(v, &vs) != 0)
+ continue;
+ if (strcasecmp(cmp, vs) == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+ldap_filt_subs_value(struct ber_element *v, struct ber_element *sub)
+{
+ int class;
+ unsigned long type;
+ const char *cmpval;
+ char *vs, *p, *end;
+
+ if (ber_get_string(v, &vs) != 0)
+ return -1;
+
+ for (; sub; sub = sub->be_next) {
+ if (ber_scanf_elements(sub, "ts", &class, &type, &cmpval) != 0)
+ return -1;
+
+ if (class != BER_CLASS_CONTEXT)
+ return -1;
+
+ switch (type) {
+ case LDAP_FILT_SUBS_INIT:
+ if (strncasecmp(cmpval, vs, strlen(cmpval)) == 0)
+ vs += strlen(cmpval);
+ else
+ return 1; /* no match */
+ break;
+ case LDAP_FILT_SUBS_ANY:
+ if ((p = strcasestr(vs, cmpval)) != NULL)
+ vs = p + strlen(cmpval);
+ else
+ return 1; /* no match */
+ break;
+ case LDAP_FILT_SUBS_FIN:
+ if (strlen(vs) < strlen(cmpval))
+ return 1; /* no match */
+ end = vs + strlen(vs) - strlen(cmpval);
+ if (strcasecmp(end, cmpval) == 0)
+ vs = end + strlen(cmpval);
+ else
+ return 1; /* no match */
+ break;
+ default:
+ log_warnx("invalid subfilter type %d", type);
+ return -1;
+ }
+ }
+
+ return 0; /* match */
+}
+
+static int
+ldap_filt_subs(struct ber_element *root, struct ber_element *filter)
+{
+ const char *key, *attr;
+ struct ber_element *sub, *a, *v;
+
+ if (ber_scanf_elements(filter, "{s{e", &key, &sub) != 0)
+ return -1;
+
+ a = ldap_get_attribute(root, key);
+ if (a == NULL)
+ return -1; /* attribute not found, false or undefined? */
+
+ if (ber_scanf_elements(a, "s(e", &attr, &v) != 0)
+ return -1; /* internal failure, false or undefined? */
+
+ /* Loop through all values, stop if any matches.
+ */
+ for (; v; v = v->be_next) {
+ /* All substrings must match. */
+ switch (ldap_filt_subs_value(v, sub)) {
+ case 0:
+ return 0;
+ case -1:
+ return -1;
+ default:
+ break;
+ }
+ }
+
+ /* All values checked, no match. */
+ return -1;
+}
+
+static int
+ldap_filt_and(struct ber_element *root, struct ber_element *filter)
+{
+ struct ber_element *elm;
+
+ if (ber_scanf_elements(filter, "{e", &elm) != 0)
+ return -1;
+
+ for (; elm; elm = elm->be_next) {
+ if (ldap_matches_filter(root, elm) != 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+ldap_filt_or(struct ber_element *root, struct ber_element *filter)
+{
+ struct ber_element *elm;
+
+ if (ber_scanf_elements(filter, "{e", &elm) != 0) {
+ log_warnx("failed to parse search filter");
+ return -1;
+ }
+
+ for (; elm; elm = elm->be_next) {
+ if (ldap_matches_filter(root, elm) == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+ldap_filt_not(struct ber_element *root, struct ber_element *filter)
+{
+ struct ber_element *elm;
+
+ if (ber_scanf_elements(filter, "{e", &elm) != 0) {
+ log_warnx("failed to parse search filter");
+ return -1;
+ }
+
+ for (; elm; elm = elm->be_next) {
+ if (ldap_matches_filter(root, elm) != 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+ldap_filt_presence(struct ber_element *root, struct ber_element *filter)
+{
+ const char *key;
+ struct ber_element *a;
+
+ if (ber_scanf_elements(filter, "s", &key) != 0) {
+ log_warnx("failed to parse presence filter");
+ return -1;
+ }
+
+ a = ldap_get_attribute(root, key);
+ if (a == NULL) {
+ log_debug("attribute %s not found", key);
+ return -1; /* attribute not found */
+ }
+
+ return 0;
+}
+
+int
+ldap_matches_filter(struct ber_element *root, struct ber_element *filter)
+{
+ if (filter == NULL)
+ return 0;
+
+ if (filter->be_class != BER_CLASS_CONTEXT) {
+ log_warnx("invalid class %d in filter", filter->be_class);
+ return -1;
+ }
+
+ switch (filter->be_type) {
+ case LDAP_FILT_EQ:
+ case LDAP_FILT_APPR:
+ return ldap_filt_eq(root, filter);
+ case LDAP_FILT_SUBS:
+ return ldap_filt_subs(root, filter);
+ case LDAP_FILT_AND:
+ return ldap_filt_and(root, filter);
+ case LDAP_FILT_OR:
+ return ldap_filt_or(root, filter);
+ case LDAP_FILT_NOT:
+ return ldap_filt_not(root, filter);
+ case LDAP_FILT_PRES:
+ return ldap_filt_presence(root, filter);
+ default:
+ log_warnx("filter type %d not implemented", filter->be_type);
+ return -1;
+ }
+}
+
diff --git a/usr.sbin/ldapd/index.c b/usr.sbin/ldapd/index.c
new file mode 100644
index 00000000000..63728e84e9b
--- /dev/null
+++ b/usr.sbin/ldapd/index.c
@@ -0,0 +1,491 @@
+/* $OpenBSD: index.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 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.
+ */
+
+/* Indices are stored as unique keys in a btree. No data is stored.
+ * The keys are made up of the attribute being indexed, concatenated
+ * with the distinguished name of the entry. The namespace suffix is
+ * stripped, however.
+ *
+ * Below, the namespace suffix dc=example,dc=com is stripped.
+ *
+ * Index b-tree sorts with plain strcmp:
+ * ...
+ * cn=Chunky Bacon,cn=chunky bacon,ou=people,
+ * cn=Chunky Bacon,uid=cbacon,ou=accounts,
+ * cn=Chunky Beans,cn=chunky beans,ou=people,
+ * cn=Chunky Beans,uid=cbeans,ou=accounts,
+ * cn=Crispy Bacon,cn=crispy bacon,ou=people,
+ * ...
+ * sn=Bacon,cn=chunky bacon,ou=people,
+ * sn=Bacon,cn=crispy bacon,ou=people,
+ * sn=Beans,cn=chunky beans,ou=people,
+ * ...
+ * This index can be used for equality, prefix and range searches.
+ *
+ * If an indexed attribute sorts numerically (integer), we might be able
+ * to just pad it with zeros... otherwise this needs to be refined.
+ *
+ * Multiple attributes can be indexed in the same database.
+ *
+ * Presence index can be stored as:
+ * !mail,cn=chunky bacon,ou=people,
+ * !mail,cn=chunky beans,ou=people,
+ * !mail,cn=crispy bacon,ou=people,
+ *
+ * Substring index could probably be stored like a suffix tree:
+ * sn>Bacon,cn=chunky bacon,ou=people,
+ * sn>acon,cn=chunky bacon,ou=people,
+ * sn>con,cn=chunky bacon,ou=people,
+ * sn>on,cn=chunky bacon,ou=people,
+ * sn>n,cn=chunky bacon,ou=people,
+ *
+ * This facilitates both substring and suffix search.
+ *
+ * Approximate index:
+ * sn~[soundex(bacon)],cn=chunky bacon,ou=people,
+ *
+ * One level searches can be indexed as follows:
+ * @<parent-rdn>,<rdn>
+ * example:
+ * @ou=accounts,uid=cbacon
+ * @ou=accounts,uid=cbeans
+ * @ou=people,cn=chunky bacon
+ * @ou=people,cn=chunky beans
+ * @ou=people,cn=crispy bacon
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldapd.h"
+
+static void continue_indexer(int fd, short why, void *arg);
+static void stop_indexer(struct ctl_conn *c);
+
+int
+index_attribute(struct namespace *ns, char *attr, struct btval *dn,
+ struct ber_element *a)
+{
+ int dnsz, rc;
+ char *s, *t;
+ struct ber_element *v;
+ struct btval key, val;
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(attr);
+ assert(dn);
+ assert(a);
+ assert(a->be_next);
+ bzero(&val, sizeof(val));
+
+ log_debug("indexing %.*s on %s", (int)dn->size, (char *)dn->data, attr);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ for (v = a->be_next->be_sub; v; v = v->be_next) {
+ if (ber_get_string(v, &s) != 0)
+ continue;
+ bzero(&key, sizeof(key));
+ key.size = asprintf(&t, "%s=%s,%.*s", attr, s, dnsz,
+ (char *)dn->data);
+ key.data = t;
+ normalize_dn(key.data);
+ rc = btree_txn_put(ns->indx_db, ns->indx_txn, &key, &val, BT_NOOVERWRITE);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+index_rdn(struct namespace *ns, struct btval *dn)
+{
+ int dnsz, rdnsz, pdnsz, rc;
+ char *t, *parent_dn;
+ struct btval key, val;
+
+ bzero(&val, sizeof(val));
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(dn);
+
+ dnsz = dn->size - strlen(ns->suffix);
+ if (dnsz-- == 0)
+ return 0;
+
+ parent_dn = memchr(dn->data, ',', dnsz);
+ if (parent_dn == NULL) {
+ rdnsz = dnsz;
+ pdnsz = 0;
+ } else {
+ rdnsz = parent_dn - (char *)dn->data;
+ pdnsz = dnsz - rdnsz - 1;
+ ++parent_dn;
+ }
+
+ key.size = asprintf(&t, "@%.*s,%.*s", pdnsz, parent_dn,
+ rdnsz, (char *)dn->data);
+ key.data = t;
+ log_debug("indexing rdn on %.*s", (int)key.size, (char *)key.data);
+ normalize_dn(key.data);
+ rc = btree_txn_put(ns->indx_db, ns->indx_txn, &key, &val, BT_NOOVERWRITE);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ return 0;
+}
+
+int
+unindex_attribute(struct namespace *ns, char *attr, struct btval *dn,
+ struct ber_element *a)
+{
+ int dnsz, rc;
+ char *s, *t;
+ struct ber_element *v;
+ struct btval key;
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(attr);
+ assert(dn);
+ assert(a);
+ assert(a->be_next);
+
+ log_debug("unindexing %.*s on %s",
+ (int)dn->size, (char *)dn->data, attr);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ for (v = a->be_next->be_sub; v; v = v->be_next) {
+ if (ber_get_string(v, &s) != 0)
+ continue;
+ bzero(&key, sizeof(key));
+ key.size = asprintf(&t, "%s=%s,%.*s", attr, s, dnsz,
+ (char *)dn->data);
+ key.data = t;
+ normalize_dn(key.data);
+ rc = btree_txn_del(ns->indx_db, ns->indx_txn, &key, NULL);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+index_entry(struct namespace *ns, struct btval *dn, struct ber_element *elm)
+{
+ struct ber_element *a;
+ struct attr_index *ai;
+
+ assert(ns);
+ assert(dn);
+ assert(elm);
+ TAILQ_FOREACH(ai, &ns->indices, next) {
+ a = ldap_get_attribute(elm, ai->attr);
+ if (a && index_attribute(ns, ai->attr, dn, a) < 0)
+ return -1;
+ }
+
+ return index_rdn(ns, dn);
+}
+
+static int
+unindex_rdn(struct namespace *ns, struct btval *dn)
+{
+ int dnsz, rdnsz, rc;
+ char *t, *parent_dn;
+ struct btval key, val;
+
+ bzero(&val, sizeof(val));
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(dn);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ parent_dn = memchr(dn->data, ',', dn->size);
+ if (parent_dn++ == NULL)
+ parent_dn = (char *)dn->data + dn->size;
+ rdnsz = parent_dn - (char *)dn->data;
+
+ key.size = asprintf(&t, "@%.*s,%.*s", (dnsz - rdnsz), parent_dn,
+ rdnsz, (char *)dn->data);
+ key.data = t;
+ log_debug("unindexing rdn on %.*s", (int)key.size, (char *)key.data);
+ normalize_dn(key.data);
+ rc = btree_txn_del(ns->indx_db, ns->indx_txn, &key, NULL);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ return 0;
+}
+
+int
+unindex_entry(struct namespace *ns, struct btval *dn, struct ber_element *elm)
+{
+ struct ber_element *a;
+ struct attr_index *ai;
+
+ assert(ns);
+ assert(dn);
+ assert(elm);
+ TAILQ_FOREACH(ai, &ns->indices, next) {
+ a = ldap_get_attribute(elm, ai->attr);
+ if (a && unindex_attribute(ns, ai->attr, dn, a) < 0)
+ return -1;
+ }
+
+ return unindex_rdn(ns, dn);
+}
+
+/* Reconstruct the full dn from the index key and the namespace suffix.
+ *
+ * Examples:
+ *
+ * index key:
+ * sn=Bacon,cn=chunky bacon,ou=people,
+ * full dn:
+ * cn=chunky bacon,ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @ou=people,cn=chunky bacon
+ * full dn:
+ * cn=chunky bacon,ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @,ou=people
+ * full dn:
+ * ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @ou=staff,ou=people,cn=chunky bacon
+ * full dn:
+ * cn=chunky bacon,ou=staff,ou=people,dc=example,dc=com
+ *
+ * Filled in dn must be freed with btval_reset().
+ */
+int
+index_to_dn(struct namespace *ns, struct btval *indx, struct btval *dn)
+{
+ char *rdn, *parent_rdn, indxtype, *dst;
+ int rdn_sz, prdn_sz;
+
+ /* Skip past first index part to get the RDN.
+ */
+
+ indxtype = ((char *)indx->data)[0];
+ if (indxtype == '@') {
+ /* One-level index.
+ * Full DN is made up of rdn + parent rdn + namespace suffix.
+ */
+ rdn = memrchr(indx->data, ',', indx->size);
+ if (rdn++ == NULL)
+ return -1;
+ rdn_sz = indx->size - (rdn - (char *)indx->data);
+
+ parent_rdn = (char *)indx->data + 1;
+ prdn_sz = rdn - parent_rdn - 1;
+ dn->size = indx->size + strlen(ns->suffix);
+ if (prdn_sz == 0)
+ dn->size--;
+ if ((dn->data = malloc(dn->size)) == NULL) {
+ log_warn("conn_search: malloc");
+ return -1;
+ }
+ dst = dn->data;
+ bcopy(rdn, dst, rdn_sz);
+ dst += rdn_sz;
+ *dst++ = ',';
+ bcopy(parent_rdn, dst, prdn_sz);
+ dst += prdn_sz;
+ if (prdn_sz > 0)
+ *dst++ = ',';
+ bcopy(ns->suffix, dst, strlen(ns->suffix));
+ } else {
+ /* Construct the full DN by appending namespace suffix.
+ */
+ rdn = memchr(indx->data, ',', indx->size);
+ if (rdn++ == NULL)
+ return -1;
+ rdn_sz = indx->size - (rdn - (char *)indx->data);
+ dn->size = rdn_sz + strlen(ns->suffix);
+ if ((dn->data = malloc(dn->size)) == NULL) {
+ log_warn("index_to_dn: malloc");
+ return -1;
+ }
+ bcopy(rdn, dn->data, rdn_sz);
+ bcopy(ns->suffix, (char *)dn->data + rdn_sz,
+ strlen(ns->suffix));
+ }
+
+ dn->free_data = 1;
+
+ return 0;
+}
+
+/* Return the next namespace that isn't already being indexed or compacted.
+ */
+static struct namespace *
+next_namespace(struct namespace *ns)
+{
+ if (ns == NULL)
+ ns = TAILQ_FIRST(&conf->namespaces);
+ else
+ ns = TAILQ_NEXT(ns, next);
+
+ do {
+ if (ns == NULL || (!ns->indexing && !ns->compacting))
+ break;
+ } while ((ns = TAILQ_NEXT(ns, next)) != NULL);
+
+ return ns;
+}
+
+static void
+continue_indexer(int fd, short why, void *arg)
+{
+ struct ctl_conn *c = arg;
+ struct ber_element *elm;
+ struct btval key, val;
+ struct timeval tv;
+ int i, rc;
+
+ if (c->cursor == NULL) {
+ log_info("begin indexing namespace %s", c->ns->suffix);
+ c->ncomplete = 0;
+ c->ns->indexing = 1;
+ c->cursor = btree_cursor_open(c->ns->data_db);
+ if (c->cursor == NULL) {
+ log_warn("failed to open cursor");
+ goto fail;
+ }
+ }
+
+ bzero(&key, sizeof(key));
+ bzero(&val, sizeof(val));
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if (namespace_begin(c->ns) != 0) {
+ tv.tv_usec = 50000;
+ evtimer_add(&c->ev, &tv);
+ return;
+ }
+
+ for (i = 0; i < 40; i++) {
+ rc = btree_cursor_get(c->cursor, &key, &val, BT_NEXT);
+ if (rc != BT_SUCCESS)
+ break;
+ if ((elm = namespace_db2ber(c->ns, &val)) == NULL)
+ continue;
+ rc = index_entry(c->ns, &key, elm);
+ ber_free_elements(elm);
+ btval_reset(&key);
+ btval_reset(&val);
+ if (rc != 0)
+ goto fail;
+ ++c->ncomplete;
+ }
+
+ if (namespace_commit(c->ns) != 0)
+ goto fail;
+
+ control_report_indexer(c, 0);
+
+ if (rc == BT_NOTFOUND) {
+ log_info("done indexing namespace %s", c->ns->suffix);
+ btree_cursor_close(c->cursor);
+ c->cursor = NULL;
+ c->ns->indexing = 0;
+ control_report_indexer(c, 1);
+
+ if (c->all)
+ c->ns = next_namespace(c->ns);
+ else
+ c->ns = NULL;
+
+ if (c->ns == NULL) {
+ log_info("done indexing all namespaces");
+ return;
+ }
+ } else if (rc != BT_SUCCESS)
+ goto fail;
+
+ evtimer_add(&c->ev, &tv);
+ return;
+
+fail:
+ if (c->ns != NULL)
+ control_report_indexer(c, -1);
+ control_end(c);
+ stop_indexer(c);
+}
+
+/* Run indexing for the given namespace, or all namespaces if ns is NULL.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int
+run_indexer(struct ctl_conn *c, struct namespace *ns)
+{
+ if (ns == NULL) {
+ c->all = 1;
+ c->ns = next_namespace(NULL);
+ } else {
+ c->all = 0;
+ c->ns = ns;
+ }
+
+ if (c->ns == NULL) {
+ control_end(c);
+ } else {
+ c->closecb = stop_indexer;
+ evtimer_set(&c->ev, continue_indexer, c);
+ continue_indexer(0, 0, c);
+ }
+
+ return 0;
+}
+
+static void
+stop_indexer(struct ctl_conn *c)
+{
+ if (c->ns != NULL) {
+ log_info("stopped indexing namespace %s", c->ns->suffix);
+ c->ns->indexing = 0;
+ }
+ btree_cursor_close(c->cursor);
+ c->cursor = NULL;
+ c->ns = NULL;
+ c->closecb = NULL;
+ evtimer_del(&c->ev);
+}
+
diff --git a/usr.sbin/ldapd/ldapd.8 b/usr.sbin/ldapd/ldapd.8
new file mode 100644
index 00000000000..906b9dd16da
--- /dev/null
+++ b/usr.sbin/ldapd/ldapd.8
@@ -0,0 +1,94 @@
+.\" $OpenBSD: ldapd.8,v 1.1 2010/05/31 17:36:31 martinh 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.
+.\"
+.Dd $Mdocdate: May 31 2010 $
+.Dt LDAPD 8
+.Os
+.Sh NAME
+.Nm ldapd
+.Nd Lightweight Directory Access Protocol daemon
+.Sh SYNOPSIS
+.Nm ldapd
+.Op Fl dnv
+.Oo
+.Fl D Ar macro Ns = Ns value
+.Oc
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a daemon which implements the LDAP protocol.
+.Pp
+A running
+.Nm
+process can be controlled using the
+.Xr ldapctl 8
+utility.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar macro Ns = Ns Ar value
+Define
+.Ar macro
+to be set to
+.Ar value
+on the command line.
+Overrides the definition of
+.Ar macro
+in the configuration file.
+.It Fl d
+Do not daemonize and log to
+.Em stderr .
+.It Fl f Ar file
+Use
+.Ar file
+as the configuration file, instead of the default
+.Pa /etc/ldapd.conf .
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/ldapd.sockXXXXXXX" -compact
+.It Pa /etc/ldapd.conf
+default
+.Nm
+configuration file
+.It Pa /var/run/ldapd.sock
+default
+.Nm
+control socket
+.It Pa /var/db/ldap/*.db
+.Nm
+database files
+.El
+.Sh SEE ALSO
+.Xr ldapd.conf 5 ,
+.Xr ldapctl 8
+.Rs
+.%R RFC 4511
+.%T Lightweight Directory Access Protocol (LDAP): The Protocol
+.%D June 2006
+.Re
+.Rs
+.%R RFC 4512
+.%T Lightweight Directory Access Protocol (LDAP): Directory Information Models
+.%D June 2006
+.Re
+.Sh CAVEATS
+.Nm
+does not fully work yet.
diff --git a/usr.sbin/ldapd/ldapd.c b/usr.sbin/ldapd/ldapd.c
new file mode 100644
index 00000000000..2ad516a4988
--- /dev/null
+++ b/usr.sbin/ldapd/ldapd.c
@@ -0,0 +1,331 @@
+/* $OpenBSD: ldapd.c,v 1.1 2010/05/31 17:36:31 martinh 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/queue.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <bsd_auth.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+void usage(void);
+void ldapd_sig_handler(int fd, short why, void *data);
+void ldapd_sigchld_handler(int sig, short why, void *data);
+void ldapd_dispatch_ldape(int fd, short event, void *ptr);
+
+struct ldapd_stats stats;
+pid_t ldape_pid;
+
+void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dnv] [-D macro=value] "
+ "[-f file] [-s file]\n", __progname);
+ exit(1);
+}
+
+void
+ldapd_sig_handler(int sig, short why, void *data)
+{
+ log_info("ldapd: got signal %d", sig);
+ if (sig == SIGINT || sig == SIGTERM)
+ event_loopexit(NULL);
+}
+
+void
+ldapd_sigchld_handler(int sig, short why, void *data)
+{
+ pid_t pid;
+ int status;
+
+ while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
+ if (pid == -1) {
+ if (errno == EINTR)
+ continue;
+ if (errno != ECHILD)
+ log_warn("waitpid");
+ break;
+ }
+
+ if (WIFEXITED(status))
+ log_debug("child %d exited with status %d",
+ pid, WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ log_debug("child %d exited due to signal %d",
+ pid, WTERMSIG(status));
+ else
+ log_debug("child %d terminated abnormally", pid);
+
+ if (pid == ldape_pid) {
+ log_info("ldapd: lost ldap server");
+ event_loopexit(NULL);
+ break;
+ }
+ }
+}
+
+/* set socket non-blocking */
+void
+fd_nonblock(int fd)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (rc == -1) {
+ log_warn("failed to set fd %i non-blocking", fd);
+ }
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int debug = 0, verbose = 0;
+ int configtest = 0, skip_chroot = 0;
+ int pipe_parent2ldap[2];
+ char *conffile = CONFFILE;
+ char *csockpath = LDAPD_SOCKET;
+ struct passwd *pw = NULL;
+ struct imsgev *iev_ldape;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+
+ log_init(1); /* log to stderr until daemonized */
+
+ while ((c = getopt(argc, argv, "dhvD:f:ns:")) != -1) {
+ switch (c) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'D':
+ if (cmdline_symset(optarg) < 0) {
+ warnx("could not parse macro definition %s",
+ optarg);
+ }
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'h':
+ usage();
+ /* NOTREACHED */
+ case 'n':
+ configtest = 1;
+ break;
+ case 's':
+ csockpath = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc > 0)
+ usage();
+
+ log_verbose(verbose);
+ stats.started_at = time(0);
+ ssl_init();
+
+ if (parse_config(conffile) != 0)
+ exit(2);
+
+ if (configtest) {
+ fprintf(stderr, "configuration ok\n");
+ exit(0);
+ }
+
+ if (geteuid()) {
+ if (!debug)
+ errx(1, "need root privileges");
+ skip_chroot = 1;
+ }
+
+ if (!skip_chroot && (pw = getpwnam(LDAPD_USER)) == NULL)
+ err(1, "%s", LDAPD_USER);
+
+ if (!debug) {
+ if (daemon(1, 0) == -1)
+ err(1, "failed to daemonize");
+ }
+
+ log_init(debug);
+ log_info("startup");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2ldap) != 0)
+ fatal("socketpair");
+
+ fd_nonblock(pipe_parent2ldap[0]);
+ fd_nonblock(pipe_parent2ldap[1]);
+
+ ldape_pid = ldape(pw, csockpath, pipe_parent2ldap);
+
+ setproctitle("auth");
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, ldapd_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, ldapd_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, ldapd_sigchld_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, ldapd_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ close(pipe_parent2ldap[1]);
+
+ if ((iev_ldape = calloc(1, sizeof(struct imsgev))) == NULL)
+ fatal("calloc");
+ imsg_init(&iev_ldape->ibuf, pipe_parent2ldap[0]);
+ iev_ldape->handler = ldapd_dispatch_ldape;
+ imsg_event_add(iev_ldape);
+
+ event_dispatch();
+ log_debug("ldapd: exiting");
+
+ return 0;
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ if (event_initialized(&iev->ev))
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, u_int16_t type,
+ u_int32_t peerid, pid_t pid, int fd, void *data, u_int16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+ pid, fd, data, datalen)) != -1)
+ imsg_event_add(iev);
+ return (ret);
+}
+
+int
+imsg_event_handle(struct imsgev *iev, short event)
+{
+ ssize_t n;
+
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(&iev->ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ return -1;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&iev->ibuf.w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(iev);
+ return 1;
+ default:
+ fatalx("unknown event");
+ }
+
+ return 0;
+}
+
+void
+ldapd_dispatch_ldape(int fd, short event, void *ptr)
+{
+ struct imsgev *iev = ptr;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int verbose;
+
+ if (imsg_event_handle(iev, event) != 0)
+ return;
+
+ ibuf = &iev->ibuf;
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("ldapd_dispatch_ldape: imsg_read error");
+ if (n == 0)
+ break;
+
+ log_debug("ldapd_dispatch_ldape: imsg type %u", imsg.hdr.type);
+
+ switch (imsg.hdr.type) {
+ case IMSG_LDAPD_AUTH: {
+ struct auth_req *areq = imsg.data;
+ struct auth_res ares;
+
+ log_debug("authenticating [%s]", areq->name);
+ ares.ok = auth_userokay(areq->name, NULL, "auth-ldap",
+ areq->password);
+ ares.fd = areq->fd;
+ ares.msgid = areq->msgid;
+ bzero(areq, sizeof(*areq));
+ imsg_compose(ibuf, IMSG_LDAPD_AUTH_RESULT, 0, 0, -1,
+ &ares, sizeof(ares));
+ imsg_event_add(iev);
+ break;
+ }
+ case IMSG_CTL_LOG_VERBOSE:
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_verbose(verbose);
+ break;
+ default:
+ log_debug("ldapd_dispatch_ldape: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(iev);
+}
+
diff --git a/usr.sbin/ldapd/ldapd.conf.5 b/usr.sbin/ldapd/ldapd.conf.5
new file mode 100644
index 00000000000..8500b2d1274
--- /dev/null
+++ b/usr.sbin/ldapd/ldapd.conf.5
@@ -0,0 +1,230 @@
+.\" $OpenBSD: ldapd.conf.5,v 1.1 2010/05/31 17:36:31 martinh Exp $
+.\"
+.\" Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
+.\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org>
+.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
+.\"
+.\" 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.
+.\"
+.\"
+.Dd $Mdocdate: May 31 2010 $
+.Dt LDAPD.CONF 5
+.Os
+.Sh NAME
+.Nm ldapd.conf
+.Nd Lightweight Directory Access Protocol daemon configuration file
+.Sh DESCRIPTION
+.Nm
+is the configuration file for the LDAP daemon
+.Xr ldapd 8 .
+.Pp
+Comments can be put anywhere in the file using a hash mark
+.Pq # ,
+and extend to the end of the current line.
+Arguments containing whitespace should be surrounded by double quotes
+.Pq \&" .
+.Pp
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, and may contain letters, digits
+and underscores.
+Macro names may not be reserved words (for example
+.Ar listen ,
+.Ar namespace ,
+.Ar port ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+wan_if = "fxp0"
+listen on $wan_if
+listen on $wan_if tls
+.Ed
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/ldap/inetorgperson.schema"
+.Ed
+.Pp
+The syntax of
+.Nm
+is described below.
+.Sh GLOBAL CONFIGURATION
+.Bl -tag -width Ds
+.It Xo
+.Ic listen on Ar interface
+.Op Ic port Ar port
+.Op Ic tls | ldaps | secure
+.Op Ic certificate Ar name
+.Xc
+Specify an
+.Ar interface
+and
+.Ar port
+to listen on.
+An IP address, domain name or absolute path may be used in place of
+.Ar interface .
+An absolute path is used to listen on a unix domain socket.
+.Pp
+Secured connections are provided either using STARTTLS
+.Pq Ic tls ,
+by default on port 389,
+or LDAPS
+.Pq Ic ldaps ,
+by default on port 636.
+Creation of certificates is documented in
+.Xr starttls 8 .
+If no certificate
+.Ar name
+is specified, the
+.Pa /etc/ldap/certs
+directory is searched for a file named by joining
+the interface name with a .crt extension, e.g.\&
+.Pa /etc/ldap/certs/fxp0.crt .
+.Pp
+If the certificate name is an absolute path, a .crt and .key extension
+is appended to form the certificate path and key path respectively.
+.Pp
+Only secured connections accept plain text password authentication.
+Connections using TLS or unix domain sockets are always considered secured.
+The secure keyword can be used to mark an otherwise insecure connection
+secured, e.g. if IPsec is used.
+.El
+.Sh NAMESPACES
+A namespace is a subtree of the global X.500 DIT (Directory Information Tree),
+also known as a naming context.
+All entries' distinguished names (DN) has the same suffix, which is used to
+identify the namespace.
+The suffix should consist of the domain components, in reverse order, of your
+domain name, as recommended by RFC2247.
+.Bd -literal -offset indent
+namespace "dc=example,dc=com" {
+ rootdn "cn=admin,dc=example,dc=com"
+ rootpw "secret"
+}
+.Ed
+.Pp
+A namespace has the following configuration properties:
+.Bl -tag -width Ds
+.It rootdn Ar dn
+Specify the distinguished name of the root user for the namespace.
+The root user is always allowed to read and write entries in the namespace.
+The distinguished name must have the same suffix as the namespace.
+.It rootpw Ar password
+Password for the root user.
+Specified either in plain text, or in hashed format.
+.It index Ar attribute
+Maintain an index on the specified attribute.
+This index can be used for equality, prefix substring and range searches.
+.Xr ldapd 8
+will update the index on each modification.
+If you add an index to an existing namespace, you need to run
+.Xr ldapctl 8
+to index the existing entries.
+.It fsync Ar on | off
+If
+.Ar off ,
+data will not be forced to disk after each commit.
+Disabling fsync can increase write speed substantially, but may lead to data
+loss.
+The default value is on.
+.It cache-size Ar size
+Set the cache size for data entries.
+The
+.Ar size
+is specified in number of pages.
+Note that more than the configured number of pages may exist in the cache, as
+dirty pages and pages referenced by cursors are excluded from cache expiration.
+.Pp
+Cached pages are expired in a least recently used (LRU) order.
+.It index-cache-size Ar size
+Set the cache size for the index database.
+.It relax schema
+Disables checking of required and optional object attributes against schema.
+All attributes and values are matched as case-insensitive strings.
+All attributes are considered multi-valued.
+.It strict schema
+Enables checking of required and optional object attributes against schema.
+This is the default.
+.It Ic allow | deny
+Allow or deny access to parts of the namespace.
+.Pp
+Each request to the
+.Xr ldapd 8
+daemon evaluates the filter rules in sequential order, from first to last.
+The last matching rule decides what action is taken.
+If no rule matches the request, the default action is to allow the request.
+The root DN is always allowed to perform any request.
+.Pp
+The allow/deny statement operates on all access types by default.
+This can be restricted by an access type specification:
+.Bl -tag -width Ds
+.It read access
+Restricts the filter rule to search operations.
+.It write access
+Restricts the filter rule to add, delete and modify operations.
+.It bind access
+Restricts the filter rule to bind operations.
+.El
+.Pp
+The scope of the filter rule can be restricted by the
+.Em to
+keyword:
+.Bl -tag -width Ds
+.It to subtree Ar DN
+The filter rule applies to the distinguished name,
+as well as for all its descendants.
+.It to children of Ar DN
+The filter rule applies to all the direct children of the distinguished name.
+.It to Ar DN
+The filter rule applies to the distinguished name only.
+.It to any
+The filter rule applies to any distinguished name in the namespace.
+This is the default if no scope is specified.
+.It to root
+The filter rule applies to the root DSE.
+.El
+.Pp
+Finally, the filter rule can match a bind DN:
+.Bl -tag -width Ds
+.It by any
+The filter rule matches by any bind dn, including anonymous binds.
+.It by Ar DN
+The filter rule matches only if the requestor has previously performed
+a bind as the specified distinguished name.
+.It by self
+The filter rule matches only if the requestor has previously performed
+a bind as the distinguished name that is being requested.
+Typically used to allow users to modify their own data.
+.El
+.It use compression Op level Ar level
+Enable compression of entries and optionally specify compression level (0 - 9).
+By default, no compression is used.
+.El
+.Sh ATTRIBUTE TYPES
+.Pp
+As defined in RFC4512...
+.Sh OBJECTCLASSES
+.Pp
+As defined in RFC4512...
+.Sh FILES
+.Bl -tag -width "/etc/ldap/ldapd.confXXX" -compact
+.It Pa /etc/ldapd.conf
+Default
+.Xr ldapd 8
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr ldapd 8 ,
+.Xr ldapctl 8
diff --git a/usr.sbin/ldapd/ldapd.h b/usr.sbin/ldapd/ldapd.h
new file mode 100644
index 00000000000..13f8ba87b86
--- /dev/null
+++ b/usr.sbin/ldapd/ldapd.h
@@ -0,0 +1,617 @@
+/* $OpenBSD: ldapd.h,v 1.1 2010/05/31 17:36:31 martinh 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.
+ */
+
+#ifndef _LDAPD_H
+#define _LDAPD_H
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdarg.h>
+
+#include "aldap.h"
+#include "btree.h"
+
+#define CONFFILE "/etc/ldapd.conf"
+#define LDAPD_USER "_ldapd"
+#define LDAPD_SOCKET "/var/run/ldapd.sock"
+#define LDAP_PORT 389
+#define LDAPS_PORT 636
+#define LDAPD_SESSION_TIMEOUT 30
+#define MAX_LISTEN 64
+
+#define F_STARTTLS 0x01
+#define F_LDAPS 0x02
+#define F_SSL (F_LDAPS|F_STARTTLS)
+
+#define F_SECURE 0x04
+
+#define F_SCERT 0x01
+
+struct conn;
+
+struct aci {
+ SIMPLEQ_ENTRY(aci) entry;
+#define ACI_DENY 0
+#define ACI_ALLOW 1
+ int type;
+#define ACI_READ 0x01
+#define ACI_WRITE 0x02
+#define ACI_COMPARE 0x04
+#define ACI_CREATE 0x08
+#define ACI_BIND 0x10
+#define ACI_ALL 0x1F
+ int rights;
+ enum scope scope; /* base, onelevel or subtree */
+ char *attribute;
+ char *target;
+ char *subject;
+ char *filter;
+};
+SIMPLEQ_HEAD(acl, aci);
+
+/* An LDAP request.
+ */
+struct request {
+ TAILQ_ENTRY(request) next;
+ unsigned long type;
+ long long msgid;
+ struct ber_element *root;
+ struct ber_element *op;
+ struct conn *conn;
+ int replayed; /* true if replayed request */
+};
+TAILQ_HEAD(request_queue, request);
+
+enum index_type {
+ INDEX_NONE,
+ INDEX_EQUAL = 1,
+ INDEX_APPROX = 1,
+ INDEX_SUBSTR,
+ INDEX_PRESENCE
+};
+
+struct attr_index {
+ TAILQ_ENTRY(attr_index) next;
+ char *attr;
+ enum index_type type;
+};
+TAILQ_HEAD(attr_index_list, attr_index);
+
+struct namespace {
+ TAILQ_ENTRY(namespace) next;
+ char *suffix;
+ char *rootdn;
+ char *rootpw;
+ char *data_path;
+ char *indx_path;
+ struct btree *data_db;
+ struct btree *indx_db;
+ struct btree_txn *data_txn;
+ struct btree_txn *indx_txn;
+ int sync; /* 1 = fsync after commit */
+ struct attr_index_list indices;
+ unsigned int cache_size;
+ unsigned int index_cache_size;
+ int compacting; /* true if being compacted */
+ int indexing; /* true if being indexed */
+ struct request_queue request_queue;
+ struct event ev_queue;
+ unsigned int queued_requests;
+ struct acl acl;
+ int relax; /* relax schema validation */
+ int compression_level; /* 0-9, 0 = disabled */
+};
+
+TAILQ_HEAD(namespace_list, namespace);
+
+struct index
+{
+ TAILQ_ENTRY(index) next;
+ char *prefix;
+};
+
+/* A query plan.
+ */
+struct plan
+{
+ TAILQ_ENTRY(plan) next;
+ TAILQ_HEAD(, plan) args;
+ TAILQ_HEAD(, index) indices;
+ int indexed;
+};
+
+/* For OR filters using multiple indices, matches are not unique. Remember
+ * all DNs sent to the client to make them unique.
+ */
+struct uniqdn {
+ RB_ENTRY(uniqdn) link;
+ struct btval key;
+};
+RB_HEAD(dn_tree, uniqdn);
+RB_PROTOTYPE(dn_tree, uniqdn, link, uniqdn_cmp);
+
+/* An LDAP search request.
+ */
+struct search {
+ TAILQ_ENTRY(search) next;
+ int init; /* 1 if cursor initiated */
+ struct conn *conn;
+ struct request *req;
+ struct namespace *ns;
+ struct cursor *cursor;
+ unsigned int nscanned, nmatched, ndups;
+ time_t started_at;
+ long long szlim, tmlim; /* size and time limits */
+ int typesonly; /* not implemented */
+ long long scope;
+ long long deref; /* not implemented */
+ char *basedn;
+ struct ber_element *filter, *attrlist;
+ struct plan *plan;
+ struct index *cindx; /* current index */
+ struct dn_tree uniqdns;
+};
+
+struct listener {
+ unsigned int flags; /* F_STARTTLS or F_LDAPS */
+ struct sockaddr_storage ss;
+ int port;
+ int fd;
+ struct event ev;
+ char ssl_cert_name[PATH_MAX];
+ struct ssl *ssl;
+ void *ssl_ctx;
+ TAILQ_ENTRY(listener) entry;
+};
+TAILQ_HEAD(listenerlist, listener);
+
+/* An LDAP client connection.
+ */
+struct conn
+{
+ TAILQ_ENTRY(conn) next;
+ int fd;
+ struct bufferevent *bev;
+ struct ber ber;
+ int disconnect;
+ struct request *bind_req; /* ongoing bind request */
+ char *binddn;
+ TAILQ_HEAD(, search) searches;
+
+ /* SSL support */
+ struct event s_ev;
+ struct timeval s_tv;
+ struct listener *s_l;
+ void *s_ssl;
+ unsigned char *s_buf;
+ int s_buflen;
+ unsigned int s_flags;
+};
+TAILQ_HEAD(conn_list, conn) conn_list;
+
+enum usage {
+ USAGE_USER_APP,
+ USAGE_DIR_OP, /* operational attribute */
+ USAGE_DIST_OP, /* operational attribute */
+ USAGE_DSA_OP /* operational attribute */
+};
+
+struct name {
+ SLIST_ENTRY(name) next;
+ const char *name;
+};
+SLIST_HEAD(name_list, name);
+
+struct attr_type {
+ RB_ENTRY(attr_type) link;
+ const char *oid;
+ struct name_list *names;
+ char *desc;
+ int obsolete;
+ struct attr_type *sup;
+ char *equality;
+ char *ordering;
+ char *substr;
+ char *syntax;
+ int single;
+ int collective;
+ int immutable; /* no-user-modification */
+ enum usage usage;
+};
+RB_HEAD(attr_type_tree, attr_type);
+RB_PROTOTYPE(attr_type_tree, attr_type, link, attr_oid_cmp);
+
+struct attr_ptr {
+ SLIST_ENTRY(attr_ptr) next;
+ struct attr_type *attr_type;
+};
+SLIST_HEAD(attr_list, attr_ptr);
+
+enum object_kind {
+ KIND_ABSTRACT,
+ KIND_STRUCTURAL,
+ KIND_AUXILIARY
+};
+
+struct object;
+struct obj_ptr {
+ SLIST_ENTRY(obj_ptr) next;
+ struct object *object;
+};
+SLIST_HEAD(obj_list, obj_ptr);
+
+struct object {
+ RB_ENTRY(object) link;
+ const char *oid;
+ struct name_list *names;
+ char *desc;
+ int obsolete;
+ struct obj_list *sup;
+ enum object_kind kind;
+ struct attr_list *must;
+ struct attr_list *may;
+};
+RB_HEAD(object_tree, object);
+RB_PROTOTYPE(object_tree, object, link, obj_oid_cmp);
+
+struct oidname {
+ RB_ENTRY(oidname) link;
+ const char *on_name;
+#define on_attr_type on_ptr.ou_attr_type
+#define on_object on_ptr.ou_object
+ union {
+ struct attr_type *ou_attr_type;
+ struct object *ou_object;
+ } on_ptr;
+};
+RB_HEAD(oidname_tree, oidname);
+RB_PROTOTYPE(oidname_tree, oidname, link, oidname_cmp);
+
+struct ssl {
+ SPLAY_ENTRY(ssl) ssl_nodes;
+ char ssl_name[PATH_MAX];
+ char *ssl_cert;
+ off_t ssl_cert_len;
+ char *ssl_key;
+ off_t ssl_key_len;
+ uint8_t flags;
+};
+
+struct ldapd_config
+{
+ struct namespace_list namespaces;
+ struct attr_type_tree attr_types;
+ struct oidname_tree attr_names;
+ struct object_tree objects;
+ struct oidname_tree object_names;
+ struct listenerlist listeners;
+ SPLAY_HEAD(ssltree, ssl) *sc_ssl;
+ struct acl acl;
+};
+
+struct ldapd_stats
+{
+ time_t started_at; /* time of daemon startup */
+ unsigned long long requests; /* total number of requests */
+ unsigned long long req_search; /* search requests */
+ unsigned long long req_bind; /* bind requests */
+ unsigned long long req_mod; /* add/mod/del requests */
+ unsigned long long timeouts; /* search timeouts */
+ unsigned long long unindexed; /* unindexed searches */
+ unsigned int conns; /* active connections */
+ unsigned int searches; /* active searches */
+};
+
+struct auth_req
+{
+ int fd;
+ long long msgid;
+ char name[128];
+ char password[128];
+};
+
+struct auth_res
+{
+ int ok;
+ int fd;
+ long long msgid;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK,
+ IMSG_CTL_FAIL,
+ IMSG_CTL_END,
+ IMSG_CTL_STATS,
+ IMSG_CTL_NSSTATS,
+ IMSG_CTL_LOG_VERBOSE,
+ IMSG_CTL_COMPACT,
+ IMSG_CTL_COMPACT_STATUS,
+ IMSG_CTL_INDEX,
+ IMSG_CTL_INDEX_STATUS,
+
+ IMSG_LDAPD_AUTH,
+ IMSG_LDAPD_AUTH_RESULT,
+};
+
+struct ns_stat {
+ char suffix[256];
+ struct btree_stat data_stat;
+ struct btree_stat indx_stat;
+};
+
+enum comp_state {
+ COMPACT_DATA,
+ COMPACT_INDX,
+ COMPACT_DONE
+};
+
+struct compaction_status {
+ char suffix[256];
+ int db;
+ int status;
+};
+
+struct indexer_status {
+ char suffix[256];
+ uint64_t entries;
+ uint64_t ncomplete;
+ int status;
+};
+
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ void *data;
+ short events;
+};
+
+struct ctl_conn;
+typedef void (*ctl_close_func)(struct ctl_conn *);
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ u_int8_t flags;
+#define CTL_CONN_NOTIFY 0x01
+#define CTL_CONN_LOCKED 0x02 /* restricted mode */
+ struct imsgev iev;
+
+ ctl_close_func closecb;
+
+ enum comp_state state;
+ pid_t pid; /* compaction process */
+ struct namespace *ns; /* compacted or indexed */
+ uint64_t ncomplete; /* completed entries */
+ int all; /* 1: traverse all namespaces */
+ struct cursor *cursor;
+ struct event ev;
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+extern struct ctl_connlist ctl_conns;
+
+
+struct control_sock {
+ const char *cs_name;
+ struct event cs_ev;
+ int cs_fd;
+ int cs_restricted;
+};
+
+/* ldapd.c */
+extern struct ldapd_stats stats;
+extern struct ldapd_config *conf;
+
+void fd_nonblock(int fd);
+void imsg_event_add(struct imsgev *iev);
+int imsg_compose_event(struct imsgev *iev, u_int16_t type,
+ u_int32_t peerid, pid_t pid, int fd, void *data,
+ u_int16_t datalen);
+int imsg_event_handle(struct imsgev *iev, short event);
+
+/* compact.c */
+int run_compaction(struct ctl_conn *c,
+ struct namespace *ns);
+void check_compaction(pid_t pid, int status);
+
+/* conn.c */
+extern struct conn_list conn_list;
+struct conn *conn_by_fd(int fd);
+void conn_read(struct bufferevent *bev, void *data);
+void conn_write(struct bufferevent *bev, void *data);
+void conn_err(struct bufferevent *bev, short w, void *data);
+void conn_accept(int fd, short why, void *data);
+void conn_close(struct conn *conn);
+void conn_disconnect(struct conn *conn);
+int request_dispatch(struct request *req);
+void request_free(struct request *req);
+
+/* ldape.c */
+pid_t ldape(struct passwd *pw, char *csockpath,
+ int pipe_parent2ldap[2]);
+int ldap_abandon(struct request *req);
+int ldap_unbind(struct request *req);
+int ldap_extended(struct request *req);
+
+void send_ldap_result(struct conn *conn, int msgid,
+ unsigned long type, long long result_code);
+int ldap_respond(struct request *req, int code);
+
+/* namespace.c
+ */
+struct namespace *namespace_new(const char *suffix);
+int namespace_open(struct namespace *ns);
+int namespace_reopen_data(struct namespace *ns);
+int namespace_reopen_indx(struct namespace *ns);
+struct namespace *namespace_init(const char *suffix, const char *dir);
+void namespace_close(struct namespace *ns);
+void namespace_remove(struct namespace *ns);
+struct ber_element *namespace_get(struct namespace *ns, char *dn);
+int namespace_exists(struct namespace *ns, char *dn);
+int namespace_add(struct namespace *ns, char *dn,
+ struct ber_element *root);
+int namespace_update(struct namespace *ns, char *dn,
+ struct ber_element *root);
+int namespace_del(struct namespace *ns, char *dn);
+struct namespace *namespace_for_base(const char *basedn);
+int namespace_has_index(struct namespace *ns,
+ const char *attr, enum index_type type);
+int namespace_begin(struct namespace *ns);
+int namespace_commit(struct namespace *ns);
+void namespace_abort(struct namespace *ns);
+int namespace_queue_request(struct namespace *ns,
+ struct request *req);
+void namespace_queue_schedule(struct namespace *ns);
+int namespace_should_queue(struct namespace *ns,
+ struct request *req);
+void namespace_cancel_conn(struct conn *conn);
+
+int namespace_ber2db(struct namespace *ns,
+ struct ber_element *root, struct btval *val);
+struct ber_element *namespace_db2ber(struct namespace *ns,
+ struct btval *val);
+
+/* attributes.c */
+struct ber_element *ldap_get_attribute(struct ber_element *root,
+ const char *attr);
+struct ber_element *ldap_find_attribute(struct ber_element *entry,
+ struct attr_type *at);
+struct ber_element *ldap_find_value(struct ber_element *elm,
+ const char *value);
+struct ber_element *ldap_add_attribute(struct ber_element *root,
+ const char *attr, struct ber_element *vals);
+int ldap_set_values(struct ber_element *elm,
+ struct ber_element *vals);
+int ldap_merge_values(struct ber_element *elm,
+ struct ber_element *vals);
+int ldap_del_attribute(struct ber_element *entry,
+ const char *attrdesc);
+int ldap_del_values(struct ber_element *elm,
+ struct ber_element *vals);
+char *ldap_strftime(time_t tm);
+char *ldap_now(void);
+
+/* control.c */
+void control_init(struct control_sock *);
+struct ctl_conn *control_connbypid(pid_t);
+void control_listen(struct control_sock *);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+void control_imsg_forward(struct imsg *);
+void control_cleanup(struct control_sock *);
+void control_end(struct ctl_conn *c);
+void control_report_compaction(struct ctl_conn *c,
+ int status);
+void control_report_indexer( struct ctl_conn *c,
+ int status);
+
+/* filter.c */
+int ldap_matches_filter(struct ber_element *root,
+ struct ber_element *filter);
+
+/* search.c */
+int ldap_search(struct request *req);
+void conn_search(struct search *search);
+void search_close(struct search *search);
+int is_child_of(struct btval *key, const char *base);
+
+/* modify.c */
+int ldap_add(struct request *req);
+int ldap_delete(struct request *req);
+int ldap_modify(struct request *req);
+
+/* auth.c */
+extern struct imsgev *iev_ldapd;
+int ldap_bind(struct request *req);
+void ldap_bind_continue(struct conn *conn, int ok);
+int authorized(struct conn *conn, struct namespace *ns,
+ int rights, char *dn, int scope);
+
+/* parse.y */
+int parse_config(char *filename);
+struct attr_type *lookup_attribute_by_oid(const char *oid);
+struct attr_type *lookup_attribute_by_name(const char *name);
+struct attr_type *lookup_attribute(const char *oid_or_name);
+struct object *lookup_object_by_oid(const char *oid);
+struct object *lookup_object_by_name(const char *name);
+struct object *lookup_object(const char *oid_or_name);
+int cmdline_symset(char *s);
+
+/* log.c */
+void log_init(int);
+void log_verbose(int v);
+void vlog(int, const char *, va_list);
+void logit(int pri, const char *fmt, ...);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+__dead void fatal(const char *);
+__dead void fatalx(const char *);
+const char *print_host(struct sockaddr_storage *ss, char *buf,
+ size_t len);
+
+/* util.c */
+int bsnprintf(char *str, size_t size,
+ const char *format, ...);
+int has_suffix(struct btval *key, const char *suffix);
+int has_prefix(struct btval *key, const char *prefix);
+void normalize_dn(char *dn);
+
+/* index.c */
+int index_namespace(struct namespace *ns);
+int index_entry(struct namespace *ns, struct btval *dn,
+ struct ber_element *elm);
+int unindex_entry(struct namespace *ns, struct btval *dn,
+ struct ber_element *elm);
+int index_attribute(struct namespace *ns, char *attr,
+ struct btval *dn, struct ber_element *a);
+int unindex_attribute(struct namespace *ns, char *attr,
+ struct btval *dn, struct ber_element *a);
+int index_to_dn(struct namespace *ns, struct btval *indx,
+ struct btval *dn);
+int run_indexer(struct ctl_conn *c, struct namespace *ns);
+
+/* ssl.c */
+void ssl_init(void);
+void ssl_transaction(struct conn *);
+
+void ssl_session_init(struct conn *);
+void ssl_session_destroy(struct conn *);
+int ssl_load_certfile(struct ldapd_config *, const char *, u_int8_t);
+void ssl_setup(struct ldapd_config *, struct listener *);
+int ssl_cmp(struct ssl *, struct ssl *);
+SPLAY_PROTOTYPE(ssltree, ssl, ssl_nodes, ssl_cmp);
+
+/* ssl_privsep.c */
+int ssl_ctx_use_private_key(void *, char *, off_t);
+int ssl_ctx_use_certificate_chain(void *, char *, off_t);
+
+/* validate.c */
+int validate_entry(const char *dn, struct ber_element *entry, int relax);
+
+#endif /* _LDAPD_H */
+
diff --git a/usr.sbin/ldapd/ldape.c b/usr.sbin/ldapd/ldape.c
new file mode 100644
index 00000000000..95b70ac93c6
--- /dev/null
+++ b/usr.sbin/ldapd/ldape.c
@@ -0,0 +1,360 @@
+/* $OpenBSD: ldape.c,v 1.1 2010/05/31 17:36:31 martinh 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+void ldape_sig_handler(int fd, short why, void *data);
+void ldape_dispatch_ldapd(int fd, short event, void *ptr);
+
+int ldap_starttls(struct request *req);
+void send_ldap_extended_response(struct conn *conn,
+ int msgid, unsigned long type,
+ long long result_code,
+ const char *extended_oid);
+
+struct imsgev *iev_ldapd;
+struct control_sock csock;
+
+void
+ldape_sig_handler(int sig, short why, void *data)
+{
+ log_debug("ldape: got signal %d", sig);
+ if (sig == SIGCHLD) {
+ for (;;) {
+ pid_t pid;
+ int status;
+
+ pid = waitpid(WAIT_ANY, &status, WNOHANG);
+ if (pid <= 0)
+ break;
+ check_compaction(pid, status);
+ }
+ return;
+ }
+
+ event_loopexit(NULL);
+}
+
+void
+send_ldap_extended_response(struct conn *conn, int msgid, unsigned long type,
+ long long result_code, const char *extended_oid)
+{
+ int rc;
+ struct ber_element *root, *elm;
+ void *buf;
+
+ log_debug("sending response %u with result %d", type, result_code);
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+
+ elm = ber_printf_elements(root, "d{tEss",
+ msgid, BER_CLASS_APP, type, result_code, "", "");
+ if (elm == NULL)
+ goto fail;
+
+ if (extended_oid)
+ elm = ber_add_string(elm, extended_oid);
+
+ rc = ber_write_elements(&conn->ber, root);
+ ber_free_elements(root);
+
+ if (rc < 0)
+ log_warn("failed to create ldap result");
+ else {
+ ber_get_writebuf(&conn->ber, &buf);
+ if (bufferevent_write(conn->bev, buf, rc) != 0)
+ log_warn("failed to send ldap result");
+ }
+
+ return;
+fail:
+ if (root)
+ ber_free_elements(root);
+}
+
+void
+send_ldap_result(struct conn *conn, int msgid, unsigned long type,
+ long long result_code)
+{
+ send_ldap_extended_response(conn, msgid, type, result_code, NULL);
+}
+
+int
+ldap_respond(struct request *req, int code)
+{
+ if (code >= 0)
+ send_ldap_result(req->conn, req->msgid, req->type + 1, code);
+ request_free(req);
+ return code;
+}
+
+int
+ldap_abandon(struct request *req)
+{
+ long long msgid;
+ struct search *search;
+
+ if (ber_scanf_elements(req->op, "i", &msgid) != 0) {
+ request_free(req);
+ return -1; /* protocol error, but don't respond */
+ }
+
+ TAILQ_FOREACH(search, &req->conn->searches, next) {
+ if (search->req->msgid == msgid) {
+ /* unlinks the search from conn->searches */
+ search_close(search);
+ break;
+ }
+ }
+ request_free(req);
+ return -1;
+}
+
+int
+ldap_unbind(struct request *req)
+{
+ log_debug("current bind dn = %s", req->conn->binddn);
+ conn_disconnect(req->conn);
+ request_free(req);
+ return -1; /* don't send any response */
+}
+
+int
+ldap_starttls(struct request *req)
+{
+ req->conn->s_flags |= F_STARTTLS;
+ return LDAP_SUCCESS;
+}
+
+int
+ldap_extended(struct request *req)
+{
+ int i, rc = LDAP_PROTOCOL_ERROR;
+ char *oid = NULL;
+ struct ber_element *ext_val = NULL;
+ struct {
+ const char *oid;
+ int (*fn)(struct request *);
+ } extended_ops[] = {
+ { "1.3.6.1.4.1.1466.20037", ldap_starttls },
+ { NULL }
+ };
+
+ if (ber_scanf_elements(req->op, "{se", &oid, &ext_val) != 0)
+ goto done;
+
+ log_debug("got extended operation %s", oid);
+ req->op = ext_val;
+
+ for (i = 0; extended_ops[i].oid != NULL; i++) {
+ if (strcmp(oid, extended_ops[i].oid) == 0) {
+ rc = extended_ops[i].fn(req);
+ break;
+ }
+ }
+
+ if (extended_ops[i].fn == NULL)
+ log_warnx("unimplemented extended operation %s", oid);
+
+done:
+ send_ldap_extended_response(req->conn, req->msgid, LDAP_RES_EXTENDED,
+ rc, oid);
+
+ request_free(req);
+ return 0;
+}
+
+pid_t
+ldape(struct passwd *pw, char *csockpath, int pipe_parent2ldap[2])
+{
+ int on = 1;
+ pid_t pid;
+ struct namespace *ns;
+ struct listener *l;
+ struct sockaddr_un *sun = NULL;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+ char host[128];
+
+ TAILQ_INIT(&conn_list);
+
+ pid = fork();
+ if (pid < 0)
+ fatal("ldape: fork");
+ if (pid > 0)
+ return pid;
+
+ setproctitle("ldap server");
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, ldape_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, ldape_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, ldape_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, ldape_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ close(pipe_parent2ldap[0]);
+
+ /* Initialize parent imsg events. */
+ if ((iev_ldapd = calloc(1, sizeof(struct imsgev))) == NULL)
+ fatal("calloc");
+ imsg_init(&iev_ldapd->ibuf, pipe_parent2ldap[1]);
+ iev_ldapd->handler = ldape_dispatch_ldapd;
+ imsg_event_add(iev_ldapd);
+
+ /* Initialize control socket. */
+ bzero(&csock, sizeof(csock));
+ csock.cs_name = csockpath;
+ control_init(&csock);
+ control_listen(&csock);
+ TAILQ_INIT(&ctl_conns);
+
+ /* Initialize LDAP listeners.
+ */
+ TAILQ_FOREACH(l, &conf->listeners, entry) {
+ l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0);
+ if (l->fd < 0)
+ fatal("ldape: socket");
+
+ setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ if (l->ss.ss_family == AF_UNIX) {
+ sun = (struct sockaddr_un *)&l->ss;
+ log_info("listening on %s", sun->sun_path);
+ if (unlink(sun->sun_path) == -1 && errno != ENOENT)
+ fatal("ldape: unlink");
+ } else {
+ print_host(&l->ss, host, sizeof(host));
+ log_info("listening on %s:%d", host, ntohs(l->port));
+ }
+
+ if (bind(l->fd, (struct sockaddr *)&l->ss, l->ss.ss_len) != 0)
+ fatal("ldape: bind");
+ if (listen(l->fd, 20) != 0)
+ fatal("ldape: listen");
+
+ if (l->ss.ss_family == AF_UNIX) {
+ mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ if (chmod(sun->sun_path, mode) == -1) {
+ unlink(sun->sun_path);
+ fatal("ldape: chmod");
+ }
+ }
+
+ fd_nonblock(l->fd);
+
+ event_set(&l->ev, l->fd, EV_READ|EV_PERSIST, conn_accept, l);
+ event_add(&l->ev, NULL);
+
+ ssl_setup(conf, l);
+ }
+
+ if (pw != NULL) {
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("cannot drop privileges");
+ }
+
+ TAILQ_FOREACH(ns, &conf->namespaces, next) {
+ if (namespace_open(ns) != 0)
+ fatal(ns->suffix);
+ }
+
+ log_debug("ldape: entering event loop");
+ event_dispatch();
+
+ while ((ns = TAILQ_FIRST(&conf->namespaces)) != NULL)
+ namespace_remove(ns);
+
+ control_cleanup(&csock);
+
+ log_info("ldape: exiting");
+ _exit(0);
+}
+
+void
+ldape_dispatch_ldapd(int fd, short event, void *ptr)
+{
+ struct imsgev *iev = ptr;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ if (imsg_event_handle(iev, event) != 0)
+ return;
+
+ ibuf = &iev->ibuf;
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("ldape_dispatch_ldapd: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_LDAPD_AUTH_RESULT: {
+ struct conn *conn;
+ struct auth_res *ares;
+
+ ares = imsg.data;
+ log_debug("authentication on conn %i/%lld = %d",
+ ares->fd, ares->msgid, ares->ok);
+ conn = conn_by_fd(ares->fd);
+ if (conn->bind_req &&
+ conn->bind_req->msgid == ares->msgid)
+ ldap_bind_continue(conn, ares->ok);
+ else
+ log_warnx("spurious auth result");
+ break;
+ }
+ default:
+ log_debug("ldape_dispatch_ldapd: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(iev);
+}
+
diff --git a/usr.sbin/ldapd/log.c b/usr.sbin/ldapd/log.c
new file mode 100644
index 00000000000..4f27184409d
--- /dev/null
+++ b/usr.sbin/ldapd/log.c
@@ -0,0 +1,172 @@
+/* $OpenBSD: log.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 MIND, 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 <sys/socket.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "ldapd.h"
+
+int debug;
+int verbose;
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+log_verbose(int v)
+{
+ verbose = v;
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
+
+const char *
+print_host(struct sockaddr_storage *ss, char *buf, size_t len)
+{
+ if (getnameinfo((struct sockaddr *)ss, ss->ss_len,
+ buf, len, NULL, 0, NI_NUMERICHOST) != 0) {
+ buf[0] = '\0';
+ return (NULL);
+ }
+ return (buf);
+}
diff --git a/usr.sbin/ldapd/modify.c b/usr.sbin/ldapd/modify.c
new file mode 100644
index 00000000000..90a944025c7
--- /dev/null
+++ b/usr.sbin/ldapd/modify.c
@@ -0,0 +1,237 @@
+/* $OpenBSD: modify.c,v 1.1 2010/05/31 17:36:31 martinh 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 <stdlib.h>
+#include <string.h>
+
+#include "ldapd.h"
+#include "uuid.h"
+
+int
+ldap_delete(struct request *req)
+{
+ char *dn;
+ struct namespace *ns;
+
+ ++stats.req_mod;
+
+ if (ber_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)
+ return ldap_respond(req, LDAP_NAMING_VIOLATION);
+
+ if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE))
+ return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
+
+ if (namespace_should_queue(ns, req)) {
+ if (namespace_queue_request(ns, req) != 0)
+ return ldap_respond(req, LDAP_BUSY);
+ return LDAP_BUSY;
+ }
+
+ switch (namespace_del(ns, dn)) {
+ case BT_NOTFOUND:
+ return ldap_respond(req, LDAP_NO_SUCH_OBJECT);
+ case BT_SUCCESS:
+ return ldap_respond(req, LDAP_SUCCESS);
+ default:
+ return ldap_respond(req, LDAP_OTHER);
+ }
+}
+
+int
+ldap_add(struct request *req)
+{
+ char uuid_str[64];
+ struct uuid uuid;
+ char *dn;
+ struct ber_element *attrs, *set;
+ struct namespace *ns;
+ int rc;
+
+ ++stats.req_mod;
+
+ if (ber_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)
+ return ldap_respond(req, LDAP_NAMING_VIOLATION);
+
+ if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE) != 0)
+ return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
+
+ if (namespace_should_queue(ns, req)) {
+ if (namespace_queue_request(ns, req) != 0)
+ return ldap_respond(req, LDAP_BUSY);
+ return LDAP_BUSY;
+ }
+
+ /* add operational attributes
+ */
+ set = ber_add_set(NULL);
+ ber_add_string(set, req->conn->binddn ?: "");
+ ldap_add_attribute(attrs, "creatorsName", set);
+
+ set = ber_add_set(NULL);
+ ber_add_string(set, ldap_now());
+ ldap_add_attribute(attrs, "createTimestamp", set);
+
+ uuid_create(&uuid);
+ uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
+ set = ber_add_set(NULL);
+ ber_add_string(set, uuid_str);
+ ldap_add_attribute(attrs, "entryUUID", set);
+
+ if ((rc = validate_entry(dn, attrs, ns->relax)) != LDAP_SUCCESS)
+ return ldap_respond(req, rc);
+
+ switch (namespace_add(ns, dn, attrs)) {
+ case BT_SUCCESS:
+ return ldap_respond(req, LDAP_SUCCESS);
+ case BT_EXISTS:
+ return ldap_respond(req, LDAP_ALREADY_EXISTS);
+ default:
+ return ldap_respond(req, LDAP_OTHER);
+ }
+}
+
+int
+ldap_modify(struct request *req)
+{
+ int rc;
+ char *dn;
+ long long op;
+ const char *attr;
+ struct ber_element *mods, *entry, *mod, *vals, *a, *set;
+ struct namespace *ns;
+ struct attr_type *at;
+
+ ++stats.req_mod;
+
+ if (ber_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)
+ return ldap_respond(req, LDAP_NAMING_VIOLATION);
+
+ if (!authorized(req->conn, ns, ACI_WRITE, dn, LDAP_SCOPE_BASE) != 0)
+ return ldap_respond(req, LDAP_INSUFFICIENT_ACCESS);
+
+ if (namespace_should_queue(ns, req)) {
+ if (namespace_queue_request(ns, req) != 0)
+ return ldap_respond(req, LDAP_BUSY);
+ return LDAP_BUSY;
+ }
+
+ if ((entry = namespace_get(ns, dn)) == NULL)
+ return ldap_respond(req, LDAP_NO_SUCH_OBJECT);
+
+ for (mod = mods->be_sub; mod; mod = mod->be_next) {
+ if (ber_scanf_elements(mod, "{E{se", &op, &attr, &vals) != 0)
+ return ldap_respond(req, LDAP_PROTOCOL_ERROR);
+
+ if ((at = lookup_attribute(attr)) == NULL && !ns->relax) {
+ log_debug("unknown attribute type %s", attr);
+ return ldap_respond(req, LDAP_NO_SUCH_ATTRIBUTE);
+ }
+ if (at != NULL && at->immutable) {
+ log_debug("attempt to modify immutable attribute %s",
+ attr);
+ return ldap_respond(req, LDAP_CONSTRAINT_VIOLATION);
+ }
+ if (at != NULL && at->usage != USAGE_USER_APP) {
+ log_debug("attempt to modify operational attribute %s",
+ attr);
+ return ldap_respond(req, LDAP_CONSTRAINT_VIOLATION);
+ }
+
+ a = ldap_get_attribute(entry, attr);
+
+ switch (op) {
+ case LDAP_MOD_ADD:
+ if (a == NULL)
+ ldap_add_attribute(entry, attr, vals);
+ else
+ ldap_merge_values(a, vals);
+ break;
+ case LDAP_MOD_DELETE:
+ if (vals->be_sub &&
+ vals->be_sub->be_type == BER_TYPE_SET)
+ ldap_del_values(a, vals);
+ else
+ ldap_del_attribute(entry, attr);
+ break;
+ case LDAP_MOD_REPLACE:
+ if (vals->be_sub) {
+ if (a == NULL)
+ ldap_add_attribute(entry, attr, vals);
+ else
+ ldap_set_values(a, vals);
+ } else if (a == NULL)
+ ldap_del_attribute(entry, attr);
+ break;
+ }
+ }
+
+ if ((rc = validate_entry(dn, entry, ns->relax)) != LDAP_SUCCESS)
+ return ldap_respond(req, rc);
+
+ set = ber_add_set(NULL);
+ ber_add_string(set, req->conn->binddn ?: "");
+ if ((a = ldap_get_attribute(entry, "modifiersName")) != NULL)
+ ldap_set_values(a, set);
+ else
+ ldap_add_attribute(entry, "modifiersName", set);
+
+ set = ber_add_set(NULL);
+ ber_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);
+
+ switch (namespace_update(ns, dn, entry)) {
+ case BT_SUCCESS:
+ return ldap_respond(req, LDAP_SUCCESS);
+ case BT_EXISTS:
+ return ldap_respond(req, LDAP_ALREADY_EXISTS);
+ default:
+ return ldap_respond(req, LDAP_OTHER);
+ }
+}
+
diff --git a/usr.sbin/ldapd/namespace.c b/usr.sbin/ldapd/namespace.c
new file mode 100644
index 00000000000..c8f9c88c839
--- /dev/null
+++ b/usr.sbin/ldapd/namespace.c
@@ -0,0 +1,594 @@
+/* $OpenBSD: namespace.c,v 1.1 2010/05/31 17:36:31 martinh 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "ldapd.h"
+
+/* 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);
+
+struct namespace *
+namespace_new(const char *suffix)
+{
+ struct namespace *ns;
+
+ if ((ns = calloc(1, sizeof(*ns))) == NULL)
+ return NULL;
+ ns->suffix = strdup(suffix);
+ ns->sync = 1;
+ if (ns->suffix == NULL) {
+ free(ns->suffix);
+ return NULL;
+ }
+ TAILQ_INIT(&ns->indices);
+ TAILQ_INIT(&ns->request_queue);
+ SIMPLEQ_INIT(&ns->acl);
+
+ return ns;
+}
+
+int
+namespace_begin(struct namespace *ns)
+{
+ if (ns->data_txn != NULL)
+ return -1;
+
+ if ((ns->data_txn = btree_txn_begin(ns->data_db, 0)) == NULL ||
+ (ns->indx_txn = btree_txn_begin(ns->indx_db, 0)) == NULL) {
+ btree_txn_abort(ns->data_txn);
+ ns->data_txn = NULL;
+ return -1;
+ }
+
+ return 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_data.db", ns->suffix) < 0)
+ 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_indx.db", ns->suffix) < 0)
+ 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;
+}
+
+int
+namespace_reopen_data(struct namespace *ns)
+{
+ uint32_t old_flags;
+
+ log_info("reopening namespace %s (entries)", ns->suffix);
+ old_flags = btree_get_flags(ns->data_db);
+ btree_close(ns->data_db);
+ ns->data_db = btree_open(ns->data_path, old_flags, 0644);
+ if (ns->data_db == NULL)
+ return -1;
+
+ return 0;
+}
+
+int
+namespace_reopen_indx(struct namespace *ns)
+{
+ uint32_t old_flags;
+
+ log_info("reopening namespace %s (index)", ns->suffix);
+ old_flags = btree_get_flags(ns->indx_db);
+ btree_close(ns->indx_db);
+ ns->indx_db = btree_open(ns->indx_path, old_flags, 0644);
+ if (ns->indx_db == NULL)
+ return -1;
+
+ return 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;
+
+ bzero(&key, sizeof(key));
+ bzero(&val, sizeof(val));
+
+ key.data = dn;
+ key.size = strlen(dn);
+
+ switch (btree_get(ns->data_db, &key, &val)) {
+ case BT_FAIL:
+ log_warn("%s", dn);
+ return NULL;
+ case BT_NOTFOUND:
+ log_debug("%s: dn not found", dn);
+ 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)
+{
+ int rc;
+ ssize_t len;
+ uLongf destlen;
+ Bytef *dest;
+ void *buf;
+ struct ber ber;
+
+ bzero(val, sizeof(*val));
+
+ bzero(&ber, sizeof(ber));
+ ber.fd = -1;
+ ber_write_elements(&ber, root);
+
+ if ((len = ber_get_writebuf(&ber, &buf)) == -1)
+ return -1;
+
+ if (ns->compression_level > 0) {
+ val->size = compressBound(len);
+ val->data = malloc(val->size + sizeof(uint32_t));
+ if (val->data == NULL) {
+ log_warn("malloc(%u)", val->size + sizeof(uint32_t));
+ ber_free(&ber);
+ return -1;
+ }
+ dest = (char *)val->data + sizeof(uint32_t);
+ destlen = val->size - sizeof(uint32_t);
+ if ((rc = compress2(dest, &destlen, buf, len,
+ ns->compression_level)) != Z_OK) {
+ log_warn("compress returned %i", rc);
+ free(val->data);
+ ber_free(&ber);
+ return -1;
+ }
+ log_debug("compressed entry from %u -> %u byte",
+ len, destlen + sizeof(uint32_t));
+
+ *(uint32_t *)val->data = len;
+ val->size = destlen + sizeof(uint32_t);
+ val->free_data = 1;
+ } else {
+ val->data = buf;
+ val->size = len;
+ val->free_data = 1; /* XXX: take over internal br_wbuf */
+ ber.br_wbuf = NULL;
+ }
+
+ ber_free(&ber);
+
+ return 0;
+}
+
+struct ber_element *
+namespace_db2ber(struct namespace *ns, struct btval *val)
+{
+ int rc;
+ uLongf len;
+ void *buf;
+ Bytef *src;
+ uLong srclen;
+ struct ber_element *elm;
+ struct ber ber;
+
+ assert(ns != NULL);
+ assert(val != NULL);
+
+ bzero(&ber, sizeof(ber));
+ ber.fd = -1;
+
+ if (ns->compression_level > 0) {
+ if (val->size < sizeof(uint32_t))
+ return NULL;
+
+ len = *(uint32_t *)val->data;
+ if ((buf = malloc(len)) == NULL) {
+ log_warn("malloc(%u)", len);
+ return NULL;
+ }
+
+ src = (char *)val->data + sizeof(uint32_t);
+ srclen = val->size - sizeof(uint32_t);
+ rc = uncompress(buf, &len, src, srclen);
+ if (rc != Z_OK) {
+ log_warnx("dbt_to_ber: uncompress returned %i", rc);
+ free(buf);
+ return NULL;
+ }
+
+ log_debug("uncompressed entry from %u -> %u byte",
+ val->size, len);
+
+ ber_set_readbuf(&ber, buf, len);
+ elm = ber_read_elements(&ber, NULL);
+ free(buf);
+ return elm;
+ } else {
+ ber_set_readbuf(&ber, val->data, val->size);
+ return ber_read_elements(&ber, NULL);
+ }
+}
+
+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);
+
+ bzero(&key, sizeof(key));
+ key.data = dn;
+ key.size = strlen(dn);
+
+ if (namespace_ber2db(ns, root, &val) != 0)
+ return BT_FAIL;
+
+ if (namespace_begin(ns) != 0)
+ return BT_FAIL;
+
+ rc = btree_txn_put(ns->data_db, ns->data_txn, &key, &val,
+ update ? 0 : BT_NOOVERWRITE);
+ if (rc != BT_SUCCESS) {
+ if (rc == BT_EXISTS)
+ log_debug("%s: already exists", dn);
+ else
+ log_warn("%s", dn);
+ goto fail;
+ }
+
+ /* FIXME: if updating, try harder to just update changed indices.
+ */
+ if (update && unindex_entry(ns, &key, root) != BT_SUCCESS)
+ goto fail;
+
+ if (index_entry(ns, &key, root) != 0)
+ goto fail;
+
+ if (namespace_commit(ns) != BT_SUCCESS)
+ goto fail;
+
+ btval_reset(&val);
+ return 0;
+
+fail:
+ namespace_abort(ns);
+ 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);
+
+ bzero(&key, sizeof(key));
+ bzero(&data, sizeof(data));
+
+ key.data = dn;
+ key.size = strlen(key.data);
+
+ if (namespace_begin(ns) != 0)
+ return BT_FAIL;
+
+ rc = btree_txn_del(ns->data_db, ns->data_txn, &key, &data);
+ if (rc != BT_SUCCESS)
+ goto fail;
+
+ if ((root = namespace_db2ber(ns, &data)) == NULL ||
+ (rc = unindex_entry(ns, &key, root)) != 0)
+ goto fail;
+
+ if (btree_txn_commit(ns->data_txn) != BT_SUCCESS)
+ goto fail;
+ ns->data_txn = NULL;
+ if (btree_txn_commit(ns->indx_txn) != BT_SUCCESS)
+ goto fail;
+ ns->indx_txn = NULL;
+
+ btval_reset(&data);
+ return 0;
+
+fail:
+ namespace_abort(ns);
+ btval_reset(&data);
+
+ return rc;
+}
+
+struct namespace *
+namespace_for_base(const char *basedn)
+{
+ size_t blen, slen;
+ struct namespace *ns;
+
+ assert(basedn);
+ blen = strlen(basedn);
+
+ TAILQ_FOREACH(ns, &conf->namespaces, next) {
+ slen = strlen(ns->suffix);
+ if (blen >= slen &&
+ bcmp(basedn + blen - slen, ns->suffix, slen) == 0)
+ return ns;
+ }
+
+ 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 compacted.
+ */
+int
+namespace_queue_request(struct namespace *ns, struct request *req)
+{
+ if (ns->queued_requests > MAX_REQUEST_QUEUE) {
+ log_warn("%u requests alreay queued, sorry");
+ return -1;
+ }
+
+ TAILQ_INSERT_TAIL(&ns->request_queue, req, next);
+ ns->queued_requests++;
+ return 0;
+}
+
+static void
+namespace_queue_replay(int fd, short event, void *data)
+{
+ struct namespace *ns = data;
+ struct request *req;
+
+ if ((req = TAILQ_FIRST(&ns->request_queue)) == NULL)
+ return;
+ TAILQ_REMOVE(&ns->request_queue, req, next);
+
+ req->replayed = 1;
+ request_dispatch(req);
+ ns->queued_requests--;
+
+ if (!ns->compacting)
+ namespace_queue_schedule(ns);
+}
+
+void
+namespace_queue_schedule(struct namespace *ns)
+{
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_add(&ns->ev_queue, &tv);
+}
+
+int
+namespace_should_queue(struct namespace *ns, struct request *req)
+{
+ if (ns->compacting) {
+ log_debug("namespace %s is being compacted", ns->suffix);
+ return 1;
+ }
+
+ if (ns->queued_requests > 0 && !req->replayed) {
+ log_debug("namespace %s is being replayed", ns->suffix);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* 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 != TAILQ_END(&ns->request_queue); req = next) {
+ next = TAILQ_NEXT(req, next);
+
+ if (req->conn == conn) {
+ TAILQ_REMOVE(&ns->request_queue, req, next);
+ request_free(req);
+ }
+ }
+ }
+}
+
diff --git a/usr.sbin/ldapd/parse.y b/usr.sbin/ldapd/parse.y
new file mode 100644
index 00000000000..fd201cd420a
--- /dev/null
+++ b/usr.sbin/ldapd/parse.y
@@ -0,0 +1,1529 @@
+/* $OpenBSD: parse.y,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 Martin Hedenfalk <martin@bzero.se>
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "ldapd.h"
+
+static int
+attr_oid_cmp(struct attr_type *a, struct attr_type *b)
+{
+ return strcasecmp(a->oid, b->oid);
+}
+
+static int
+obj_oid_cmp(struct object *a, struct object *b)
+{
+ return strcasecmp(a->oid, b->oid);
+}
+
+static int
+oidname_cmp(struct oidname *a, struct oidname *b)
+{
+ return strcasecmp(a->on_name, b->on_name);
+}
+
+RB_GENERATE(attr_type_tree, attr_type, link, attr_oid_cmp);
+RB_GENERATE(object_tree, object, link, obj_oid_cmp);
+RB_GENERATE(oidname_tree, oidname, link, oidname_cmp);
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file, *topfile;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+struct listener *host_unix(const char *path);
+struct listener *host_v4(const char *, in_port_t);
+struct listener *host_v6(const char *, in_port_t);
+int host_dns(const char *, const char *,
+ struct listenerlist *, int, in_port_t, u_int8_t);
+int host(const char *, const char *,
+ struct listenerlist *, int, in_port_t, u_int8_t);
+int interface(const char *, const char *,
+ struct listenerlist *, int, in_port_t, u_int8_t);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+struct ldapd_config *conf;
+
+static struct attr_list *append_attr(struct attr_list *alist,
+ struct attr_type *a);
+static struct obj_list *append_obj(struct obj_list *olist, struct object *obj);
+int is_oidstr(const char *oidstr);
+static struct name_list *append_name(struct name_list *nl, const char *name);
+static struct aci *mk_aci(int type, int rights, enum scope scope,
+ char *target, char *subject);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ struct attr_type *attr;
+ struct attr_list *attrlist;
+ struct object *obj;
+ struct obj_list *objlist;
+ struct name_list *namelist;
+ struct {
+ int type;
+ void *data;
+ long long value;
+ } data;
+ struct aci *aci;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+static struct attr_type *cattr = NULL;
+static struct object *cobj = NULL;
+static struct namespace *cns = NULL;
+
+%}
+
+%token ERROR LISTEN ON TLS LDAPS PORT NAMESPACE ROOTDN ROOTPW INDEX
+%token SUP SYNTAX EQUALITY ORDERING SUBSTR OBSOLETE NAME SINGLE_VALUE
+%token DESC USAGE ATTRIBUTETYPE NO_USER_MOD COLLECTIVE
+%token INCLUDE CERTIFICATE FSYNC CACHE_SIZE INDEX_CACHE_SIZE
+%token OBJECTCLASS MUST MAY SECURE RELAX STRICT SCHEMA
+%token ABSTRACT AUXILIARY STRUCTURAL USE COMPRESSION LEVEL
+%token USER_APPLICATIONS DIRECTORY_OPERATION
+%token DISTRIBUTED_OPERATION DSA_OPERATION
+%token DENY ALLOW READ WRITE BIND ACCESS TO ROOT
+%token ANY CHILDREN OF ATTRIBUTE IN SUBTREE BY SELF
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.string> numericoid oidlen
+%type <v.string> qdescr certname
+%type <v.number> usage kind port ssl boolean
+%type <v.attrlist> attrptrs attrlist
+%type <v.attr> attrptr
+%type <v.objlist> objptrs objlist
+%type <v.obj> objptr
+%type <v.namelist> qdescrs qdescrlist
+%type <v.number> aci_type aci_access aci_rights aci_right aci_scope
+%type <v.string> aci_target aci_subject
+%type <v.aci> aci
+%type <v.number> comp_level
+
+%%
+
+grammar : /* empty */
+ | grammar include
+ | grammar attr_type
+ | grammar object
+ | grammar varset
+ | grammar conf_main
+ | grammar database
+ | grammar aci {
+ SIMPLEQ_INSERT_TAIL(&conf->acl, $2, entry);
+ }
+ | grammar error { file->errors++; }
+ ;
+
+ssl : /* empty */ { $$ = 0; }
+ | TLS { $$ = F_STARTTLS; }
+ | LDAPS { $$ = F_LDAPS; }
+ | SECURE { $$ = F_SECURE; }
+ ;
+
+certname : /* empty */ { $$ = NULL; }
+ | CERTIFICATE STRING { $$ = $2; }
+ ;
+
+port : PORT STRING {
+ struct servent *servent;
+
+ servent = getservbyname($2, "tcp");
+ if (servent == NULL) {
+ yyerror("port %s is invalid", $2);
+ free($2);
+ YYERROR;
+ }
+ $$ = servent->s_port;
+ free($2);
+ }
+ | PORT NUMBER {
+ if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+ yyerror("invalid port: %lld", $2);
+ YYERROR;
+ }
+ $$ = htons($2);
+ }
+ | /* empty */ {
+ $$ = 0;
+ }
+ ;
+
+conf_main : LISTEN ON STRING port ssl certname {
+ char *cert;
+
+ if ($4 == 0) {
+ if ($5 == F_LDAPS)
+ $4 = htons(LDAPS_PORT);
+ else
+ $4 = htons(LDAP_PORT);
+ }
+
+ cert = ($6 != NULL) ? $6 : $3;
+
+ if (($5 == F_STARTTLS || $5 == F_LDAPS) &&
+ ssl_load_certfile(conf, cert, F_SCERT) < 0) {
+ yyerror("cannot load certificate: %s", cert);
+ free($6);
+ free($3);
+ YYERROR;
+ }
+
+ if (! interface($3, cert, &conf->listeners,
+ MAX_LISTEN, $4, $5)) {
+ if (host($3, cert, &conf->listeners,
+ MAX_LISTEN, $4, $5) <= 0) {
+ yyerror("invalid virtual ip or interface: %s", $3);
+ free($6);
+ free($3);
+ YYERROR;
+ }
+ }
+ free($6);
+ free($3);
+ }
+ ;
+
+database : NAMESPACE STRING '{' {
+ cns = namespace_new($2);
+ free($2);
+ TAILQ_INSERT_TAIL(&conf->namespaces, cns, next);
+ } ns_opts '}' {
+ cns = NULL;
+ }
+ ;
+
+boolean : STRING {
+ if (strcasecmp($1, "true") == 0 ||
+ strcasecmp($1, "yes") == 0)
+ $$ = 1;
+ else if (strcasecmp($1, "false") == 0 ||
+ strcasecmp($1, "off") == 0 ||
+ strcasecmp($1, "no") == 0)
+ $$ = 0;
+ else {
+ yyerror("invalid boolean value '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | ON { $$ = 1; }
+ ;
+
+ns_opts : /* empty */
+ | ns_opts ROOTDN STRING {
+ cns->rootdn = $3;
+ normalize_dn(cns->rootdn);
+ }
+ | ns_opts ROOTPW STRING { cns->rootpw = $3; }
+ | ns_opts INDEX STRING {
+ struct attr_index *ai;
+ if ((ai = calloc(1, sizeof(*ai))) == NULL) {
+ yyerror("calloc");
+ free($3);
+ YYERROR;
+ }
+ ai->attr = $3;
+ ai->type = INDEX_EQUAL;
+ TAILQ_INSERT_TAIL(&cns->indices, ai, next);
+ }
+ | ns_opts CACHE_SIZE NUMBER {
+ cns->cache_size = $3;
+ }
+ | ns_opts INDEX_CACHE_SIZE NUMBER {
+ cns->index_cache_size = $3;
+ }
+ | ns_opts FSYNC boolean {
+ cns->sync = $3;
+ }
+ | ns_opts aci {
+ SIMPLEQ_INSERT_TAIL(&cns->acl, $2, entry);
+ }
+ | ns_opts RELAX SCHEMA {
+ cns->relax = 1;
+ }
+ | ns_opts STRICT SCHEMA {
+ cns->relax = 0;
+ }
+ | ns_opts USE COMPRESSION comp_level {
+ cns->compression_level = $4;
+ }
+ ;
+
+comp_level : /* empty */ { $$ = 6; }
+ | LEVEL NUMBER { $$ = $2; }
+ ;
+
+aci : aci_type aci_access TO aci_scope aci_target aci_subject {
+ if (($$ = mk_aci($1, $2, $4, $5, $6)) == NULL) {
+ free($5);
+ free($6);
+ YYERROR;
+ }
+ }
+ | aci_type aci_access {
+ if (($$ = mk_aci($1, $2, LDAP_SCOPE_SUBTREE, NULL,
+ NULL)) == NULL) {
+ YYERROR;
+ }
+ }
+ ;
+
+aci_type : DENY { $$ = ACI_DENY; }
+ | ALLOW { $$ = ACI_ALLOW; }
+ ;
+
+aci_access : /* empty */ { $$ = ACI_ALL; };
+ | ACCESS { $$ = ACI_ALL; };
+ | aci_rights ACCESS { $$ = $1; };
+ ;
+
+aci_rights : aci_right { $$ = $1; }
+ | aci_rights ',' aci_right { $$ = $1 | $3; }
+ ;
+
+aci_right : READ { $$ = ACI_READ; };
+ | WRITE { $$ = ACI_WRITE; };
+ | BIND { $$ = ACI_BIND; };
+ ;
+
+
+aci_scope : /* empty */ { $$ = LDAP_SCOPE_BASE; }
+ | SUBTREE { $$ = LDAP_SCOPE_SUBTREE; }
+ | CHILDREN OF { $$ = LDAP_SCOPE_ONELEVEL; }
+ ;
+
+aci_target : ANY { $$ = NULL; }
+ | ROOT { $$ = strdup(""); }
+ | STRING { $$ = $1; normalize_dn($$); }
+ ;
+
+aci_subject : /* empty */ { $$ = NULL; }
+ | BY ANY { $$ = NULL; }
+ | BY STRING { $$ = $2; normalize_dn($$); }
+ | BY SELF { $$ = strdup("@"); }
+ ;
+
+attr_type : ATTRIBUTETYPE '(' numericoid {
+ struct attr_type *p;
+
+ if ((cattr = calloc(1, sizeof(*cattr))) == NULL) {
+ yyerror("calloc");
+ free($3);
+ YYERROR;
+ }
+ cattr->oid = $3;
+ p = RB_INSERT(attr_type_tree, &conf->attr_types, cattr);
+ if (p != NULL) {
+ yyerror("attribute '%s' already defined", $3);
+ free($3);
+ free(cattr);
+ cattr = NULL;
+ YYERROR;
+ }
+ } attr_data ')' {
+ cattr = NULL;
+ }
+ ;
+
+attr_data : /* empty */
+ | attr_data NAME qdescrs { cattr->names = $3; }
+ | attr_data DESC STRING { cattr->desc = $3; }
+ | attr_data OBSOLETE { cattr->obsolete = 1; }
+ | attr_data SUP STRING {
+ cattr->sup = lookup_attribute($3);
+ if (cattr->sup == NULL)
+ yyerror("%s: no such attribute", $3);
+ free($3);
+ if (cattr->sup == NULL)
+ YYERROR;
+ }
+ | attr_data EQUALITY STRING { cattr->equality = $3; }
+ | attr_data ORDERING STRING { cattr->ordering = $3; }
+ | attr_data SUBSTR STRING { cattr->substr = $3; }
+ | attr_data SYNTAX oidlen { cattr->syntax = $3; }
+ | attr_data SINGLE_VALUE { cattr->single = 1; }
+ | attr_data COLLECTIVE { cattr->collective = 1; }
+ | attr_data NO_USER_MOD { cattr->immutable = 1; }
+ | attr_data USAGE usage { cattr->usage = $3; }
+ ;
+
+usage : USER_APPLICATIONS { $$ = USAGE_USER_APP; }
+ | DIRECTORY_OPERATION { $$ = USAGE_DIR_OP; }
+ | DISTRIBUTED_OPERATION { $$ = USAGE_DIST_OP; }
+ | DSA_OPERATION { $$ = USAGE_DSA_OP; }
+ ;
+
+object : OBJECTCLASS '(' numericoid {
+ struct object *p;
+
+ if ((cobj = calloc(1, sizeof(*cobj))) == NULL) {
+ yyerror("calloc");
+ free($3);
+ YYERROR;
+ }
+ cobj->oid = $3;
+ p = RB_INSERT(object_tree, &conf->objects, cobj);
+ if (p != NULL) {
+ yyerror("object '%s' already defined", $3);
+ free($3);
+ free(cobj);
+ cobj = NULL;
+ YYERROR;
+ }
+ } obj_data ')' {
+ cobj = NULL;
+ }
+ ;
+
+obj_data : /* empty */
+ | obj_data NAME qdescrs { cobj->names = $3; }
+ | obj_data DESC STRING { cobj->desc = $3; }
+ | obj_data OBSOLETE { cobj->obsolete = 1; }
+ | obj_data SUP objlist { cobj->sup = $3; }
+ | obj_data kind { cobj->kind = $2; }
+ | obj_data MUST attrlist { cobj->must = $3; }
+ | obj_data MAY attrlist { cobj->may = $3; }
+ ;
+
+attrptr : STRING {
+ $$ = lookup_attribute($1);
+ if ($$ == NULL) {
+ yyerror("undeclared attribute '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+attrptrs : attrptr {
+ if (($$ = append_attr(NULL, $1)) == NULL)
+ YYERROR;
+ }
+ | attrptrs '$' attrptr {
+ if (($$ = append_attr($1, $3)) == NULL)
+ YYERROR;
+ }
+ ;
+
+attrlist : attrptr {
+ if (($$ = append_attr(NULL, $1)) == NULL)
+ YYERROR;
+ }
+ | '(' attrptrs ')' { $$ = $2; }
+ ;
+
+objptr : STRING {
+ $$ = lookup_object($1);
+ if ($$ == NULL) {
+ yyerror("undeclared object class '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+objptrs : objptr {
+ if (($$ = append_obj(NULL, $1)) == NULL)
+ YYERROR;
+ }
+ | objptrs '$' objptr {
+ if (($$ = append_obj($1, $3)) == NULL)
+ YYERROR;
+ }
+ ;
+
+objlist : objptr {
+ if (($$ = append_obj(NULL, $1)) == NULL)
+ YYERROR;
+ }
+ | '(' objptrs ')' { $$ = $2; }
+ ;
+
+kind : ABSTRACT { $$ = KIND_ABSTRACT; }
+ | STRUCTURAL { $$ = KIND_STRUCTURAL; }
+ | AUXILIARY { $$ = KIND_AUXILIARY; }
+ ;
+
+qdescr : STRING {
+ struct oidname *on, *old;
+
+ if ((on = calloc(1, sizeof(*on))) == NULL) {
+ yyerror("calloc");
+ free($1);
+ YYERROR;
+ }
+ on->on_name = $1;
+ if (cattr) {
+ on->on_attr_type = cattr;
+ old = RB_INSERT(oidname_tree, &conf->attr_names,
+ on);
+ if (old != NULL) {
+ yyerror("attribute name '%s' already"
+ " defined for oid %s",
+ $1, old->on_attr_type->oid);
+ free($1);
+ free(on);
+ YYERROR;
+ }
+ } else {
+ on->on_object = cobj;
+ old = RB_INSERT(oidname_tree,
+ &conf->object_names, on);
+ if (old != NULL) {
+ yyerror("object name '%s' already"
+ " defined for oid %s",
+ $1, old->on_object->oid);
+ free($1);
+ free(on);
+ YYERROR;
+ }
+ }
+ }
+ ;
+
+qdescrs : qdescr { $$ = append_name(NULL, $1); }
+ | '(' qdescrlist ')' { $$ = $2; }
+ ;
+
+qdescrlist : /* empty */ { $$ = NULL; }
+ | qdescrlist qdescr { $$ = append_name($1, $2); }
+ ;
+
+numericoid : STRING {
+ if (!is_oidstr($1)) {
+ yyerror("invalid OID: %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+oidlen : STRING { $$ = $1; }
+ | numericoid '{' NUMBER '}' {
+ $$ = $1;
+ }
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 1)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ // lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ /*if (conf->opts & BGPD_OPT_VERBOSE)*/
+ printf("%s = \"%s\"\n", $1, $3);
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *nfmt;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
+ fatalx("yyerror asprintf");
+ vlog(LOG_CRIT, nfmt, ap);
+ va_end(ap);
+ free(nfmt);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "ABSTRACT", ABSTRACT },
+ { "AUXILIARY", AUXILIARY },
+ { "COLLECTIVE", COLLECTIVE },
+ { "DESC", DESC },
+ { "EQUALITY", EQUALITY },
+ { "MAY", MAY },
+ { "MUST", MUST },
+ { "NAME", NAME },
+ { "NO-USER-MODIFICATION", NO_USER_MOD},
+ { "ORDERING", ORDERING },
+ { "SINGLE-VALUE", SINGLE_VALUE },
+ { "STRUCTURAL", STRUCTURAL },
+ { "SUBSTR", SUBSTR },
+ { "SUP", SUP },
+ { "SYNTAX", SYNTAX },
+ { "USAGE", USAGE },
+ { "access", ACCESS },
+ { "allow", ALLOW },
+ { "any", ANY },
+ { "attribute", ATTRIBUTE },
+ { "attributetype", ATTRIBUTETYPE },
+ { "bind", BIND },
+ { "by", BY },
+ { "cache-size", CACHE_SIZE },
+ { "certificate", CERTIFICATE },
+ { "children", CHILDREN },
+ { "compression", COMPRESSION },
+ { "dSAOperation", DSA_OPERATION },
+ { "deny", DENY },
+ { "directoryOperation", DIRECTORY_OPERATION },
+ { "distributedOperation", DISTRIBUTED_OPERATION },
+ { "fsync", FSYNC },
+ { "in", IN },
+ { "include", INCLUDE },
+ { "index", INDEX },
+ { "index-cache-size", INDEX_CACHE_SIZE },
+ { "ldaps", LDAPS },
+ { "level", LEVEL },
+ { "listen", LISTEN },
+ { "namespace", NAMESPACE },
+ { "objectclass", OBJECTCLASS },
+ { "of", OF },
+ { "on", ON },
+ { "port", PORT },
+ { "read", READ },
+ { "relax", RELAX },
+ { "root", ROOT },
+ { "rootdn", ROOTDN },
+ { "rootpw", ROOTPW },
+ { "schema", SCHEMA },
+ { "secure", SECURE },
+ { "self", SELF },
+ { "strict", STRICT },
+ { "subtree", SUBTREE },
+ { "tls", TLS },
+ { "to", TO },
+ { "use", USE },
+ { "userApplications", USER_APPLICATIONS },
+ { "write", WRITE },
+
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ if (*buf == '\0')
+ return ('$');
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n')
+ continue;
+ else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ fatal("yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '/' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_' || c == '*') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ fatal("yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ goto top;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IRWXG | S_IRWXO)) {
+ log_warnx("%s: group/world readable/writeable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ log_debug("parsing config %s", name);
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ log_warn("malloc");
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ log_warn("malloc");
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("%s", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+#if 0
+ if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+#endif
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+int
+parse_config(char *filename)
+{
+ struct sym *sym, *next;
+ int errors = 0;
+
+ if ((conf = calloc(1, sizeof(struct ldapd_config))) == NULL)
+ fatal(NULL);
+
+ RB_INIT(&conf->attr_types);
+ RB_INIT(&conf->objects);
+ TAILQ_INIT(&conf->namespaces);
+ TAILQ_INIT(&conf->listeners);
+ if ((conf->sc_ssl = calloc(1, sizeof(*conf->sc_ssl))) == NULL)
+ fatal(NULL);
+ SPLAY_INIT(conf->sc_ssl);
+ SIMPLEQ_INIT(&conf->acl);
+
+ if ((file = pushfile(filename, 1)) == NULL) {
+ free(conf);
+ return (-1);
+ }
+ topfile = file;
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if (/*(conf->opts & BGPD_OPT_VERBOSE2) &&*/ !sym->used)
+ fprintf(stderr, "warning: macro \"%s\" not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entry))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ fatal("cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct attr_type *
+lookup_attribute_by_name(const char *name)
+{
+ struct oidname *on, find;
+
+ find.on_name = name;
+ on = RB_FIND(oidname_tree, &conf->attr_names, &find);
+
+ if (on)
+ return on->on_attr_type;
+ return NULL;
+}
+
+struct attr_type *
+lookup_attribute_by_oid(const char *oid)
+{
+ struct attr_type find;
+
+ find.oid = oid;
+ return RB_FIND(attr_type_tree, &conf->attr_types, &find);
+}
+
+struct attr_type *
+lookup_attribute(const char *oid_or_name)
+{
+ if (is_oidstr(oid_or_name))
+ return lookup_attribute_by_oid(oid_or_name);
+ return lookup_attribute_by_name(oid_or_name);
+}
+
+struct object *
+lookup_object_by_oid(const char *oid)
+{
+ struct object find;
+
+ find.oid = oid;
+ return RB_FIND(object_tree, &conf->objects, &find);
+}
+
+struct object *
+lookup_object_by_name(const char *name)
+{
+ struct oidname *on, find;
+
+ find.on_name = name;
+ on = RB_FIND(oidname_tree, &conf->object_names, &find);
+
+ if (on)
+ return on->on_object;
+ return NULL;
+}
+
+struct object *
+lookup_object(const char *oid_or_name)
+{
+ if (is_oidstr(oid_or_name))
+ return lookup_object_by_oid(oid_or_name);
+ return lookup_object_by_name(oid_or_name);
+}
+
+static struct attr_list *
+append_attr(struct attr_list *alist, struct attr_type *a)
+{
+ struct attr_ptr *aptr;
+
+ if (alist == NULL) {
+ if ((alist = calloc(1, sizeof(*alist))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ SLIST_INIT(alist);
+ }
+
+ if ((aptr = calloc(1, sizeof(*aptr))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ aptr->attr_type = a;
+ SLIST_INSERT_HEAD(alist, aptr, next);
+
+ return alist;
+}
+
+static struct obj_list *
+append_obj(struct obj_list *olist, struct object *obj)
+{
+ struct obj_ptr *optr;
+
+ if (olist == NULL) {
+ if ((olist = calloc(1, sizeof(*olist))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ SLIST_INIT(olist);
+ }
+
+ if ((optr = calloc(1, sizeof(*optr))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ optr->object = obj;
+ SLIST_INSERT_HEAD(olist, optr, next);
+
+ return olist;
+}
+
+int
+is_oidstr(const char *oidstr)
+{
+ struct ber_oid oid;
+ return (ber_string2oid(oidstr, &oid) == 0);
+}
+
+static struct name_list *
+append_name(struct name_list *nl, const char *name)
+{
+ struct name *n;
+
+ if (nl == NULL) {
+ if ((nl = calloc(1, sizeof(*nl))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ SLIST_INIT(nl);
+ }
+ if ((n = calloc(1, sizeof(*n))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ n->name = name;
+ SLIST_INSERT_HEAD(nl, n, next);
+
+ return nl;
+}
+
+struct listener *
+host_unix(const char *path)
+{
+ struct sockaddr_un *saun;
+ struct listener *h;
+
+ if (*path != '/')
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ saun = (struct sockaddr_un *)&h->ss;
+ saun->sun_len = sizeof(struct sockaddr_un);
+ saun->sun_family = AF_UNIX;
+ if (strlcpy(saun->sun_path, path, sizeof(saun->sun_path)) >=
+ sizeof(saun->sun_path))
+ fatal("socket path too long");
+ h->flags = F_SECURE;
+
+ return (h);
+}
+
+struct listener *
+host_v4(const char *s, in_port_t port)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct listener *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+ sain->sin_port = port;
+
+ return (h);
+}
+
+struct listener *
+host_v6(const char *s, in_port_t port)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ bzero(&ina6, sizeof(ina6));
+ if (inet_pton(AF_INET6, s, &ina6) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = port;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ return (h);
+}
+
+int
+host_dns(const char *s, const char *cert,
+ struct listenerlist *al, int max, in_port_t port, u_int8_t flags)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("host_dns: could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ h->port = port;
+ h->flags = flags;
+ h->ss.ss_family = res->ai_family;
+ h->ssl = NULL;
+ h->ssl_cert_name[0] = '\0';
+ if (cert != NULL)
+ (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name));
+
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ sain->sin_port = port;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_port = port;
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("host_dns: %s resolves to more than %d hosts",
+ s, max);
+ }
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+host(const char *s, const char *cert, struct listenerlist *al,
+ int max, in_port_t port, u_int8_t flags)
+{
+ struct listener *h;
+
+ /* Unix socket path? */
+ h = host_unix(s);
+
+ /* IPv4 address? */
+ if (h == NULL)
+ h = host_v4(s, port);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s, port);
+
+ if (h != NULL) {
+ h->port = port;
+ h->flags |= flags;
+ h->ssl = NULL;
+ h->ssl_cert_name[0] = '\0';
+ if (cert != NULL)
+ strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name));
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, cert, al, max, port, flags));
+}
+
+int
+interface(const char *s, const char *cert,
+ struct listenerlist *al, int max, in_port_t port, u_int8_t flags)
+{
+ int ret = 0;
+ struct ifaddrs *ifap, *p;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ if (getifaddrs(&ifap) == -1)
+ fatal("getifaddrs");
+
+ for (p = ifap; p != NULL; p = p->ifa_next) {
+ if (strcmp(s, p->ifa_name) != 0)
+ continue;
+
+ switch (p->ifa_addr->sa_family) {
+ case AF_INET:
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ *sain = *(struct sockaddr_in *)p->ifa_addr;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_port = port;
+
+ h->fd = -1;
+ h->port = port;
+ h->flags = flags;
+ h->ssl = NULL;
+ h->ssl_cert_name[0] = '\0';
+ if (cert != NULL)
+ (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name));
+
+ ret = 1;
+ TAILQ_INSERT_HEAD(al, h, entry);
+
+ break;
+
+ case AF_INET6:
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ *sin6 = *(struct sockaddr_in6 *)p->ifa_addr;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_port = port;
+
+ h->fd = -1;
+ h->port = port;
+ h->flags = flags;
+ h->ssl = NULL;
+ h->ssl_cert_name[0] = '\0';
+ if (cert != NULL)
+ (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name));
+
+ ret = 1;
+ TAILQ_INSERT_HEAD(al, h, entry);
+
+ break;
+ }
+ }
+
+ freeifaddrs(ifap);
+
+ return ret;
+}
+
+static struct aci *
+mk_aci(int type, int rights, enum scope scope, char *target, char *subject)
+{
+ struct aci *aci;
+
+ if ((aci = calloc(1, sizeof(*aci))) == NULL) {
+ yyerror("calloc");
+ return NULL;
+ }
+ aci->type = type;
+ aci->rights = rights;
+ aci->scope = scope;
+ aci->target = target;
+ aci->subject = subject;
+
+ log_debug("%s %02X access to %s scope %d by %s",
+ aci->type == ACI_DENY ? "deny" : "allow",
+ aci->rights,
+ aci->target ?: "any",
+ aci->scope,
+ aci->subject ?: "any");
+
+ return aci;
+}
+
diff --git a/usr.sbin/ldapd/schema/core.schema b/usr.sbin/ldapd/schema/core.schema
new file mode 100644
index 00000000000..18d782dca35
--- /dev/null
+++ b/usr.sbin/ldapd/schema/core.schema
@@ -0,0 +1,678 @@
+################ rfc4512
+
+attributetype ( 2.5.4.1 NAME 'aliasedObjectName'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE )
+
+attributetype ( 2.5.4.0 NAME 'objectClass'
+ EQUALITY objectIdentifierMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
+
+attributetype ( 2.5.18.3 NAME 'creatorsName'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.18.1 NAME 'createTimestamp'
+ EQUALITY generalizedTimeMatch
+ ORDERING generalizedTimeOrderingMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.18.4 NAME 'modifiersName'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.18.2 NAME 'modifyTimestamp'
+ EQUALITY generalizedTimeMatch
+ ORDERING generalizedTimeOrderingMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.9 NAME 'structuralObjectClass'
+ EQUALITY objectIdentifierMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.10 NAME 'governingStructureRule'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.18.10 NAME 'subschemaSubentry'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.6 NAME 'objectClasses'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.37
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.5 NAME 'attributeTypes'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.3
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.4 NAME 'matchingRules'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.30
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.8 NAME 'matchingRuleUse'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.31
+ USAGE directoryOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.54
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.2 NAME 'dITContentRules'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.16
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.1 NAME 'dITStructureRules'
+ EQUALITY integerFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.17
+ USAGE directoryOperation )
+
+attributetype ( 2.5.21.7 NAME 'nameForms'
+ EQUALITY objectIdentifierFirstComponentMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.35
+ USAGE directoryOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.13 NAME 'supportedControl'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.7 NAME 'supportedExtension'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.4203.1.3.5 NAME 'supportedFeatures'
+ EQUALITY objectIdentifierMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.4.1.1466.101.120.14 NAME 'supportedSASLMechanisms'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ USAGE dSAOperation )
+
+objectclass ( 2.5.6.0 NAME 'top'
+ ABSTRACT
+ MUST objectClass )
+
+objectclass ( 2.5.6.1 NAME 'alias'
+ SUP top
+ STRUCTURAL
+ MUST aliasedObjectName )
+
+objectclass ( 2.5.20.1 NAME 'subschema'
+ AUXILIARY
+ MAY ( dITStructureRules $ nameForms $ ditContentRules $
+ objectClasses $ attributeTypes $ matchingRules $
+ matchingRuleUse ) )
+
+objectclass ( 1.3.6.1.4.1.1466.101.120.111 NAME 'extensibleObject'
+ SUP top
+ AUXILIARY )
+
+
+################ rfc4519
+
+attributetype ( 2.5.4.41 NAME 'name'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.49 NAME 'distinguishedName'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+
+attributetype ( 0.9.2342.19200300.100.1.1 NAME 'uid'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 0.9.2342.19200300.100.1.25 NAME 'dc'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 2.5.4.10 NAME 'o'
+ SUP name )
+
+attributetype ( 2.5.4.11 NAME 'ou'
+ SUP name )
+
+attributetype ( 2.5.4.12 NAME 'title'
+ SUP name )
+
+attributetype ( 2.5.4.13 NAME 'description'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.14 NAME 'searchGuide'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 )
+
+attributetype ( 2.5.4.15 NAME 'businessCategory'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.16 NAME 'postalAddress'
+ EQUALITY caseIgnoreListMatch
+ SUBSTR caseIgnoreListSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
+
+attributetype ( 2.5.4.17 NAME 'postalCode'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.18 NAME 'postOfficeBox'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.19 NAME 'physicalDeliveryOfficeName'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.20 NAME 'telephoneNumber'
+ EQUALITY telephoneNumberMatch
+ SUBSTR telephoneNumberSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )
+
+attributetype ( 2.5.4.21 NAME 'telexNumber'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )
+
+attributetype ( 2.5.4.22 NAME 'teletexTerminalIdentifier'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )
+
+attributetype ( 2.5.4.23 NAME 'facsimileTelephoneNumber'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 )
+
+attributetype ( 2.5.4.24 NAME 'x121Address'
+ EQUALITY numericStringMatch
+ SUBSTR numericStringSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )
+
+attributetype ( 2.5.4.25 NAME 'internationalISDNNumber'
+ EQUALITY numericStringMatch
+ SUBSTR numericStringSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )
+
+attributetype ( 2.5.4.26 NAME 'registeredAddress'
+ SUP postalAddress
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
+
+attributetype ( 2.5.4.27 NAME 'destinationIndicator'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
+
+attributetype ( 2.5.4.28 NAME 'preferredDeliveryMethod'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.14
+ SINGLE-VALUE )
+
+attributetype ( 2.5.4.3 NAME 'cn'
+ SUP name )
+
+attributetype ( 2.5.4.31 NAME 'member'
+ SUP distinguishedName )
+
+attributetype ( 2.5.4.32 NAME 'owner'
+ SUP distinguishedName )
+
+attributetype ( 2.5.4.33 NAME 'roleOccupant'
+ SUP distinguishedName )
+
+attributetype ( 2.5.4.34 NAME 'seeAlso'
+ SUP distinguishedName )
+
+attributetype ( 2.5.4.35 NAME 'userPassword'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
+
+attributetype ( 2.5.4.4 NAME 'sn'
+ SUP name )
+
+attributetype ( 2.5.4.42 NAME 'givenName'
+ SUP name )
+
+attributetype ( 2.5.4.43 NAME 'initials'
+ SUP name )
+
+attributetype ( 2.5.4.44 NAME 'generationQualifier'
+ SUP name )
+
+attributetype ( 2.5.4.45 NAME 'x500UniqueIdentifier'
+ EQUALITY bitStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )
+
+attributetype ( 2.5.4.46 NAME 'dnQualifier'
+ EQUALITY caseIgnoreMatch
+ ORDERING caseIgnoreOrderingMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
+
+attributetype ( 2.5.4.47 NAME 'enhancedSearchGuide'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )
+
+attributetype ( 2.5.4.5 NAME 'serialNumber'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
+
+attributetype ( 2.5.4.50 NAME 'uniqueMember'
+ EQUALITY uniqueMemberMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )
+
+attributetype ( 2.5.4.51 NAME 'houseIdentifier'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.5.4.6 NAME 'c'
+ SUP name
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.11
+ SINGLE-VALUE )
+
+attributetype ( 2.5.4.7 NAME 'l'
+ SUP name )
+
+attributetype ( 2.5.4.8 NAME 'st'
+ SUP name )
+
+attributetype ( 2.5.4.9 NAME 'street'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+objectclass ( 1.3.6.1.1.3.1 NAME 'uidObject'
+ SUP top
+ AUXILIARY
+ MUST uid )
+
+objectclass ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
+ SUP top
+ AUXILIARY
+ MUST dc )
+
+objectclass ( 2.5.6.2 NAME 'country'
+ SUP top
+ STRUCTURAL
+ MUST c
+ MAY ( searchGuide $ description ) )
+
+objectclass ( 2.5.6.3 NAME 'locality'
+ SUP top
+ STRUCTURAL
+ MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) )
+
+objectclass ( 2.5.6.4 NAME 'organization'
+ SUP top
+ STRUCTURAL
+ MUST o
+ MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
+ x121Address $ registeredAddress $ destinationIndicator $
+ preferredDeliveryMethod $ telexNumber $
+ teletexTerminalIdentifier $ telephoneNumber $
+ internationalISDNNumber $ facsimileTelephoneNumber $ street $
+ postOfficeBox $ postalCode $ postalAddress $
+ physicalDeliveryOfficeName $ st $ l $ description ) )
+
+objectclass ( 2.5.6.5 NAME 'organizationalUnit'
+ SUP top
+ STRUCTURAL
+ MUST ou
+ MAY ( businessCategory $ description $ destinationIndicator $
+ facsimileTelephoneNumber $ internationalISDNNumber $ l $
+ physicalDeliveryOfficeName $ postalAddress $ postalCode $
+ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $
+ searchGuide $ seeAlso $ st $ street $ telephoneNumber $
+ teletexTerminalIdentifier $ telexNumber $ userPassword $
+ x121Address ) )
+
+objectclass ( 2.5.6.6 NAME 'person'
+ SUP top
+ STRUCTURAL
+ MUST ( sn $ cn )
+ MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
+
+objectclass ( 2.5.6.7 NAME 'organizationalPerson'
+ SUP person
+ STRUCTURAL
+ MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $
+ preferredDeliveryMethod $ telexNumber $
+ teletexTerminalIdentifier $ telephoneNumber $
+ internationalISDNNumber $ facsimileTelephoneNumber $ street $
+ postOfficeBox $ postalCode $ postalAddress $
+ physicalDeliveryOfficeName $ ou $ st $ l ) )
+
+objectclass ( 2.5.6.8 NAME 'organizationalRole'
+ SUP top
+ STRUCTURAL
+ MUST cn
+ MAY ( x121Address $ registeredAddress $ destinationIndicator $
+ preferredDeliveryMethod $ telexNumber $
+ teletexTerminalIdentifier $ telephoneNumber $
+ internationalISDNNumber $ facsimileTelephoneNumber $ seeAlso $
+ roleOccupant $ preferredDeliveryMethod $ street $
+ postOfficeBox $ postalCode $ postalAddress $
+ physicalDeliveryOfficeName $ ou $ st $ l $ description ) )
+
+objectclass ( 2.5.6.9 NAME 'groupOfNames'
+ SUP top
+ STRUCTURAL
+ MUST ( member $ cn )
+ MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
+
+objectclass ( 2.5.6.10 NAME 'residentialPerson'
+ SUP person
+ STRUCTURAL
+ MUST l
+ MAY ( businessCategory $ x121Address $ registeredAddress $
+ destinationIndicator $ preferredDeliveryMethod $ telexNumber $
+ teletexTerminalIdentifier $ telephoneNumber $
+ internationalISDNNumber $ facsimileTelephoneNumber $
+ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $
+ postalAddress $ physicalDeliveryOfficeName $ st $ l ) )
+
+objectclass ( 2.5.6.11 NAME 'applicationProcess'
+ SUP top
+ STRUCTURAL
+ MUST cn
+ MAY ( seeAlso $ ou $ l $ description ) )
+
+objectclass ( 2.5.6.14 NAME 'device'
+ SUP top
+ STRUCTURAL
+ MUST cn
+ MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) )
+
+objectclass ( 2.5.6.17 NAME 'groupOfUniqueNames'
+ SUP top
+ STRUCTURAL
+ MUST ( uniqueMember $ cn )
+ MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
+
+
+################ rfc4524 (cosine)
+attributetype ( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 0.9.2342.19200300.100.1.38 NAME 'associatedName'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+
+attributetype ( 0.9.2342.19200300.100.1.48 NAME 'buildingName'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.43 NAME 'co'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+
+attributetype ( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.15 NAME 'documentLocation'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 0.9.2342.19200300.100.1.12 NAME 'documentTitle'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.13 NAME 'documentVersion'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.5 NAME 'drink'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.20 NAME 'homePhone'
+ EQUALITY telephoneNumberMatch
+ SUBSTR telephoneNumberSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )
+
+attributetype ( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress'
+ EQUALITY caseIgnoreListMatch
+ SUBSTR caseIgnoreListSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
+
+attributetype ( 0.9.2342.19200300.100.1.9 NAME 'host'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.4 NAME 'info'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )
+
+attributetype ( 0.9.2342.19200300.100.1.3 NAME 'mail'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.10 NAME 'manager'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+
+attributetype ( 0.9.2342.19200300.100.1.41 NAME 'mobile'
+ EQUALITY telephoneNumberMatch
+ SUBSTR telephoneNumberSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )
+
+attributetype ( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.42 NAME 'pager'
+ EQUALITY telephoneNumberMatch
+ SUBSTR telephoneNumberSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )
+
+attributetype ( 0.9.2342.19200300.100.1.40 NAME 'personalTitle'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.6 NAME 'roomNumber'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.21 NAME 'secretary'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+
+attributetype ( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+attributetype ( 0.9.2342.19200300.100.1.8 NAME 'userClass'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+
+objectclass ( 0.9.2342.19200300.100.4.5 NAME 'account'
+ SUP top
+ STRUCTURAL
+ MUST uid
+ MAY ( description $ seeAlso $ l $ o $ ou $ host ) )
+
+objectclass ( 0.9.2342.19200300.100.4.6 NAME 'document'
+ SUP top
+ STRUCTURAL
+ MUST documentIdentifier
+ MAY ( cn $ description $ seeAlso $ l $ o $ ou $ documentTitle $
+ documentVersion $ documentAuthor $ documentLocation $
+ documentPublisher ) )
+
+objectclass ( 0.9.2342.19200300.100.4.9 NAME 'documentSeries'
+ SUP top
+ STRUCTURAL
+ MUST cn
+ MAY ( description $ l $ o $ ou $ seeAlso $ telephonenumber ) )
+
+objectclass ( 0.9.2342.19200300.100.4.13 NAME 'domain'
+ SUP top
+ STRUCTURAL
+ MUST dc
+ MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $
+ x121Address $ registeredAddress $ destinationIndicator $
+ preferredDeliveryMethod $ telexNumber $
+ teletexTerminalIdentifier $ telephoneNumber $
+ internationaliSDNNumber $ facsimileTelephoneNumber $ street $
+ postOfficeBox $ postalCode $ postalAddress $
+ physicalDeliveryOfficeName $ st $ l $ description $ o $
+ associatedName ) )
+
+objectclass ( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject'
+ SUP top
+ AUXILIARY
+ MUST associatedDomain )
+
+objectclass ( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry'
+ SUP country
+ STRUCTURAL
+ MUST co )
+
+
+objectclass ( 0.9.2342.19200300.100.4.14 NAME 'rFC822localPart'
+ SUP domain
+ STRUCTURAL
+ MAY ( cn $ description $ destinationIndicator $
+ facsimileTelephoneNumber $ internationaliSDNNumber $
+ physicalDeliveryOfficeName $ postalAddress $ postalCode $
+ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $
+ seeAlso $ sn $ street $ telephoneNumber $
+ teletexTerminalIdentifier $ telexNumber $ x121Address ) )
+
+objectclass ( 0.9.2342.19200300.100.4.7 NAME 'room'
+ SUP top
+ STRUCTURAL
+ MUST cn
+ MAY ( roomNumber $ description $ seeAlso $ telephoneNumber ) )
+
+objectclass ( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject'
+ SUP top
+ AUXILIARY
+ MUST userPassword )
+
+
+################ rfc2079
+attributetype ( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI'
+ DESC 'Uniform Resource Identifier with optional label'
+ EQUALITY caseExactMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+objectclass ( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject'
+ DESC 'object that contains the URI attribute type'
+ SUP top
+ MAY labeledURI )
+
+
+################ rfc3045
+attributetype ( 1.3.6.1.1.4 NAME 'vendorName'
+ EQUALITY 1.3.6.1.4.1.1466.109.114.1
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE dSAOperation )
+
+attributetype ( 1.3.6.1.1.5 NAME 'vendorVersion'
+ EQUALITY 1.3.6.1.4.1.1466.109.114.1
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE dSAOperation )
+
+
+################ rfc3672
+attributetype ( 2.5.18.5 NAME 'administrativeRole'
+ EQUALITY objectIdentifierMatch
+ USAGE directoryOperation
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
+
+attributetype ( 2.5.18.6 NAME 'subtreeSpecification'
+ SINGLE-VALUE
+ USAGE directoryOperation
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.45 )
+
+objectclass ( 2.5.17.0 NAME 'subentry'
+ SUP top
+ STRUCTURAL
+ MUST ( cn $ subtreeSpecification ) )
+
+
+################ rfc4530
+attributetype ( 1.3.6.1.1.16.4 NAME 'entryUUID'
+ DESC 'UUID of the entry'
+ EQUALITY uuidMatch
+ ORDERING uuidOrderingMatch
+ SYNTAX 1.3.6.1.1.16.1
+ SINGLE-VALUE
+ NO-USER-MODIFICATION
+ USAGE directoryOperation )
+
diff --git a/usr.sbin/ldapd/schema/inetorgperson.schema b/usr.sbin/ldapd/schema/inetorgperson.schema
new file mode 100644
index 00000000000..f3cca47d322
--- /dev/null
+++ b/usr.sbin/ldapd/schema/inetorgperson.schema
@@ -0,0 +1,81 @@
+attributetype ( 2.16.840.1.113730.3.1.1 NAME 'carLicense'
+ DESC 'vehicle license or registration plate'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.16.840.1.113730.3.1.2
+ NAME 'departmentNumber'
+ DESC 'identifies a department within an organization'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 2.16.840.1.113730.3.1.241
+ NAME 'displayName'
+ DESC 'preferred name of a person to be used when displaying entries'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE )
+
+attributetype ( 2.16.840.1.113730.3.1.3
+ NAME 'employeeNumber'
+ DESC 'numerically identifies an employee within an organization'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE )
+
+attributetype ( 2.16.840.1.113730.3.1.4
+ NAME 'employeeType'
+ DESC 'type of employment for a person'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+
+attributetype ( 0.9.2342.19200300.100.1.60
+ NAME 'jpegPhoto'
+ DESC 'a JPEG image'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
+
+attributetype ( 2.16.840.1.113730.3.1.39
+ NAME 'preferredLanguage'
+ DESC 'preferred written or spoken language for a person'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE )
+
+attributetype ( 2.16.840.1.113730.3.1.40
+ NAME 'userSMIMECertificate'
+ DESC 'PKCS#7 SignedData used to support S/MIME'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
+
+attributetype ( 2.16.840.1.113730.3.1.216
+ NAME 'userPKCS12'
+ DESC 'PKCS #12 PFX PDU for exchange of personal identity information'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
+
+# from rfc 1274
+attributetype ( 0.9.2342.19200300.100.1.55 NAME 'audio'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{250000} )
+
+# obsolete, from rfc 1274
+#attributetype ( 0.9.2342.19200300.100.1.7 NAME 'photo' )
+# Note: Photo attribute values are encoded in G3 fax format with an
+# ASN.1 wrapper. Please refer to RFC 1274 section 9.3.7 for
+# detailed syntax information for this attribute.
+
+# removed obsolete attributes audio, photo and userCertificate
+objectclass ( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson'
+ SUP organizationalPerson
+ STRUCTURAL
+ MAY ( audio $ businessCategory $ carLicense $ departmentNumber $
+ displayName $ employeeNumber $ employeeType $ givenName $
+ homePhone $ homePostalAddress $ initials $ jpegPhoto $
+ labeledURI $ mail $ manager $ mobile $ o $ pager $
+ roomNumber $ secretary $ uid $ x500uniqueIdentifier $
+ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
+
diff --git a/usr.sbin/ldapd/schema/nis.schema b/usr.sbin/ldapd/schema/nis.schema
new file mode 100644
index 00000000000..0c67c742eba
--- /dev/null
+++ b/usr.sbin/ldapd/schema/nis.schema
@@ -0,0 +1,250 @@
+######### rfc2307
+
+attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber'
+ DESC 'An integer uniquely identifying a user in an
+ administrative domain'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.1 NAME 'gidNumber'
+ DESC 'An integer uniquely identifying a group in an
+ administrative domain'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
+ DESC 'The GECOS field; the common name'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 'IA5String'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory'
+ DESC 'The absolute path to the home directory'
+ EQUALITY caseExactIA5Match
+ SYNTAX 'IA5String'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.4 NAME 'loginShell'
+ DESC 'The path to the login shell'
+ EQUALITY caseExactIA5Match
+ SYNTAX 'IA5String'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.6 NAME 'shadowMin'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.7 NAME 'shadowMax'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.12 NAME 'memberUid'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 'IA5String' )
+
+attributetype ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 'IA5String' )
+
+attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple'
+ DESC 'Netgroup triple'
+ SYNTAX 'nisNetgroupTripleSyntax' )
+
+attributetype ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol'
+ SUP name )
+
+attributetype ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber'
+ EQUALITY integerMatch
+ SYNTAX 'INTEGER'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber'
+ DESC 'IP address as a dotted decimal, eg. 192.168.1.1,
+ omitting leading zeros'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 'IA5String{128}' )
+
+attributetype ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber'
+ DESC 'IP network as a dotted decimal, eg. 192.168,
+ omitting leading zeros'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 'IA5String{128}'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber'
+ DESC 'IP netmask as a dotted decimal, eg. 255.255.255.0,
+ omitting leading zeros'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 'IA5String{128}'
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.22 NAME 'macAddress'
+ DESC 'MAC address in maximal, colon separated hex notation,
+ eg. 00:00:92:90:ee:e2'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 'IA5String{128}' )
+
+attributetype ( 1.3.6.1.1.1.1.23 NAME 'bootParameter'
+ DESC 'rpc.bootparamd parameter'
+ SYNTAX 'bootParameterSyntax' )
+
+attributetype ( 1.3.6.1.1.1.1.24 NAME 'bootFile'
+ DESC 'Boot image name'
+ EQUALITY caseExactIA5Match
+ SYNTAX 'IA5String' )
+
+attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName'
+ SUP name )
+
+attributetype ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 'IA5String{1024}'
+ SINGLE-VALUE )
+
+objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
+ SUP top
+ AUXILIARY
+ DESC 'Abstraction of an account with POSIX attributes'
+ MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
+ MAY ( userPassword $ loginShell $ gecos $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount'
+ SUP top
+ AUXILIARY
+ DESC 'Additional attributes for shadow passwords'
+ MUST uid
+ MAY ( userPassword $ shadowLastChange $ shadowMin $ shadowMax $
+ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $
+ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction of a group of accounts'
+ MUST ( cn $ gidNumber )
+ MAY ( userPassword $ memberUid $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.3 NAME 'ipService'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction an Internet Protocol service. Maps an IP port
+ and protocol (such as tcp or udp) to one or more names; the
+ distinguished value of the cn attribute denotes the services
+ canonical name'
+ MUST ( cn $ ipServicePort $ ipServiceProtocol )
+ MAY ( description ) )
+
+objectclass ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction of an IP protocol. Maps a protocol number to one
+ or more names. The distinguished value of the cn attribute
+ denotes the protocols canonical name'
+ MUST ( cn $ ipProtocolNumber $ description )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.5 NAME 'oncRpc'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction of an Open Network Computing (ONC) [RFC1057]
+ Remote Procedure Call (RPC) binding. This class maps an
+ ONC RPC number to a name. The distinguished value of the cn
+ attribute denotes the RPC services canonical name'
+ MUST ( cn $ oncRpcNumber $ description )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.6 NAME 'ipHost'
+ SUP top
+ AUXILIARY
+ DESC 'Abstraction of a host, an IP device. The distinguished value
+ of the cn attribute denotes the hosts canonical name. Device
+ SHOULD be used as a structural class'
+ MUST ( cn $ ipHostNumber )
+ MAY ( l $ description $ manager ) )
+
+objectclass ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction of a network. The distinguished value of the cn
+ attribute denotes the networks canonical name'
+ MUST ( cn $ ipNetworkNumber )
+ MAY ( ipNetmaskNumber $ l $ description $ manager ) )
+
+objectclass ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup'
+ SUP top
+ STRUCTURAL
+ DESC 'Abstraction of a netgroup. May refer to other netgroups'
+ MUST cn
+ MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.09 NAME 'nisMap'
+ SUP top
+ STRUCTURAL
+ DESC 'A generic abstraction of a NIS map'
+ MUST nisMapName
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.10 NAME 'nisObject'
+ SUP top
+ STRUCTURAL
+ DESC 'An entry in a NIS map'
+ MUST ( cn $ nisMapEntry $ nisMapName )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device'
+ SUP top
+ AUXILIARY
+ DESC 'A device with a MAC address; device SHOULD be used as a
+ structural class'
+ MAY macAddress )
+
+objectclass ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice'
+ SUP top
+ AUXILIARY
+ DESC 'A device with boot parameters; device SHOULD be used as a
+ structural class'
+ MAY ( bootFile $ bootParameter ) )
+
diff --git a/usr.sbin/ldapd/search.c b/usr.sbin/ldapd/search.c
new file mode 100644
index 00000000000..48c1df449b0
--- /dev/null
+++ b/usr.sbin/ldapd/search.c
@@ -0,0 +1,827 @@
+/* $OpenBSD: search.c,v 1.1 2010/05/31 17:36:31 martinh 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/queue.h>
+#include <sys/types.h>
+#include <sys/tree.h>
+
+#include <event.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "ldapd.h"
+
+#define MAX_SEARCHES 200
+
+void filter_free(struct plan *filter);
+static int search_result(const char *dn,
+ size_t dnlen,
+ struct ber_element *attrs,
+ struct search *search);
+
+static int
+uniqdn_cmp(struct uniqdn *a, struct uniqdn *b)
+{
+ if (a->key.size < b->key.size)
+ return -1;
+ if (a->key.size > b->key.size)
+ return +1;
+ return memcmp(a->key.data, b->key.data, a->key.size);
+}
+
+RB_GENERATE(dn_tree, uniqdn, link, uniqdn_cmp);
+
+/* Return true if the attribute is operational.
+ */
+static int
+is_operational(char *adesc)
+{
+ struct attr_type *at;
+
+ at = lookup_attribute(adesc);
+ if (at)
+ return at->usage != USAGE_USER_APP;
+
+ return 0;
+}
+
+/* Return true if attr should be included in search entry.
+ */
+static int
+should_include_attribute(struct ber_element *attr, struct search *search)
+{
+ char *adesc, *fdesc;
+ struct ber_element *elm;
+
+ if (ber_get_string(attr->be_sub, &adesc) != 0)
+ return 0;
+
+ if (search->attrlist->be_sub->be_encoding == BER_TYPE_EOC) {
+ /* An empty list with no attributes requests the return of
+ * all user attributes. */
+ return !is_operational(adesc);
+ }
+
+ for (elm = search->attrlist->be_sub; elm; elm = elm->be_next) {
+ if (ber_get_string(elm, &fdesc) != 0)
+ continue;
+ if (strcasecmp(fdesc, adesc) == 0)
+ return 1;
+ if (strcmp(fdesc, "*") == 0 && !is_operational(adesc))
+ return 1;
+ if (strcmp(fdesc, "+") == 0 && is_operational(adesc))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+search_result(const char *dn, size_t dnlen, struct ber_element *attrs,
+ struct search *search)
+{
+ int rc;
+ struct conn *conn = search->conn;
+ struct ber_element *root, *elm, *filtered_attrs = NULL, *link, *a;
+ struct ber_element *prev, *next;
+ void *buf;
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+
+ if ((filtered_attrs = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+ link = filtered_attrs;
+
+ for (prev = NULL, a = attrs->be_sub; a; a = next) {
+ if (should_include_attribute(a, search)) {
+ next = a->be_next;
+ if (prev != NULL)
+ prev->be_next = a->be_next; /* unlink a */
+ else
+ attrs->be_sub = a->be_next;
+ a->be_next = NULL; /* break chain*/
+ ber_link_elements(link, a);
+ link = a;
+ } else {
+ prev = a;
+ next = a->be_next;
+ }
+ }
+
+ elm = ber_printf_elements(root, "i{txe", search->req->msgid,
+ BER_CLASS_APP, (unsigned long)LDAP_RES_SEARCH_ENTRY,
+ dn, dnlen, filtered_attrs);
+ if (elm == NULL)
+ goto fail;
+
+ rc = ber_write_elements(&conn->ber, root);
+ ber_free_elements(root);
+
+ if (rc < 0) {
+ log_warn("failed to create search-entry response");
+ return -1;
+ }
+
+ ber_get_writebuf(&conn->ber, &buf);
+ if (bufferevent_write(conn->bev, buf, rc) != 0) {
+ log_warn("failed to send ldap result");
+ return -1;
+ }
+
+ return 0;
+fail:
+ log_warn("search result");
+ if (root)
+ ber_free_elements(root);
+ return -1;
+}
+
+void
+search_close(struct search *search)
+{
+ struct uniqdn *dn, *next;
+
+ for (dn = RB_MIN(dn_tree, &search->uniqdns); dn; dn = next) {
+ next = RB_NEXT(dn_tree, &search->uniqdns, dn);
+ RB_REMOVE(dn_tree, &search->uniqdns, dn);
+ free(dn->key.data);
+ free(dn);
+ }
+
+ btree_cursor_close(search->cursor);
+
+ log_debug("finished search on msgid %d", search->req->msgid);
+ TAILQ_REMOVE(&search->conn->searches, search, next);
+ request_free(search->req);
+ filter_free(search->plan);
+ free(search);
+ --stats.searches;
+}
+
+/* Returns true (1) if key is a direct subordinate of base.
+ */
+int
+is_child_of(struct btval *key, const char *base)
+{
+ size_t ksz, bsz;
+ char *p;
+
+ if ((p = memchr(key->data, ',', key->size)) == NULL)
+ return 0;
+ p++;
+ ksz = key->size - (p - (char *)key->data);
+ bsz = strlen(base);
+ return (ksz == bsz && bcmp(p, base, ksz) == 0);
+}
+
+static int
+check_search_entry(struct btval *key, struct btval *val, struct search *search)
+{
+ int rc;
+ struct ber_element *elm;
+
+ /* verify entry is a direct subordinate of basedn */
+ if (search->scope == LDAP_SCOPE_ONELEVEL &&
+ !is_child_of(key, search->basedn)) {
+ log_debug("not a direct subordinate of base");
+ return 0;
+ }
+
+ char *dn0 = malloc(key->size + 1);
+ strncpy(dn0, key->data, key->size);
+ dn0[key->size] = 0;
+ if (!authorized(search->conn, search->ns, ACI_READ, dn0,
+ LDAP_SCOPE_BASE)) {
+ /* LDAP_INSUFFICIENT_ACCESS */
+ free(dn0);
+ return 0;
+ }
+ free(dn0);
+
+ if ((elm = namespace_db2ber(search->ns, val)) == NULL) {
+ log_warnx("failed to parse entry [%.*s]",
+ (int)key->size, (char *)key->data);
+ return 0;
+ }
+
+ if (ldap_matches_filter(elm, search->filter) != 0) {
+ ber_free_elements(elm);
+ return 0;
+ }
+
+ rc = search_result(key->data, key->size, elm, search);
+ ber_free_elements(elm);
+
+ if (rc == 0)
+ search->nmatched++;
+
+ return rc;
+}
+
+static int
+mk_dup(struct search *search, struct btval *key)
+{
+ struct uniqdn *udn;
+
+ if ((udn = calloc(1, sizeof(*udn))) == NULL)
+ return BT_FAIL;
+
+ if ((udn->key.data = malloc(key->size)) == NULL) {
+ free(udn);
+ return BT_FAIL;
+ }
+ bcopy(key->data, udn->key.data, key->size);
+ udn->key.size = key->size;
+ RB_INSERT(dn_tree, &search->uniqdns, udn);
+ return BT_SUCCESS;
+}
+
+/* check if this entry was already sent */
+static int
+is_dup(struct search *search, struct btval *key)
+{
+ struct uniqdn find;
+
+ find.key.data = key->data;
+ find.key.size = key->size;
+ return RB_FIND(dn_tree, &search->uniqdns, &find) != NULL;
+}
+
+void
+conn_search(struct search *search)
+{
+ int i, rc = BT_SUCCESS;
+ unsigned int reason = LDAP_SUCCESS;
+ unsigned int op = BT_NEXT;
+ time_t now;
+ struct conn *conn;
+ struct btree *db;
+ struct btval key, ikey, val;
+
+ conn = search->conn;
+
+ bzero(&key, sizeof(key));
+ bzero(&val, sizeof(val));
+
+ if (search->plan->indexed)
+ db = search->ns->indx_db;
+ else
+ db = search->ns->data_db;
+
+ if (!search->init) {
+ if ((search->cursor = btree_cursor_open(db)) == NULL) {
+ log_warn("btree_cursor_open");
+ search_close(search);
+ return;
+ }
+
+ if (search->plan->indexed) {
+ search->cindx = TAILQ_FIRST(&search->plan->indices);
+ key.data = search->cindx->prefix;
+ log_debug("init index scan on [%s]", key.data);
+ } else {
+ if (*search->basedn)
+ key.data = search->basedn;
+ log_debug("init full scan");
+ }
+
+ if (key.data) {
+ key.size = strlen(key.data);
+ op = BT_CURSOR;
+ }
+
+ search->init = 1;
+ }
+
+ for (i = 0; i < 10 && rc == BT_SUCCESS; i++) {
+ rc = btree_cursor_get(search->cursor, &key, &val, op);
+ op = BT_NEXT;
+
+ if (rc == BT_SUCCESS && search->plan->indexed) {
+ log_debug("found index %.*s", key.size, key.data);
+
+ if (!has_prefix(&key, search->cindx->prefix)) {
+ log_debug("scanned past index prefix [%s]",
+ search->cindx->prefix);
+ btval_reset(&val);
+ btval_reset(&key);
+ rc = BT_NOTFOUND;
+ }
+ }
+
+ if (rc == BT_NOTFOUND && search->plan->indexed > 1) {
+ search->cindx = TAILQ_NEXT(search->cindx, next);
+ if (search->cindx != NULL) {
+ rc = BT_SUCCESS;
+ bzero(&key, sizeof(key));
+ key.data = search->cindx->prefix;
+ key.size = strlen(key.data);
+ log_debug("re-init cursor on [%s]", key.data);
+ op = BT_CURSOR;
+ continue;
+ }
+ }
+
+ if (rc != BT_SUCCESS) {
+ if (rc == BT_FAIL) {
+ log_warnx("btree failure");
+ reason = LDAP_OTHER;
+ }
+ break;
+ }
+
+ search->nscanned++;
+
+ if (search->plan->indexed) {
+ bcopy(&key, &ikey, sizeof(key));
+ bzero(&key, sizeof(key));
+ btval_reset(&val);
+
+ rc = index_to_dn(search->ns, &ikey, &key);
+ btval_reset(&ikey);
+ if (rc != 0) {
+ reason = LDAP_OTHER;
+ break;
+ }
+
+ log_debug("lookup indexed key [%.*s]",
+ (int)key.size, (char *)key.data);
+
+ /* verify entry is a direct subordinate */
+ if (search->scope == LDAP_SCOPE_ONELEVEL &&
+ !is_child_of(&key, search->basedn)) {
+ log_debug("not a direct subordinate of base");
+ btval_reset(&key);
+ continue;
+ }
+
+ if (search->plan->indexed > 1 && is_dup(search, &key)) {
+ log_debug("skipping duplicate dn %.*s",
+ (int)key.size, (char *)key.data);
+ search->ndups++;
+ btval_reset(&key);
+ continue;
+ }
+ rc = btree_get(search->ns->data_db, &key, &val);
+ if (rc == BT_FAIL) {
+ log_warnx("btree failure");
+ reason = LDAP_OTHER;
+ btval_reset(&key);
+ break;
+ } else if (rc == BT_NOTFOUND) {
+ log_warnx("indexed key [%.*s] doesn't exist!",
+ (int)key.size, (char *)key.data);
+ rc = BT_SUCCESS;
+ btval_reset(&key);
+ continue;
+ }
+ }
+
+ log_debug("found dn %.*s", (int)key.size, (char *)key.data);
+
+ if (!has_suffix(&key, search->basedn)) {
+ btval_reset(&val);
+ btval_reset(&key);
+ if (search->plan->indexed)
+ continue;
+ else {
+ log_debug("scanned past basedn suffix");
+ rc = 1;
+ break;
+ }
+ }
+
+ rc = check_search_entry(&key, &val, search);
+ btval_reset(&val);
+ if (rc == BT_SUCCESS && search->plan->indexed > 1)
+ rc = mk_dup(search, &key);
+
+ btval_reset(&key);
+
+ /* Check if we have passed the size limit. */
+ if (rc == BT_SUCCESS && search->szlim > 0 &&
+ search->nmatched >= search->szlim) {
+ log_debug("search %i/%i has reached size limit (%u)",
+ search->conn->fd, search->req->msgid,
+ search->szlim);
+ reason = LDAP_SIZELIMIT_EXCEEDED;
+ rc = BT_NOTFOUND;
+ }
+ }
+
+ /* Check if we have passed the time limit. */
+ now = time(0);
+ if (rc == 0 && search->tmlim > 0 &&
+ search->started_at + search->tmlim <= now) {
+ log_debug("search %i/%i has reached time limit (%u)",
+ search->conn->fd, search->req->msgid,
+ search->tmlim);
+ reason = LDAP_TIMELIMIT_EXCEEDED;
+ rc = 1;
+ ++stats.timeouts;
+ }
+
+ if (rc == 0) {
+ bufferevent_enable(search->conn->bev, EV_WRITE);
+ } else {
+ log_debug("%u scanned, %u matched, %u dups",
+ search->nscanned, search->nmatched, search->ndups);
+ send_ldap_result(conn, search->req->msgid,
+ LDAP_RES_SEARCH_RESULT, reason);
+ if (rc == -1)
+ log_debug("search failed");
+ search_close(search);
+ }
+}
+
+static void
+ldap_search_root_dse(struct search *search)
+{
+ struct namespace *ns;
+ struct ber_element *root, *elm, *key, *val;
+
+ if ((root = ber_add_sequence(NULL)) == NULL) {
+ return;
+ }
+
+ elm = ber_add_sequence(root);
+ key = ber_add_string(elm, "objectClass");
+ val = ber_add_set(key);
+ ber_add_string(val, "top");
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "supportedLDAPVersion");
+ val = ber_add_set(key);
+ ber_add_string(val, "3");
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "namingContexts");
+ val = ber_add_set(key);
+ TAILQ_FOREACH(ns, &conf->namespaces, next)
+ val = ber_add_string(val, ns->suffix);
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "supportedExtension");
+ val = ber_add_set(key);
+ val = ber_add_string(val, "1.3.6.1.4.1.1466.20037"); /* StartTLS */
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "supportedFeatures");
+ val = ber_add_set(key);
+ /* All Operational Attributes (RFC 3673) */
+ val = ber_add_string(val, "1.3.6.1.4.1.4203.1.5.1");
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "subschemaSubentry");
+ val = ber_add_set(key);
+ ber_add_string(val, "cn=schema");
+
+ if ((search->conn->s_flags & F_SECURE) == F_SECURE) {
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "supportedSASLMechanisms");
+ val = ber_add_set(key);
+ ber_add_string(val, "PLAIN");
+ }
+
+ search_result("", 0, root, search);
+ ber_free_elements(root);
+ send_ldap_result(search->conn, search->req->msgid,
+ LDAP_RES_SEARCH_RESULT, LDAP_SUCCESS);
+ search_close(search);
+}
+
+static void
+ldap_search_subschema(struct search *search)
+{
+ struct ber_element *root, *elm, *key, *val;
+
+ if ((root = ber_add_sequence(NULL)) == NULL) {
+ return;
+ }
+
+ elm = ber_add_sequence(root);
+ key = ber_add_string(elm, "objectClass");
+ val = ber_add_set(key);
+ val = ber_add_string(val, "top");
+ ber_add_string(val, "subschema");
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "createTimestamp");
+ val = ber_add_set(key);
+ ber_add_string(val, ldap_strftime(stats.started_at));
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "modifyTimestamp");
+ val = ber_add_set(key);
+ ber_add_string(val, ldap_strftime(stats.started_at));
+
+ elm = ber_add_sequence(elm);
+ key = ber_add_string(elm, "subschemaSubentry");
+ val = ber_add_set(key);
+ ber_add_string(val, "cn=schema");
+
+ search_result("cn=schema", 9, root, search);
+ ber_free_elements(root);
+ send_ldap_result(search->conn, search->req->msgid,
+ LDAP_RES_SEARCH_RESULT, LDAP_SUCCESS);
+ search_close(search);
+}
+
+static int
+add_index(struct plan *plan, const char *fmt, ...)
+{
+ struct index *indx;
+ va_list ap;
+
+ if ((indx = calloc(1, sizeof(*indx))) == NULL)
+ return -1;
+
+ va_start(ap, fmt);
+ vasprintf(&indx->prefix, fmt, ap);
+ va_end(ap);
+
+ normalize_dn(indx->prefix);
+
+ TAILQ_INSERT_TAIL(&plan->indices, indx, next);
+ plan->indexed++;
+
+ return 0;
+}
+
+static struct plan *
+search_planner(struct namespace *ns, struct ber_element *filter)
+{
+ int class;
+ unsigned long type;
+ char *s, *attr;
+ struct ber_element *elm;
+ struct index *indx;
+ struct plan *plan, *arg = NULL;
+
+ if (filter->be_class != BER_CLASS_CONTEXT) {
+ log_warnx("invalid class %d in filter", filter->be_class);
+ return NULL;
+ }
+
+ if ((plan = calloc(1, sizeof(*plan))) == NULL) {
+ log_warn("search_planner: calloc");
+ return NULL;
+ }
+ TAILQ_INIT(&plan->args);
+ TAILQ_INIT(&plan->indices);
+
+ switch (filter->be_type) {
+ case LDAP_FILT_EQ:
+ case LDAP_FILT_APPR:
+ if (ber_scanf_elements(filter, "{ss", &attr, &s) != 0)
+ goto fail;
+ if (namespace_has_index(ns, attr, INDEX_EQUAL))
+ add_index(plan, "%s=%s,", attr, s);
+ break;
+ case LDAP_FILT_SUBS:
+ if (ber_scanf_elements(filter, "{s{ts",
+ &attr, &class, &type, &s) != 0)
+ goto fail;
+ if (class == BER_CLASS_CONTEXT && type == LDAP_FILT_SUBS_INIT) {
+ /* only prefix substrings usable for index */
+ if (namespace_has_index(ns, attr, INDEX_EQUAL))
+ add_index(plan, "%s=%s", attr, s);
+ }
+ break;
+ case LDAP_FILT_PRES:
+ if (ber_scanf_elements(filter, "s", &attr) != 0)
+ goto fail;
+ if (strcasecmp(attr, "objectClass") != 0) {
+ if (namespace_has_index(ns, attr, INDEX_PRESENCE))
+ add_index(plan, "!%s,", attr);
+ }
+ break;
+ case LDAP_FILT_AND:
+ if (ber_scanf_elements(filter, "{e", &elm) != 0)
+ goto fail;
+ for (; elm; elm = elm->be_next) {
+ if ((arg = search_planner(ns, elm)) == NULL)
+ goto fail;
+ TAILQ_INSERT_TAIL(&plan->args, arg, next);
+ }
+ /* select an index to use */
+ TAILQ_FOREACH(arg, &plan->args, next) {
+ if (arg->indexed) {
+ while ((indx = TAILQ_FIRST(&arg->indices))) {
+ TAILQ_REMOVE(&arg->indices, indx, next);
+ TAILQ_INSERT_TAIL(&plan->indices, indx,
+ next);
+ }
+ plan->indexed = arg->indexed;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILT_OR:
+ if (ber_scanf_elements(filter, "{e", &elm) != 0)
+ goto fail;
+ for (; elm; elm = elm->be_next) {
+ if ((arg = search_planner(ns, elm)) == NULL)
+ goto fail;
+ TAILQ_INSERT_TAIL(&plan->args, arg, next);
+ }
+ TAILQ_FOREACH(arg, &plan->args, next) {
+ if (!arg->indexed) {
+ plan->indexed = 0;
+ break;
+ }
+ while ((indx = TAILQ_FIRST(&arg->indices))) {
+ TAILQ_REMOVE(&arg->indices, indx, next);
+ TAILQ_INSERT_TAIL(&plan->indices, indx,next);
+ plan->indexed++;
+ }
+ }
+ break;
+ default:
+ log_warnx("filter type %d not implemented", filter->be_type);
+ break;
+ }
+
+ return plan;
+
+fail:
+ free(plan);
+ return NULL;
+}
+
+void
+filter_free(struct plan *filter)
+{
+ struct index *indx;
+ struct plan *arg;
+
+ if (filter) {
+ while ((arg = TAILQ_FIRST(&filter->args)) != NULL) {
+ TAILQ_REMOVE(&filter->args, arg, next);
+ filter_free(arg);
+ }
+ while ((indx = TAILQ_FIRST(&filter->indices)) != NULL) {
+ TAILQ_REMOVE(&filter->indices, indx, next);
+ free(indx->prefix);
+ free(indx);
+ }
+ free(filter);
+ }
+}
+
+int
+ldap_search(struct request *req)
+{
+ long long reason = LDAP_OTHER;
+ struct search *search = NULL;
+
+ if (stats.searches > MAX_SEARCHES) {
+ log_warnx("refusing more than %u concurrent searches",
+ MAX_SEARCHES);
+ reason = LDAP_BUSY;
+ goto done;
+ }
+ ++stats.searches;
+ ++stats.req_search;
+
+ if ((search = calloc(1, sizeof(*search))) == NULL)
+ return -1;
+ search->req = req;
+ search->conn = req->conn;
+ search->init = 0;
+ search->started_at = time(0);
+ TAILQ_INSERT_HEAD(&req->conn->searches, search, next);
+ RB_INIT(&search->uniqdns);
+
+ if (ber_scanf_elements(req->op, "{sEEiibeSeS",
+ &search->basedn,
+ &search->scope,
+ &search->deref,
+ &search->szlim,
+ &search->tmlim,
+ &search->typesonly,
+ &search->filter,
+ &search->attrlist) != 0)
+ {
+ log_warnx("failed to parse search request");
+ reason = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ normalize_dn(search->basedn);
+ log_debug("base dn = %s, scope = %d", search->basedn, search->scope);
+
+ if (*search->basedn == '\0') {
+ /* request for the root DSE */
+ if (!authorized(req->conn, NULL, ACI_READ, "",
+ LDAP_SCOPE_BASE)) {
+ reason = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+ if (search->scope != LDAP_SCOPE_BASE) {
+ /* only base searches are valid */
+ reason = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+ /* TODO: verify filter is (objectClass=*) */
+ ldap_search_root_dse(search);
+ return 0;
+ }
+
+ if (strcasecmp(search->basedn, "cn=schema") == 0) {
+ /* request for the subschema subentries */
+ if (!authorized(req->conn, NULL, ACI_READ,
+ "cn=schema", LDAP_SCOPE_BASE)) {
+ reason = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+ if (search->scope != LDAP_SCOPE_BASE) {
+ /* only base searches are valid */
+ reason = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+ /* TODO: verify filter is (objectClass=subschema) */
+ ldap_search_subschema(search);
+ return 0;
+ }
+
+ if ((search->ns = namespace_for_base(search->basedn)) == NULL) {
+ log_debug("no database configured for suffix %s",
+ search->basedn);
+ reason = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+
+ if (!authorized(req->conn, search->ns, ACI_READ,
+ search->basedn, search->scope)) {
+ reason = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ if (search->scope == LDAP_SCOPE_BASE) {
+ struct btval key, val;
+
+ bzero(&key, sizeof(key));
+ bzero(&val, sizeof(val));
+ key.data = search->basedn;
+ key.size = strlen(key.data);
+ switch (btree_get(search->ns->data_db, &key, &val)) {
+ case BT_SUCCESS:
+ check_search_entry(&key, &val, search);
+ btval_reset(&val);
+ /* FALLTHROUGH */
+ case BT_NOTFOUND:
+ reason = LDAP_SUCCESS;
+ goto done;
+ case BT_FAIL:
+ reason = LDAP_OTHER;
+ goto done;
+ }
+
+ return 0;
+ }
+
+ search->plan = search_planner(search->ns, search->filter);
+ if (search->plan == NULL) {
+ reason = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ if (!search->plan->indexed && search->scope == LDAP_SCOPE_ONELEVEL) {
+ int sz;
+ sz = strlen(search->basedn) - strlen(search->ns->suffix);
+ if (sz > 0 && search->basedn[sz - 1] == ',')
+ sz--;
+ add_index(search->plan, "@%.*s,", sz, search->basedn);
+ }
+
+ if (!search->plan->indexed)
+ ++stats.unindexed;
+
+ bufferevent_enable(req->conn->bev, EV_WRITE);
+ return 0;
+
+done:
+ send_ldap_result(req->conn, req->msgid, LDAP_RES_SEARCH_RESULT, reason);
+ if (search)
+ search_close(search);
+ return 0;
+}
+
diff --git a/usr.sbin/ldapd/ssl.c b/usr.sbin/ldapd/ssl.c
new file mode 100644
index 00000000000..7d320bf6b93
--- /dev/null
+++ b/usr.sbin/ldapd/ssl.c
@@ -0,0 +1,710 @@
+/* $OpenBSD: ssl.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+
+#include "ldapd.h"
+
+#define SSL_CIPHERS "HIGH"
+
+void ssl_error(const char *);
+char *ssl_load_file(const char *, off_t *);
+SSL_CTX *ssl_ctx_create(void);
+void ssl_session_accept(int, short, void *);
+void ssl_read(int, short, void *);
+void ssl_write(int, short, void *);
+int ssl_bufferevent_add(struct event *, int);
+void ssl_connect(int, short, void *);
+
+SSL *ssl_client_init(int, char *, size_t, char *, size_t);
+
+int ssl_buf_read(SSL *, struct ibuf_read *);
+int ssl_buf_write(SSL *, struct msgbuf *);
+
+DH *get_dh512(void);
+void ssl_set_ephemeral_key_exchange(SSL_CTX *);
+
+extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+ size_t, void *);
+
+/* From OpenSSL's documentation:
+ *
+ * If "strong" primes were used to generate the DH parameters, it is
+ * not strictly necessary to generate a new key for each handshake
+ * but it does improve forward secrecy.
+ *
+ * These are the parameters used by both sendmail and openssl's
+ * s_server.
+ *
+ * -- gilles@
+ */
+
+unsigned char dh512_p[] = {
+ 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
+ 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
+ 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
+ 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
+ 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
+ 0x47,0x74,0xE8,0x33,
+};
+
+unsigned char dh512_g[] = {
+ 0x02,
+};
+
+#if 0
+void
+ssl_connect(int fd, short event, void *p)
+{
+ struct conn *s = p;
+ int ret;
+ int retry_flag;
+ int ssl_err;
+
+ if (event == EV_TIMEOUT) {
+ log_debug("ssl_connect: session timed out");
+ conn_close(s);
+ return;
+ }
+
+ ret = SSL_connect(s->s_ssl);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ retry_flag = EV_READ;
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ retry_flag = EV_WRITE;
+ goto retry;
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ log_debug("session destroy in MTA #1");
+ conn_close(s);
+ return;
+ }
+ /* FALLTHROUGH */
+ default:
+ ssl_error("ssl_session_connect");
+ conn_close(s);
+ return;
+ }
+ }
+
+ event_set(&s->bev->ev_read, s->fd, EV_READ, ssl_read, s->bev);
+ event_set(&s->bev->ev_write, s->fd, EV_WRITE, ssl_write, s->bev);
+
+ log_info("ssl_connect: connected to remote ssl server");
+ bufferevent_enable(s->bev, EV_READ|EV_WRITE);
+ s->s_flags |= F_SECURE;
+
+ if (s->s_flags & F_PEERHASTLS) {
+ session_respond(s, "EHLO %s", s->s_env->sc_hostname);
+ }
+
+ return;
+retry:
+ event_add(&s->s_ev, &s->s_tv);
+}
+#endif
+
+void
+ssl_read(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct conn *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+ size_t len;
+ char rbuf[IBUF_READ_SIZE];
+ int howmuch = IBUF_READ_SIZE;
+
+ what = EVBUFFER_READ;
+ ret = ssl_err = 0;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (bufev->wm_read.high != 0)
+ howmuch = MIN(sizeof(rbuf), bufev->wm_read.high);
+
+ ret = SSL_read(s->s_ssl, rbuf, howmuch);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_read");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+
+ if (evbuffer_add(bufev->input, rbuf, ret) == -1) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+ len = EVBUFFER_LENGTH(bufev->input);
+ if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+ return;
+ if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+ struct evbuffer *buf = bufev->input;
+ event_del(&bufev->ev_read);
+ evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+ return;
+ }
+
+ if (bufev->readcb != NULL)
+ (*bufev->readcb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+ return;
+
+err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+
+void
+ssl_write(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct conn *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+
+ ret = 0;
+ what = EVBUFFER_WRITE;
+
+ if (event == EV_TIMEOUT) {
+ what |= EV_TIMEOUT;
+ goto err;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output)) {
+ if (s->s_buf == NULL) {
+ s->s_buflen = EVBUFFER_LENGTH(bufev->output);
+ if ((s->s_buf = malloc(s->s_buflen)) == NULL) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ memcpy(s->s_buf, EVBUFFER_DATA(bufev->output),
+ s->s_buflen);
+ }
+
+ ret = SSL_write(s->s_ssl, s->s_buf, s->s_buflen);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_write");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+ evbuffer_drain(bufev->output, ret);
+ }
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ if (EVBUFFER_LENGTH(bufev->output) != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+ if (bufev->writecb != NULL &&
+ EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+ (*bufev->writecb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ if (s->s_buflen != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+ return;
+
+err:
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+int
+ssl_bufferevent_add(struct event *ev, int timeout)
+{
+ struct timeval tv;
+ struct timeval *ptv = NULL;
+
+ if (timeout) {
+ timerclear(&tv);
+ tv.tv_sec = timeout;
+ ptv = &tv;
+ }
+
+ return (event_add(ev, ptv));
+}
+
+int
+ssl_cmp(struct ssl *s1, struct ssl *s2)
+{
+ return (strcmp(s1->ssl_name, s2->ssl_name));
+}
+
+SPLAY_GENERATE(ssltree, ssl, ssl_nodes, ssl_cmp);
+
+char *
+ssl_load_file(const char *name, off_t *len)
+{
+ struct stat st;
+ off_t size;
+ char *buf = NULL;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY)) == -1)
+ return (NULL);
+ if (fstat(fd, &st) != 0)
+ goto fail;
+ size = st.st_size;
+ if ((buf = calloc(1, size + 1)) == NULL)
+ goto fail;
+ if (read(fd, buf, size) != size)
+ goto fail;
+ close(fd);
+
+ *len = size + 1;
+ return (buf);
+
+fail:
+ if (buf != NULL)
+ free(buf);
+ close(fd);
+ return (NULL);
+}
+
+SSL_CTX *
+ssl_ctx_create(void)
+{
+ SSL_CTX *ctx;
+
+ ctx = SSL_CTX_new(SSLv23_method());
+ if (ctx == NULL) {
+ ssl_error("ssl_ctx_create");
+ fatal("ssl_ctx_create: could not create SSL context");
+ }
+
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+ SSL_CTX_set_timeout(ctx, LDAPD_SESSION_TIMEOUT);
+ SSL_CTX_set_options(ctx, SSL_OP_ALL);
+ SSL_CTX_set_options(ctx,
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ if (!SSL_CTX_set_cipher_list(ctx, SSL_CIPHERS)) {
+ ssl_error("ssl_ctx_create");
+ fatal("ssl_ctx_create: could not set cipher list");
+ }
+ return (ctx);
+}
+
+int
+ssl_load_certfile(struct ldapd_config *env, const char *name, u_int8_t flags)
+{
+ struct ssl *s;
+ struct ssl key;
+ char certfile[PATH_MAX];
+
+ if (strlcpy(key.ssl_name, name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name)) {
+ log_warn("ssl_load_certfile: certificate name truncated");
+ return -1;
+ }
+
+ s = SPLAY_FIND(ssltree, env->sc_ssl, &key);
+ if (s != NULL) {
+ s->flags |= flags;
+ return 0;
+ }
+
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+
+ s->flags = flags;
+ (void)strlcpy(s->ssl_name, key.ssl_name, sizeof(s->ssl_name));
+
+ if ((name[0] == '/' &&
+ !bsnprintf(certfile, sizeof(certfile), "%s.crt", name)) ||
+ !bsnprintf(certfile, sizeof(certfile), "/etc/ldap/certs/%s.crt",
+ name)) {
+ log_warn("ssl_load_certfile: path truncated");
+ free(s);
+ return -1;
+ }
+
+ log_debug("loading certificate file %s", certfile);
+ if ((s->ssl_cert = ssl_load_file(certfile, &s->ssl_cert_len)) == NULL) {
+ free(s);
+ return (-1);
+ }
+
+ if ((name[0] == '/' &&
+ !bsnprintf(certfile, sizeof(certfile), "%s.key", name)) ||
+ !bsnprintf(certfile, sizeof(certfile), "/etc/ldap/certs/%s.key",
+ name)) {
+ log_warn("ssl_load_certfile: path truncated");
+ free(s->ssl_cert);
+ free(s);
+ return -1;
+ }
+
+ log_debug("loading key file %s", certfile);
+ if ((s->ssl_key = ssl_load_file(certfile, &s->ssl_key_len)) == NULL) {
+ free(s->ssl_cert);
+ free(s);
+ return (-1);
+ }
+
+ SPLAY_INSERT(ssltree, env->sc_ssl, s);
+
+ return (0);
+}
+
+void
+ssl_init(void)
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ OpenSSL_add_all_algorithms();
+
+ /* Init hardware crypto engines. */
+ ENGINE_load_builtin_engines();
+ ENGINE_register_all_complete();
+}
+
+void
+ssl_setup(struct ldapd_config *env, struct listener *l)
+{
+ struct ssl key;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ if (strlcpy(key.ssl_name, l->ssl_cert_name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name))
+ fatal("ssl_setup: certificate name truncated");
+
+ if ((l->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &key)) == NULL)
+ fatal("ssl_setup: certificate tree corrupted");
+
+ l->ssl_ctx = ssl_ctx_create();
+
+ if (!ssl_ctx_use_certificate_chain(l->ssl_ctx,
+ l->ssl->ssl_cert, l->ssl->ssl_cert_len))
+ goto err;
+ if (!ssl_ctx_use_private_key(l->ssl_ctx,
+ l->ssl->ssl_key, l->ssl->ssl_key_len))
+ goto err;
+
+ if (!SSL_CTX_check_private_key(l->ssl_ctx))
+ goto err;
+ if (!SSL_CTX_set_session_id_context(l->ssl_ctx,
+ (const unsigned char *)l->ssl_cert_name, strlen(l->ssl_cert_name) + 1))
+ goto err;
+
+ ssl_set_ephemeral_key_exchange(l->ssl_ctx);
+
+ log_debug("ssl_setup: ssl setup finished for listener: %p", l);
+ return;
+
+err:
+ if (l->ssl_ctx != NULL)
+ SSL_CTX_free(l->ssl_ctx);
+ ssl_error("ssl_setup");
+ fatal("ssl_setup: cannot set SSL up");
+ return;
+}
+
+void
+ssl_error(const char *where)
+{
+ unsigned long code;
+ char errbuf[128];
+ extern int debug;
+
+ if (!debug)
+ return;
+ for (; (code = ERR_get_error()) != 0 ;) {
+ ERR_error_string_n(code, errbuf, sizeof(errbuf));
+ log_debug("SSL library error: %s: %s", where, errbuf);
+ }
+}
+
+void
+ssl_session_accept(int fd, short event, void *p)
+{
+ struct conn *s = p;
+ int ret;
+ int retry_flag;
+ int ssl_err;
+
+ if (event == EV_TIMEOUT) {
+ log_debug("ssl_session_accept: session timed out");
+ conn_close(s);
+ return;
+ }
+
+ retry_flag = ssl_err = 0;
+
+ log_debug("ssl_session_accept: accepting client");
+ ret = SSL_accept(s->s_ssl);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ retry_flag = EV_READ;
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ retry_flag = EV_WRITE;
+ goto retry;
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ conn_close(s);
+ return;
+ }
+ /* FALLTHROUGH */
+ default:
+ ssl_error("ssl_session_accept");
+ conn_close(s);
+ return;
+ }
+ }
+
+ log_debug("ssl_session_accept: accepted ssl client");
+ s->s_flags |= F_SECURE;
+
+ s->bev = bufferevent_new(s->fd, conn_read, conn_write, conn_err, s);
+ if (s->bev == NULL) {
+ log_warn("ssl_session_accept: bufferevent_new");
+ conn_close(s);
+ return;
+ }
+ bufferevent_settimeout(s->bev, 0, 60);
+
+ event_set(&s->bev->ev_read, s->fd, EV_READ, ssl_read, s->bev);
+ event_set(&s->bev->ev_write, s->fd, EV_WRITE, ssl_write, s->bev);
+ bufferevent_enable(s->bev, EV_READ);
+
+ return;
+retry:
+ event_add(&s->s_ev, &s->s_tv);
+}
+
+void
+ssl_session_init(struct conn *s)
+{
+ struct listener *l;
+ SSL *ssl;
+
+ l = s->s_l;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ log_debug("ssl_session_init: switching to SSL");
+ ssl = SSL_new(l->ssl_ctx);
+ if (ssl == NULL)
+ goto err;
+
+ if (!SSL_set_ssl_method(ssl, SSLv23_server_method()))
+ goto err;
+ if (!SSL_set_fd(ssl, s->fd))
+ goto err;
+ SSL_set_accept_state(ssl);
+
+ s->s_ssl = ssl;
+
+ s->s_tv.tv_sec = LDAPD_SESSION_TIMEOUT;
+ s->s_tv.tv_usec = 0;
+ event_set(&s->s_ev, s->fd, EV_READ|EV_TIMEOUT, ssl_session_accept, s);
+ event_add(&s->s_ev, &s->s_tv);
+ return;
+
+ err:
+ if (ssl != NULL)
+ SSL_free(ssl);
+ ssl_error("ssl_session_init");
+}
+
+SSL *
+ssl_client_init(int fd, char *cert, size_t certsz, char *key, size_t keysz)
+{
+ SSL_CTX *ctx;
+ SSL *ssl = NULL;
+ int rv = -1;
+
+ ctx = ssl_ctx_create();
+
+ if (cert && key) {
+ if (!ssl_ctx_use_certificate_chain(ctx, cert, certsz))
+ goto done;
+ else if (!ssl_ctx_use_private_key(ctx, key, keysz))
+ goto done;
+ else if (!SSL_CTX_check_private_key(ctx))
+ goto done;
+ }
+
+ if ((ssl = SSL_new(ctx)) == NULL)
+ goto done;
+
+ if (!SSL_set_ssl_method(ssl, SSLv23_client_method()))
+ goto done;
+ if (!SSL_set_fd(ssl, fd))
+ goto done;
+ SSL_set_connect_state(ssl);
+
+ rv = 0;
+done:
+ if (rv) {
+ if (ssl)
+ SSL_free(ssl);
+ else if (ctx)
+ SSL_CTX_free(ctx);
+ ssl = NULL;
+ }
+ return (ssl);
+}
+
+void
+ssl_session_destroy(struct conn *s)
+{
+ SSL_free(s->s_ssl);
+}
+
+int
+ssl_buf_read(SSL *s, struct ibuf_read *r)
+{
+ char *buf = r->buf + r->wpos;
+ ssize_t bufsz = sizeof(r->buf) - r->wpos;
+ int ret;
+
+ if (bufsz == 0) {
+ errno = EMSGSIZE;
+ return (SSL_ERROR_SYSCALL);
+ }
+
+ if ((ret = SSL_read(s, buf, bufsz)) > 0)
+ r->wpos += ret;
+
+ return SSL_get_error(s, ret);
+}
+
+int
+ssl_buf_write(SSL *s, struct msgbuf *msgbuf)
+{
+ struct ibuf *buf;
+ int ret;
+
+ buf = TAILQ_FIRST(&msgbuf->bufs);
+ if (buf == NULL)
+ return (SSL_ERROR_NONE);
+
+ ret = SSL_write(s, buf->buf + buf->rpos, buf->wpos - buf->rpos);
+
+ if (ret > 0)
+ msgbuf_drain(msgbuf, ret);
+
+ return SSL_get_error(s, ret);
+}
+
+DH *
+get_dh512(void)
+{
+ DH *dh;
+
+ if ((dh = DH_new()) == NULL)
+ return NULL;
+
+ dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
+ dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
+ if (dh->p == NULL || dh->g == NULL)
+ return NULL;
+
+ return dh;
+}
+
+
+void
+ssl_set_ephemeral_key_exchange(SSL_CTX *ctx)
+{
+ DH *dh;
+
+ dh = get_dh512();
+ if (dh != NULL)
+ SSL_CTX_set_tmp_dh(ctx, dh);
+}
diff --git a/usr.sbin/ldapd/ssl_privsep.c b/usr.sbin/ldapd/ssl_privsep.c
new file mode 100644
index 00000000000..924e7b50c00
--- /dev/null
+++ b/usr.sbin/ldapd/ssl_privsep.c
@@ -0,0 +1,252 @@
+/* $OpenBSD: ssl_privsep.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to. The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code. The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * "This product includes cryptographic software written by
+ * Eric Young (eay@cryptsoft.com)"
+ * The word 'cryptographic' can be left out if the rouines from the library
+ * being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ * the apps directory (application code) you must include an acknowledgement:
+ * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed. i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
+/*
+ * SSL operations needed when running in a privilege separated environment.
+ * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard .
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include <openssl/err.h>
+#include <openssl/bio.h>
+#include <openssl/objects.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+
+int ssl_ctx_use_private_key(SSL_CTX *, char *, off_t);
+int ssl_ctx_use_certificate_chain(SSL_CTX *, char *, off_t);
+int ssl_ctx_load_verify_memory(SSL_CTX *, char *, off_t);
+int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **);
+
+X509_LOOKUP_METHOD x509_mem_lookup = {
+ "Load cert from memory",
+ NULL, /* new */
+ NULL, /* free */
+ NULL, /* init */
+ NULL, /* shutdown */
+ ssl_by_mem_ctrl, /* ctrl */
+ NULL, /* get_by_subject */
+ NULL, /* get_by_issuer_serial */
+ NULL, /* get_by_fingerprint */
+ NULL, /* get_by_alias */
+};
+
+#define X509_L_ADD_MEM 3
+
+int
+ssl_ctx_use_private_key(SSL_CTX *ctx, char *buf, off_t len)
+{
+ int ret;
+ BIO *in;
+ EVP_PKEY *pkey;
+
+ ret = 0;
+
+ if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB);
+ return 0;
+ }
+
+ pkey = PEM_read_bio_PrivateKey(in, NULL,
+ ctx->default_passwd_callback,
+ ctx->default_passwd_callback_userdata);
+
+ if (pkey == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB);
+ goto end;
+ }
+ ret = SSL_CTX_use_PrivateKey(ctx, pkey);
+ EVP_PKEY_free(pkey);
+end:
+ if (in != NULL)
+ BIO_free(in);
+ return ret;
+}
+
+int
+ssl_ctx_load_verify_memory(SSL_CTX *ctx, char *buf, off_t len)
+{
+ X509_LOOKUP *lu;
+ struct iovec iov;
+
+ if ((lu = X509_STORE_add_lookup(ctx->cert_store,
+ &x509_mem_lookup)) == NULL)
+ return (0);
+
+ iov.iov_base = buf;
+ iov.iov_len = len;
+
+ if (!ssl_by_mem_ctrl(lu, X509_L_ADD_MEM,
+ (const char *)&iov, X509_FILETYPE_PEM, NULL))
+ return (0);
+
+ return (1);
+}
+
+int
+ssl_by_mem_ctrl(X509_LOOKUP *lu, int cmd, const char *buf,
+ long type, char **ret)
+{
+ STACK_OF(X509_INFO) *inf;
+ const struct iovec *iov;
+ X509_INFO *itmp;
+ BIO *in = NULL;
+ int i, count = 0;
+
+ iov = (const struct iovec *)buf;
+
+ if (type != X509_FILETYPE_PEM)
+ goto done;
+
+ if ((in = BIO_new_mem_buf(iov->iov_base, iov->iov_len)) == NULL)
+ goto done;
+
+ if ((inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL)) == NULL)
+ goto done;
+
+ for(i = 0; i < sk_X509_INFO_num(inf); i++) {
+ itmp = sk_X509_INFO_value(inf, i);
+ if(itmp->x509) {
+ X509_STORE_add_cert(lu->store_ctx, itmp->x509);
+ count++;
+ }
+ if(itmp->crl) {
+ X509_STORE_add_crl(lu->store_ctx, itmp->crl);
+ count++;
+ }
+ }
+ sk_X509_INFO_pop_free(inf, X509_INFO_free);
+
+done:
+ if (!count)
+ X509err(X509_F_X509_LOAD_CERT_CRL_FILE,ERR_R_PEM_LIB);
+
+ if (in != NULL)
+ BIO_free(in);
+ return (count);
+}
+
+int
+ssl_ctx_use_certificate_chain(SSL_CTX *ctx, char *buf, off_t len)
+{
+ int ret;
+ BIO *in;
+ X509 *x;
+ X509 *ca;
+ unsigned long err;
+
+ ret = 0;
+ x = ca = NULL;
+
+ if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB);
+ goto end;
+ }
+
+ if ((x = PEM_read_bio_X509(in, NULL,
+ ctx->default_passwd_callback,
+ ctx->default_passwd_callback_userdata)) == NULL) {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB);
+ goto end;
+ }
+
+ if (!SSL_CTX_use_certificate(ctx, x) || ERR_peek_error() != 0)
+ goto end;
+
+ /* If we could set up our certificate, now proceed to
+ * the CA certificates.
+ */
+
+ if (ctx->extra_certs != NULL) {
+ sk_X509_pop_free(ctx->extra_certs, X509_free);
+ ctx->extra_certs = NULL;
+ }
+
+ while ((ca = PEM_read_bio_X509(in, NULL,
+ ctx->default_passwd_callback,
+ ctx->default_passwd_callback_userdata)) != NULL) {
+
+ if (!SSL_CTX_add_extra_chain_cert(ctx, ca))
+ goto end;
+ }
+
+ err = ERR_peek_last_error();
+ if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
+ ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
+ ERR_clear_error();
+ else
+ goto end;
+
+ ret = 1;
+end:
+ if (ca != NULL)
+ X509_free(ca);
+ if (x != NULL)
+ X509_free(x);
+ if (in != NULL)
+ BIO_free(in);
+ return (ret);
+}
diff --git a/usr.sbin/ldapd/util.c b/usr.sbin/ldapd/util.c
new file mode 100644
index 00000000000..752ee89463d
--- /dev/null
+++ b/usr.sbin/ldapd/util.c
@@ -0,0 +1,98 @@
+/* $OpenBSD: util.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "ldapd.h"
+
+int
+bsnprintf(char *str, size_t size, const char *format, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, format);
+ ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+ if (ret == -1 || ret >= (int)size)
+ return 0;
+
+ return 1;
+}
+
+/* Normalize a DN in preparation for searches.
+ * Modifies its argument.
+ * Currently only made lowercase, and spaces around comma is removed.
+ * TODO: unescape backslash escapes, handle UTF-8.
+ */
+void
+normalize_dn(char *dn)
+{
+ size_t n;
+ char *s, *p;
+
+ for (s = p = dn; *s != '\0'; s++) {
+ if (*s == ' ') {
+ if (p == dn || p[-1] == ',')
+ continue;
+ n = strspn(s, " ");
+ if (s[n] == '\0' || s[n] == ',')
+ continue;
+ }
+ *p++ = tolower(*s);
+ }
+ *p = '\0';
+}
+
+/* Returns true (1) if key ends with suffix.
+ */
+int
+has_suffix(struct btval *key, const char *suffix)
+{
+ size_t slen;
+
+ slen = strlen(suffix);
+
+ if (key->size < slen)
+ return 0;
+ return (bcmp((char *)key->data + key->size - slen, suffix, slen) == 0);
+}
+
+/* Returns true (1) if key begins with prefix.
+ */
+int
+has_prefix(struct btval *key, const char *prefix)
+{
+ size_t pfxlen;
+
+ pfxlen = strlen(prefix);
+ if (pfxlen > key->size)
+ return 0;
+ return (memcmp(key->data, prefix, pfxlen) == 0);
+}
+
diff --git a/usr.sbin/ldapd/uuid.c b/usr.sbin/ldapd/uuid.c
new file mode 100644
index 00000000000..4b59d29ba7d
--- /dev/null
+++ b/usr.sbin/ldapd/uuid.c
@@ -0,0 +1,361 @@
+/* $OpenBSD: uuid.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+/*
+ * Copyright (c) 2002, Stockholms Universitet
+ * (Stockholm University, Stockholm Sweden)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the university nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * NCS/DCE/AFS/GUID generator
+ *
+ * for more information about DCE UUID, see
+ * <http://www.opengroup.org/onlinepubs/9629399/apdxa.htm>
+ *
+ * Note, the Microsoft GUID is a DCE UUID, but it seems like they
+ * folded in the seq num with the node part. That would explain how
+ * the reserved field have a bit pattern 110 when reserved is a 2 bit
+ * field.
+ *
+ * XXX should hash the node address for privacy issues
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <sys/file.h>
+
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "uuid.h"
+
+static afsUUID niluuid;
+static uint32_t seq_num;
+static struct timeval last_time;
+static int32_t counter;
+static char nodeaddr[6];
+
+enum { UUID_NODE_MULTICAST = 0x80 };
+
+static int
+time_cmp(struct timeval *tv1, struct timeval *tv2)
+{
+ if (tv1->tv_sec > tv2->tv_sec)
+ return -1;
+ if (tv1->tv_sec < tv2->tv_sec)
+ return 1;
+ if (tv1->tv_usec > tv2->tv_usec)
+ return -1;
+ if (tv1->tv_usec < tv2->tv_usec)
+ return 1;
+ return 0;
+}
+
+static void
+get_node_addr(char *addr)
+{
+ struct ifaddrs *ifa, *ifa0;
+ int found_mac = 0;
+
+ if (getifaddrs(&ifa0) != 0)
+ ifa0 = NULL;
+
+ for (ifa = ifa0; ifa != NULL && !found_mac; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+
+#if IFF_LOOPBACK
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ continue;
+#endif
+
+ switch (ifa->ifa_addr->sa_family) {
+#ifdef AF_LINK
+ case AF_LINK: {
+ struct sockaddr_dl *dl = (struct sockaddr_dl *)ifa->ifa_addr;
+
+ switch (dl->sdl_type) {
+ case IFT_ETHER:
+ case IFT_FDDI:
+ if (dl->sdl_alen == 6) {
+ memcpy(addr, LLADDR(dl), 6);
+ found_mac = 1;
+ }
+ }
+
+ }
+#endif
+ default:
+ break;
+ }
+ }
+
+ if (ifa0 != NULL)
+ freeifaddrs(ifa0);
+
+ if (!found_mac) {
+ /*
+ * Set the multicast bit to make sure we won't collide with an
+ * allocated (mac) address.
+ */
+ arc4random_buf(addr, 6);
+ addr[0] |= UUID_NODE_MULTICAST;
+ }
+ return;
+}
+
+/*
+ * Compares two UUIDs
+ */
+
+int
+uuid_compare(const afsUUID *uuid1, const afsUUID *uuid2)
+{
+ if (memcmp(uuid1, uuid2, sizeof(*uuid1)) == 0)
+ return 0;
+ return 1;
+}
+
+/*
+ * Creates a new UUID.
+ */
+
+int
+uuid_create(afsUUID *uuid)
+{
+ static int uuid_inited = 0;
+ struct timeval tv;
+ int ret, got_time;
+ uint64_t dce_time;
+
+ if (uuid_inited == 0) {
+ gettimeofday(&last_time, NULL);
+ seq_num = arc4random();
+ get_node_addr(nodeaddr);
+ uuid_inited = 1;
+ }
+
+ gettimeofday(&tv, NULL);
+
+ got_time = 0;
+
+ do {
+ ret = time_cmp(&tv, &last_time);
+ if (ret < 0) {
+ /* Time went backward, just inc seq_num and be done.
+ * seq_num is 6 + 8 bit field it the uuid, so let it wrap
+ * around. don't let it be zero.
+ */
+ seq_num = (seq_num + 1) & 0x3fff ;
+ if (seq_num == 0)
+ seq_num++;
+ got_time = 1;
+ counter = 0;
+ last_time = tv;
+ } else if (ret > 0) {
+ /* time went forward, reset counter and be happy */
+ last_time = tv;
+ counter = 0;
+ got_time = 1;
+ } else {
+#define UUID_MAX_HZ (1) /* make this bigger fix you have larger tickrate */
+#define MULTIPLIER_100_NANO_SEC 10
+ if (++counter < UUID_MAX_HZ * MULTIPLIER_100_NANO_SEC)
+ got_time = 1;
+ }
+ } while(!got_time);
+
+ /*
+ * now shift time to dce_time, epoch 00:00:00:00, 15 October 1582
+ * dce time ends year ~3400, so start to worry now
+ */
+
+ dce_time = tv.tv_usec * MULTIPLIER_100_NANO_SEC + counter;
+ dce_time += ((uint64_t)tv.tv_sec) * 10000000;
+ dce_time += (((uint64_t)0x01b21dd2) << 32) + 0x13814000;
+
+ uuid->time_low = dce_time & 0xffffffff;
+ uuid->time_mid = 0xffff & (dce_time >> 32);
+ uuid->time_hi_and_version = 0x0fff & (dce_time >> 48);
+
+ uuid->time_hi_and_version |= (1 << 12);
+
+ uuid->clock_seq_low = seq_num & 0xff;
+ uuid->clock_seq_hi_and_reserved = (seq_num >> 8) & 0x3f;
+ uuid->clock_seq_hi_and_reserved |= 0x80; /* dce variant */
+
+ memcpy(uuid->node, nodeaddr, 6);
+
+ return 0;
+}
+
+/*
+ * Creates a nil UUID.
+ * A nil UUID has all all fields set to zero
+ */
+
+int
+uuid_create_nil(afsUUID *uuid)
+{
+ memcpy(uuid, &niluuid, sizeof(niluuid));
+ return 0;
+}
+
+/*
+ * Determines if two UUIDs are equal.
+ * return non zero if true.
+ */
+
+int
+uuid_equal(const afsUUID *uuid1, const afsUUID *uuid2)
+{
+ return uuid_compare(uuid1, uuid2) == 0;
+}
+
+/*
+ * Converts a string UUID to binary representation.
+ */
+
+int
+uuid_from_string(const char *str, afsUUID *uuid)
+{
+ unsigned int time_low, time_mid, time_hi_and_version;
+ unsigned int clock_seq_hi_and_reserved, clock_seq_low;
+ unsigned int node[6];
+ int i;
+
+ i = sscanf(str, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ &time_low,
+ &time_mid,
+ &time_hi_and_version,
+ &clock_seq_hi_and_reserved,
+ &clock_seq_low,
+ &node[0], &node[1], &node[2], &node[3], &node[4], &node[5]);
+ if (i != 11)
+ return -1;
+
+ uuid->time_low = time_low;
+ uuid->time_mid = time_mid;
+ uuid->time_hi_and_version = time_hi_and_version;
+ uuid->clock_seq_hi_and_reserved = clock_seq_hi_and_reserved;
+ uuid->clock_seq_low = clock_seq_low;
+
+ for (i = 0; i < 6; i++)
+ uuid->node[i] = node[i];
+
+ return 0;
+}
+
+/*
+ * Creates a hash value for a UUID.
+ */
+
+uint32_t
+uuid_hash(const afsUUID *uuid)
+{
+ uint32_t hash;
+ const uint32_t *hp;
+ unsigned int i;
+
+ /* use the sum instead ? */
+
+ hash = 0;
+ hp = (const uint32_t *)uuid;
+ for (i = 0; i < sizeof(*uuid)/4; i++, hp++)
+ hash ^= *hp;
+ return hash;
+}
+
+/*
+ * Determines if a UUID is nil.
+ */
+
+int
+uuid_is_nil(const afsUUID *uuid)
+{
+ return uuid_compare(uuid, &niluuid);
+}
+
+/*
+ * Converts a UUID from binary representation to a string representation.
+ */
+
+int
+uuid_to_string(const afsUUID *uuid, char *str, size_t strsz)
+{
+ snprintf(str, strsz,
+ "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid->time_low,
+ uuid->time_mid,
+ uuid->time_hi_and_version,
+ (unsigned char)uuid->clock_seq_hi_and_reserved,
+ (unsigned char)uuid->clock_seq_low,
+ (unsigned char)uuid->node[0],
+ (unsigned char)uuid->node[1],
+ (unsigned char)uuid->node[2],
+ (unsigned char)uuid->node[3],
+ (unsigned char)uuid->node[4],
+ (unsigned char)uuid->node[5]);
+
+ return 0;
+}
+
+
+#ifdef TEST
+int
+main(int argc, char **argv)
+{
+ char str[1000];
+ afsUUID u1, u2;
+
+ uuid_create(&u1);
+
+ uuid_to_string(&u1, str, sizeof(str));
+
+ printf("u: %s\n", str);
+
+ if (uuid_from_string(str, &u2)) {
+ printf("failed to parse\n");
+ return 0;
+ }
+
+ if (uuid_compare(&u1, &u2) != 0)
+ printf("u1 != u2\n");
+
+ return 0;
+}
+#endif
diff --git a/usr.sbin/ldapd/uuid.h b/usr.sbin/ldapd/uuid.h
new file mode 100644
index 00000000000..c83f01c7d97
--- /dev/null
+++ b/usr.sbin/ldapd/uuid.h
@@ -0,0 +1,61 @@
+/* $OpenBSD: uuid.h,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+/*
+ * Copyright (c) 2002, Stockholms Universitet
+ * (Stockholm University, Stockholm Sweden)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the university nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* $arla: afs_uuid.h,v 1.3 2002/05/30 00:48:12 mattiasa Exp $ */
+
+#ifndef __ARLA_UUID_H__
+#define __ARLA_UUID_H__ 1
+
+struct uuid {
+ u_long time_low;
+ u_short time_mid;
+ u_short time_hi_and_version;
+ char clock_seq_hi_and_reserved;
+ char clock_seq_low;
+ char node[6];
+};
+
+typedef struct uuid afsUUID;
+
+int uuid_compare(const afsUUID *, const afsUUID *);
+int uuid_create(afsUUID *);
+int uuid_create_nil(afsUUID *);
+int uuid_equal(const afsUUID *, const afsUUID *);
+int uuid_from_string(const char *, afsUUID *);
+uint32_t uuid_hash(const afsUUID *);
+int uuid_is_nil(const afsUUID *);
+int uuid_to_string(const afsUUID *, char *, size_t);
+
+#endif /* __ARLA_UUID_H__ */
+
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;
+}
+