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 ×tamp, &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