1 /* 2 * Copyright (c) 1997 - 2007 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 36 RCSID("$Id: ipropd_slave.c 22211 2007-12-07 19:27:27Z lha $"); 37 38 static krb5_log_facility *log_facility; 39 static char *server_time_lost = "5 min"; 40 static int time_before_lost; 41 const char *slave_str = NULL; 42 43 static int 44 connect_to_master (krb5_context context, const char *master, 45 const char *port_str) 46 { 47 int fd; 48 struct sockaddr_in addr; 49 struct hostent *he; 50 51 fd = socket (AF_INET, SOCK_STREAM, 0); 52 if (fd < 0) 53 krb5_err (context, 1, errno, "socket AF_INET"); 54 memset (&addr, 0, sizeof(addr)); 55 addr.sin_family = AF_INET; 56 if (port_str) { 57 addr.sin_port = krb5_getportbyname (context, 58 port_str, "tcp", 59 0); 60 if (addr.sin_port == 0) { 61 char *ptr; 62 long port; 63 64 port = strtol (port_str, &ptr, 10); 65 if (port == 0 && ptr == port_str) 66 krb5_errx (context, 1, "bad port `%s'", port_str); 67 addr.sin_port = htons(port); 68 } 69 } else { 70 addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE, 71 "tcp", IPROP_PORT); 72 } 73 he = roken_gethostbyname (master); 74 if (he == NULL) 75 krb5_errx (context, 1, "gethostbyname: %s", hstrerror(h_errno)); 76 memcpy (&addr.sin_addr, he->h_addr, sizeof(addr.sin_addr)); 77 if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 78 krb5_err (context, 1, errno, "connect"); 79 return fd; 80 } 81 82 static void 83 get_creds(krb5_context context, const char *keytab_str, 84 krb5_ccache *cache, const char *serverhost) 85 { 86 krb5_keytab keytab; 87 krb5_principal client; 88 krb5_error_code ret; 89 krb5_get_init_creds_opt *init_opts; 90 krb5_creds creds; 91 char *server; 92 char keytab_buf[256]; 93 94 if (keytab_str == NULL) { 95 ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf)); 96 if (ret) 97 krb5_err (context, 1, ret, "krb5_kt_default_name"); 98 keytab_str = keytab_buf; 99 } 100 101 ret = krb5_kt_resolve(context, keytab_str, &keytab); 102 if(ret) 103 krb5_err(context, 1, ret, "%s", keytab_str); 104 105 106 ret = krb5_sname_to_principal (context, slave_str, IPROP_NAME, 107 KRB5_NT_SRV_HST, &client); 108 if (ret) krb5_err(context, 1, ret, "krb5_sname_to_principal"); 109 110 ret = krb5_get_init_creds_opt_alloc(context, &init_opts); 111 if (ret) krb5_err(context, 1, ret, "krb5_get_init_creds_opt_alloc"); 112 113 asprintf (&server, "%s/%s", IPROP_NAME, serverhost); 114 if (server == NULL) 115 krb5_errx (context, 1, "malloc: no memory"); 116 117 ret = krb5_get_init_creds_keytab(context, &creds, client, keytab, 118 0, server, init_opts); 119 free (server); 120 krb5_get_init_creds_opt_free(context, init_opts); 121 if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds"); 122 123 ret = krb5_kt_close(context, keytab); 124 if(ret) krb5_err(context, 1, ret, "krb5_kt_close"); 125 126 ret = krb5_cc_gen_new(context, &krb5_mcc_ops, cache); 127 if(ret) krb5_err(context, 1, ret, "krb5_cc_gen_new"); 128 129 ret = krb5_cc_initialize(context, *cache, client); 130 if(ret) krb5_err(context, 1, ret, "krb5_cc_initialize"); 131 132 ret = krb5_cc_store_cred(context, *cache, &creds); 133 if(ret) krb5_err(context, 1, ret, "krb5_cc_store_cred"); 134 } 135 136 static void 137 ihave (krb5_context context, krb5_auth_context auth_context, 138 int fd, uint32_t version) 139 { 140 int ret; 141 u_char buf[8]; 142 krb5_storage *sp; 143 krb5_data data; 144 145 sp = krb5_storage_from_mem (buf, 8); 146 krb5_store_int32 (sp, I_HAVE); 147 krb5_store_int32 (sp, version); 148 krb5_storage_free (sp); 149 data.length = 8; 150 data.data = buf; 151 152 ret = krb5_write_priv_message(context, auth_context, &fd, &data); 153 if (ret) 154 krb5_err (context, 1, ret, "krb5_write_priv_message"); 155 } 156 157 static void 158 receive_loop (krb5_context context, 159 krb5_storage *sp, 160 kadm5_server_context *server_context) 161 { 162 int ret; 163 off_t left, right; 164 void *buf; 165 int32_t vers, vers2; 166 ssize_t sret; 167 168 /* 169 * Seek to the current version of the local database. 170 */ 171 do { 172 int32_t len, timestamp, tmp; 173 enum kadm_ops op; 174 175 if(krb5_ret_int32 (sp, &vers) != 0) 176 return; 177 krb5_ret_int32 (sp, ×tamp); 178 krb5_ret_int32 (sp, &tmp); 179 op = tmp; 180 krb5_ret_int32 (sp, &len); 181 if (vers <= server_context->log_context.version) 182 krb5_storage_seek(sp, len + 8, SEEK_CUR); 183 } while(vers <= server_context->log_context.version); 184 185 /* 186 * Read up rest of the entires into the memory... 187 */ 188 left = krb5_storage_seek (sp, -16, SEEK_CUR); 189 right = krb5_storage_seek (sp, 0, SEEK_END); 190 buf = malloc (right - left); 191 if (buf == NULL && (right - left) != 0) 192 krb5_errx (context, 1, "malloc: no memory"); 193 194 /* 195 * ...and then write them out to the on-disk log. 196 */ 197 krb5_storage_seek (sp, left, SEEK_SET); 198 krb5_storage_read (sp, buf, right - left); 199 sret = write (server_context->log_context.log_fd, buf, right-left); 200 if (sret != right - left) 201 krb5_err(context, 1, errno, "Failed to write log to disk"); 202 ret = fsync (server_context->log_context.log_fd); 203 if (ret) 204 krb5_err(context, 1, errno, "Failed to sync log to disk"); 205 free (buf); 206 207 /* 208 * Go back to the startpoint and start to commit the entires to 209 * the database. 210 */ 211 krb5_storage_seek (sp, left, SEEK_SET); 212 213 for(;;) { 214 int32_t len, len2, timestamp, tmp; 215 off_t cur, cur2; 216 enum kadm_ops op; 217 218 if(krb5_ret_int32 (sp, &vers) != 0) 219 break; 220 ret = krb5_ret_int32 (sp, ×tamp); 221 if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers); 222 ret = krb5_ret_int32 (sp, &tmp); 223 if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers); 224 op = tmp; 225 ret = krb5_ret_int32 (sp, &len); 226 if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers); 227 if (len < 0) 228 krb5_errx(context, 1, "log is corrupted, " 229 "negative length of entry version %ld: %ld", 230 (long)vers, (long)len); 231 cur = krb5_storage_seek(sp, 0, SEEK_CUR); 232 233 krb5_warnx (context, "replaying entry %d", (int)vers); 234 235 ret = kadm5_log_replay (server_context, 236 op, vers, len, sp); 237 if (ret) { 238 char *s = krb5_get_error_message(server_context->context, ret); 239 krb5_warnx (context, 240 "kadm5_log_replay: %ld. Lost entry entry, " 241 "Database out of sync ?: %s (%d)", 242 (long)vers, s ? s : "unknown error", ret); 243 krb5_xfree(s); 244 } 245 246 { 247 /* 248 * Make sure the krb5_log_replay does the right thing wrt 249 * reading out data from the sp. 250 */ 251 cur2 = krb5_storage_seek(sp, 0, SEEK_CUR); 252 if (cur + len != cur2) 253 krb5_errx(context, 1, 254 "kadm5_log_reply version: %ld didn't read the whole entry", 255 (long)vers); 256 } 257 258 if (krb5_ret_int32 (sp, &len2) != 0) 259 krb5_errx(context, 1, "entry %ld: postamble too short", (long)vers); 260 if(krb5_ret_int32 (sp, &vers2) != 0) 261 krb5_errx(context, 1, "entry %ld: postamble too short", (long)vers); 262 263 if (len != len2) 264 krb5_errx(context, 1, "entry %ld: len != len2", (long)vers); 265 if (vers != vers2) 266 krb5_errx(context, 1, "entry %ld: vers != vers2", (long)vers); 267 } 268 269 /* 270 * Update version 271 */ 272 273 server_context->log_context.version = vers; 274 } 275 276 static void 277 receive (krb5_context context, 278 krb5_storage *sp, 279 kadm5_server_context *server_context) 280 { 281 int ret; 282 283 ret = server_context->db->hdb_open(context, 284 server_context->db, 285 O_RDWR | O_CREAT, 0600); 286 if (ret) 287 krb5_err (context, 1, ret, "db->open"); 288 289 receive_loop (context, sp, server_context); 290 291 ret = server_context->db->hdb_close (context, server_context->db); 292 if (ret) 293 krb5_err (context, 1, ret, "db->close"); 294 } 295 296 static void 297 send_im_here (krb5_context context, int fd, 298 krb5_auth_context auth_context) 299 { 300 krb5_storage *sp; 301 krb5_data data; 302 int ret; 303 304 ret = krb5_data_alloc (&data, 4); 305 if (ret) 306 krb5_err (context, 1, ret, "send_im_here"); 307 308 sp = krb5_storage_from_data (&data); 309 if (sp == NULL) 310 krb5_errx (context, 1, "krb5_storage_from_data"); 311 krb5_store_int32(sp, I_AM_HERE); 312 krb5_storage_free(sp); 313 314 ret = krb5_write_priv_message(context, auth_context, &fd, &data); 315 krb5_data_free(&data); 316 317 if (ret) 318 krb5_err (context, 1, ret, "krb5_write_priv_message"); 319 } 320 321 static void 322 receive_everything (krb5_context context, int fd, 323 kadm5_server_context *server_context, 324 krb5_auth_context auth_context) 325 { 326 int ret; 327 krb5_data data; 328 int32_t vno; 329 int32_t opcode; 330 krb5_storage *sp; 331 332 char *dbname; 333 HDB *mydb; 334 335 krb5_warnx(context, "receive complete database"); 336 337 asprintf(&dbname, "%s-NEW", server_context->db->hdb_name); 338 ret = hdb_create(context, &mydb, dbname); 339 if(ret) 340 krb5_err(context,1, ret, "hdb_create"); 341 free(dbname); 342 343 ret = hdb_set_master_keyfile (context, 344 mydb, server_context->config.stash_file); 345 if(ret) 346 krb5_err(context,1, ret, "hdb_set_master_keyfile"); 347 348 /* I really want to use O_EXCL here, but given that I can't easily clean 349 up on error, I won't */ 350 ret = mydb->hdb_open(context, mydb, O_RDWR | O_CREAT | O_TRUNC, 0600); 351 if (ret) 352 krb5_err (context, 1, ret, "db->open"); 353 354 sp = NULL; 355 do { 356 ret = krb5_read_priv_message(context, auth_context, &fd, &data); 357 358 if (ret) 359 krb5_err (context, 1, ret, "krb5_read_priv_message"); 360 361 sp = krb5_storage_from_data (&data); 362 if (sp == NULL) 363 krb5_errx (context, 1, "krb5_storage_from_data"); 364 krb5_ret_int32 (sp, &opcode); 365 if (opcode == ONE_PRINC) { 366 krb5_data fake_data; 367 hdb_entry_ex entry; 368 369 krb5_storage_free(sp); 370 371 fake_data.data = (char *)data.data + 4; 372 fake_data.length = data.length - 4; 373 374 memset(&entry, 0, sizeof(entry)); 375 376 ret = hdb_value2entry (context, &fake_data, &entry.entry); 377 if (ret) 378 krb5_err (context, 1, ret, "hdb_value2entry"); 379 ret = mydb->hdb_store(server_context->context, 380 mydb, 381 0, &entry); 382 if (ret) 383 krb5_err (context, 1, ret, "hdb_store"); 384 385 hdb_free_entry (context, &entry); 386 krb5_data_free (&data); 387 } else if (opcode == NOW_YOU_HAVE) 388 ; 389 else 390 krb5_errx (context, 1, "strange opcode %d", opcode); 391 } while (opcode == ONE_PRINC); 392 393 if (opcode != NOW_YOU_HAVE) 394 krb5_errx (context, 1, "receive_everything: strange %d", opcode); 395 396 krb5_ret_int32 (sp, &vno); 397 krb5_storage_free(sp); 398 399 ret = kadm5_log_reinit (server_context); 400 if (ret) 401 krb5_err(context, 1, ret, "kadm5_log_reinit"); 402 403 ret = kadm5_log_set_version (server_context, vno - 1); 404 if (ret) 405 krb5_err (context, 1, ret, "kadm5_log_set_version"); 406 407 ret = kadm5_log_nop (server_context); 408 if (ret) 409 krb5_err (context, 1, ret, "kadm5_log_nop"); 410 411 krb5_data_free (&data); 412 413 ret = mydb->hdb_rename (context, mydb, server_context->db->hdb_name); 414 if (ret) 415 krb5_err (context, 1, ret, "db->rename"); 416 417 ret = mydb->hdb_close (context, mydb); 418 if (ret) 419 krb5_err (context, 1, ret, "db->close"); 420 421 ret = mydb->hdb_destroy (context, mydb); 422 if (ret) 423 krb5_err (context, 1, ret, "db->destroy"); 424 425 krb5_warnx(context, "receive complete database, version %ld", (long)vno); 426 } 427 428 static char *config_file; 429 static char *realm; 430 static int version_flag; 431 static int help_flag; 432 static char *keytab_str; 433 static char *port_str; 434 static int detach_from_console = 0; 435 436 static struct getargs args[] = { 437 { "config-file", 'c', arg_string, &config_file }, 438 { "realm", 'r', arg_string, &realm }, 439 { "keytab", 'k', arg_string, &keytab_str, 440 "keytab to get authentication from", "kspec" }, 441 { "time-lost", 0, arg_string, &server_time_lost, 442 "time before server is considered lost", "time" }, 443 { "port", 0, arg_string, &port_str, 444 "port ipropd-slave will connect to", "port"}, 445 { "detach", 0, arg_flag, &detach_from_console, 446 "detach from console" }, 447 { "hostname", 0, arg_string, &slave_str, 448 "hostname of slave (if not same as hostname)", "hostname" }, 449 { "version", 0, arg_flag, &version_flag }, 450 { "help", 0, arg_flag, &help_flag } 451 }; 452 453 static int num_args = sizeof(args) / sizeof(args[0]); 454 455 int 456 main(int argc, char **argv) 457 { 458 krb5_error_code ret; 459 krb5_context context; 460 krb5_auth_context auth_context; 461 void *kadm_handle; 462 kadm5_server_context *server_context; 463 kadm5_config_params conf; 464 int master_fd; 465 krb5_ccache ccache; 466 krb5_principal server; 467 char **files; 468 int optidx; 469 470 const char *master; 471 472 optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL); 473 474 if(help_flag) 475 krb5_std_usage(0, args, num_args); 476 if(version_flag) { 477 print_version(NULL); 478 exit(0); 479 } 480 481 setup_signal(); 482 483 if (config_file == NULL) { 484 asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); 485 if (config_file == NULL) 486 errx(1, "out of memory"); 487 } 488 489 ret = krb5_prepend_config_files_default(config_file, &files); 490 if (ret) 491 krb5_err(context, 1, ret, "getting configuration files"); 492 493 ret = krb5_set_config_files(context, files); 494 krb5_free_config_files(files); 495 if (ret) 496 krb5_err(context, 1, ret, "reading configuration files"); 497 498 argc -= optidx; 499 argv += optidx; 500 501 if (argc != 1) 502 krb5_std_usage(1, args, num_args); 503 504 master = argv[0]; 505 506 if (detach_from_console) 507 daemon(0, 0); 508 pidfile (NULL); 509 krb5_openlog (context, "ipropd-slave", &log_facility); 510 krb5_set_warn_dest(context, log_facility); 511 512 ret = krb5_kt_register(context, &hdb_kt_ops); 513 if(ret) 514 krb5_err(context, 1, ret, "krb5_kt_register"); 515 516 time_before_lost = parse_time (server_time_lost, "s"); 517 if (time_before_lost < 0) 518 krb5_errx (context, 1, "couldn't parse time: %s", server_time_lost); 519 520 memset(&conf, 0, sizeof(conf)); 521 if(realm) { 522 conf.mask |= KADM5_CONFIG_REALM; 523 conf.realm = realm; 524 } 525 ret = kadm5_init_with_password_ctx (context, 526 KADM5_ADMIN_SERVICE, 527 NULL, 528 KADM5_ADMIN_SERVICE, 529 &conf, 0, 0, 530 &kadm_handle); 531 if (ret) 532 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 533 534 server_context = (kadm5_server_context *)kadm_handle; 535 536 ret = kadm5_log_init (server_context); 537 if (ret) 538 krb5_err (context, 1, ret, "kadm5_log_init"); 539 540 get_creds(context, keytab_str, &ccache, master); 541 542 master_fd = connect_to_master (context, master, port_str); 543 544 ret = krb5_sname_to_principal (context, master, IPROP_NAME, 545 KRB5_NT_SRV_HST, &server); 546 if (ret) 547 krb5_err (context, 1, ret, "krb5_sname_to_principal"); 548 549 auth_context = NULL; 550 ret = krb5_sendauth (context, &auth_context, &master_fd, 551 IPROP_VERSION, NULL, server, 552 AP_OPTS_MUTUAL_REQUIRED, NULL, NULL, 553 ccache, NULL, NULL, NULL); 554 if (ret) 555 krb5_err (context, 1, ret, "krb5_sendauth"); 556 557 krb5_warnx(context, "ipropd-slave started at version: %ld", 558 (long)server_context->log_context.version); 559 560 ihave (context, auth_context, master_fd, 561 server_context->log_context.version); 562 563 while (exit_flag == 0) { 564 krb5_data out; 565 krb5_storage *sp; 566 int32_t tmp; 567 fd_set readset; 568 struct timeval to; 569 570 if (master_fd >= FD_SETSIZE) 571 krb5_errx (context, 1, "fd too large"); 572 573 FD_ZERO(&readset); 574 FD_SET(master_fd, &readset); 575 576 to.tv_sec = time_before_lost; 577 to.tv_usec = 0; 578 579 ret = select (master_fd + 1, 580 &readset, NULL, NULL, &to); 581 if (ret < 0) { 582 if (errno == EINTR) 583 continue; 584 else 585 krb5_err (context, 1, errno, "select"); 586 } 587 if (ret == 0) 588 krb5_errx (context, 1, "server didn't send a message " 589 "in %d seconds", time_before_lost); 590 591 ret = krb5_read_priv_message(context, auth_context, &master_fd, &out); 592 593 if (ret) 594 krb5_err (context, 1, ret, "krb5_read_priv_message"); 595 596 sp = krb5_storage_from_mem (out.data, out.length); 597 krb5_ret_int32 (sp, &tmp); 598 switch (tmp) { 599 case FOR_YOU : 600 receive (context, sp, server_context); 601 ihave (context, auth_context, master_fd, 602 server_context->log_context.version); 603 break; 604 case TELL_YOU_EVERYTHING : 605 receive_everything (context, master_fd, server_context, 606 auth_context); 607 break; 608 case ARE_YOU_THERE : 609 send_im_here (context, master_fd, auth_context); 610 break; 611 case NOW_YOU_HAVE : 612 case I_HAVE : 613 case ONE_PRINC : 614 case I_AM_HERE : 615 default : 616 krb5_warnx (context, "Ignoring command %d", tmp); 617 break; 618 } 619 krb5_storage_free (sp); 620 krb5_data_free (&out); 621 } 622 623 if(exit_flag == SIGXCPU) 624 krb5_warnx(context, "%s CPU time limit exceeded", getprogname()); 625 else if(exit_flag == SIGINT || exit_flag == SIGTERM) 626 krb5_warnx(context, "%s terminated", getprogname()); 627 else 628 krb5_warnx(context, "%s unexpected exit reason: %d", 629 getprogname(), exit_flag); 630 631 return 0; 632 } 633