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