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 static krb5_socket_t 123 make_listen6_socket (krb5_context context, const char *port_str) 124 { 125 krb5_socket_t fd; 126 int one = 1; 127 struct sockaddr_in6 addr; 128 129 fd = socket (AF_INET6, SOCK_STREAM, 0); 130 if (rk_IS_BAD_SOCKET(fd)) 131 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET6"); 132 setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); 133 memset (&addr, 0, sizeof(addr)); 134 addr.sin6_family = AF_INET6; 135 136 if (port_str) { 137 addr.sin6_port = krb5_getportbyname (context, 138 port_str, "tcp", 139 0); 140 if (addr.sin6_port == 0) { 141 char *ptr; 142 long port; 143 144 port = strtol (port_str, &ptr, 10); 145 if (port == 0 && ptr == port_str) 146 krb5_errx (context, 1, "bad port `%s'", port_str); 147 addr.sin6_port = htons(port); 148 } 149 } else { 150 addr.sin6_port = krb5_getportbyname (context, IPROP_SERVICE, 151 "tcp", IPROP_PORT); 152 } 153 if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 154 krb5_err (context, 1, errno, "bind6"); 155 if (listen(fd, SOMAXCONN) < 0) 156 krb5_err (context, 1, errno, "listen6"); 157 return fd; 158 } 159 160 #ifndef _SOCKADDR_UNION 161 #define _SOCKADDR_UNION 162 union sockaddr_union { 163 struct sockaddr sa; 164 struct sockaddr_in sin; 165 struct sockaddr_in6 sin6; 166 }; 167 #endif /* _SOCKADDR_UNION */ 168 169 struct slave { 170 krb5_socket_t fd; 171 union sockaddr_union addr; 172 char *name; 173 krb5_auth_context ac; 174 uint32_t version; 175 time_t seen; 176 unsigned long flags; 177 #define SLAVE_F_DEAD 0x1 178 #define SLAVE_F_AYT 0x2 179 struct slave *next; 180 }; 181 182 typedef struct slave slave; 183 184 static int 185 check_acl (krb5_context context, const char *name) 186 { 187 const char *fn; 188 FILE *fp; 189 char buf[256]; 190 int ret = 1; 191 char *slavefile = NULL; 192 193 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1 194 || slavefile == NULL) 195 errx(1, "out of memory"); 196 197 fn = krb5_config_get_string_default(context, 198 NULL, 199 slavefile, 200 "kdc", 201 "iprop-acl", 202 NULL); 203 204 fp = fopen (fn, "r"); 205 free(slavefile); 206 if (fp == NULL) 207 return 1; 208 while (fgets(buf, sizeof(buf), fp) != NULL) { 209 buf[strcspn(buf, "\r\n")] = '\0'; 210 if (strcmp (buf, name) == 0) { 211 ret = 0; 212 break; 213 } 214 } 215 fclose (fp); 216 return ret; 217 } 218 219 static void 220 slave_seen(slave *s) 221 { 222 s->flags &= ~SLAVE_F_AYT; 223 s->seen = time(NULL); 224 } 225 226 static int 227 slave_missing_p (slave *s) 228 { 229 if (time(NULL) > s->seen + time_before_missing) 230 return 1; 231 return 0; 232 } 233 234 static int 235 slave_gone_p (slave *s) 236 { 237 if (time(NULL) > s->seen + time_before_gone) 238 return 1; 239 return 0; 240 } 241 242 static void 243 slave_dead(krb5_context context, slave *s) 244 { 245 krb5_warnx(context, "slave %s dead", s->name); 246 247 if (!rk_IS_BAD_SOCKET(s->fd)) { 248 rk_closesocket (s->fd); 249 s->fd = rk_INVALID_SOCKET; 250 } 251 s->flags |= SLAVE_F_DEAD; 252 slave_seen(s); 253 } 254 255 static void 256 remove_slave (krb5_context context, slave *s, slave **root) 257 { 258 slave **p; 259 260 if (!rk_IS_BAD_SOCKET(s->fd)) 261 rk_closesocket (s->fd); 262 if (s->name) 263 free (s->name); 264 if (s->ac) 265 krb5_auth_con_free (context, s->ac); 266 267 for (p = root; *p; p = &(*p)->next) 268 if (*p == s) { 269 *p = s->next; 270 break; 271 } 272 free (s); 273 } 274 275 static void 276 add_slave (krb5_context context, krb5_keytab keytab, slave **root, 277 krb5_socket_t fd) 278 { 279 krb5_principal server; 280 krb5_error_code ret; 281 slave *s; 282 socklen_t addr_len; 283 krb5_ticket *ticket = NULL; 284 char hostname[128]; 285 286 s = malloc(sizeof(*s)); 287 if (s == NULL) { 288 krb5_warnx (context, "add_slave: no memory"); 289 return; 290 } 291 s->name = NULL; 292 s->ac = NULL; 293 294 addr_len = sizeof(s->addr.sin6); 295 s->fd = accept (fd, (struct sockaddr *)&s->addr.sa, &addr_len); 296 if (rk_IS_BAD_SOCKET(s->fd)) { 297 krb5_warn (context, rk_SOCK_ERRNO, "accept"); 298 goto error; 299 } 300 if (master_hostname) 301 strlcpy(hostname, master_hostname, sizeof(hostname)); 302 else 303 gethostname(hostname, sizeof(hostname)); 304 305 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME, 306 KRB5_NT_SRV_HST, &server); 307 if (ret) { 308 krb5_warn (context, ret, "krb5_sname_to_principal"); 309 goto error; 310 } 311 312 ret = krb5_recvauth (context, &s->ac, &s->fd, 313 IPROP_VERSION, server, 0, keytab, &ticket); 314 krb5_free_principal (context, server); 315 if (ret) { 316 krb5_warn (context, ret, "krb5_recvauth"); 317 goto error; 318 } 319 ret = krb5_unparse_name (context, ticket->client, &s->name); 320 krb5_free_ticket (context, ticket); 321 if (ret) { 322 krb5_warn (context, ret, "krb5_unparse_name"); 323 goto error; 324 } 325 if (check_acl (context, s->name)) { 326 krb5_warnx (context, "%s not in acl", s->name); 327 goto error; 328 } 329 330 { 331 slave *l = *root; 332 333 while (l) { 334 if (strcmp(l->name, s->name) == 0) 335 break; 336 l = l->next; 337 } 338 if (l) { 339 if (l->flags & SLAVE_F_DEAD) { 340 remove_slave(context, l, root); 341 } else { 342 krb5_warnx (context, "second connection from %s", s->name); 343 goto error; 344 } 345 } 346 } 347 348 krb5_warnx (context, "connection from %s", s->name); 349 350 s->version = 0; 351 s->flags = 0; 352 slave_seen(s); 353 s->next = *root; 354 *root = s; 355 return; 356 error: 357 remove_slave(context, s, root); 358 } 359 360 struct prop_context { 361 krb5_auth_context auth_context; 362 krb5_socket_t fd; 363 }; 364 365 static int 366 prop_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v) 367 { 368 krb5_error_code ret; 369 krb5_storage *sp; 370 krb5_data data; 371 struct slave *s = (struct slave *)v; 372 373 ret = hdb_entry2value (context, &entry->entry, &data); 374 if (ret) 375 return ret; 376 ret = krb5_data_realloc (&data, data.length + 4); 377 if (ret) { 378 krb5_data_free (&data); 379 return ret; 380 } 381 memmove ((char *)data.data + 4, data.data, data.length - 4); 382 sp = krb5_storage_from_data(&data); 383 if (sp == NULL) { 384 krb5_data_free (&data); 385 return ENOMEM; 386 } 387 krb5_store_int32(sp, ONE_PRINC); 388 krb5_storage_free(sp); 389 390 ret = krb5_write_priv_message (context, s->ac, &s->fd, &data); 391 krb5_data_free (&data); 392 return ret; 393 } 394 395 static int 396 send_complete (krb5_context context, slave *s, 397 const char *database, uint32_t current_version) 398 { 399 krb5_error_code ret; 400 krb5_storage *sp; 401 HDB *db; 402 krb5_data data; 403 char buf[8]; 404 405 ret = hdb_create (context, &db, database); 406 if (ret) 407 krb5_err (context, 1, ret, "hdb_create: %s", database); 408 ret = db->hdb_open (context, db, O_RDONLY, 0); 409 if (ret) 410 krb5_err (context, 1, ret, "db->open"); 411 412 sp = krb5_storage_from_mem (buf, 4); 413 if (sp == NULL) 414 krb5_errx (context, 1, "krb5_storage_from_mem"); 415 krb5_store_int32 (sp, TELL_YOU_EVERYTHING); 416 krb5_storage_free (sp); 417 418 data.data = buf; 419 data.length = 4; 420 421 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 422 423 if (ret) { 424 krb5_warn (context, ret, "krb5_write_priv_message"); 425 slave_dead(context, s); 426 return ret; 427 } 428 429 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, prop_one, s); 430 if (ret) { 431 krb5_warn (context, ret, "hdb_foreach"); 432 slave_dead(context, s); 433 return ret; 434 } 435 436 (*db->hdb_close)(context, db); 437 (*db->hdb_destroy)(context, db); 438 439 sp = krb5_storage_from_mem (buf, 8); 440 if (sp == NULL) 441 krb5_errx (context, 1, "krb5_storage_from_mem"); 442 krb5_store_int32 (sp, NOW_YOU_HAVE); 443 krb5_store_int32 (sp, current_version); 444 krb5_storage_free (sp); 445 446 data.length = 8; 447 448 s->version = current_version; 449 450 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 451 if (ret) { 452 slave_dead(context, s); 453 krb5_warn (context, ret, "krb5_write_priv_message"); 454 return ret; 455 } 456 457 slave_seen(s); 458 459 return 0; 460 } 461 462 static int 463 send_are_you_there (krb5_context context, slave *s) 464 { 465 krb5_storage *sp; 466 krb5_data data; 467 char buf[4]; 468 int ret; 469 470 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT)) 471 return 0; 472 473 krb5_warnx(context, "slave %s missing, sending AYT", s->name); 474 475 s->flags |= SLAVE_F_AYT; 476 477 data.data = buf; 478 data.length = 4; 479 480 sp = krb5_storage_from_mem (buf, 4); 481 if (sp == NULL) { 482 krb5_warnx (context, "are_you_there: krb5_data_alloc"); 483 slave_dead(context, s); 484 return 1; 485 } 486 krb5_store_int32 (sp, ARE_YOU_THERE); 487 krb5_storage_free (sp); 488 489 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 490 491 if (ret) { 492 krb5_warn (context, ret, "are_you_there: krb5_write_priv_message"); 493 slave_dead(context, s); 494 return 1; 495 } 496 497 return 0; 498 } 499 500 static int 501 send_diffs (krb5_context context, slave *s, int log_fd, 502 const char *database, uint32_t current_version) 503 { 504 krb5_storage *sp; 505 uint32_t ver; 506 time_t timestamp; 507 enum kadm_ops op; 508 uint32_t len; 509 off_t right, left; 510 krb5_data data; 511 int ret = 0; 512 513 if (s->version == current_version) { 514 krb5_warnx(context, "slave %s in sync already at version %ld", 515 s->name, (long)s->version); 516 return 0; 517 } 518 519 if (s->flags & SLAVE_F_DEAD) 520 return 0; 521 522 /* if slave is a fresh client, starting over */ 523 if (s->version == 0) { 524 krb5_warnx(context, "sending complete log to fresh slave %s", 525 s->name); 526 return send_complete (context, s, database, current_version); 527 } 528 529 sp = kadm5_log_goto_end (log_fd); 530 right = krb5_storage_seek(sp, 0, SEEK_CUR); 531 for (;;) { 532 ret = kadm5_log_previous (context, sp, &ver, ×tamp, &op, &len); 533 if (ret) 534 krb5_err(context, 1, ret, 535 "send_diffs: failed to find previous entry"); 536 left = krb5_storage_seek(sp, -16, SEEK_CUR); 537 if (ver == s->version) 538 return 0; 539 if (ver == s->version + 1) 540 break; 541 if (left == 0) { 542 krb5_storage_free(sp); 543 krb5_warnx(context, 544 "slave %s (version %lu) out of sync with master " 545 "(first version in log %lu), sending complete database", 546 s->name, (unsigned long)s->version, (unsigned long)ver); 547 return send_complete (context, s, database, current_version); 548 } 549 } 550 551 krb5_warnx(context, 552 "syncing slave %s from version %lu to version %lu", 553 s->name, (unsigned long)s->version, 554 (unsigned long)current_version); 555 556 ret = krb5_data_alloc (&data, right - left + 4); 557 if (ret) { 558 krb5_storage_free(sp); 559 krb5_warn (context, ret, "send_diffs: krb5_data_alloc"); 560 slave_dead(context, s); 561 return 1; 562 } 563 krb5_storage_read (sp, (char *)data.data + 4, data.length - 4); 564 krb5_storage_free(sp); 565 566 sp = krb5_storage_from_data (&data); 567 if (sp == NULL) { 568 krb5_warnx (context, "send_diffs: krb5_storage_from_data"); 569 slave_dead(context, s); 570 return 1; 571 } 572 krb5_store_int32 (sp, FOR_YOU); 573 krb5_storage_free(sp); 574 575 ret = krb5_write_priv_message(context, s->ac, &s->fd, &data); 576 krb5_data_free(&data); 577 578 if (ret) { 579 krb5_warn (context, ret, "send_diffs: krb5_write_priv_message"); 580 slave_dead(context, s); 581 return 1; 582 } 583 slave_seen(s); 584 585 s->version = current_version; 586 587 return 0; 588 } 589 590 static int 591 process_msg (krb5_context context, slave *s, int log_fd, 592 const char *database, uint32_t current_version) 593 { 594 int ret = 0; 595 krb5_data out; 596 krb5_storage *sp; 597 int32_t tmp; 598 599 ret = krb5_read_priv_message(context, s->ac, &s->fd, &out); 600 if(ret) { 601 krb5_warn (context, ret, "error reading message from %s", s->name); 602 return 1; 603 } 604 605 sp = krb5_storage_from_mem (out.data, out.length); 606 if (sp == NULL) { 607 krb5_warnx (context, "process_msg: no memory"); 608 krb5_data_free (&out); 609 return 1; 610 } 611 if (krb5_ret_int32 (sp, &tmp) != 0) { 612 krb5_warnx (context, "process_msg: client send too short command"); 613 krb5_data_free (&out); 614 return 1; 615 } 616 switch (tmp) { 617 case I_HAVE : 618 ret = krb5_ret_int32 (sp, &tmp); 619 if (ret != 0) { 620 krb5_warnx (context, "process_msg: client send too I_HAVE data"); 621 break; 622 } 623 /* new started slave that have old log */ 624 if (s->version == 0 && tmp != 0) { 625 if (current_version < (uint32_t)tmp) { 626 krb5_warnx (context, "Slave %s (version %lu) have later version " 627 "the master (version %lu) OUT OF SYNC", 628 s->name, (unsigned long)tmp, 629 (unsigned long)current_version); 630 } 631 s->version = tmp; 632 } 633 if ((uint32_t)tmp < s->version) { 634 krb5_warnx (context, "Slave claims to not have " 635 "version we already sent to it"); 636 } else { 637 ret = send_diffs (context, s, log_fd, database, current_version); 638 } 639 break; 640 case I_AM_HERE : 641 break; 642 case ARE_YOU_THERE: 643 case FOR_YOU : 644 default : 645 krb5_warnx (context, "Ignoring command %d", tmp); 646 break; 647 } 648 649 krb5_data_free (&out); 650 krb5_storage_free (sp); 651 652 slave_seen(s); 653 654 return ret; 655 } 656 657 #define SLAVE_NAME "Name" 658 #define SLAVE_ADDRESS "Address" 659 #define SLAVE_VERSION "Version" 660 #define SLAVE_STATUS "Status" 661 #define SLAVE_SEEN "Last Seen" 662 663 static FILE * 664 open_stats(krb5_context context) 665 { 666 char *statfile = NULL; 667 const char *fn; 668 FILE *f; 669 670 if (slave_stats_file) 671 fn = slave_stats_file; 672 else { 673 asprintf(&statfile, "%s/slaves-stats", hdb_db_dir(context)); 674 fn = krb5_config_get_string_default(context, 675 NULL, 676 statfile, 677 "kdc", 678 "iprop-stats", 679 NULL); 680 } 681 f = fopen(fn, "w"); 682 if (statfile) 683 free(statfile); 684 685 return f; 686 } 687 688 static void 689 write_master_down(krb5_context context) 690 { 691 char str[100]; 692 time_t t = time(NULL); 693 FILE *fp; 694 695 fp = open_stats(context); 696 if (fp == NULL) 697 return; 698 krb5_format_time(context, t, str, sizeof(str), TRUE); 699 fprintf(fp, "master down at %s\n", str); 700 701 fclose(fp); 702 } 703 704 static void 705 write_stats(krb5_context context, slave *slaves, uint32_t current_version) 706 { 707 char str[100]; 708 rtbl_t tbl; 709 time_t t = time(NULL); 710 FILE *fp; 711 712 fp = open_stats(context); 713 if (fp == NULL) 714 return; 715 716 krb5_format_time(context, t, str, sizeof(str), TRUE); 717 fprintf(fp, "Status for slaves, last updated: %s\n\n", str); 718 719 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version); 720 721 tbl = rtbl_create(); 722 if (tbl == NULL) { 723 fclose(fp); 724 return; 725 } 726 727 rtbl_add_column(tbl, SLAVE_NAME, 0); 728 rtbl_add_column(tbl, SLAVE_ADDRESS, 0); 729 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT); 730 rtbl_add_column(tbl, SLAVE_STATUS, 0); 731 rtbl_add_column(tbl, SLAVE_SEEN, 0); 732 733 rtbl_set_prefix(tbl, " "); 734 rtbl_set_column_prefix(tbl, SLAVE_NAME, ""); 735 736 while (slaves) { 737 krb5_address addr; 738 krb5_error_code ret; 739 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name); 740 ret = krb5_sockaddr2address (context, 741 (struct sockaddr*)&slaves->addr.sa, &addr); 742 if(ret == 0) { 743 krb5_print_address(&addr, str, sizeof(str), NULL); 744 krb5_free_address(context, &addr); 745 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str); 746 } else 747 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>"); 748 749 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version); 750 rtbl_add_column_entry(tbl, SLAVE_VERSION, str); 751 752 if (slaves->flags & SLAVE_F_DEAD) 753 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down"); 754 else 755 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up"); 756 757 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE); 758 if (ret) 759 rtbl_add_column_entry(tbl, SLAVE_SEEN, "<error-formatting-time>"); 760 else 761 rtbl_add_column_entry(tbl, SLAVE_SEEN, str); 762 763 slaves = slaves->next; 764 } 765 766 rtbl_format(tbl, fp); 767 rtbl_destroy(tbl); 768 769 fclose(fp); 770 } 771 772 773 static char sHDB[] = "HDB:"; 774 static char *realm; 775 static int version_flag; 776 static int help_flag; 777 static char *keytab_str = sHDB; 778 static char *database; 779 static char *config_file; 780 static char *port_str; 781 #ifdef SUPPORT_DETACH 782 static int detach_from_console = 0; 783 #endif 784 785 static struct getargs args[] = { 786 { "config-file", 'c', arg_string, &config_file, NULL, NULL }, 787 { "realm", 'r', arg_string, &realm, NULL, NULL }, 788 { "keytab", 'k', arg_string, &keytab_str, 789 "keytab to get authentication from", "kspec" }, 790 { "database", 'd', arg_string, &database, "database", "file"}, 791 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file), 792 "file for slave status information", "file"}, 793 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing), 794 "time before slave is polled for presence", "time"}, 795 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone), 796 "time of inactivity after which a slave is considered gone", "time"}, 797 { "port", 0, arg_string, &port_str, 798 "port ipropd will listen to", "port"}, 799 #ifdef SUPPORT_DETACH 800 { "detach", 0, arg_flag, &detach_from_console, 801 "detach from console", NULL }, 802 #endif 803 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname), 804 "hostname of master (if not same as hostname)", "hostname" }, 805 { "version", 0, arg_flag, &version_flag, NULL, NULL }, 806 { "help", 0, arg_flag, &help_flag, NULL, NULL } 807 }; 808 static int num_args = sizeof(args) / sizeof(args[0]); 809 810 int 811 main(int argc, char **argv) 812 { 813 krb5_error_code ret; 814 krb5_context context; 815 void *kadm_handle; 816 kadm5_server_context *server_context; 817 kadm5_config_params conf; 818 krb5_socket_t signal_fd, listen_fd, listen6_fd; 819 int log_fd; 820 slave *slaves = NULL; 821 uint32_t current_version = 0, old_version = 0; 822 krb5_keytab keytab; 823 int optidx; 824 char **files; 825 826 optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL); 827 828 if(help_flag) 829 krb5_std_usage(0, args, num_args); 830 if(version_flag) { 831 print_version(NULL); 832 exit(0); 833 } 834 835 setup_signal(); 836 837 if (config_file == NULL) { 838 asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); 839 if (config_file == NULL) 840 errx(1, "out of memory"); 841 } 842 843 ret = krb5_prepend_config_files_default(config_file, &files); 844 if (ret) 845 krb5_err(context, 1, ret, "getting configuration files"); 846 847 ret = krb5_set_config_files(context, files); 848 krb5_free_config_files(files); 849 if (ret) 850 krb5_err(context, 1, ret, "reading configuration files"); 851 852 time_before_gone = parse_time (slave_time_gone, "s"); 853 if (time_before_gone < 0) 854 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone); 855 time_before_missing = parse_time (slave_time_missing, "s"); 856 if (time_before_missing < 0) 857 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing); 858 859 #ifdef SUPPORT_DETACH 860 if (detach_from_console) 861 daemon(0, 0); 862 #endif 863 pidfile (NULL); 864 krb5_openlog (context, "ipropd-master", &log_facility); 865 krb5_set_warn_dest(context, log_facility); 866 867 ret = krb5_kt_register(context, &hdb_kt_ops); 868 if(ret) 869 krb5_err(context, 1, ret, "krb5_kt_register"); 870 871 ret = krb5_kt_resolve(context, keytab_str, &keytab); 872 if(ret) 873 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str); 874 875 memset(&conf, 0, sizeof(conf)); 876 if(realm) { 877 conf.mask |= KADM5_CONFIG_REALM; 878 conf.realm = realm; 879 } 880 ret = kadm5_init_with_skey_ctx (context, 881 KADM5_ADMIN_SERVICE, 882 NULL, 883 KADM5_ADMIN_SERVICE, 884 &conf, 0, 0, 885 &kadm_handle); 886 if (ret) 887 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 888 889 server_context = (kadm5_server_context *)kadm_handle; 890 891 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0); 892 if (log_fd < 0) 893 krb5_err (context, 1, errno, "open %s", 894 server_context->log_context.log_file); 895 896 signal_fd = make_signal_socket (context); 897 listen_fd = make_listen_socket (context, port_str); 898 listen6_fd = make_listen6_socket (context, port_str); 899 900 kadm5_log_get_version_fd (log_fd, ¤t_version); 901 902 krb5_warnx(context, "ipropd-master started at version: %lu", 903 (unsigned long)current_version); 904 905 while(exit_flag == 0){ 906 slave *p; 907 fd_set readset; 908 int max_fd = 0; 909 struct timeval to = {30, 0}; 910 uint32_t vers; 911 912 #ifndef NO_LIMIT_FD_SETSIZE 913 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE || 914 listen6_fd >= FD_SETSIZE) 915 krb5_errx (context, 1, "fd too large"); 916 #endif 917 918 FD_ZERO(&readset); 919 FD_SET(signal_fd, &readset); 920 max_fd = max(max_fd, signal_fd); 921 FD_SET(listen_fd, &readset); 922 max_fd = max(max_fd, listen_fd); 923 FD_SET(listen6_fd, &readset); 924 max_fd = max(max_fd, listen6_fd); 925 926 for (p = slaves; p != NULL; p = p->next) { 927 if (p->flags & SLAVE_F_DEAD) 928 continue; 929 FD_SET(p->fd, &readset); 930 max_fd = max(max_fd, p->fd); 931 } 932 933 ret = select (max_fd + 1, 934 &readset, NULL, NULL, &to); 935 if (ret < 0) { 936 if (errno == EINTR) 937 continue; 938 else 939 krb5_err (context, 1, errno, "select"); 940 } 941 942 if (ret == 0) { 943 old_version = current_version; 944 kadm5_log_get_version_fd (log_fd, ¤t_version); 945 946 if (current_version > old_version) { 947 krb5_warnx(context, 948 "Missed a signal, updating slaves %lu to %lu", 949 (unsigned long)old_version, 950 (unsigned long)current_version); 951 for (p = slaves; p != NULL; p = p->next) { 952 if (p->flags & SLAVE_F_DEAD) 953 continue; 954 send_diffs (context, p, log_fd, database, current_version); 955 } 956 } 957 } 958 959 if (ret && FD_ISSET(signal_fd, &readset)) { 960 #ifndef NO_UNIX_SOCKETS 961 struct sockaddr_un peer_addr; 962 #else 963 struct sockaddr_storage peer_addr; 964 #endif 965 socklen_t peer_len = sizeof(peer_addr); 966 967 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0, 968 (struct sockaddr *)&peer_addr, &peer_len) < 0) { 969 krb5_warn (context, errno, "recvfrom"); 970 continue; 971 } 972 --ret; 973 assert(ret >= 0); 974 old_version = current_version; 975 kadm5_log_get_version_fd (log_fd, ¤t_version); 976 if (current_version > old_version) { 977 krb5_warnx(context, 978 "Got a signal, updating slaves %lu to %lu", 979 (unsigned long)old_version, 980 (unsigned long)current_version); 981 for (p = slaves; p != NULL; p = p->next) { 982 if (p->flags & SLAVE_F_DEAD) 983 continue; 984 send_diffs (context, p, log_fd, database, current_version); 985 } 986 } else { 987 krb5_warnx(context, 988 "Got a signal, but no update in log version %lu", 989 (unsigned long)current_version); 990 } 991 } 992 993 for(p = slaves; p != NULL; p = p->next) { 994 if (p->flags & SLAVE_F_DEAD) 995 continue; 996 if (ret && FD_ISSET(p->fd, &readset)) { 997 --ret; 998 assert(ret >= 0); 999 if(process_msg (context, p, log_fd, database, current_version)) 1000 slave_dead(context, p); 1001 } else if (slave_gone_p (p)) 1002 slave_dead(context, p); 1003 else if (slave_missing_p (p)) 1004 send_are_you_there (context, p); 1005 } 1006 1007 if (ret && FD_ISSET(listen6_fd, &readset)) { 1008 add_slave (context, keytab, &slaves, listen6_fd); 1009 --ret; 1010 assert(ret >= 0); 1011 } 1012 if (ret && FD_ISSET(listen_fd, &readset)) { 1013 add_slave (context, keytab, &slaves, listen_fd); 1014 --ret; 1015 assert(ret >= 0); 1016 } 1017 write_stats(context, slaves, current_version); 1018 } 1019 1020 if(exit_flag == SIGINT || exit_flag == SIGTERM) 1021 krb5_warnx(context, "%s terminated", getprogname()); 1022 #ifdef SIGXCPU 1023 else if(exit_flag == SIGXCPU) 1024 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 1025 #endif 1026 else 1027 krb5_warnx(context, "%s unexpected exit reason: %ld", 1028 getprogname(), (long)exit_flag); 1029 1030 write_master_down(context); 1031 1032 return 0; 1033 } 1034