155682Smarkm/* 2233294Sstas * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan 3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden). 4233294Sstas * All rights reserved. 555682Smarkm * 6233294Sstas * Portions Copyright (c) 2009 Apple Inc. All rights reserved. 755682Smarkm * 8233294Sstas * Redistribution and use in source and binary forms, with or without 9233294Sstas * modification, are permitted provided that the following conditions 10233294Sstas * are met: 1155682Smarkm * 12233294Sstas * 1. Redistributions of source code must retain the above copyright 13233294Sstas * notice, this list of conditions and the following disclaimer. 1455682Smarkm * 15233294Sstas * 2. Redistributions in binary form must reproduce the above copyright 16233294Sstas * notice, this list of conditions and the following disclaimer in the 17233294Sstas * documentation and/or other materials provided with the distribution. 1855682Smarkm * 19233294Sstas * 3. Neither the name of the Institute nor the names of its contributors 20233294Sstas * may be used to endorse or promote products derived from this software 21233294Sstas * without specific prior written permission. 22233294Sstas * 23233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26233294Sstas * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33233294Sstas * SUCH DAMAGE. 3455682Smarkm */ 3555682Smarkm 36233294Sstas#include "krb5_locl.h" 3755682Smarkm#include "hdb_locl.h" 3855682Smarkm 39178825Sdfr#ifdef HAVE_DLFCN_H 40178825Sdfr#include <dlfcn.h> 41178825Sdfr#endif 42178825Sdfr 43233294Sstas/*! @mainpage Heimdal database backend library 44233294Sstas * 45233294Sstas * @section intro Introduction 46233294Sstas * 47233294Sstas * Heimdal libhdb library provides the backend support for Heimdal kdc 48233294Sstas * and kadmind. Its here where plugins for diffrent database engines 49233294Sstas * can be pluged in and extend support for here Heimdal get the 50233294Sstas * principal and policy data from. 51233294Sstas * 52233294Sstas * Example of Heimdal backend are: 53233294Sstas * - Berkeley DB 1.85 54233294Sstas * - Berkeley DB 3.0 55233294Sstas * - Berkeley DB 4.0 56233294Sstas * - New Berkeley DB 57233294Sstas * - LDAP 58233294Sstas * 59233294Sstas * 60233294Sstas * The project web page: http://www.h5l.org/ 61233294Sstas * 62233294Sstas */ 6372445Sassar 64233294Sstasconst int hdb_interface_version = HDB_INTERFACE_VERSION; 65233294Sstas 6672445Sassarstatic struct hdb_method methods[] = { 6790926Snectar#if HAVE_DB1 || HAVE_DB3 68233294Sstas { HDB_INTERFACE_VERSION, "db:", hdb_db_create}, 6972445Sassar#endif 70233294Sstas#if HAVE_DB1 71233294Sstas { HDB_INTERFACE_VERSION, "mit-db:", hdb_mdb_create}, 72233294Sstas#endif 7390926Snectar#if HAVE_NDBM 74233294Sstas { HDB_INTERFACE_VERSION, "ndbm:", hdb_ndbm_create}, 7572445Sassar#endif 76233294Sstas { HDB_INTERFACE_VERSION, "keytab:", hdb_keytab_create}, 77178825Sdfr#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE) 78233294Sstas { HDB_INTERFACE_VERSION, "ldap:", hdb_ldap_create}, 79233294Sstas { HDB_INTERFACE_VERSION, "ldapi:", hdb_ldapi_create}, 8072445Sassar#endif 81233294Sstas#ifdef HAVE_SQLITE3 82233294Sstas { HDB_INTERFACE_VERSION, "sqlite:", hdb_sqlite_create}, 8372445Sassar#endif 84233294Sstas {0, NULL, NULL} 8572445Sassar}; 8672445Sassar 87178825Sdfr#if HAVE_DB1 || HAVE_DB3 88233294Sstasstatic struct hdb_method dbmetod = 89233294Sstas { HDB_INTERFACE_VERSION, "", hdb_db_create }; 90178825Sdfr#elif defined(HAVE_NDBM) 91233294Sstasstatic struct hdb_method dbmetod = 92233294Sstas { HDB_INTERFACE_VERSION, "", hdb_ndbm_create }; 93178825Sdfr#endif 94178825Sdfr 95178825Sdfr 9655682Smarkmkrb5_error_code 9755682Smarkmhdb_next_enctype2key(krb5_context context, 9872445Sassar const hdb_entry *e, 9955682Smarkm krb5_enctype enctype, 10055682Smarkm Key **key) 10155682Smarkm{ 10255682Smarkm Key *k; 103233294Sstas 10472445Sassar for (k = *key ? (*key) + 1 : e->keys.val; 105233294Sstas k < e->keys.val + e->keys.len; 106233294Sstas k++) 107178825Sdfr { 10855682Smarkm if(k->key.keytype == enctype){ 10955682Smarkm *key = k; 11055682Smarkm return 0; 11155682Smarkm } 112178825Sdfr } 113233294Sstas krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP, 114233294Sstas "No next enctype %d for hdb-entry", 115178825Sdfr (int)enctype); 11655682Smarkm return KRB5_PROG_ETYPE_NOSUPP; /* XXX */ 11755682Smarkm} 11855682Smarkm 11955682Smarkmkrb5_error_code 120233294Sstashdb_enctype2key(krb5_context context, 121233294Sstas hdb_entry *e, 122233294Sstas krb5_enctype enctype, 12355682Smarkm Key **key) 12455682Smarkm{ 12555682Smarkm *key = NULL; 12655682Smarkm return hdb_next_enctype2key(context, e, enctype, key); 12755682Smarkm} 12855682Smarkm 12955682Smarkmvoid 13055682Smarkmhdb_free_key(Key *key) 13155682Smarkm{ 132233294Sstas memset(key->key.keyvalue.data, 13355682Smarkm 0, 13455682Smarkm key->key.keyvalue.length); 13555682Smarkm free_Key(key); 13655682Smarkm free(key); 13755682Smarkm} 13855682Smarkm 13955682Smarkm 14055682Smarkmkrb5_error_code 14155682Smarkmhdb_lock(int fd, int operation) 14255682Smarkm{ 14372445Sassar int i, code = 0; 14472445Sassar 14555682Smarkm for(i = 0; i < 3; i++){ 14655682Smarkm code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB); 14755682Smarkm if(code == 0 || errno != EWOULDBLOCK) 14855682Smarkm break; 14955682Smarkm sleep(1); 15055682Smarkm } 15155682Smarkm if(code == 0) 15255682Smarkm return 0; 15355682Smarkm if(errno == EWOULDBLOCK) 15455682Smarkm return HDB_ERR_DB_INUSE; 15555682Smarkm return HDB_ERR_CANT_LOCK_DB; 15655682Smarkm} 15755682Smarkm 15855682Smarkmkrb5_error_code 15955682Smarkmhdb_unlock(int fd) 16055682Smarkm{ 16155682Smarkm int code; 16255682Smarkm code = flock(fd, LOCK_UN); 16355682Smarkm if(code) 16455682Smarkm return 4711 /* XXX */; 16555682Smarkm return 0; 16655682Smarkm} 16755682Smarkm 16855682Smarkmvoid 169178825Sdfrhdb_free_entry(krb5_context context, hdb_entry_ex *ent) 17055682Smarkm{ 171233294Sstas size_t i; 17255682Smarkm 173178825Sdfr if (ent->free_entry) 174178825Sdfr (*ent->free_entry)(context, ent); 17555682Smarkm 176178825Sdfr for(i = 0; i < ent->entry.keys.len; ++i) { 177178825Sdfr Key *k = &ent->entry.keys.val[i]; 178178825Sdfr 17955682Smarkm memset (k->key.keyvalue.data, 0, k->key.keyvalue.length); 18055682Smarkm } 181178825Sdfr free_hdb_entry(&ent->entry); 18255682Smarkm} 18355682Smarkm 18455682Smarkmkrb5_error_code 18555682Smarkmhdb_foreach(krb5_context context, 18655682Smarkm HDB *db, 18755682Smarkm unsigned flags, 18855682Smarkm hdb_foreach_func_t func, 18955682Smarkm void *data) 19055682Smarkm{ 19155682Smarkm krb5_error_code ret; 192178825Sdfr hdb_entry_ex entry; 193178825Sdfr ret = db->hdb_firstkey(context, db, flags, &entry); 194178825Sdfr if (ret == 0) 195233294Sstas krb5_clear_error_message(context); 19655682Smarkm while(ret == 0){ 19755682Smarkm ret = (*func)(context, db, &entry, data); 19855682Smarkm hdb_free_entry(context, &entry); 19955682Smarkm if(ret == 0) 200178825Sdfr ret = db->hdb_nextkey(context, db, flags, &entry); 20155682Smarkm } 20255682Smarkm if(ret == HDB_ERR_NOENTRY) 20355682Smarkm ret = 0; 20455682Smarkm return ret; 20555682Smarkm} 20655682Smarkm 20755682Smarkmkrb5_error_code 20855682Smarkmhdb_check_db_format(krb5_context context, HDB *db) 20955682Smarkm{ 21055682Smarkm krb5_data tag; 21155682Smarkm krb5_data version; 212178825Sdfr krb5_error_code ret, ret2; 21355682Smarkm unsigned ver; 21455682Smarkm int foo; 21555682Smarkm 216178825Sdfr ret = db->hdb_lock(context, db, HDB_RLOCK); 217178825Sdfr if (ret) 218178825Sdfr return ret; 219178825Sdfr 220233294Sstas tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY; 22155682Smarkm tag.length = strlen(tag.data); 222178825Sdfr ret = (*db->hdb__get)(context, db, tag, &version); 223178825Sdfr ret2 = db->hdb_unlock(context, db); 22455682Smarkm if(ret) 22555682Smarkm return ret; 226178825Sdfr if (ret2) 227178825Sdfr return ret2; 22855682Smarkm foo = sscanf(version.data, "%u", &ver); 22955682Smarkm krb5_data_free (&version); 23055682Smarkm if (foo != 1) 23155682Smarkm return HDB_ERR_BADVERSION; 23255682Smarkm if(ver != HDB_DB_FORMAT) 23355682Smarkm return HDB_ERR_BADVERSION; 23455682Smarkm return 0; 23555682Smarkm} 23655682Smarkm 23755682Smarkmkrb5_error_code 23855682Smarkmhdb_init_db(krb5_context context, HDB *db) 23955682Smarkm{ 240178825Sdfr krb5_error_code ret, ret2; 24155682Smarkm krb5_data tag; 24255682Smarkm krb5_data version; 24355682Smarkm char ver[32]; 244233294Sstas 24555682Smarkm ret = hdb_check_db_format(context, db); 24655682Smarkm if(ret != HDB_ERR_NOENTRY) 24755682Smarkm return ret; 248233294Sstas 249178825Sdfr ret = db->hdb_lock(context, db, HDB_WLOCK); 250178825Sdfr if (ret) 251178825Sdfr return ret; 252178825Sdfr 253233294Sstas tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY; 25455682Smarkm tag.length = strlen(tag.data); 25555682Smarkm snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT); 25655682Smarkm version.data = ver; 25755682Smarkm version.length = strlen(version.data) + 1; /* zero terminated */ 258178825Sdfr ret = (*db->hdb__put)(context, db, 0, tag, version); 259178825Sdfr ret2 = db->hdb_unlock(context, db); 260178825Sdfr if (ret) { 261178825Sdfr if (ret2) 262233294Sstas krb5_clear_error_message(context); 263178825Sdfr return ret; 264178825Sdfr } 265178825Sdfr return ret2; 26655682Smarkm} 26755682Smarkm 268178825Sdfr#ifdef HAVE_DLOPEN 269178825Sdfr 270178825Sdfr /* 271178825Sdfr * Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so, 272178825Sdfr * looking for the hdb_NAME_create symbol. 273178825Sdfr */ 274178825Sdfr 275178825Sdfrstatic const struct hdb_method * 276178825Sdfrfind_dynamic_method (krb5_context context, 277233294Sstas const char *filename, 278178825Sdfr const char **rest) 279178825Sdfr{ 280178825Sdfr static struct hdb_method method; 281178825Sdfr struct hdb_so_method *mso; 282178825Sdfr char *prefix, *path, *symbol; 283178825Sdfr const char *p; 284178825Sdfr void *dl; 285178825Sdfr size_t len; 286233294Sstas 287178825Sdfr p = strchr(filename, ':'); 288178825Sdfr 289178825Sdfr /* if no prefix, don't know what module to load, just ignore it */ 290178825Sdfr if (p == NULL) 291178825Sdfr return NULL; 292178825Sdfr 293178825Sdfr len = p - filename; 294178825Sdfr *rest = filename + len + 1; 295233294Sstas 296233294Sstas prefix = malloc(len + 1); 297178825Sdfr if (prefix == NULL) 298178825Sdfr krb5_errx(context, 1, "out of memory"); 299233294Sstas strlcpy(prefix, filename, len + 1); 300233294Sstas 301178825Sdfr if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1) 302178825Sdfr krb5_errx(context, 1, "out of memory"); 303178825Sdfr 304178825Sdfr#ifndef RTLD_NOW 305178825Sdfr#define RTLD_NOW 0 306178825Sdfr#endif 307178825Sdfr#ifndef RTLD_GLOBAL 308178825Sdfr#define RTLD_GLOBAL 0 309178825Sdfr#endif 310178825Sdfr 311178825Sdfr dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL); 312178825Sdfr if (dl == NULL) { 313178825Sdfr krb5_warnx(context, "error trying to load dynamic module %s: %s\n", 314178825Sdfr path, dlerror()); 315178825Sdfr free(prefix); 316178825Sdfr free(path); 317178825Sdfr return NULL; 318178825Sdfr } 319233294Sstas 320178825Sdfr if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1) 321178825Sdfr krb5_errx(context, 1, "out of memory"); 322233294Sstas 323233294Sstas mso = (struct hdb_so_method *) dlsym(dl, symbol); 324178825Sdfr if (mso == NULL) { 325233294Sstas krb5_warnx(context, "error finding symbol %s in %s: %s\n", 326178825Sdfr symbol, path, dlerror()); 327178825Sdfr dlclose(dl); 328178825Sdfr free(symbol); 329178825Sdfr free(prefix); 330178825Sdfr free(path); 331178825Sdfr return NULL; 332178825Sdfr } 333178825Sdfr free(path); 334178825Sdfr free(symbol); 335178825Sdfr 336178825Sdfr if (mso->version != HDB_INTERFACE_VERSION) { 337233294Sstas krb5_warnx(context, 338178825Sdfr "error wrong version in shared module %s " 339233294Sstas "version: %d should have been %d\n", 340178825Sdfr prefix, mso->version, HDB_INTERFACE_VERSION); 341178825Sdfr dlclose(dl); 342178825Sdfr free(prefix); 343178825Sdfr return NULL; 344178825Sdfr } 345178825Sdfr 346178825Sdfr if (mso->create == NULL) { 347178825Sdfr krb5_errx(context, 1, 348178825Sdfr "no entry point function in shared mod %s ", 349178825Sdfr prefix); 350178825Sdfr dlclose(dl); 351178825Sdfr free(prefix); 352178825Sdfr return NULL; 353178825Sdfr } 354178825Sdfr 355178825Sdfr method.create = mso->create; 356178825Sdfr method.prefix = prefix; 357178825Sdfr 358178825Sdfr return &method; 359178825Sdfr} 360178825Sdfr#endif /* HAVE_DLOPEN */ 361178825Sdfr 36272445Sassar/* 36372445Sassar * find the relevant method for `filename', returning a pointer to the 36472445Sassar * rest in `rest'. 36572445Sassar * return NULL if there's no such method. 36672445Sassar */ 36755682Smarkm 36872445Sassarstatic const struct hdb_method * 36972445Sassarfind_method (const char *filename, const char **rest) 37055682Smarkm{ 37172445Sassar const struct hdb_method *h; 37255682Smarkm 373178825Sdfr for (h = methods; h->prefix != NULL; ++h) { 37472445Sassar if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) { 37572445Sassar *rest = filename + strlen(h->prefix); 37672445Sassar return h; 37772445Sassar } 378178825Sdfr } 379178825Sdfr#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM) 380178825Sdfr if (strncmp(filename, "/", 1) == 0 381178825Sdfr || strncmp(filename, "./", 2) == 0 382178825Sdfr || strncmp(filename, "../", 3) == 0) 383178825Sdfr { 384178825Sdfr *rest = filename; 385178825Sdfr return &dbmetod; 386178825Sdfr } 387178825Sdfr#endif 388178825Sdfr 38972445Sassar return NULL; 39055682Smarkm} 39155682Smarkm 39255682Smarkmkrb5_error_code 393178825Sdfrhdb_list_builtin(krb5_context context, char **list) 394178825Sdfr{ 395178825Sdfr const struct hdb_method *h; 396178825Sdfr size_t len = 0; 397178825Sdfr char *buf = NULL; 398178825Sdfr 399178825Sdfr for (h = methods; h->prefix != NULL; ++h) { 400178825Sdfr if (h->prefix[0] == '\0') 401178825Sdfr continue; 402178825Sdfr len += strlen(h->prefix) + 2; 403178825Sdfr } 404178825Sdfr 405178825Sdfr len += 1; 406178825Sdfr buf = malloc(len); 407178825Sdfr if (buf == NULL) { 408233294Sstas krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 409178825Sdfr return ENOMEM; 410178825Sdfr } 411178825Sdfr buf[0] = '\0'; 412178825Sdfr 413178825Sdfr for (h = methods; h->prefix != NULL; ++h) { 414178825Sdfr if (h != methods) 415178825Sdfr strlcat(buf, ", ", len); 416178825Sdfr strlcat(buf, h->prefix, len); 417178825Sdfr } 418178825Sdfr *list = buf; 419178825Sdfr return 0; 420178825Sdfr} 421178825Sdfr 422178825Sdfrkrb5_error_code 423233294Sstas_hdb_keytab2hdb_entry(krb5_context context, 424233294Sstas const krb5_keytab_entry *ktentry, 425233294Sstas hdb_entry_ex *entry) 426233294Sstas{ 427233294Sstas entry->entry.kvno = ktentry->vno; 428233294Sstas entry->entry.created_by.time = ktentry->timestamp; 429233294Sstas 430233294Sstas entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0])); 431233294Sstas if (entry->entry.keys.val == NULL) 432233294Sstas return ENOMEM; 433233294Sstas entry->entry.keys.len = 1; 434233294Sstas 435233294Sstas entry->entry.keys.val[0].mkvno = NULL; 436233294Sstas entry->entry.keys.val[0].salt = NULL; 437233294Sstas 438233294Sstas return krb5_copy_keyblock_contents(context, 439233294Sstas &ktentry->keyblock, 440233294Sstas &entry->entry.keys.val[0].key); 441233294Sstas} 442233294Sstas 443233294Sstas/** 444233294Sstas * Create a handle for a Kerberos database 445233294Sstas * 446233294Sstas * Create a handle for a Kerberos database backend specified by a 447233294Sstas * filename. Doesn't create a file if its doesn't exists, you have to 448233294Sstas * use O_CREAT to tell the backend to create the file. 449233294Sstas */ 450233294Sstas 451233294Sstaskrb5_error_code 45272445Sassarhdb_create(krb5_context context, HDB **db, const char *filename) 45355682Smarkm{ 45472445Sassar const struct hdb_method *h; 45572445Sassar const char *residual; 456233294Sstas krb5_error_code ret; 457233294Sstas struct krb5_plugin *list = NULL, *e; 45855682Smarkm 45972445Sassar if(filename == NULL) 46072445Sassar filename = HDB_DEFAULT_DB; 46190926Snectar krb5_add_et_list(context, initialize_hdb_error_table_r); 46272445Sassar h = find_method (filename, &residual); 463233294Sstas 464233294Sstas if (h == NULL) { 465233294Sstas ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "hdb", &list); 466233294Sstas if(ret == 0 && list != NULL) { 467233294Sstas for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) { 468233294Sstas h = _krb5_plugin_get_symbol(e); 469233294Sstas if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0 470233294Sstas && h->interface_version == HDB_INTERFACE_VERSION) { 471233294Sstas residual = filename + strlen(h->prefix); 472233294Sstas break; 473233294Sstas } 474233294Sstas } 475233294Sstas if (e == NULL) { 476233294Sstas h = NULL; 477233294Sstas _krb5_plugin_free(list); 478233294Sstas } 479233294Sstas } 480233294Sstas } 481233294Sstas 482178825Sdfr#ifdef HAVE_DLOPEN 48372445Sassar if (h == NULL) 484178825Sdfr h = find_dynamic_method (context, filename, &residual); 485178825Sdfr#endif 486178825Sdfr if (h == NULL) 487178825Sdfr krb5_errx(context, 1, "No database support for %s", filename); 48872445Sassar return (*h->create)(context, db, residual); 48955682Smarkm} 490