/* $OpenBSD: index.c,v 1.13 2019/10/24 12:39:26 tb Exp $ */ /* * Copyright (c) 2009 Martin Hedenfalk * * 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: * @, * 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 #include #include #include #include #include #include "ldapd.h" #include "log.h" static 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); memset(&val, 0, 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 (ober_get_string(v, &s) != 0) continue; memset(&key, 0, sizeof(key)); key.size = asprintf(&t, "%s=%s,%.*s", attr, s, dnsz, (char *)dn->data); if (key.size == (size_t)-1) return -1; key.data = t; normalize_dn(key.data); rc = btree_txn_put(NULL, ns->indx_txn, &key, &val, BT_NOOVERWRITE); free(t); if (rc == -1 && errno != EEXIST) return -1; } return 0; } static int index_rdn_key(struct namespace *ns, struct btval *dn, struct btval *key) { int dnsz, rdnsz, pdnsz; char *t, *parent_dn; memset(key, 0, sizeof(*key)); dnsz = dn->size - strlen(ns->suffix); if (dnsz-- == 0) return -1; parent_dn = memchr(dn->data, ',', dnsz); if (parent_dn == NULL) { rdnsz = dnsz; pdnsz = 0; parent_dn = ""; } else { rdnsz = parent_dn - (char *)dn->data; pdnsz = dnsz - rdnsz - 1; ++parent_dn; } if (asprintf(&t, "@%.*s,%.*s", pdnsz, parent_dn, rdnsz, (char *)dn->data) == -1) return -1; normalize_dn(t); key->data = t; key->size = strlen(t); key->free_data = 1; return 0; } static int index_rdn(struct namespace *ns, struct btval *dn) { struct btval key, val; int rc; memset(&val, 0, sizeof(val)); assert(ns); assert(ns->indx_txn); assert(dn); if (index_rdn_key(ns, dn, &key) < 0) return 0; log_debug("indexing rdn on %.*s", (int)key.size, (char *)key.data); rc = btree_txn_put(NULL, ns->indx_txn, &key, &val, BT_NOOVERWRITE); btval_reset(&key); if (rc == -1 && errno != EEXIST) return -1; return 0; } static 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 (ober_get_string(v, &s) != 0) continue; memset(&key, 0, 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(NULL, ns->indx_txn, &key, NULL); free(t); if (rc == BT_FAIL && errno != ENOENT) 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 rc; struct btval key; assert(ns); assert(ns->indx_txn); assert(dn); if (index_rdn_key(ns, dn, &key) < 0) return 0; log_debug("unindexing rdn on %.*s", (int)key.size, (char *)key.data); rc = btree_txn_del(NULL, ns->indx_txn, &key, NULL); btval_reset(&key); if (rc == BT_FAIL && errno != ENOENT) 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; }