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