1/*	$NetBSD: hdb.c,v 1.5 2023/06/19 21:41:43 christos Exp $	*/
2
3/*
4 * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38#include "krb5_locl.h"
39#include "hdb_locl.h"
40
41#ifdef HAVE_DLFCN_H
42#include <dlfcn.h>
43#endif
44
45/*! @mainpage Heimdal database backend library
46 *
47 * @section intro Introduction
48 *
49 * Heimdal libhdb library provides the backend support for Heimdal kdc
50 * and kadmind. Its here where plugins for diffrent database engines
51 * can be pluged in and extend support for here Heimdal get the
52 * principal and policy data from.
53 *
54 * Example of Heimdal backend are:
55 * - Berkeley DB 1.85
56 * - Berkeley DB 3.0
57 * - Berkeley DB 4.0
58 * - New Berkeley DB
59 * - LDAP
60 *
61 *
62 * The project web page: http://www.h5l.org/
63 *
64 */
65
66const int hdb_interface_version = HDB_INTERFACE_VERSION;
67
68static struct hdb_method methods[] = {
69    /* "db:" should be db3 if we have db3, or db1 if we have db1 */
70#if HAVE_DB3
71    { HDB_INTERFACE_VERSION, NULL, NULL, "db:",	hdb_db3_create},
72#elif HAVE_DB1
73    { HDB_INTERFACE_VERSION, NULL, NULL, "db:",	hdb_db1_create},
74#endif
75#if HAVE_DB1
76    { HDB_INTERFACE_VERSION, NULL, NULL, "db1:",	hdb_db1_create},
77#endif
78#if HAVE_DB3
79    { HDB_INTERFACE_VERSION, NULL, NULL, "db3:",	hdb_db3_create},
80#endif
81#if HAVE_DB1
82    { HDB_INTERFACE_VERSION, NULL, NULL, "mit-db:",	hdb_mitdb_create},
83#endif
84#if HAVE_LMDB
85    { HDB_INTERFACE_VERSION, NULL, NULL, "mdb:",	hdb_mdb_create},
86    { HDB_INTERFACE_VERSION, NULL, NULL, "lmdb:",	hdb_mdb_create},
87#endif
88#if HAVE_NDBM
89    { HDB_INTERFACE_VERSION, NULL, NULL, "ndbm:",	hdb_ndbm_create},
90#endif
91    { HDB_INTERFACE_VERSION, NULL, NULL, "keytab:",	hdb_keytab_create},
92#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
93    { HDB_INTERFACE_VERSION, NULL, NULL, "ldap:",	hdb_ldap_create},
94    { HDB_INTERFACE_VERSION, NULL, NULL, "ldapi:",	hdb_ldapi_create},
95#elif defined(OPENLDAP)
96    { HDB_INTERFACE_VERSION, NULL, NULL, "ldap:",	NULL},
97    { HDB_INTERFACE_VERSION, NULL, NULL, "ldapi:",	NULL},
98#endif
99#ifdef HAVE_SQLITE3
100    { HDB_INTERFACE_VERSION, NULL, NULL, "sqlite:", hdb_sqlite_create},
101#endif
102    { 0, NULL, NULL, NULL, NULL}
103};
104
105/*
106 * It'd be nice if we could try opening an HDB with each supported
107 * backend until one works or all fail.  It may not be possible for all
108 * flavors, but where it's possible we should.
109 */
110#if defined(HAVE_LMDB)
111static struct hdb_method default_dbmethod =
112    { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_mdb_create };
113#elif defined(HAVE_DB3)
114static struct hdb_method default_dbmethod =
115    { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db3_create };
116#elif defined(HAVE_DB1)
117static struct hdb_method default_dbmethod =
118    { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db1_create };
119#elif defined(HAVE_NDBM)
120static struct hdb_method default_dbmethod =
121    { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_ndbm_create };
122#endif
123
124const Keys *
125hdb_kvno2keys(krb5_context context,
126	      const hdb_entry *e,
127	      krb5_kvno kvno)
128{
129    HDB_Ext_KeySet *hist_keys;
130    HDB_extension *extp;
131    size_t i;
132
133    if (kvno == 0)
134	return &e->keys;
135
136    extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
137    if (extp == NULL)
138	return 0;
139
140    hist_keys = &extp->data.u.hist_keys;
141    for (i = 0; i < hist_keys->len; i++) {
142	if (hist_keys->val[i].kvno == kvno)
143	    return &hist_keys->val[i].keys;
144    }
145
146    return NULL;
147}
148
149krb5_error_code
150hdb_next_enctype2key(krb5_context context,
151		     const hdb_entry *e,
152		     const Keys *keyset,
153		     krb5_enctype enctype,
154		     Key **key)
155{
156    const Keys *keys = keyset ? keyset : &e->keys;
157    Key *k;
158
159    for (k = *key ? (*key) + 1 : keys->val; k < keys->val + keys->len; k++) {
160	if(k->key.keytype == enctype){
161	    *key = k;
162	    return 0;
163	}
164    }
165    krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
166			   "No next enctype %d for hdb-entry",
167			  (int)enctype);
168    return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
169}
170
171krb5_error_code
172hdb_enctype2key(krb5_context context,
173		hdb_entry *e,
174		const Keys *keyset,
175		krb5_enctype enctype,
176		Key **key)
177{
178    *key = NULL;
179    return hdb_next_enctype2key(context, e, keyset, enctype, key);
180}
181
182void
183hdb_free_key(Key *key)
184{
185    memset(key->key.keyvalue.data,
186	   0,
187	   key->key.keyvalue.length);
188    free_Key(key);
189    free(key);
190}
191
192
193krb5_error_code
194hdb_lock(int fd, int operation)
195{
196    int i, code = 0;
197
198    for(i = 0; i < 3; i++){
199	code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
200	if(code == 0 || errno != EWOULDBLOCK)
201	    break;
202	sleep(1);
203    }
204    if(code == 0)
205	return 0;
206    if(errno == EWOULDBLOCK)
207	return HDB_ERR_DB_INUSE;
208    return HDB_ERR_CANT_LOCK_DB;
209}
210
211krb5_error_code
212hdb_unlock(int fd)
213{
214    int code;
215    code = flock(fd, LOCK_UN);
216    if(code)
217	return 4711 /* XXX */;
218    return 0;
219}
220
221void
222hdb_free_entry(krb5_context context, hdb_entry_ex *ent)
223{
224    Key *k;
225    size_t i;
226
227    if (ent->free_entry)
228	(*ent->free_entry)(context, ent);
229
230    for(i = 0; i < ent->entry.keys.len; i++) {
231	k = &ent->entry.keys.val[i];
232
233	memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
234    }
235    free_hdb_entry(&ent->entry);
236}
237
238krb5_error_code
239hdb_foreach(krb5_context context,
240	    HDB *db,
241	    unsigned flags,
242	    hdb_foreach_func_t func,
243	    void *data)
244{
245    krb5_error_code ret;
246    hdb_entry_ex entry;
247    ret = db->hdb_firstkey(context, db, flags, &entry);
248    if (ret == 0)
249	krb5_clear_error_message(context);
250    while(ret == 0){
251	ret = (*func)(context, db, &entry, data);
252	hdb_free_entry(context, &entry);
253	if(ret == 0)
254	    ret = db->hdb_nextkey(context, db, flags, &entry);
255    }
256    if(ret == HDB_ERR_NOENTRY)
257	ret = 0;
258    return ret;
259}
260
261krb5_error_code
262hdb_check_db_format(krb5_context context, HDB *db)
263{
264    krb5_data tag;
265    krb5_data version;
266    krb5_error_code ret, ret2;
267    unsigned ver;
268    int foo;
269
270    ret = db->hdb_lock(context, db, HDB_RLOCK);
271    if (ret)
272	return ret;
273
274    tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
275    tag.length = strlen(tag.data);
276    ret = (*db->hdb__get)(context, db, tag, &version);
277    ret2 = db->hdb_unlock(context, db);
278    if(ret)
279	return ret;
280    if (ret2)
281	return ret2;
282    foo = sscanf(version.data, "%u", &ver);
283    krb5_data_free (&version);
284    if (foo != 1)
285	return HDB_ERR_BADVERSION;
286    if(ver != HDB_DB_FORMAT)
287	return HDB_ERR_BADVERSION;
288    return 0;
289}
290
291krb5_error_code
292hdb_init_db(krb5_context context, HDB *db)
293{
294    krb5_error_code ret, ret2;
295    krb5_data tag;
296    krb5_data version;
297    char ver[32];
298
299    ret = hdb_check_db_format(context, db);
300    if(ret != HDB_ERR_NOENTRY)
301	return ret;
302
303    ret = db->hdb_lock(context, db, HDB_WLOCK);
304    if (ret)
305	return ret;
306
307    tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
308    tag.length = strlen(tag.data);
309    snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
310    version.data = ver;
311    version.length = strlen(version.data) + 1; /* zero terminated */
312    ret = (*db->hdb__put)(context, db, 0, tag, version);
313    ret2 = db->hdb_unlock(context, db);
314    if (ret) {
315	if (ret2)
316	    krb5_clear_error_message(context);
317	return ret;
318    }
319    return ret2;
320}
321
322/*
323 * find the relevant method for `filename', returning a pointer to the
324 * rest in `rest'.
325 * return NULL if there's no such method.
326 */
327
328static const struct hdb_method *
329find_method (const char *filename, const char **rest)
330{
331    const struct hdb_method *h;
332
333    for (h = methods; h->prefix != NULL; ++h) {
334	if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) {
335	    *rest = filename + strlen(h->prefix);
336	    return h;
337	}
338    }
339#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_LMDB) || defined(HAVE_NDBM)
340    if (strncmp(filename, "/", sizeof("/") - 1) == 0
341	|| strncmp(filename, "./", sizeof("./") - 1) == 0
342	|| strncmp(filename, "../", sizeof("../") - 1) == 0
343#ifdef WIN32
344        || strncmp(filename, "\\\\", sizeof("\\\\") - 1)
345        || (isalpha(filename[0]) && filename[1] == ':')
346#endif
347        )
348    {
349	*rest = filename;
350	return &default_dbmethod;
351    }
352#endif
353
354    return NULL;
355}
356
357struct cb_s {
358    const char *residual;
359    const char *filename;
360    const struct hdb_method *h;
361};
362
363static krb5_error_code KRB5_LIB_CALL
364callback(krb5_context context, const void *plug, void *plugctx, void *userctx)
365{
366    const struct hdb_method *h = (const struct hdb_method *)plug;
367    struct cb_s *cb_ctx = (struct cb_s *)userctx;
368
369    if (strncmp(cb_ctx->filename, h->prefix, strlen(h->prefix)) == 0) {
370	cb_ctx->residual = cb_ctx->filename + strlen(h->prefix) + 1;
371	cb_ctx->h = h;
372	return 0;
373    }
374   return KRB5_PLUGIN_NO_HANDLE;
375}
376
377static char *
378make_sym(const char *prefix)
379{
380    char *s, *sym;
381
382    errno = 0;
383    if (prefix == NULL || prefix[0] == '\0')
384        return NULL;
385    if ((s = strdup(prefix)) == NULL)
386        return NULL;
387    if (strchr(s, ':') != NULL)
388        *strchr(s, ':') = '\0';
389    if (asprintf(&sym, "hdb_%s_interface", s) == -1)
390        sym = NULL;
391    free(s);
392    return sym;
393}
394
395krb5_error_code
396hdb_list_builtin(krb5_context context, char **list)
397{
398    const struct hdb_method *h;
399    size_t len = 0;
400    char *buf = NULL;
401
402    for (h = methods; h->prefix != NULL; ++h) {
403	if (h->prefix[0] == '\0')
404	    continue;
405	len += strlen(h->prefix) + 2;
406    }
407
408    len += 1;
409    buf = malloc(len);
410    if (buf == NULL) {
411	return krb5_enomem(context);
412    }
413    buf[0] = '\0';
414
415    for (h = methods; h->prefix != NULL; ++h) {
416        if (h->create == NULL) {
417            struct cb_s cb_ctx;
418            char *f;
419            char *sym;
420
421            /* Try loading the plugin */
422            if (asprintf(&f, "%sfoo", h->prefix) == -1)
423                f = NULL;
424            if ((sym = make_sym(h->prefix)) == NULL) {
425                free(buf);
426                free(f);
427                return krb5_enomem(context);
428            }
429            cb_ctx.filename = f;
430            cb_ctx.residual = NULL;
431            cb_ctx.h = NULL;
432            (void)_krb5_plugin_run_f(context, "krb5", sym,
433                                     HDB_INTERFACE_VERSION, 0, &cb_ctx,
434                                     callback);
435            free(f);
436            free(sym);
437            if (cb_ctx.h == NULL || cb_ctx.h->create == NULL)
438                continue;
439        }
440	if (h != methods)
441	    strlcat(buf, ", ", len);
442	strlcat(buf, h->prefix, len);
443    }
444    *list = buf;
445    return 0;
446}
447
448krb5_error_code
449_hdb_keytab2hdb_entry(krb5_context context,
450		      const krb5_keytab_entry *ktentry,
451		      hdb_entry_ex *entry)
452{
453    entry->entry.kvno = ktentry->vno;
454    entry->entry.created_by.time = ktentry->timestamp;
455
456    entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0]));
457    if (entry->entry.keys.val == NULL)
458	return ENOMEM;
459    entry->entry.keys.len = 1;
460
461    entry->entry.keys.val[0].mkvno = NULL;
462    entry->entry.keys.val[0].salt = NULL;
463
464    return krb5_copy_keyblock_contents(context,
465				       &ktentry->keyblock,
466				       &entry->entry.keys.val[0].key);
467}
468
469/**
470 * Create a handle for a Kerberos database
471 *
472 * Create a handle for a Kerberos database backend specified by a
473 * filename.  Doesn't create a file if its doesn't exists, you have to
474 * use O_CREAT to tell the backend to create the file.
475 */
476
477krb5_error_code
478hdb_create(krb5_context context, HDB **db, const char *filename)
479{
480    struct cb_s cb_ctx;
481
482    if (filename == NULL)
483	filename = HDB_DEFAULT_DB;
484    cb_ctx.h = find_method (filename, &cb_ctx.residual);
485    cb_ctx.filename = filename;
486
487    if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) {
488        char *sym;
489
490        if ((sym = make_sym(filename)) == NULL)
491            return krb5_enomem(context);
492
493        (void)_krb5_plugin_run_f(context, "krb5", sym, HDB_INTERFACE_VERSION,
494                                 0, &cb_ctx, callback);
495
496        free(sym);
497    }
498    if (cb_ctx.h == NULL)
499	krb5_errx(context, 1, "No database support for %s", cb_ctx.filename);
500    return (*cb_ctx.h->create)(context, db, cb_ctx.residual);
501}
502