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
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
DB_close(krb5_context context,HDB * db)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
DB_destroy(krb5_context context,HDB * db)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
DB_set_sync(krb5_context context,HDB * db,int on)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
DB_lock(krb5_context context,HDB * db,int operation)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
DB_unlock(krb5_context context,HDB * db)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
DB_seq(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry,int flag)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
DB_firstkey(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry)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
DB_nextkey(krb5_context context,HDB * db,unsigned flags,hdb_entry_ex * entry)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
DB_rename(krb5_context context,HDB * db,const char * new_name)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
DB__get(krb5_context context,HDB * db,krb5_data key,krb5_data * reply)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
DB__put(krb5_context context,HDB * db,int replace,krb5_data key,krb5_data value)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
DB__del(krb5_context context,HDB * db,krb5_data key)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
DB_open(krb5_context context,HDB * db,int flags,mode_t mode)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
hdb_mdb_create(krb5_context context,HDB ** db,const char * filename)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