1 /* $NetBSD: ipropd_master.c,v 1.2 2017/01/28 21:31:49 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 struct slave { 127 krb5_socket_t fd; 128 struct sockaddr_in addr; 129 char *name; 130 krb5_auth_context ac; 131 uint32_t version; 132 uint32_t version_tstamp; 133 time_t seen; 134 unsigned long flags; 135 #define SLAVE_F_DEAD 0x1 136 #define SLAVE_F_AYT 0x2 137 struct slave *next; 138 }; 139 140 typedef struct slave slave; 141 142 static int 143 check_acl (krb5_context context, const char *name) 144 { 145 const char *fn; 146 FILE *fp; 147 char buf[256]; 148 int ret = 1; 149 char *slavefile = NULL; 150 151 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1 152 || slavefile == NULL) 153 errx(1, "out of memory"); 154 155 fn = krb5_config_get_string_default(context, 156 NULL, 157 slavefile, 158 "kdc", 159 "iprop-acl", 160 NULL); 161 162 fp = fopen (fn, "r"); 163 free(slavefile); 164 if (fp == NULL) 165 return 1; 166 while (fgets(buf, sizeof(buf), fp) != NULL) { 167 buf[strcspn(buf, "\r\n")] = '\0'; 168 if (strcmp (buf, name) == 0) { 169 ret = 0; 170 break; 171 } 172 } 173 fclose (fp); 174 return ret; 175 } 176 177 static void 178 slave_seen(slave *s) 179 { 180 s->flags &= ~SLAVE_F_AYT; 181 s->seen = time(NULL); 182 } 183 184 static int 185 slave_missing_p (slave *s) 186 { 187 if (time(NULL) > s->seen + time_before_missing) 188 return 1; 189 return 0; 190 } 191 192 static int 193 slave_gone_p (slave *s) 194 { 195 if (time(NULL) > s->seen + time_before_gone) 196 return 1; 197 return 0; 198 } 199 200 static void 201 slave_dead(krb5_context context, slave *s) 202 { 203 krb5_warnx(context, "slave %s dead", s->name); 204 205 if (!rk_IS_BAD_SOCKET(s->fd)) { 206 rk_closesocket (s->fd); 207 s->fd = rk_INVALID_SOCKET; 208 } 209 s->flags |= SLAVE_F_DEAD; 210 slave_seen(s); 211 } 212 213 static void 214 remove_slave (krb5_context context, slave *s, slave **root) 215 { 216 slave **p; 217 218 if (!rk_IS_BAD_SOCKET(s->fd)) 219 rk_closesocket (s->fd); 220 if (s->name) 221 free (s->name); 222 if (s->ac) 223 krb5_auth_con_free (context, s->ac); 224 225 for (p = root; *p; p = &(*p)->next) 226 if (*p == s) { 227 *p = s->next; 228 break; 229 } 230 free (s); 231 } 232 233 static void 234 add_slave (krb5_context context, krb5_keytab keytab, slave **root, 235 krb5_socket_t fd) 236 { 237 krb5_principal server; 238 krb5_error_code ret; 239 slave *s; 240 socklen_t addr_len; 241 krb5_ticket *ticket = NULL; 242 char hostname[128]; 243 244 s = malloc(sizeof(*s)); 245 if (s == NULL) { 246 krb5_warnx (context, "add_slave: no memory"); 247 return; 248 } 249 s->name = NULL; 250 s->ac = NULL; 251 252 addr_len = sizeof(s->addr); 253 s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len); 254 if (rk_IS_BAD_SOCKET(s->fd)) { 255 krb5_warn (context, rk_SOCK_ERRNO, "accept"); 256 goto error; 257 } 258 if (master_hostname) 259 strlcpy(hostname, master_hostname, sizeof(hostname)); 260 else 261 gethostname(hostname, sizeof(hostname)); 262 263 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME, 264 KRB5_NT_SRV_HST, &server); 265 if (ret) { 266 krb5_warn (context, ret, "krb5_sname_to_principal"); 267 goto error; 268 } 269 270 ret = krb5_recvauth (context, &s->ac, &s->fd, 271 IPROP_VERSION, server, 0, keytab, &ticket); 272 krb5_free_principal (context, server); 273 if (ret) { 274 krb5_warn (context, ret, "krb5_recvauth"); 275 goto error; 276 } 277 ret = krb5_unparse_name (context, ticket->client, &s->name); 278 krb5_free_ticket (context, ticket); 279 if (ret) { 280 krb5_warn (context, ret, "krb5_unparse_name"); 281 goto error; 282 } 283 if (check_acl (context, s->name)) { 284 krb5_warnx (context, "%s not in acl", s->name); 285 goto error; 286 } 287 288 { 289 slave *l = *root; 290 291 while (l) { 292 if (strcmp(l->name, s->name) == 0) 293 break; 294 l = l->next; 295 } 296 if (l) { 297 if (l->flags & SLAVE_F_DEAD) { 298 remove_slave(context, l, root); 299 } else { 300 krb5_warnx (context, "second connection from %s", s->name); 301 goto error; 302 } 303 } 304 } 305 306 krb5_warnx (context, "connection from %s", s->name); 307 308 s->version = 0; 309 s->flags = 0; 310 slave_seen(s); 311 s->next = *root; 312 *root = s; 313 return; 314 error: 315 remove_slave(context, s, root); 316 } 317 318 static int 319 dump_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v) 320 { 321 krb5_error_code ret; 322 krb5_storage *dump = (krb5_storage *)v; 323 krb5_storage *sp; 324 krb5_data data; 325 326 ret = hdb_entry2value (context, &entry->entry, &data); 327 if (ret) 328 return ret; 329 ret = krb5_data_realloc (&data, data.length + 4); 330 if (ret) 331 goto done; 332 memmove ((char *)data.data + 4, data.data, data.length - 4); 333 sp = krb5_storage_from_data(&data); 334 if (sp == NULL) { 335 ret = ENOMEM; 336 goto done; 337 } 338 ret = krb5_store_uint32(sp, ONE_PRINC); 339 krb5_storage_free(sp); 340 341 if (ret == 0) 342 ret = krb5_store_data(dump, data); 343 344 done: 345 krb5_data_free (&data); 346 return ret; 347 } 348 349 static int 350 write_dump (krb5_context context, krb5_storage *dump, 351 const char *database, uint32_t current_version) 352 { 353 krb5_error_code ret; 354 krb5_storage *sp; 355 HDB *db; 356 krb5_data data; 357 char buf[8]; 358 359 /* we assume that the caller has obtained an exclusive lock */ 360 361 ret = krb5_storage_truncate(dump, 0); 362 if (ret) 363 return ret; 364 365 if (krb5_storage_seek(dump, 0, SEEK_SET) != 0) 366 return errno; 367 368 /* 369 * First we store zero as the HDB version, this will indicate to a 370 * later reader that the dumpfile is invalid. We later write the 371 * correct version in the file after we have written all of the 372 * messages. A dump with a zero version will not be considered 373 * to be valid. 374 */ 375 376 ret = krb5_store_uint32(dump, 0); 377 378 ret = hdb_create (context, &db, database); 379 if (ret) 380 krb5_err (context, IPROPD_RESTART, ret, "hdb_create: %s", database); 381 ret = db->hdb_open (context, db, O_RDONLY, 0); 382 if (ret) 383 krb5_err (context, IPROPD_RESTART, ret, "db->open"); 384 385 sp = krb5_storage_from_mem (buf, 4); 386 if (sp == NULL) 387 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem"); 388 krb5_store_uint32 (sp, TELL_YOU_EVERYTHING); 389 krb5_storage_free (sp); 390 391 data.data = buf; 392 data.length = 4; 393 394 ret = krb5_store_data(dump, data); 395 if (ret) { 396 krb5_warn (context, ret, "write_dump"); 397 return ret; 398 } 399 400 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, dump_one, dump); 401 if (ret) { 402 krb5_warn (context, ret, "write_dump: hdb_foreach"); 403 return ret; 404 } 405 406 (*db->hdb_close)(context, db); 407 (*db->hdb_destroy)(context, db); 408 409 sp = krb5_storage_from_mem (buf, 8); 410 if (sp == NULL) 411 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem"); 412 ret = krb5_store_uint32(sp, NOW_YOU_HAVE); 413 if (ret == 0) 414 krb5_store_uint32(sp, current_version); 415 krb5_storage_free (sp); 416 417 data.length = 8; 418 419 if (ret == 0) 420 ret = krb5_store_data(dump, data); 421 422 /* 423 * We must ensure that the entire valid dump is written to disk 424 * before we write the current version at the front thus making 425 * it a valid dump file. If we crash around here, this can be 426 * important upon reboot. 427 */ 428 429 if (ret == 0) 430 ret = krb5_storage_fsync(dump); 431 432 if (ret == 0 && krb5_storage_seek(dump, 0, SEEK_SET) == -1) 433 ret = errno; 434 435 /* Write current version at the front making the dump valid */ 436 437 if (ret == 0) 438 ret = krb5_store_uint32(dump, current_version); 439 440 /* 441 * We don't need to fsync(2) after the real version is written as 442 * it is not a disaster if it doesn't make it to disk if we crash. 443 * After all, we'll just create a new dumpfile. 444 */ 445 446 if (ret == 0) 447 krb5_warnx(context, "wrote new dumpfile (version %u)", 448 current_version); 449 else 450 krb5_warn(context, ret, "failed to write new dumpfile (version %u)", 451 current_version); 452 453 return ret; 454 } 455 456 static int 457 send_complete (krb5_context context, slave *s, const char *database, 458 uint32_t current_version, uint32_t oldest_version, 459 uint32_t initial_log_tstamp) 460 { 461 krb5_error_code ret; 462 krb5_storage *dump = NULL; 463 uint32_t vno = 0; 464 krb5_data data; 465 int fd = -1; 466 struct stat st; 467 char *dfn; 468 469 ret = asprintf(&dfn, "%s/ipropd.dumpfile", hdb_db_dir(context)); 470 if (ret == -1 || !dfn) { 471 krb5_warn(context, ENOMEM, "Cannot allocate memory"); 472 return ENOMEM; 473 } 474 475 fd = open(dfn, O_CREAT|O_RDWR, 0600); 476 if (fd == -1) { 477 ret = errno; 478 krb5_warn(context, ret, "Cannot open/create iprop dumpfile %s", dfn); 479 free(dfn); 480 return ret; 481 } 482 free(dfn); 483 484 dump = krb5_storage_from_fd(fd); 485 if (!dump) { 486 ret = errno; 487 krb5_warn(context, ret, "krb5_storage_from_fd"); 488 goto done; 489 } 490 491 for (;;) { 492 ret = flock(fd, LOCK_SH); 493 if (ret == -1) { 494 ret = errno; 495 krb5_warn(context, ret, "flock(fd, LOCK_SH)"); 496 goto done; 497 } 498 499 if (krb5_storage_seek(dump, 0, SEEK_SET) == (off_t)-1) { 500 ret = errno; 501 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)"); 502 goto done; 503 } 504 505 vno = 0; 506 ret = krb5_ret_uint32(dump, &vno); 507 if (ret && ret != HEIM_ERR_EOF) { 508 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)"); 509 goto done; 510 } 511 512 if (fstat(fd, &st) == -1) { 513 ret = errno; 514 krb5_warn(context, ret, "send_complete: could not stat dump file"); 515 goto done; 516 } 517 518 /* 519 * If the current dump has an appropriate version, then we can 520 * break out of the loop and send the file below. 521 */ 522 523 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp && 524 vno >= oldest_version && vno <= current_version) 525 break; 526 527 if (verbose) 528 krb5_warnx(context, "send_complete: dumping HDB"); 529 530 /* 531 * Otherwise, we may need to write a new dump file. We 532 * obtain an exclusive lock on the fd. Because this is 533 * not guaranteed to be an upgrade of our existing shared 534 * lock, someone else may have written a new dumpfile while 535 * we were waiting and so we must first check the vno of 536 * the dump to see if that happened. If it did, we need 537 * to go back to the top of the loop so that we can downgrade 538 * our lock to a shared one. 539 */ 540 541 ret = flock(fd, LOCK_EX); 542 if (ret == -1) { 543 ret = errno; 544 krb5_warn(context, ret, "flock(fd, LOCK_EX)"); 545 goto done; 546 } 547 548 ret = krb5_storage_seek(dump, 0, SEEK_SET); 549 if (ret == -1) { 550 ret = errno; 551 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)"); 552 goto done; 553 } 554 555 vno = 0; 556 ret = krb5_ret_uint32(dump, &vno); 557 if (ret && ret != HEIM_ERR_EOF) { 558 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)"); 559 goto done; 560 } 561 562 if (fstat(fd, &st) == -1) { 563 ret = errno; 564 krb5_warn(context, ret, "send_complete: could not stat dump file"); 565 goto done; 566 } 567 568 /* check if someone wrote a better version for us */ 569 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp && 570 vno >= oldest_version && vno <= current_version) 571 continue; 572 573 /* Now, we know that we must write a new dump file. */ 574 575 ret = write_dump(context, dump, database, current_version); 576 if (ret) 577 goto done; 578 579 /* 580 * And we must continue to the top of the loop so that we can 581 * downgrade to a shared lock. 582 */ 583 } 584 585 /* 586 * Leaving the above loop, dump should have a ptr right after the initial 587 * 4 byte DB version number and we should have a shared lock on the file 588 * (which we may have just created), so we are reading to simply blast 589 * the data down the wire. 590 */ 591 592 for (;;) { 593 ret = krb5_ret_data(dump, &data); 594 if (ret == HEIM_ERR_EOF) { 595 ret = 0; /* EOF is not an error, it's success */ 596 goto done; 597 } 598 599 if (ret) { 600 krb5_warn(context, ret, "krb5_ret_data(dump, &data)"); 601 slave_dead(context, s); 602 goto done; 603 } 604 605 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 606 krb5_data_free(&data); 607 608 if (ret) { 609 krb5_warn (context, ret, "krb5_write_priv_message"); 610 slave_dead(context, s); 611 goto done; 612 } 613 } 614 615 done: 616 if (!ret) { 617 s->version = vno; 618 slave_seen(s); 619 } 620 if (fd != -1) 621 close(fd); 622 if (dump) 623 krb5_storage_free(dump); 624 return ret; 625 } 626 627 static int 628 send_are_you_there (krb5_context context, slave *s) 629 { 630 krb5_storage *sp; 631 krb5_data data; 632 char buf[4]; 633 int ret; 634 635 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT)) 636 return 0; 637 638 krb5_warnx(context, "slave %s missing, sending AYT", s->name); 639 640 s->flags |= SLAVE_F_AYT; 641 642 data.data = buf; 643 data.length = 4; 644 645 sp = krb5_storage_from_mem (buf, 4); 646 if (sp == NULL) { 647 krb5_warnx (context, "are_you_there: krb5_data_alloc"); 648 slave_dead(context, s); 649 return 1; 650 } 651 ret = krb5_store_uint32(sp, ARE_YOU_THERE); 652 krb5_storage_free (sp); 653 654 if (ret == 0) { 655 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 656 657 if (ret) { 658 krb5_warn(context, ret, "are_you_there: krb5_write_priv_message"); 659 slave_dead(context, s); 660 return 1; 661 } 662 } 663 664 return 0; 665 } 666 667 static int 668 send_diffs (kadm5_server_context *server_context, slave *s, int log_fd, 669 const char *database, uint32_t current_version, 670 uint32_t current_tstamp) 671 { 672 krb5_context context = server_context->context; 673 krb5_storage *sp; 674 uint32_t ver, initial_version, initial_version2; 675 uint32_t initial_tstamp, initial_tstamp2; 676 enum kadm_ops op; 677 uint32_t len; 678 off_t right, left; 679 krb5_ssize_t bytes; 680 krb5_data data; 681 int ret = 0; 682 683 if (s->flags & SLAVE_F_DEAD) { 684 krb5_warnx(context, "not sending diffs to dead slave %s", s->name); 685 return 0; 686 } 687 688 if (s->version == current_version) { 689 char buf[4]; 690 691 sp = krb5_storage_from_mem(buf, 4); 692 if (sp == NULL) 693 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_mem"); 694 ret = krb5_store_uint32(sp, YOU_HAVE_LAST_VERSION); 695 krb5_storage_free(sp); 696 data.data = buf; 697 data.length = 4; 698 if (ret == 0) { 699 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 700 if (ret) { 701 krb5_warn(context, ret, "send_diffs: failed to send to slave"); 702 slave_dead(context, s); 703 } 704 krb5_warnx(context, "slave %s in sync already at version %ld", 705 s->name, (long)s->version); 706 } 707 return ret; 708 } 709 710 if (verbose) 711 krb5_warnx(context, "sending diffs to live-seeming slave %s", s->name); 712 713 /* 714 * XXX The code that makes the diffs should be made a separate function, 715 * then error handling (send_are_you_there() or slave_dead()) can be done 716 * here. 717 */ 718 719 if (flock(log_fd, LOCK_SH) == -1) { 720 krb5_warn(context, errno, "could not obtain shared lock on log file"); 721 send_are_you_there(context, s); 722 return errno; 723 } 724 ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST, 725 &initial_version, &initial_tstamp); 726 sp = kadm5_log_goto_end(server_context, log_fd); 727 flock(log_fd, LOCK_UN); 728 if (ret) { 729 if (sp != NULL) 730 krb5_storage_free(sp); 731 krb5_warn(context, ret, "send_diffs: failed to read log"); 732 send_are_you_there(context, s); 733 return ret; 734 } 735 if (sp == NULL) { 736 send_are_you_there(context, s); 737 krb5_warn(context, errno ? errno : EINVAL, 738 "send_diffs: failed to read log"); 739 return errno ? errno : EINVAL; 740 } 741 /* 742 * We're not holding any locks here, so we can't prevent truncations. 743 * 744 * We protect against this by re-checking that the initial version and 745 * timestamp are the same before and after this loop. 746 */ 747 right = krb5_storage_seek(sp, 0, SEEK_CUR); 748 if (right == (off_t)-1) { 749 krb5_storage_free(sp); 750 send_are_you_there(context, s); 751 return errno; 752 } 753 for (;;) { 754 ret = kadm5_log_previous (context, sp, &ver, NULL, &op, &len); 755 if (ret) 756 krb5_err(context, IPROPD_RESTART, ret, 757 "send_diffs: failed to find previous entry"); 758 left = krb5_storage_seek(sp, -16, SEEK_CUR); 759 if (left == (off_t)-1) { 760 krb5_storage_free(sp); 761 send_are_you_there(context, s); 762 return errno; 763 } 764 if (ver == s->version + 1) 765 break; 766 767 /* 768 * We don't expect to reach the slave's version, except when it is 769 * starting empty with the uber record. 770 */ 771 if (ver == s->version && !(ver == 0 && op == kadm_nop)) { 772 /* 773 * This shouldn't happen, but recall we're not holding a lock on 774 * the log. 775 */ 776 krb5_storage_free(sp); 777 krb5_warnx(context, "iprop log truncated while sending diffs to " 778 "slave?? ver = %lu", (unsigned long)ver); 779 send_are_you_there(context, s); 780 return 0; 781 } 782 783 /* If we've reached the uber record, send the complete database */ 784 if (left == 0 || (ver == 0 && op == kadm_nop)) { 785 krb5_storage_free(sp); 786 krb5_warnx(context, 787 "slave %s (version %lu) out of sync with master " 788 "(first version in log %lu), sending complete database", 789 s->name, (unsigned long)s->version, (unsigned long)ver); 790 return send_complete (context, s, database, current_version, ver, 791 initial_tstamp); 792 } 793 } 794 795 assert(ver == s->version + 1); 796 797 krb5_warnx(context, 798 "syncing slave %s from version %lu to version %lu", 799 s->name, (unsigned long)s->version, 800 (unsigned long)current_version); 801 802 ret = krb5_data_alloc (&data, right - left + 4); 803 if (ret) { 804 krb5_storage_free(sp); 805 krb5_warn (context, ret, "send_diffs: krb5_data_alloc"); 806 send_are_you_there(context, s); 807 return 1; 808 } 809 bytes = krb5_storage_read(sp, (char *)data.data + 4, data.length - 4); 810 krb5_storage_free(sp); 811 if (bytes != data.length - 4) { 812 krb5_warnx(context, "iprop log truncated while sending diffs to " 813 "slave?? ver = %lu", (unsigned long)ver); 814 send_are_you_there(context, s); 815 return 1; 816 } 817 818 /* 819 * Check that we have the same log initial version and timestamp now as 820 * when we dropped the shared lock on the log file! Else we could be 821 * sending garbage to the slave. 822 */ 823 if (flock(log_fd, LOCK_SH) == -1) { 824 krb5_warn(context, errno, "could not obtain shared lock on log file"); 825 send_are_you_there(context, s); 826 return 1; 827 } 828 ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST, 829 &initial_version2, &initial_tstamp2); 830 flock(log_fd, LOCK_UN); 831 if (ret) { 832 krb5_warn(context, ret, 833 "send_diffs: failed to read log while producing diffs"); 834 send_are_you_there(context, s); 835 return 1; 836 } 837 if (initial_version != initial_version2 || 838 initial_tstamp != initial_tstamp2) { 839 krb5_warn(context, ret, 840 "send_diffs: log truncated while producing diffs"); 841 send_are_you_there(context, s); 842 return 1; 843 } 844 845 sp = krb5_storage_from_data (&data); 846 if (sp == NULL) { 847 krb5_warnx (context, "send_diffs: krb5_storage_from_data"); 848 send_are_you_there(context, s); 849 return 1; 850 } 851 krb5_store_uint32 (sp, FOR_YOU); 852 krb5_storage_free(sp); 853 854 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 855 krb5_data_free(&data); 856 857 if (ret) { 858 krb5_warn (context, ret, "send_diffs: krb5_write_priv_message"); 859 slave_dead(context, s); 860 return 1; 861 } 862 slave_seen(s); 863 864 s->version = current_version; 865 866 krb5_warnx(context, "slave %s is now up to date (%u)", s->name, s->version); 867 868 return 0; 869 } 870 871 static int 872 process_msg (kadm5_server_context *server_context, slave *s, int log_fd, 873 const char *database, uint32_t current_version, 874 uint32_t current_tstamp) 875 { 876 krb5_context context = server_context->context; 877 int ret = 0; 878 krb5_data out; 879 krb5_storage *sp; 880 uint32_t tmp; 881 882 ret = krb5_read_priv_message(context, s->ac, &s->fd, &out); 883 if(ret) { 884 krb5_warn(context, ret, "error reading message from %s", s->name); 885 return 1; 886 } 887 888 sp = krb5_storage_from_mem(out.data, out.length); 889 if (sp == NULL) { 890 krb5_warnx(context, "process_msg: no memory"); 891 krb5_data_free(&out); 892 return 1; 893 } 894 if (krb5_ret_uint32(sp, &tmp) != 0) { 895 krb5_warnx(context, "process_msg: client send too short command"); 896 krb5_data_free(&out); 897 return 1; 898 } 899 switch (tmp) { 900 case I_HAVE : 901 ret = krb5_ret_uint32(sp, &tmp); 902 if (ret != 0) { 903 krb5_warnx(context, "process_msg: client send too little I_HAVE data"); 904 break; 905 } 906 /* new started slave that have old log */ 907 if (s->version == 0 && tmp != 0) { 908 if (current_version < tmp) { 909 krb5_warnx(context, "Slave %s (version %u) have later version " 910 "the master (version %u) OUT OF SYNC", 911 s->name, tmp, current_version); 912 } 913 if (verbose) 914 krb5_warnx(context, "slave %s updated from %u to %u", 915 s->name, s->version, tmp); 916 s->version = tmp; 917 } 918 if (tmp < s->version) { 919 krb5_warnx(context, "Slave %s claims to not have " 920 "version we already sent to it", s->name); 921 s->version = tmp; 922 } 923 ret = send_diffs(server_context, s, log_fd, database, current_version, 924 current_tstamp); 925 break; 926 case I_AM_HERE : 927 if (verbose) 928 krb5_warnx(context, "slave %s is there", s->name); 929 break; 930 case ARE_YOU_THERE: 931 case FOR_YOU : 932 default : 933 krb5_warnx(context, "Ignoring command %d", tmp); 934 break; 935 } 936 937 krb5_data_free(&out); 938 krb5_storage_free(sp); 939 940 slave_seen(s); 941 942 return ret; 943 } 944 945 #define SLAVE_NAME "Name" 946 #define SLAVE_ADDRESS "Address" 947 #define SLAVE_VERSION "Version" 948 #define SLAVE_STATUS "Status" 949 #define SLAVE_SEEN "Last Seen" 950 951 static FILE * 952 open_stats(krb5_context context) 953 { 954 char *statfile = NULL; 955 const char *fn = NULL; 956 FILE *out = NULL; 957 958 /* 959 * krb5_config_get_string_default() returs default value as-is, 960 * delay free() of "statfile" until we're done with "fn". 961 */ 962 if (slave_stats_file) 963 fn = slave_stats_file; 964 else if (asprintf(&statfile, "%s/slaves-stats", hdb_db_dir(context)) != -1 965 && statfile != NULL) 966 fn = krb5_config_get_string_default(context, 967 NULL, 968 statfile, 969 "kdc", 970 "iprop-stats", 971 NULL); 972 if (fn != NULL) 973 out = fopen(fn, "w"); 974 if (statfile != NULL) 975 free(statfile); 976 return out; 977 } 978 979 static void 980 write_master_down(krb5_context context) 981 { 982 char str[100]; 983 time_t t = time(NULL); 984 FILE *fp; 985 986 fp = open_stats(context); 987 if (fp == NULL) 988 return; 989 krb5_format_time(context, t, str, sizeof(str), TRUE); 990 fprintf(fp, "master down at %s\n", str); 991 992 fclose(fp); 993 } 994 995 static void 996 write_stats(krb5_context context, slave *slaves, uint32_t current_version) 997 { 998 char str[100]; 999 rtbl_t tbl; 1000 time_t t = time(NULL); 1001 FILE *fp; 1002 1003 fp = open_stats(context); 1004 if (fp == NULL) 1005 return; 1006 1007 krb5_format_time(context, t, str, sizeof(str), TRUE); 1008 fprintf(fp, "Status for slaves, last updated: %s\n\n", str); 1009 1010 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version); 1011 1012 tbl = rtbl_create(); 1013 if (tbl == NULL) { 1014 fclose(fp); 1015 return; 1016 } 1017 1018 rtbl_add_column(tbl, SLAVE_NAME, 0); 1019 rtbl_add_column(tbl, SLAVE_ADDRESS, 0); 1020 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT); 1021 rtbl_add_column(tbl, SLAVE_STATUS, 0); 1022 rtbl_add_column(tbl, SLAVE_SEEN, 0); 1023 1024 rtbl_set_prefix(tbl, " "); 1025 rtbl_set_column_prefix(tbl, SLAVE_NAME, ""); 1026 1027 while (slaves) { 1028 krb5_address addr; 1029 krb5_error_code ret; 1030 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name); 1031 ret = krb5_sockaddr2address (context, 1032 (struct sockaddr*)&slaves->addr, &addr); 1033 if(ret == 0) { 1034 krb5_print_address(&addr, str, sizeof(str), NULL); 1035 krb5_free_address(context, &addr); 1036 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str); 1037 } else 1038 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>"); 1039 1040 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version); 1041 rtbl_add_column_entry(tbl, SLAVE_VERSION, str); 1042 1043 if (slaves->flags & SLAVE_F_DEAD) 1044 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down"); 1045 else 1046 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up"); 1047 1048 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE); 1049 rtbl_add_column_entry(tbl, SLAVE_SEEN, str); 1050 1051 slaves = slaves->next; 1052 } 1053 1054 rtbl_format(tbl, fp); 1055 rtbl_destroy(tbl); 1056 1057 fclose(fp); 1058 } 1059 1060 1061 static char sHDB[] = "HDBGET:"; 1062 static char *realm; 1063 static int version_flag; 1064 static int help_flag; 1065 static char *keytab_str = sHDB; 1066 static char *database; 1067 static char *config_file; 1068 static char *port_str; 1069 static int detach_from_console; 1070 static int daemon_child = -1; 1071 1072 static struct getargs args[] = { 1073 { "config-file", 'c', arg_string, &config_file, NULL, NULL }, 1074 { "realm", 'r', arg_string, &realm, NULL, NULL }, 1075 { "keytab", 'k', arg_string, &keytab_str, 1076 "keytab to get authentication from", "kspec" }, 1077 { "database", 'd', arg_string, &database, "database", "file"}, 1078 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file), 1079 "file for slave status information", "file"}, 1080 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing), 1081 "time before slave is polled for presence", "time"}, 1082 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone), 1083 "time of inactivity after which a slave is considered gone", "time"}, 1084 { "port", 0, arg_string, &port_str, 1085 "port ipropd will listen to", "port"}, 1086 { "detach", 0, arg_flag, &detach_from_console, 1087 "detach from console", NULL }, 1088 { "daemon-child", 0 , arg_integer, &daemon_child, 1089 "private argument, do not use", NULL }, 1090 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname), 1091 "hostname of master (if not same as hostname)", "hostname" }, 1092 { "verbose", 0, arg_flag, &verbose, NULL, NULL }, 1093 { "version", 0, arg_flag, &version_flag, NULL, NULL }, 1094 { "help", 0, arg_flag, &help_flag, NULL, NULL } 1095 }; 1096 static int num_args = sizeof(args) / sizeof(args[0]); 1097 1098 int 1099 main(int argc, char **argv) 1100 { 1101 krb5_error_code ret; 1102 krb5_context context; 1103 void *kadm_handle; 1104 kadm5_server_context *server_context; 1105 kadm5_config_params conf; 1106 krb5_socket_t signal_fd, listen_fd; 1107 int log_fd; 1108 slave *slaves = NULL; 1109 uint32_t current_version = 0, old_version = 0; 1110 uint32_t current_tstamp = 0; 1111 krb5_keytab keytab; 1112 char **files; 1113 int aret; 1114 int optidx = 0; 1115 int restarter_fd = -1; 1116 struct stat st; 1117 1118 setprogname(argv[0]); 1119 1120 if (getarg(args, num_args, argc, argv, &optidx)) 1121 krb5_std_usage(1, args, num_args); 1122 1123 if (help_flag) 1124 krb5_std_usage(0, args, num_args); 1125 1126 if (version_flag) { 1127 print_version(NULL); 1128 exit(0); 1129 } 1130 1131 if (detach_from_console && daemon_child == -1) 1132 roken_detach_prep(argc, argv, "--daemon-child"); 1133 rk_pidfile(NULL); 1134 1135 ret = krb5_init_context(&context); 1136 if (ret) 1137 errx(1, "krb5_init_context failed: %d", ret); 1138 1139 setup_signal(); 1140 1141 if (config_file == NULL) { 1142 aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); 1143 if (aret == -1 || config_file == NULL) 1144 errx(1, "out of memory"); 1145 } 1146 1147 ret = krb5_prepend_config_files_default(config_file, &files); 1148 if (ret) 1149 krb5_err(context, 1, ret, "getting configuration files"); 1150 1151 ret = krb5_set_config_files(context, files); 1152 krb5_free_config_files(files); 1153 if (ret) 1154 krb5_err(context, 1, ret, "reading configuration files"); 1155 1156 time_before_gone = parse_time (slave_time_gone, "s"); 1157 if (time_before_gone < 0) 1158 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone); 1159 time_before_missing = parse_time (slave_time_missing, "s"); 1160 if (time_before_missing < 0) 1161 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing); 1162 1163 krb5_openlog(context, "ipropd-master", &log_facility); 1164 krb5_set_warn_dest(context, log_facility); 1165 1166 ret = krb5_kt_register(context, &hdb_get_kt_ops); 1167 if(ret) 1168 krb5_err(context, 1, ret, "krb5_kt_register"); 1169 1170 ret = krb5_kt_resolve(context, keytab_str, &keytab); 1171 if(ret) 1172 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str); 1173 1174 memset(&conf, 0, sizeof(conf)); 1175 if(realm) { 1176 conf.mask |= KADM5_CONFIG_REALM; 1177 conf.realm = realm; 1178 } 1179 ret = kadm5_init_with_skey_ctx (context, 1180 KADM5_ADMIN_SERVICE, 1181 NULL, 1182 KADM5_ADMIN_SERVICE, 1183 &conf, 0, 0, 1184 &kadm_handle); 1185 if (ret) 1186 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 1187 1188 server_context = (kadm5_server_context *)kadm_handle; 1189 1190 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0); 1191 if (log_fd < 0) 1192 krb5_err (context, 1, errno, "open %s", 1193 server_context->log_context.log_file); 1194 1195 if (fstat(log_fd, &st) == -1) 1196 krb5_err(context, 1, errno, "stat %s", 1197 server_context->log_context.log_file); 1198 1199 if (flock(log_fd, LOCK_SH) == -1) 1200 krb5_err(context, 1, errno, "shared flock %s", 1201 server_context->log_context.log_file); 1202 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1203 ¤t_version, ¤t_tstamp); 1204 flock(log_fd, LOCK_UN); 1205 1206 signal_fd = make_signal_socket (context); 1207 listen_fd = make_listen_socket (context, port_str); 1208 1209 krb5_warnx(context, "ipropd-master started at version: %lu", 1210 (unsigned long)current_version); 1211 1212 roken_detach_finish(NULL, daemon_child); 1213 restarter_fd = restarter(context, NULL); 1214 1215 while (exit_flag == 0){ 1216 slave *p; 1217 fd_set readset; 1218 int max_fd = 0; 1219 struct timeval to = {30, 0}; 1220 uint32_t vers; 1221 struct stat st2;; 1222 1223 #ifndef NO_LIMIT_FD_SETSIZE 1224 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE || 1225 restarter_fd >= FD_SETSIZE) 1226 krb5_errx (context, IPROPD_RESTART, "fd too large"); 1227 #endif 1228 1229 FD_ZERO(&readset); 1230 FD_SET(signal_fd, &readset); 1231 max_fd = max(max_fd, signal_fd); 1232 FD_SET(listen_fd, &readset); 1233 max_fd = max(max_fd, listen_fd); 1234 if (restarter_fd > -1) { 1235 FD_SET(restarter_fd, &readset); 1236 max_fd = max(max_fd, restarter_fd); 1237 } 1238 1239 for (p = slaves; p != NULL; p = p->next) { 1240 if (p->flags & SLAVE_F_DEAD) 1241 continue; 1242 FD_SET(p->fd, &readset); 1243 max_fd = max(max_fd, p->fd); 1244 } 1245 1246 ret = select (max_fd + 1, 1247 &readset, NULL, NULL, &to); 1248 if (ret < 0) { 1249 if (errno == EINTR) 1250 continue; 1251 else 1252 krb5_err (context, IPROPD_RESTART, errno, "select"); 1253 } 1254 1255 if (stat(server_context->log_context.log_file, &st2) == -1) { 1256 krb5_warn(context, errno, "could not stat log file by path"); 1257 st2 = st; 1258 } 1259 1260 if (st2.st_dev != st.st_dev || st2.st_ino != st.st_ino) { 1261 (void) close(log_fd); 1262 1263 log_fd = open(server_context->log_context.log_file, O_RDONLY, 0); 1264 if (log_fd < 0) 1265 krb5_err(context, 1, IPROPD_RESTART_SLOW, "open %s", 1266 server_context->log_context.log_file); 1267 1268 if (fstat(log_fd, &st) == -1) 1269 krb5_err(context, IPROPD_RESTART_SLOW, errno, "stat %s", 1270 server_context->log_context.log_file); 1271 1272 if (flock(log_fd, LOCK_SH) == -1) 1273 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s", 1274 server_context->log_context.log_file); 1275 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1276 ¤t_version, ¤t_tstamp); 1277 flock(log_fd, LOCK_UN); 1278 } 1279 1280 if (ret == 0) { 1281 /* Recover from failed transactions */ 1282 if (kadm5_log_init_nb(server_context) == 0) 1283 kadm5_log_end(server_context); 1284 1285 if (flock(log_fd, LOCK_SH) == -1) 1286 krb5_err(context, IPROPD_RESTART, errno, 1287 "could not lock log file"); 1288 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1289 ¤t_version, ¤t_tstamp); 1290 flock(log_fd, LOCK_UN); 1291 1292 if (current_version > old_version) { 1293 krb5_warnx(context, 1294 "Missed a signal, updating slaves %lu to %lu", 1295 (unsigned long)old_version, 1296 (unsigned long)current_version); 1297 for (p = slaves; p != NULL; p = p->next) { 1298 if (p->flags & SLAVE_F_DEAD) 1299 continue; 1300 send_diffs (server_context, p, log_fd, database, 1301 current_version, current_tstamp); 1302 } 1303 old_version = current_version; 1304 } 1305 } 1306 1307 if (ret && FD_ISSET(restarter_fd, &readset)) { 1308 exit_flag = SIGTERM; 1309 break; 1310 } 1311 1312 if (ret && FD_ISSET(signal_fd, &readset)) { 1313 #ifndef NO_UNIX_SOCKETS 1314 struct sockaddr_un peer_addr; 1315 #else 1316 struct sockaddr_storage peer_addr; 1317 #endif 1318 socklen_t peer_len = sizeof(peer_addr); 1319 1320 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0, 1321 (struct sockaddr *)&peer_addr, &peer_len) < 0) { 1322 krb5_warn (context, errno, "recvfrom"); 1323 continue; 1324 } 1325 --ret; 1326 assert(ret >= 0); 1327 old_version = current_version; 1328 if (flock(log_fd, LOCK_SH) == -1) 1329 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s", 1330 server_context->log_context.log_file); 1331 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST, 1332 ¤t_version, ¤t_tstamp); 1333 flock(log_fd, LOCK_UN); 1334 if (current_version != old_version) { 1335 /* 1336 * If current_version < old_version then the log got 1337 * truncated and we'll end up doing full propagations. 1338 * 1339 * Truncating the log when the current version is 1340 * numerically small can lead to race conditions. 1341 * Ideally we should identify log versions as 1342 * {init_or_trunc_time, vno}, then we could not have any 1343 * such race conditions, but this would either require 1344 * breaking backwards compatibility for the protocol or 1345 * adding new messages to it. 1346 */ 1347 krb5_warnx(context, 1348 "Got a signal, updating slaves %lu to %lu", 1349 (unsigned long)old_version, 1350 (unsigned long)current_version); 1351 for (p = slaves; p != NULL; p = p->next) { 1352 if (p->flags & SLAVE_F_DEAD) 1353 continue; 1354 send_diffs (server_context, p, log_fd, database, 1355 current_version, current_tstamp); 1356 } 1357 } else { 1358 krb5_warnx(context, 1359 "Got a signal, but no update in log version %lu", 1360 (unsigned long)current_version); 1361 } 1362 } 1363 1364 for(p = slaves; p != NULL; p = p->next) { 1365 if (p->flags & SLAVE_F_DEAD) 1366 continue; 1367 if (ret && FD_ISSET(p->fd, &readset)) { 1368 --ret; 1369 assert(ret >= 0); 1370 if(process_msg (server_context, p, log_fd, database, 1371 current_version, current_tstamp)) 1372 slave_dead(context, p); 1373 } else if (slave_gone_p (p)) 1374 slave_dead(context, p); 1375 else if (slave_missing_p (p)) 1376 send_are_you_there (context, p); 1377 } 1378 1379 if (ret && FD_ISSET(listen_fd, &readset)) { 1380 add_slave (context, keytab, &slaves, listen_fd); 1381 --ret; 1382 assert(ret >= 0); 1383 } 1384 write_stats(context, slaves, current_version); 1385 } 1386 1387 if(exit_flag == SIGINT || exit_flag == SIGTERM) 1388 krb5_warnx(context, "%s terminated", getprogname()); 1389 #ifdef SIGXCPU 1390 else if(exit_flag == SIGXCPU) 1391 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 1392 #endif 1393 else 1394 krb5_warnx(context, "%s unexpected exit reason: %ld", 1395 getprogname(), (long)exit_flag); 1396 1397 write_master_down(context); 1398 1399 return 0; 1400 } 1401