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