xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/kadm5/log.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
1 /*	$NetBSD: log.c,v 1.3 2023/06/19 21:41:44 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.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  */
195 static kadm5_ret_t
get_header(krb5_storage * sp,int peek,uint32_t * verp,uint32_t * tstampp,enum kadm_ops * opp,uint32_t * lenp)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
seek_prev(krb5_storage * sp,uint32_t * verp,uint32_t * lenp)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
seek_next(krb5_storage * sp)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
get_version_prev(krb5_storage * sp,uint32_t * verp,uint32_t * tstampp)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
get_max_log_size(krb5_context context)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 
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  */
489 kadm5_ret_t
kadm5_log_get_version_fd(kadm5_server_context * server_context,int fd,int which,uint32_t * ver,uint32_t * tstamp)490 kadm5_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 */
541 kadm5_ret_t
kadm5_log_get_version(kadm5_server_context * server_context,uint32_t * ver)542 kadm5_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 */
550 kadm5_ret_t
kadm5_log_set_version(kadm5_server_context * context,uint32_t vno)551 kadm5_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  */
562 static kadm5_ret_t
log_open(kadm5_server_context * server_context,int lock_mode)563 log_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  */
623 static kadm5_ret_t
log_init(kadm5_server_context * server_context,int lock_mode)624 log_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 */
681 kadm5_ret_t
kadm5_log_init(kadm5_server_context * server_context)682 kadm5_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 */
688 kadm5_ret_t
kadm5_log_init_nb(kadm5_server_context * server_context)689 kadm5_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 */
695 kadm5_ret_t
kadm5_log_init_nolock(kadm5_server_context * server_context)696 kadm5_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 */
702 kadm5_ret_t
kadm5_log_init_sharedlock(kadm5_server_context * server_context,int lock_flags)703 kadm5_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  */
711 kadm5_ret_t
kadm5_log_reinit(kadm5_server_context * server_context,uint32_t vno)712 kadm5_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. */
737 kadm5_ret_t
kadm5_log_end(kadm5_server_context * server_context)738 kadm5_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  */
765 static kadm5_ret_t
kadm5_log_preamble(kadm5_server_context * context,krb5_storage * sp,enum kadm_ops op,uint32_t vno)766 kadm5_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 */
789 static kadm5_ret_t
kadm5_log_postamble(kadm5_log_context * context,krb5_storage * sp,uint32_t vno)790 kadm5_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  */
809 void
kadm5_log_signal_master(kadm5_server_context * context)810 kadm5_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 
837 static kadm5_ret_t
kadm5_log_flush(kadm5_server_context * context,krb5_storage * sp)838 kadm5_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  */
936 kadm5_ret_t
kadm5_log_create(kadm5_server_context * context,hdb_entry * entry)937 kadm5_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  */
999 static kadm5_ret_t
kadm5_log_replay_create(kadm5_server_context * context,uint32_t ver,uint32_t len,krb5_storage * sp)1000 kadm5_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  */
1033 kadm5_ret_t
kadm5_log_delete(kadm5_server_context * context,krb5_principal princ)1034 kadm5_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  */
1110 static kadm5_ret_t
kadm5_log_replay_delete(kadm5_server_context * context,uint32_t ver,uint32_t len,krb5_storage * sp)1111 kadm5_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 
1129 static 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  */
1136 kadm5_ret_t
kadm5_log_rename(kadm5_server_context * context,krb5_principal source,hdb_entry * entry)1137 kadm5_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 
1251 static kadm5_ret_t
kadm5_log_replay_rename(kadm5_server_context * context,uint32_t ver,uint32_t len,krb5_storage * sp)1252 kadm5_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  */
1303 kadm5_ret_t
kadm5_log_modify(kadm5_server_context * context,hdb_entry * entry,uint32_t mask)1304 kadm5_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  */
1373 static kadm5_ret_t
kadm5_log_replay_modify(kadm5_server_context * context,uint32_t ver,uint32_t len,krb5_storage * sp)1374 kadm5_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  */
1584 static kadm5_ret_t
log_update_uber(kadm5_server_context * context,off_t off)1585 log_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 
1659 out:
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  */
1674 kadm5_ret_t
kadm5_log_nop(kadm5_server_context * context,enum kadm_nop_type nop_type)1675 kadm5_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 
1733 out:
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  */
1747 static kadm5_ret_t
kadm5_log_replay_nop(kadm5_server_context * context,uint32_t ver,uint32_t len,krb5_storage * sp)1748 kadm5_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 
1756 struct 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  */
1766 static kadm5_ret_t
recover_replay(kadm5_server_context * context,uint32_t ver,time_t timestamp,enum kadm_ops op,uint32_t len,krb5_storage * sp,void * ctx)1767 recover_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 
1819 kadm5_ret_t
kadm5_log_recover(kadm5_server_context * context,enum kadm_recover_mode mode)1820 kadm5_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  */
1852 kadm5_ret_t
kadm5_log_foreach(kadm5_server_context * context,enum kadm_iter_opts iter_opts,off_t * off_lastp,kadm5_ret_t (* func)(kadm5_server_context * server_context,uint32_t ver,time_t timestamp,enum kadm_ops op,uint32_t len,krb5_storage * sp,void * ctx),void * ctx)1853 kadm5_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  */
2057 kadm5_ret_t
kadm5_log_goto_first(kadm5_server_context * server_context,krb5_storage * sp)2058 kadm5_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  */
2078 kadm5_ret_t
kadm5_log_goto_end(kadm5_server_context * server_context,krb5_storage * sp)2079 kadm5_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 
2131 truncate:
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  */
2155 kadm5_ret_t
kadm5_log_next(krb5_context context,krb5_storage * sp,uint32_t * verp,time_t * tstampp,enum kadm_ops * opp,uint32_t * lenp)2156 kadm5_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  */
2206 kadm5_ret_t
kadm5_log_previous(krb5_context context,krb5_storage * sp,uint32_t * verp,time_t * tstampp,enum kadm_ops * opp,uint32_t * lenp)2207 kadm5_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 
2239 log_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 
2248 kadm5_ret_t
kadm5_log_replay(kadm5_server_context * context,enum kadm_ops op,uint32_t ver,uint32_t len,krb5_storage * sp)2249 kadm5_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 
2278 struct 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  */
2306 static kadm5_ret_t
load_entries_cb(kadm5_server_context * server_context,uint32_t ver,time_t timestamp,enum kadm_ops op,uint32_t len,krb5_storage * sp,void * ctx)2307 load_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  */
2386 static kadm5_ret_t
load_entries(kadm5_server_context * context,krb5_data * p,size_t maxentries,size_t maxbytes,uint32_t * first,uint32_t * last)2387 load_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  */
2442 kadm5_ret_t
kadm5_log_truncate(kadm5_server_context * context,size_t keep,size_t maxbytes)2443 kadm5_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  */
2589 static kadm5_ret_t
truncate_if_needed(kadm5_server_context * context)2590 truncate_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 
2618 static char *default_signal = NULL;
2619 static HEIMDAL_MUTEX signal_mutex = HEIMDAL_MUTEX_INITIALIZER;
2620 
2621 const char *
kadm5_log_signal_socket(krb5_context context)2622 kadm5_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 
2646 kadm5_ret_t
kadm5_log_signal_socket_info(krb5_context context,int server_end,struct addrinfo ** ret_addrs)2647 kadm5_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