diff options
37 files changed, 15293 insertions, 0 deletions
diff --git a/usr.sbin/ldapctl/Makefile b/usr.sbin/ldapctl/Makefile new file mode 100644 index 00000000000..648f999a562 --- /dev/null +++ b/usr.sbin/ldapctl/Makefile @@ -0,0 +1,17 @@ +# $OpenBSD: Makefile,v 1.1 2010/05/31 17:36:31 martinh Exp $ + +.PATH: ${.CURDIR}/../ldapd + +PROG= ldapctl +MAN= ldapctl.8 +SRCS= ldapctl.c + +LDADD= -levent -lutil +DPADD= ${LIBEVENT} ${LIBUTIL} +CFLAGS+= -I${.CURDIR}/../ldapd +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare + +.include <bsd.prog.mk> diff --git a/usr.sbin/ldapctl/ldapctl.8 b/usr.sbin/ldapctl/ldapctl.8 new file mode 100644 index 00000000000..50244eded01 --- /dev/null +++ b/usr.sbin/ldapctl/ldapctl.8 @@ -0,0 +1,73 @@ +.\" $OpenBSD: ldapctl.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 LDAPCTL 8 +.Os +.Sh NAME +.Nm ldapctl +.Nd LDAP daemon control program +.Sh SYNOPSIS +.Nm ldapctl +.Op Fl s Ar socket +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr ldapd 8 +daemon. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl s Ar socket +Use +.Ar socket +instead of the default +.Pa /var/run/ldapd.sock +to communicate with +.Xr ldapd 8 . +.El +.Pp +The commands are as follows: +.Bl -tag -width xxxxxx +.It Cm stats +Show statistics counters. +.It Cm compact +Initiate compaction of all databases. +Compaction is performed online against a running +.Xr ldapd 8 . +When compaction of a database file is complete, +.Xr ldapd 8 +reopens the file and new requests are performed against the new database. +Write requests are buffered until the compaction is complete. +Read requests are handled without disruption. +.It Cm index +Initiate re-indexing of all databases. +Indexing is performed online against a running +.Xr ldapd 8 . +.El +.Sh FILES +.Bl -tag -width "/var/run/ldapd.sockXXXXXXX" -compact +.It Pa /var/run/ldapd.sock +default +.Nm +control socket +.El +.Sh SEE ALSO +.Xr ldapd.conf 5 , +.Xr ldapd 8 diff --git a/usr.sbin/ldapctl/ldapctl.c b/usr.sbin/ldapctl/ldapctl.c new file mode 100644 index 00000000000..c2979a041e4 --- /dev/null +++ b/usr.sbin/ldapctl/ldapctl.c @@ -0,0 +1,277 @@ +/* $OpenBSD: ldapctl.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */ + +/* + * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> + * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 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/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/un.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "ldapd.h" + +enum action { + NONE, + SHOW_STATS, + LOG_VERBOSE, + LOG_BRIEF, + COMPACT_DB, + INDEX_DB +}; + +__dead void usage(void); +void show_stats(struct imsg *imsg); +void show_dbstats(const char *prefix, struct btree_stat *st); +void show_nsstats(struct imsg *imsg); +void show_compact_status(struct imsg *imsg); +void show_index_status(struct imsg *imsg); + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-s socket] command [arg ...]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int ctl_sock; + int done = 0, verbose = 0; + ssize_t n; + int ch; + enum action action = NONE; + const char *sock = LDAPD_SOCKET; + struct sockaddr_un sun; + struct imsg imsg; + struct imsgbuf ibuf; + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + sock = optarg; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + if (strcmp(argv[0], "stats") == 0) + action = SHOW_STATS; + else if (strcmp(argv[0], "compact") == 0) + action = COMPACT_DB; + else if (strcmp(argv[0], "index") == 0) + action = INDEX_DB; + else if (strcmp(argv[0], "log") == 0) { + if (argc != 2) + usage(); + if (strcmp(argv[1], "verbose") == 0) + action = LOG_VERBOSE; + else if (strcmp(argv[1], "brief") == 0) + action = LOG_BRIEF; + else + usage(); + } else + usage(); + + /* connect to ldapd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, sock, sizeof(sun.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", sock); + + imsg_init(&ibuf, ctl_sock); + done = 0; + + /* process user request */ + switch (action) { + case SHOW_STATS: + imsg_compose(&ibuf, IMSG_CTL_STATS, 0, 0, -1, NULL, 0); + break; + case COMPACT_DB: + imsg_compose(&ibuf, IMSG_CTL_COMPACT, 0, 0, -1, NULL, 0); + break; + case INDEX_DB: + imsg_compose(&ibuf, IMSG_CTL_INDEX, 0, 0, -1, NULL, 0); + break; + case LOG_VERBOSE: + verbose = 1; + /* FALLTHROUGH */ + case LOG_BRIEF: + imsg_compose(&ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1, + &verbose, sizeof(verbose)); + printf("logging request sent.\n"); + done = 1; + break; + case NONE: + break; + } + + while (ibuf.w.queued) + if (msgbuf_write(&ibuf.w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(&ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (imsg.hdr.type) { + case IMSG_CTL_STATS: + show_stats(&imsg); + break; + case IMSG_CTL_NSSTATS: + show_nsstats(&imsg); + break; + case IMSG_CTL_COMPACT_STATUS: + show_compact_status(&imsg); + break; + case IMSG_CTL_INDEX_STATUS: + show_index_status(&imsg); + break; + case IMSG_CTL_END: + done = 1; + break; + case NONE: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + + return (0); +} + +void +show_stats(struct imsg *imsg) +{ + struct ldapd_stats *st; + + st = imsg->data; + + printf("start time: %s", ctime(&st->started_at)); + printf("requests: %llu\n", st->requests); + printf("search requests: %llu\n", st->req_search); + printf("bind requests: %llu\n", st->req_bind); + printf("modify requests: %llu\n", st->req_mod); + printf("timeouts: %llu\n", st->timeouts); + printf("unindexed searches: %llu\n", st->unindexed); + printf("active connections: %u\n", st->conns); + printf("active searches: %u\n", st->searches); +} + +#define ZDIV(t,n) ((n) == 0 ? 0 : (float)(t) / (n)) + +void +show_dbstats(const char *prefix, struct btree_stat *st) +{ + printf("%s timestamp: %s", prefix, ctime(&st->created_at)); + printf("%s page size: %u\n", prefix, st->psize); + printf("%s depth: %u\n", prefix, st->depth); + printf("%s revisions: %u\n", prefix, st->revisions); + printf("%s entries: %llu\n", prefix, st->entries); + printf("%s branch/leaf/overflow pages: %u/%u/%u\n", + prefix, st->branch_pages, st->leaf_pages, st->overflow_pages); + + printf("%s cache size: %u of %u (%.1f%% full)\n", prefix, + st->cache_size, st->max_cache, + 100 * ZDIV(st->cache_size, st->max_cache)); + printf("%s page reads: %llu\n", prefix, st->reads); + printf("%s cache hits: %llu (%.1f%%)\n", prefix, st->hits, + 100 * ZDIV(st->hits, (st->hits + st->reads))); +} + +void +show_nsstats(struct imsg *imsg) +{ + struct ns_stat *nss; + + nss = imsg->data; + + printf("\nsuffix: %s\n", nss->suffix); + show_dbstats("data", &nss->data_stat); + show_dbstats("indx", &nss->indx_stat); +} + +void +show_compact_status(struct imsg *imsg) +{ + struct compaction_status *cs; + + cs = imsg->data; + printf("%s (%s): %s\n", cs->suffix, + cs->db == 1 ? "entries" : "index", + cs->status == 0 ? "ok" : "failed"); +} + +void +show_index_status(struct imsg *imsg) +{ + struct indexer_status *is; + + is = imsg->data; + if (is->status != 0) + printf("\r%s: %s \n", is->suffix, + is->status < 0 ? "failed" : "ok"); + else if (is->entries > 0) + printf("\r%s: %i%%", + is->suffix, + (int)(100 * (double)is->ncomplete / is->entries)); + else + printf("\r%s: 100%%", is->suffix); + + fflush(stdout); +} + 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, ©->page, sizeof(mp->page)); + bcopy(&mp->prefix, ©->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, ©, PAGESIZE); + assert(mp->ref == 0); /* XXX */ + bzero(&mp->page.ptrs, PAGESIZE - PAGEHDRSZ); + mp->page.lower = PAGEHDRSZ; + mp->page.upper = PAGESIZE; + + split_indx = NUMKEYSP(©) / 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(©, 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(©, 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(©); 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(©)) { + break; + } else { + node = NODEPTRP(©, 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; +} + |