155682Smarkm/*
2233294Sstas * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
755682Smarkm *
8233294Sstas * Redistribution and use in source and binary forms, with or without
9233294Sstas * modification, are permitted provided that the following conditions
10233294Sstas * are met:
1155682Smarkm *
12233294Sstas * 1. Redistributions of source code must retain the above copyright
13233294Sstas *    notice, this list of conditions and the following disclaimer.
1455682Smarkm *
15233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
16233294Sstas *    notice, this list of conditions and the following disclaimer in the
17233294Sstas *    documentation and/or other materials provided with the distribution.
1855682Smarkm *
19233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
20233294Sstas *    may be used to endorse or promote products derived from this software
21233294Sstas *    without specific prior written permission.
22233294Sstas *
23233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33233294Sstas * SUCH DAMAGE.
3455682Smarkm */
3555682Smarkm
36233294Sstas#include "krb5_locl.h"
3755682Smarkm#include "hdb_locl.h"
3855682Smarkm
39178825Sdfr#ifdef HAVE_DLFCN_H
40178825Sdfr#include <dlfcn.h>
41178825Sdfr#endif
42178825Sdfr
43233294Sstas/*! @mainpage Heimdal database backend library
44233294Sstas *
45233294Sstas * @section intro Introduction
46233294Sstas *
47233294Sstas * Heimdal libhdb library provides the backend support for Heimdal kdc
48233294Sstas * and kadmind. Its here where plugins for diffrent database engines
49233294Sstas * can be pluged in and extend support for here Heimdal get the
50233294Sstas * principal and policy data from.
51233294Sstas *
52233294Sstas * Example of Heimdal backend are:
53233294Sstas * - Berkeley DB 1.85
54233294Sstas * - Berkeley DB 3.0
55233294Sstas * - Berkeley DB 4.0
56233294Sstas * - New Berkeley DB
57233294Sstas * - LDAP
58233294Sstas *
59233294Sstas *
60233294Sstas * The project web page: http://www.h5l.org/
61233294Sstas *
62233294Sstas */
6372445Sassar
64233294Sstasconst int hdb_interface_version = HDB_INTERFACE_VERSION;
65233294Sstas
6672445Sassarstatic struct hdb_method methods[] = {
6790926Snectar#if HAVE_DB1 || HAVE_DB3
68233294Sstas    { HDB_INTERFACE_VERSION, "db:",	hdb_db_create},
6972445Sassar#endif
70233294Sstas#if HAVE_DB1
71233294Sstas    { HDB_INTERFACE_VERSION, "mit-db:",	hdb_mdb_create},
72233294Sstas#endif
7390926Snectar#if HAVE_NDBM
74233294Sstas    { HDB_INTERFACE_VERSION, "ndbm:",	hdb_ndbm_create},
7572445Sassar#endif
76233294Sstas    { HDB_INTERFACE_VERSION, "keytab:",	hdb_keytab_create},
77178825Sdfr#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
78233294Sstas    { HDB_INTERFACE_VERSION, "ldap:",	hdb_ldap_create},
79233294Sstas    { HDB_INTERFACE_VERSION, "ldapi:",	hdb_ldapi_create},
8072445Sassar#endif
81233294Sstas#ifdef HAVE_SQLITE3
82233294Sstas    { HDB_INTERFACE_VERSION, "sqlite:", hdb_sqlite_create},
8372445Sassar#endif
84233294Sstas    {0, NULL,	NULL}
8572445Sassar};
8672445Sassar
87178825Sdfr#if HAVE_DB1 || HAVE_DB3
88233294Sstasstatic struct hdb_method dbmetod =
89233294Sstas    { HDB_INTERFACE_VERSION, "", hdb_db_create };
90178825Sdfr#elif defined(HAVE_NDBM)
91233294Sstasstatic struct hdb_method dbmetod =
92233294Sstas    { HDB_INTERFACE_VERSION, "", hdb_ndbm_create };
93178825Sdfr#endif
94178825Sdfr
95178825Sdfr
9655682Smarkmkrb5_error_code
9755682Smarkmhdb_next_enctype2key(krb5_context context,
9872445Sassar		     const hdb_entry *e,
9955682Smarkm		     krb5_enctype enctype,
10055682Smarkm		     Key **key)
10155682Smarkm{
10255682Smarkm    Key *k;
103233294Sstas
10472445Sassar    for (k = *key ? (*key) + 1 : e->keys.val;
105233294Sstas	 k < e->keys.val + e->keys.len;
106233294Sstas	 k++)
107178825Sdfr    {
10855682Smarkm	if(k->key.keytype == enctype){
10955682Smarkm	    *key = k;
11055682Smarkm	    return 0;
11155682Smarkm	}
112178825Sdfr    }
113233294Sstas    krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
114233294Sstas			   "No next enctype %d for hdb-entry",
115178825Sdfr			  (int)enctype);
11655682Smarkm    return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
11755682Smarkm}
11855682Smarkm
11955682Smarkmkrb5_error_code
120233294Sstashdb_enctype2key(krb5_context context,
121233294Sstas		hdb_entry *e,
122233294Sstas		krb5_enctype enctype,
12355682Smarkm		Key **key)
12455682Smarkm{
12555682Smarkm    *key = NULL;
12655682Smarkm    return hdb_next_enctype2key(context, e, enctype, key);
12755682Smarkm}
12855682Smarkm
12955682Smarkmvoid
13055682Smarkmhdb_free_key(Key *key)
13155682Smarkm{
132233294Sstas    memset(key->key.keyvalue.data,
13355682Smarkm	   0,
13455682Smarkm	   key->key.keyvalue.length);
13555682Smarkm    free_Key(key);
13655682Smarkm    free(key);
13755682Smarkm}
13855682Smarkm
13955682Smarkm
14055682Smarkmkrb5_error_code
14155682Smarkmhdb_lock(int fd, int operation)
14255682Smarkm{
14372445Sassar    int i, code = 0;
14472445Sassar
14555682Smarkm    for(i = 0; i < 3; i++){
14655682Smarkm	code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
14755682Smarkm	if(code == 0 || errno != EWOULDBLOCK)
14855682Smarkm	    break;
14955682Smarkm	sleep(1);
15055682Smarkm    }
15155682Smarkm    if(code == 0)
15255682Smarkm	return 0;
15355682Smarkm    if(errno == EWOULDBLOCK)
15455682Smarkm	return HDB_ERR_DB_INUSE;
15555682Smarkm    return HDB_ERR_CANT_LOCK_DB;
15655682Smarkm}
15755682Smarkm
15855682Smarkmkrb5_error_code
15955682Smarkmhdb_unlock(int fd)
16055682Smarkm{
16155682Smarkm    int code;
16255682Smarkm    code = flock(fd, LOCK_UN);
16355682Smarkm    if(code)
16455682Smarkm	return 4711 /* XXX */;
16555682Smarkm    return 0;
16655682Smarkm}
16755682Smarkm
16855682Smarkmvoid
169178825Sdfrhdb_free_entry(krb5_context context, hdb_entry_ex *ent)
17055682Smarkm{
171233294Sstas    size_t i;
17255682Smarkm
173178825Sdfr    if (ent->free_entry)
174178825Sdfr	(*ent->free_entry)(context, ent);
17555682Smarkm
176178825Sdfr    for(i = 0; i < ent->entry.keys.len; ++i) {
177178825Sdfr	Key *k = &ent->entry.keys.val[i];
178178825Sdfr
17955682Smarkm	memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
18055682Smarkm    }
181178825Sdfr    free_hdb_entry(&ent->entry);
18255682Smarkm}
18355682Smarkm
18455682Smarkmkrb5_error_code
18555682Smarkmhdb_foreach(krb5_context context,
18655682Smarkm	    HDB *db,
18755682Smarkm	    unsigned flags,
18855682Smarkm	    hdb_foreach_func_t func,
18955682Smarkm	    void *data)
19055682Smarkm{
19155682Smarkm    krb5_error_code ret;
192178825Sdfr    hdb_entry_ex entry;
193178825Sdfr    ret = db->hdb_firstkey(context, db, flags, &entry);
194178825Sdfr    if (ret == 0)
195233294Sstas	krb5_clear_error_message(context);
19655682Smarkm    while(ret == 0){
19755682Smarkm	ret = (*func)(context, db, &entry, data);
19855682Smarkm	hdb_free_entry(context, &entry);
19955682Smarkm	if(ret == 0)
200178825Sdfr	    ret = db->hdb_nextkey(context, db, flags, &entry);
20155682Smarkm    }
20255682Smarkm    if(ret == HDB_ERR_NOENTRY)
20355682Smarkm	ret = 0;
20455682Smarkm    return ret;
20555682Smarkm}
20655682Smarkm
20755682Smarkmkrb5_error_code
20855682Smarkmhdb_check_db_format(krb5_context context, HDB *db)
20955682Smarkm{
21055682Smarkm    krb5_data tag;
21155682Smarkm    krb5_data version;
212178825Sdfr    krb5_error_code ret, ret2;
21355682Smarkm    unsigned ver;
21455682Smarkm    int foo;
21555682Smarkm
216178825Sdfr    ret = db->hdb_lock(context, db, HDB_RLOCK);
217178825Sdfr    if (ret)
218178825Sdfr	return ret;
219178825Sdfr
220233294Sstas    tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
22155682Smarkm    tag.length = strlen(tag.data);
222178825Sdfr    ret = (*db->hdb__get)(context, db, tag, &version);
223178825Sdfr    ret2 = db->hdb_unlock(context, db);
22455682Smarkm    if(ret)
22555682Smarkm	return ret;
226178825Sdfr    if (ret2)
227178825Sdfr	return ret2;
22855682Smarkm    foo = sscanf(version.data, "%u", &ver);
22955682Smarkm    krb5_data_free (&version);
23055682Smarkm    if (foo != 1)
23155682Smarkm	return HDB_ERR_BADVERSION;
23255682Smarkm    if(ver != HDB_DB_FORMAT)
23355682Smarkm	return HDB_ERR_BADVERSION;
23455682Smarkm    return 0;
23555682Smarkm}
23655682Smarkm
23755682Smarkmkrb5_error_code
23855682Smarkmhdb_init_db(krb5_context context, HDB *db)
23955682Smarkm{
240178825Sdfr    krb5_error_code ret, ret2;
24155682Smarkm    krb5_data tag;
24255682Smarkm    krb5_data version;
24355682Smarkm    char ver[32];
244233294Sstas
24555682Smarkm    ret = hdb_check_db_format(context, db);
24655682Smarkm    if(ret != HDB_ERR_NOENTRY)
24755682Smarkm	return ret;
248233294Sstas
249178825Sdfr    ret = db->hdb_lock(context, db, HDB_WLOCK);
250178825Sdfr    if (ret)
251178825Sdfr	return ret;
252178825Sdfr
253233294Sstas    tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
25455682Smarkm    tag.length = strlen(tag.data);
25555682Smarkm    snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
25655682Smarkm    version.data = ver;
25755682Smarkm    version.length = strlen(version.data) + 1; /* zero terminated */
258178825Sdfr    ret = (*db->hdb__put)(context, db, 0, tag, version);
259178825Sdfr    ret2 = db->hdb_unlock(context, db);
260178825Sdfr    if (ret) {
261178825Sdfr	if (ret2)
262233294Sstas	    krb5_clear_error_message(context);
263178825Sdfr	return ret;
264178825Sdfr    }
265178825Sdfr    return ret2;
26655682Smarkm}
26755682Smarkm
268178825Sdfr#ifdef HAVE_DLOPEN
269178825Sdfr
270178825Sdfr /*
271178825Sdfr * Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so,
272178825Sdfr * looking for the hdb_NAME_create symbol.
273178825Sdfr */
274178825Sdfr
275178825Sdfrstatic const struct hdb_method *
276178825Sdfrfind_dynamic_method (krb5_context context,
277233294Sstas		     const char *filename,
278178825Sdfr		     const char **rest)
279178825Sdfr{
280178825Sdfr    static struct hdb_method method;
281178825Sdfr    struct hdb_so_method *mso;
282178825Sdfr    char *prefix, *path, *symbol;
283178825Sdfr    const char *p;
284178825Sdfr    void *dl;
285178825Sdfr    size_t len;
286233294Sstas
287178825Sdfr    p = strchr(filename, ':');
288178825Sdfr
289178825Sdfr    /* if no prefix, don't know what module to load, just ignore it */
290178825Sdfr    if (p == NULL)
291178825Sdfr	return NULL;
292178825Sdfr
293178825Sdfr    len = p - filename;
294178825Sdfr    *rest = filename + len + 1;
295233294Sstas
296233294Sstas    prefix = malloc(len + 1);
297178825Sdfr    if (prefix == NULL)
298178825Sdfr	krb5_errx(context, 1, "out of memory");
299233294Sstas    strlcpy(prefix, filename, len + 1);
300233294Sstas
301178825Sdfr    if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1)
302178825Sdfr	krb5_errx(context, 1, "out of memory");
303178825Sdfr
304178825Sdfr#ifndef RTLD_NOW
305178825Sdfr#define RTLD_NOW 0
306178825Sdfr#endif
307178825Sdfr#ifndef RTLD_GLOBAL
308178825Sdfr#define RTLD_GLOBAL 0
309178825Sdfr#endif
310178825Sdfr
311178825Sdfr    dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
312178825Sdfr    if (dl == NULL) {
313178825Sdfr	krb5_warnx(context, "error trying to load dynamic module %s: %s\n",
314178825Sdfr		   path, dlerror());
315178825Sdfr	free(prefix);
316178825Sdfr	free(path);
317178825Sdfr	return NULL;
318178825Sdfr    }
319233294Sstas
320178825Sdfr    if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1)
321178825Sdfr	krb5_errx(context, 1, "out of memory");
322233294Sstas
323233294Sstas    mso = (struct hdb_so_method *) dlsym(dl, symbol);
324178825Sdfr    if (mso == NULL) {
325233294Sstas	krb5_warnx(context, "error finding symbol %s in %s: %s\n",
326178825Sdfr		   symbol, path, dlerror());
327178825Sdfr	dlclose(dl);
328178825Sdfr	free(symbol);
329178825Sdfr	free(prefix);
330178825Sdfr	free(path);
331178825Sdfr	return NULL;
332178825Sdfr    }
333178825Sdfr    free(path);
334178825Sdfr    free(symbol);
335178825Sdfr
336178825Sdfr    if (mso->version != HDB_INTERFACE_VERSION) {
337233294Sstas	krb5_warnx(context,
338178825Sdfr		   "error wrong version in shared module %s "
339233294Sstas		   "version: %d should have been %d\n",
340178825Sdfr		   prefix, mso->version, HDB_INTERFACE_VERSION);
341178825Sdfr	dlclose(dl);
342178825Sdfr	free(prefix);
343178825Sdfr	return NULL;
344178825Sdfr    }
345178825Sdfr
346178825Sdfr    if (mso->create == NULL) {
347178825Sdfr	krb5_errx(context, 1,
348178825Sdfr		  "no entry point function in shared mod %s ",
349178825Sdfr		   prefix);
350178825Sdfr	dlclose(dl);
351178825Sdfr	free(prefix);
352178825Sdfr	return NULL;
353178825Sdfr    }
354178825Sdfr
355178825Sdfr    method.create = mso->create;
356178825Sdfr    method.prefix = prefix;
357178825Sdfr
358178825Sdfr    return &method;
359178825Sdfr}
360178825Sdfr#endif /* HAVE_DLOPEN */
361178825Sdfr
36272445Sassar/*
36372445Sassar * find the relevant method for `filename', returning a pointer to the
36472445Sassar * rest in `rest'.
36572445Sassar * return NULL if there's no such method.
36672445Sassar */
36755682Smarkm
36872445Sassarstatic const struct hdb_method *
36972445Sassarfind_method (const char *filename, const char **rest)
37055682Smarkm{
37172445Sassar    const struct hdb_method *h;
37255682Smarkm
373178825Sdfr    for (h = methods; h->prefix != NULL; ++h) {
37472445Sassar	if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) {
37572445Sassar	    *rest = filename + strlen(h->prefix);
37672445Sassar	    return h;
37772445Sassar	}
378178825Sdfr    }
379178825Sdfr#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM)
380178825Sdfr    if (strncmp(filename, "/", 1) == 0
381178825Sdfr	|| strncmp(filename, "./", 2) == 0
382178825Sdfr	|| strncmp(filename, "../", 3) == 0)
383178825Sdfr    {
384178825Sdfr	*rest = filename;
385178825Sdfr	return &dbmetod;
386178825Sdfr    }
387178825Sdfr#endif
388178825Sdfr
38972445Sassar    return NULL;
39055682Smarkm}
39155682Smarkm
39255682Smarkmkrb5_error_code
393178825Sdfrhdb_list_builtin(krb5_context context, char **list)
394178825Sdfr{
395178825Sdfr    const struct hdb_method *h;
396178825Sdfr    size_t len = 0;
397178825Sdfr    char *buf = NULL;
398178825Sdfr
399178825Sdfr    for (h = methods; h->prefix != NULL; ++h) {
400178825Sdfr	if (h->prefix[0] == '\0')
401178825Sdfr	    continue;
402178825Sdfr	len += strlen(h->prefix) + 2;
403178825Sdfr    }
404178825Sdfr
405178825Sdfr    len += 1;
406178825Sdfr    buf = malloc(len);
407178825Sdfr    if (buf == NULL) {
408233294Sstas	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
409178825Sdfr	return ENOMEM;
410178825Sdfr    }
411178825Sdfr    buf[0] = '\0';
412178825Sdfr
413178825Sdfr    for (h = methods; h->prefix != NULL; ++h) {
414178825Sdfr	if (h != methods)
415178825Sdfr	    strlcat(buf, ", ", len);
416178825Sdfr	strlcat(buf, h->prefix, len);
417178825Sdfr    }
418178825Sdfr    *list = buf;
419178825Sdfr    return 0;
420178825Sdfr}
421178825Sdfr
422178825Sdfrkrb5_error_code
423233294Sstas_hdb_keytab2hdb_entry(krb5_context context,
424233294Sstas		      const krb5_keytab_entry *ktentry,
425233294Sstas		      hdb_entry_ex *entry)
426233294Sstas{
427233294Sstas    entry->entry.kvno = ktentry->vno;
428233294Sstas    entry->entry.created_by.time = ktentry->timestamp;
429233294Sstas
430233294Sstas    entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0]));
431233294Sstas    if (entry->entry.keys.val == NULL)
432233294Sstas	return ENOMEM;
433233294Sstas    entry->entry.keys.len = 1;
434233294Sstas
435233294Sstas    entry->entry.keys.val[0].mkvno = NULL;
436233294Sstas    entry->entry.keys.val[0].salt = NULL;
437233294Sstas
438233294Sstas    return krb5_copy_keyblock_contents(context,
439233294Sstas				       &ktentry->keyblock,
440233294Sstas				       &entry->entry.keys.val[0].key);
441233294Sstas}
442233294Sstas
443233294Sstas/**
444233294Sstas * Create a handle for a Kerberos database
445233294Sstas *
446233294Sstas * Create a handle for a Kerberos database backend specified by a
447233294Sstas * filename.  Doesn't create a file if its doesn't exists, you have to
448233294Sstas * use O_CREAT to tell the backend to create the file.
449233294Sstas */
450233294Sstas
451233294Sstaskrb5_error_code
45272445Sassarhdb_create(krb5_context context, HDB **db, const char *filename)
45355682Smarkm{
45472445Sassar    const struct hdb_method *h;
45572445Sassar    const char *residual;
456233294Sstas    krb5_error_code ret;
457233294Sstas    struct krb5_plugin *list = NULL, *e;
45855682Smarkm
45972445Sassar    if(filename == NULL)
46072445Sassar	filename = HDB_DEFAULT_DB;
46190926Snectar    krb5_add_et_list(context, initialize_hdb_error_table_r);
46272445Sassar    h = find_method (filename, &residual);
463233294Sstas
464233294Sstas    if (h == NULL) {
465233294Sstas	    ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "hdb", &list);
466233294Sstas	    if(ret == 0 && list != NULL) {
467233294Sstas		    for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
468233294Sstas			    h = _krb5_plugin_get_symbol(e);
469233294Sstas			    if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0
470233294Sstas				&& h->interface_version == HDB_INTERFACE_VERSION) {
471233294Sstas				    residual = filename + strlen(h->prefix);
472233294Sstas				    break;
473233294Sstas			    }
474233294Sstas		    }
475233294Sstas		    if (e == NULL) {
476233294Sstas			    h = NULL;
477233294Sstas			    _krb5_plugin_free(list);
478233294Sstas		    }
479233294Sstas	    }
480233294Sstas    }
481233294Sstas
482178825Sdfr#ifdef HAVE_DLOPEN
48372445Sassar    if (h == NULL)
484178825Sdfr	h = find_dynamic_method (context, filename, &residual);
485178825Sdfr#endif
486178825Sdfr    if (h == NULL)
487178825Sdfr	krb5_errx(context, 1, "No database support for %s", filename);
48872445Sassar    return (*h->create)(context, db, residual);
48955682Smarkm}
490