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