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