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#ifdef HAVE_OPENDIRECTORY 77 { HDB_INTERFACE_VERSION, "od:", hdb_od_create}, 78#endif 79 { HDB_INTERFACE_VERSION, "keytab:", hdb_keytab_create}, 80#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE) 81 { HDB_INTERFACE_VERSION, "ldap:", hdb_ldap_create}, 82 { HDB_INTERFACE_VERSION, "ldapi:", hdb_ldapi_create}, 83#endif 84#ifdef HAVE_SQLITE3 85 { HDB_INTERFACE_VERSION, "sqlite:", hdb_sqlite_create}, 86#endif 87 {0, NULL, NULL} 88}; 89 90#if HAVE_DB1 || HAVE_DB3 91static struct hdb_method dbmetod = 92 { HDB_INTERFACE_VERSION, "", hdb_db_create }; 93#elif defined(HAVE_NDBM) 94static struct hdb_method dbmetod = 95 { HDB_INTERFACE_VERSION, "", hdb_ndbm_create }; 96#endif 97 98 99krb5_error_code 100hdb_next_enctype2key(krb5_context context, 101 const hdb_entry *e, 102 krb5_enctype enctype, 103 Key **key) 104{ 105 Key *k; 106 107 for (k = *key ? (*key) + 1 : e->keys.val; 108 k < e->keys.val + e->keys.len; 109 k++) 110 { 111 if(k->key.keytype == enctype){ 112 *key = k; 113 return 0; 114 } 115 } 116 krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP, 117 "No next enctype %d for hdb-entry", 118 (int)enctype); 119 return KRB5_PROG_ETYPE_NOSUPP; /* XXX */ 120} 121 122krb5_error_code 123hdb_enctype2key(krb5_context context, 124 hdb_entry *e, 125 krb5_enctype enctype, 126 Key **key) 127{ 128 *key = NULL; 129 return hdb_next_enctype2key(context, e, enctype, key); 130} 131 132void 133hdb_free_key(Key *key) 134{ 135 memset(key->key.keyvalue.data, 136 0, 137 key->key.keyvalue.length); 138 free_Key(key); 139 free(key); 140} 141 142 143krb5_error_code 144hdb_lock(int fd, int operation) 145{ 146 int i, code = 0; 147 148 for(i = 0; i < 3; i++){ 149 code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB); 150 if(code == 0 || errno != EWOULDBLOCK) 151 break; 152 sleep(1); 153 } 154 if(code == 0) 155 return 0; 156 if(errno == EWOULDBLOCK) 157 return HDB_ERR_DB_INUSE; 158 return HDB_ERR_CANT_LOCK_DB; 159} 160 161krb5_error_code 162hdb_unlock(int fd) 163{ 164 int code; 165 code = flock(fd, LOCK_UN); 166 if(code) 167 return 4711 /* XXX */; 168 return 0; 169} 170 171void 172hdb_free_entry(krb5_context context, hdb_entry_ex *ent) 173{ 174 Key *k; 175 size_t i; 176 177 if (ent->free_entry) 178 (*ent->free_entry)(context, ent); 179 180 for(i = 0; i < ent->entry.keys.len; i++) { 181 k = &ent->entry.keys.val[i]; 182 183 memset (k->key.keyvalue.data, 0, k->key.keyvalue.length); 184 } 185 free_hdb_entry(&ent->entry); 186} 187 188krb5_error_code 189hdb_foreach(krb5_context context, 190 HDB *db, 191 unsigned flags, 192 hdb_foreach_func_t func, 193 void *data) 194{ 195 krb5_error_code ret; 196 hdb_entry_ex entry; 197 ret = db->hdb_firstkey(context, db, flags, &entry); 198 if (ret == 0) 199 krb5_clear_error_message(context); 200 while(ret == 0){ 201 ret = (*func)(context, db, &entry, data); 202 hdb_free_entry(context, &entry); 203 if(ret == 0) 204 ret = db->hdb_nextkey(context, db, flags, &entry); 205 } 206 if(ret == HDB_ERR_NOENTRY) 207 ret = 0; 208 return ret; 209} 210 211krb5_error_code 212hdb_check_db_format(krb5_context context, HDB *db) 213{ 214 krb5_data tag; 215 krb5_data version; 216 krb5_error_code ret, ret2; 217 unsigned ver; 218 int foo; 219 220 ret = db->hdb_lock(context, db, HDB_RLOCK); 221 if (ret) 222 return ret; 223 224 tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY; 225 tag.length = strlen(tag.data); 226 ret = (*db->hdb__get)(context, db, tag, &version); 227 ret2 = db->hdb_unlock(context, db); 228 if(ret) 229 return ret; 230 if (ret2) 231 return ret2; 232 foo = sscanf(version.data, "%u", &ver); 233 krb5_data_free (&version); 234 if (foo != 1) 235 return HDB_ERR_BADVERSION; 236 if(ver != HDB_DB_FORMAT) 237 return HDB_ERR_BADVERSION; 238 return 0; 239} 240 241krb5_error_code 242hdb_init_db(krb5_context context, HDB *db) 243{ 244 krb5_error_code ret, ret2; 245 krb5_data tag; 246 krb5_data version; 247 char ver[32]; 248 249 ret = hdb_check_db_format(context, db); 250 if(ret != HDB_ERR_NOENTRY) 251 return ret; 252 253 ret = db->hdb_lock(context, db, HDB_WLOCK); 254 if (ret) 255 return ret; 256 257 tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY; 258 tag.length = strlen(tag.data); 259 snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT); 260 version.data = ver; 261 version.length = strlen(version.data) + 1; /* zero terminated */ 262 ret = (*db->hdb__put)(context, db, 0, tag, version); 263 ret2 = db->hdb_unlock(context, db); 264 if (ret) { 265 if (ret2) 266 krb5_clear_error_message(context); 267 return ret; 268 } 269 return ret2; 270} 271 272#ifdef HAVE_DLOPEN 273 274 /* 275 * Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so, 276 * looking for the hdb_NAME_create symbol. 277 */ 278 279static const struct hdb_method * 280find_dynamic_method (krb5_context context, 281 const char *filename, 282 const char **rest) 283{ 284 static struct hdb_method method; 285 struct hdb_so_method *mso; 286 char *prefix, *path, *symbol; 287 const char *p; 288 void *dl; 289 size_t len; 290 291 p = strchr(filename, ':'); 292 293 /* if no prefix, don't know what module to load, just ignore it */ 294 if (p == NULL) 295 return NULL; 296 297 len = p - filename; 298 *rest = filename + len + 1; 299 300 prefix = malloc(len + 1); 301 if (prefix == NULL) 302 krb5_errx(context, 1, "out of memory"); 303 strlcpy(prefix, filename, len + 1); 304 305 if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1) 306 krb5_errx(context, 1, "out of memory"); 307 308#ifndef RTLD_NOW 309#define RTLD_NOW 0 310#endif 311#ifndef RTLD_GLOBAL 312#define RTLD_GLOBAL 0 313#endif 314 315 dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL); 316 if (dl == NULL) { 317 krb5_warnx(context, "error trying to load dynamic module %s: %s\n", 318 path, dlerror()); 319 free(prefix); 320 free(path); 321 return NULL; 322 } 323 324 if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1) 325 krb5_errx(context, 1, "out of memory"); 326 327 mso = (struct hdb_so_method *) dlsym(dl, symbol); 328 if (mso == NULL) { 329 krb5_warnx(context, "error finding symbol %s in %s: %s\n", 330 symbol, path, dlerror()); 331 dlclose(dl); 332 free(symbol); 333 free(prefix); 334 free(path); 335 return NULL; 336 } 337 free(path); 338 free(symbol); 339 340 if (mso->version != HDB_INTERFACE_VERSION) { 341 krb5_warnx(context, 342 "error wrong version in shared module %s " 343 "version: %d should have been %d\n", 344 prefix, mso->version, HDB_INTERFACE_VERSION); 345 dlclose(dl); 346 free(prefix); 347 return NULL; 348 } 349 350 if (mso->create == NULL) { 351 krb5_errx(context, 1, 352 "no entry point function in shared mod %s ", 353 prefix); 354 dlclose(dl); 355 free(prefix); 356 return NULL; 357 } 358 359 method.create = mso->create; 360 method.prefix = prefix; 361 362 return &method; 363} 364#endif /* HAVE_DLOPEN */ 365 366/* 367 * find the relevant method for `filename', returning a pointer to the 368 * rest in `rest'. 369 * return NULL if there's no such method. 370 */ 371 372static const struct hdb_method * 373find_method (const char *filename, const char **rest) 374{ 375 const struct hdb_method *h; 376 377 for (h = methods; h->prefix != NULL; ++h) { 378 if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) { 379 *rest = filename + strlen(h->prefix); 380 return h; 381 } 382 } 383#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM) 384 if (strncmp(filename, "/", 1) == 0 385 || strncmp(filename, "./", 2) == 0 386 || strncmp(filename, "../", 3) == 0) 387 { 388 *rest = filename; 389 return &dbmetod; 390 } 391#endif 392 393 return NULL; 394} 395 396krb5_error_code 397hdb_list_builtin(krb5_context context, char **list) 398{ 399 const struct hdb_method *h; 400 size_t len = 0; 401 char *buf = NULL; 402 403 for (h = methods; h->prefix != NULL; ++h) { 404 if (h->prefix[0] == '\0') 405 continue; 406 len += strlen(h->prefix) + 2; 407 } 408 409 len += 1; 410 buf = malloc(len); 411 if (buf == NULL) { 412 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 413 return ENOMEM; 414 } 415 buf[0] = '\0'; 416 417 for (h = methods; h->prefix != NULL; ++h) { 418 if (h != methods) 419 strlcat(buf, ", ", len); 420 strlcat(buf, h->prefix, len); 421 } 422 *list = buf; 423 return 0; 424} 425 426krb5_error_code 427_hdb_keytab2hdb_entry(krb5_context context, 428 const krb5_keytab_entry *ktentry, 429 hdb_entry_ex *entry) 430{ 431 entry->entry.kvno = ktentry->vno; 432 entry->entry.created_by.time = ktentry->timestamp; 433 434 entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0])); 435 if (entry->entry.keys.val == NULL) 436 return ENOMEM; 437 entry->entry.keys.len = 1; 438 439 entry->entry.keys.val[0].mkvno = NULL; 440 entry->entry.keys.val[0].salt = NULL; 441 442 return krb5_copy_keyblock_contents(context, 443 &ktentry->keyblock, 444 &entry->entry.keys.val[0].key); 445} 446 447/** 448 * Create a handle for a Kerberos database 449 * 450 * Create a handle for a Kerberos database backend specified by a 451 * filename. Doesn't create a file if its doesn't exists, you have to 452 * use O_CREAT to tell the backend to create the file. 453 */ 454 455krb5_error_code 456hdb_create(krb5_context context, HDB **db, const char *filename) 457{ 458 const struct hdb_method *h; 459 const char *residual; 460 krb5_error_code ret; 461 struct krb5_plugin *list = NULL, *e; 462 463 if(filename == NULL) 464 filename = HDB_DEFAULT_DB; 465 krb5_add_et_list(context, initialize_hdb_error_table_r); 466 h = find_method (filename, &residual); 467 468 if (h == NULL) { 469 ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "hdb", &list); 470 if(ret == 0 && list != NULL) { 471 for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) { 472 h = _krb5_plugin_get_symbol(e); 473 if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0 474 && h->interface_version == HDB_INTERFACE_VERSION) { 475 residual = filename + strlen(h->prefix); 476 break; 477 } 478 } 479 if (e == NULL) { 480 h = NULL; 481 _krb5_plugin_free(list); 482 } 483 } 484 } 485 486#ifdef HAVE_DLOPEN 487 if (h == NULL) 488 h = find_dynamic_method (context, filename, &residual); 489#endif 490 if (h == NULL) 491 krb5_errx(context, 1, "No database support for %s", filename); 492 return (*h->create)(context, db, residual); 493} 494