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