Set uber record nominal version when truncating
[heimdal.git] / lib / kadm5 / log.c
blobefe1f781c852077ff96270c38ed6128e9a5ab2fc
1 /*
2 * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "kadm5_locl.h"
35 #include "heim_threads.h"
37 RCSID("$Id$");
40 * A log consists of a sequence of records of this form:
42 * version number 4 bytes -\
43 * time in seconds 4 bytes +> preamble --+> header
44 * operation (enum kadm_ops) 4 bytes -/ /
45 * n, length of payload 4 bytes --------------+
46 * PAYLOAD DATA... n bytes
47 * n, length of payload 4 bytes ----------------+> trailer
48 * version number 4 bytes ->postamble ---/
50 * I.e., records have a header and a trailer so that knowing the offset
51 * of an record's start or end one can traverse the log forwards and
52 * backwards.
54 * The log always starts with a nop record (uber record) that contains the
55 * offset (8 bytes) of the first unconfirmed record (typically EOF), and the
56 * version number and timestamp of the preceding last confirmed record:
58 * offset of next new record 8 bytes
59 * last record time 4 bytes
60 * last record version number 4 bytes
62 * When an iprop slave receives a complete database, it saves that version as
63 * the last confirmed version, without writing any other records to the log. We
64 * use that version as the basis for further updates.
66 * kadm5 write operations are done in this order:
68 * - replay unconfirmed log records
69 * - write (append) and fsync() the log record for the kadm5 update
70 * - update the HDB (which includes fsync() or moral equivalent)
71 * - update the log uber record to mark the log record written as
72 * confirmed (not fsync()ed)
74 * This makes it possible and safe to seek to the logical end of the log
75 * (that is, the end of the last confirmed record) without traversing
76 * the whole log forward from offset zero. Unconfirmed records (which
77 * -currently- should never be more than one) can then be found (and
78 * rolled forward) by traversing forward from the logical end of the
79 * log. The trailers make it possible to traverse the log backwards
80 * from the logical end.
82 * This also makes the log + the HDB a two-phase commit with
83 * roll-forward system.
85 * HDB entry exists and HDB entry does not exist errors occurring during
86 * replay of unconfirmed records are ignored. This is because the
87 * corresponding HDB update might have completed. But also because a
88 * change to add aliases to a principal can fail because we don't check
89 * for alias conflicts before going ahead with the write operation.
91 * Non-sensical and incomplete log records found during roll-forward are
92 * truncated. A log record is non-sensical if its header and trailer
93 * don't match.
95 * Recovery (by rolling forward) occurs at the next read or write by a
96 * kadm5 API reader (e.g., kadmin), but not by an hdb API reader (e.g.,
97 * the KDC). This means that, e.g., a principal rename could fail in
98 * between the store and the delete, and recovery might not take place
99 * until the next write operation.
101 * The log record payload format for create is:
103 * DER-encoded HDB_entry n bytes
105 * The log record payload format for update is:
107 * mask 4 bytes
108 * DER-encoded HDB_entry n-4 bytes
110 * The log record payload format for delete is:
112 * krb5_store_principal n bytes
114 * The log record payload format for rename is:
116 * krb5_store_principal m bytes (old principal name)
117 * DER-encoded HDB_entry n-m bytes (new record)
119 * The log record payload format for nop varies:
121 * - The zeroth record in new logs is a nop with a 16 byte payload:
123 * offset of end of last confirmed record 8 bytes
124 * timestamp of last confirmed record 4 bytes
125 * version number of last confirmed record 4 bytes
127 * - New non-zeroth nop records:
129 * nop type 4 bytes
131 * - Old nop records:
133 * version number 4 bytes
134 * timestamp 4 bytes
136 * Upon initialization, the log's uber record will have version 1, and
137 * will be followed by a nop record with version 2. The version numbers
138 * of additional records will be monotonically increasing.
140 * Truncation (kadm5_log_truncate()) takes some N > 0 records from the
141 * tail of the log and writes them to the beginning of the log after an
142 * uber record whose version will then be one less than the first of
143 * those records.
145 * On masters the log should never have more than one unconfirmed
146 * record, but slaves append all of a master's "diffs" and then call
147 * kadm5_log_recover() to recover.
151 * HDB and log lock order on the master:
153 * 1) open and lock the HDB
154 * 2) open and lock the log
155 * 3) do something
156 * 4) unlock and close the log
157 * 5) repeat (2)..(4) if desired
158 * 6) unlock and close the HDB
160 * The kadmin -l lock command can be used to hold the HDB open and
161 * locked for multiple operations.
163 * HDB and log lock order on the slave:
165 * 1) open and lock the log
166 * 2) open and lock the HDB
167 * 3) replay entries
168 * 4) unlock and close the HDB
169 * 5) repeat (2)..(4) until signaled
170 * 6) unlock and close the HDB
172 * The slave doesn't want to allow other local writers, after all, thus
173 * the order is reversed. This means that using "kadmin -l" on a slave
174 * will deadlock with ipropd-slave -- don't do that.
177 #define LOG_HEADER_SZ (sizeof(uint32_t) * 4)
178 #define LOG_TRAILER_SZ (sizeof(uint32_t) * 2)
179 #define LOG_WRAPPER_SZ (LOG_HEADER_SZ + LOG_TRAILER_SZ)
180 #define LOG_UBER_LEN (sizeof(uint64_t) + sizeof(uint32_t) * 2)
181 #define LOG_UBER_SZ (LOG_WRAPPER_SZ + LOG_UBER_LEN)
183 #define LOG_NOPEEK 0
184 #define LOG_DOPEEK 1
187 * Read the header of the record starting at the current offset into sp.
189 * Preserves sp's offset on success if `peek', else skips the header.
191 * Preserves sp's offset on failure where possible.
193 static kadm5_ret_t
194 get_header(krb5_storage *sp, int peek, uint32_t *verp, uint32_t *tstampp,
195 enum kadm_ops *opp, uint32_t *lenp)
197 krb5_error_code ret;
198 uint32_t tstamp, op, len;
199 off_t off, new_off;
201 if (tstampp == NULL)
202 tstampp = &tstamp;
203 if (lenp == NULL)
204 lenp = &len;
206 *verp = 0;
207 *tstampp = 0;
209 off = krb5_storage_seek(sp, 0, SEEK_CUR);
210 if (off < 0)
211 return errno;
212 ret = krb5_ret_uint32(sp, verp);
213 if (ret == HEIM_ERR_EOF) {
214 (void) krb5_storage_seek(sp, off, SEEK_SET);
215 return HEIM_ERR_EOF;
217 if (ret)
218 goto log_corrupt;
219 ret = krb5_ret_uint32(sp, tstampp);
220 if (ret)
221 goto log_corrupt;
223 /* Note: sizeof(*opp) might not == sizeof(op) */
224 ret = krb5_ret_uint32(sp, &op);
225 if (ret)
226 goto log_corrupt;
227 if (opp != NULL)
228 *opp = op;
230 ret = krb5_ret_uint32(sp, lenp);
231 if (ret)
232 goto log_corrupt;
234 /* Restore offset if requested */
235 if (peek == LOG_DOPEEK) {
236 new_off = krb5_storage_seek(sp, off, SEEK_SET);
237 if (new_off == -1)
238 return errno;
239 if (new_off != off)
240 return EIO;
243 return 0;
245 log_corrupt:
246 (void) krb5_storage_seek(sp, off, SEEK_SET);
247 return KADM5_LOG_CORRUPT;
251 * Seek to the start of the preceding record's header and returns its
252 * offset. If sp is at offset zero this sets *verp = 0 and returns 0.
254 * Does not verify the header of the previous entry.
256 * On error returns -1, setting errno (possibly to a kadm5_ret_t or
257 * krb5_error_code value) and preserves sp's offset where possible.
259 static off_t
260 seek_prev(krb5_storage *sp, uint32_t *verp, uint32_t *lenp)
262 krb5_error_code ret;
263 uint32_t len, ver;
264 off_t off_len;
265 off_t off, new_off;
267 if (lenp == NULL)
268 lenp = &len;
269 if (verp == NULL)
270 verp = &ver;
272 *verp = 0;
273 *lenp = 0;
275 off = krb5_storage_seek(sp, 0, SEEK_CUR);
276 if (off < 0)
277 return off;
278 if (off == 0)
279 return 0;
281 /* Check that `off' allows for the record's header and trailer */
282 if (off < LOG_WRAPPER_SZ)
283 goto log_corrupt;
285 /* Get the previous entry's length and version from its trailer */
286 new_off = krb5_storage_seek(sp, -8, SEEK_CUR);
287 if (new_off == -1)
288 return -1;
289 if (new_off != off - 8) {
290 errno = EIO;
291 return -1;
293 ret = krb5_ret_uint32(sp, lenp);
294 if (ret)
295 goto log_corrupt;
297 /* Check for overflow/sign extension */
298 off_len = (off_t)*lenp;
299 if (off_len < 0 || *lenp != (uint32_t)off_len)
300 goto log_corrupt;
302 ret = krb5_ret_uint32(sp, verp);
303 if (ret)
304 goto log_corrupt;
306 /* Check that `off' allows for the record */
307 if (off < LOG_WRAPPER_SZ + off_len)
308 goto log_corrupt;
310 /* Seek backwards to the entry's start */
311 new_off = krb5_storage_seek(sp, -(LOG_WRAPPER_SZ + off_len), SEEK_CUR);
312 if (new_off == -1)
313 return -1;
314 if (new_off != off - (LOG_WRAPPER_SZ + off_len)) {
315 errno = EIO;
316 return -1;
318 return new_off;
320 log_corrupt:
321 (void) krb5_storage_seek(sp, off, SEEK_SET);
322 errno = KADM5_LOG_CORRUPT;
323 return -1;
327 * Seek to the start of the next entry's header.
329 * On error returns -1 and preserves sp's offset.
331 static off_t
332 seek_next(krb5_storage *sp)
334 krb5_error_code ret;
335 uint32_t ver, ver2, len, len2;
336 enum kadm_ops op;
337 uint32_t tstamp;
338 off_t off, off_len, new_off;
340 off = krb5_storage_seek(sp, 0, SEEK_CUR);
341 if (off < 0)
342 return off;
344 errno = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
345 if (errno)
346 return -1;
348 /* Check for overflow */
349 off_len = len;
350 if (off_len < 0)
351 goto log_corrupt;
353 new_off = krb5_storage_seek(sp, off_len, SEEK_CUR);
354 if (new_off == -1) {
355 (void) krb5_storage_seek(sp, off, SEEK_SET);
356 return -1;
358 if (new_off != off + LOG_HEADER_SZ + off_len)
359 goto log_corrupt;
360 ret = krb5_ret_uint32(sp, &len2);
361 if (ret || len2 != len)
362 goto log_corrupt;
363 ret = krb5_ret_uint32(sp, &ver2);
364 if (ret || ver2 != ver)
365 goto log_corrupt;
366 new_off = krb5_storage_seek(sp, 0, SEEK_CUR);
367 if (new_off == -1) {
368 (void) krb5_storage_seek(sp, off, SEEK_SET);
369 return -1;
371 if (new_off != off + off_len + LOG_WRAPPER_SZ)
372 goto log_corrupt;
374 return off + off_len + LOG_WRAPPER_SZ;
376 log_corrupt:
377 (void) krb5_storage_seek(sp, off, SEEK_SET);
378 errno = KADM5_LOG_CORRUPT;
379 return -1;
383 * Get the version of the entry ending at the current offset into sp.
384 * If it is the uber record, return its nominal version instead.
386 * Returns HEIM_ERR_EOF if sp is at offset zero.
388 * Preserves sp's offset.
390 static kadm5_ret_t
391 get_version_prev(krb5_storage *sp, uint32_t *verp, uint32_t *tstampp)
393 krb5_error_code ret;
394 uint32_t ver, ver2, len, len2;
395 off_t off, prev_off, new_off;
397 *verp = 0;
398 if (tstampp != NULL)
399 *tstampp = 0;
401 off = krb5_storage_seek(sp, 0, SEEK_CUR);
402 if (off < 0)
403 return errno;
404 if (off == 0)
405 return HEIM_ERR_EOF;
407 /* Read the trailer and seek back */
408 prev_off = seek_prev(sp, &ver, &len);
409 if (prev_off == -1)
410 return errno;
412 /* Uber record? Return nominal version. */
413 if (prev_off == 0 && len == LOG_UBER_LEN && ver == 0) {
414 /* Skip 8 byte offset and 4 byte time */
415 if (krb5_storage_seek(sp, LOG_HEADER_SZ + 12, SEEK_SET)
416 != LOG_HEADER_SZ + 12)
417 return errno;
418 ret = krb5_ret_uint32(sp, verp);
419 if (krb5_storage_seek(sp, 0, SEEK_SET) != 0)
420 return errno;
421 if (ret != 0)
422 return ret;
423 } else {
424 *verp = ver;
427 /* Verify that the trailer matches header */
428 ret = get_header(sp, LOG_NOPEEK, &ver2, tstampp, NULL, &len2);
429 if (ret || ver != ver2 || len != len2)
430 goto log_corrupt;
432 /* Preserve offset */
433 new_off = krb5_storage_seek(sp, off, SEEK_SET);
434 if (new_off == -1)
435 return errno;
436 if (new_off != off) {
437 errno = EIO;
438 return errno;
440 return 0;
442 log_corrupt:
443 (void) krb5_storage_seek(sp, off, SEEK_SET);
444 return KADM5_LOG_CORRUPT;
447 static size_t
448 get_max_log_size(krb5_context context)
450 off_t n;
452 /* Use database-label-specific lookup? No, ETOOHARD. */
453 /* Default to 50MB max log size */
454 n = krb5_config_get_int_default(context, NULL, 52428800,
455 "kdc",
456 "log-max-size",
457 NULL);
458 if (n >= 4 * (LOG_UBER_LEN + LOG_WRAPPER_SZ) && n == (size_t)n)
459 return (size_t)n;
460 return 0;
463 static kadm5_ret_t truncate_if_needed(kadm5_server_context *);
464 static krb5_storage *log_goto_first(kadm5_server_context *, int);
467 * Get the version and timestamp metadata of either the first, or last
468 * confirmed entry in the log.
470 * If `which' is LOG_VERSION_UBER, then this gets the version number of the uber
471 * uber record which must be 0, or else we need to upgrade the log.
473 * If `which' is LOG_VERSION_FIRST, then this gets the metadata for the
474 * logically first entry past the uberblock, or returns HEIM_EOF if
475 * only the uber record is present.
477 * If `which' is LOG_VERSION_LAST, then this gets metadata for the last
478 * confirmed entry's version and timestamp. If only the uber record is present,
479 * then the version will be its "nominal" version, which may differ from its
480 * actual version (0).
482 * The `fd''s offset will be set to the start of the header of the entry
483 * identified by `which'.
485 kadm5_ret_t
486 kadm5_log_get_version_fd(kadm5_server_context *server_context, int fd,
487 int which, uint32_t *ver, uint32_t *tstamp)
489 kadm5_ret_t ret = 0;
490 krb5_storage *sp;
491 enum kadm_ops op = kadm_get;
492 uint32_t len = 0;
493 uint32_t tmp;
495 if (fd == -1)
496 return 0; /* /dev/null */
498 if (tstamp == NULL)
499 tstamp = &tmp;
501 *ver = 0;
502 *tstamp = 0;
504 switch (which) {
505 case LOG_VERSION_LAST:
506 sp = kadm5_log_goto_end(server_context, fd);
507 if (sp == NULL)
508 return errno;
509 ret = get_version_prev(sp, ver, tstamp);
510 krb5_storage_free(sp);
511 break;
512 case LOG_VERSION_FIRST:
513 sp = log_goto_first(server_context, fd);
514 if (sp == NULL)
515 return errno;
516 ret = get_header(sp, LOG_DOPEEK, ver, tstamp, NULL, NULL);
517 krb5_storage_free(sp);
518 break;
519 case LOG_VERSION_UBER:
520 sp = krb5_storage_from_fd(server_context->log_context.log_fd);
521 if (sp == NULL)
522 return errno;
523 if (krb5_storage_seek(sp, 0, SEEK_SET) == 0)
524 ret = get_header(sp, LOG_DOPEEK, ver, tstamp, &op, &len);
525 else
526 ret = errno;
527 if (ret == 0 && (op != kadm_nop || len != LOG_UBER_LEN || *ver != 0))
528 ret = KADM5_LOG_NEEDS_UPGRADE;
529 krb5_storage_free(sp);
530 break;
531 default:
532 return ENOTSUP;
535 return ret;
538 /* Get the version of the last confirmed entry in the log */
539 kadm5_ret_t
540 kadm5_log_get_version(kadm5_server_context *server_context, uint32_t *ver)
542 return kadm5_log_get_version_fd(server_context,
543 server_context->log_context.log_fd,
544 LOG_VERSION_LAST, ver, NULL);
547 /* Sets the version in the context, but NOT in the log */
548 kadm5_ret_t
549 kadm5_log_set_version(kadm5_server_context *context, uint32_t vno)
551 kadm5_log_context *log_context = &context->log_context;
553 log_context->version = vno;
554 return 0;
558 * Open the log and setup server_context->log_context
560 static kadm5_ret_t
561 log_open(kadm5_server_context *server_context, int lock_mode)
563 int fd = -1;
564 int lock_it = 0;
565 int lock_nb = 0;
566 int oflags = O_RDWR;
567 kadm5_ret_t ret;
568 kadm5_log_context *log_context = &server_context->log_context;
570 if (lock_mode & LOCK_NB) {
571 lock_mode &= ~LOCK_NB;
572 lock_nb = LOCK_NB;
575 if (lock_mode == log_context->lock_mode && log_context->log_fd != -1)
576 return 0;
578 if (strcmp(log_context->log_file, "/dev/null") == 0) {
579 /* log_context->log_fd should be -1 here */
580 return 0;
583 if (log_context->log_fd != -1) {
584 /* Lock or change lock */
585 fd = log_context->log_fd;
586 if (lseek(fd, 0, SEEK_SET) == -1)
587 return errno;
588 lock_it = (lock_mode != log_context->lock_mode);
589 } else {
590 /* Open and lock */
591 if (lock_mode != LOCK_UN)
592 oflags |= O_CREAT;
593 fd = open(log_context->log_file, oflags, 0600);
594 if (fd < 0) {
595 ret = errno;
596 krb5_set_error_message(server_context->context, ret,
597 "log_open: open %s", log_context->log_file);
598 return ret;
600 lock_it = (lock_mode != LOCK_UN);
602 if (lock_it && flock(fd, lock_mode | lock_nb) < 0) {
603 ret = errno;
604 krb5_set_error_message(server_context->context, ret,
605 "log_open: flock %s", log_context->log_file);
606 if (fd != log_context->log_fd)
607 (void) close(fd);
608 return ret;
611 log_context->log_fd = fd;
612 log_context->lock_mode = lock_mode;
613 log_context->read_only = (lock_mode != LOCK_EX);
615 return 0;
619 * Open the log and setup server_context->log_context
621 static kadm5_ret_t
622 log_init(kadm5_server_context *server_context, int lock_mode)
624 int fd;
625 struct stat st;
626 uint32_t vno;
627 size_t maxbytes = get_max_log_size(server_context->context);
628 kadm5_ret_t ret;
629 kadm5_log_context *log_context = &server_context->log_context;
631 if (strcmp(log_context->log_file, "/dev/null") == 0) {
632 /* log_context->log_fd should be -1 here */
633 return 0;
636 ret = log_open(server_context, lock_mode);
637 if (ret)
638 return ret;
640 fd = log_context->log_fd;
641 if (!log_context->read_only) {
642 if (fstat(fd, &st) == -1)
643 ret = errno;
644 if (ret == 0 && st.st_size == 0) {
645 /* Write first entry */
646 log_context->version = 0;
647 ret = kadm5_log_nop(server_context, kadm_nop_plain);
648 if (ret == 0)
649 return 0; /* no need to truncate_if_needed(): it's not */
651 if (ret == 0) {
652 ret = kadm5_log_get_version_fd(server_context, fd,
653 LOG_VERSION_UBER, &vno, NULL);
655 /* Upgrade the log if it was an old-style log */
656 if (ret == KADM5_LOG_NEEDS_UPGRADE)
657 ret = kadm5_log_truncate(server_context, 0, maxbytes / 4);
659 if (ret == 0)
660 ret = kadm5_log_recover(server_context, kadm_recover_replay);
663 if (ret == 0) {
664 ret = kadm5_log_get_version_fd(server_context, fd, LOG_VERSION_LAST,
665 &log_context->version, NULL);
666 if (ret == HEIM_ERR_EOF)
667 ret = 0;
670 if (ret == 0)
671 ret = truncate_if_needed(server_context);
673 if (ret != 0)
674 (void) kadm5_log_end(server_context);
675 return ret;
678 /* Open the log with an exclusive lock */
679 kadm5_ret_t
680 kadm5_log_init(kadm5_server_context *server_context)
682 return log_init(server_context, LOCK_EX);
685 /* Open the log with an exclusive non-blocking lock */
686 kadm5_ret_t
687 kadm5_log_init_nb(kadm5_server_context *server_context)
689 return log_init(server_context, LOCK_EX | LOCK_NB);
692 /* Open the log with no locks */
693 kadm5_ret_t
694 kadm5_log_init_nolock(kadm5_server_context *server_context)
696 return log_init(server_context, LOCK_UN);
699 /* Open the log with a shared lock */
700 kadm5_ret_t
701 kadm5_log_init_sharedlock(kadm5_server_context *server_context, int lock_flags)
703 return log_init(server_context, LOCK_SH | lock_flags);
707 * Reinitialize the log and open it
709 kadm5_ret_t
710 kadm5_log_reinit(kadm5_server_context *server_context, uint32_t vno)
712 int ret;
713 kadm5_log_context *log_context = &server_context->log_context;
715 ret = log_open(server_context, LOCK_EX);
716 if (ret)
717 return ret;
718 if (log_context->log_fd != -1) {
719 if (ftruncate(log_context->log_fd, 0) < 0) {
720 ret = errno;
721 return ret;
723 if (lseek(log_context->log_fd, 0, SEEK_SET) < 0) {
724 ret = errno;
725 return ret;
729 /* Write uber entry and truncation nop with version `vno` */
730 log_context->version = vno;
731 ret = kadm5_log_nop(server_context, kadm_nop_plain);
732 return 0;
735 /* Close the server_context->log_context. */
736 kadm5_ret_t
737 kadm5_log_end(kadm5_server_context *server_context)
739 kadm5_log_context *log_context = &server_context->log_context;
740 kadm5_ret_t ret = 0;
741 int fd = log_context->log_fd;
743 if (fd != -1) {
744 if (log_context->lock_mode != LOCK_UN) {
745 if (flock(fd, LOCK_UN) == -1 && errno == EBADF)
746 ret = errno;
748 if (ret != EBADF && close(fd) == -1)
749 ret = errno;
751 log_context->log_fd = -1;
752 log_context->lock_mode = LOCK_UN;
753 return ret;
757 * Write the version, timestamp, and op for a new entry.
759 * Note that the sp should be a krb5_storage_emem(), not a file.
761 * On success the sp's offset will be where the length of the payload
762 * should be written.
764 static kadm5_ret_t
765 kadm5_log_preamble(kadm5_server_context *context,
766 krb5_storage *sp,
767 enum kadm_ops op,
768 uint32_t vno)
770 kadm5_log_context *log_context = &context->log_context;
771 time_t now = time(NULL);
772 kadm5_ret_t ret;
774 ret = krb5_store_uint32(sp, vno);
775 if (ret)
776 return ret;
777 ret = krb5_store_uint32(sp, now);
778 if (ret)
779 return ret;
780 log_context->last_time = now;
782 if (op < kadm_first || op > kadm_last)
783 return ERANGE;
784 return krb5_store_uint32(sp, op);
787 /* Writes the version part of the trailer */
788 static kadm5_ret_t
789 kadm5_log_postamble(kadm5_log_context *context,
790 krb5_storage *sp,
791 uint32_t vno)
793 return krb5_store_uint32(sp, vno);
797 * Signal the ipropd-master about changes to the log.
800 * XXX Get rid of the ifdef by having a sockaddr in log_context in both
801 * cases.
803 * XXX Better yet, just connect to the master's socket that slaves
804 * connect to, and then disconnect. The master should then check the
805 * log on every connection accepted. Then we wouldn't need IPC to
806 * signal the master.
808 void
809 kadm5_log_signal_master(kadm5_server_context *context)
811 kadm5_log_context *log_context = &context->log_context;
812 #ifndef NO_UNIX_SOCKETS
813 sendto(log_context->socket_fd,
814 (void *)&log_context->version,
815 sizeof(log_context->version),
817 (struct sockaddr *)&log_context->socket_name,
818 sizeof(log_context->socket_name));
819 #else
820 sendto(log_context->socket_fd,
821 (void *)&log_context->version,
822 sizeof(log_context->version),
824 log_context->socket_info->ai_addr,
825 log_context->socket_info->ai_addrlen);
826 #endif
830 * Write sp's contents (which must be a fully formed record, complete
831 * with header, payload, and trailer) to the log and fsync the log.
833 * Does not free sp.
836 static kadm5_ret_t
837 kadm5_log_flush(kadm5_server_context *context, krb5_storage *sp)
839 kadm5_log_context *log_context = &context->log_context;
840 kadm5_ret_t ret;
841 krb5_data data;
842 size_t len;
843 krb5_ssize_t bytes;
844 uint32_t new_ver, prev_ver;
845 off_t off, end;
847 if (strcmp(log_context->log_file, "/dev/null") == 0)
848 return 0;
850 if (log_context->read_only)
851 return EROFS;
853 if (krb5_storage_seek(sp, 0, SEEK_SET) == -1)
854 return errno;
856 ret = get_header(sp, LOG_DOPEEK, &new_ver, NULL, NULL, NULL);
857 if (ret)
858 return ret;
860 ret = krb5_storage_to_data(sp, &data);
861 if (ret)
862 return ret;
864 /* Abandon the emem storage reference */
865 sp = krb5_storage_from_fd(log_context->log_fd);
866 if (sp == NULL) {
867 krb5_data_free(&data);
868 return ENOMEM;
871 /* Check that we are at the end of the log and fail if not */
872 off = krb5_storage_seek(sp, 0, SEEK_CUR);
873 if (off == -1) {
874 krb5_data_free(&data);
875 krb5_storage_free(sp);
876 return errno;
878 end = krb5_storage_seek(sp, 0, SEEK_END);
879 if (end == -1) {
880 krb5_data_free(&data);
881 krb5_storage_free(sp);
882 return errno;
884 if (end != off) {
885 krb5_data_free(&data);
886 krb5_storage_free(sp);
887 return KADM5_LOG_CORRUPT;
890 /* Enforce monotonically incremented versioning of records */
891 if (seek_prev(sp, &prev_ver, NULL) == -1 ||
892 krb5_storage_seek(sp, end, SEEK_SET) == -1) {
893 ret = errno;
894 krb5_data_free(&data);
895 krb5_storage_free(sp);
896 return ret;
899 if (prev_ver != 0 && prev_ver != log_context->version)
900 return EINVAL; /* Internal error, really; just a consistency check */
902 if (prev_ver != 0 && new_ver != prev_ver + 1) {
903 krb5_warnx(context->context, "refusing to write a log record "
904 "with non-monotonic version (new: %u, old: %u)",
905 new_ver, prev_ver);
906 return KADM5_LOG_CORRUPT;
909 len = data.length;
910 bytes = krb5_storage_write(sp, data.data, len);
911 krb5_data_free(&data);
912 if (bytes < 0) {
913 krb5_storage_free(sp);
914 return errno;
916 if (bytes != (krb5_ssize_t)len) {
917 krb5_storage_free(sp);
918 return EIO;
921 ret = krb5_storage_fsync(sp);
922 krb5_storage_free(sp);
923 if (ret)
924 return ret;
926 /* Retain the nominal database version when flushing the uber record */
927 if (new_ver != 0)
928 log_context->version = new_ver;
929 return 0;
933 * Add a `create' operation to the log and perform the create against the HDB.
935 kadm5_ret_t
936 kadm5_log_create(kadm5_server_context *context, hdb_entry *entry)
938 krb5_storage *sp;
939 kadm5_ret_t ret;
940 krb5_data value;
941 hdb_entry_ex ent;
942 kadm5_log_context *log_context = &context->log_context;
944 memset(&ent, 0, sizeof(ent));
945 ent.ctx = 0;
946 ent.free_entry = 0;
947 ent.entry = *entry;
950 * If we're not logging then we can't recover-to-perform, so just
951 * perform.
953 if (strcmp(log_context->log_file, "/dev/null") == 0)
954 return context->db->hdb_store(context->context, context->db, 0, &ent);
957 * Test for any conflicting entries before writing the log. If we commit
958 * to the log we'll end-up rolling forward on recovery, but that would be
959 * wrong if the initial create is rejected.
961 ret = context->db->hdb_store(context->context, context->db,
962 HDB_F_PRECHECK, &ent);
963 if (ret == 0)
964 ret = hdb_entry2value(context->context, entry, &value);
965 if (ret)
966 return ret;
967 sp = krb5_storage_emem();
968 if (sp == NULL)
969 ret = ENOMEM;
970 if (ret == 0)
971 ret = kadm5_log_preamble(context, sp, kadm_create,
972 log_context->version + 1);
973 if (ret == 0)
974 ret = krb5_store_uint32(sp, value.length);
975 if (ret == 0) {
976 if (krb5_storage_write(sp, value.data, value.length) !=
977 (krb5_ssize_t)value.length)
978 ret = errno;
980 if (ret == 0)
981 ret = krb5_store_uint32(sp, value.length);
982 if (ret == 0)
983 ret = kadm5_log_postamble(log_context, sp,
984 log_context->version + 1);
985 if (ret == 0)
986 ret = kadm5_log_flush(context, sp);
987 krb5_storage_free(sp);
988 krb5_data_free(&value);
989 if (ret == 0)
990 ret = kadm5_log_recover(context, kadm_recover_commit);
991 return ret;
995 * Read the data of a create log record from `sp' and change the
996 * database.
998 static kadm5_ret_t
999 kadm5_log_replay_create(kadm5_server_context *context,
1000 uint32_t ver,
1001 uint32_t len,
1002 krb5_storage *sp)
1004 krb5_error_code ret;
1005 krb5_data data;
1006 hdb_entry_ex ent;
1008 memset(&ent, 0, sizeof(ent));
1010 ret = krb5_data_alloc(&data, len);
1011 if (ret) {
1012 krb5_set_error_message(context->context, ret, "out of memory");
1013 return ret;
1015 krb5_storage_read(sp, data.data, len);
1016 ret = hdb_value2entry(context->context, &data, &ent.entry);
1017 krb5_data_free(&data);
1018 if (ret) {
1019 krb5_set_error_message(context->context, ret,
1020 "Unmarshaling hdb entry in log failed, "
1021 "version: %ld", (long)ver);
1022 return ret;
1024 ret = context->db->hdb_store(context->context, context->db, 0, &ent);
1025 hdb_free_entry(context->context, &ent);
1026 return ret;
1030 * Add a `delete' operation to the log.
1032 kadm5_ret_t
1033 kadm5_log_delete(kadm5_server_context *context,
1034 krb5_principal princ)
1036 kadm5_ret_t ret;
1037 kadm5_log_context *log_context = &context->log_context;
1038 krb5_storage *sp;
1039 uint32_t len = 0; /* So dumb compilers don't warn */
1040 off_t end_off = 0; /* Ditto; this allows de-indentation by two levels */
1041 off_t off;
1043 if (strcmp(log_context->log_file, "/dev/null") == 0)
1044 return context->db->hdb_remove(context->context, context->db, 0,
1045 princ);
1046 ret = context->db->hdb_remove(context->context, context->db,
1047 HDB_F_PRECHECK, princ);
1048 if (ret)
1049 return ret;
1050 sp = krb5_storage_emem();
1051 if (sp == NULL)
1052 ret = ENOMEM;
1053 if (ret == 0)
1054 ret = kadm5_log_preamble(context, sp, kadm_delete,
1055 log_context->version + 1);
1056 if (ret) {
1057 krb5_storage_free(sp);
1058 return ret;
1062 * Write a 0 length which we overwrite once we know the length of
1063 * the principal name payload.
1065 off = krb5_storage_seek(sp, 0, SEEK_CUR);
1066 if (off == -1)
1067 ret = errno;
1068 if (ret == 0)
1069 ret = krb5_store_uint32(sp, 0);
1070 if (ret == 0)
1071 ret = krb5_store_principal(sp, princ);
1072 if (ret == 0) {
1073 end_off = krb5_storage_seek(sp, 0, SEEK_CUR);
1074 if (end_off == -1)
1075 ret = errno;
1076 else if (end_off < off)
1077 ret = KADM5_LOG_CORRUPT;
1079 if (ret == 0) {
1080 /* We wrote sizeof(uint32_t) + payload length bytes */
1081 len = (uint32_t)(end_off - off);
1082 if (end_off - off != len || len < sizeof(len))
1083 ret = KADM5_LOG_CORRUPT;
1084 else
1085 len -= sizeof(len);
1087 if (ret == 0 && krb5_storage_seek(sp, off, SEEK_SET) == -1)
1088 ret = errno;
1089 if (ret == 0)
1090 ret = krb5_store_uint32(sp, len);
1091 if (ret == 0 && krb5_storage_seek(sp, end_off, SEEK_SET) == -1)
1092 ret = errno;
1093 if (ret == 0)
1094 ret = krb5_store_uint32(sp, len);
1095 if (ret == 0)
1096 ret = kadm5_log_postamble(log_context, sp,
1097 log_context->version + 1);
1098 if (ret == 0)
1099 ret = kadm5_log_flush(context, sp);
1100 if (ret == 0)
1101 ret = kadm5_log_recover(context, kadm_recover_commit);
1102 krb5_storage_free(sp);
1103 return ret;
1107 * Read a `delete' log operation from `sp' and apply it.
1109 static kadm5_ret_t
1110 kadm5_log_replay_delete(kadm5_server_context *context,
1111 uint32_t ver, uint32_t len, krb5_storage *sp)
1113 krb5_error_code ret;
1114 krb5_principal principal;
1116 ret = krb5_ret_principal(sp, &principal);
1117 if (ret) {
1118 krb5_set_error_message(context->context, ret, "Failed to read deleted "
1119 "principal from log version: %ld", (long)ver);
1120 return ret;
1123 ret = context->db->hdb_remove(context->context, context->db, 0, principal);
1124 krb5_free_principal(context->context, principal);
1125 return ret;
1128 static kadm5_ret_t kadm5_log_replay_rename(kadm5_server_context *,
1129 uint32_t, uint32_t,
1130 krb5_storage *);
1133 * Add a `rename' operation to the log.
1135 kadm5_ret_t
1136 kadm5_log_rename(kadm5_server_context *context,
1137 krb5_principal source,
1138 hdb_entry *entry)
1140 krb5_storage *sp;
1141 kadm5_ret_t ret;
1142 uint32_t len = 0; /* So dumb compilers don't warn */
1143 off_t end_off = 0; /* Ditto; this allows de-indentation by two levels */
1144 off_t off;
1145 krb5_data value;
1146 hdb_entry_ex ent;
1147 kadm5_log_context *log_context = &context->log_context;
1149 memset(&ent, 0, sizeof(ent));
1150 ent.ctx = 0;
1151 ent.free_entry = 0;
1152 ent.entry = *entry;
1154 if (strcmp(log_context->log_file, "/dev/null") == 0) {
1155 ret = context->db->hdb_store(context->context, context->db, 0, &ent);
1156 if (ret == 0)
1157 return context->db->hdb_remove(context->context, context->db, 0,
1158 source);
1159 return ret;
1163 * Pre-check that the transaction will succeed.
1165 * Note that rename doesn't work to swap a principal's canonical
1166 * name with one of its aliases. To make that work would require
1167 * adding an hdb_rename() method for renaming principals (there's an
1168 * hdb_rename() method already, but for renaming the HDB), which is
1169 * ETOOMUCHWORK for the time being.
1171 ret = context->db->hdb_store(context->context, context->db,
1172 HDB_F_PRECHECK, &ent);
1173 if (ret == 0)
1174 ret = context->db->hdb_remove(context->context, context->db,
1175 HDB_F_PRECHECK, source);
1176 if (ret)
1177 return ret;
1179 sp = krb5_storage_emem();
1180 krb5_data_zero(&value);
1181 if (sp == NULL)
1182 ret = ENOMEM;
1183 if (ret == 0)
1184 ret = kadm5_log_preamble(context, sp, kadm_rename,
1185 log_context->version + 1);
1186 if (ret == 0)
1187 ret = hdb_entry2value(context->context, entry, &value);
1188 if (ret) {
1189 krb5_data_free(&value);
1190 krb5_storage_free(sp);
1191 return ret;
1195 * Write a zero length which we'll overwrite once we know the length of the
1196 * payload.
1198 off = krb5_storage_seek(sp, 0, SEEK_CUR);
1199 if (off == -1)
1200 ret = errno;
1201 if (ret == 0)
1202 ret = krb5_store_uint32(sp, 0);
1203 if (ret == 0)
1204 ret = krb5_store_principal(sp, source);
1205 if (ret == 0) {
1206 errno = 0;
1207 if (krb5_storage_write(sp, value.data, value.length) !=
1208 (krb5_ssize_t)value.length)
1209 ret = errno ? errno : EIO;
1211 if (ret == 0) {
1212 end_off = krb5_storage_seek(sp, 0, SEEK_CUR);
1213 if (end_off == -1)
1214 ret = errno;
1215 else if (end_off < off)
1216 ret = KADM5_LOG_CORRUPT;
1218 if (ret == 0) {
1219 /* We wrote sizeof(uint32_t) + payload length bytes */
1220 len = (uint32_t)(end_off - off);
1221 if (end_off - off != len || len < sizeof(len))
1222 ret = KADM5_LOG_CORRUPT;
1223 else
1224 len -= sizeof(len);
1225 if (ret == 0 && krb5_storage_seek(sp, off, SEEK_SET) == -1)
1226 ret = errno;
1227 if (ret == 0)
1228 ret = krb5_store_uint32(sp, len);
1229 if (ret == 0 && krb5_storage_seek(sp, end_off, SEEK_SET) == -1)
1230 ret = errno;
1231 if (ret == 0)
1232 ret = krb5_store_uint32(sp, len);
1233 if (ret == 0)
1234 ret = kadm5_log_postamble(log_context, sp,
1235 log_context->version + 1);
1236 if (ret == 0)
1237 ret = kadm5_log_flush(context, sp);
1238 if (ret == 0)
1239 ret = kadm5_log_recover(context, kadm_recover_commit);
1241 krb5_data_free(&value);
1242 krb5_storage_free(sp);
1243 return ret;
1247 * Read a `rename' log operation from `sp' and apply it.
1250 static kadm5_ret_t
1251 kadm5_log_replay_rename(kadm5_server_context *context,
1252 uint32_t ver,
1253 uint32_t len,
1254 krb5_storage *sp)
1256 krb5_error_code ret;
1257 krb5_principal source;
1258 hdb_entry_ex target_ent;
1259 krb5_data value;
1260 off_t off;
1261 size_t princ_len, data_len;
1263 memset(&target_ent, 0, sizeof(target_ent));
1265 off = krb5_storage_seek(sp, 0, SEEK_CUR);
1266 ret = krb5_ret_principal(sp, &source);
1267 if (ret) {
1268 krb5_set_error_message(context->context, ret, "Failed to read renamed "
1269 "principal in log, version: %ld", (long)ver);
1270 return ret;
1272 princ_len = krb5_storage_seek(sp, 0, SEEK_CUR) - off;
1273 data_len = len - princ_len;
1274 ret = krb5_data_alloc(&value, data_len);
1275 if (ret) {
1276 krb5_free_principal (context->context, source);
1277 return ret;
1279 krb5_storage_read(sp, value.data, data_len);
1280 ret = hdb_value2entry(context->context, &value, &target_ent.entry);
1281 krb5_data_free(&value);
1282 if (ret) {
1283 krb5_free_principal(context->context, source);
1284 return ret;
1286 ret = context->db->hdb_store(context->context, context->db,
1287 0, &target_ent);
1288 hdb_free_entry(context->context, &target_ent);
1289 if (ret) {
1290 krb5_free_principal(context->context, source);
1291 return ret;
1293 ret = context->db->hdb_remove(context->context, context->db, 0, source);
1294 krb5_free_principal(context->context, source);
1296 return ret;
1300 * Add a `modify' operation to the log.
1302 kadm5_ret_t
1303 kadm5_log_modify(kadm5_server_context *context,
1304 hdb_entry *entry,
1305 uint32_t mask)
1307 krb5_storage *sp;
1308 kadm5_ret_t ret;
1309 krb5_data value;
1310 uint32_t len;
1311 hdb_entry_ex ent;
1312 kadm5_log_context *log_context = &context->log_context;
1314 memset(&ent, 0, sizeof(ent));
1315 ent.ctx = 0;
1316 ent.free_entry = 0;
1317 ent.entry = *entry;
1319 if (strcmp(log_context->log_file, "/dev/null") == 0)
1320 return context->db->hdb_store(context->context, context->db,
1321 HDB_F_REPLACE, &ent);
1323 ret = context->db->hdb_store(context->context, context->db,
1324 HDB_F_PRECHECK | HDB_F_REPLACE, &ent);
1325 if (ret)
1326 return ret;
1328 sp = krb5_storage_emem();
1329 krb5_data_zero(&value);
1330 if (sp == NULL)
1331 ret = ENOMEM;
1332 if (ret == 0)
1333 ret = hdb_entry2value(context->context, entry, &value);
1334 if (ret) {
1335 krb5_data_free(&value);
1336 krb5_storage_free(sp);
1337 return ret;
1340 len = value.length + sizeof(len);
1341 if (value.length > len || len > INT32_MAX)
1342 ret = E2BIG;
1343 if (ret == 0)
1344 ret = kadm5_log_preamble(context, sp, kadm_modify,
1345 log_context->version + 1);
1346 if (ret == 0)
1347 ret = krb5_store_uint32(sp, len);
1348 if (ret == 0)
1349 ret = krb5_store_uint32(sp, mask);
1350 if (ret == 0) {
1351 if (krb5_storage_write(sp, value.data, value.length) !=
1352 (krb5_ssize_t)value.length)
1353 ret = errno;
1355 if (ret == 0)
1356 ret = krb5_store_uint32(sp, len);
1357 if (ret == 0)
1358 ret = kadm5_log_postamble(log_context, sp,
1359 log_context->version + 1);
1360 if (ret == 0)
1361 ret = kadm5_log_flush(context, sp);
1362 if (ret == 0)
1363 ret = kadm5_log_recover(context, kadm_recover_commit);
1364 krb5_data_free(&value);
1365 krb5_storage_free(sp);
1366 return ret;
1370 * Read a `modify' log operation from `sp' and apply it.
1372 static kadm5_ret_t
1373 kadm5_log_replay_modify(kadm5_server_context *context,
1374 uint32_t ver,
1375 uint32_t len,
1376 krb5_storage *sp)
1378 krb5_error_code ret;
1379 uint32_t mask;
1380 krb5_data value;
1381 hdb_entry_ex ent, log_ent;
1383 memset(&log_ent, 0, sizeof(log_ent));
1385 ret = krb5_ret_uint32(sp, &mask);
1386 if (ret)
1387 return ret;
1388 len -= 4;
1389 ret = krb5_data_alloc (&value, len);
1390 if (ret) {
1391 krb5_set_error_message(context->context, ret, "out of memory");
1392 return ret;
1394 errno = 0;
1395 if (krb5_storage_read (sp, value.data, len) != (krb5_ssize_t)len) {
1396 ret = errno ? errno : EIO;
1397 return ret;
1399 ret = hdb_value2entry (context->context, &value, &log_ent.entry);
1400 krb5_data_free(&value);
1401 if (ret)
1402 return ret;
1404 memset(&ent, 0, sizeof(ent));
1405 ret = context->db->hdb_fetch_kvno(context->context, context->db,
1406 log_ent.entry.principal,
1407 HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
1408 HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
1409 if (ret)
1410 goto out;
1411 if (mask & KADM5_PRINC_EXPIRE_TIME) {
1412 if (log_ent.entry.valid_end == NULL) {
1413 ent.entry.valid_end = NULL;
1414 } else {
1415 if (ent.entry.valid_end == NULL) {
1416 ent.entry.valid_end = malloc(sizeof(*ent.entry.valid_end));
1417 if (ent.entry.valid_end == NULL) {
1418 ret = ENOMEM;
1419 krb5_set_error_message(context->context, ret, "out of memory");
1420 goto out;
1423 *ent.entry.valid_end = *log_ent.entry.valid_end;
1426 if (mask & KADM5_PW_EXPIRATION) {
1427 if (log_ent.entry.pw_end == NULL) {
1428 ent.entry.pw_end = NULL;
1429 } else {
1430 if (ent.entry.pw_end == NULL) {
1431 ent.entry.pw_end = malloc(sizeof(*ent.entry.pw_end));
1432 if (ent.entry.pw_end == NULL) {
1433 ret = ENOMEM;
1434 krb5_set_error_message(context->context, ret, "out of memory");
1435 goto out;
1438 *ent.entry.pw_end = *log_ent.entry.pw_end;
1441 if (mask & KADM5_LAST_PWD_CHANGE) {
1442 krb5_warnx (context->context,
1443 "Unimplemented mask KADM5_LAST_PWD_CHANGE");
1445 if (mask & KADM5_ATTRIBUTES) {
1446 ent.entry.flags = log_ent.entry.flags;
1448 if (mask & KADM5_MAX_LIFE) {
1449 if (log_ent.entry.max_life == NULL) {
1450 ent.entry.max_life = NULL;
1451 } else {
1452 if (ent.entry.max_life == NULL) {
1453 ent.entry.max_life = malloc (sizeof(*ent.entry.max_life));
1454 if (ent.entry.max_life == NULL) {
1455 ret = ENOMEM;
1456 krb5_set_error_message(context->context, ret, "out of memory");
1457 goto out;
1460 *ent.entry.max_life = *log_ent.entry.max_life;
1463 if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) {
1464 if (ent.entry.modified_by == NULL) {
1465 ent.entry.modified_by = malloc(sizeof(*ent.entry.modified_by));
1466 if (ent.entry.modified_by == NULL) {
1467 ret = ENOMEM;
1468 krb5_set_error_message(context->context, ret, "out of memory");
1469 goto out;
1471 } else
1472 free_Event(ent.entry.modified_by);
1473 ret = copy_Event(log_ent.entry.modified_by, ent.entry.modified_by);
1474 if (ret) {
1475 krb5_set_error_message(context->context, ret, "out of memory");
1476 goto out;
1479 if (mask & KADM5_KVNO) {
1480 ent.entry.kvno = log_ent.entry.kvno;
1482 if (mask & KADM5_MKVNO) {
1483 krb5_warnx(context->context, "Unimplemented mask KADM5_KVNO");
1485 if (mask & KADM5_AUX_ATTRIBUTES) {
1486 krb5_warnx(context->context,
1487 "Unimplemented mask KADM5_AUX_ATTRIBUTES");
1489 if (mask & KADM5_POLICY_CLR) {
1490 krb5_warnx(context->context, "Unimplemented mask KADM5_POLICY_CLR");
1492 if (mask & KADM5_MAX_RLIFE) {
1493 if (log_ent.entry.max_renew == NULL) {
1494 ent.entry.max_renew = NULL;
1495 } else {
1496 if (ent.entry.max_renew == NULL) {
1497 ent.entry.max_renew = malloc (sizeof(*ent.entry.max_renew));
1498 if (ent.entry.max_renew == NULL) {
1499 ret = ENOMEM;
1500 krb5_set_error_message(context->context, ret, "out of memory");
1501 goto out;
1504 *ent.entry.max_renew = *log_ent.entry.max_renew;
1507 if (mask & KADM5_LAST_SUCCESS) {
1508 krb5_warnx(context->context, "Unimplemented mask KADM5_LAST_SUCCESS");
1510 if (mask & KADM5_LAST_FAILED) {
1511 krb5_warnx(context->context, "Unimplemented mask KADM5_LAST_FAILED");
1513 if (mask & KADM5_FAIL_AUTH_COUNT) {
1514 krb5_warnx(context->context,
1515 "Unimplemented mask KADM5_FAIL_AUTH_COUNT");
1517 if (mask & KADM5_KEY_DATA) {
1518 size_t num;
1519 size_t i;
1522 * We don't need to do anything about key history here because
1523 * the log entry contains a complete entry, including hdb
1524 * extensions. We do need to make sure that KADM5_TL_DATA is in
1525 * the mask though, since that's what it takes to update the
1526 * extensions (see below).
1528 mask |= KADM5_TL_DATA;
1530 for (i = 0; i < ent.entry.keys.len; ++i)
1531 free_Key(&ent.entry.keys.val[i]);
1532 free (ent.entry.keys.val);
1534 num = log_ent.entry.keys.len;
1536 ent.entry.keys.len = num;
1537 ent.entry.keys.val = malloc(len * sizeof(*ent.entry.keys.val));
1538 if (ent.entry.keys.val == NULL) {
1539 krb5_set_error_message(context->context, ENOMEM, "out of memory");
1540 return ENOMEM;
1542 for (i = 0; i < ent.entry.keys.len; ++i) {
1543 ret = copy_Key(&log_ent.entry.keys.val[i],
1544 &ent.entry.keys.val[i]);
1545 if (ret) {
1546 krb5_set_error_message(context->context, ret, "out of memory");
1547 goto out;
1551 if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) {
1552 HDB_extensions *es = ent.entry.extensions;
1554 ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions));
1555 if (ent.entry.extensions == NULL)
1556 goto out;
1558 ret = copy_HDB_extensions(log_ent.entry.extensions,
1559 ent.entry.extensions);
1560 if (ret) {
1561 krb5_set_error_message(context->context, ret, "out of memory");
1562 free(ent.entry.extensions);
1563 ent.entry.extensions = es;
1564 goto out;
1566 if (es) {
1567 free_HDB_extensions(es);
1568 free(es);
1571 ret = context->db->hdb_store(context->context, context->db,
1572 HDB_F_REPLACE, &ent);
1573 out:
1574 hdb_free_entry(context->context, &ent);
1575 hdb_free_entry(context->context, &log_ent);
1576 return ret;
1580 * Update the first entry (which should be a `nop'), the "uber-entry".
1582 static kadm5_ret_t
1583 log_update_uber(kadm5_server_context *context, off_t off)
1585 kadm5_log_context *log_context = &context->log_context;
1586 kadm5_ret_t ret = 0;
1587 krb5_storage *sp, *mem_sp;
1588 krb5_data data;
1589 uint32_t op, len;
1590 ssize_t bytes;
1592 if (strcmp(log_context->log_file, "/dev/null") == 0)
1593 return 0;
1595 if (log_context->read_only)
1596 return EROFS;
1598 krb5_data_zero(&data);
1600 mem_sp = krb5_storage_emem();
1601 if (mem_sp == NULL)
1602 return ENOMEM;
1604 sp = krb5_storage_from_fd(log_context->log_fd);
1605 if (sp == NULL) {
1606 krb5_storage_free(mem_sp);
1607 return ENOMEM;
1610 /* Skip first entry's version and timestamp */
1611 if (krb5_storage_seek(sp, 8, SEEK_SET) == -1) {
1612 ret = errno;
1613 goto out;
1616 /* If the first entry is not a nop, there's nothing we can do here */
1617 ret = krb5_ret_uint32(sp, &op);
1618 if (ret || op != kadm_nop)
1619 goto out;
1621 /* If the first entry is not a 16-byte nop, ditto */
1622 ret = krb5_ret_uint32(sp, &len);
1623 if (ret || len != LOG_UBER_LEN)
1624 goto out;
1627 * Try to make the writes here as close to atomic as possible: a
1628 * single write() call.
1630 ret = krb5_store_uint64(mem_sp, off);
1631 if (ret)
1632 goto out;
1633 ret = krb5_store_uint32(mem_sp, log_context->last_time);
1634 if (ret)
1635 goto out;
1636 ret = krb5_store_uint32(mem_sp, log_context->version);
1637 if (ret)
1638 goto out;
1640 krb5_storage_to_data(mem_sp, &data);
1641 bytes = krb5_storage_write(sp, data.data, data.length);
1642 if (bytes < 0)
1643 ret = errno;
1644 else if (bytes != data.length)
1645 ret = EIO;
1648 * We don't fsync() this write because we can recover if the write
1649 * doesn't complete, though for now we don't have code for properly
1650 * dealing with the offset not getting written completely.
1652 * We should probably have two copies of the offset so we can use
1653 * one copy to verify the other, and when they don't match we could
1654 * traverse the whole log forwards, replaying just the last entry.
1657 out:
1658 if (ret == 0)
1659 kadm5_log_signal_master(context);
1660 krb5_data_free(&data);
1661 krb5_storage_free(sp);
1662 krb5_storage_free(mem_sp);
1663 if (lseek(log_context->log_fd, off, SEEK_SET) == -1)
1664 ret = ret ? ret : errno;
1666 return ret;
1670 * Add a `nop' operation to the log. Does not close the log.
1672 kadm5_ret_t
1673 kadm5_log_nop(kadm5_server_context *context, enum kadm_nop_type nop_type)
1675 krb5_storage *sp;
1676 kadm5_ret_t ret;
1677 kadm5_log_context *log_context = &context->log_context;
1678 off_t off;
1679 uint32_t vno = log_context->version;
1681 if (strcmp(log_context->log_file, "/dev/null") == 0)
1682 return 0;
1684 off = lseek(log_context->log_fd, 0, SEEK_CUR);
1685 if (off == -1)
1686 return errno;
1688 sp = krb5_storage_emem();
1689 ret = kadm5_log_preamble(context, sp, kadm_nop, off == 0 ? 0 : vno + 1);
1690 if (ret)
1691 goto out;
1693 if (off == 0) {
1695 * First entry (uber-entry) gets room for offset of next new
1696 * entry and time and version of last entry.
1698 ret = krb5_store_uint32(sp, LOG_UBER_LEN);
1699 /* These get overwritten with the same values below */
1700 if (ret == 0)
1701 ret = krb5_store_uint64(sp, LOG_UBER_SZ);
1702 if (ret == 0)
1703 ret = krb5_store_uint32(sp, log_context->last_time);
1704 if (ret == 0)
1705 ret = krb5_store_uint32(sp, vno);
1706 if (ret == 0)
1707 ret = krb5_store_uint32(sp, LOG_UBER_LEN);
1708 } else if (nop_type == kadm_nop_plain) {
1709 ret = krb5_store_uint32(sp, 0);
1710 if (ret == 0)
1711 ret = krb5_store_uint32(sp, 0);
1712 } else {
1713 ret = krb5_store_uint32(sp, sizeof(uint32_t));
1714 if (ret == 0)
1715 ret = krb5_store_uint32(sp, nop_type);
1716 if (ret == 0)
1717 ret = krb5_store_uint32(sp, sizeof(uint32_t));
1720 if (ret == 0)
1721 ret = kadm5_log_postamble(log_context, sp, off == 0 ? 0 : vno + 1);
1722 if (ret == 0)
1723 ret = kadm5_log_flush(context, sp);
1725 if (ret == 0 && off == 0 && nop_type != kadm_nop_plain)
1726 ret = kadm5_log_nop(context, nop_type);
1728 if (ret == 0 && off != 0)
1729 ret = kadm5_log_recover(context, kadm_recover_commit);
1731 out:
1732 krb5_storage_free(sp);
1733 return ret;
1737 * Read a `nop' log operation from `sp' and "apply" it (there's nothing
1738 * to do).
1740 * FIXME Actually, if the nop payload is 4 bytes and contains an enum
1741 * kadm_nop_type value of kadm_nop_trunc then we should truncate the
1742 * log, and if it contains a kadm_nop_close then we should rename a new
1743 * log into place. However, this is not implemented yet.
1745 static kadm5_ret_t
1746 kadm5_log_replay_nop(kadm5_server_context *context,
1747 uint32_t ver,
1748 uint32_t len,
1749 krb5_storage *sp)
1751 return 0;
1754 struct replay_cb_data {
1755 size_t count;
1756 uint32_t ver;
1757 enum kadm_recover_mode mode;
1762 * Recover or perform the initial commit of an unconfirmed log entry
1764 static kadm5_ret_t
1765 recover_replay(kadm5_server_context *context,
1766 uint32_t ver, time_t timestamp, enum kadm_ops op,
1767 uint32_t len, krb5_storage *sp, void *ctx)
1769 struct replay_cb_data *data = ctx;
1770 kadm5_ret_t ret;
1771 off_t off;
1773 /* On initial commit there must be just one pending unconfirmed entry */
1774 if (data->count > 0 && data->mode == kadm_recover_commit)
1775 return KADM5_LOG_CORRUPT;
1777 /* We're at the start of the payload; compute end of entry offset */
1778 off = krb5_storage_seek(sp, 0, SEEK_CUR) + len + LOG_TRAILER_SZ;
1780 /* We cannot perform log recovery on LDAP and such backends */
1781 if (data->mode == kadm_recover_replay &&
1782 (context->db->hdb_capability_flags & HDB_CAP_F_SHARED_DIRECTORY))
1783 ret = 0;
1784 else
1785 ret = kadm5_log_replay(context, op, ver, len, sp);
1786 switch (ret) {
1787 case HDB_ERR_NOENTRY:
1788 case HDB_ERR_EXISTS:
1789 if (data->mode != kadm_recover_replay)
1790 return ret;
1791 case 0:
1792 break;
1793 case KADM5_LOG_CORRUPT:
1794 return -1;
1795 default:
1796 krb5_warn(context->context, ret, "unexpected error while replaying");
1797 return -1;
1799 data->count++;
1800 data->ver = ver;
1803 * With replay we may be making multiple HDB changes. We must sync the
1804 * confirmation of each one before moving on to the next. Otherwise, we
1805 * might attempt to replay multiple already applied updates, and this may
1806 * introduce unintended intermediate states or fail to yield the same final
1807 * result.
1809 kadm5_log_set_version(context, ver);
1810 ret = log_update_uber(context, off);
1811 if (ret == 0 && data->mode != kadm_recover_commit)
1812 ret = krb5_storage_fsync(sp);
1813 return ret;
1817 kadm5_ret_t
1818 kadm5_log_recover(kadm5_server_context *context, enum kadm_recover_mode mode)
1820 kadm5_ret_t ret;
1821 krb5_storage *sp;
1822 struct replay_cb_data replay_data;
1824 replay_data.count = 0;
1825 replay_data.ver = 0;
1826 replay_data.mode = mode;
1828 sp = kadm5_log_goto_end(context, context->log_context.log_fd);
1829 if (sp == NULL)
1830 return errno ? errno : EIO;
1832 ret = kadm5_log_foreach(context, kadm_forward | kadm_unconfirmed,
1833 NULL, recover_replay, &replay_data);
1834 if (ret == 0 && mode == kadm_recover_commit && replay_data.count != 1)
1835 ret = KADM5_LOG_CORRUPT;
1836 krb5_storage_free(sp);
1837 return ret;
1841 * Call `func' for each log record in the log in `context'.
1843 * `func' is optional.
1845 * If `func' returns -1 then log traversal terminates and this returns 0.
1846 * Otherwise `func''s return is returned if there are no other errors.
1848 kadm5_ret_t
1849 kadm5_log_foreach(kadm5_server_context *context,
1850 enum kadm_iter_opts iter_opts,
1851 off_t *off_lastp,
1852 kadm5_ret_t (*func)(kadm5_server_context *server_context,
1853 uint32_t ver, time_t timestamp,
1854 enum kadm_ops op, uint32_t len,
1855 krb5_storage *sp, void *ctx),
1856 void *ctx)
1858 kadm5_ret_t ret = 0;
1859 int fd = context->log_context.log_fd;
1860 krb5_storage *sp;
1861 off_t off_last;
1862 off_t this_entry = 0;
1863 off_t log_end = 0;
1865 if (strcmp(context->log_context.log_file, "/dev/null") == 0)
1866 return 0;
1868 if (off_lastp == NULL)
1869 off_lastp = &off_last;
1870 *off_lastp = -1;
1872 if (((iter_opts & kadm_forward) && (iter_opts & kadm_backward)) ||
1873 (!(iter_opts & kadm_confirmed) && !(iter_opts & kadm_unconfirmed)))
1874 return EINVAL;
1876 if ((iter_opts & kadm_forward) && (iter_opts & kadm_confirmed) &&
1877 (iter_opts & kadm_unconfirmed)) {
1879 * We want to traverse all log entries, confirmed or not, from
1880 * the start, then there's no need to kadm5_log_goto_end()
1881 * -- no reason to try to find the end.
1883 sp = krb5_storage_from_fd(fd);
1884 if (sp == NULL)
1885 return errno;
1887 log_end = krb5_storage_seek(sp, 0, SEEK_END);
1888 if (log_end == -1 ||
1889 krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
1890 ret = errno;
1891 krb5_storage_free(sp);
1892 return ret;
1894 } else {
1895 /* Get the end of the log based on the uber entry */
1896 sp = kadm5_log_goto_end(context, fd);
1897 if (sp == NULL)
1898 return errno;
1899 log_end = krb5_storage_seek(sp, 0, SEEK_CUR);
1902 *off_lastp = log_end;
1904 if ((iter_opts & kadm_forward) && (iter_opts & kadm_confirmed)) {
1905 /* Start at the beginning */
1906 if (krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
1907 ret = errno;
1908 krb5_storage_free(sp);
1909 return ret;
1911 } else if ((iter_opts & kadm_backward) && (iter_opts & kadm_unconfirmed)) {
1913 * We're at the confirmed end but need to be at the unconfirmed
1914 * end. Skip forward to the real end, re-entering to do it.
1916 ret = kadm5_log_foreach(context, kadm_forward | kadm_unconfirmed,
1917 &log_end, NULL, NULL);
1918 if (ret)
1919 return ret;
1920 if (krb5_storage_seek(sp, log_end, SEEK_SET) == -1) {
1921 ret = errno;
1922 krb5_storage_free(sp);
1923 return ret;
1927 for (;;) {
1928 uint32_t ver, ver2, len, len2;
1929 uint32_t tstamp;
1930 time_t timestamp;
1931 enum kadm_ops op;
1933 if ((iter_opts & kadm_backward)) {
1934 off_t o;
1936 o = krb5_storage_seek(sp, 0, SEEK_CUR);
1937 if (o == 0 ||
1938 ((iter_opts & kadm_unconfirmed) && o <= *off_lastp))
1939 break;
1940 ret = kadm5_log_previous(context->context, sp, &ver,
1941 &timestamp, &op, &len);
1942 if (ret)
1943 break;
1945 /* Offset is now at payload of current entry */
1947 o = krb5_storage_seek(sp, 0, SEEK_CUR);
1948 if (o == -1) {
1949 ret = errno;
1950 break;
1952 this_entry = o - LOG_HEADER_SZ;
1953 if (this_entry < 0) {
1954 ret = KADM5_LOG_CORRUPT;
1955 break;
1957 } else {
1958 /* Offset is now at start of current entry, read header */
1959 this_entry = krb5_storage_seek(sp, 0, SEEK_CUR);
1960 if (!(iter_opts & kadm_unconfirmed) && this_entry == log_end)
1961 break;
1962 ret = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
1963 if (ret == HEIM_ERR_EOF) {
1964 ret = 0;
1965 break;
1967 timestamp = tstamp;
1968 if (ret)
1969 break;
1970 /* Offset is now at payload of current entry */
1973 /* Validate trailer before calling the callback */
1974 if (krb5_storage_seek(sp, len, SEEK_CUR) == -1) {
1975 ret = errno;
1976 break;
1979 ret = krb5_ret_uint32(sp, &len2);
1980 if (ret)
1981 break;
1982 ret = krb5_ret_uint32(sp, &ver2);
1983 if (ret)
1984 break;
1985 if (len != len2 || ver != ver2) {
1986 ret = KADM5_LOG_CORRUPT;
1987 break;
1990 /* Rewind to start of payload and call callback if we have one */
1991 if (krb5_storage_seek(sp, this_entry + LOG_HEADER_SZ,
1992 SEEK_SET) == -1) {
1993 ret = errno;
1994 break;
1997 if (func != NULL) {
1998 ret = (*func)(context, ver, timestamp, op, len, sp, ctx);
1999 if (ret) {
2000 /* Callback signals desire to stop by returning -1 */
2001 if (ret == -1)
2002 ret = 0;
2003 break;
2006 if ((iter_opts & kadm_forward)) {
2007 off_t o;
2009 o = krb5_storage_seek(sp, this_entry+LOG_WRAPPER_SZ+len, SEEK_SET);
2010 if (o == -1) {
2011 ret = errno;
2012 break;
2014 if (o > log_end)
2015 *off_lastp = o;
2016 } else if ((iter_opts & kadm_backward)) {
2018 * Rewind to the start of this entry so kadm5_log_previous()
2019 * can find the previous one.
2021 if (krb5_storage_seek(sp, this_entry, SEEK_SET) == -1) {
2022 ret = errno;
2023 break;
2027 if ((ret == HEIM_ERR_EOF || ret == KADM5_LOG_CORRUPT) &&
2028 (iter_opts & kadm_forward) &&
2029 context->log_context.lock_mode == LOCK_EX) {
2031 * Truncate partially written last log entry so we can write
2032 * again.
2034 ret = krb5_storage_truncate(sp, this_entry);
2035 if (ret == 0 &&
2036 krb5_storage_seek(sp, this_entry, SEEK_SET) == -1)
2037 ret = errno;
2038 krb5_warnx(context->context, "Truncating log at partial or "
2039 "corrupt %s entry",
2040 this_entry > log_end ? "unconfirmed" : "confirmed");
2042 krb5_storage_free(sp);
2043 return ret;
2047 * Go to the second record, which, if we have an uber record, will be
2048 * the first record.
2050 static krb5_storage *
2051 log_goto_first(kadm5_server_context *server_context, int fd)
2053 krb5_storage *sp;
2054 enum kadm_ops op;
2055 uint32_t ver, len;
2056 kadm5_ret_t ret;
2058 if (fd == -1) {
2059 errno = EINVAL;
2060 return NULL;
2063 sp = krb5_storage_from_fd(fd);
2064 if (sp == NULL)
2065 return NULL;
2067 if (krb5_storage_seek(sp, 0, SEEK_SET) == -1)
2068 return NULL;
2070 ret = get_header(sp, LOG_DOPEEK, &ver, NULL, &op, &len);
2071 if (ret) {
2072 krb5_storage_free(sp);
2073 errno = ret;
2074 return NULL;
2076 if (op == kadm_nop && len == LOG_UBER_LEN && seek_next(sp) == -1) {
2077 krb5_storage_free(sp);
2078 return NULL;
2080 return sp;
2084 * Go to end of log.
2086 * XXX This really needs to return a kadm5_ret_t and either output a
2087 * krb5_storage * via an argument, or take one as input.
2090 krb5_storage *
2091 kadm5_log_goto_end(kadm5_server_context *server_context, int fd)
2093 krb5_error_code ret = 0;
2094 krb5_storage *sp;
2095 enum kadm_ops op;
2096 uint32_t ver, len;
2097 uint32_t tstamp;
2098 uint64_t off;
2100 if (fd == -1) {
2101 errno = EINVAL;
2102 return NULL;
2105 sp = krb5_storage_from_fd(fd);
2106 if (sp == NULL)
2107 return NULL;
2109 if (krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
2110 ret = errno;
2111 goto fail;
2113 ret = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
2114 if (ret == HEIM_ERR_EOF) {
2115 (void) krb5_storage_seek(sp, 0, SEEK_SET);
2116 return sp;
2118 if (ret == KADM5_LOG_CORRUPT)
2119 goto truncate;
2120 if (ret)
2121 goto fail;
2123 if (op == kadm_nop && len == LOG_UBER_LEN) {
2124 /* New style log */
2125 ret = krb5_ret_uint64(sp, &off);
2126 if (ret)
2127 goto truncate;
2129 if (krb5_storage_seek(sp, off, SEEK_SET) == -1)
2130 goto fail;
2132 if (off >= LOG_UBER_SZ) {
2133 ret = get_version_prev(sp, &ver, NULL);
2134 if (ret == 0)
2135 return sp;
2137 /* Invalid offset in uber entry */
2138 goto truncate;
2141 /* Old log with no uber entry */
2142 if (krb5_storage_seek(sp, 0, SEEK_END) == -1) {
2143 static int warned = 0;
2144 if (!warned) {
2145 warned = 1;
2146 krb5_warnx(server_context->context,
2147 "Old log found; truncate it to upgrade");
2150 ret = get_version_prev(sp, &ver, NULL);
2151 if (ret)
2152 goto truncate;
2153 return sp;
2155 truncate:
2156 /* If we can, truncate */
2157 if (server_context->log_context.lock_mode == LOCK_EX) {
2158 ret = kadm5_log_reinit(server_context, 0);
2159 if (ret == 0) {
2160 krb5_warn(server_context->context, ret,
2161 "Invalid log; truncating to recover");
2162 if (krb5_storage_seek(sp, 0, SEEK_END) == -1)
2163 return NULL;
2164 return sp;
2167 krb5_warn(server_context->context, ret,
2168 "Invalid log; truncate to recover");
2170 fail:
2171 errno = ret;
2172 krb5_storage_free(sp);
2173 return NULL;
2177 * Return previous log entry.
2179 * The pointer in `sp' is assumed to be at the top of the entry after
2180 * previous entry (e.g., at EOF). On success, the `sp' pointer is set to
2181 * data portion of previous entry. In case of error, it's not changed
2182 * at all.
2184 kadm5_ret_t
2185 kadm5_log_previous(krb5_context context,
2186 krb5_storage *sp,
2187 uint32_t *verp,
2188 time_t *tstampp,
2189 enum kadm_ops *opp,
2190 uint32_t *lenp)
2192 krb5_error_code ret;
2193 off_t oldoff;
2194 uint32_t ver2, len2;
2195 uint32_t tstamp;
2197 oldoff = krb5_storage_seek(sp, 0, SEEK_CUR);
2198 if (oldoff == -1)
2199 goto log_corrupt;
2201 /* This reads the physical version of the uber record */
2202 if (seek_prev(sp, verp, lenp) == -1)
2203 goto log_corrupt;
2205 ret = get_header(sp, LOG_NOPEEK, &ver2, &tstamp, opp, &len2);
2206 if (ret) {
2207 (void) krb5_storage_seek(sp, oldoff, SEEK_SET);
2208 return ret;
2210 if (tstampp)
2211 *tstampp = tstamp;
2212 if (ver2 != *verp || len2 != *lenp)
2213 goto log_corrupt;
2215 return 0;
2217 log_corrupt:
2218 (void) krb5_storage_seek(sp, oldoff, SEEK_SET);
2219 return KADM5_LOG_CORRUPT;
2223 * Replay a record from the log
2226 kadm5_ret_t
2227 kadm5_log_replay(kadm5_server_context *context,
2228 enum kadm_ops op,
2229 uint32_t ver,
2230 uint32_t len,
2231 krb5_storage *sp)
2233 switch (op) {
2234 case kadm_create :
2235 return kadm5_log_replay_create(context, ver, len, sp);
2236 case kadm_delete :
2237 return kadm5_log_replay_delete(context, ver, len, sp);
2238 case kadm_rename :
2239 return kadm5_log_replay_rename(context, ver, len, sp);
2240 case kadm_modify :
2241 return kadm5_log_replay_modify(context, ver, len, sp);
2242 case kadm_nop :
2243 return kadm5_log_replay_nop(context, ver, len, sp);
2244 default :
2246 * FIXME This default arm makes it difficult to add new kadm_ops
2247 * values.
2249 krb5_set_error_message(context->context, KADM5_FAILURE,
2250 "Unsupported replay op %d", (int)op);
2251 (void) krb5_storage_seek(sp, len, SEEK_CUR);
2252 return KADM5_FAILURE;
2256 struct load_entries_data {
2257 krb5_data *entries;
2258 unsigned char *p;
2259 uint32_t first;
2260 uint32_t last;
2261 size_t bytes;
2262 size_t nentries;
2263 size_t maxbytes;
2264 size_t maxentries;
2269 * Prepend one entry with header and trailer to the entry buffer, stopping when
2270 * we've reached either of the byte or entry-count limits (if non-zero).
2272 * This is a two-pass algorithm:
2274 * In the first pass, when entries->entries == NULL, we compute the space
2275 * required, and count the entries that fit up from zero.
2277 * In the second pass we fill the buffer, and count the entries back down to
2278 * zero. The space used must be an exact fit, and the number of entries must
2279 * reach zero at that point or an error is returned.
2281 * The caller MUST check that entries->nentries == 0 at the end of the second
2282 * pass.
2284 static kadm5_ret_t
2285 load_entries_cb(kadm5_server_context *server_context,
2286 uint32_t ver,
2287 time_t timestamp,
2288 enum kadm_ops op,
2289 uint32_t len,
2290 krb5_storage *sp,
2291 void *ctx)
2293 struct load_entries_data *entries = ctx;
2294 kadm5_ret_t ret;
2295 ssize_t bytes;
2296 size_t entry_len = len + LOG_WRAPPER_SZ;
2297 unsigned char *base;
2299 if (entries->entries == NULL) {
2300 size_t total = entries->bytes + entry_len;
2303 * First run: find the size of krb5_data buffer needed.
2305 * If the log was huge we'd have to perhaps open a temp file for this.
2306 * For now KISS.
2308 if ((op == kadm_nop && entry_len == LOG_UBER_SZ) ||
2309 entry_len < len /*overflow?*/ ||
2310 (entries->maxbytes > 0 && total > entries->maxbytes) ||
2311 total < entries->bytes /*overflow?*/ ||
2312 (entries->maxentries > 0 && entries->nentries == entries->maxentries))
2313 return -1; /* stop iteration */
2314 entries->bytes = total;
2315 entries->first = ver;
2316 if (entries->nentries++ == 0)
2317 entries->last = ver;
2318 return 0;
2321 /* Second run: load the data into memory */
2322 base = (unsigned char *)entries->entries->data;
2323 if (entries->p - base < entry_len && entries->p != base) {
2325 * This can't happen normally: we stop the log record iteration
2326 * above before we get here. This could happen if someone wrote
2327 * garbage to the log while we were traversing it. We return an
2328 * error instead of asserting.
2330 return KADM5_LOG_CORRUPT;
2334 * sp here is a krb5_storage_from_fd() of the log file, and the
2335 * offset pointer points at the current log record payload.
2337 * Seek back to the start of the record poayload so we can read the
2338 * whole record.
2340 if (krb5_storage_seek(sp, -LOG_HEADER_SZ, SEEK_CUR) == -1)
2341 return errno;
2344 * We read the header, payload, and trailer into the buffer we have, that
2345 * many bytes before the previous record we read.
2347 errno = 0;
2348 bytes = krb5_storage_read(sp, entries->p - entry_len, entry_len);
2349 ret = errno;
2350 if (bytes < 0 || bytes != entry_len)
2351 return ret ? ret : EIO;
2353 entries->first = ver;
2354 --entries->nentries;
2355 entries->p -= entry_len;
2356 return (entries->p == base) ? -1 : 0;
2361 * Serialize a tail fragment of the log as a krb5_data, this is constrained to
2362 * at most `maxbytes' bytes and to at most `maxentries' entries if not zero.
2364 static kadm5_ret_t
2365 load_entries(kadm5_server_context *context, krb5_data *p,
2366 size_t maxentries, size_t maxbytes,
2367 uint32_t *first, uint32_t *last)
2369 struct load_entries_data entries;
2370 kadm5_ret_t ret;
2371 unsigned char *base;
2373 krb5_data_zero(p);
2375 *first = 0;
2377 memset(&entries, 0, sizeof(entries));
2378 entries.entries = NULL;
2379 entries.p = NULL;
2380 entries.maxentries = maxentries;
2381 entries.maxbytes = maxbytes;
2383 /* Figure out how many bytes it will take */
2384 ret = kadm5_log_foreach(context, kadm_backward | kadm_confirmed,
2385 NULL, load_entries_cb, &entries);
2386 if (ret)
2387 return ret;
2390 * If no entries fit our limits, we do not truncate, instead the caller can
2391 * call kadm5_log_reinit() if desired.
2393 if (entries.bytes == 0)
2394 return 0;
2396 ret = krb5_data_alloc(p, entries.bytes);
2397 if (ret)
2398 return ret;
2400 *first = entries.first;
2401 *last = entries.last;
2402 entries.entries = p;
2403 base = (unsigned char *)entries.entries->data;
2404 entries.p = base + entries.bytes;
2406 ret = kadm5_log_foreach(context, kadm_backward | kadm_confirmed,
2407 NULL, load_entries_cb, &entries);
2408 if (ret == 0 &&
2409 (entries.nentries || entries.p != base || entries.first != *first))
2410 ret = KADM5_LOG_CORRUPT;
2411 if (ret)
2412 krb5_data_free(p);
2413 return ret;
2417 * Truncate the log, retaining at most `keep' entries and at most `maxbytes'.
2418 * If `maxbytes' is zero, keep at most the default log size limit.
2420 kadm5_ret_t
2421 kadm5_log_truncate(kadm5_server_context *context, size_t keep, size_t maxbytes)
2423 kadm5_ret_t ret;
2424 uint32_t first, last, last_tstamp;
2425 time_t now = time(NULL);
2426 krb5_data entries;
2427 krb5_storage *sp;
2428 ssize_t bytes;
2429 uint64_t sz;
2430 off_t off;
2432 if (maxbytes == 0)
2433 maxbytes = get_max_log_size(context->context);
2435 if (strcmp(context->log_context.log_file, "/dev/null") == 0)
2436 return 0;
2438 if (context->log_context.read_only)
2439 return EROFS;
2441 /* Get the desired records. */
2442 krb5_data_zero(&entries);
2443 ret = load_entries(context, &entries, keep, maxbytes, &first, &last);
2444 if (ret)
2445 return ret;
2447 if (first == 0) {
2449 * No records found/fit within resource limits. The caller should call
2450 * kadm5_log_reinit(context) to truly truncate and reset the log to
2451 * version 0, else call again with better limits.
2453 krb5_data_free(&entries);
2454 return EINVAL;
2457 /* Check that entries.length won't overflow off_t */
2458 sz = LOG_UBER_SZ + entries.length;
2459 off = (off_t)sz;
2460 if (off < 0 || off != sz || sz < entries.length) {
2461 krb5_data_free(&entries);
2462 return EOVERFLOW; /* caller should ask for fewer entries */
2465 /* Truncate to zero size and seek to zero offset */
2466 if (ftruncate(context->log_context.log_fd, 0) < 0 ||
2467 lseek(context->log_context.log_fd, 0, SEEK_SET) < 0) {
2468 krb5_data_free(&entries);
2469 return errno;
2473 * Write the uber record and then the records loaded. Confirm the entries
2474 * after writing them.
2476 * If we crash then the log may not have all the entries we want, and
2477 * replaying only some of the entries will leave us in a bad state.
2478 * Additionally, we don't have mathematical proof that replaying the last
2479 * N>1 entries is always idempotent. And though we believe we can make
2480 * such replays idempotent, they would still leave the HDB with
2481 * intermediate states that would not have occurred on the master.
2483 * By initially setting the offset in the uber record to 0, the log will be
2484 * seen as invalid should we crash here, thus the only
2485 * harm will be that we'll reinitialize the log and force full props.
2487 * We can't use the normal kadm5_log_*() machinery for this because
2488 * we must set specific version numbers and timestamps. To keep
2489 * things simple we don't try to do a single atomic write here as we
2490 * do in kadm5_log_flush().
2492 * We really do want to keep the new first entry's version and
2493 * timestamp so we don't trip up iprop.
2495 * Keep this in sync with kadm5_log_nop().
2497 sp = krb5_storage_from_fd(context->log_context.log_fd);
2498 if (sp == NULL) {
2499 ret = errno;
2500 krb5_warn(context->context, ret, "Unable to keep entries");
2501 krb5_data_free(&entries);
2502 return errno;
2504 ret = krb5_store_uint32(sp, 0);
2505 if (ret == 0)
2506 ret = krb5_store_uint32(sp, now);
2507 if (ret == 0)
2508 ret = krb5_store_uint32(sp, kadm_nop); /* end of preamble */
2509 if (ret == 0)
2510 ret = krb5_store_uint32(sp, LOG_UBER_LEN); /* end of header */
2511 if (ret == 0)
2512 ret = krb5_store_uint64(sp, LOG_UBER_SZ);
2513 if (ret == 0)
2514 ret = krb5_store_uint32(sp, now);
2515 if (ret == 0)
2516 ret = krb5_store_uint32(sp, last);
2517 if (ret == 0)
2518 ret = krb5_store_uint32(sp, LOG_UBER_LEN);
2519 if (ret == 0)
2520 ret = krb5_store_uint32(sp, 0); /* end of trailer */
2521 if (ret == 0) {
2522 bytes = krb5_storage_write(sp, entries.data, entries.length);
2523 if (bytes == -1)
2524 ret = errno;
2526 if (ret == 0)
2527 ret = krb5_storage_fsync(sp);
2528 /* Confirm all the records now */
2529 if (ret == 0) {
2530 if (krb5_storage_seek(sp, LOG_HEADER_SZ, SEEK_SET) == -1)
2531 ret = errno;
2533 if (ret == 0)
2534 ret = krb5_store_uint64(sp, off);
2535 krb5_data_free(&entries);
2536 krb5_storage_free(sp);
2538 if (ret) {
2539 krb5_warn(context->context, ret, "Unable to keep entries");
2540 (void) ftruncate(context->log_context.log_fd, LOG_UBER_SZ);
2541 (void) lseek(context->log_context.log_fd, 0, SEEK_SET);
2542 return ret;
2545 /* Done. Now rebuild the log_context state. */
2546 (void) lseek(context->log_context.log_fd, off, SEEK_SET);
2547 sp = kadm5_log_goto_end(context, context->log_context.log_fd);
2548 if (sp == NULL)
2549 return ENOMEM;
2550 ret = get_version_prev(sp, &context->log_context.version, &last_tstamp);
2551 context->log_context.last_time = last_tstamp;
2552 krb5_storage_free(sp);
2553 return ret;
2557 * "Truncate" the log if not read only and over the desired maximum size. We
2558 * attempt to retain 1/4 of the existing storage.
2560 * Called after successful log recovery, so at this point we must have no
2561 * unconfirmed entries in the log.
2563 static kadm5_ret_t
2564 truncate_if_needed(kadm5_server_context *context)
2566 kadm5_ret_t ret = 0;
2567 kadm5_log_context *log_context = &context->log_context;
2568 size_t maxbytes;
2569 struct stat st;
2571 if (log_context->log_fd == -1 || log_context->read_only)
2572 return 0;
2573 if (strcmp(context->log_context.log_file, "/dev/null") == 0)
2574 return 0;
2576 maxbytes = get_max_log_size(context->context);
2577 if (maxbytes <= 0)
2578 return 0;
2580 if (fstat(log_context->log_fd, &st) == -1)
2581 return errno;
2582 if (st.st_size == (size_t)st.st_size && (size_t)st.st_size <= maxbytes)
2583 return 0;
2585 /* Shrink the log by a factor of 4 */
2586 ret = kadm5_log_truncate(context, 0, maxbytes/4);
2587 return ret == EINVAL ? 0 : ret;
2590 #ifndef NO_UNIX_SOCKETS
2592 static char *default_signal = NULL;
2593 static HEIMDAL_MUTEX signal_mutex = HEIMDAL_MUTEX_INITIALIZER;
2595 const char *
2596 kadm5_log_signal_socket(krb5_context context)
2598 int ret = 0;
2600 HEIMDAL_MUTEX_lock(&signal_mutex);
2601 if (!default_signal)
2602 ret = asprintf(&default_signal, "%s/signal", hdb_db_dir(context));
2603 if (ret == -1)
2604 default_signal = NULL;
2605 HEIMDAL_MUTEX_unlock(&signal_mutex);
2607 return krb5_config_get_string_default(context,
2608 NULL,
2609 default_signal,
2610 "kdc",
2611 "signal_socket",
2612 NULL);
2615 #else /* NO_UNIX_SOCKETS */
2617 #define SIGNAL_SOCKET_HOST "127.0.0.1"
2618 #define SIGNAL_SOCKET_PORT "12701"
2620 kadm5_ret_t
2621 kadm5_log_signal_socket_info(krb5_context context,
2622 int server_end,
2623 struct addrinfo **ret_addrs)
2625 struct addrinfo hints;
2626 struct addrinfo *addrs = NULL;
2627 kadm5_ret_t ret = KADM5_FAILURE;
2628 int wsret;
2630 memset(&hints, 0, sizeof(hints));
2632 hints.ai_flags = AI_NUMERICHOST;
2633 if (server_end)
2634 hints.ai_flags |= AI_PASSIVE;
2635 hints.ai_family = AF_INET;
2636 hints.ai_socktype = SOCK_STREAM;
2637 hints.ai_protocol = IPPROTO_TCP;
2639 wsret = getaddrinfo(SIGNAL_SOCKET_HOST,
2640 SIGNAL_SOCKET_PORT,
2641 &hints, &addrs);
2643 if (wsret != 0) {
2644 krb5_set_error_message(context, KADM5_FAILURE,
2645 "%s", gai_strerror(wsret));
2646 goto done;
2649 if (addrs == NULL) {
2650 krb5_set_error_message(context, KADM5_FAILURE,
2651 "getaddrinfo() failed to return address list");
2652 goto done;
2655 *ret_addrs = addrs;
2656 addrs = NULL;
2657 ret = 0;
2659 done:
2660 if (addrs)
2661 freeaddrinfo(addrs);
2662 return ret;
2665 #endif