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