1 /* 2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include "iprop.h" 35 #include <rtbl.h> 36 37 static krb5_log_facility *log_facility; 38 39 const char *slave_stats_file; 40 const char *slave_time_missing = "2 min"; 41 const char *slave_time_gone = "5 min"; 42 43 static int time_before_missing; 44 static int time_before_gone; 45 46 const char *master_hostname; 47 48 static krb5_socket_t 49 make_signal_socket (krb5_context context) 50 { 51 #ifndef NO_UNIX_SOCKETS 52 struct sockaddr_un addr; 53 const char *fn; 54 krb5_socket_t fd; 55 56 fn = kadm5_log_signal_socket(context); 57 58 fd = socket (AF_UNIX, SOCK_DGRAM, 0); 59 if (fd < 0) 60 krb5_err (context, 1, errno, "socket AF_UNIX"); 61 memset (&addr, 0, sizeof(addr)); 62 addr.sun_family = AF_UNIX; 63 strlcpy (addr.sun_path, fn, sizeof(addr.sun_path)); 64 unlink (addr.sun_path); 65 if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 66 krb5_err (context, 1, errno, "bind %s", addr.sun_path); 67 return fd; 68 #else 69 struct addrinfo *ai = NULL; 70 krb5_socket_t fd; 71 72 kadm5_log_signal_socket_info(context, 1, &ai); 73 74 fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 75 if (rk_IS_BAD_SOCKET(fd)) 76 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family); 77 78 if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) )) 79 krb5_err (context, 1, rk_SOCK_ERRNO, "bind"); 80 return fd; 81 #endif 82 } 83 84 static krb5_socket_t 85 make_listen_socket (krb5_context context, const char *port_str) 86 { 87 krb5_socket_t fd; 88 int one = 1; 89 struct sockaddr_in addr; 90 91 fd = socket (AF_INET, SOCK_STREAM, 0); 92 if (rk_IS_BAD_SOCKET(fd)) 93 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET"); 94 setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); 95 memset (&addr, 0, sizeof(addr)); 96 addr.sin_family = AF_INET; 97 98 if (port_str) { 99 addr.sin_port = krb5_getportbyname (context, 100 port_str, "tcp", 101 0); 102 if (addr.sin_port == 0) { 103 char *ptr; 104 long port; 105 106 port = strtol (port_str, &ptr, 10); 107 if (port == 0 && ptr == port_str) 108 krb5_errx (context, 1, "bad port `%s'", port_str); 109 addr.sin_port = htons(port); 110 } 111 } else { 112 addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE, 113 "tcp", IPROP_PORT); 114 } 115 if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 116 krb5_err (context, 1, errno, "bind"); 117 if (listen(fd, SOMAXCONN) < 0) 118 krb5_err (context, 1, errno, "listen"); 119 return fd; 120 } 121 122 struct slave { 123 krb5_socket_t fd; 124 struct sockaddr_in addr; 125 char *name; 126 krb5_auth_context ac; 127 uint32_t version; 128 time_t seen; 129 unsigned long flags; 130 #define SLAVE_F_DEAD 0x1 131 #define SLAVE_F_AYT 0x2 132 struct slave *next; 133 }; 134 135 typedef struct slave slave; 136 137 static int 138 check_acl (krb5_context context, const char *name) 139 { 140 const char *fn; 141 FILE *fp; 142 char buf[256]; 143 int ret = 1; 144 char *slavefile = NULL; 145 146 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1 147 || slavefile == NULL) 148 errx(1, "out of memory"); 149 150 fn = krb5_config_get_string_default(context, 151 NULL, 152 slavefile, 153 "kdc", 154 "iprop-acl", 155 NULL); 156 157 fp = fopen (fn, "r"); 158 free(slavefile); 159 if (fp == NULL) 160 return 1; 161 while (fgets(buf, sizeof(buf), fp) != NULL) { 162 buf[strcspn(buf, "\r\n")] = '\0'; 163 if (strcmp (buf, name) == 0) { 164 ret = 0; 165 break; 166 } 167 } 168 fclose (fp); 169 return ret; 170 } 171 172 static void 173 slave_seen(slave *s) 174 { 175 s->flags &= ~SLAVE_F_AYT; 176 s->seen = time(NULL); 177 } 178 179 static int 180 slave_missing_p (slave *s) 181 { 182 if (time(NULL) > s->seen + time_before_missing) 183 return 1; 184 return 0; 185 } 186 187 static int 188 slave_gone_p (slave *s) 189 { 190 if (time(NULL) > s->seen + time_before_gone) 191 return 1; 192 return 0; 193 } 194 195 static void 196 slave_dead(krb5_context context, slave *s) 197 { 198 krb5_warnx(context, "slave %s dead", s->name); 199 200 if (!rk_IS_BAD_SOCKET(s->fd)) { 201 rk_closesocket (s->fd); 202 s->fd = rk_INVALID_SOCKET; 203 } 204 s->flags |= SLAVE_F_DEAD; 205 slave_seen(s); 206 } 207 208 static void 209 remove_slave (krb5_context context, slave *s, slave **root) 210 { 211 slave **p; 212 213 if (!rk_IS_BAD_SOCKET(s->fd)) 214 rk_closesocket (s->fd); 215 if (s->name) 216 free (s->name); 217 if (s->ac) 218 krb5_auth_con_free (context, s->ac); 219 220 for (p = root; *p; p = &(*p)->next) 221 if (*p == s) { 222 *p = s->next; 223 break; 224 } 225 free (s); 226 } 227 228 static void 229 add_slave (krb5_context context, krb5_keytab keytab, slave **root, 230 krb5_socket_t fd) 231 { 232 krb5_principal server; 233 krb5_error_code ret; 234 slave *s; 235 socklen_t addr_len; 236 krb5_ticket *ticket = NULL; 237 char hostname[128]; 238 239 s = malloc(sizeof(*s)); 240 if (s == NULL) { 241 krb5_warnx (context, "add_slave: no memory"); 242 return; 243 } 244 s->name = NULL; 245 s->ac = NULL; 246 247 addr_len = sizeof(s->addr); 248 s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len); 249 if (rk_IS_BAD_SOCKET(s->fd)) { 250 krb5_warn (context, rk_SOCK_ERRNO, "accept"); 251 goto error; 252 } 253 if (master_hostname) 254 strlcpy(hostname, master_hostname, sizeof(hostname)); 255 else 256 gethostname(hostname, sizeof(hostname)); 257 258 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME, 259 KRB5_NT_SRV_HST, &server); 260 if (ret) { 261 krb5_warn (context, ret, "krb5_sname_to_principal"); 262 goto error; 263 } 264 265 ret = krb5_recvauth (context, &s->ac, &s->fd, 266 IPROP_VERSION, server, 0, keytab, &ticket); 267 krb5_free_principal (context, server); 268 if (ret) { 269 krb5_warn (context, ret, "krb5_recvauth"); 270 goto error; 271 } 272 ret = krb5_unparse_name (context, ticket->client, &s->name); 273 krb5_free_ticket (context, ticket); 274 if (ret) { 275 krb5_warn (context, ret, "krb5_unparse_name"); 276 goto error; 277 } 278 if (check_acl (context, s->name)) { 279 krb5_warnx (context, "%s not in acl", s->name); 280 goto error; 281 } 282 283 { 284 slave *l = *root; 285 286 while (l) { 287 if (strcmp(l->name, s->name) == 0) 288 break; 289 l = l->next; 290 } 291 if (l) { 292 if (l->flags & SLAVE_F_DEAD) { 293 remove_slave(context, l, root); 294 } else { 295 krb5_warnx (context, "second connection from %s", s->name); 296 goto error; 297 } 298 } 299 } 300 301 krb5_warnx (context, "connection from %s", s->name); 302 303 s->version = 0; 304 s->flags = 0; 305 slave_seen(s); 306 s->next = *root; 307 *root = s; 308 return; 309 error: 310 remove_slave(context, s, root); 311 } 312 313 struct prop_context { 314 krb5_auth_context auth_context; 315 krb5_socket_t fd; 316 }; 317 318 static int 319 prop_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v) 320 { 321 krb5_error_code ret; 322 krb5_storage *sp; 323 krb5_data data; 324 struct slave *s = (struct slave *)v; 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 krb5_data_free (&data); 332 return ret; 333 } 334 memmove ((char *)data.data + 4, data.data, data.length - 4); 335 sp = krb5_storage_from_data(&data); 336 if (sp == NULL) { 337 krb5_data_free (&data); 338 return ENOMEM; 339 } 340 krb5_store_int32(sp, ONE_PRINC); 341 krb5_storage_free(sp); 342 343 ret = krb5_write_priv_message (context, s->ac, &s->fd, &data); 344 krb5_data_free (&data); 345 return ret; 346 } 347 348 static int 349 send_complete (krb5_context context, slave *s, 350 const char *database, uint32_t current_version) 351 { 352 krb5_error_code ret; 353 krb5_storage *sp; 354 HDB *db; 355 krb5_data data; 356 char buf[8]; 357 358 ret = hdb_create (context, &db, database); 359 if (ret) 360 krb5_err (context, 1, ret, "hdb_create: %s", database); 361 ret = db->hdb_open (context, db, O_RDONLY, 0); 362 if (ret) 363 krb5_err (context, 1, ret, "db->open"); 364 365 sp = krb5_storage_from_mem (buf, 4); 366 if (sp == NULL) 367 krb5_errx (context, 1, "krb5_storage_from_mem"); 368 krb5_store_int32 (sp, TELL_YOU_EVERYTHING); 369 krb5_storage_free (sp); 370 371 data.data = buf; 372 data.length = 4; 373 374 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 375 376 if (ret) { 377 krb5_warn (context, ret, "krb5_write_priv_message"); 378 slave_dead(context, s); 379 return ret; 380 } 381 382 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, prop_one, s); 383 if (ret) { 384 krb5_warn (context, ret, "hdb_foreach"); 385 slave_dead(context, s); 386 return ret; 387 } 388 389 (*db->hdb_close)(context, db); 390 (*db->hdb_destroy)(context, db); 391 392 sp = krb5_storage_from_mem (buf, 8); 393 if (sp == NULL) 394 krb5_errx (context, 1, "krb5_storage_from_mem"); 395 krb5_store_int32 (sp, NOW_YOU_HAVE); 396 krb5_store_int32 (sp, current_version); 397 krb5_storage_free (sp); 398 399 data.length = 8; 400 401 s->version = current_version; 402 403 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 404 if (ret) { 405 slave_dead(context, s); 406 krb5_warn (context, ret, "krb5_write_priv_message"); 407 return ret; 408 } 409 410 slave_seen(s); 411 412 return 0; 413 } 414 415 static int 416 send_are_you_there (krb5_context context, slave *s) 417 { 418 krb5_storage *sp; 419 krb5_data data; 420 char buf[4]; 421 int ret; 422 423 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT)) 424 return 0; 425 426 krb5_warnx(context, "slave %s missing, sending AYT", s->name); 427 428 s->flags |= SLAVE_F_AYT; 429 430 data.data = buf; 431 data.length = 4; 432 433 sp = krb5_storage_from_mem (buf, 4); 434 if (sp == NULL) { 435 krb5_warnx (context, "are_you_there: krb5_data_alloc"); 436 slave_dead(context, s); 437 return 1; 438 } 439 krb5_store_int32 (sp, ARE_YOU_THERE); 440 krb5_storage_free (sp); 441 442 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 443 444 if (ret) { 445 krb5_warn (context, ret, "are_you_there: krb5_write_priv_message"); 446 slave_dead(context, s); 447 return 1; 448 } 449 450 return 0; 451 } 452 453 static int 454 send_diffs (krb5_context context, slave *s, int log_fd, 455 const char *database, uint32_t current_version) 456 { 457 krb5_storage *sp; 458 uint32_t ver; 459 time_t timestamp; 460 enum kadm_ops op; 461 uint32_t len; 462 off_t right, left; 463 krb5_data data; 464 int ret = 0; 465 466 if (s->version == current_version) { 467 krb5_warnx(context, "slave %s in sync already at version %ld", 468 s->name, (long)s->version); 469 return 0; 470 } 471 472 if (s->flags & SLAVE_F_DEAD) 473 return 0; 474 475 /* if slave is a fresh client, starting over */ 476 if (s->version == 0) { 477 krb5_warnx(context, "sending complete log to fresh slave %s", 478 s->name); 479 return send_complete (context, s, database, current_version); 480 } 481 482 sp = kadm5_log_goto_end (log_fd); 483 right = krb5_storage_seek(sp, 0, SEEK_CUR); 484 for (;;) { 485 ret = kadm5_log_previous (context, sp, &ver, ×tamp, &op, &len); 486 if (ret) 487 krb5_err(context, 1, ret, 488 "send_diffs: failed to find previous entry"); 489 left = krb5_storage_seek(sp, -16, SEEK_CUR); 490 if (ver == s->version) 491 return 0; 492 if (ver == s->version + 1) 493 break; 494 if (left == 0) { 495 krb5_storage_free(sp); 496 krb5_warnx(context, 497 "slave %s (version %lu) out of sync with master " 498 "(first version in log %lu), sending complete database", 499 s->name, (unsigned long)s->version, (unsigned long)ver); 500 return send_complete (context, s, database, current_version); 501 } 502 } 503 504 krb5_warnx(context, 505 "syncing slave %s from version %lu to version %lu", 506 s->name, (unsigned long)s->version, 507 (unsigned long)current_version); 508 509 ret = krb5_data_alloc (&data, right - left + 4); 510 if (ret) { 511 krb5_storage_free(sp); 512 krb5_warn (context, ret, "send_diffs: krb5_data_alloc"); 513 slave_dead(context, s); 514 return 1; 515 } 516 krb5_storage_read (sp, (char *)data.data + 4, data.length - 4); 517 krb5_storage_free(sp); 518 519 sp = krb5_storage_from_data (&data); 520 if (sp == NULL) { 521 krb5_warnx (context, "send_diffs: krb5_storage_from_data"); 522 slave_dead(context, s); 523 return 1; 524 } 525 krb5_store_int32 (sp, FOR_YOU); 526 krb5_storage_free(sp); 527 528 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 529 krb5_data_free(&data); 530 531 if (ret) { 532 krb5_warn (context, ret, "send_diffs: krb5_write_priv_message"); 533 slave_dead(context, s); 534 return 1; 535 } 536 slave_seen(s); 537 538 s->version = current_version; 539 540 return 0; 541 } 542 543 static int 544 process_msg (krb5_context context, slave *s, int log_fd, 545 const char *database, uint32_t current_version) 546 { 547 int ret = 0; 548 krb5_data out; 549 krb5_storage *sp; 550 int32_t tmp; 551 552 ret = krb5_read_priv_message(context, s->ac, &s->fd, &out); 553 if(ret) { 554 krb5_warn (context, ret, "error reading message from %s", s->name); 555 return 1; 556 } 557 558 sp = krb5_storage_from_mem (out.data, out.length); 559 if (sp == NULL) { 560 krb5_warnx (context, "process_msg: no memory"); 561 krb5_data_free (&out); 562 return 1; 563 } 564 if (krb5_ret_int32 (sp, &tmp) != 0) { 565 krb5_warnx (context, "process_msg: client send too short command"); 566 krb5_data_free (&out); 567 return 1; 568 } 569 switch (tmp) { 570 case I_HAVE : 571 ret = krb5_ret_int32 (sp, &tmp); 572 if (ret != 0) { 573 krb5_warnx (context, "process_msg: client send too I_HAVE data"); 574 break; 575 } 576 /* new started slave that have old log */ 577 if (s->version == 0 && tmp != 0) { 578 if (current_version < (uint32_t)tmp) { 579 krb5_warnx (context, "Slave %s (version %lu) have later version " 580 "the master (version %lu) OUT OF SYNC", 581 s->name, (unsigned long)tmp, 582 (unsigned long)current_version); 583 } 584 s->version = tmp; 585 } 586 if ((uint32_t)tmp < s->version) { 587 krb5_warnx (context, "Slave claims to not have " 588 "version we already sent to it"); 589 } else { 590 ret = send_diffs (context, s, log_fd, database, current_version); 591 } 592 break; 593 case I_AM_HERE : 594 break; 595 case ARE_YOU_THERE: 596 case FOR_YOU : 597 default : 598 krb5_warnx (context, "Ignoring command %d", tmp); 599 break; 600 } 601 602 krb5_data_free (&out); 603 krb5_storage_free (sp); 604 605 slave_seen(s); 606 607 return ret; 608 } 609 610 #define SLAVE_NAME "Name" 611 #define SLAVE_ADDRESS "Address" 612 #define SLAVE_VERSION "Version" 613 #define SLAVE_STATUS "Status" 614 #define SLAVE_SEEN "Last Seen" 615 616 static FILE * 617 open_stats(krb5_context context) 618 { 619 char *statfile = NULL; 620 const char *fn; 621 FILE *f; 622 623 if (slave_stats_file) 624 fn = slave_stats_file; 625 else { 626 asprintf(&statfile, "%s/slaves-stats", hdb_db_dir(context)); 627 fn = krb5_config_get_string_default(context, 628 NULL, 629 statfile, 630 "kdc", 631 "iprop-stats", 632 NULL); 633 } 634 f = fopen(fn, "w"); 635 if (statfile) 636 free(statfile); 637 638 return f; 639 } 640 641 static void 642 write_master_down(krb5_context context) 643 { 644 char str[100]; 645 time_t t = time(NULL); 646 FILE *fp; 647 648 fp = open_stats(context); 649 if (fp == NULL) 650 return; 651 krb5_format_time(context, t, str, sizeof(str), TRUE); 652 fprintf(fp, "master down at %s\n", str); 653 654 fclose(fp); 655 } 656 657 static void 658 write_stats(krb5_context context, slave *slaves, uint32_t current_version) 659 { 660 char str[100]; 661 rtbl_t tbl; 662 time_t t = time(NULL); 663 FILE *fp; 664 665 fp = open_stats(context); 666 if (fp == NULL) 667 return; 668 669 krb5_format_time(context, t, str, sizeof(str), TRUE); 670 fprintf(fp, "Status for slaves, last updated: %s\n\n", str); 671 672 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version); 673 674 tbl = rtbl_create(); 675 if (tbl == NULL) { 676 fclose(fp); 677 return; 678 } 679 680 rtbl_add_column(tbl, SLAVE_NAME, 0); 681 rtbl_add_column(tbl, SLAVE_ADDRESS, 0); 682 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT); 683 rtbl_add_column(tbl, SLAVE_STATUS, 0); 684 rtbl_add_column(tbl, SLAVE_SEEN, 0); 685 686 rtbl_set_prefix(tbl, " "); 687 rtbl_set_column_prefix(tbl, SLAVE_NAME, ""); 688 689 while (slaves) { 690 krb5_address addr; 691 krb5_error_code ret; 692 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name); 693 ret = krb5_sockaddr2address (context, 694 (struct sockaddr*)&slaves->addr, &addr); 695 if(ret == 0) { 696 krb5_print_address(&addr, str, sizeof(str), NULL); 697 krb5_free_address(context, &addr); 698 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str); 699 } else 700 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>"); 701 702 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version); 703 rtbl_add_column_entry(tbl, SLAVE_VERSION, str); 704 705 if (slaves->flags & SLAVE_F_DEAD) 706 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down"); 707 else 708 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up"); 709 710 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE); 711 rtbl_add_column_entry(tbl, SLAVE_SEEN, str); 712 713 slaves = slaves->next; 714 } 715 716 rtbl_format(tbl, fp); 717 rtbl_destroy(tbl); 718 719 fclose(fp); 720 } 721 722 723 static char sHDB[] = "HDB:"; 724 static char *realm; 725 static int version_flag; 726 static int help_flag; 727 static char *keytab_str = sHDB; 728 static char *database; 729 static char *config_file; 730 static char *port_str; 731 #ifdef SUPPORT_DETACH 732 static int detach_from_console = 0; 733 #endif 734 735 static struct getargs args[] = { 736 { "config-file", 'c', arg_string, &config_file, NULL, NULL }, 737 { "realm", 'r', arg_string, &realm, NULL, NULL }, 738 { "keytab", 'k', arg_string, &keytab_str, 739 "keytab to get authentication from", "kspec" }, 740 { "database", 'd', arg_string, &database, "database", "file"}, 741 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file), 742 "file for slave status information", "file"}, 743 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing), 744 "time before slave is polled for presence", "time"}, 745 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone), 746 "time of inactivity after which a slave is considered gone", "time"}, 747 { "port", 0, arg_string, &port_str, 748 "port ipropd will listen to", "port"}, 749 #ifdef SUPPORT_DETACH 750 { "detach", 0, arg_flag, &detach_from_console, 751 "detach from console", NULL }, 752 #endif 753 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname), 754 "hostname of master (if not same as hostname)", "hostname" }, 755 { "version", 0, arg_flag, &version_flag, NULL, NULL }, 756 { "help", 0, arg_flag, &help_flag, NULL, NULL } 757 }; 758 static int num_args = sizeof(args) / sizeof(args[0]); 759 760 int 761 main(int argc, char **argv) 762 { 763 krb5_error_code ret; 764 krb5_context context; 765 void *kadm_handle; 766 kadm5_server_context *server_context; 767 kadm5_config_params conf; 768 krb5_socket_t signal_fd, listen_fd; 769 int log_fd; 770 slave *slaves = NULL; 771 uint32_t current_version = 0, old_version = 0; 772 krb5_keytab keytab; 773 int optidx; 774 char **files; 775 776 optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL); 777 778 if(help_flag) 779 krb5_std_usage(0, args, num_args); 780 if(version_flag) { 781 print_version(NULL); 782 exit(0); 783 } 784 785 setup_signal(); 786 787 if (config_file == NULL) { 788 asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); 789 if (config_file == NULL) 790 errx(1, "out of memory"); 791 } 792 793 ret = krb5_prepend_config_files_default(config_file, &files); 794 if (ret) 795 krb5_err(context, 1, ret, "getting configuration files"); 796 797 ret = krb5_set_config_files(context, files); 798 krb5_free_config_files(files); 799 if (ret) 800 krb5_err(context, 1, ret, "reading configuration files"); 801 802 time_before_gone = parse_time (slave_time_gone, "s"); 803 if (time_before_gone < 0) 804 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone); 805 time_before_missing = parse_time (slave_time_missing, "s"); 806 if (time_before_missing < 0) 807 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing); 808 809 #ifdef SUPPORT_DETACH 810 if (detach_from_console) 811 daemon(0, 0); 812 #endif 813 pidfile (NULL); 814 krb5_openlog (context, "ipropd-master", &log_facility); 815 krb5_set_warn_dest(context, log_facility); 816 817 ret = krb5_kt_register(context, &hdb_kt_ops); 818 if(ret) 819 krb5_err(context, 1, ret, "krb5_kt_register"); 820 821 ret = krb5_kt_resolve(context, keytab_str, &keytab); 822 if(ret) 823 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str); 824 825 memset(&conf, 0, sizeof(conf)); 826 if(realm) { 827 conf.mask |= KADM5_CONFIG_REALM; 828 conf.realm = realm; 829 } 830 ret = kadm5_init_with_skey_ctx (context, 831 KADM5_ADMIN_SERVICE, 832 NULL, 833 KADM5_ADMIN_SERVICE, 834 &conf, 0, 0, 835 &kadm_handle); 836 if (ret) 837 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 838 839 server_context = (kadm5_server_context *)kadm_handle; 840 841 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0); 842 if (log_fd < 0) 843 krb5_err (context, 1, errno, "open %s", 844 server_context->log_context.log_file); 845 846 signal_fd = make_signal_socket (context); 847 listen_fd = make_listen_socket (context, port_str); 848 849 kadm5_log_get_version_fd (log_fd, ¤t_version); 850 851 krb5_warnx(context, "ipropd-master started at version: %lu", 852 (unsigned long)current_version); 853 854 while(exit_flag == 0){ 855 slave *p; 856 fd_set readset; 857 int max_fd = 0; 858 struct timeval to = {30, 0}; 859 uint32_t vers; 860 861 #ifndef NO_LIMIT_FD_SETSIZE 862 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE) 863 krb5_errx (context, 1, "fd too large"); 864 #endif 865 866 FD_ZERO(&readset); 867 FD_SET(signal_fd, &readset); 868 max_fd = max(max_fd, signal_fd); 869 FD_SET(listen_fd, &readset); 870 max_fd = max(max_fd, listen_fd); 871 872 for (p = slaves; p != NULL; p = p->next) { 873 if (p->flags & SLAVE_F_DEAD) 874 continue; 875 FD_SET(p->fd, &readset); 876 max_fd = max(max_fd, p->fd); 877 } 878 879 ret = select (max_fd + 1, 880 &readset, NULL, NULL, &to); 881 if (ret < 0) { 882 if (errno == EINTR) 883 continue; 884 else 885 krb5_err (context, 1, errno, "select"); 886 } 887 888 if (ret == 0) { 889 old_version = current_version; 890 kadm5_log_get_version_fd (log_fd, ¤t_version); 891 892 if (current_version > old_version) { 893 krb5_warnx(context, 894 "Missed a signal, updating slaves %lu to %lu", 895 (unsigned long)old_version, 896 (unsigned long)current_version); 897 for (p = slaves; p != NULL; p = p->next) { 898 if (p->flags & SLAVE_F_DEAD) 899 continue; 900 send_diffs (context, p, log_fd, database, current_version); 901 } 902 } 903 } 904 905 if (ret && FD_ISSET(signal_fd, &readset)) { 906 #ifndef NO_UNIX_SOCKETS 907 struct sockaddr_un peer_addr; 908 #else 909 struct sockaddr_storage peer_addr; 910 #endif 911 socklen_t peer_len = sizeof(peer_addr); 912 913 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0, 914 (struct sockaddr *)&peer_addr, &peer_len) < 0) { 915 krb5_warn (context, errno, "recvfrom"); 916 continue; 917 } 918 --ret; 919 assert(ret >= 0); 920 old_version = current_version; 921 kadm5_log_get_version_fd (log_fd, ¤t_version); 922 if (current_version > old_version) { 923 krb5_warnx(context, 924 "Got a signal, updating slaves %lu to %lu", 925 (unsigned long)old_version, 926 (unsigned long)current_version); 927 for (p = slaves; p != NULL; p = p->next) { 928 if (p->flags & SLAVE_F_DEAD) 929 continue; 930 send_diffs (context, p, log_fd, database, current_version); 931 } 932 } else { 933 krb5_warnx(context, 934 "Got a signal, but no update in log version %lu", 935 (unsigned long)current_version); 936 } 937 } 938 939 for(p = slaves; p != NULL; p = p->next) { 940 if (p->flags & SLAVE_F_DEAD) 941 continue; 942 if (ret && FD_ISSET(p->fd, &readset)) { 943 --ret; 944 assert(ret >= 0); 945 if(process_msg (context, p, log_fd, database, current_version)) 946 slave_dead(context, p); 947 } else if (slave_gone_p (p)) 948 slave_dead(context, p); 949 else if (slave_missing_p (p)) 950 send_are_you_there (context, p); 951 } 952 953 if (ret && FD_ISSET(listen_fd, &readset)) { 954 add_slave (context, keytab, &slaves, listen_fd); 955 --ret; 956 assert(ret >= 0); 957 } 958 write_stats(context, slaves, current_version); 959 } 960 961 if(exit_flag == SIGINT || exit_flag == SIGTERM) 962 krb5_warnx(context, "%s terminated", getprogname()); 963 #ifdef SIGXCPU 964 else if(exit_flag == SIGXCPU) 965 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 966 #endif 967 else 968 krb5_warnx(context, "%s unexpected exit reason: %ld", 969 getprogname(), (long)exit_flag); 970 971 write_master_down(context); 972 973 return 0; 974 } 975