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