1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* kprop/kprop.c */ 3 /* 4 * Copyright 1990,1991,2008 by the Massachusetts Institute of Technology. 5 * All Rights Reserved. 6 * 7 * Export of this software from the United States of America may 8 * require a specific license from the United States Government. 9 * It is the responsibility of any person or organization contemplating 10 * export to obtain such a license before exporting. 11 * 12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 13 * distribute this software and its documentation for any purpose and 14 * without fee is hereby granted, provided that the above copyright 15 * notice appear in all copies and that both that copyright notice and 16 * this permission notice appear in supporting documentation, and that 17 * the name of M.I.T. not be used in advertising or publicity pertaining 18 * to distribution of the software without specific, written prior 19 * permission. Furthermore if you modify this software you must label 20 * your software as modified software and not distribute it in such a 21 * fashion that it might be confused with the original M.I.T. software. 22 * M.I.T. makes no representations about the suitability of 23 * this software for any purpose. It is provided "as is" without express 24 * or implied warranty. 25 */ 26 27 #include "k5-int.h" 28 #include <inttypes.h> 29 #include <locale.h> 30 #include <sys/file.h> 31 #include <signal.h> 32 #include <sys/types.h> 33 #include <sys/time.h> 34 #include <sys/stat.h> 35 #include <sys/socket.h> 36 #include <netinet/in.h> 37 #include <sys/param.h> 38 #include <netdb.h> 39 #include <fcntl.h> 40 41 #include "com_err.h" 42 #include "fake-addrinfo.h" 43 #include "kprop.h" 44 45 #ifndef GETSOCKNAME_ARG3_TYPE 46 #define GETSOCKNAME_ARG3_TYPE unsigned int 47 #endif 48 49 static char *kprop_version = KPROP_PROT_VERSION; 50 51 static char *progname = NULL; 52 static int debug = 0; 53 static char *keytab_path = NULL; 54 static char *replica_host; 55 static char *realm = NULL; 56 static char *def_realm = NULL; 57 static char *file = KPROP_DEFAULT_FILE; 58 59 /* The Kerberos principal we'll be sending as, initialized in get_tickets. */ 60 static krb5_principal my_principal; 61 62 static krb5_creds creds; 63 static krb5_address *sender_addr; 64 static const char *port = KPROP_SERVICE; 65 static char *dbpathname; 66 67 static void parse_args(krb5_context context, int argc, char **argv); 68 static void get_tickets(krb5_context context); 69 static void usage(void); 70 static void open_connection(krb5_context context, char *host, int *fd_out); 71 static void kerberos_authenticate(krb5_context context, 72 krb5_auth_context *auth_context, int fd, 73 krb5_principal me, krb5_creds **new_creds); 74 static int open_database(krb5_context context, char *data_fn, off_t *size); 75 static void close_database(krb5_context context, int fd); 76 static void xmit_database(krb5_context context, 77 krb5_auth_context auth_context, krb5_creds *my_creds, 78 int fd, int database_fd, off_t in_database_size); 79 static void send_error(krb5_context context, krb5_creds *my_creds, int fd, 80 char *err_text, krb5_error_code err_code); 81 static void update_last_prop_file(char *hostname, char *file_name); 82 83 static void usage(void) 84 { 85 fprintf(stderr, _("\nUsage: %s [-r realm] [-f file] [-d] [-P port] " 86 "[-s keytab] replica_host\n\n"), progname); 87 exit(1); 88 } 89 90 int 91 main(int argc, char **argv) 92 { 93 int fd, database_fd; 94 off_t database_size; 95 krb5_error_code retval; 96 krb5_context context; 97 krb5_creds *my_creds; 98 krb5_auth_context auth_context; 99 100 setlocale(LC_ALL, ""); 101 retval = krb5_init_context(&context); 102 if (retval) { 103 com_err(argv[0], retval, _("while initializing krb5")); 104 exit(1); 105 } 106 parse_args(context, argc, argv); 107 get_tickets(context); 108 109 database_fd = open_database(context, file, &database_size); 110 open_connection(context, replica_host, &fd); 111 kerberos_authenticate(context, &auth_context, fd, my_principal, &my_creds); 112 xmit_database(context, auth_context, my_creds, fd, database_fd, 113 database_size); 114 update_last_prop_file(replica_host, file); 115 printf(_("Database propagation to %s: SUCCEEDED\n"), replica_host); 116 krb5_free_cred_contents(context, my_creds); 117 close_database(context, database_fd); 118 krb5_free_default_realm(context, def_realm); 119 exit(0); 120 } 121 122 static void 123 parse_args(krb5_context context, int argc, char **argv) 124 { 125 int c; 126 krb5_error_code ret; 127 128 progname = argv[0]; 129 while ((c = getopt(argc, argv, "r:f:dP:s:")) != -1) { 130 switch (c) { 131 case 'r': 132 realm = optarg; 133 break; 134 case 'f': 135 file = optarg; 136 break; 137 case 'd': 138 debug++; 139 break; 140 case 'P': 141 port = optarg; 142 break; 143 case 's': 144 keytab_path = optarg; 145 break; 146 default: 147 usage(); 148 } 149 } 150 if (argc - optind != 1) 151 usage(); 152 replica_host = argv[optind]; 153 154 if (realm == NULL) { 155 ret = krb5_get_default_realm(context, &def_realm); 156 if (ret) { 157 com_err(progname, errno, _("while getting default realm")); 158 exit(1); 159 } 160 realm = def_realm; 161 } 162 } 163 164 static void 165 get_tickets(krb5_context context) 166 { 167 char *server; 168 krb5_error_code retval; 169 krb5_keytab keytab = NULL; 170 krb5_principal server_princ = NULL; 171 172 /* Figure out what tickets we'll be using to send. */ 173 retval = sn2princ_realm(context, NULL, KPROP_SERVICE_NAME, realm, 174 &my_principal); 175 if (retval) { 176 com_err(progname, errno, _("while setting client principal name")); 177 exit(1); 178 } 179 180 /* Construct the principal name for the replica host. */ 181 memset(&creds, 0, sizeof(creds)); 182 retval = sn2princ_realm(context, replica_host, KPROP_SERVICE_NAME, realm, 183 &server_princ); 184 if (retval) { 185 com_err(progname, errno, _("while setting server principal name")); 186 exit(1); 187 } 188 retval = krb5_unparse_name_flags(context, server_princ, 189 KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server); 190 if (retval) { 191 com_err(progname, retval, _("while unparsing server name")); 192 exit(1); 193 } 194 195 if (keytab_path != NULL) { 196 retval = krb5_kt_resolve(context, keytab_path, &keytab); 197 if (retval) { 198 com_err(progname, retval, _("while resolving keytab")); 199 exit(1); 200 } 201 } 202 203 retval = krb5_get_init_creds_keytab(context, &creds, my_principal, keytab, 204 0, server, NULL); 205 if (retval) { 206 com_err(progname, retval, _("while getting initial credentials\n")); 207 exit(1); 208 } 209 210 if (keytab != NULL) 211 krb5_kt_close(context, keytab); 212 krb5_free_unparsed_name(context, server); 213 krb5_free_principal(context, server_princ); 214 } 215 216 static void 217 open_connection(krb5_context context, char *host, int *fd_out) 218 { 219 krb5_error_code retval; 220 krb5_address addr; 221 GETSOCKNAME_ARG3_TYPE socket_length; 222 struct addrinfo hints, *res, *answers; 223 struct sockaddr_storage my_sin; 224 int s, error; 225 226 *fd_out = -1; 227 memset(&hints, 0, sizeof(hints)); 228 hints.ai_family = PF_UNSPEC; 229 hints.ai_socktype = SOCK_STREAM; 230 hints.ai_flags = AI_ADDRCONFIG; 231 error = getaddrinfo(host, port, &hints, &answers); 232 if (error != 0) { 233 com_err(progname, 0, "%s: %s", host, gai_strerror(error)); 234 exit(1); 235 } 236 237 s = -1; 238 retval = EINVAL; 239 for (res = answers; res != NULL; res = res->ai_next) { 240 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 241 if (s < 0) { 242 com_err(progname, errno, _("while creating socket")); 243 exit(1); 244 } 245 246 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { 247 retval = errno; 248 close(s); 249 s = -1; 250 continue; 251 } 252 253 /* We successfully connect()ed */ 254 *fd_out = s; 255 256 break; 257 } 258 259 freeaddrinfo(answers); 260 261 if (s == -1) { 262 com_err(progname, retval, _("while connecting to server")); 263 exit(1); 264 } 265 266 /* Set sender_addr. */ 267 socket_length = sizeof(my_sin); 268 if (getsockname(s, (struct sockaddr *)&my_sin, &socket_length) < 0) { 269 com_err(progname, errno, _("while getting local socket address")); 270 exit(1); 271 } 272 if (k5_sockaddr_to_address(ss2sa(&my_sin), FALSE, &addr) != 0) 273 addr = k5_addr_directional_init; 274 retval = krb5_copy_addr(context, &addr, &sender_addr); 275 if (retval) { 276 com_err(progname, retval, _("while converting local address")); 277 exit(1); 278 } 279 } 280 281 static void 282 kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context, 283 int fd, krb5_principal me, krb5_creds **new_creds) 284 { 285 krb5_error_code retval; 286 krb5_error *error = NULL; 287 krb5_ap_rep_enc_part *rep_result; 288 289 retval = krb5_auth_con_init(context, auth_context); 290 if (retval) 291 exit(1); 292 293 krb5_auth_con_setflags(context, *auth_context, 294 KRB5_AUTH_CONTEXT_DO_SEQUENCE); 295 296 retval = krb5_auth_con_setaddrs(context, *auth_context, sender_addr, NULL); 297 if (retval) { 298 com_err(progname, retval, _("in krb5_auth_con_setaddrs")); 299 exit(1); 300 } 301 302 retval = krb5_sendauth(context, auth_context, &fd, kprop_version, 303 me, creds.server, AP_OPTS_MUTUAL_REQUIRED, NULL, 304 &creds, NULL, &error, &rep_result, new_creds); 305 if (retval) { 306 com_err(progname, retval, _("while authenticating to server")); 307 if (error != NULL) { 308 if (error->error == KRB_ERR_GENERIC) { 309 if (error->text.data) { 310 fprintf(stderr, _("Generic remote error: %s\n"), 311 error->text.data); 312 } 313 } else if (error->error) { 314 com_err(progname, 315 (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5, 316 _("signalled from server")); 317 if (error->text.data) { 318 fprintf(stderr, _("Error text from server: %s\n"), 319 error->text.data); 320 } 321 } 322 krb5_free_error(context, error); 323 } 324 exit(1); 325 } 326 krb5_free_ap_rep_enc_part(context, rep_result); 327 } 328 329 /* 330 * Open the Kerberos database dump file. Takes care of locking it 331 * and making sure that the .ok file is more recent that the database 332 * dump file itself. 333 * 334 * Returns the file descriptor of the database dump file. Also fills 335 * in the size of the database file. 336 */ 337 static int 338 open_database(krb5_context context, char *data_fn, off_t *size) 339 { 340 struct stat stbuf, stbuf_ok; 341 char *data_ok_fn; 342 int fd, err; 343 344 dbpathname = strdup(data_fn); 345 if (dbpathname == NULL) { 346 com_err(progname, ENOMEM, _("allocating database file name '%s'"), 347 data_fn); 348 exit(1); 349 } 350 fd = open(dbpathname, O_RDONLY); 351 if (fd < 0) { 352 com_err(progname, errno, _("while trying to open %s"), dbpathname); 353 exit(1); 354 } 355 356 err = krb5_lock_file(context, fd, 357 KRB5_LOCKMODE_SHARED | KRB5_LOCKMODE_DONTBLOCK); 358 if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) { 359 com_err(progname, 0, _("database locked")); 360 exit(1); 361 } else if (err) { 362 com_err(progname, err, _("while trying to lock '%s'"), dbpathname); 363 exit(1); 364 } 365 if (fstat(fd, &stbuf)) { 366 com_err(progname, errno, _("while trying to stat %s"), data_fn); 367 exit(1); 368 } 369 if (asprintf(&data_ok_fn, "%s.dump_ok", data_fn) < 0) { 370 com_err(progname, ENOMEM, _("while trying to malloc data_ok_fn")); 371 exit(1); 372 } 373 if (stat(data_ok_fn, &stbuf_ok)) { 374 com_err(progname, errno, _("while trying to stat %s"), data_ok_fn); 375 free(data_ok_fn); 376 exit(1); 377 } 378 if (stbuf.st_mtime > stbuf_ok.st_mtime) { 379 com_err(progname, 0, _("'%s' more recent than '%s'."), data_fn, 380 data_ok_fn); 381 exit(1); 382 } 383 free(data_ok_fn); 384 *size = stbuf.st_size; 385 return fd; 386 } 387 388 static void 389 close_database(krb5_context context, int fd) 390 { 391 int err; 392 393 err = krb5_lock_file(context, fd, KRB5_LOCKMODE_UNLOCK); 394 if (err) 395 com_err(progname, err, _("while unlocking database '%s'"), dbpathname); 396 free(dbpathname); 397 close(fd); 398 } 399 400 /* 401 * Now we send over the database. We use the following protocol: 402 * Send over a KRB_SAFE message with the size. Then we send over the 403 * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV. 404 * Then we expect to see a KRB_SAFE message with the size sent back. 405 * 406 * At any point in the protocol, we may send a KRB_ERROR message; this 407 * will abort the entire operation. 408 */ 409 static void 410 xmit_database(krb5_context context, krb5_auth_context auth_context, 411 krb5_creds *my_creds, int fd, int database_fd, 412 off_t in_database_size) 413 { 414 krb5_int32 n; 415 krb5_data inbuf, outbuf; 416 char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ]; 417 krb5_error_code retval; 418 krb5_error *error; 419 uint64_t database_size = in_database_size, send_size, sent_size; 420 421 /* Send over the size. */ 422 inbuf = make_data(dbsize_buf, sizeof(dbsize_buf)); 423 encode_database_size(database_size, &inbuf); 424 /* KPROP_CKSUMTYPE */ 425 retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL); 426 if (retval) { 427 com_err(progname, retval, _("while encoding database size")); 428 send_error(context, my_creds, fd, _("while encoding database size"), 429 retval); 430 exit(1); 431 } 432 433 retval = krb5_write_message(context, &fd, &outbuf); 434 if (retval) { 435 krb5_free_data_contents(context, &outbuf); 436 com_err(progname, retval, _("while sending database size")); 437 exit(1); 438 } 439 krb5_free_data_contents(context, &outbuf); 440 441 /* Initialize the initial vector. */ 442 retval = krb5_auth_con_initivector(context, auth_context); 443 if (retval) { 444 send_error(context, my_creds, fd, 445 "failed while initializing i_vector", retval); 446 com_err(progname, retval, _("while allocating i_vector")); 447 exit(1); 448 } 449 450 /* Send over the file, block by block. */ 451 inbuf.data = buf; 452 sent_size = 0; 453 while ((n = read(database_fd, buf, sizeof(buf)))) { 454 inbuf.length = n; 455 retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL); 456 if (retval) { 457 snprintf(buf, sizeof(buf), 458 "while encoding database block starting at %"PRIu64, 459 sent_size); 460 com_err(progname, retval, "%s", buf); 461 send_error(context, my_creds, fd, buf, retval); 462 exit(1); 463 } 464 465 retval = krb5_write_message(context, &fd, &outbuf); 466 if (retval) { 467 krb5_free_data_contents(context, &outbuf); 468 com_err(progname, retval, 469 _("while sending database block starting at %"PRIu64), 470 sent_size); 471 exit(1); 472 } 473 krb5_free_data_contents(context, &outbuf); 474 sent_size += n; 475 if (debug) 476 printf("%"PRIu64" bytes sent.\n", sent_size); 477 } 478 if (sent_size != database_size) { 479 com_err(progname, 0, _("Premature EOF found for database file!")); 480 send_error(context, my_creds, fd, 481 "Premature EOF found for database file!", 482 KRB5KRB_ERR_GENERIC); 483 exit(1); 484 } 485 486 /* 487 * OK, we've sent the database; now let's wait for a success 488 * indication from the remote end. 489 */ 490 retval = krb5_read_message(context, &fd, &inbuf); 491 if (retval) { 492 com_err(progname, retval, _("while reading response from server")); 493 exit(1); 494 } 495 /* 496 * If we got an error response back from the server, display 497 * the error message 498 */ 499 if (krb5_is_krb_error(&inbuf)) { 500 retval = krb5_rd_error(context, &inbuf, &error); 501 if (retval) { 502 com_err(progname, retval, 503 _("while decoding error response from server")); 504 exit(1); 505 } 506 if (error->error == KRB_ERR_GENERIC) { 507 if (error->text.data) { 508 fprintf(stderr, _("Generic remote error: %s\n"), 509 error->text.data); 510 } 511 } else if (error->error) { 512 com_err(progname, 513 (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5, 514 _("signalled from server")); 515 if (error->text.data) { 516 fprintf(stderr, _("Error text from server: %s\n"), 517 error->text.data); 518 } 519 } 520 krb5_free_error(context, error); 521 exit(1); 522 } 523 524 retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL); 525 if (retval) { 526 com_err(progname, retval, 527 "while decoding final size packet from server"); 528 exit(1); 529 } 530 531 retval = decode_database_size(&outbuf, &send_size); 532 if (retval) { 533 com_err(progname, retval, _("malformed sent database size message")); 534 exit(1); 535 } 536 if (send_size != database_size) { 537 com_err(progname, 0, _("Kpropd sent database size %"PRIu64 538 ", expecting %"PRIu64), 539 send_size, database_size); 540 exit(1); 541 } 542 free(inbuf.data); 543 free(outbuf.data); 544 } 545 546 static void 547 send_error(krb5_context context, krb5_creds *my_creds, int fd, char *err_text, 548 krb5_error_code err_code) 549 { 550 krb5_error error; 551 const char *text; 552 krb5_data outbuf; 553 554 memset(&error, 0, sizeof(error)); 555 krb5_us_timeofday(context, &error.ctime, &error.cusec); 556 error.server = my_creds->server; 557 error.client = my_principal; 558 error.error = err_code - ERROR_TABLE_BASE_krb5; 559 if (error.error > 127) 560 error.error = KRB_ERR_GENERIC; 561 text = (err_text != NULL) ? err_text : error_message(err_code); 562 error.text.length = strlen(text) + 1; 563 error.text.data = strdup(text); 564 if (error.text.data) { 565 if (!krb5_mk_error(context, &error, &outbuf)) { 566 (void)krb5_write_message(context, &fd, &outbuf); 567 krb5_free_data_contents(context, &outbuf); 568 } 569 free(error.text.data); 570 } 571 } 572 573 static void 574 update_last_prop_file(char *hostname, char *file_name) 575 { 576 char *file_last_prop; 577 int fd; 578 static char last_prop[] = ".last_prop"; 579 580 if (asprintf(&file_last_prop, "%s.%s%s", file_name, hostname, 581 last_prop) < 0) { 582 com_err(progname, ENOMEM, 583 _("while allocating filename for update_last_prop_file")); 584 return; 585 } 586 fd = THREEPARAMOPEN(file_last_prop, O_WRONLY | O_CREAT | O_TRUNC, 0600); 587 if (fd < 0) { 588 com_err(progname, errno, _("while creating 'last_prop' file, '%s'"), 589 file_last_prop); 590 free(file_last_prop); 591 return; 592 } 593 write(fd, "", 1); 594 free(file_last_prop); 595 close(fd); 596 } 597