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
66 const int hdb_interface_version = HDB_INTERFACE_VERSION;
67
68 static 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)
111 static struct hdb_method default_dbmethod =
112 { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_mdb_create };
113 #elif defined(HAVE_DB3)
114 static struct hdb_method default_dbmethod =
115 { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db3_create };
116 #elif defined(HAVE_DB1)
117 static struct hdb_method default_dbmethod =
118 { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_db1_create };
119 #elif defined(HAVE_NDBM)
120 static struct hdb_method default_dbmethod =
121 { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_ndbm_create };
122 #endif
123
124 const Keys *
hdb_kvno2keys(krb5_context context,const hdb_entry * e,krb5_kvno kvno)125 hdb_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
149 krb5_error_code
hdb_next_enctype2key(krb5_context context,const hdb_entry * e,const Keys * keyset,krb5_enctype enctype,Key ** key)150 hdb_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
171 krb5_error_code
hdb_enctype2key(krb5_context context,hdb_entry * e,const Keys * keyset,krb5_enctype enctype,Key ** key)172 hdb_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
182 void
hdb_free_key(Key * key)183 hdb_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
193 krb5_error_code
hdb_lock(int fd,int operation)194 hdb_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
211 krb5_error_code
hdb_unlock(int fd)212 hdb_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
221 void
hdb_free_entry(krb5_context context,hdb_entry_ex * ent)222 hdb_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
238 krb5_error_code
hdb_foreach(krb5_context context,HDB * db,unsigned flags,hdb_foreach_func_t func,void * data)239 hdb_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
261 krb5_error_code
hdb_check_db_format(krb5_context context,HDB * db)262 hdb_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
291 krb5_error_code
hdb_init_db(krb5_context context,HDB * db)292 hdb_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
328 static const struct hdb_method *
find_method(const char * filename,const char ** rest)329 find_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
357 struct cb_s {
358 const char *residual;
359 const char *filename;
360 const struct hdb_method *h;
361 };
362
363 static krb5_error_code KRB5_LIB_CALL
callback(krb5_context context,const void * plug,void * plugctx,void * userctx)364 callback(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
377 static char *
make_sym(const char * prefix)378 make_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
395 krb5_error_code
hdb_list_builtin(krb5_context context,char ** list)396 hdb_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
448 krb5_error_code
_hdb_keytab2hdb_entry(krb5_context context,const krb5_keytab_entry * ktentry,hdb_entry_ex * entry)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
477 krb5_error_code
hdb_create(krb5_context context,HDB ** db,const char * filename)478 hdb_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