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