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