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