1/* $NetBSD: hdb-mdb.c,v 1.3 2019/12/15 22:50:49 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997 - 2006 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * Copyright (c) 2011 - Howard Chu, Symas Corp. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * 3. Neither the name of the Institute nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#include "hdb_locl.h" 38 39#if HAVE_LMDB 40 41/* LMDB */ 42 43#include <lmdb.h> 44 45#define KILO 1024 46 47typedef struct mdb_info { 48 MDB_env *e; 49 MDB_txn *t; 50 MDB_dbi d; 51 MDB_cursor *c; 52} mdb_info; 53 54static krb5_error_code 55DB_close(krb5_context context, HDB *db) 56{ 57 mdb_info *mi = (mdb_info *)db->hdb_db; 58 59 mdb_cursor_close(mi->c); 60 mdb_txn_abort(mi->t); 61 mdb_env_close(mi->e); 62 mi->c = 0; 63 mi->t = 0; 64 mi->e = 0; 65 return 0; 66} 67 68static krb5_error_code 69DB_destroy(krb5_context context, HDB *db) 70{ 71 krb5_error_code ret; 72 73 ret = hdb_clear_master_key (context, db); 74 free(db->hdb_name); 75 free(db->hdb_db); 76 free(db); 77 return ret; 78} 79 80static krb5_error_code 81DB_set_sync(krb5_context context, HDB *db, int on) 82{ 83 mdb_info *mi = (mdb_info *)db->hdb_db; 84 85 mdb_env_set_flags(mi->e, MDB_NOSYNC, !on); 86 return mdb_env_sync(mi->e, 0); 87} 88 89static krb5_error_code 90DB_lock(krb5_context context, HDB *db, int operation) 91{ 92 db->lock_count++; 93 return 0; 94} 95 96static krb5_error_code 97DB_unlock(krb5_context context, HDB *db) 98{ 99 if (db->lock_count > 1) { 100 db->lock_count--; 101 return 0; 102 } 103 heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match"); 104 db->lock_count--; 105 return 0; 106} 107 108 109static krb5_error_code 110DB_seq(krb5_context context, HDB *db, 111 unsigned flags, hdb_entry_ex *entry, int flag) 112{ 113 mdb_info *mi = db->hdb_db; 114 MDB_val key, value; 115 krb5_data key_data, data; 116 int code; 117 118 key.mv_size = 0; 119 value.mv_size = 0; 120 code = mdb_cursor_get(mi->c, &key, &value, flag); 121 if (code == MDB_NOTFOUND) 122 return HDB_ERR_NOENTRY; 123 if (code) 124 return code; 125 126 key_data.data = key.mv_data; 127 key_data.length = key.mv_size; 128 data.data = value.mv_data; 129 data.length = value.mv_size; 130 memset(entry, 0, sizeof(*entry)); 131 if (hdb_value2entry(context, &data, &entry->entry)) 132 return DB_seq(context, db, flags, entry, MDB_NEXT); 133 if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) { 134 code = hdb_unseal_keys (context, db, &entry->entry); 135 if (code) 136 hdb_free_entry (context, entry); 137 } 138 if (entry->entry.principal == NULL) { 139 entry->entry.principal = malloc(sizeof(*entry->entry.principal)); 140 if (entry->entry.principal == NULL) { 141 hdb_free_entry (context, entry); 142 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 143 return ENOMEM; 144 } else { 145 hdb_key2principal(context, &key_data, entry->entry.principal); 146 } 147 } 148 return 0; 149} 150 151 152static krb5_error_code 153DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) 154{ 155 mdb_info *mi = db->hdb_db; 156 int code; 157 158 /* Always start with a fresh cursor to pick up latest DB state */ 159 if (mi->t) 160 mdb_txn_abort(mi->t); 161 162 code = mdb_txn_begin(mi->e, NULL, MDB_RDONLY, &mi->t); 163 if (code) 164 return code; 165 166 code = mdb_cursor_open(mi->t, mi->d, &mi->c); 167 if (code) 168 return code; 169 170 return DB_seq(context, db, flags, entry, MDB_FIRST); 171} 172 173 174static krb5_error_code 175DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) 176{ 177 return DB_seq(context, db, flags, entry, MDB_NEXT); 178} 179 180static krb5_error_code 181DB_rename(krb5_context context, HDB *db, const char *new_name) 182{ 183 int ret; 184 char *old, *new; 185 186 if (strncmp(new_name, "mdb:", sizeof("mdb:") - 1) == 0) 187 new_name += sizeof("mdb:") - 1; 188 else if (strncmp(new_name, "lmdb:", sizeof("lmdb:") - 1) == 0) 189 new_name += sizeof("lmdb:") - 1; 190 if (asprintf(&old, "%s.mdb", db->hdb_name) == -1) 191 return ENOMEM; 192 if (asprintf(&new, "%s.mdb", new_name) == -1) { 193 free(old); 194 return ENOMEM; 195 } 196 ret = rename(old, new); 197 free(old); 198 free(new); 199 if(ret) 200 return errno; 201 202 free(db->hdb_name); 203 db->hdb_name = strdup(new_name); 204 return 0; 205} 206 207static krb5_error_code 208DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply) 209{ 210 mdb_info *mi = (mdb_info*)db->hdb_db; 211 MDB_txn *txn; 212 MDB_val k, v; 213 int code; 214 215 k.mv_data = key.data; 216 k.mv_size = key.length; 217 218 code = mdb_txn_begin(mi->e, NULL, MDB_RDONLY, &txn); 219 if (code) 220 return code; 221 222 code = mdb_get(txn, mi->d, &k, &v); 223 if (code == 0) 224 krb5_data_copy(reply, v.mv_data, v.mv_size); 225 mdb_txn_abort(txn); 226 if(code == MDB_NOTFOUND) 227 return HDB_ERR_NOENTRY; 228 return code; 229} 230 231static krb5_error_code 232DB__put(krb5_context context, HDB *db, int replace, 233 krb5_data key, krb5_data value) 234{ 235 mdb_info *mi = (mdb_info*)db->hdb_db; 236 MDB_txn *txn; 237 MDB_val k, v; 238 int code; 239 240 k.mv_data = key.data; 241 k.mv_size = key.length; 242 v.mv_data = value.data; 243 v.mv_size = value.length; 244 245 code = mdb_txn_begin(mi->e, NULL, 0, &txn); 246 if (code) 247 return code; 248 249 code = mdb_put(txn, mi->d, &k, &v, replace ? 0 : MDB_NOOVERWRITE); 250 if (code) 251 mdb_txn_abort(txn); 252 else 253 code = mdb_txn_commit(txn); 254 /* 255 * No need to call mdb_env_sync(); it's done automatically if MDB_NOSYNC is 256 * not set. 257 */ 258 if(code == MDB_KEYEXIST) 259 return HDB_ERR_EXISTS; 260 return code; 261} 262 263static krb5_error_code 264DB__del(krb5_context context, HDB *db, krb5_data key) 265{ 266 mdb_info *mi = (mdb_info*)db->hdb_db; 267 MDB_txn *txn; 268 MDB_val k; 269 krb5_error_code code; 270 271 k.mv_data = key.data; 272 k.mv_size = key.length; 273 274 code = mdb_txn_begin(mi->e, NULL, 0, &txn); 275 if (code) 276 return code; 277 278 code = mdb_del(txn, mi->d, &k, NULL); 279 if (code) 280 mdb_txn_abort(txn); 281 else 282 code = mdb_txn_commit(txn); 283 /* 284 * No need to call mdb_env_sync(); it's done automatically if MDB_NOSYNC is 285 * not set. 286 */ 287 if(code == MDB_NOTFOUND) 288 return HDB_ERR_NOENTRY; 289 return code; 290} 291 292static krb5_error_code 293DB_open(krb5_context context, HDB *db, int flags, mode_t mode) 294{ 295 mdb_info *mi = (mdb_info *)db->hdb_db; 296 MDB_txn *txn; 297 char *fn; 298 krb5_error_code ret; 299 int myflags = MDB_NOSUBDIR, tmp; 300 301 if((flags & O_ACCMODE) == O_RDONLY) 302 myflags |= MDB_RDONLY; 303 304 if (asprintf(&fn, "%s.mdb", db->hdb_name) == -1) 305 return krb5_enomem(context); 306 if (mdb_env_create(&mi->e)) { 307 free(fn); 308 return krb5_enomem(context); 309 } 310 311 tmp = krb5_config_get_int_default(context, NULL, 0, "kdc", 312 "hdb-mdb-maxreaders", NULL); 313 if (tmp) { 314 ret = mdb_env_set_maxreaders(mi->e, tmp); 315 if (ret) { 316 free(fn); 317 krb5_set_error_message(context, ret, "setting maxreaders on %s: %s", 318 db->hdb_name, mdb_strerror(ret)); 319 return ret; 320 } 321 } 322 323 tmp = krb5_config_get_int_default(context, NULL, 0, "kdc", 324 "hdb-mdb-mapsize", NULL); 325 if (tmp) { 326 size_t maps = tmp; 327 maps *= KILO; 328 ret = mdb_env_set_mapsize(mi->e, maps); 329 if (ret) { 330 free(fn); 331 krb5_set_error_message(context, ret, "setting mapsize on %s: %s", 332 db->hdb_name, mdb_strerror(ret)); 333 return ret; 334 } 335 } 336 337 ret = mdb_env_open(mi->e, fn, myflags, mode); 338 free(fn); 339 if (ret) { 340fail: 341 mdb_env_close(mi->e); 342 mi->e = 0; 343 krb5_set_error_message(context, ret, "opening %s: %s", 344 db->hdb_name, mdb_strerror(ret)); 345 return ret; 346 } 347 348 ret = mdb_txn_begin(mi->e, NULL, MDB_RDONLY, &txn); 349 if (ret) 350 goto fail; 351 352 ret = mdb_open(txn, NULL, 0, &mi->d); 353 mdb_txn_abort(txn); 354 if (ret) 355 goto fail; 356 357 if((flags & O_ACCMODE) == O_RDONLY) 358 ret = hdb_check_db_format(context, db); 359 else 360 ret = hdb_init_db(context, db); 361 if(ret == HDB_ERR_NOENTRY) 362 return 0; 363 if (ret) { 364 DB_close(context, db); 365 krb5_set_error_message(context, ret, "hdb_open: failed %s database %s", 366 (flags & O_ACCMODE) == O_RDONLY ? 367 "checking format of" : "initialize", 368 db->hdb_name); 369 } 370 371 return ret; 372} 373 374krb5_error_code 375hdb_mdb_create(krb5_context context, HDB **db, 376 const char *filename) 377{ 378 *db = calloc(1, sizeof(**db)); 379 if (*db == NULL) { 380 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 381 return ENOMEM; 382 } 383 384 (*db)->hdb_db = calloc(1, sizeof(mdb_info)); 385 if ((*db)->hdb_db == NULL) { 386 free(*db); 387 *db = NULL; 388 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 389 return ENOMEM; 390 } 391 (*db)->hdb_name = strdup(filename); 392 if ((*db)->hdb_name == NULL) { 393 free((*db)->hdb_db); 394 free(*db); 395 *db = NULL; 396 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 397 return ENOMEM; 398 } 399 (*db)->hdb_master_key_set = 0; 400 (*db)->hdb_openp = 0; 401 (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL; 402 (*db)->hdb_open = DB_open; 403 (*db)->hdb_close = DB_close; 404 (*db)->hdb_fetch_kvno = _hdb_fetch_kvno; 405 (*db)->hdb_store = _hdb_store; 406 (*db)->hdb_remove = _hdb_remove; 407 (*db)->hdb_firstkey = DB_firstkey; 408 (*db)->hdb_nextkey= DB_nextkey; 409 (*db)->hdb_lock = DB_lock; 410 (*db)->hdb_unlock = DB_unlock; 411 (*db)->hdb_rename = DB_rename; 412 (*db)->hdb__get = DB__get; 413 (*db)->hdb__put = DB__put; 414 (*db)->hdb__del = DB__del; 415 (*db)->hdb_destroy = DB_destroy; 416 (*db)->hdb_set_sync = DB_set_sync; 417 return 0; 418} 419#endif /* HAVE_LMDB */ 420