Home | History | Annotate | Line # | Download | only in hdb
hdb-mdb.c revision 1.3
      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 Hgskolan
      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 
     47 typedef struct mdb_info {
     48     MDB_env *e;
     49     MDB_txn *t;
     50     MDB_dbi d;
     51     MDB_cursor *c;
     52 } mdb_info;
     53 
     54 static krb5_error_code
     55 DB_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 
     68 static krb5_error_code
     69 DB_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 
     80 static krb5_error_code
     81 DB_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 
     89 static krb5_error_code
     90 DB_lock(krb5_context context, HDB *db, int operation)
     91 {
     92     db->lock_count++;
     93     return 0;
     94 }
     95 
     96 static krb5_error_code
     97 DB_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 
    109 static krb5_error_code
    110 DB_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 
    152 static krb5_error_code
    153 DB_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 
    174 static krb5_error_code
    175 DB_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 
    180 static krb5_error_code
    181 DB_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 
    207 static krb5_error_code
    208 DB__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 
    231 static krb5_error_code
    232 DB__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 
    263 static krb5_error_code
    264 DB__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 
    292 static krb5_error_code
    293 DB_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) {
    340 fail:
    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 
    374 krb5_error_code
    375 hdb_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