1 /* $NetBSD: ipropd_master.c,v 1.3 2023/06/19 21:41:44 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1997 - 2008 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 "iprop.h" 37 #include <krb5/rtbl.h> 38 39 static krb5_log_facility *log_facility; 40 41 static int verbose; 42 43 const char *slave_stats_file; 44 const char *slave_time_missing = "2 min"; 45 const char *slave_time_gone = "5 min"; 46 47 static int time_before_missing; 48 static int time_before_gone; 49 50 const char *master_hostname; 51 52 static krb5_socket_t 53 make_signal_socket (krb5_context context) 54 { 55 #ifndef NO_UNIX_SOCKETS 56 struct sockaddr_un addr; 57 const char *fn; 58 krb5_socket_t fd; 59 60 fn = kadm5_log_signal_socket(context); 61 62 fd = socket (AF_UNIX, SOCK_DGRAM, 0); 63 if (fd < 0) 64 krb5_err (context, 1, errno, "socket AF_UNIX"); 65 memset (&addr, 0, sizeof(addr)); 66 addr.sun_family = AF_UNIX; 67 strlcpy (addr.sun_path, fn, sizeof(addr.sun_path)); 68 unlink (addr.sun_path); 69 if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 70 krb5_err (context, 1, errno, "bind %s", addr.sun_path); 71 return fd; 72 #else 73 struct addrinfo *ai = NULL; 74 krb5_socket_t fd; 75 76 kadm5_log_signal_socket_info(context, 1, &ai); 77 78 fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 79 if (rk_IS_BAD_SOCKET(fd)) 80 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family); 81 82 if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) )) 83 krb5_err (context, 1, rk_SOCK_ERRNO, "bind"); 84 return fd; 85 #endif 86 } 87 88 static krb5_socket_t 89 make_listen_socket (krb5_context context, const char *port_str) 90 { 91 krb5_socket_t fd; 92 int one = 1; 93 struct sockaddr_in addr; 94 95 fd = socket (AF_INET, SOCK_STREAM, 0); 96 if (rk_IS_BAD_SOCKET(fd)) 97 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET"); 98 setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); 99 memset (&addr, 0, sizeof(addr)); 100 addr.sin_family = AF_INET; 101 102 if (port_str) { 103 addr.sin_port = krb5_getportbyname (context, 104 port_str, "tcp", 105 0); 106 if (addr.sin_port == 0) { 107 char *ptr; 108 long port; 109 110 port = strtol (port_str, &ptr, 10); 111 if (port == 0 && ptr == port_str) 112 krb5_errx (context, 1, "bad port `%s'", port_str); 113 addr.sin_port = htons(port); 114 } 115 } else { 116 addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE, 117 "tcp", IPROP_PORT); 118 } 119 if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 120 krb5_err (context, 1, errno, "bind"); 121 if (listen(fd, SOMAXCONN) < 0) 122 krb5_err (context, 1, errno, "listen"); 123 return fd; 124 } 125 126 127 struct slave { 128 krb5_socket_t fd; 129 struct sockaddr_in addr; 130 char *name; 131 krb5_auth_context ac; 132 uint32_t version; 133 uint32_t version_tstamp; 134 uint32_t version_ack; 135 time_t seen; 136 unsigned long flags; 137 #define SLAVE_F_DEAD 0x1 138 #define SLAVE_F_AYT 0x2 139 #define SLAVE_F_READY 0x4 140 /* 141 * We'll use non-blocking I/O so no slave can hold us back. 142 * 143 * We call the state left over from a partial write a "tail". 144 * 145 * The krb5_data holding an KRB-PRIV will be the write buffer. 146 */ 147 struct { 148 /* Every message we send is a KRB-PRIV with a 4-byte length prefixed */ 149 uint8_t header_buf[4]; 150 krb5_data header; 151 krb5_data packet; 152 size_t packet_off; 153 /* For send_complete() we need an sp as part of the tail */ 154 krb5_storage *dump; 155 uint32_t vno; 156 } tail; 157 struct { 158 uint8_t header_buf[4]; 159 krb5_data packet; 160 size_t offset; 161 int hlen; 162 } input; 163 /* 164 * Continuation for fair diff sending we send N entries at a time. 165 */ 166 struct { 167 off_t off_next_version; /* offset in log of next diff */ 168 uint32_t initial_version; /* at time of previous diff */ 169 uint32_t initial_tstamp; /* at time of previous diff */ 170 uint32_t last_version_sent; 171 int more; /* need to send more diffs */ 172 } next_diff; 173 struct slave *next; 174 }; 175 176 typedef struct slave slave; 177 178 static int 179 check_acl (krb5_context context, const char *name) 180 { 181 const char *fn; 182 FILE *fp; 183 char buf[256]; 184 int ret = 1; 185 char *slavefile = NULL; 186 187 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1 188 || slavefile == NULL) 189 errx(1, "out of memory"); 190 191 fn = krb5_config_get_string_default(context, 192 NULL, 193 slavefile, 194 "kdc", 195 "iprop-acl", 196 NULL); 197 198 fp = fopen (fn, "r"); 199 free(slavefile); 200 if (fp == NULL) 201 return 1; 202 while (fgets(buf, sizeof(buf), fp) != NULL) { 203 buf[strcspn(buf, "\r\n")] = '\0'; 204 if (strcmp (buf, name) == 0) { 205 ret = 0; 206 break; 207 } 208 } 209 fclose (fp); 210 return ret; 211 } 212 213 static void 214 slave_seen(slave *s) 215 { 216 s->flags &= ~SLAVE_F_AYT; 217 s->seen = time(NULL); 218 } 219 220 static int 221 slave_missing_p (slave *s) 222 { 223 if (time(NULL) > s->seen + time_before_missing) 224 return 1; 225 return 0; 226 } 227 228 static int 229 slave_gone_p (slave *s) 230 { 231 if (time(NULL) > s->seen + time_before_gone) 232 return 1; 233 return 0; 234 } 235 236 static void 237 slave_dead(krb5_context context, slave *s) 238 { 239 krb5_warnx(context, "slave %s dead", s->name); 240 241 if (!rk_IS_BAD_SOCKET(s->fd)) { 242 rk_closesocket (s->fd); 243 s->fd = rk_INVALID_SOCKET; 244 } 245 s->flags |= SLAVE_F_DEAD; 246 slave_seen(s); 247 } 248 249 static void 250 remove_slave (krb5_context context, slave *s, slave **root) 251 { 252 slave **p; 253 254 if (!rk_IS_BAD_SOCKET(s->fd)) 255 rk_closesocket (s->fd); 256 if (s->name) 257 free (s->name); 258 if (s->ac) 259 krb5_auth_con_free (context, s->ac); 260 261 /* Free any pending input/output state */ 262 krb5_data_free(&s->input.packet); 263 krb5_data_free(&s->tail.packet); 264 krb5_storage_free(s->tail.dump); 265 266 for (p = root; *p; p = &(*p)->next) 267 if (*p == s) { 268 *p = s->next; 269 break; 270 } 271 free (s); 272 } 273 274 static void 275 add_slave (krb5_context context, krb5_keytab keytab, slave **root, 276 krb5_socket_t fd) 277 { 278 krb5_principal server; 279 krb5_error_code ret; 280 slave *s; 281 socklen_t addr_len; 282 krb5_ticket *ticket = NULL; 283 char hostname[128]; 284 285 s = calloc(1, sizeof(*s)); 286 if (s == NULL) { 287 krb5_warnx (context, "add_slave: no memory"); 288 return; 289 } 290 s->name = NULL; 291 s->ac = NULL; 292 s->input.packet.data = NULL; 293 s->tail.header.data = NULL; 294 s->tail.packet.data = NULL; 295 s->tail.dump = NULL; 296 297 addr_len = sizeof(s->addr); 298 s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len); 299 if (rk_IS_BAD_SOCKET(s->fd)) { 300 krb5_warn (context, rk_SOCK_ERRNO, "accept"); 301 goto error; 302 } 303 304 if (master_hostname) 305 strlcpy(hostname, master_hostname, sizeof(hostname)); 306 else 307 gethostname(hostname, sizeof(hostname)); 308 309 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME, 310 KRB5_NT_SRV_HST, &server); 311 if (ret) { 312 krb5_warn (context, ret, "krb5_sname_to_principal"); 313 goto error; 314 } 315 316 ret = krb5_recvauth (context, &s->ac, &s->fd, 317 IPROP_VERSION, server, 0, keytab, &ticket); 318 319 /* 320 * We'll be doing non-blocking I/O only after authentication. We don't 321 * want to get stuck talking to any one slave. 322 * 323 * If we get a partial write, we'll finish writing when the socket becomes 324 * writable. 325 * 326 * Partial reads will be treated as EOF, causing the slave to be marked 327 * dead. 328 * 329 * To do non-blocking I/O for authentication we'll have to implement our 330 * own krb5_recvauth(). 331 */ 332 socket_set_nonblocking(s->fd, 1); 333 334 /* 335 * We write message lengths separately from the payload, and may do 336 * back-to-back small writes when flushing pending input and then a new 337 * update. Avoid Nagle delays. 338 */ 339 #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) 340 { 341 int nodelay = 1; 342 (void) setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY, 343 (void *)&nodelay, sizeof(nodelay)); 344 } 345 #endif 346 347 krb5_free_principal (context, server); 348 if (ret) { 349 krb5_warn (context, ret, "krb5_recvauth"); 350 goto error; 351 } 352 ret = krb5_unparse_name (context, ticket->client, &s->name); 353 krb5_free_ticket (context, ticket); 354 if (ret) { 355 krb5_warn (context, ret, "krb5_unparse_name"); 356 goto error; 357 } 358 if (check_acl (context, s->name)) { 359 krb5_warnx (context, "%s not in acl", s->name); 360 goto error; 361 } 362 363 { 364 slave *l = *root; 365 366 while (l) { 367 if (strcmp(l->name, s->name) == 0) 368 break; 369 l = l->next; 370 } 371 if (l) { 372 if (l->flags & SLAVE_F_DEAD) { 373 remove_slave(context, l, root); 374 } else { 375 krb5_warnx (context, "second connection from %s", s->name); 376 goto error; 377 } 378 } 379 } 380 381 krb5_warnx (context, "connection from %s", s->name); 382 383 s->version = 0; 384 s->version_ack = 0; 385 s->flags = 0; 386 slave_seen(s); 387 s->next = *root; 388 *root = s; 389 return; 390 error: 391 remove_slave(context, s, root); 392 } 393 394 static int 395 dump_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v) 396 { 397 krb5_error_code ret; 398 krb5_storage *dump = (krb5_storage *)v; 399 krb5_storage *sp; 400 krb5_data data; 401 402 ret = hdb_entry2value (context, &entry->entry, &data); 403 if (ret) 404 return ret; 405 ret = krb5_data_realloc (&data, data.length + 4); 406 if (ret) 407 goto done; 408 memmove ((char *)data.data + 4, data.data, data.length - 4); 409 sp = krb5_storage_from_data(&data); 410 if (sp == NULL) { 411 ret = ENOMEM; 412 goto done; 413 } 414 ret = krb5_store_uint32(sp, ONE_PRINC); 415 krb5_storage_free(sp); 416 417 if (ret == 0) 418 ret = krb5_store_data(dump, data); 419 420 done: 421 krb5_data_free (&data); 422 return ret; 423 } 424 425 static int 426 write_dump (krb5_context context, krb5_storage *dump, 427 const char *database, uint32_t current_version) 428 { 429 krb5_error_code ret; 430 krb5_storage *sp; 431 HDB *db; 432 krb5_data data; 433 char buf[8]; 434 435 /* we assume that the caller has obtained an exclusive lock */ 436 437 ret = krb5_storage_truncate(dump, 0); 438 if (ret) 439 return ret; 440 441 if (krb5_storage_seek(dump, 0, SEEK_SET) != 0) 442 return errno; 443 444 /* 445 * First we store zero as the HDB version, this will indicate to a 446 * later reader that the dumpfile is invalid. We later write the 447 * correct version in the file after we have written all of the 448 * messages. A dump with a zero version will not be considered 449 * to be valid. 450 */ 451 452 ret = krb5_store_uint32(dump, 0); 453 if (ret) 454 return ret; 455 456 ret = hdb_create (context, &db, database); 457 if (ret) 458 krb5_err (context, IPROPD_RESTART, ret, "hdb_create: %s", database); 459 ret = db->hdb_open (context, db, O_RDONLY, 0); 460 if (ret) 461 krb5_err (context, IPROPD_RESTART, ret, "db->open"); 462 463 sp = krb5_storage_from_mem (buf, 4); 464 if (sp == NULL) 465 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem"); 466 krb5_store_uint32 (sp, TELL_YOU_EVERYTHING); 467 krb5_storage_free (sp); 468 469 data.data = buf; 470 data.length = 4; 471 472 ret = krb5_store_data(dump, data); 473 if (ret) { 474 krb5_warn (context, ret, "write_dump"); 475 return ret; 476 } 477 478 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, dump_one, dump); 479 if (ret) { 480 krb5_warn (context, ret, "write_dump: hdb_foreach"); 481 return ret; 482 } 483 484 (*db->hdb_close)(context, db); 485 (*db->hdb_destroy)(context, db); 486 487 sp = krb5_storage_from_mem (buf, 8); 488 if (sp == NULL) 489 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem"); 490 ret = krb5_store_uint32(sp, NOW_YOU_HAVE); 491 if (ret == 0) 492 krb5_store_uint32(sp, current_version); 493 krb5_storage_free (sp); 494 495 data.length = 8; 496 497 if (ret == 0) 498 ret = krb5_store_data(dump, data); 499 500 /* 501 * We must ensure that the entire valid dump is written to disk 502 * before we write the current version at the front thus making 503 * it a valid dump file. If we crash around here, this can be 504 * important upon reboot. 505 */ 506 507 if (ret == 0) 508 ret = krb5_storage_fsync(dump); 509 510 if (ret == 0 && krb5_storage_seek(dump, 0, SEEK_SET) == -1) 511 ret = errno; 512 513 /* Write current version at the front making the dump valid */ 514 515 if (ret == 0) 516 ret = krb5_store_uint32(dump, current_version); 517 518 /* 519 * We don't need to fsync(2) after the real version is written as 520 * it is not a disaster if it doesn't make it to disk if we crash. 521 * After all, we'll just create a new dumpfile. 522 */ 523 524 if (ret == 0) 525 krb5_warnx(context, "wrote new dumpfile (version %u)", 526 current_version); 527 else 528 krb5_warn(context, ret, "failed to write new dumpfile (version %u)", 529 current_version); 530 531 return ret; 532 } 533 534 static int 535 mk_priv_tail(krb5_context context, slave *s, krb5_data *data) 536 { 537 uint32_t len; 538 int ret; 539 540 ret = krb5_mk_priv(context, s->ac, data, &s->tail.packet, NULL); 541 if (ret) 542 return ret; 543 544 len = s->tail.packet.length; 545 _krb5_put_int(s->tail.header_buf, len, sizeof(s->tail.header_buf)); 546 s->tail.header.length = sizeof(s->tail.header_buf); 547 s->tail.header.data = s->tail.header_buf; 548 return 0; 549 } 550 551 static int 552 have_tail(slave *s) 553 { 554 return s->tail.header.length || s->tail.packet.length || s->tail.dump; 555 } 556 557 static int 558 more_diffs(slave *s) 559 { 560 return s->next_diff.more; 561 } 562 563 #define SEND_COMPLETE_MAX_RECORDS 50 564 #define SEND_DIFFS_MAX_RECORDS 50 565 566 static int 567 send_tail(krb5_context context, slave *s) 568 { 569 krb5_data data; 570 ssize_t bytes = 0; 571 size_t rem = 0; 572 size_t n; 573 int ret; 574 575 if (! have_tail(s)) 576 return 0; 577 578 /* 579 * For the case where we're continuing a send_complete() send up to 580 * SEND_COMPLETE_MAX_RECORDS records now, and the rest asynchronously 581 * later. This ensures that sending a complete dump to a slow-to-drain 582 * client does not prevent others from getting serviced. 583 */ 584 for (n = 0; n < SEND_COMPLETE_MAX_RECORDS; n++) { 585 if (! have_tail(s)) 586 return 0; 587 588 if (s->tail.header.length) { 589 bytes = krb5_net_write(context, &s->fd, 590 s->tail.header.data, 591 s->tail.header.length); 592 if (bytes < 0) 593 goto err; 594 595 s->tail.header.length -= bytes; 596 s->tail.header.data = (char *)s->tail.header.data + bytes; 597 rem = s->tail.header.length; 598 if (rem) 599 goto ewouldblock; 600 } 601 602 if (s->tail.packet.length) { 603 bytes = krb5_net_write(context, &s->fd, 604 (char *)s->tail.packet.data + s->tail.packet_off, 605 s->tail.packet.length - s->tail.packet_off); 606 if (bytes < 0) 607 goto err; 608 s->tail.packet_off += bytes; 609 if (bytes) 610 slave_seen(s); 611 rem = s->tail.packet.length - s->tail.packet_off; 612 if (rem) 613 goto ewouldblock; 614 615 krb5_data_free(&s->tail.packet); 616 s->tail.packet_off = 0; 617 } 618 619 if (s->tail.dump == NULL) 620 return 0; 621 622 /* 623 * We're in the middle of a send_complete() that was interrupted by 624 * EWOULDBLOCK. Continue the sending of the dump. 625 */ 626 ret = krb5_ret_data(s->tail.dump, &data); 627 if (ret == HEIM_ERR_EOF) { 628 krb5_storage_free(s->tail.dump); 629 s->tail.dump = NULL; 630 s->version = s->tail.vno; 631 return 0; 632 } 633 634 if (ret) { 635 krb5_warn(context, ret, "failed to read entry from dump!"); 636 } else { 637 ret = mk_priv_tail(context, s, &data); 638 krb5_data_free(&data); 639 if (ret == 0) 640 continue; 641 krb5_warn(context, ret, "failed to make and send a KRB-PRIV to %s", 642 s->name); 643 } 644 645 slave_dead(context, s); 646 return ret; 647 } 648 649 if (ret == 0 && s->tail.dump != NULL) 650 return EWOULDBLOCK; 651 652 err: 653 if (errno != EAGAIN && errno != EWOULDBLOCK) { 654 krb5_warn(context, ret = errno, 655 "error sending diffs to now-dead slave %s", s->name); 656 slave_dead(context, s); 657 return ret; 658 } 659 660 ewouldblock: 661 if (verbose) 662 krb5_warnx(context, "would block writing %llu bytes to slave %s", 663 (unsigned long long)rem, s->name); 664 return EWOULDBLOCK; 665 } 666 667 static int 668 send_complete(krb5_context context, slave *s, const char *database, 669 uint32_t current_version, uint32_t oldest_version, 670 uint32_t initial_log_tstamp) 671 { 672 krb5_error_code ret; 673 krb5_storage *dump = NULL; 674 uint32_t vno = 0; 675 int fd = -1; 676 struct stat st; 677 char *dfn; 678 679 ret = asprintf(&dfn, "%s/ipropd.dumpfile", hdb_db_dir(context)); 680 if (ret == -1 || !dfn) { 681 krb5_warn(context, ENOMEM, "Cannot allocate memory"); 682 return ENOMEM; 683 } 684 685 fd = open(dfn, O_CREAT|O_RDWR, 0600); 686 if (fd == -1) { 687 ret = errno; 688 krb5_warn(context, ret, "Cannot open/create iprop dumpfile %s", dfn); 689 free(dfn); 690 return ret; 691 } 692 free(dfn); 693 694 dump = krb5_storage_from_fd(fd); 695 if (!dump) { 696 ret = errno; 697 krb5_warn(context, ret, "krb5_storage_from_fd"); 698 goto done; 699 } 700 701 for (;;) { 702 ret = flock(fd, LOCK_SH); 703 if (ret == -1) { 704 ret = errno; 705 krb5_warn(context, ret, "flock(fd, LOCK_SH)"); 706 goto done; 707 } 708 709 if (krb5_storage_seek(dump, 0, SEEK_SET) == (off_t)-1) { 710 ret = errno; 711 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)"); 712 goto done; 713 } 714 715 vno = 0; 716 ret = krb5_ret_uint32(dump, &vno); 717 if (ret && ret != HEIM_ERR_EOF) { 718 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)"); 719 goto done; 720 } 721 722 if (fstat(fd, &st) == -1) { 723 ret = errno; 724 krb5_warn(context, ret, "send_complete: could not stat dump file"); 725 goto done; 726 } 727 728 /* 729 * If the current dump has an appropriate version, then we can 730 * break out of the loop and send the file below. 731 */ 732 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp && 733 vno >= oldest_version && vno <= current_version) 734 break; 735 736 if (verbose) 737 krb5_warnx(context, "send_complete: dumping HDB"); 738 739 /* 740 * Otherwise, we may need to write a new dump file. We 741 * obtain an exclusive lock on the fd. Because this is 742 * not guaranteed to be an upgrade of our existing shared 743 * lock, someone else may have written a new dumpfile while 744 * we were waiting and so we must first check the vno of 745 * the dump to see if that happened. If it did, we need 746 * to go back to the top of the loop so that we can downgrade 747 * our lock to a shared one. 748 */ 749 750 ret = flock(fd, LOCK_EX); 751 if (ret == -1) { 752 ret = errno; 753 krb5_warn(context, ret, "flock(fd, LOCK_EX)"); 754 goto done; 755 } 756 757 ret = krb5_storage_seek(dump, 0, SEEK_SET); 758 if (ret == -1) { 759 ret = errno; 760 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)"); 761 goto done; 762 } 763 764 vno = 0; 765 ret = krb5_ret_uint32(dump, &vno); 766 if (ret && ret != HEIM_ERR_EOF) { 767 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)"); 768 goto done; 769 } 770 771 if (fstat(fd, &st) == -1) { 772 ret = errno; 773 krb5_warn(context, ret, "send_complete: could not stat dump file"); 774 goto done; 775 } 776 777 /* check if someone wrote a better version for us */ 778 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp && 779 vno >= oldest_version && vno <= current_version) 780 continue; 781 782 /* Now, we know that we must write a new dump file. */ 783 784 ret = write_dump(context, dump, database, current_version); 785 if (ret) 786 goto done; 787 788 /* 789 * And we must continue to the top of the loop so that we can 790 * downgrade to a shared lock. 791 */ 792 } 793 794 /* 795 * Leaving the above loop, dump should have a ptr right after the initial 796 * 4 byte DB version number and we should have a shared lock on the file 797 * (which we may have just created), so we are reading to start sending 798 * the data down the wire. 799 * 800 * Note: (krb5_storage_from_fd() dup()'s the fd) 801 */ 802 803 s->tail.dump = dump; 804 s->tail.vno = vno; 805 dump = NULL; 806 ret = send_tail(context, s); 807 808 done: 809 if (fd != -1) 810 close(fd); 811 if (dump) 812 krb5_storage_free(dump); 813 return ret; 814 } 815 816 static int 817 send_are_you_there (krb5_context context, slave *s) 818 { 819 krb5_storage *sp; 820 krb5_data data; 821 char buf[4]; 822 int ret; 823 824 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT)) 825 return 0; 826 827 /* 828 * Write any remainder of previous write, if we can. If we'd block we'll 829 * return EWOULDBLOCK. 830 */ 831 ret = send_tail(context, s); 832 if (ret) 833 return ret; 834 835 krb5_warnx(context, "slave %s missing, sending AYT", s->name); 836 837 s->flags |= SLAVE_F_AYT; 838 839 data.data = buf; 840 data.length = 4; 841 842 sp = krb5_storage_from_mem (buf, 4); 843 if (sp == NULL) { 844 krb5_warnx (context, "are_you_there: krb5_data_alloc"); 845 slave_dead(context, s); 846 return ENOMEM; 847 } 848 ret = krb5_store_uint32(sp, ARE_YOU_THERE); 849 krb5_storage_free (sp); 850 851 if (ret == 0) 852 ret = mk_priv_tail(context, s, &data); 853 if (ret == 0) 854 ret = send_tail(context, s); 855 if (ret && ret != EWOULDBLOCK) { 856 krb5_warn(context, ret, "are_you_there"); 857 slave_dead(context, s); 858 } 859 return ret; 860 } 861 862 static int 863 diffready(krb5_context context, slave *s) 864 { 865 /* 866 * Don't send any diffs until slave has sent an I_HAVE telling us the 867 * initial version number! 868 */ 869 if ((s->flags & SLAVE_F_READY) == 0) 870 return 0; 871 872 if (s->flags & SLAVE_F_DEAD) { 873 if (verbose) 874 krb5_warnx(context, "not sending diffs to dead slave %s", s->name); 875 return 0; 876 } 877 878 /* Write any remainder of previous write, if we can. */ 879 if (send_tail(context, s) != 0) 880 return 0; 881 882 return 1; 883 } 884 885 static int 886 nodiffs(krb5_context context, slave *s, uint32_t current_version) 887 { 888 krb5_storage *sp; 889 krb5_data data; 890 int ret; 891 892 if (s->version < current_version) 893 return 0; 894 895 /* 896 * If we had sent a partial diff, and now they're caught up, then there's 897 * no more. 898 */ 899 s->next_diff.more = 0; 900 901 if (verbose) 902 krb5_warnx(context, "slave %s version %ld already sent", s->name, 903 (long)s->version); 904 sp = krb5_storage_emem(); 905 if (sp == NULL) 906 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_mem"); 907 908 ret = krb5_store_uint32(sp, YOU_HAVE_LAST_VERSION); 909 if (ret == 0) { 910 krb5_data_zero(&data); 911 ret = krb5_storage_to_data(sp, &data); 912 } 913 krb5_storage_free(sp); 914 if (ret == 0) { 915 ret = mk_priv_tail(context, s, &data); 916 krb5_data_free(&data); 917 } 918 if (ret == 0) 919 send_tail(context, s); 920 921 return 1; 922 } 923 924 /* 925 * Lock the log and return initial version and timestamp 926 */ 927 static int 928 get_first(kadm5_server_context *server_context, int log_fd, 929 uint32_t *initial_verp, uint32_t *initial_timep) 930 { 931 krb5_context context = server_context->context; 932 int ret; 933 934 /* 935 * We don't want to perform tight retry loops on log access errors, so on 936 * error mark the slave dead. The slave reconnect after a delay... 937 */ 938 if (flock(log_fd, LOCK_SH) == -1) { 939 krb5_warn(context, errno, "could not obtain shared lock on log file"); 940 return -1; 941 } 942 943 ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST, 944 initial_verp, initial_timep); 945 if (ret != 0) { 946 flock(log_fd, LOCK_UN); 947 krb5_warnx(context, "could not read initial log entry"); 948 return -1; 949 } 950 951 return 0; 952 } 953 954 /*- 955 * Find the left end of the diffs in the log we want to send. 956 * 957 * - On success, return a positive offset to the first new entry, retaining 958 * a read lock on the log file. 959 * - On error, return a negative offset, with the lock released. 960 * - If we simply find no successor entry in the log, return zero 961 * with the lock released, which indicates that fallback to send_complete() 962 * is needed. 963 */ 964 static off_t 965 get_left(kadm5_server_context *server_context, slave *s, krb5_storage *sp, 966 int log_fd, uint32_t current_version, 967 uint32_t *initial_verp, uint32_t *initial_timep) 968 { 969 krb5_context context = server_context->context; 970 off_t pos; 971 off_t left; 972 int ret; 973 974 for (;;) { 975 uint32_t ver = s->version; 976 977 /* This acquires a read lock on success */ 978 ret = get_first(server_context, log_fd, 979 initial_verp, initial_timep); 980 if (ret != 0) 981 return -1; 982 983 /* When the slave version is out of range, send the whole database. */ 984 if (ver == 0 || ver < *initial_verp || ver > current_version) { 985 flock(log_fd, LOCK_UN); 986 return 0; 987 } 988 989 /* Avoid seeking past the last committed record */ 990 if (kadm5_log_goto_end(server_context, sp) != 0 || 991 (pos = krb5_storage_seek(sp, 0, SEEK_CUR)) < 0) 992 goto err; 993 994 /* 995 * First try to see if we can find it quickly by seeking to the right 996 * end of the previous diff sent. 997 */ 998 if (s->next_diff.last_version_sent > 0 && 999 s->next_diff.off_next_version > 0 && 1000 s->next_diff.off_next_version < pos && 1001 s->next_diff.initial_version == *initial_verp && 1002 s->next_diff.initial_tstamp == *initial_timep) { 1003 /* 1004 * Sanity check that the left version matches what we wanted, the 1005 * log may have been truncated since. 1006 */ 1007 left = s->next_diff.off_next_version; 1008 if (krb5_storage_seek(sp, left, SEEK_SET) != left) 1009 goto err; 1010 if (kadm5_log_next(context, sp, &ver, NULL, NULL, NULL) == 0 && 1011 ver == s->next_diff.last_version_sent + 1) 1012 return left; 1013 } 1014 1015 if (krb5_storage_seek(sp, pos, SEEK_SET) != pos) 1016 goto err; 1017 1018 /* 1019 * Drop the lock and try to find the left entry by seeking backward 1020 * from the end of the end of the log. If we succeed, re-acquire the 1021 * lock, update "next_diff", and retry the fast-path. 1022 */ 1023 flock(log_fd, LOCK_UN); 1024 1025 /* Slow path: seek backwards, entry by entry, from the end */ 1026 for (;;) { 1027 enum kadm_ops op; 1028 uint32_t len; 1029 1030 ret = kadm5_log_previous(context, sp, &ver, NULL, &op, &len); 1031 if (ret) 1032 return -1; 1033 left = krb5_storage_seek(sp, -16, SEEK_CUR); 1034 if (left < 0) 1035 return left; 1036 if (ver == s->version + 1) 1037 break; 1038 1039 /* 1040 * We don't expect to reach the slave's version, unless the log 1041 * has been modified after we released the lock. 1042 */ 1043 if (ver == s->version) { 1044 krb5_warnx(context, "iprop log truncated while sending diffs " 1045 "to slave?? ver = %lu", (unsigned long)ver); 1046 return -1; 1047 } 1048 1049 /* If we've reached the uber record, send the complete database */ 1050 if (left == 0 || (ver == 0 && op == kadm_nop)) 1051 return 0; 1052 } 1053 assert(ver == s->version + 1); 1054 1055 /* Set up the fast-path pre-conditions */ 1056 s->next_diff.last_version_sent = s->version; 1057 s->next_diff.off_next_version = left; 1058 s->next_diff.initial_version = *initial_verp; 1059 s->next_diff.initial_tstamp = *initial_timep; 1060 1061 /* 1062 * If we loop then we're hoping to hit the fast path so we can return a 1063 * non-zero, positive left offset with the lock held. 1064 * 1065 * We just updated the fast path pre-conditions, so unless a log 1066 * truncation event happens between the point where we dropped the lock 1067 * and the point where we rearcuire it above, we will hit the fast 1068 * path. 1069 */ 1070 } 1071 1072 return left; 1073 1074 err: 1075 flock(log_fd, LOCK_UN); 1076 return -1; 1077 } 1078 1079 static off_t 1080 get_right(krb5_context context, int log_fd, krb5_storage *sp, 1081 int lastver, slave *s, off_t left, uint32_t *verp) 1082 { 1083 int ret = 0; 1084 int i = 0; 1085 uint32_t ver = s->version; 1086 off_t right = krb5_storage_seek(sp, left, SEEK_SET); 1087 1088 if (right <= 0) { 1089 flock(log_fd, LOCK_UN); 1090 return -1; 1091 } 1092 1093 /* The "lastver" bound should preclude us reaching EOF */ 1094 for (; ret == 0 && i < SEND_DIFFS_MAX_RECORDS && ver < lastver; ++i) { 1095 uint32_t logver; 1096 1097 ret = kadm5_log_next(context, sp, &logver, NULL, NULL, NULL); 1098 if (logver != ++ver) 1099 ret = KADM5_LOG_CORRUPT; 1100 } 1101 1102 if (ret == 0) 1103 right = krb5_storage_seek(sp, 0, SEEK_CUR); 1104 else 1105 right = -1; 1106 if (right <= 0) { 1107 flock(log_fd, LOCK_UN); 1108 return -1; 1109 } 1110 *verp = ver; 1111 return right; 1112 } 1113 1114 static void 1115 send_diffs(kadm5_server_context *server_context, slave *s, int log_fd, 1116 const char *database, uint32_t current_version) 1117 { 1118 krb5_context context = server_context->context; 1119 krb5_storage *sp; 1120 uint32_t initial_version; 1121 uint32_t initial_tstamp; 1122 uint32_t ver; 1123 off_t left = 0; 1124 off_t right = 0; 1125 krb5_ssize_t bytes; 1126 krb5_data data; 1127 int ret = 0; 1128 1129 if (!diffready(context, s) || nodiffs(context, s, current_version)) 1130 return; 1131 1132 if (verbose) 1133 krb5_warnx(context, "sending diffs to live-seeming slave %s", s->name); 1134 1135 sp = krb5_storage_from_fd(log_fd); 1136 if (sp == NULL) 1137 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM, 1138 "send_diffs: out of memory"); 1139 1140 left = get_left(server_context, s, sp, log_fd, current_version, 1141 &initial_version, &initial_tstamp); 1142 if (left < 0) { 1143 krb5_storage_free(sp); 1144 slave_dead(context, s); 1145 return; 1146 } 1147 1148 if (left == 0) { 1149 /* Slave's version is not in the log, fall back on send_complete() */ 1150 krb5_storage_free(sp); 1151 send_complete(context, s, database, current_version, 1152 initial_version, initial_tstamp); 1153 return; 1154 } 1155 1156 /* We still hold the read lock, if right > 0 */ 1157 right = get_right(server_context->context, log_fd, sp, current_version, 1158 s, left, &ver); 1159 if (right == left) { 1160 flock(log_fd, LOCK_UN); 1161 krb5_storage_free(sp); 1162 return; 1163 } 1164 if (right < left) { 1165 assert(right < 0); 1166 krb5_storage_free(sp); 1167 slave_dead(context, s); 1168 return; 1169 } 1170 1171 if (krb5_storage_seek(sp, left, SEEK_SET) != left) { 1172 ret = errno ? errno : EIO; 1173 flock(log_fd, LOCK_UN); 1174 krb5_warn(context, ret, "send_diffs: krb5_storage_seek"); 1175 krb5_storage_free(sp); 1176 slave_dead(context, s); 1177 return; 1178 } 1179 1180 ret = krb5_data_alloc(&data, right - left + 4); 1181 if (ret) { 1182 flock(log_fd, LOCK_UN); 1183 krb5_warn(context, ret, "send_diffs: krb5_data_alloc"); 1184 krb5_storage_free(sp); 1185 slave_dead(context, s); 1186 return; 1187 } 1188 1189 bytes = krb5_storage_read(sp, (char *)data.data + 4, data.length - 4); 1190 flock(log_fd, LOCK_UN); 1191 krb5_storage_free(sp); 1192 if (bytes != data.length - 4) 1193 krb5_errx(context, IPROPD_RESTART, "locked log truncated???"); 1194 1195 sp = krb5_storage_from_data(&data); 1196 if (sp == NULL) { 1197 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM, "out of memory"); 1198 krb5_warnx(context, "send_diffs: krb5_storage_from_data"); 1199 return; 1200 } 1201 krb5_store_uint32(sp, FOR_YOU); 1202 krb5_storage_free(sp); 1203 1204 ret = mk_priv_tail(context, s, &data); 1205 krb5_data_free(&data); 1206 if (ret == 0) { 1207 /* Save the fast-path continuation */ 1208 s->next_diff.last_version_sent = ver; 1209 s->next_diff.off_next_version = right; 1210 s->next_diff.initial_version = initial_version; 1211 s->next_diff.initial_tstamp = initial_tstamp; 1212 s->next_diff.more = ver < current_version; 1213 ret = send_tail(context, s); 1214 1215 krb5_warnx(context, 1216 "syncing slave %s from version %lu to version %lu", 1217 s->name, (unsigned long)s->version, 1218 (unsigned long)ver); 1219 s->version = ver; 1220 } 1221 1222 if (ret && ret != EWOULDBLOCK) { 1223 krb5_warn(context, ret, "send_diffs: making or sending " 1224 "KRB-PRIV message"); 1225 slave_dead(context, s); 1226 return; 1227 } 1228 slave_seen(s); 1229 return; 1230 } 1231 1232 /* Sensible bound on slave message size */ 1233 #define SLAVE_MSG_MAX 65536 1234 1235 static int 1236 fill_input(krb5_context context, slave *s) 1237 { 1238 krb5_error_code ret; 1239 1240 if (s->input.hlen < 4) { 1241 uint8_t *buf = s->input.header_buf + s->input.hlen; 1242 size_t len = 4 - s->input.hlen; 1243 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len); 1244 1245 if (bytes == 0) 1246 return HEIM_ERR_EOF; 1247 if (bytes < 0) { 1248 if (errno == EWOULDBLOCK || errno == EAGAIN) 1249 return EWOULDBLOCK; 1250 return errno ? errno : EIO; 1251 } 1252 s->input.hlen += bytes; 1253 if (bytes < len) 1254 return EWOULDBLOCK; 1255 1256 buf = s->input.header_buf; 1257 len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; 1258 if (len > SLAVE_MSG_MAX) 1259 return EINVAL; 1260 ret = krb5_data_alloc(&s->input.packet, len); 1261 if (ret != 0) 1262 return ret; 1263 } 1264 1265 if (s->input.offset < s->input.packet.length) { 1266 u_char *buf = (u_char *)s->input.packet.data + s->input.offset; 1267 size_t len = s->input.packet.length - s->input.offset; 1268 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len); 1269 1270 if (bytes == 0) 1271 return HEIM_ERR_EOF; 1272 if (bytes < 0) { 1273 if (errno == EWOULDBLOCK || errno == EAGAIN) 1274 return EWOULDBLOCK; 1275 return errno ? errno : EIO; 1276 } 1277 s->input.offset += bytes; 1278 if (bytes != len) 1279 return EWOULDBLOCK; 1280 } 1281 return 0; 1282 } 1283 1284 static int 1285 read_msg(krb5_context context, slave *s, krb5_data *out) 1286 { 1287 int ret = fill_input(context, s); 1288 1289 if (ret != 0) 1290 return ret; 1291 1292 ret = krb5_rd_priv(context, s->ac, &s->input.packet, out, NULL); 1293 1294 /* Prepare for next packet */ 1295 krb5_data_free(&s->input.packet); 1296 s->input.offset = 0; 1297 s->input.hlen = 0; 1298 1299 return ret; 1300 } 1301 1302 static int 1303 process_msg(kadm5_server_context *server_context, slave *s, int log_fd, 1304 const char *database, uint32_t current_version) 1305 { 1306 krb5_context context = server_context->context; 1307 int ret = 0; 1308 krb5_data out; 1309 krb5_storage *sp; 1310 uint32_t tmp; 1311 1312 ret = read_msg(context, s, &out); 1313 if (ret) { 1314 if (ret != EWOULDBLOCK) 1315 krb5_warn(context, ret, "error reading message from %s", s->name); 1316 return ret; 1317 } 1318 1319 sp = krb5_storage_from_mem(out.data, out.length); 1320 if (sp == NULL) { 1321 krb5_warnx(context, "process_msg: no memory"); 1322 krb5_data_free(&out); 1323 return 1; 1324 } 1325 if (krb5_ret_uint32(sp, &tmp) != 0) { 1326 krb5_warnx(context, "process_msg: client send too short command"); 1327 krb5_data_free(&out); 1328 return 1; 1329 } 1330 switch (tmp) { 1331 case I_HAVE : 1332 ret = krb5_ret_uint32(sp, &tmp); 1333 if (ret != 0) { 1334 krb5_warnx(context, "process_msg: client send too little I_HAVE data"); 1335 break; 1336 } 1337 /* 1338 * XXX Make the slave send the timestamp as well, and try to get it 1339 * here, and pass it to send_diffs(). 1340 */ 1341 /* 1342 * New slave whose version number we've not yet seen. If the version 1343 * number is zero, the slave has no data, and we'll send a complete 1344 * database (that happens in send_diffs()). Otherwise, we'll record a 1345 * non-zero initial version and attempt an incremental update. 1346 * 1347 * NOTE!: Once the slave is "ready" (its first I_HAVE has conveyed its 1348 * initial version), we MUST NOT update s->version to the slave's 1349 * I_HAVE version, since we may already have sent later updates, and 1350 * MUST NOT send them again, otherwise we can get further and further 1351 * out of sync resending larger and larger diffs. The "not yet ready" 1352 * is an essential precondition for setting s->version to the value 1353 * in the I_HAVE message. This happens only once when the slave 1354 * first connects. 1355 */ 1356 if (!(s->flags & SLAVE_F_READY)) { 1357 if (current_version < tmp) { 1358 krb5_warnx(context, "Slave %s (version %u) has later version " 1359 "than the master (version %u) OUT OF SYNC", 1360 s->name, tmp, current_version); 1361 /* Force send_complete() */ 1362 tmp = 0; 1363 } 1364 /* 1365 * Mark the slave as ready for updates based on incoming signals. 1366 * Prior to the initial I_HAVE, we don't know the slave's version 1367 * number, and MUST not send it anything, since we'll needlessly 1368 * attempt to send the whole database! 1369 */ 1370 s->version = tmp; 1371 s->flags |= SLAVE_F_READY; 1372 if (verbose) 1373 krb5_warnx(context, "slave %s ready for updates from version %u", 1374 s->name, tmp); 1375 } 1376 if ((s->version_ack = tmp) < s->version) 1377 break; 1378 send_diffs(server_context, s, log_fd, database, current_version); 1379 break; 1380 case I_AM_HERE : 1381 if (verbose) 1382 krb5_warnx(context, "slave %s is there", s->name); 1383 break; 1384 case ARE_YOU_THERE: 1385 case FOR_YOU : 1386 default : 1387 krb5_warnx(context, "Ignoring command %d", tmp); 1388 break; 1389 } 1390 1391 krb5_data_free(&out); 1392 krb5_storage_free(sp); 1393 1394 slave_seen(s); 1395 1396 return ret; 1397 } 1398 1399 #define SLAVE_NAME "Name" 1400 #define SLAVE_ADDRESS "Address" 1401 #define SLAVE_VERSION "Version" 1402 #define SLAVE_STATUS "Status" 1403 #define SLAVE_SEEN "Last Seen" 1404 1405 static FILE * 1406 open_stats(krb5_context context) 1407 { 1408 char *statfile = NULL; 1409 const char *fn = NULL; 1410 FILE *out = NULL; 1411 1412 /* 1413 * krb5_config_get_string_default() returs default value as-is, 1414 * delay free() of "statfile" until we're done with "fn". 1415 */ 1416 if (slave_stats_file) 1417 fn = slave_stats_file; 1418 else if (asprintf(&statfile, "%s/slaves-stats", hdb_db_dir(context)) != -1 1419 && statfile != NULL) 1420 fn = krb5_config_get_string_default(context, 1421 NULL, 1422 statfile, 1423 "kdc", 1424 "iprop-stats", 1425 NULL); 1426 if (fn != NULL) 1427 out = fopen(fn, "w"); 1428 if (statfile != NULL) 1429 free(statfile); 1430 return out; 1431 } 1432 1433 static void 1434 write_master_down(krb5_context context) 1435 { 1436 char str[100]; 1437 time_t t = time(NULL); 1438 FILE *fp; 1439 1440 fp = open_stats(context); 1441 if (fp == NULL) 1442 return; 1443 krb5_format_time(context, t, str, sizeof(str), TRUE); 1444 fprintf(fp, "master down at %s\n", str); 1445 1446 fclose(fp); 1447 } 1448 1449 static void 1450 write_stats(krb5_context context, slave *slaves, uint32_t current_version) 1451 { 1452 char str[100]; 1453 rtbl_t tbl; 1454 time_t t = time(NULL); 1455 FILE *fp; 1456 1457 fp = open_stats(context); 1458 if (fp == NULL) 1459 return; 1460 1461 krb5_format_time(context, t, str, sizeof(str), TRUE); 1462 fprintf(fp, "Status for slaves, last updated: %s\n\n", str); 1463 1464 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version); 1465 1466 tbl = rtbl_create(); 1467 if (tbl == NULL) { 1468 fclose(fp); 1469 return; 1470 } 1471 1472 rtbl_add_column(tbl, SLAVE_NAME, 0); 1473 rtbl_add_column(tbl, SLAVE_ADDRESS, 0); 1474 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT); 1475 rtbl_add_column(tbl, SLAVE_STATUS, 0); 1476 rtbl_add_column(tbl, SLAVE_SEEN, 0); 1477 1478 rtbl_set_prefix(tbl, " "); 1479 rtbl_set_column_prefix(tbl, SLAVE_NAME, ""); 1480 1481 while (slaves) { 1482 krb5_address addr; 1483 krb5_error_code ret; 1484 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name); 1485 ret = krb5_sockaddr2address (context, 1486 (struct sockaddr*)&slaves->addr, &addr); 1487 if(ret == 0) { 1488 krb5_print_address(&addr, str, sizeof(str), NULL); 1489 krb5_free_address(context, &addr); 1490 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str); 1491 } else 1492 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>"); 1493 1494 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version_ack); 1495 rtbl_add_column_entry(tbl, SLAVE_VERSION, str); 1496 1497 if (slaves->flags & SLAVE_F_DEAD) 1498 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down"); 1499 else 1500 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up"); 1501 1502 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE); 1503 if (ret) 1504 rtbl_add_column_entry(tbl, SLAVE_SEEN, "<error-formatting-time>"); 1505 else 1506 rtbl_add_column_entry(tbl, SLAVE_SEEN, str); 1507 1508 slaves = slaves->next; 1509 } 1510 1511 rtbl_format(tbl, fp); 1512 rtbl_destroy(tbl); 1513 1514 fclose(fp); 1515 } 1516 1517 1518 static char sHDB[] = "HDBGET:"; 1519 static char *realm; 1520 static int version_flag; 1521 static int help_flag; 1522 static char *keytab_str = sHDB; 1523 static char *database; 1524 static char *config_file; 1525 static char *port_str; 1526 static int detach_from_console; 1527 static int daemon_child = -1; 1528 1529 static struct getargs args[] = { 1530 { "config-file", 'c', arg_string, &config_file, NULL, NULL }, 1531 { "realm", 'r', arg_string, &realm, NULL, NULL }, 1532 { "keytab", 'k', arg_string, &keytab_str, 1533 "keytab to get authentication from", "kspec" }, 1534 { "database", 'd', arg_string, &database, "database", "file"}, 1535 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file), 1536 "file for slave status information", "file"}, 1537 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing), 1538 "time before slave is polled for presence", "time"}, 1539 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone), 1540 "time of inactivity after which a slave is considered gone", "time"}, 1541 { "port", 0, arg_string, &port_str, 1542 "port ipropd will listen to", "port"}, 1543 { "detach", 0, arg_flag, &detach_from_console, 1544 "detach from console", NULL }, 1545 { "daemon-child", 0 , arg_integer, &daemon_child, 1546 "private argument, do not use", NULL }, 1547 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname), 1548 "hostname of master (if not same as hostname)", "hostname" }, 1549 { "verbose", 0, arg_flag, &verbose, NULL, NULL }, 1550 { "version", 0, arg_flag, &version_flag, NULL, NULL }, 1551 { "help", 0, arg_flag, &help_flag, NULL, NULL } 1552 }; 1553 static int num_args = sizeof(args) / sizeof(args[0]); 1554 1555 int 1556 main(int argc, char **argv) 1557 { 1558 krb5_error_code ret; 1559 krb5_context context; 1560 void *kadm_handle; 1561 kadm5_server_context *server_context; 1562 kadm5_config_params conf; 1563 krb5_socket_t signal_fd, listen_fd; 1564 int log_fd; 1565 slave *slaves = NULL; 1566 uint32_t current_version = 0, old_version = 0; 1567 krb5_keytab keytab; 1568 char **files; 1569 int aret; 1570 int optidx = 0; 1571 int restarter_fd = -1; 1572 struct stat st; 1573 1574 setprogname(argv[0]); 1575 1576 if (getarg(args, num_args, argc, argv, &optidx)) 1577 krb5_std_usage(1, args, num_args); 1578 1579 if (help_flag) 1580 krb5_std_usage(0, args, num_args); 1581 1582 if (version_flag) { 1583 print_version(NULL); 1584 exit(0); 1585 } 1586 1587 if (detach_from_console && daemon_child == -1) 1588 roken_detach_prep(argc, argv, "--daemon-child"); 1589 rk_pidfile(NULL); 1590 1591 ret = krb5_init_context(&context); 1592 if (ret) 1593 errx(1, "krb5_init_context failed: %d", ret); 1594 1595 setup_signal(); 1596 1597 if (config_file == NULL) { 1598 aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); 1599 if (aret == -1 || config_file == NULL) 1600 errx(1, "out of memory"); 1601 } 1602 1603 ret = krb5_prepend_config_files_default(config_file, &files); 1604 if (ret) 1605 krb5_err(context, 1, ret, "getting configuration files"); 1606 1607 ret = krb5_set_config_files(context, files); 1608 krb5_free_config_files(files); 1609 if (ret) 1610 krb5_err(context, 1, ret, "reading configuration files"); 1611 1612 time_before_gone = parse_time (slave_time_gone, "s"); 1613 if (time_before_gone < 0) 1614 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone); 1615 time_before_missing = parse_time (slave_time_missing, "s"); 1616 if (time_before_missing < 0) 1617 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing); 1618 1619 krb5_openlog(context, "ipropd-master", &log_facility); 1620 krb5_set_warn_dest(context, log_facility); 1621 1622 ret = krb5_kt_register(context, &hdb_get_kt_ops); 1623 if(ret) 1624 krb5_err(context, 1, ret, "krb5_kt_register"); 1625 1626 ret = krb5_kt_resolve(context, keytab_str, &keytab); 1627 if(ret) 1628 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str); 1629 1630 memset(&conf, 0, sizeof(conf)); 1631 if(realm) { 1632 conf.mask |= KADM5_CONFIG_REALM; 1633 conf.realm = realm; 1634 } 1635 ret = kadm5_init_with_skey_ctx (context, 1636 KADM5_ADMIN_SERVICE, 1637 NULL, 1638 KADM5_ADMIN_SERVICE, 1639 &conf, 0, 0, 1640 &kadm_handle); 1641 if (ret) 1642 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 1643 1644 server_context = (kadm5_server_context *)kadm_handle; 1645 1646 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0); 1647 if (log_fd < 0) 1648 krb5_err (context, 1, errno, "open %s", 1649 server_context->log_context.log_file); 1650 1651 if (fstat(log_fd, &st) == -1) 1652 krb5_err(context, 1, errno, "stat %s", 1653 server_context->log_context.log_file); 1654 1655 if (flock(log_fd, LOCK_SH) == -1) 1656 krb5_err(context, 1, errno, "shared flock %s", 1657 server_context->log_context.log_file); 1658 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1659 ¤t_version, NULL); 1660 flock(log_fd, LOCK_UN); 1661 1662 signal_fd = make_signal_socket (context); 1663 listen_fd = make_listen_socket (context, port_str); 1664 1665 krb5_warnx(context, "ipropd-master started at version: %lu", 1666 (unsigned long)current_version); 1667 1668 roken_detach_finish(NULL, daemon_child); 1669 restarter_fd = restarter(context, NULL); 1670 1671 while (exit_flag == 0){ 1672 slave *p; 1673 fd_set readset, writeset; 1674 int max_fd = 0; 1675 struct timeval to = {30, 0}; 1676 uint32_t vers; 1677 struct stat st2;; 1678 1679 #ifndef NO_LIMIT_FD_SETSIZE 1680 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE || 1681 restarter_fd >= FD_SETSIZE) 1682 krb5_errx (context, IPROPD_RESTART, "fd too large"); 1683 #endif 1684 1685 FD_ZERO(&readset); 1686 FD_ZERO(&writeset); 1687 FD_SET(signal_fd, &readset); 1688 max_fd = max(max_fd, signal_fd); 1689 FD_SET(listen_fd, &readset); 1690 max_fd = max(max_fd, listen_fd); 1691 if (restarter_fd > -1) { 1692 FD_SET(restarter_fd, &readset); 1693 max_fd = max(max_fd, restarter_fd); 1694 } 1695 1696 for (p = slaves; p != NULL; p = p->next) { 1697 if (p->flags & SLAVE_F_DEAD) 1698 continue; 1699 FD_SET(p->fd, &readset); 1700 if (have_tail(p) || more_diffs(p)) 1701 FD_SET(p->fd, &writeset); 1702 max_fd = max(max_fd, p->fd); 1703 } 1704 1705 ret = select(max_fd + 1, &readset, &writeset, NULL, &to); 1706 if (ret < 0) { 1707 if (errno == EINTR) 1708 continue; 1709 else 1710 krb5_err (context, IPROPD_RESTART, errno, "select"); 1711 } 1712 1713 if (stat(server_context->log_context.log_file, &st2) == -1) { 1714 krb5_warn(context, errno, "could not stat log file by path"); 1715 st2 = st; 1716 } 1717 1718 if (st2.st_dev != st.st_dev || st2.st_ino != st.st_ino) { 1719 (void) close(log_fd); 1720 1721 log_fd = open(server_context->log_context.log_file, O_RDONLY, 0); 1722 if (log_fd < 0) 1723 krb5_err(context, IPROPD_RESTART_SLOW, errno, "open %s", 1724 server_context->log_context.log_file); 1725 1726 if (fstat(log_fd, &st) == -1) 1727 krb5_err(context, IPROPD_RESTART_SLOW, errno, "stat %s", 1728 server_context->log_context.log_file); 1729 1730 if (flock(log_fd, LOCK_SH) == -1) 1731 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s", 1732 server_context->log_context.log_file); 1733 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1734 ¤t_version, NULL); 1735 flock(log_fd, LOCK_UN); 1736 } 1737 1738 if (ret == 0) { 1739 /* Recover from failed transactions */ 1740 if (kadm5_log_init_nb(server_context) == 0) 1741 kadm5_log_end(server_context); 1742 1743 if (flock(log_fd, LOCK_SH) == -1) 1744 krb5_err(context, IPROPD_RESTART, errno, 1745 "could not lock log file"); 1746 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1747 ¤t_version, NULL); 1748 flock(log_fd, LOCK_UN); 1749 1750 if (current_version > old_version) { 1751 if (verbose) 1752 krb5_warnx(context, 1753 "Missed a signal, updating slaves %lu to %lu", 1754 (unsigned long)old_version, 1755 (unsigned long)current_version); 1756 for (p = slaves; p != NULL; p = p->next) { 1757 if (p->flags & SLAVE_F_DEAD) 1758 continue; 1759 send_diffs(server_context, p, log_fd, database, 1760 current_version); 1761 } 1762 old_version = current_version; 1763 } 1764 } 1765 1766 if (ret && FD_ISSET(restarter_fd, &readset)) { 1767 exit_flag = SIGTERM; 1768 break; 1769 } 1770 1771 if (ret && FD_ISSET(signal_fd, &readset)) { 1772 #ifndef NO_UNIX_SOCKETS 1773 struct sockaddr_un peer_addr; 1774 #else 1775 struct sockaddr_storage peer_addr; 1776 #endif 1777 socklen_t peer_len = sizeof(peer_addr); 1778 1779 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0, 1780 (struct sockaddr *)&peer_addr, &peer_len) < 0) { 1781 krb5_warn (context, errno, "recvfrom"); 1782 continue; 1783 } 1784 --ret; 1785 assert(ret >= 0); 1786 old_version = current_version; 1787 if (flock(log_fd, LOCK_SH) == -1) 1788 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s", 1789 server_context->log_context.log_file); 1790 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1791 ¤t_version, NULL); 1792 flock(log_fd, LOCK_UN); 1793 if (current_version != old_version) { 1794 /* 1795 * If current_version < old_version then the log got 1796 * truncated and we'll end up doing full propagations. 1797 * 1798 * Truncating the log when the current version is 1799 * numerically small can lead to race conditions. 1800 * Ideally we should identify log versions as 1801 * {init_or_trunc_time, vno}, then we could not have any 1802 * such race conditions, but this would either require 1803 * breaking backwards compatibility for the protocol or 1804 * adding new messages to it. 1805 */ 1806 if (verbose) 1807 krb5_warnx(context, 1808 "Got a signal, updating slaves %lu to %lu", 1809 (unsigned long)old_version, 1810 (unsigned long)current_version); 1811 for (p = slaves; p != NULL; p = p->next) { 1812 if (p->flags & SLAVE_F_DEAD) 1813 continue; 1814 send_diffs(server_context, p, log_fd, database, 1815 current_version); 1816 } 1817 } else { 1818 if (verbose) 1819 krb5_warnx(context, 1820 "Got a signal, but no update in log version %lu", 1821 (unsigned long)current_version); 1822 } 1823 } 1824 1825 for (p = slaves; p != NULL; p = p->next) { 1826 if (!(p->flags & SLAVE_F_DEAD) && 1827 FD_ISSET(p->fd, &writeset) && 1828 ((have_tail(p) && send_tail(context, p) == 0) || 1829 (!have_tail(p) && more_diffs(p)))) { 1830 send_diffs(server_context, p, log_fd, database, 1831 current_version); 1832 } 1833 } 1834 1835 for(p = slaves; p != NULL; p = p->next) { 1836 if (p->flags & SLAVE_F_DEAD) 1837 continue; 1838 if (ret && FD_ISSET(p->fd, &readset)) { 1839 --ret; 1840 assert(ret >= 0); 1841 ret = process_msg(server_context, p, log_fd, database, 1842 current_version); 1843 if (ret && ret != EWOULDBLOCK) 1844 slave_dead(context, p); 1845 } else if (slave_gone_p (p)) 1846 slave_dead(context, p); 1847 else if (slave_missing_p (p)) 1848 send_are_you_there (context, p); 1849 } 1850 1851 if (ret && FD_ISSET(listen_fd, &readset)) { 1852 add_slave (context, keytab, &slaves, listen_fd); 1853 --ret; 1854 assert(ret >= 0); 1855 } 1856 write_stats(context, slaves, current_version); 1857 } 1858 1859 if(exit_flag == SIGINT || exit_flag == SIGTERM) 1860 krb5_warnx(context, "%s terminated", getprogname()); 1861 #ifdef SIGXCPU 1862 else if(exit_flag == SIGXCPU) 1863 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 1864 #endif 1865 else 1866 krb5_warnx(context, "%s unexpected exit reason: %ld", 1867 getprogname(), (long)exit_flag); 1868 1869 write_master_down(context); 1870 1871 return 0; 1872 } 1873