2 * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
34 #include "kadm5_locl.h"
35 #include "heim_threads.h"
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
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
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:
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:
133 * version number 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
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
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
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)
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.
194 get_header(krb5_storage
*sp
, int peek
, uint32_t *verp
, uint32_t *tstampp
,
195 enum kadm_ops
*opp
, uint32_t *lenp
)
198 uint32_t tstamp
, op
, len
;
209 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
212 ret
= krb5_ret_uint32(sp
, verp
);
213 if (ret
== HEIM_ERR_EOF
) {
214 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
219 ret
= krb5_ret_uint32(sp
, tstampp
);
223 /* Note: sizeof(*opp) might not == sizeof(op) */
224 ret
= krb5_ret_uint32(sp
, &op
);
230 ret
= krb5_ret_uint32(sp
, lenp
);
234 /* Restore offset if requested */
235 if (peek
== LOG_DOPEEK
) {
236 new_off
= krb5_storage_seek(sp
, off
, SEEK_SET
);
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.
260 seek_prev(krb5_storage
*sp
, uint32_t *verp
, uint32_t *lenp
)
275 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
281 /* Check that `off' allows for the record's header and trailer */
282 if (off
< LOG_WRAPPER_SZ
)
285 /* Get the previous entry's length and version from its trailer */
286 new_off
= krb5_storage_seek(sp
, -8, SEEK_CUR
);
289 if (new_off
!= off
- 8) {
293 ret
= krb5_ret_uint32(sp
, lenp
);
297 /* Check for overflow/sign extension */
298 off_len
= (off_t
)*lenp
;
299 if (off_len
< 0 || *lenp
!= (uint32_t)off_len
)
302 ret
= krb5_ret_uint32(sp
, verp
);
306 /* Check that `off' allows for the record */
307 if (off
< LOG_WRAPPER_SZ
+ off_len
)
310 /* Seek backwards to the entry's start */
311 new_off
= krb5_storage_seek(sp
, -(LOG_WRAPPER_SZ
+ off_len
), SEEK_CUR
);
314 if (new_off
!= off
- (LOG_WRAPPER_SZ
+ off_len
)) {
321 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
322 errno
= KADM5_LOG_CORRUPT
;
327 * Seek to the start of the next entry's header.
329 * On error returns -1 and preserves sp's offset.
332 seek_next(krb5_storage
*sp
)
335 uint32_t ver
, ver2
, len
, len2
;
338 off_t off
, off_len
, new_off
;
340 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
344 errno
= get_header(sp
, LOG_NOPEEK
, &ver
, &tstamp
, &op
, &len
);
348 /* Check for overflow */
353 new_off
= krb5_storage_seek(sp
, off_len
, SEEK_CUR
);
355 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
358 if (new_off
!= off
+ LOG_HEADER_SZ
+ off_len
)
360 ret
= krb5_ret_uint32(sp
, &len2
);
361 if (ret
|| len2
!= len
)
363 ret
= krb5_ret_uint32(sp
, &ver2
);
364 if (ret
|| ver2
!= ver
)
366 new_off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
368 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
371 if (new_off
!= off
+ off_len
+ LOG_WRAPPER_SZ
)
374 return off
+ off_len
+ LOG_WRAPPER_SZ
;
377 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
378 errno
= KADM5_LOG_CORRUPT
;
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.
391 get_version_prev(krb5_storage
*sp
, uint32_t *verp
, uint32_t *tstampp
)
394 uint32_t ver
, ver2
, len
, len2
;
395 off_t off
, prev_off
, new_off
;
401 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
407 /* Read the trailer and seek back */
408 prev_off
= seek_prev(sp
, &ver
, &len
);
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)
418 ret
= krb5_ret_uint32(sp
, verp
);
419 if (krb5_storage_seek(sp
, 0, SEEK_SET
) != 0)
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
)
432 /* Preserve offset */
433 new_off
= krb5_storage_seek(sp
, off
, SEEK_SET
);
436 if (new_off
!= off
) {
443 (void) krb5_storage_seek(sp
, off
, SEEK_SET
);
444 return KADM5_LOG_CORRUPT
;
448 get_max_log_size(krb5_context context
)
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,
458 if (n
>= 4 * (LOG_UBER_LEN
+ LOG_WRAPPER_SZ
) && n
== (size_t)n
)
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'.
486 kadm5_log_get_version_fd(kadm5_server_context
*server_context
, int fd
,
487 int which
, uint32_t *ver
, uint32_t *tstamp
)
491 enum kadm_ops op
= kadm_get
;
496 return 0; /* /dev/null */
505 case LOG_VERSION_LAST
:
506 sp
= kadm5_log_goto_end(server_context
, fd
);
509 ret
= get_version_prev(sp
, ver
, tstamp
);
510 krb5_storage_free(sp
);
512 case LOG_VERSION_FIRST
:
513 sp
= log_goto_first(server_context
, fd
);
516 ret
= get_header(sp
, LOG_DOPEEK
, ver
, tstamp
, NULL
, NULL
);
517 krb5_storage_free(sp
);
519 case LOG_VERSION_UBER
:
520 sp
= krb5_storage_from_fd(server_context
->log_context
.log_fd
);
523 if (krb5_storage_seek(sp
, 0, SEEK_SET
) == 0)
524 ret
= get_header(sp
, LOG_DOPEEK
, ver
, tstamp
, &op
, &len
);
527 if (ret
== 0 && (op
!= kadm_nop
|| len
!= LOG_UBER_LEN
|| *ver
!= 0))
528 ret
= KADM5_LOG_NEEDS_UPGRADE
;
529 krb5_storage_free(sp
);
538 /* Get the version of the last confirmed entry in the log */
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 */
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
;
558 * Open the log and setup server_context->log_context
561 log_open(kadm5_server_context
*server_context
, int lock_mode
)
568 kadm5_log_context
*log_context
= &server_context
->log_context
;
570 if (lock_mode
& LOCK_NB
) {
571 lock_mode
&= ~LOCK_NB
;
575 if (lock_mode
== log_context
->lock_mode
&& log_context
->log_fd
!= -1)
578 if (strcmp(log_context
->log_file
, "/dev/null") == 0) {
579 /* log_context->log_fd should be -1 here */
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)
588 lock_it
= (lock_mode
!= log_context
->lock_mode
);
591 if (lock_mode
!= LOCK_UN
)
593 fd
= open(log_context
->log_file
, oflags
, 0600);
596 krb5_set_error_message(server_context
->context
, ret
,
597 "log_open: open %s", log_context
->log_file
);
600 lock_it
= (lock_mode
!= LOCK_UN
);
602 if (lock_it
&& flock(fd
, lock_mode
| lock_nb
) < 0) {
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
)
611 log_context
->log_fd
= fd
;
612 log_context
->lock_mode
= lock_mode
;
613 log_context
->read_only
= (lock_mode
!= LOCK_EX
);
619 * Open the log and setup server_context->log_context
622 log_init(kadm5_server_context
*server_context
, int lock_mode
)
627 size_t maxbytes
= get_max_log_size(server_context
->context
);
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 */
636 ret
= log_open(server_context
, lock_mode
);
640 fd
= log_context
->log_fd
;
641 if (!log_context
->read_only
) {
642 if (fstat(fd
, &st
) == -1)
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
);
649 return 0; /* no need to truncate_if_needed(): it's not */
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);
660 ret
= kadm5_log_recover(server_context
, kadm_recover_replay
);
664 ret
= kadm5_log_get_version_fd(server_context
, fd
, LOG_VERSION_LAST
,
665 &log_context
->version
, NULL
);
666 if (ret
== HEIM_ERR_EOF
)
671 ret
= truncate_if_needed(server_context
);
674 (void) kadm5_log_end(server_context
);
678 /* Open the log with an exclusive lock */
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 */
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 */
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 */
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
710 kadm5_log_reinit(kadm5_server_context
*server_context
, uint32_t vno
)
713 kadm5_log_context
*log_context
= &server_context
->log_context
;
715 ret
= log_open(server_context
, LOCK_EX
);
718 if (log_context
->log_fd
!= -1) {
719 if (ftruncate(log_context
->log_fd
, 0) < 0) {
723 if (lseek(log_context
->log_fd
, 0, SEEK_SET
) < 0) {
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
);
735 /* Close the server_context->log_context. */
737 kadm5_log_end(kadm5_server_context
*server_context
)
739 kadm5_log_context
*log_context
= &server_context
->log_context
;
741 int fd
= log_context
->log_fd
;
744 if (log_context
->lock_mode
!= LOCK_UN
) {
745 if (flock(fd
, LOCK_UN
) == -1 && errno
== EBADF
)
748 if (ret
!= EBADF
&& close(fd
) == -1)
751 log_context
->log_fd
= -1;
752 log_context
->lock_mode
= LOCK_UN
;
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
765 kadm5_log_preamble(kadm5_server_context
*context
,
770 kadm5_log_context
*log_context
= &context
->log_context
;
771 time_t now
= time(NULL
);
774 ret
= krb5_store_uint32(sp
, vno
);
777 ret
= krb5_store_uint32(sp
, now
);
780 log_context
->last_time
= now
;
782 if (op
< kadm_first
|| op
> kadm_last
)
784 return krb5_store_uint32(sp
, op
);
787 /* Writes the version part of the trailer */
789 kadm5_log_postamble(kadm5_log_context
*context
,
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
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
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
));
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
);
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.
837 kadm5_log_flush(kadm5_server_context
*context
, krb5_storage
*sp
)
839 kadm5_log_context
*log_context
= &context
->log_context
;
844 uint32_t new_ver
, prev_ver
;
847 if (strcmp(log_context
->log_file
, "/dev/null") == 0)
850 if (log_context
->read_only
)
853 if (krb5_storage_seek(sp
, 0, SEEK_SET
) == -1)
856 ret
= get_header(sp
, LOG_DOPEEK
, &new_ver
, NULL
, NULL
, NULL
);
860 ret
= krb5_storage_to_data(sp
, &data
);
864 /* Abandon the emem storage reference */
865 sp
= krb5_storage_from_fd(log_context
->log_fd
);
867 krb5_data_free(&data
);
871 /* Check that we are at the end of the log and fail if not */
872 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
874 krb5_data_free(&data
);
875 krb5_storage_free(sp
);
878 end
= krb5_storage_seek(sp
, 0, SEEK_END
);
880 krb5_data_free(&data
);
881 krb5_storage_free(sp
);
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) {
894 krb5_data_free(&data
);
895 krb5_storage_free(sp
);
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)",
906 return KADM5_LOG_CORRUPT
;
910 bytes
= krb5_storage_write(sp
, data
.data
, len
);
911 krb5_data_free(&data
);
913 krb5_storage_free(sp
);
916 if (bytes
!= (krb5_ssize_t
)len
) {
917 krb5_storage_free(sp
);
921 ret
= krb5_storage_fsync(sp
);
922 krb5_storage_free(sp
);
926 /* Retain the nominal database version when flushing the uber record */
928 log_context
->version
= new_ver
;
933 * Add a `create' operation to the log and perform the create against the HDB.
936 kadm5_log_create(kadm5_server_context
*context
, hdb_entry
*entry
)
942 kadm5_log_context
*log_context
= &context
->log_context
;
944 memset(&ent
, 0, sizeof(ent
));
950 * If we're not logging then we can't recover-to-perform, so just
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
);
964 ret
= hdb_entry2value(context
->context
, entry
, &value
);
967 sp
= krb5_storage_emem();
971 ret
= kadm5_log_preamble(context
, sp
, kadm_create
,
972 log_context
->version
+ 1);
974 ret
= krb5_store_uint32(sp
, value
.length
);
976 if (krb5_storage_write(sp
, value
.data
, value
.length
) !=
977 (krb5_ssize_t
)value
.length
)
981 ret
= krb5_store_uint32(sp
, value
.length
);
983 ret
= kadm5_log_postamble(log_context
, sp
,
984 log_context
->version
+ 1);
986 ret
= kadm5_log_flush(context
, sp
);
987 krb5_storage_free(sp
);
988 krb5_data_free(&value
);
990 ret
= kadm5_log_recover(context
, kadm_recover_commit
);
995 * Read the data of a create log record from `sp' and change the
999 kadm5_log_replay_create(kadm5_server_context
*context
,
1004 krb5_error_code ret
;
1008 memset(&ent
, 0, sizeof(ent
));
1010 ret
= krb5_data_alloc(&data
, len
);
1012 krb5_set_error_message(context
->context
, ret
, "out of memory");
1015 krb5_storage_read(sp
, data
.data
, len
);
1016 ret
= hdb_value2entry(context
->context
, &data
, &ent
.entry
);
1017 krb5_data_free(&data
);
1019 krb5_set_error_message(context
->context
, ret
,
1020 "Unmarshaling hdb entry in log failed, "
1021 "version: %ld", (long)ver
);
1024 ret
= context
->db
->hdb_store(context
->context
, context
->db
, 0, &ent
);
1025 hdb_free_entry(context
->context
, &ent
);
1030 * Add a `delete' operation to the log.
1033 kadm5_log_delete(kadm5_server_context
*context
,
1034 krb5_principal princ
)
1037 kadm5_log_context
*log_context
= &context
->log_context
;
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 */
1043 if (strcmp(log_context
->log_file
, "/dev/null") == 0)
1044 return context
->db
->hdb_remove(context
->context
, context
->db
, 0,
1046 ret
= context
->db
->hdb_remove(context
->context
, context
->db
,
1047 HDB_F_PRECHECK
, princ
);
1050 sp
= krb5_storage_emem();
1054 ret
= kadm5_log_preamble(context
, sp
, kadm_delete
,
1055 log_context
->version
+ 1);
1057 krb5_storage_free(sp
);
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
);
1069 ret
= krb5_store_uint32(sp
, 0);
1071 ret
= krb5_store_principal(sp
, princ
);
1073 end_off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
1076 else if (end_off
< off
)
1077 ret
= KADM5_LOG_CORRUPT
;
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
;
1087 if (ret
== 0 && krb5_storage_seek(sp
, off
, SEEK_SET
) == -1)
1090 ret
= krb5_store_uint32(sp
, len
);
1091 if (ret
== 0 && krb5_storage_seek(sp
, end_off
, SEEK_SET
) == -1)
1094 ret
= krb5_store_uint32(sp
, len
);
1096 ret
= kadm5_log_postamble(log_context
, sp
,
1097 log_context
->version
+ 1);
1099 ret
= kadm5_log_flush(context
, sp
);
1101 ret
= kadm5_log_recover(context
, kadm_recover_commit
);
1102 krb5_storage_free(sp
);
1107 * Read a `delete' log operation from `sp' and apply it.
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
);
1118 krb5_set_error_message(context
->context
, ret
, "Failed to read deleted "
1119 "principal from log version: %ld", (long)ver
);
1123 ret
= context
->db
->hdb_remove(context
->context
, context
->db
, 0, principal
);
1124 krb5_free_principal(context
->context
, principal
);
1128 static kadm5_ret_t
kadm5_log_replay_rename(kadm5_server_context
*,
1133 * Add a `rename' operation to the log.
1136 kadm5_log_rename(kadm5_server_context
*context
,
1137 krb5_principal source
,
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 */
1147 kadm5_log_context
*log_context
= &context
->log_context
;
1149 memset(&ent
, 0, sizeof(ent
));
1154 if (strcmp(log_context
->log_file
, "/dev/null") == 0) {
1155 ret
= context
->db
->hdb_store(context
->context
, context
->db
, 0, &ent
);
1157 return context
->db
->hdb_remove(context
->context
, context
->db
, 0,
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
);
1174 ret
= context
->db
->hdb_remove(context
->context
, context
->db
,
1175 HDB_F_PRECHECK
, source
);
1179 sp
= krb5_storage_emem();
1180 krb5_data_zero(&value
);
1184 ret
= kadm5_log_preamble(context
, sp
, kadm_rename
,
1185 log_context
->version
+ 1);
1187 ret
= hdb_entry2value(context
->context
, entry
, &value
);
1189 krb5_data_free(&value
);
1190 krb5_storage_free(sp
);
1195 * Write a zero length which we'll overwrite once we know the length of the
1198 off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
1202 ret
= krb5_store_uint32(sp
, 0);
1204 ret
= krb5_store_principal(sp
, source
);
1207 if (krb5_storage_write(sp
, value
.data
, value
.length
) !=
1208 (krb5_ssize_t
)value
.length
)
1209 ret
= errno
? errno
: EIO
;
1212 end_off
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
1215 else if (end_off
< off
)
1216 ret
= KADM5_LOG_CORRUPT
;
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
;
1225 if (ret
== 0 && krb5_storage_seek(sp
, off
, SEEK_SET
) == -1)
1228 ret
= krb5_store_uint32(sp
, len
);
1229 if (ret
== 0 && krb5_storage_seek(sp
, end_off
, SEEK_SET
) == -1)
1232 ret
= krb5_store_uint32(sp
, len
);
1234 ret
= kadm5_log_postamble(log_context
, sp
,
1235 log_context
->version
+ 1);
1237 ret
= kadm5_log_flush(context
, sp
);
1239 ret
= kadm5_log_recover(context
, kadm_recover_commit
);
1241 krb5_data_free(&value
);
1242 krb5_storage_free(sp
);
1247 * Read a `rename' log operation from `sp' and apply it.
1251 kadm5_log_replay_rename(kadm5_server_context
*context
,
1256 krb5_error_code ret
;
1257 krb5_principal source
;
1258 hdb_entry_ex target_ent
;
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
);
1268 krb5_set_error_message(context
->context
, ret
, "Failed to read renamed "
1269 "principal in log, version: %ld", (long)ver
);
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
);
1276 krb5_free_principal (context
->context
, source
);
1279 krb5_storage_read(sp
, value
.data
, data_len
);
1280 ret
= hdb_value2entry(context
->context
, &value
, &target_ent
.entry
);
1281 krb5_data_free(&value
);
1283 krb5_free_principal(context
->context
, source
);
1286 ret
= context
->db
->hdb_store(context
->context
, context
->db
,
1288 hdb_free_entry(context
->context
, &target_ent
);
1290 krb5_free_principal(context
->context
, source
);
1293 ret
= context
->db
->hdb_remove(context
->context
, context
->db
, 0, source
);
1294 krb5_free_principal(context
->context
, source
);
1300 * Add a `modify' operation to the log.
1303 kadm5_log_modify(kadm5_server_context
*context
,
1312 kadm5_log_context
*log_context
= &context
->log_context
;
1314 memset(&ent
, 0, sizeof(ent
));
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
);
1328 sp
= krb5_storage_emem();
1329 krb5_data_zero(&value
);
1333 ret
= hdb_entry2value(context
->context
, entry
, &value
);
1335 krb5_data_free(&value
);
1336 krb5_storage_free(sp
);
1340 len
= value
.length
+ sizeof(len
);
1341 if (value
.length
> len
|| len
> INT32_MAX
)
1344 ret
= kadm5_log_preamble(context
, sp
, kadm_modify
,
1345 log_context
->version
+ 1);
1347 ret
= krb5_store_uint32(sp
, len
);
1349 ret
= krb5_store_uint32(sp
, mask
);
1351 if (krb5_storage_write(sp
, value
.data
, value
.length
) !=
1352 (krb5_ssize_t
)value
.length
)
1356 ret
= krb5_store_uint32(sp
, len
);
1358 ret
= kadm5_log_postamble(log_context
, sp
,
1359 log_context
->version
+ 1);
1361 ret
= kadm5_log_flush(context
, sp
);
1363 ret
= kadm5_log_recover(context
, kadm_recover_commit
);
1364 krb5_data_free(&value
);
1365 krb5_storage_free(sp
);
1370 * Read a `modify' log operation from `sp' and apply it.
1373 kadm5_log_replay_modify(kadm5_server_context
*context
,
1378 krb5_error_code ret
;
1381 hdb_entry_ex ent
, log_ent
;
1383 memset(&log_ent
, 0, sizeof(log_ent
));
1385 ret
= krb5_ret_uint32(sp
, &mask
);
1389 ret
= krb5_data_alloc (&value
, len
);
1391 krb5_set_error_message(context
->context
, ret
, "out of memory");
1395 if (krb5_storage_read (sp
, value
.data
, len
) != (krb5_ssize_t
)len
) {
1396 ret
= errno
? errno
: EIO
;
1399 ret
= hdb_value2entry (context
->context
, &value
, &log_ent
.entry
);
1400 krb5_data_free(&value
);
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
);
1411 if (mask
& KADM5_PRINC_EXPIRE_TIME
) {
1412 if (log_ent
.entry
.valid_end
== NULL
) {
1413 ent
.entry
.valid_end
= NULL
;
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
) {
1419 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
;
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
) {
1434 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
;
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
) {
1456 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
) {
1468 krb5_set_error_message(context
->context
, ret
, "out of memory");
1472 free_Event(ent
.entry
.modified_by
);
1473 ret
= copy_Event(log_ent
.entry
.modified_by
, ent
.entry
.modified_by
);
1475 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
;
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
) {
1500 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
) {
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");
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
]);
1546 krb5_set_error_message(context
->context
, ret
, "out of memory");
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
)
1558 ret
= copy_HDB_extensions(log_ent
.entry
.extensions
,
1559 ent
.entry
.extensions
);
1561 krb5_set_error_message(context
->context
, ret
, "out of memory");
1562 free(ent
.entry
.extensions
);
1563 ent
.entry
.extensions
= es
;
1567 free_HDB_extensions(es
);
1571 ret
= context
->db
->hdb_store(context
->context
, context
->db
,
1572 HDB_F_REPLACE
, &ent
);
1574 hdb_free_entry(context
->context
, &ent
);
1575 hdb_free_entry(context
->context
, &log_ent
);
1580 * Update the first entry (which should be a `nop'), the "uber-entry".
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
;
1592 if (strcmp(log_context
->log_file
, "/dev/null") == 0)
1595 if (log_context
->read_only
)
1598 krb5_data_zero(&data
);
1600 mem_sp
= krb5_storage_emem();
1604 sp
= krb5_storage_from_fd(log_context
->log_fd
);
1606 krb5_storage_free(mem_sp
);
1610 /* Skip first entry's version and timestamp */
1611 if (krb5_storage_seek(sp
, 8, SEEK_SET
) == -1) {
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
)
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
)
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
);
1633 ret
= krb5_store_uint32(mem_sp
, log_context
->last_time
);
1636 ret
= krb5_store_uint32(mem_sp
, log_context
->version
);
1640 krb5_storage_to_data(mem_sp
, &data
);
1641 bytes
= krb5_storage_write(sp
, data
.data
, data
.length
);
1644 else if (bytes
!= data
.length
)
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.
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
;
1670 * Add a `nop' operation to the log. Does not close the log.
1673 kadm5_log_nop(kadm5_server_context
*context
, enum kadm_nop_type nop_type
)
1677 kadm5_log_context
*log_context
= &context
->log_context
;
1679 uint32_t vno
= log_context
->version
;
1681 if (strcmp(log_context
->log_file
, "/dev/null") == 0)
1684 off
= lseek(log_context
->log_fd
, 0, SEEK_CUR
);
1688 sp
= krb5_storage_emem();
1689 ret
= kadm5_log_preamble(context
, sp
, kadm_nop
, off
== 0 ? 0 : vno
+ 1);
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 */
1701 ret
= krb5_store_uint64(sp
, LOG_UBER_SZ
);
1703 ret
= krb5_store_uint32(sp
, log_context
->last_time
);
1705 ret
= krb5_store_uint32(sp
, vno
);
1707 ret
= krb5_store_uint32(sp
, LOG_UBER_LEN
);
1708 } else if (nop_type
== kadm_nop_plain
) {
1709 ret
= krb5_store_uint32(sp
, 0);
1711 ret
= krb5_store_uint32(sp
, 0);
1713 ret
= krb5_store_uint32(sp
, sizeof(uint32_t));
1715 ret
= krb5_store_uint32(sp
, nop_type
);
1717 ret
= krb5_store_uint32(sp
, sizeof(uint32_t));
1721 ret
= kadm5_log_postamble(log_context
, sp
, off
== 0 ? 0 : vno
+ 1);
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
);
1732 krb5_storage_free(sp
);
1737 * Read a `nop' log operation from `sp' and "apply" it (there's nothing
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.
1746 kadm5_log_replay_nop(kadm5_server_context
*context
,
1754 struct replay_cb_data
{
1757 enum kadm_recover_mode mode
;
1762 * Recover or perform the initial commit of an unconfirmed log entry
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
;
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
))
1785 ret
= kadm5_log_replay(context
, op
, ver
, len
, sp
);
1787 case HDB_ERR_NOENTRY
:
1788 case HDB_ERR_EXISTS
:
1789 if (data
->mode
!= kadm_recover_replay
)
1793 case KADM5_LOG_CORRUPT
:
1796 krb5_warn(context
->context
, ret
, "unexpected error while replaying");
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
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
);
1818 kadm5_log_recover(kadm5_server_context
*context
, enum kadm_recover_mode mode
)
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
);
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
);
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.
1849 kadm5_log_foreach(kadm5_server_context
*context
,
1850 enum kadm_iter_opts iter_opts
,
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
),
1858 kadm5_ret_t ret
= 0;
1859 int fd
= context
->log_context
.log_fd
;
1862 off_t this_entry
= 0;
1865 if (strcmp(context
->log_context
.log_file
, "/dev/null") == 0)
1868 if (off_lastp
== NULL
)
1869 off_lastp
= &off_last
;
1872 if (((iter_opts
& kadm_forward
) && (iter_opts
& kadm_backward
)) ||
1873 (!(iter_opts
& kadm_confirmed
) && !(iter_opts
& kadm_unconfirmed
)))
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
);
1887 log_end
= krb5_storage_seek(sp
, 0, SEEK_END
);
1888 if (log_end
== -1 ||
1889 krb5_storage_seek(sp
, 0, SEEK_SET
) == -1) {
1891 krb5_storage_free(sp
);
1895 /* Get the end of the log based on the uber entry */
1896 sp
= kadm5_log_goto_end(context
, fd
);
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) {
1908 krb5_storage_free(sp
);
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
);
1920 if (krb5_storage_seek(sp
, log_end
, SEEK_SET
) == -1) {
1922 krb5_storage_free(sp
);
1928 uint32_t ver
, ver2
, len
, len2
;
1933 if ((iter_opts
& kadm_backward
)) {
1936 o
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
1938 ((iter_opts
& kadm_unconfirmed
) && o
<= *off_lastp
))
1940 ret
= kadm5_log_previous(context
->context
, sp
, &ver
,
1941 ×tamp
, &op
, &len
);
1945 /* Offset is now at payload of current entry */
1947 o
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
1952 this_entry
= o
- LOG_HEADER_SZ
;
1953 if (this_entry
< 0) {
1954 ret
= KADM5_LOG_CORRUPT
;
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
)
1962 ret
= get_header(sp
, LOG_NOPEEK
, &ver
, &tstamp
, &op
, &len
);
1963 if (ret
== HEIM_ERR_EOF
) {
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) {
1979 ret
= krb5_ret_uint32(sp
, &len2
);
1982 ret
= krb5_ret_uint32(sp
, &ver2
);
1985 if (len
!= len2
|| ver
!= ver2
) {
1986 ret
= KADM5_LOG_CORRUPT
;
1990 /* Rewind to start of payload and call callback if we have one */
1991 if (krb5_storage_seek(sp
, this_entry
+ LOG_HEADER_SZ
,
1998 ret
= (*func
)(context
, ver
, timestamp
, op
, len
, sp
, ctx
);
2000 /* Callback signals desire to stop by returning -1 */
2006 if ((iter_opts
& kadm_forward
)) {
2009 o
= krb5_storage_seek(sp
, this_entry
+LOG_WRAPPER_SZ
+len
, SEEK_SET
);
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) {
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
2034 ret
= krb5_storage_truncate(sp
, this_entry
);
2036 krb5_storage_seek(sp
, this_entry
, SEEK_SET
) == -1)
2038 krb5_warnx(context
->context
, "Truncating log at partial or "
2040 this_entry
> log_end
? "unconfirmed" : "confirmed");
2042 krb5_storage_free(sp
);
2047 * Go to the second record, which, if we have an uber record, will be
2050 static krb5_storage
*
2051 log_goto_first(kadm5_server_context
*server_context
, int fd
)
2063 sp
= krb5_storage_from_fd(fd
);
2067 if (krb5_storage_seek(sp
, 0, SEEK_SET
) == -1)
2070 ret
= get_header(sp
, LOG_DOPEEK
, &ver
, NULL
, &op
, &len
);
2072 krb5_storage_free(sp
);
2076 if (op
== kadm_nop
&& len
== LOG_UBER_LEN
&& seek_next(sp
) == -1) {
2077 krb5_storage_free(sp
);
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.
2091 kadm5_log_goto_end(kadm5_server_context
*server_context
, int fd
)
2093 krb5_error_code ret
= 0;
2105 sp
= krb5_storage_from_fd(fd
);
2109 if (krb5_storage_seek(sp
, 0, SEEK_SET
) == -1) {
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
);
2118 if (ret
== KADM5_LOG_CORRUPT
)
2123 if (op
== kadm_nop
&& len
== LOG_UBER_LEN
) {
2125 ret
= krb5_ret_uint64(sp
, &off
);
2129 if (krb5_storage_seek(sp
, off
, SEEK_SET
) == -1)
2132 if (off
>= LOG_UBER_SZ
) {
2133 ret
= get_version_prev(sp
, &ver
, NULL
);
2137 /* Invalid offset in uber entry */
2141 /* Old log with no uber entry */
2142 if (krb5_storage_seek(sp
, 0, SEEK_END
) == -1) {
2143 static int warned
= 0;
2146 krb5_warnx(server_context
->context
,
2147 "Old log found; truncate it to upgrade");
2150 ret
= get_version_prev(sp
, &ver
, NULL
);
2156 /* If we can, truncate */
2157 if (server_context
->log_context
.lock_mode
== LOCK_EX
) {
2158 ret
= kadm5_log_reinit(server_context
, 0);
2160 krb5_warn(server_context
->context
, ret
,
2161 "Invalid log; truncating to recover");
2162 if (krb5_storage_seek(sp
, 0, SEEK_END
) == -1)
2167 krb5_warn(server_context
->context
, ret
,
2168 "Invalid log; truncate to recover");
2172 krb5_storage_free(sp
);
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
2185 kadm5_log_previous(krb5_context context
,
2192 krb5_error_code ret
;
2194 uint32_t ver2
, len2
;
2197 oldoff
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
2201 /* This reads the physical version of the uber record */
2202 if (seek_prev(sp
, verp
, lenp
) == -1)
2205 ret
= get_header(sp
, LOG_NOPEEK
, &ver2
, &tstamp
, opp
, &len2
);
2207 (void) krb5_storage_seek(sp
, oldoff
, SEEK_SET
);
2212 if (ver2
!= *verp
|| len2
!= *lenp
)
2218 (void) krb5_storage_seek(sp
, oldoff
, SEEK_SET
);
2219 return KADM5_LOG_CORRUPT
;
2223 * Replay a record from the log
2227 kadm5_log_replay(kadm5_server_context
*context
,
2235 return kadm5_log_replay_create(context
, ver
, len
, sp
);
2237 return kadm5_log_replay_delete(context
, ver
, len
, sp
);
2239 return kadm5_log_replay_rename(context
, ver
, len
, sp
);
2241 return kadm5_log_replay_modify(context
, ver
, len
, sp
);
2243 return kadm5_log_replay_nop(context
, ver
, len
, sp
);
2246 * FIXME This default arm makes it difficult to add new kadm_ops
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
{
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
2285 load_entries_cb(kadm5_server_context
*server_context
,
2293 struct load_entries_data
*entries
= ctx
;
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.
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
;
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
2340 if (krb5_storage_seek(sp
, -LOG_HEADER_SZ
, SEEK_CUR
) == -1)
2344 * We read the header, payload, and trailer into the buffer we have, that
2345 * many bytes before the previous record we read.
2348 bytes
= krb5_storage_read(sp
, entries
->p
- entry_len
, entry_len
);
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.
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
;
2371 unsigned char *base
;
2377 memset(&entries
, 0, sizeof(entries
));
2378 entries
.entries
= 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
);
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)
2396 ret
= krb5_data_alloc(p
, entries
.bytes
);
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
);
2409 (entries
.nentries
|| entries
.p
!= base
|| entries
.first
!= *first
))
2410 ret
= KADM5_LOG_CORRUPT
;
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.
2421 kadm5_log_truncate(kadm5_server_context
*context
, size_t keep
, size_t maxbytes
)
2424 uint32_t first
, last
, last_tstamp
;
2425 time_t now
= time(NULL
);
2433 maxbytes
= get_max_log_size(context
->context
);
2435 if (strcmp(context
->log_context
.log_file
, "/dev/null") == 0)
2438 if (context
->log_context
.read_only
)
2441 /* Get the desired records. */
2442 krb5_data_zero(&entries
);
2443 ret
= load_entries(context
, &entries
, keep
, maxbytes
, &first
, &last
);
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
);
2457 /* Check that entries.length won't overflow off_t */
2458 sz
= LOG_UBER_SZ
+ entries
.length
;
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
);
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
);
2500 krb5_warn(context
->context
, ret
, "Unable to keep entries");
2501 krb5_data_free(&entries
);
2504 ret
= krb5_store_uint32(sp
, 0);
2506 ret
= krb5_store_uint32(sp
, now
);
2508 ret
= krb5_store_uint32(sp
, kadm_nop
); /* end of preamble */
2510 ret
= krb5_store_uint32(sp
, LOG_UBER_LEN
); /* end of header */
2512 ret
= krb5_store_uint64(sp
, LOG_UBER_SZ
);
2514 ret
= krb5_store_uint32(sp
, now
);
2516 ret
= krb5_store_uint32(sp
, last
);
2518 ret
= krb5_store_uint32(sp
, LOG_UBER_LEN
);
2520 ret
= krb5_store_uint32(sp
, 0); /* end of trailer */
2522 bytes
= krb5_storage_write(sp
, entries
.data
, entries
.length
);
2527 ret
= krb5_storage_fsync(sp
);
2528 /* Confirm all the records now */
2530 if (krb5_storage_seek(sp
, LOG_HEADER_SZ
, SEEK_SET
) == -1)
2534 ret
= krb5_store_uint64(sp
, off
);
2535 krb5_data_free(&entries
);
2536 krb5_storage_free(sp
);
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
);
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
);
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
);
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.
2564 truncate_if_needed(kadm5_server_context
*context
)
2566 kadm5_ret_t ret
= 0;
2567 kadm5_log_context
*log_context
= &context
->log_context
;
2571 if (log_context
->log_fd
== -1 || log_context
->read_only
)
2573 if (strcmp(context
->log_context
.log_file
, "/dev/null") == 0)
2576 maxbytes
= get_max_log_size(context
->context
);
2580 if (fstat(log_context
->log_fd
, &st
) == -1)
2582 if (st
.st_size
== (size_t)st
.st_size
&& (size_t)st
.st_size
<= maxbytes
)
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
;
2596 kadm5_log_signal_socket(krb5_context context
)
2600 HEIMDAL_MUTEX_lock(&signal_mutex
);
2601 if (!default_signal
)
2602 ret
= asprintf(&default_signal
, "%s/signal", hdb_db_dir(context
));
2604 default_signal
= NULL
;
2605 HEIMDAL_MUTEX_unlock(&signal_mutex
);
2607 return krb5_config_get_string_default(context
,
2615 #else /* NO_UNIX_SOCKETS */
2617 #define SIGNAL_SOCKET_HOST "127.0.0.1"
2618 #define SIGNAL_SOCKET_PORT "12701"
2621 kadm5_log_signal_socket_info(krb5_context context
,
2623 struct addrinfo
**ret_addrs
)
2625 struct addrinfo hints
;
2626 struct addrinfo
*addrs
= NULL
;
2627 kadm5_ret_t ret
= KADM5_FAILURE
;
2630 memset(&hints
, 0, sizeof(hints
));
2632 hints
.ai_flags
= AI_NUMERICHOST
;
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
,
2644 krb5_set_error_message(context
, KADM5_FAILURE
,
2645 "%s", gai_strerror(wsret
));
2649 if (addrs
== NULL
) {
2650 krb5_set_error_message(context
, KADM5_FAILURE
,
2651 "getaddrinfo() failed to return address list");
2661 freeaddrinfo(addrs
);