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