1 /* $NetBSD: ipropd_slave.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 38 __RCSID("$NetBSD: ipropd_slave.c,v 1.2 2017/01/28 21:31:49 christos Exp $"); 39 40 static const char *config_name = "ipropd-slave"; 41 42 static int verbose; 43 44 static krb5_log_facility *log_facility; 45 static char five_min[] = "5 min"; 46 static char *server_time_lost = five_min; 47 static int time_before_lost; 48 const char *slave_str = NULL; 49 50 static int 51 connect_to_master (krb5_context context, const char *master, 52 const char *port_str) 53 { 54 char port[NI_MAXSERV]; 55 struct addrinfo *ai, *a; 56 struct addrinfo hints; 57 int error; 58 int one = 1; 59 int s = -1; 60 61 memset(&hints, 0, sizeof(hints)); 62 hints.ai_socktype = SOCK_STREAM; 63 64 if (port_str == NULL) { 65 snprintf(port, sizeof(port), "%u", IPROP_PORT); 66 port_str = port; 67 } 68 69 error = getaddrinfo(master, port_str, &hints, &ai); 70 if (error) { 71 krb5_warnx(context, "Failed to get address of to %s: %s", 72 master, gai_strerror(error)); 73 return -1; 74 } 75 76 for (a = ai; a != NULL; a = a->ai_next) { 77 char node[NI_MAXHOST]; 78 error = getnameinfo(a->ai_addr, a->ai_addrlen, 79 node, sizeof(node), NULL, 0, NI_NUMERICHOST); 80 if (error) 81 strlcpy(node, "[unknown-addr]", sizeof(node)); 82 83 s = socket(a->ai_family, a->ai_socktype, a->ai_protocol); 84 if (s < 0) 85 continue; 86 if (connect(s, a->ai_addr, a->ai_addrlen) < 0) { 87 krb5_warn(context, errno, "connection failed to %s[%s]", 88 master, node); 89 close(s); 90 continue; 91 } 92 krb5_warnx(context, "connection successful " 93 "to master: %s[%s]", master, node); 94 break; 95 } 96 freeaddrinfo(ai); 97 98 if (a == NULL) 99 return -1; 100 101 if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) < 0) 102 krb5_warn(context, errno, "setsockopt(SO_KEEPALIVE) failed"); 103 104 return s; 105 } 106 107 static void 108 get_creds(krb5_context context, const char *keytab_str, 109 krb5_ccache *cache, const char *serverhost) 110 { 111 krb5_keytab keytab; 112 krb5_principal client; 113 krb5_error_code ret; 114 krb5_get_init_creds_opt *init_opts; 115 krb5_creds creds; 116 char *server; 117 char keytab_buf[256]; 118 int aret; 119 120 if (keytab_str == NULL) { 121 ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf)); 122 if (ret) 123 krb5_err (context, 1, ret, "krb5_kt_default_name"); 124 keytab_str = keytab_buf; 125 } 126 127 ret = krb5_kt_resolve(context, keytab_str, &keytab); 128 if(ret) 129 krb5_err(context, 1, ret, "%s", keytab_str); 130 131 132 ret = krb5_sname_to_principal (context, slave_str, IPROP_NAME, 133 KRB5_NT_SRV_HST, &client); 134 if (ret) krb5_err(context, 1, ret, "krb5_sname_to_principal"); 135 136 ret = krb5_get_init_creds_opt_alloc(context, &init_opts); 137 if (ret) krb5_err(context, 1, ret, "krb5_get_init_creds_opt_alloc"); 138 139 aret = asprintf (&server, "%s/%s", IPROP_NAME, serverhost); 140 if (aret == -1 || server == NULL) 141 krb5_errx (context, 1, "malloc: no memory"); 142 143 ret = krb5_get_init_creds_keytab(context, &creds, client, keytab, 144 0, server, init_opts); 145 free (server); 146 krb5_get_init_creds_opt_free(context, init_opts); 147 if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds"); 148 149 ret = krb5_kt_close(context, keytab); 150 if(ret) krb5_err(context, 1, ret, "krb5_kt_close"); 151 152 ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, cache); 153 if(ret) krb5_err(context, 1, ret, "krb5_cc_new_unique"); 154 155 ret = krb5_cc_initialize(context, *cache, creds.client); 156 if(ret) krb5_err(context, 1, ret, "krb5_cc_initialize"); 157 158 ret = krb5_cc_store_cred(context, *cache, &creds); 159 if(ret) krb5_err(context, 1, ret, "krb5_cc_store_cred"); 160 161 krb5_free_cred_contents(context, &creds); 162 krb5_free_principal(context, client); 163 } 164 165 static krb5_error_code 166 ihave(krb5_context context, krb5_auth_context auth_context, 167 int fd, uint32_t version) 168 { 169 int ret; 170 u_char buf[8]; 171 krb5_storage *sp; 172 krb5_data data; 173 174 sp = krb5_storage_from_mem(buf, 8); 175 ret = krb5_store_uint32(sp, I_HAVE); 176 if (ret == 0) 177 ret = krb5_store_uint32(sp, version); 178 krb5_storage_free(sp); 179 data.length = 8; 180 data.data = buf; 181 182 if (ret == 0) { 183 if (verbose) 184 krb5_warnx(context, "telling master we are at %u", version); 185 186 ret = krb5_write_priv_message(context, auth_context, &fd, &data); 187 if (ret) 188 krb5_warn(context, ret, "krb5_write_message"); 189 } 190 return ret; 191 } 192 193 #ifndef EDQUOT 194 /* There's no EDQUOT on WIN32, for example */ 195 #define EDQUOT ENOSPC 196 #endif 197 198 static int 199 append_to_log_file(krb5_context context, 200 kadm5_server_context *server_context, 201 krb5_storage *sp, off_t start, ssize_t slen) 202 { 203 size_t len; 204 ssize_t sret; 205 off_t log_off; 206 int ret, ret2; 207 void *buf; 208 209 if (verbose) 210 krb5_warnx(context, "appending diffs to log"); 211 212 if (slen == 0) 213 return 0; 214 if (slen < 0) 215 return EINVAL; 216 len = slen; 217 if (len != slen) 218 return EOVERFLOW; 219 220 buf = malloc(len); 221 if (buf == NULL && len != 0) { 222 krb5_warn(context, errno, "malloc: no memory"); 223 return ENOMEM; 224 } 225 226 if (krb5_storage_seek(sp, start, SEEK_SET) != start) { 227 krb5_errx(context, IPROPD_RESTART, 228 "krb5_storage_seek() failed"); /* can't happen */ 229 } 230 sret = krb5_storage_read(sp, buf, len); 231 if (sret < 0) 232 return errno; 233 if (len != (size_t)sret) { 234 /* Can't happen */ 235 krb5_errx(context, IPROPD_RESTART, 236 "short krb5_storage_read() from memory buffer"); 237 } 238 log_off = lseek(server_context->log_context.log_fd, 0, SEEK_CUR); 239 if (log_off == -1) 240 return errno; 241 242 /* 243 * Use net_write() so we get an errno if less that len bytes were 244 * written. 245 */ 246 sret = net_write(server_context->log_context.log_fd, buf, len); 247 free(buf); 248 if (sret != slen) 249 ret = errno; 250 else 251 ret = fsync(server_context->log_context.log_fd); 252 if (ret == 0) 253 return 0; 254 255 /* 256 * Attempt to recover from this. First, truncate the log file 257 * and reset the fd offset. Failure to do this -> unlink the 258 * log file and re-create it. Since we're the slave, we ought to be 259 * able to recover from the log being unlinked... 260 */ 261 if (ftruncate(server_context->log_context.log_fd, log_off) == -1 || 262 lseek(server_context->log_context.log_fd, log_off, SEEK_SET) == -1) { 263 (void) kadm5_log_end(server_context); 264 if (unlink(server_context->log_context.log_file) == -1) { 265 krb5_err(context, IPROPD_FATAL, errno, 266 "Failed to recover from failure to write log " 267 "entries from master to disk"); 268 } 269 ret2 = kadm5_log_init(server_context); 270 if (ret2) { 271 krb5_err(context, IPROPD_RESTART_SLOW, ret2, 272 "Failed to initialize log to recover from " 273 "failure to write log entries from master to disk"); 274 } 275 } 276 if (ret == ENOSPC || ret == EDQUOT || ret == EFBIG) { 277 /* Unlink the file in these cases. */ 278 krb5_warn(context, IPROPD_RESTART_SLOW, 279 "Failed to write log entries from master to disk"); 280 (void) kadm5_log_end(server_context); 281 if (unlink(server_context->log_context.log_file) == -1) { 282 krb5_err(context, IPROPD_FATAL, errno, 283 "Failed to recover from failure to write log " 284 "entries from master to disk"); 285 } 286 ret2 = kadm5_log_init(server_context); 287 if (ret2) { 288 krb5_err(context, IPROPD_RESTART_SLOW, ret2, 289 "Failed to initialize log to recover from " 290 "failure to write log entries from master to disk"); 291 } 292 return ret; 293 } 294 /* 295 * All other errors we treat as fatal here. This includes, for 296 * example, EIO and EPIPE (sorry, can't log to pipes nor sockets). 297 */ 298 krb5_err(context, IPROPD_FATAL, ret, 299 "Failed to write log entries from master to disk"); 300 } 301 302 static int 303 receive_loop (krb5_context context, 304 krb5_storage *sp, 305 kadm5_server_context *server_context) 306 { 307 int ret; 308 off_t left, right, off; 309 uint32_t len, vers; 310 311 if (verbose) 312 krb5_warnx(context, "receiving diffs"); 313 314 /* 315 * Seek to the first entry in the message from the master that is 316 * past the current version of the local database. 317 */ 318 do { 319 uint32_t timestamp; 320 uint32_t op; 321 322 if ((ret = krb5_ret_uint32(sp, &vers)) == HEIM_ERR_EOF) { 323 krb5_warnx(context, "master sent no new iprop entries"); 324 return 0; 325 } 326 327 /* 328 * TODO We could do more to validate the entries from the master 329 * here. And we could use/reuse more kadm5_log_*() code here. 330 * 331 * Alternatively we should trust that the master sent us exactly 332 * what we needed and just write this to the log file and let 333 * kadm5_log_recover() do the rest. 334 */ 335 if (ret || krb5_ret_uint32(sp, ×tamp) != 0 || 336 krb5_ret_uint32(sp, &op) != 0 || 337 krb5_ret_uint32(sp, &len) != 0) { 338 339 /* 340 * This shouldn't happen. Reconnecting probably won't help 341 * if it does happen, but by reconnecting we get a chance to 342 * connect to a new master if a new one is configured. 343 */ 344 krb5_warnx(context, "iprop entries from master were truncated"); 345 return EINVAL; 346 } 347 if (vers > server_context->log_context.version) { 348 break; 349 } 350 off = krb5_storage_seek(sp, 0, SEEK_CUR); 351 if (krb5_storage_seek(sp, len + 8, SEEK_CUR) != off + len + 8) { 352 krb5_warnx(context, "iprop entries from master were truncated"); 353 return EINVAL; 354 } 355 if (verbose) { 356 krb5_warnx(context, "diff contains old log record version " 357 "%u %lld %u length %u", 358 vers, (long long)timestamp, op, len); 359 } 360 } while(vers <= server_context->log_context.version); 361 362 /* 363 * Read the remaining entries into memory... 364 */ 365 /* SEEK_CUR is a header into the first entry we care about */ 366 left = krb5_storage_seek(sp, -16, SEEK_CUR); 367 right = krb5_storage_seek(sp, 0, SEEK_END); 368 if (right - left < 24 + len) { 369 krb5_warnx(context, "iprop entries from master were truncated"); 370 return EINVAL; 371 } 372 373 /* 374 * ...and then write them out to the on-disk log. 375 */ 376 377 ret = append_to_log_file(context, server_context, sp, left, right - left); 378 if (ret) 379 return ret; 380 381 /* 382 * Replay the new entries. 383 */ 384 if (verbose) 385 krb5_warnx(context, "replaying entries from master"); 386 ret = kadm5_log_recover(server_context, kadm_recover_replay); 387 if (ret) { 388 krb5_warn(context, ret, "replay failed"); 389 return ret; 390 } 391 392 ret = kadm5_log_get_version(server_context, &vers); 393 if (ret) { 394 krb5_warn(context, ret, 395 "could not get log version after applying diffs!"); 396 return ret; 397 } 398 if (verbose) 399 krb5_warnx(context, "slave at version %u", vers); 400 401 if (vers != server_context->log_context.version) { 402 krb5_warnx(context, "slave's log_context version (%u) is " 403 "inconsistent with log's version (%u)", 404 server_context->log_context.version, vers); 405 } 406 407 return 0; 408 } 409 410 static int 411 receive(krb5_context context, 412 krb5_storage *sp, 413 kadm5_server_context *server_context) 414 { 415 krb5_error_code ret, ret2; 416 417 ret = server_context->db->hdb_open(context, 418 server_context->db, 419 O_RDWR | O_CREAT, 0600); 420 if (ret) 421 krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->open"); 422 423 ret2 = receive_loop(context, sp, server_context); 424 if (ret2) 425 krb5_warn(context, ret2, "receive from ipropd-master had errors"); 426 427 ret = server_context->db->hdb_close(context, server_context->db); 428 if (ret) 429 krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close"); 430 431 return ret2; 432 } 433 434 static void 435 send_im_here(krb5_context context, int fd, 436 krb5_auth_context auth_context) 437 { 438 krb5_storage *sp; 439 krb5_data data; 440 krb5_error_code ret; 441 442 ret = krb5_data_alloc(&data, 4); 443 if (ret) 444 krb5_err(context, IPROPD_RESTART, ret, "send_im_here"); 445 446 sp = krb5_storage_from_data (&data); 447 if (sp == NULL) 448 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_data"); 449 ret = krb5_store_uint32(sp, I_AM_HERE); 450 krb5_storage_free(sp); 451 452 if (ret == 0) { 453 ret = krb5_write_priv_message(context, auth_context, &fd, &data); 454 krb5_data_free(&data); 455 456 if (ret) 457 krb5_err(context, IPROPD_RESTART, ret, "krb5_write_priv_message"); 458 459 if (verbose) 460 krb5_warnx(context, "pinged master"); 461 } 462 463 return; 464 } 465 466 static void 467 reinit_log(krb5_context context, 468 kadm5_server_context *server_context, 469 uint32_t vno) 470 { 471 krb5_error_code ret; 472 473 if (verbose) 474 krb5_warnx(context, "truncating log on slave"); 475 476 ret = kadm5_log_reinit(server_context, vno); 477 if (ret) 478 krb5_err(context, IPROPD_RESTART_SLOW, ret, "kadm5_log_reinit"); 479 } 480 481 482 static krb5_error_code 483 receive_everything(krb5_context context, int fd, 484 kadm5_server_context *server_context, 485 krb5_auth_context auth_context) 486 { 487 int ret; 488 krb5_data data; 489 uint32_t vno = 0; 490 uint32_t opcode; 491 krb5_storage *sp; 492 493 char *dbname; 494 HDB *mydb; 495 496 krb5_warnx(context, "receive complete database"); 497 498 ret = asprintf(&dbname, "%s-NEW", server_context->db->hdb_name); 499 if (ret == -1) 500 krb5_err(context, IPROPD_RESTART, ENOMEM, "asprintf"); 501 ret = hdb_create(context, &mydb, dbname); 502 if(ret) 503 krb5_err(context, IPROPD_RESTART, ret, "hdb_create"); 504 free(dbname); 505 506 ret = hdb_set_master_keyfile(context, 507 mydb, server_context->config.stash_file); 508 if(ret) 509 krb5_err(context, IPROPD_RESTART, ret, "hdb_set_master_keyfile"); 510 511 /* I really want to use O_EXCL here, but given that I can't easily clean 512 up on error, I won't */ 513 ret = mydb->hdb_open(context, mydb, O_RDWR | O_CREAT | O_TRUNC, 0600); 514 if (ret) 515 krb5_err(context, IPROPD_RESTART, ret, "db->open"); 516 517 sp = NULL; 518 krb5_data_zero(&data); 519 do { 520 ret = krb5_read_priv_message(context, auth_context, &fd, &data); 521 522 if (ret) { 523 krb5_warn(context, ret, "krb5_read_priv_message"); 524 goto cleanup; 525 } 526 527 sp = krb5_storage_from_data(&data); 528 if (sp == NULL) 529 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_data"); 530 krb5_ret_uint32(sp, &opcode); 531 if (opcode == ONE_PRINC) { 532 krb5_data fake_data; 533 hdb_entry_ex entry; 534 535 krb5_storage_free(sp); 536 537 fake_data.data = (char *)data.data + 4; 538 fake_data.length = data.length - 4; 539 540 memset(&entry, 0, sizeof(entry)); 541 542 ret = hdb_value2entry(context, &fake_data, &entry.entry); 543 if (ret) 544 krb5_err(context, IPROPD_RESTART, ret, "hdb_value2entry"); 545 ret = mydb->hdb_store(server_context->context, 546 mydb, 547 0, &entry); 548 if (ret) 549 krb5_err(context, IPROPD_RESTART_SLOW, ret, "hdb_store"); 550 551 hdb_free_entry(context, &entry); 552 krb5_data_free(&data); 553 } else if (opcode == NOW_YOU_HAVE) 554 ; 555 else 556 krb5_errx(context, 1, "strange opcode %d", opcode); 557 } while (opcode == ONE_PRINC); 558 559 if (opcode != NOW_YOU_HAVE) 560 krb5_errx(context, IPROPD_RESTART_SLOW, 561 "receive_everything: strange %d", opcode); 562 563 krb5_ret_uint32(sp, &vno); 564 krb5_storage_free(sp); 565 566 reinit_log(context, server_context, vno); 567 568 ret = mydb->hdb_close(context, mydb); 569 if (ret) 570 krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close"); 571 572 ret = mydb->hdb_rename(context, mydb, server_context->db->hdb_name); 573 if (ret) 574 krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->rename"); 575 576 577 return 0; 578 579 cleanup: 580 krb5_data_free(&data); 581 582 if (ret) 583 krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close"); 584 585 ret = mydb->hdb_destroy(context, mydb); 586 if (ret) 587 krb5_err(context, IPROPD_RESTART, ret, "db->destroy"); 588 589 krb5_warnx(context, "receive complete database, version %ld", (long)vno); 590 return ret; 591 } 592 593 static void 594 slave_status(krb5_context context, 595 const char *file, 596 const char *status, ...) 597 __attribute__ ((__format__ (__printf__, 3, 4))); 598 599 600 static void 601 slave_status(krb5_context context, 602 const char *file, 603 const char *fmt, ...) 604 { 605 char *status; 606 char *fmt2; 607 va_list args; 608 int len; 609 610 if (asprintf(&fmt2, "%s\n", fmt) == -1 || fmt2 == NULL) { 611 (void) unlink(file); 612 return; 613 } 614 va_start(args, fmt); 615 len = vasprintf(&status, fmt2, args); 616 free(fmt2); 617 va_end(args); 618 if (len < 0 || status == NULL) { 619 (void) unlink(file); 620 return; 621 } 622 krb5_warnx(context, "slave status change: %s", status); 623 624 rk_dumpdata(file, status, len); 625 free(status); 626 } 627 628 static void 629 is_up_to_date(krb5_context context, const char *file, 630 kadm5_server_context *server_context) 631 { 632 krb5_error_code ret; 633 char buf[80]; 634 ret = krb5_format_time(context, time(NULL), buf, sizeof(buf), 1); 635 if (ret) { 636 unlink(file); 637 return; 638 } 639 slave_status(context, file, "up-to-date with version: %lu at %s", 640 (unsigned long)server_context->log_context.version, buf); 641 } 642 643 static char *status_file; 644 static char *config_file; 645 static char *realm; 646 static int version_flag; 647 static int help_flag; 648 static char *keytab_str; 649 static char *port_str; 650 static int detach_from_console; 651 static int daemon_child = -1; 652 653 static struct getargs args[] = { 654 { "config-file", 'c', arg_string, &config_file, NULL, NULL }, 655 { "realm", 'r', arg_string, &realm, NULL, NULL }, 656 { "keytab", 'k', arg_string, &keytab_str, 657 "keytab to get authentication from", "kspec" }, 658 { "time-lost", 0, arg_string, &server_time_lost, 659 "time before server is considered lost", "time" }, 660 { "status-file", 0, arg_string, &status_file, 661 "file to write out status into", "file" }, 662 { "port", 0, arg_string, &port_str, 663 "port ipropd-slave will connect to", "port"}, 664 { "detach", 0, arg_flag, &detach_from_console, 665 "detach from console", NULL }, 666 { "daemon-child", 0 , arg_integer, &daemon_child, 667 "private argument, do not use", NULL }, 668 { "hostname", 0, arg_string, rk_UNCONST(&slave_str), 669 "hostname of slave (if not same as hostname)", "hostname" }, 670 { "verbose", 0, arg_flag, &verbose, NULL, NULL }, 671 { "version", 0, arg_flag, &version_flag, NULL, NULL }, 672 { "help", 0, arg_flag, &help_flag, NULL, NULL } 673 }; 674 675 static int num_args = sizeof(args) / sizeof(args[0]); 676 677 static void 678 usage(int status) 679 { 680 arg_printusage(args, num_args, NULL, "master"); 681 exit(status); 682 } 683 684 int 685 main(int argc, char **argv) 686 { 687 krb5_error_code ret, ret2; 688 krb5_context context; 689 krb5_auth_context auth_context; 690 void *kadm_handle; 691 kadm5_server_context *server_context; 692 kadm5_config_params conf; 693 int master_fd; 694 krb5_ccache ccache; 695 krb5_principal server; 696 char **files; 697 int optidx = 0; 698 time_t reconnect_min; 699 time_t backoff; 700 time_t reconnect_max; 701 time_t reconnect; 702 time_t before = 0; 703 int restarter_fd = -1; 704 705 const char *master; 706 707 setprogname(argv[0]); 708 709 if (getarg(args, num_args, argc, argv, &optidx)) 710 usage(1); 711 712 if (help_flag) 713 usage(0); 714 715 if (version_flag) { 716 print_version(NULL); 717 exit(0); 718 } 719 720 if (detach_from_console && daemon_child == -1) 721 roken_detach_prep(argc, argv, "--daemon-child"); 722 rk_pidfile(NULL); 723 724 ret = krb5_init_context(&context); 725 if (ret) 726 errx (1, "krb5_init_context failed: %d", ret); 727 728 setup_signal(); 729 730 if (config_file == NULL) { 731 if (asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)) == -1 732 || config_file == NULL) 733 errx(1, "out of memory"); 734 } 735 736 ret = krb5_prepend_config_files_default(config_file, &files); 737 if (ret) 738 krb5_err(context, 1, ret, "getting configuration files"); 739 740 ret = krb5_set_config_files(context, files); 741 krb5_free_config_files(files); 742 if (ret) 743 krb5_err(context, 1, ret, "reading configuration files"); 744 745 argc -= optidx; 746 argv += optidx; 747 748 if (argc != 1) 749 usage(1); 750 751 master = argv[0]; 752 753 if (status_file == NULL) { 754 if (asprintf(&status_file, "%s/ipropd-slave-status", hdb_db_dir(context)) < 0 || status_file == NULL) 755 krb5_errx(context, 1, "can't allocate status file buffer"); 756 } 757 758 krb5_openlog(context, "ipropd-slave", &log_facility); 759 krb5_set_warn_dest(context, log_facility); 760 761 slave_status(context, status_file, "bootstrapping"); 762 763 ret = krb5_kt_register(context, &hdb_get_kt_ops); 764 if(ret) 765 krb5_err(context, 1, ret, "krb5_kt_register"); 766 767 time_before_lost = parse_time (server_time_lost, "s"); 768 if (time_before_lost < 0) 769 krb5_errx (context, 1, "couldn't parse time: %s", server_time_lost); 770 771 slave_status(context, status_file, "getting credentials from keytab/database"); 772 773 memset(&conf, 0, sizeof(conf)); 774 if(realm) { 775 conf.mask |= KADM5_CONFIG_REALM; 776 conf.realm = realm; 777 } 778 ret = kadm5_init_with_password_ctx (context, 779 KADM5_ADMIN_SERVICE, 780 NULL, 781 KADM5_ADMIN_SERVICE, 782 &conf, 0, 0, 783 &kadm_handle); 784 if (ret) 785 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 786 787 server_context = (kadm5_server_context *)kadm_handle; 788 789 slave_status(context, status_file, "creating log file"); 790 791 ret = kadm5_log_init (server_context); 792 if (ret) 793 krb5_err (context, 1, ret, "kadm5_log_init"); 794 795 get_creds(context, keytab_str, &ccache, master); 796 797 ret = krb5_sname_to_principal (context, master, IPROP_NAME, 798 KRB5_NT_SRV_HST, &server); 799 if (ret) 800 krb5_err (context, 1, ret, "krb5_sname_to_principal"); 801 802 auth_context = NULL; 803 master_fd = -1; 804 805 krb5_appdefault_time(context, config_name, NULL, "reconnect-min", 806 10, &reconnect_min); 807 krb5_appdefault_time(context, config_name, NULL, "reconnect-max", 808 300, &reconnect_max); 809 krb5_appdefault_time(context, config_name, NULL, "reconnect-backoff", 810 10, &backoff); 811 reconnect = reconnect_min; 812 813 slave_status(context, status_file, "ipropd-slave started"); 814 815 roken_detach_finish(NULL, daemon_child); 816 restarter_fd = restarter(context, NULL); 817 818 while (!exit_flag) { 819 struct timeval to; 820 time_t now, elapsed; 821 fd_set readset; 822 int connected = FALSE; 823 824 #ifndef NO_LIMIT_FD_SETSIZE 825 if (restarter_fd >= FD_SETSIZE) 826 krb5_errx(context, IPROPD_RESTART, "fd too large"); 827 #endif 828 829 FD_ZERO(&readset); 830 if (restarter_fd > -1) 831 FD_SET(restarter_fd, &readset); 832 833 now = time(NULL); 834 elapsed = now - before; 835 836 if (elapsed < reconnect) { 837 time_t left = reconnect - elapsed; 838 krb5_warnx(context, "sleeping %d seconds before " 839 "retrying to connect", (int)left); 840 to.tv_sec = left; 841 to.tv_usec = 0; 842 if (select(restarter_fd + 1, &readset, NULL, NULL, &to) == 1) { 843 exit_flag = SIGTERM; 844 continue; 845 } 846 } 847 before = now; 848 849 slave_status(context, status_file, "connecting to master: %s\n", master); 850 851 master_fd = connect_to_master (context, master, port_str); 852 if (master_fd < 0) 853 goto retry; 854 855 reconnect = reconnect_min; 856 857 if (auth_context) { 858 krb5_auth_con_free(context, auth_context); 859 auth_context = NULL; 860 krb5_cc_destroy(context, ccache); 861 get_creds(context, keytab_str, &ccache, master); 862 } 863 if (verbose) 864 krb5_warnx(context, "authenticating to master"); 865 ret = krb5_sendauth (context, &auth_context, &master_fd, 866 IPROP_VERSION, NULL, server, 867 AP_OPTS_MUTUAL_REQUIRED, NULL, NULL, 868 ccache, NULL, NULL, NULL); 869 if (ret) { 870 krb5_warn (context, ret, "krb5_sendauth"); 871 goto retry; 872 } 873 874 krb5_warnx(context, "ipropd-slave started at version: %ld", 875 (long)server_context->log_context.version); 876 877 ret = ihave(context, auth_context, master_fd, 878 server_context->log_context.version); 879 if (ret) 880 goto retry; 881 882 connected = TRUE; 883 884 if (verbose) 885 krb5_warnx(context, "connected to master"); 886 887 slave_status(context, status_file, "connected to master, waiting instructions"); 888 889 while (connected && !exit_flag) { 890 krb5_data out; 891 krb5_storage *sp; 892 uint32_t tmp; 893 int max_fd; 894 895 #ifndef NO_LIMIT_FD_SETSIZE 896 if (master_fd >= FD_SETSIZE) 897 krb5_errx(context, IPROPD_RESTART, "fd too large"); 898 if (restarter_fd >= FD_SETSIZE) 899 krb5_errx(context, IPROPD_RESTART, "fd too large"); 900 max_fd = max(restarter_fd, master_fd); 901 #endif 902 903 FD_ZERO(&readset); 904 FD_SET(master_fd, &readset); 905 if (restarter_fd != -1) 906 FD_SET(restarter_fd, &readset); 907 908 to.tv_sec = time_before_lost; 909 to.tv_usec = 0; 910 911 ret = select (max_fd + 1, 912 &readset, NULL, NULL, &to); 913 if (ret < 0) { 914 if (errno == EINTR) 915 continue; 916 else 917 krb5_err (context, 1, errno, "select"); 918 } 919 if (ret == 0) { 920 krb5_warnx(context, "server didn't send a message " 921 "in %d seconds", time_before_lost); 922 connected = FALSE; 923 continue; 924 } 925 926 if (restarter_fd > -1 && FD_ISSET(restarter_fd, &readset)) { 927 if (verbose) 928 krb5_warnx(context, "slave restarter exited"); 929 exit_flag = SIGTERM; 930 } 931 932 if (!FD_ISSET(master_fd, &readset)) 933 continue; 934 935 if (verbose) 936 krb5_warnx(context, "message from master"); 937 938 ret = krb5_read_priv_message(context, auth_context, &master_fd, &out); 939 if (ret) { 940 krb5_warn(context, ret, "krb5_read_priv_message"); 941 connected = FALSE; 942 continue; 943 } 944 945 sp = krb5_storage_from_mem (out.data, out.length); 946 if (sp == NULL) 947 krb5_err(context, IPROPD_RESTART, errno, "krb5_storage_from_mem"); 948 ret = krb5_ret_uint32(sp, &tmp); 949 if (ret == HEIM_ERR_EOF) { 950 krb5_warn(context, ret, "master sent zero-length message"); 951 connected = FALSE; 952 continue; 953 } 954 if (ret != 0) { 955 krb5_warn(context, ret, "couldn't read master's message"); 956 connected = FALSE; 957 continue; 958 } 959 960 ret = kadm5_log_init(server_context); 961 if (ret) { 962 krb5_err(context, IPROPD_RESTART, ret, "kadm5_log_init while " 963 "handling a message from the master"); 964 } 965 switch (tmp) { 966 case FOR_YOU : 967 if (verbose) 968 krb5_warnx(context, "master sent us diffs"); 969 ret2 = receive(context, sp, server_context); 970 if (ret2) 971 krb5_warn(context, ret2, 972 "receive from ipropd-master had errors"); 973 ret = ihave(context, auth_context, master_fd, 974 server_context->log_context.version); 975 if (ret || ret2) 976 connected = FALSE; 977 978 /* 979 * If it returns an error, receive() may nonetheless 980 * have committed some entries successfully, so we must 981 * update the slave_status even if there were errors. 982 */ 983 is_up_to_date(context, status_file, server_context); 984 break; 985 case TELL_YOU_EVERYTHING : 986 if (verbose) 987 krb5_warnx(context, "master sent us a full dump"); 988 ret = receive_everything(context, master_fd, server_context, 989 auth_context); 990 if (ret == 0) { 991 ret = ihave(context, auth_context, master_fd, 992 server_context->log_context.version); 993 } 994 if (ret) 995 connected = FALSE; 996 else 997 is_up_to_date(context, status_file, server_context); 998 break; 999 case ARE_YOU_THERE : 1000 if (verbose) 1001 krb5_warnx(context, "master sent us a ping"); 1002 is_up_to_date(context, status_file, server_context); 1003 ret = ihave(context, auth_context, master_fd, 1004 server_context->log_context.version); 1005 if (ret) 1006 connected = FALSE; 1007 1008 send_im_here(context, master_fd, auth_context); 1009 break; 1010 case YOU_HAVE_LAST_VERSION: 1011 if (verbose) 1012 krb5_warnx(context, "master tells us we are up to date"); 1013 is_up_to_date(context, status_file, server_context); 1014 break; 1015 case NOW_YOU_HAVE : 1016 case I_HAVE : 1017 case ONE_PRINC : 1018 case I_AM_HERE : 1019 default : 1020 krb5_warnx (context, "Ignoring command %d", tmp); 1021 break; 1022 } 1023 krb5_storage_free (sp); 1024 krb5_data_free (&out); 1025 1026 } 1027 1028 slave_status(context, status_file, "disconnected from master"); 1029 retry: 1030 if (connected == FALSE) 1031 krb5_warnx (context, "disconnected for server"); 1032 1033 if (exit_flag) 1034 krb5_warnx (context, "got an exit signal"); 1035 1036 if (master_fd >= 0) 1037 close(master_fd); 1038 1039 reconnect += backoff; 1040 if (reconnect > reconnect_max) { 1041 slave_status(context, status_file, "disconnected from master for a long time"); 1042 reconnect = reconnect_max; 1043 } 1044 } 1045 1046 if (status_file) { 1047 /* XXX It'd be better to leave it saying we're not here */ 1048 unlink(status_file); 1049 } 1050 1051 if (0); 1052 #ifndef NO_SIGXCPU 1053 else if(exit_flag == SIGXCPU) 1054 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 1055 #endif 1056 else if(exit_flag == SIGINT || exit_flag == SIGTERM) 1057 krb5_warnx(context, "%s terminated", getprogname()); 1058 else 1059 krb5_warnx(context, "%s unexpected exit reason: %ld", 1060 getprogname(), (long)exit_flag); 1061 1062 return 0; 1063 } 1064