Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/plugins/kdb/lmdb/kdb_lmdb.c
34914 views
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* plugins/kdb/lmdb/klmdb.c - KDB module using LMDB */
3
/*
4
* Copyright (C) 2018 by the Massachusetts Institute of Technology.
5
* All rights reserved.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
*
11
* * Redistributions of source code must retain the above copyright
12
* notice, this list of conditions and the following disclaimer.
13
*
14
* * Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in
16
* the documentation and/or other materials provided with the
17
* distribution.
18
*
19
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30
* OF THE POSSIBILITY OF SUCH DAMAGE.
31
*/
32
33
/*
34
* Thread-safety note: unlike the other two in-tree KDB modules, this module
35
* performs no mutex locking to ensure thread safety. As the KDC and kadmind
36
* are single-threaded, and applications are not allowed to access the same
37
* krb5_context in multiple threads simultaneously, there is no current need
38
* for this code to be thread-safe. If a need arises in the future, mutex
39
* locking should be added around the read_txn and load_txn fields of
40
* lmdb_context to ensure that only one thread at a time accesses those
41
* transactions.
42
*/
43
44
/*
45
* This KDB module stores principal and policy data using LMDB (Lightning
46
* Memory-Mapped Database). We use two LMDB environments, the first to hold
47
* the majority of principal and policy data (suffix ".mdb") in the "principal"
48
* and "policy" databases, and the second to hold the three non-replicated
49
* account lockout attributes (suffix ".lockout.mdb") in the "lockout"
50
* database. The KDC only needs to write to the lockout database.
51
*
52
* For iteration we create a read transaction in the main environment for the
53
* cursor. Because the iteration callback might need to create its own
54
* transactions for write operations (e.g. for kdb5_util
55
* update_princ_encryption), we set the MDB_NOTLS flag on the main environment,
56
* so that a thread can hold multiple transactions.
57
*
58
* To mitigate the overhead from MDB_NOTLS, we keep around a read_txn handle
59
* in the database context for get operations, using mdb_txn_reset() and
60
* mdb_txn_renew() between calls.
61
*
62
* For database loads, kdb5_util calls the create() method with the "temporary"
63
* db_arg, and then promotes the finished contents at the end with the
64
* promote_db() method. In this case we create or open the same LMDB
65
* environments as above, open a write_txn handle for the lifetime of the
66
* context, and empty out the principal and policy databases. On promote_db()
67
* we commit the transaction. We do not empty the lockout database and write
68
* to it non-transactionally during the load so that we don't block writes by
69
* the KDC; this isn't ideal if the load is aborted, but it shouldn't cause any
70
* practical issues.
71
*
72
* For iprop loads, kdb5_util also includes the "merge_nra" db_arg, signifying
73
* that the lockout attributes from existing principal entries should be
74
* preserved. This attribute is noted in the LMDB context, and put_principal
75
* operations will not write to the lockout database if an existing lockout
76
* entry is already present for the principal.
77
*/
78
79
#include "k5-int.h"
80
#include <kadm5/admin.h>
81
#include "kdb5.h"
82
#include "klmdb-int.h"
83
#include <lmdb.h>
84
85
/* The presence of any of these mask bits indicates a change to one of the
86
* three principal lockout attributes. */
87
#define LOCKOUT_MASK (KADM5_LAST_SUCCESS | KADM5_LAST_FAILED | \
88
KADM5_FAIL_AUTH_COUNT)
89
90
/* The default map size (for both environments) in megabytes. */
91
#define DEFAULT_MAPSIZE 128
92
93
#ifndef O_CLOEXEC
94
#define O_CLOEXEC 0
95
#endif
96
97
typedef struct {
98
char *path;
99
char *lockout_path;
100
krb5_boolean temporary; /* save changes until promote_db */
101
krb5_boolean merge_nra; /* preserve existing lockout attributes */
102
krb5_boolean disable_last_success;
103
krb5_boolean disable_lockout;
104
krb5_boolean nosync;
105
size_t mapsize;
106
unsigned int maxreaders;
107
108
MDB_env *env;
109
MDB_env *lockout_env;
110
MDB_dbi princ_db;
111
MDB_dbi policy_db;
112
MDB_dbi lockout_db;
113
114
/* Used for get operations; each transaction is short-lived but we save the
115
* handle between calls to reduce overhead from MDB_NOTLS. */
116
MDB_txn *read_txn;
117
118
/* Write transaction for load operations (create() with the "temporary"
119
* db_arg). */
120
MDB_txn *load_txn;
121
} klmdb_context;
122
123
static krb5_error_code
124
klerr(krb5_context context, int err, const char *msg)
125
{
126
krb5_error_code ret;
127
klmdb_context *dbc = context->dal_handle->db_context;
128
129
/* Pass through system errors; map MDB errors to a com_err code. */
130
ret = (err > 0) ? err : KRB5_KDB_ACCESS_ERROR;
131
132
k5_setmsg(context, ret, _("%s (path: %s): %s"), msg, dbc->path,
133
mdb_strerror(err));
134
return ret;
135
}
136
137
/* Using db_args and the profile, create a DB context inside context and
138
* initialize its configurable parameters. */
139
static krb5_error_code
140
configure_context(krb5_context context, const char *conf_section,
141
char *const *db_args)
142
{
143
krb5_error_code ret;
144
klmdb_context *dbc;
145
char *pval = NULL;
146
const char *path = NULL;
147
profile_t profile = context->profile;
148
size_t i;
149
int bval, ival;
150
151
dbc = k5alloc(sizeof(*dbc), &ret);
152
if (dbc == NULL)
153
return ret;
154
context->dal_handle->db_context = dbc;
155
156
for (i = 0; db_args != NULL && db_args[i] != NULL; i++) {
157
if (strcmp(db_args[i], "temporary") == 0) {
158
dbc->temporary = TRUE;
159
} else if (strcmp(db_args[i], "merge_nra") == 0) {
160
dbc->merge_nra = TRUE;
161
} else if (strncmp(db_args[i], "dbname=", 7) == 0) {
162
path = db_args[i] + 7;
163
} else {
164
ret = EINVAL;
165
k5_setmsg(context, ret, _("Unsupported argument \"%s\" for LMDB"),
166
db_args[i]);
167
goto cleanup;
168
}
169
}
170
171
if (path == NULL) {
172
/* Check for database_name in the db_module section. */
173
ret = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,
174
KRB5_CONF_DATABASE_NAME, NULL, &pval);
175
if (!ret && pval == NULL) {
176
/* For compatibility, check for database_name in the realm. */
177
ret = profile_get_string(profile, KDB_REALM_SECTION,
178
KRB5_DB_GET_REALM(context),
179
KRB5_CONF_DATABASE_NAME, DEFAULT_KDB_FILE,
180
&pval);
181
}
182
if (ret)
183
goto cleanup;
184
path = pval;
185
}
186
187
if (asprintf(&dbc->path, "%s.mdb", path) < 0) {
188
dbc->path = NULL;
189
ret = ENOMEM;
190
goto cleanup;
191
}
192
if (asprintf(&dbc->lockout_path, "%s.lockout.mdb", path) < 0) {
193
dbc->lockout_path = NULL;
194
ret = ENOMEM;
195
goto cleanup;
196
}
197
198
ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
199
KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);
200
if (ret)
201
goto cleanup;
202
dbc->disable_last_success = bval;
203
204
ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
205
KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);
206
if (ret)
207
goto cleanup;
208
dbc->disable_lockout = bval;
209
210
ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,
211
KRB5_CONF_MAPSIZE, DEFAULT_MAPSIZE, &ival);
212
if (ret)
213
goto cleanup;
214
dbc->mapsize = (size_t)ival * 1024 * 1024;
215
216
ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,
217
KRB5_CONF_MAX_READERS, 0, &ival);
218
if (ret)
219
goto cleanup;
220
dbc->maxreaders = ival;
221
222
ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
223
KRB5_CONF_NOSYNC, FALSE, &bval);
224
if (ret)
225
goto cleanup;
226
dbc->nosync = bval;
227
228
cleanup:
229
profile_release_string(pval);
230
return ret;
231
}
232
233
static krb5_error_code
234
open_lmdb_env(krb5_context context, klmdb_context *dbc,
235
krb5_boolean is_lockout, krb5_boolean readonly,
236
MDB_env **env_out)
237
{
238
krb5_error_code ret;
239
const char *path = is_lockout ? dbc->lockout_path : dbc->path;
240
unsigned int flags;
241
MDB_env *env = NULL;
242
int err;
243
244
*env_out = NULL;
245
246
err = mdb_env_create(&env);
247
if (err)
248
goto lmdb_error;
249
250
/* Use a pair of files instead of a subdirectory. */
251
flags = MDB_NOSUBDIR;
252
253
/*
254
* For the primary database, tie read transaction locktable slots to the
255
* transaction and not the thread, so read transactions for iteration
256
* cursors can coexist with short-lived transactions for operations invoked
257
* by the iteration callback..
258
*/
259
if (!is_lockout)
260
flags |= MDB_NOTLS;
261
262
if (readonly)
263
flags |= MDB_RDONLY;
264
265
/* Durability for lockout records is never worth the performance penalty.
266
* For the primary environment it might be, so we make it configurable. */
267
if (is_lockout || dbc->nosync)
268
flags |= MDB_NOSYNC;
269
270
/* We use one database in the lockout env, two in the primary env. */
271
err = mdb_env_set_maxdbs(env, is_lockout ? 1 : 2);
272
if (err)
273
goto lmdb_error;
274
275
if (dbc->mapsize) {
276
err = mdb_env_set_mapsize(env, dbc->mapsize);
277
if (err)
278
goto lmdb_error;
279
}
280
281
if (dbc->maxreaders) {
282
err = mdb_env_set_maxreaders(env, dbc->maxreaders);
283
if (err)
284
goto lmdb_error;
285
}
286
287
err = mdb_env_open(env, path, flags, S_IRUSR | S_IWUSR);
288
if (err)
289
goto lmdb_error;
290
291
*env_out = env;
292
return 0;
293
294
lmdb_error:
295
ret = klerr(context, err, _("LMDB environment open failure"));
296
mdb_env_close(env);
297
return ret;
298
}
299
300
/* Read a key from the primary environment, using a saved read transaction from
301
* the database context. Return KRB5_KDB_NOENTRY if the key is not found. */
302
static krb5_error_code
303
fetch(krb5_context context, MDB_dbi db, MDB_val *key, MDB_val *val_out)
304
{
305
krb5_error_code ret = 0;
306
klmdb_context *dbc = context->dal_handle->db_context;
307
int err;
308
309
if (dbc->read_txn == NULL)
310
err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &dbc->read_txn);
311
else
312
err = mdb_txn_renew(dbc->read_txn);
313
314
if (!err)
315
err = mdb_get(dbc->read_txn, db, key, val_out);
316
317
if (err == MDB_NOTFOUND)
318
ret = KRB5_KDB_NOENTRY;
319
else if (err)
320
ret = klerr(context, err, _("LMDB read failure"));
321
322
mdb_txn_reset(dbc->read_txn);
323
return ret;
324
}
325
326
/* If we are using a lockout database, try to fetch the lockout attributes for
327
* key and set them in entry. */
328
static void
329
fetch_lockout(krb5_context context, MDB_val *key, krb5_db_entry *entry)
330
{
331
klmdb_context *dbc = context->dal_handle->db_context;
332
MDB_txn *txn = NULL;
333
MDB_val val;
334
int err;
335
336
if (dbc->lockout_env == NULL)
337
return;
338
err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);
339
if (!err)
340
err = mdb_get(txn, dbc->lockout_db, key, &val);
341
if (!err && val.mv_size >= LOCKOUT_RECORD_LEN)
342
klmdb_decode_princ_lockout(context, entry, val.mv_data);
343
mdb_txn_abort(txn);
344
}
345
346
/*
347
* Store a value for key in the specified database within the primary
348
* environment. Use the saved load transaction if one is present, or a
349
* temporary write transaction if not. If no_overwrite is true and the key
350
* already exists, return KRB5_KDB_INUSE. If must_overwrite is true and the
351
* key does not already exist, return KRB5_KDB_NOENTRY.
352
*/
353
static krb5_error_code
354
put(krb5_context context, MDB_dbi db, char *keystr, uint8_t *bytes, size_t len,
355
krb5_boolean no_overwrite, krb5_boolean must_overwrite)
356
{
357
klmdb_context *dbc = context->dal_handle->db_context;
358
unsigned int putflags = no_overwrite ? MDB_NOOVERWRITE : 0;
359
MDB_txn *temp_txn = NULL, *txn;
360
MDB_val key = { strlen(keystr), keystr }, val = { len, bytes }, dummy;
361
int err;
362
363
if (dbc->load_txn != NULL) {
364
txn = dbc->load_txn;
365
} else {
366
err = mdb_txn_begin(dbc->env, NULL, 0, &temp_txn);
367
if (err)
368
goto error;
369
txn = temp_txn;
370
}
371
372
if (must_overwrite && mdb_get(txn, db, &key, &dummy) == MDB_NOTFOUND) {
373
mdb_txn_abort(temp_txn);
374
return KRB5_KDB_NOENTRY;
375
}
376
377
err = mdb_put(txn, db, &key, &val, putflags);
378
if (err)
379
goto error;
380
381
if (temp_txn != NULL) {
382
err = mdb_txn_commit(temp_txn);
383
temp_txn = NULL;
384
if (err)
385
goto error;
386
}
387
388
return 0;
389
390
error:
391
mdb_txn_abort(temp_txn);
392
if (err == MDB_KEYEXIST)
393
return KRB5_KDB_INUSE;
394
else
395
return klerr(context, err, _("LMDB write failure"));
396
}
397
398
/* Delete an entry from the specified env and database, using a temporary write
399
* transaction. Return KRB5_KDB_NOENTRY if the key does not exist. */
400
static krb5_error_code
401
del(krb5_context context, MDB_env *env, MDB_dbi db, char *keystr)
402
{
403
krb5_error_code ret = 0;
404
MDB_txn *txn = NULL;
405
MDB_val key = { strlen(keystr), keystr };
406
int err;
407
408
err = mdb_txn_begin(env, NULL, 0, &txn);
409
if (!err)
410
err = mdb_del(txn, db, &key, NULL);
411
if (!err) {
412
err = mdb_txn_commit(txn);
413
txn = NULL;
414
}
415
416
if (err == MDB_NOTFOUND)
417
ret = KRB5_KDB_NOENTRY;
418
else if (err)
419
ret = klerr(context, err, _("LMDB delete failure"));
420
421
mdb_txn_abort(txn);
422
return ret;
423
}
424
425
/* Zero out and unlink filename. */
426
static krb5_error_code
427
destroy_file(const char *filename)
428
{
429
krb5_error_code ret;
430
struct stat st;
431
ssize_t len;
432
off_t pos;
433
uint8_t buf[BUFSIZ], zbuf[BUFSIZ] = { 0 };
434
int fd;
435
436
fd = open(filename, O_RDWR | O_CLOEXEC, 0);
437
if (fd < 0)
438
return errno;
439
set_cloexec_fd(fd);
440
if (fstat(fd, &st) == -1)
441
goto error;
442
443
memset(zbuf, 0, BUFSIZ);
444
pos = 0;
445
while (pos < st.st_size) {
446
len = read(fd, buf, BUFSIZ);
447
if (len < 0)
448
goto error;
449
/* Only rewrite the block if it's not already zeroed, in case the file
450
* is sparse. */
451
if (memcmp(buf, zbuf, len) != 0) {
452
(void)lseek(fd, pos, SEEK_SET);
453
len = write(fd, zbuf, len);
454
if (len < 0)
455
goto error;
456
}
457
pos += len;
458
}
459
close(fd);
460
461
if (unlink(filename) != 0)
462
return errno;
463
return 0;
464
465
error:
466
ret = errno;
467
close(fd);
468
return ret;
469
}
470
471
static krb5_error_code
472
klmdb_lib_init(void)
473
{
474
return 0;
475
}
476
477
static krb5_error_code
478
klmdb_lib_cleanup(void)
479
{
480
return 0;
481
}
482
483
static krb5_error_code
484
klmdb_fini(krb5_context context)
485
{
486
klmdb_context *dbc;
487
488
dbc = context->dal_handle->db_context;
489
if (dbc == NULL)
490
return 0;
491
mdb_txn_abort(dbc->read_txn);
492
mdb_txn_abort(dbc->load_txn);
493
mdb_env_close(dbc->env);
494
mdb_env_close(dbc->lockout_env);
495
free(dbc->path);
496
free(dbc->lockout_path);
497
free(dbc);
498
context->dal_handle->db_context = NULL;
499
return 0;
500
}
501
502
static krb5_error_code
503
klmdb_open(krb5_context context, char *conf_section, char **db_args, int mode)
504
{
505
krb5_error_code ret;
506
klmdb_context *dbc;
507
krb5_boolean readonly;
508
MDB_txn *txn = NULL;
509
struct stat st;
510
int err;
511
512
if (context->dal_handle->db_context != NULL)
513
return 0;
514
515
ret = configure_context(context, conf_section, db_args);
516
if (ret)
517
return ret;
518
dbc = context->dal_handle->db_context;
519
520
if (stat(dbc->path, &st) != 0) {
521
ret = ENOENT;
522
k5_setmsg(context, ret, _("LMDB file %s does not exist"), dbc->path);
523
goto error;
524
}
525
526
/* Open the primary environment and databases. The KDC can open this
527
* environment read-only. */
528
readonly = (mode & KRB5_KDB_OPEN_RO) || (mode & KRB5_KDB_SRV_TYPE_KDC);
529
ret = open_lmdb_env(context, dbc, FALSE, readonly, &dbc->env);
530
if (ret)
531
goto error;
532
err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
533
if (err)
534
goto lmdb_error;
535
err = mdb_dbi_open(txn, "principal", 0, &dbc->princ_db);
536
if (err)
537
goto lmdb_error;
538
err = mdb_dbi_open(txn, "policy", 0, &dbc->policy_db);
539
if (err)
540
goto lmdb_error;
541
err = mdb_txn_commit(txn);
542
txn = NULL;
543
if (err)
544
goto lmdb_error;
545
546
/* Open the lockout environment and database if we will need it. */
547
if (!dbc->disable_last_success || !dbc->disable_lockout) {
548
readonly = !!(mode & KRB5_KDB_OPEN_RO);
549
ret = open_lmdb_env(context, dbc, TRUE, readonly, &dbc->lockout_env);
550
if (ret)
551
goto error;
552
err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);
553
if (err)
554
goto lmdb_error;
555
err = mdb_dbi_open(txn, "lockout", 0, &dbc->lockout_db);
556
if (err)
557
goto lmdb_error;
558
err = mdb_txn_commit(txn);
559
txn = NULL;
560
if (err)
561
goto lmdb_error;
562
}
563
564
return 0;
565
566
lmdb_error:
567
ret = klerr(context, err, _("LMDB open failure"));
568
error:
569
mdb_txn_abort(txn);
570
klmdb_fini(context);
571
return ret;
572
}
573
574
static krb5_error_code
575
klmdb_create(krb5_context context, char *conf_section, char **db_args)
576
{
577
krb5_error_code ret;
578
klmdb_context *dbc;
579
MDB_txn *txn = NULL;
580
struct stat st;
581
int err;
582
583
if (context->dal_handle->db_context != NULL)
584
return 0;
585
586
ret = configure_context(context, conf_section, db_args);
587
if (ret)
588
return ret;
589
dbc = context->dal_handle->db_context;
590
591
if (!dbc->temporary) {
592
if (stat(dbc->path, &st) == 0) {
593
ret = ENOENT;
594
k5_setmsg(context, ret, _("LMDB file %s already exists"),
595
dbc->path);
596
goto error;
597
}
598
}
599
600
/* Open (and create if necessary) the LMDB environments. */
601
ret = open_lmdb_env(context, dbc, FALSE, FALSE, &dbc->env);
602
if (ret)
603
goto error;
604
ret = open_lmdb_env(context, dbc, TRUE, FALSE, &dbc->lockout_env);
605
if (ret)
606
goto error;
607
608
/* Open the primary databases, creating them if they don't exist. */
609
err = mdb_txn_begin(dbc->env, NULL, 0, &txn);
610
if (err)
611
goto lmdb_error;
612
err = mdb_dbi_open(txn, "principal", MDB_CREATE, &dbc->princ_db);
613
if (err)
614
goto lmdb_error;
615
err = mdb_dbi_open(txn, "policy", MDB_CREATE, &dbc->policy_db);
616
if (err)
617
goto lmdb_error;
618
err = mdb_txn_commit(txn);
619
txn = NULL;
620
if (err)
621
goto lmdb_error;
622
623
/* Create the lockout database if it doesn't exist. */
624
err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
625
if (err)
626
goto lmdb_error;
627
err = mdb_dbi_open(txn, "lockout", MDB_CREATE, &dbc->lockout_db);
628
if (err)
629
goto lmdb_error;
630
err = mdb_txn_commit(txn);
631
txn = NULL;
632
if (err)
633
goto lmdb_error;
634
635
if (dbc->temporary) {
636
/* Create a load transaction and empty the primary databases within
637
* it. */
638
err = mdb_txn_begin(dbc->env, NULL, 0, &dbc->load_txn);
639
if (err)
640
goto lmdb_error;
641
err = mdb_drop(dbc->load_txn, dbc->princ_db, 0);
642
if (err)
643
goto lmdb_error;
644
err = mdb_drop(dbc->load_txn, dbc->policy_db, 0);
645
if (err)
646
goto lmdb_error;
647
}
648
649
/* Close the lockout environment if we won't need it. */
650
if (dbc->disable_last_success && dbc->disable_lockout) {
651
mdb_env_close(dbc->lockout_env);
652
dbc->lockout_env = NULL;
653
dbc->lockout_db = 0;
654
}
655
656
return 0;
657
658
lmdb_error:
659
ret = klerr(context, err, _("LMDB create error"));
660
error:
661
mdb_txn_abort(txn);
662
klmdb_fini(context);
663
return ret;
664
}
665
666
/* Unlink the "-lock" extension of path. */
667
static krb5_error_code
668
unlink_lock_file(krb5_context context, const char *path)
669
{
670
char *lock_path;
671
int st;
672
673
if (asprintf(&lock_path, "%s-lock", path) < 0)
674
return ENOMEM;
675
st = unlink(lock_path);
676
if (st)
677
k5_prependmsg(context, st, _("Could not unlink %s"), lock_path);
678
free(lock_path);
679
return st;
680
}
681
682
static krb5_error_code
683
klmdb_destroy(krb5_context context, char *conf_section, char **db_args)
684
{
685
krb5_error_code ret;
686
klmdb_context *dbc;
687
688
if (context->dal_handle->db_context != NULL)
689
klmdb_fini(context);
690
ret = configure_context(context, conf_section, db_args);
691
if (ret)
692
goto cleanup;
693
dbc = context->dal_handle->db_context;
694
695
ret = destroy_file(dbc->path);
696
if (ret)
697
goto cleanup;
698
ret = unlink_lock_file(context, dbc->path);
699
if (ret)
700
goto cleanup;
701
702
ret = destroy_file(dbc->lockout_path);
703
if (ret)
704
goto cleanup;
705
ret = unlink_lock_file(context, dbc->lockout_path);
706
707
cleanup:
708
klmdb_fini(context);
709
return ret;
710
}
711
712
static krb5_error_code
713
klmdb_get_principal(krb5_context context, krb5_const_principal searchfor,
714
unsigned int flags, krb5_db_entry **entry_out)
715
{
716
krb5_error_code ret;
717
klmdb_context *dbc = context->dal_handle->db_context;
718
MDB_val key, val;
719
char *name = NULL;
720
721
*entry_out = NULL;
722
if (dbc == NULL)
723
return KRB5_KDB_DBNOTINITED;
724
725
ret = krb5_unparse_name(context, searchfor, &name);
726
if (ret)
727
goto cleanup;
728
729
key.mv_data = name;
730
key.mv_size = strlen(name);
731
ret = fetch(context, dbc->princ_db, &key, &val);
732
if (ret)
733
goto cleanup;
734
735
ret = klmdb_decode_princ(context, name, strlen(name),
736
val.mv_data, val.mv_size, entry_out);
737
if (ret)
738
goto cleanup;
739
740
fetch_lockout(context, &key, *entry_out);
741
742
cleanup:
743
krb5_free_unparsed_name(context, name);
744
return ret;
745
}
746
747
static krb5_error_code
748
klmdb_put_principal(krb5_context context, krb5_db_entry *entry, char **db_args)
749
{
750
krb5_error_code ret;
751
klmdb_context *dbc = context->dal_handle->db_context;
752
MDB_val key, val, dummy;
753
MDB_txn *txn = NULL;
754
uint8_t lockout[LOCKOUT_RECORD_LEN], *enc;
755
size_t len;
756
char *name = NULL;
757
int err;
758
759
if (db_args != NULL) {
760
/* This module does not support DB arguments for put_principal. */
761
k5_setmsg(context, EINVAL, _("Unsupported argument \"%s\" for lmdb"),
762
db_args[0]);
763
return EINVAL;
764
}
765
766
if (dbc == NULL)
767
return KRB5_KDB_DBNOTINITED;
768
769
ret = krb5_unparse_name(context, entry->princ, &name);
770
if (ret)
771
goto cleanup;
772
773
ret = klmdb_encode_princ(context, entry, &enc, &len);
774
if (ret)
775
goto cleanup;
776
ret = put(context, dbc->princ_db, name, enc, len, FALSE, FALSE);
777
free(enc);
778
if (ret)
779
goto cleanup;
780
781
/*
782
* Write the lockout attributes to the lockout database if we are using
783
* one. During a load operation, changes to lockout attributes will become
784
* visible before the load is finished, which is an acceptable compromise
785
* on load atomicity.
786
*/
787
if (dbc->lockout_env != NULL &&
788
(entry->mask & (LOCKOUT_MASK | KADM5_PRINCIPAL))) {
789
key.mv_data = name;
790
key.mv_size = strlen(name);
791
klmdb_encode_princ_lockout(context, entry, lockout);
792
val.mv_data = lockout;
793
val.mv_size = sizeof(lockout);
794
err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
795
if (!err && dbc->merge_nra) {
796
/* During an iprop load, do not change existing lockout entries. */
797
if (mdb_get(txn, dbc->lockout_db, &key, &dummy) == 0)
798
goto cleanup;
799
}
800
if (!err)
801
err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);
802
if (!err) {
803
err = mdb_txn_commit(txn);
804
txn = NULL;
805
}
806
if (err) {
807
ret = klerr(context, err, _("LMDB lockout write failure"));
808
goto cleanup;
809
}
810
}
811
812
cleanup:
813
mdb_txn_abort(txn);
814
krb5_free_unparsed_name(context, name);
815
return ret;
816
}
817
818
static krb5_error_code
819
klmdb_delete_principal(krb5_context context, krb5_const_principal searchfor)
820
{
821
krb5_error_code ret;
822
klmdb_context *dbc = context->dal_handle->db_context;
823
char *name;
824
825
if (dbc == NULL)
826
return KRB5_KDB_DBNOTINITED;
827
828
ret = krb5_unparse_name(context, searchfor, &name);
829
if (ret)
830
return ret;
831
832
ret = del(context, dbc->env, dbc->princ_db, name);
833
if (!ret && dbc->lockout_env != NULL)
834
(void)del(context, dbc->lockout_env, dbc->lockout_db, name);
835
836
krb5_free_unparsed_name(context, name);
837
return ret;
838
}
839
840
static krb5_error_code
841
klmdb_iterate(krb5_context context, char *match_expr,
842
krb5_error_code (*func)(void *, krb5_db_entry *), void *arg,
843
krb5_flags iterflags)
844
{
845
krb5_error_code ret;
846
klmdb_context *dbc = context->dal_handle->db_context;
847
krb5_db_entry *entry;
848
MDB_txn *txn = NULL;
849
MDB_cursor *cursor = NULL;
850
MDB_val key, val;
851
MDB_cursor_op op = (iterflags & KRB5_DB_ITER_REV) ? MDB_PREV : MDB_NEXT;
852
int err;
853
854
if (dbc == NULL)
855
return KRB5_KDB_DBNOTINITED;
856
857
err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
858
if (err)
859
goto lmdb_error;
860
err = mdb_cursor_open(txn, dbc->princ_db, &cursor);
861
if (err)
862
goto lmdb_error;
863
for (;;) {
864
err = mdb_cursor_get(cursor, &key, &val, op);
865
if (err == MDB_NOTFOUND)
866
break;
867
if (err)
868
goto lmdb_error;
869
ret = klmdb_decode_princ(context, key.mv_data, key.mv_size,
870
val.mv_data, val.mv_size, &entry);
871
if (ret)
872
goto cleanup;
873
fetch_lockout(context, &key, entry);
874
ret = (*func)(arg, entry);
875
krb5_db_free_principal(context, entry);
876
if (ret)
877
goto cleanup;
878
}
879
ret = 0;
880
goto cleanup;
881
882
lmdb_error:
883
ret = klerr(context, err, _("LMDB principal iteration failure"));
884
cleanup:
885
mdb_cursor_close(cursor);
886
mdb_txn_abort(txn);
887
return ret;
888
}
889
890
krb5_error_code
891
klmdb_get_policy(krb5_context context, char *name, osa_policy_ent_t *policy)
892
{
893
krb5_error_code ret;
894
klmdb_context *dbc = context->dal_handle->db_context;
895
MDB_val key, val;
896
897
*policy = NULL;
898
if (dbc == NULL)
899
return KRB5_KDB_DBNOTINITED;
900
901
key.mv_data = name;
902
key.mv_size = strlen(name);
903
ret = fetch(context, dbc->policy_db, &key, &val);
904
if (ret)
905
return ret;
906
return klmdb_decode_policy(context, name, strlen(name),
907
val.mv_data, val.mv_size, policy);
908
}
909
910
static krb5_error_code
911
klmdb_create_policy(krb5_context context, osa_policy_ent_t policy)
912
{
913
krb5_error_code ret;
914
klmdb_context *dbc = context->dal_handle->db_context;
915
uint8_t *enc;
916
size_t len;
917
918
if (dbc == NULL)
919
return KRB5_KDB_DBNOTINITED;
920
921
ret = klmdb_encode_policy(context, policy, &enc, &len);
922
if (ret)
923
return ret;
924
ret = put(context, dbc->policy_db, policy->name, enc, len, TRUE, FALSE);
925
free(enc);
926
return ret;
927
}
928
929
static krb5_error_code
930
klmdb_put_policy(krb5_context context, osa_policy_ent_t policy)
931
{
932
krb5_error_code ret;
933
klmdb_context *dbc = context->dal_handle->db_context;
934
uint8_t *enc;
935
size_t len;
936
937
if (dbc == NULL)
938
return KRB5_KDB_DBNOTINITED;
939
940
ret = klmdb_encode_policy(context, policy, &enc, &len);
941
if (ret)
942
return ret;
943
ret = put(context, dbc->policy_db, policy->name, enc, len, FALSE, TRUE);
944
free(enc);
945
return ret;
946
}
947
948
static krb5_error_code
949
klmdb_iter_policy(krb5_context context, char *match_entry,
950
osa_adb_iter_policy_func func, void *arg)
951
{
952
krb5_error_code ret;
953
klmdb_context *dbc = context->dal_handle->db_context;
954
osa_policy_ent_t pol;
955
MDB_txn *txn = NULL;
956
MDB_cursor *cursor = NULL;
957
MDB_val key, val;
958
int err;
959
960
if (dbc == NULL)
961
return KRB5_KDB_DBNOTINITED;
962
963
err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
964
if (err)
965
goto lmdb_error;
966
err = mdb_cursor_open(txn, dbc->policy_db, &cursor);
967
if (err)
968
goto lmdb_error;
969
for (;;) {
970
err = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
971
if (err == MDB_NOTFOUND)
972
break;
973
if (err)
974
goto lmdb_error;
975
ret = klmdb_decode_policy(context, key.mv_data, key.mv_size,
976
val.mv_data, val.mv_size, &pol);
977
if (ret)
978
goto cleanup;
979
(*func)(arg, pol);
980
krb5_db_free_policy(context, pol);
981
}
982
ret = 0;
983
goto cleanup;
984
985
lmdb_error:
986
ret = klerr(context, err, _("LMDB policy iteration failure"));
987
cleanup:
988
mdb_cursor_close(cursor);
989
mdb_txn_abort(txn);
990
return ret;
991
}
992
993
static krb5_error_code
994
klmdb_delete_policy(krb5_context context, char *policy)
995
{
996
klmdb_context *dbc = context->dal_handle->db_context;
997
998
if (dbc == NULL)
999
return KRB5_KDB_DBNOTINITED;
1000
return del(context, dbc->env, dbc->policy_db, policy);
1001
}
1002
1003
static krb5_error_code
1004
klmdb_promote_db(krb5_context context, char *conf_section, char **db_args)
1005
{
1006
krb5_error_code ret = 0;
1007
klmdb_context *dbc = context->dal_handle->db_context;
1008
int err;
1009
1010
if (dbc == NULL)
1011
return KRB5_KDB_DBNOTINITED;
1012
if (dbc->load_txn == NULL)
1013
return EINVAL;
1014
err = mdb_txn_commit(dbc->load_txn);
1015
dbc->load_txn = NULL;
1016
if (err)
1017
ret = klerr(context, err, _("LMDB transaction commit failure"));
1018
klmdb_fini(context);
1019
return ret;
1020
}
1021
1022
static krb5_error_code
1023
klmdb_check_policy_as(krb5_context context, krb5_kdc_req *request,
1024
krb5_db_entry *client, krb5_db_entry *server,
1025
krb5_timestamp kdc_time, const char **status,
1026
krb5_pa_data ***e_data)
1027
{
1028
krb5_error_code ret;
1029
klmdb_context *dbc = context->dal_handle->db_context;
1030
1031
if (dbc->disable_lockout)
1032
return 0;
1033
1034
ret = klmdb_lockout_check_policy(context, client, kdc_time);
1035
if (ret == KRB5KDC_ERR_CLIENT_REVOKED)
1036
*status = "LOCKED_OUT";
1037
return ret;
1038
}
1039
1040
static void
1041
klmdb_audit_as_req(krb5_context context, krb5_kdc_req *request,
1042
const krb5_address *local_addr,
1043
const krb5_address *remote_addr, krb5_db_entry *client,
1044
krb5_db_entry *server, krb5_timestamp authtime,
1045
krb5_error_code status)
1046
{
1047
klmdb_context *dbc = context->dal_handle->db_context;
1048
1049
(void)klmdb_lockout_audit(context, client, authtime, status,
1050
dbc->disable_last_success, dbc->disable_lockout);
1051
}
1052
1053
krb5_error_code
1054
klmdb_update_lockout(krb5_context context, krb5_db_entry *entry,
1055
krb5_timestamp stamp, krb5_boolean zero_fail_count,
1056
krb5_boolean set_last_success,
1057
krb5_boolean set_last_failure)
1058
{
1059
krb5_error_code ret;
1060
klmdb_context *dbc = context->dal_handle->db_context;
1061
krb5_db_entry dummy = { 0 };
1062
uint8_t lockout[LOCKOUT_RECORD_LEN];
1063
MDB_txn *txn = NULL;
1064
MDB_val key, val;
1065
char *name = NULL;
1066
int err;
1067
1068
if (dbc == NULL)
1069
return KRB5_KDB_DBNOTINITED;
1070
if (dbc->lockout_env == NULL)
1071
return 0;
1072
if (!zero_fail_count && !set_last_success && !set_last_failure)
1073
return 0;
1074
1075
ret = krb5_unparse_name(context, entry->princ, &name);
1076
if (ret)
1077
goto cleanup;
1078
key.mv_data = name;
1079
key.mv_size = strlen(name);
1080
1081
err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
1082
if (err)
1083
goto lmdb_error;
1084
/* Fetch base lockout info within txn so we update transactionally. */
1085
err = mdb_get(txn, dbc->lockout_db, &key, &val);
1086
if (!err && val.mv_size >= LOCKOUT_RECORD_LEN) {
1087
klmdb_decode_princ_lockout(context, &dummy, val.mv_data);
1088
} else {
1089
dummy.last_success = entry->last_success;
1090
dummy.last_failed = entry->last_failed;
1091
dummy.fail_auth_count = entry->fail_auth_count;
1092
}
1093
1094
if (zero_fail_count)
1095
dummy.fail_auth_count = 0;
1096
if (set_last_success)
1097
dummy.last_success = stamp;
1098
if (set_last_failure) {
1099
dummy.last_failed = stamp;
1100
dummy.fail_auth_count++;
1101
}
1102
1103
klmdb_encode_princ_lockout(context, &dummy, lockout);
1104
val.mv_data = lockout;
1105
val.mv_size = sizeof(lockout);
1106
err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);
1107
if (err)
1108
goto lmdb_error;
1109
err = mdb_txn_commit(txn);
1110
txn = NULL;
1111
if (err)
1112
goto lmdb_error;
1113
goto cleanup;
1114
1115
lmdb_error:
1116
ret = klerr(context, err, _("LMDB lockout update failure"));
1117
cleanup:
1118
krb5_free_unparsed_name(context, name);
1119
mdb_txn_abort(txn);
1120
return 0;
1121
}
1122
1123
kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_lmdb, kdb_function_table) = {
1124
.maj_ver = KRB5_KDB_DAL_MAJOR_VERSION,
1125
.min_ver = 0,
1126
.init_library = klmdb_lib_init,
1127
.fini_library = klmdb_lib_cleanup,
1128
.init_module = klmdb_open,
1129
.fini_module = klmdb_fini,
1130
.create = klmdb_create,
1131
.destroy = klmdb_destroy,
1132
.get_principal = klmdb_get_principal,
1133
.put_principal = klmdb_put_principal,
1134
.delete_principal = klmdb_delete_principal,
1135
.iterate = klmdb_iterate,
1136
.create_policy = klmdb_create_policy,
1137
.get_policy = klmdb_get_policy,
1138
.put_policy = klmdb_put_policy,
1139
.iter_policy = klmdb_iter_policy,
1140
.delete_policy = klmdb_delete_policy,
1141
.promote_db = klmdb_promote_db,
1142
.check_policy_as = klmdb_check_policy_as,
1143
.audit_as_req = klmdb_audit_as_req
1144
};
1145
1146