1 /* 2 * Copyright (c) 1997 - 2006 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 "kadm5_locl.h" 35 #include <sys/types.h> 36 #include <sys/socket.h> 37 #include <netinet/in.h> 38 #include <netdb.h> 39 40 RCSID("$Id: init_c.c 21972 2007-10-18 19:11:15Z lha $"); 41 42 static void 43 set_funcs(kadm5_client_context *c) 44 { 45 #define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F 46 SET(c, chpass_principal); 47 SET(c, chpass_principal_with_key); 48 SET(c, create_principal); 49 SET(c, delete_principal); 50 SET(c, destroy); 51 SET(c, flush); 52 SET(c, get_principal); 53 SET(c, get_principals); 54 SET(c, get_privs); 55 SET(c, modify_principal); 56 SET(c, randkey_principal); 57 SET(c, rename_principal); 58 } 59 60 kadm5_ret_t 61 _kadm5_c_init_context(kadm5_client_context **ctx, 62 kadm5_config_params *params, 63 krb5_context context) 64 { 65 krb5_error_code ret; 66 char *colon; 67 68 *ctx = malloc(sizeof(**ctx)); 69 if(*ctx == NULL) 70 return ENOMEM; 71 memset(*ctx, 0, sizeof(**ctx)); 72 krb5_add_et_list (context, initialize_kadm5_error_table_r); 73 set_funcs(*ctx); 74 (*ctx)->context = context; 75 if(params->mask & KADM5_CONFIG_REALM) { 76 ret = 0; 77 (*ctx)->realm = strdup(params->realm); 78 if ((*ctx)->realm == NULL) 79 ret = ENOMEM; 80 } else 81 ret = krb5_get_default_realm((*ctx)->context, &(*ctx)->realm); 82 if (ret) { 83 free(*ctx); 84 return ret; 85 } 86 if(params->mask & KADM5_CONFIG_ADMIN_SERVER) 87 (*ctx)->admin_server = strdup(params->admin_server); 88 else { 89 char **hostlist; 90 91 ret = krb5_get_krb_admin_hst (context, &(*ctx)->realm, &hostlist); 92 if (ret) { 93 free((*ctx)->realm); 94 free(*ctx); 95 return ret; 96 } 97 (*ctx)->admin_server = strdup(*hostlist); 98 krb5_free_krbhst (context, hostlist); 99 } 100 101 if ((*ctx)->admin_server == NULL) { 102 free((*ctx)->realm); 103 free(*ctx); 104 return ENOMEM; 105 } 106 colon = strchr ((*ctx)->admin_server, ':'); 107 if (colon != NULL) 108 *colon++ = '\0'; 109 110 (*ctx)->kadmind_port = 0; 111 112 if(params->mask & KADM5_CONFIG_KADMIND_PORT) 113 (*ctx)->kadmind_port = params->kadmind_port; 114 else if (colon != NULL) { 115 char *end; 116 117 (*ctx)->kadmind_port = htons(strtol (colon, &end, 0)); 118 } 119 if ((*ctx)->kadmind_port == 0) 120 (*ctx)->kadmind_port = krb5_getportbyname (context, "kerberos-adm", 121 "tcp", 749); 122 return 0; 123 } 124 125 static krb5_error_code 126 get_kadm_ticket(krb5_context context, 127 krb5_ccache id, 128 krb5_principal client, 129 const char *server_name) 130 { 131 krb5_error_code ret; 132 krb5_creds in, *out; 133 134 memset(&in, 0, sizeof(in)); 135 in.client = client; 136 ret = krb5_parse_name(context, server_name, &in.server); 137 if(ret) 138 return ret; 139 ret = krb5_get_credentials(context, 0, id, &in, &out); 140 if(ret == 0) 141 krb5_free_creds(context, out); 142 krb5_free_principal(context, in.server); 143 return ret; 144 } 145 146 static krb5_error_code 147 get_new_cache(krb5_context context, 148 krb5_principal client, 149 const char *password, 150 krb5_prompter_fct prompter, 151 const char *keytab, 152 const char *server_name, 153 krb5_ccache *ret_cache) 154 { 155 krb5_error_code ret; 156 krb5_creds cred; 157 krb5_get_init_creds_opt *opt; 158 krb5_ccache id; 159 160 ret = krb5_get_init_creds_opt_alloc (context, &opt); 161 if (ret) 162 return ret; 163 164 krb5_get_init_creds_opt_set_default_flags(context, "kadmin", 165 krb5_principal_get_realm(context, 166 client), 167 opt); 168 169 170 krb5_get_init_creds_opt_set_forwardable (opt, FALSE); 171 krb5_get_init_creds_opt_set_proxiable (opt, FALSE); 172 173 if(password == NULL && prompter == NULL) { 174 krb5_keytab kt; 175 if(keytab == NULL) 176 ret = krb5_kt_default(context, &kt); 177 else 178 ret = krb5_kt_resolve(context, keytab, &kt); 179 if(ret) { 180 krb5_get_init_creds_opt_free(context, opt); 181 return ret; 182 } 183 ret = krb5_get_init_creds_keytab (context, 184 &cred, 185 client, 186 kt, 187 0, 188 server_name, 189 opt); 190 krb5_kt_close(context, kt); 191 } else { 192 ret = krb5_get_init_creds_password (context, 193 &cred, 194 client, 195 password, 196 prompter, 197 NULL, 198 0, 199 server_name, 200 opt); 201 } 202 krb5_get_init_creds_opt_free(context, opt); 203 switch(ret){ 204 case 0: 205 break; 206 case KRB5_LIBOS_PWDINTR: /* don't print anything if it was just C-c:ed */ 207 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 208 case KRB5KRB_AP_ERR_MODIFIED: 209 return KADM5_BAD_PASSWORD; 210 default: 211 return ret; 212 } 213 ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &id); 214 if(ret) 215 return ret; 216 ret = krb5_cc_initialize (context, id, cred.client); 217 if (ret) 218 return ret; 219 ret = krb5_cc_store_cred (context, id, &cred); 220 if (ret) 221 return ret; 222 krb5_free_cred_contents (context, &cred); 223 *ret_cache = id; 224 return 0; 225 } 226 227 /* 228 * Check the credential cache `id� to figure out what principal to use 229 * when talking to the kadmind. If there is a initial kadmin/admin@ 230 * credential in the cache, use that client principal. Otherwise, use 231 * the client principals first component and add /admin to the 232 * principal. 233 */ 234 235 static krb5_error_code 236 get_cache_principal(krb5_context context, 237 krb5_ccache *id, 238 krb5_principal *client) 239 { 240 krb5_error_code ret; 241 const char *name, *inst; 242 krb5_principal p1, p2; 243 244 ret = krb5_cc_default(context, id); 245 if(ret) { 246 *id = NULL; 247 return ret; 248 } 249 250 ret = krb5_cc_get_principal(context, *id, &p1); 251 if(ret) { 252 krb5_cc_close(context, *id); 253 *id = NULL; 254 return ret; 255 } 256 257 ret = krb5_make_principal(context, &p2, NULL, 258 "kadmin", "admin", NULL); 259 if (ret) { 260 krb5_cc_close(context, *id); 261 *id = NULL; 262 krb5_free_principal(context, p1); 263 return ret; 264 } 265 266 { 267 krb5_creds in, *out; 268 krb5_kdc_flags flags; 269 270 flags.i = 0; 271 memset(&in, 0, sizeof(in)); 272 273 in.client = p1; 274 in.server = p2; 275 276 /* check for initial ticket kadmin/admin */ 277 ret = krb5_get_credentials_with_flags(context, KRB5_GC_CACHED, flags, 278 *id, &in, &out); 279 krb5_free_principal(context, p2); 280 if (ret == 0) { 281 if (out->flags.b.initial) { 282 *client = p1; 283 krb5_free_creds(context, out); 284 return 0; 285 } 286 krb5_free_creds(context, out); 287 } 288 } 289 krb5_cc_close(context, *id); 290 *id = NULL; 291 292 name = krb5_principal_get_comp_string(context, p1, 0); 293 inst = krb5_principal_get_comp_string(context, p1, 1); 294 if(inst == NULL || strcmp(inst, "admin") != 0) { 295 ret = krb5_make_principal(context, &p2, NULL, name, "admin", NULL); 296 krb5_free_principal(context, p1); 297 if(ret != 0) 298 return ret; 299 300 *client = p2; 301 return 0; 302 } 303 304 *client = p1; 305 306 return 0; 307 } 308 309 krb5_error_code 310 _kadm5_c_get_cred_cache(krb5_context context, 311 const char *client_name, 312 const char *server_name, 313 const char *password, 314 krb5_prompter_fct prompter, 315 const char *keytab, 316 krb5_ccache ccache, 317 krb5_ccache *ret_cache) 318 { 319 krb5_error_code ret; 320 krb5_ccache id = NULL; 321 krb5_principal default_client = NULL, client = NULL; 322 323 /* treat empty password as NULL */ 324 if(password && *password == '\0') 325 password = NULL; 326 if(server_name == NULL) 327 server_name = KADM5_ADMIN_SERVICE; 328 329 if(client_name != NULL) { 330 ret = krb5_parse_name(context, client_name, &client); 331 if(ret) 332 return ret; 333 } 334 335 if(ccache != NULL) { 336 id = ccache; 337 ret = krb5_cc_get_principal(context, id, &client); 338 if(ret) 339 return ret; 340 } else { 341 /* get principal from default cache, ok if this doesn't work */ 342 343 ret = get_cache_principal(context, &id, &default_client); 344 if (ret) { 345 /* 346 * No client was specified by the caller and we cannot 347 * determine the client from a credentials cache. 348 */ 349 const char *user; 350 351 user = get_default_username (); 352 353 if(user == NULL) { 354 krb5_set_error_string(context, "Unable to find local user name"); 355 return KADM5_FAILURE; 356 } 357 ret = krb5_make_principal(context, &default_client, 358 NULL, user, "admin", NULL); 359 if(ret) 360 return ret; 361 } 362 } 363 364 365 /* 366 * No client was specified by the caller, but we have a client 367 * from the default credentials cache. 368 */ 369 if (client == NULL && default_client != NULL) 370 client = default_client; 371 372 373 if(id && (default_client == NULL || 374 krb5_principal_compare(context, client, default_client))) { 375 ret = get_kadm_ticket(context, id, client, server_name); 376 if(ret == 0) { 377 *ret_cache = id; 378 krb5_free_principal(context, default_client); 379 if (default_client != client) 380 krb5_free_principal(context, client); 381 return 0; 382 } 383 if(ccache != NULL) 384 /* couldn't get ticket from cache */ 385 return -1; 386 } 387 /* get creds via AS request */ 388 if(id && (id != ccache)) 389 krb5_cc_close(context, id); 390 if (client != default_client) 391 krb5_free_principal(context, default_client); 392 393 ret = get_new_cache(context, client, password, prompter, keytab, 394 server_name, ret_cache); 395 krb5_free_principal(context, client); 396 return ret; 397 } 398 399 static kadm5_ret_t 400 kadm_connect(kadm5_client_context *ctx) 401 { 402 kadm5_ret_t ret; 403 krb5_principal server; 404 krb5_ccache cc; 405 int s; 406 struct addrinfo *ai, *a; 407 struct addrinfo hints; 408 int error; 409 char portstr[NI_MAXSERV]; 410 char *hostname, *slash; 411 char *service_name; 412 krb5_context context = ctx->context; 413 414 memset (&hints, 0, sizeof(hints)); 415 hints.ai_socktype = SOCK_STREAM; 416 hints.ai_protocol = IPPROTO_TCP; 417 418 snprintf (portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port)); 419 420 hostname = ctx->admin_server; 421 slash = strchr (hostname, '/'); 422 if (slash != NULL) 423 hostname = slash + 1; 424 425 error = getaddrinfo (hostname, portstr, &hints, &ai); 426 if (error) { 427 krb5_clear_error_string(context); 428 return KADM5_BAD_SERVER_NAME; 429 } 430 431 for (a = ai; a != NULL; a = a->ai_next) { 432 s = socket (a->ai_family, a->ai_socktype, a->ai_protocol); 433 if (s < 0) 434 continue; 435 if (connect (s, a->ai_addr, a->ai_addrlen) < 0) { 436 krb5_clear_error_string(context); 437 krb5_warn (context, errno, "connect(%s)", hostname); 438 close (s); 439 continue; 440 } 441 break; 442 } 443 if (a == NULL) { 444 freeaddrinfo (ai); 445 krb5_clear_error_string(context); 446 krb5_warnx (context, "failed to contact %s", hostname); 447 return KADM5_FAILURE; 448 } 449 ret = _kadm5_c_get_cred_cache(context, 450 ctx->client_name, 451 ctx->service_name, 452 NULL, ctx->prompter, ctx->keytab, 453 ctx->ccache, &cc); 454 455 if(ret) { 456 freeaddrinfo (ai); 457 close(s); 458 return ret; 459 } 460 461 if (ctx->realm) 462 asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE, ctx->realm); 463 else 464 asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE); 465 466 if (service_name == NULL) { 467 freeaddrinfo (ai); 468 close(s); 469 krb5_clear_error_string(context); 470 return ENOMEM; 471 } 472 473 ret = krb5_parse_name(context, service_name, &server); 474 free(service_name); 475 if(ret) { 476 freeaddrinfo (ai); 477 if(ctx->ccache == NULL) 478 krb5_cc_close(context, cc); 479 close(s); 480 return ret; 481 } 482 ctx->ac = NULL; 483 484 ret = krb5_sendauth(context, &ctx->ac, &s, 485 KADMIN_APPL_VERSION, NULL, 486 server, AP_OPTS_MUTUAL_REQUIRED, 487 NULL, NULL, cc, NULL, NULL, NULL); 488 if(ret == 0) { 489 krb5_data params; 490 kadm5_config_params p; 491 memset(&p, 0, sizeof(p)); 492 if(ctx->realm) { 493 p.mask |= KADM5_CONFIG_REALM; 494 p.realm = ctx->realm; 495 } 496 ret = _kadm5_marshal_params(context, &p, ¶ms); 497 498 ret = krb5_write_priv_message(context, ctx->ac, &s, ¶ms); 499 krb5_data_free(¶ms); 500 if(ret) { 501 freeaddrinfo (ai); 502 close(s); 503 if(ctx->ccache == NULL) 504 krb5_cc_close(context, cc); 505 return ret; 506 } 507 } else if(ret == KRB5_SENDAUTH_BADAPPLVERS) { 508 close(s); 509 510 s = socket (a->ai_family, a->ai_socktype, a->ai_protocol); 511 if (s < 0) { 512 freeaddrinfo (ai); 513 krb5_clear_error_string(context); 514 return errno; 515 } 516 if (connect (s, a->ai_addr, a->ai_addrlen) < 0) { 517 close (s); 518 freeaddrinfo (ai); 519 krb5_clear_error_string(context); 520 return errno; 521 } 522 ret = krb5_sendauth(context, &ctx->ac, &s, 523 KADMIN_OLD_APPL_VERSION, NULL, 524 server, AP_OPTS_MUTUAL_REQUIRED, 525 NULL, NULL, cc, NULL, NULL, NULL); 526 } 527 freeaddrinfo (ai); 528 if(ret) { 529 close(s); 530 return ret; 531 } 532 533 krb5_free_principal(context, server); 534 if(ctx->ccache == NULL) 535 krb5_cc_close(context, cc); 536 ctx->sock = s; 537 538 return 0; 539 } 540 541 kadm5_ret_t 542 _kadm5_connect(void *handle) 543 { 544 kadm5_client_context *ctx = handle; 545 if(ctx->sock == -1) 546 return kadm_connect(ctx); 547 return 0; 548 } 549 550 static kadm5_ret_t 551 kadm5_c_init_with_context(krb5_context context, 552 const char *client_name, 553 const char *password, 554 krb5_prompter_fct prompter, 555 const char *keytab, 556 krb5_ccache ccache, 557 const char *service_name, 558 kadm5_config_params *realm_params, 559 unsigned long struct_version, 560 unsigned long api_version, 561 void **server_handle) 562 { 563 kadm5_ret_t ret; 564 kadm5_client_context *ctx; 565 krb5_ccache cc; 566 567 ret = _kadm5_c_init_context(&ctx, realm_params, context); 568 if(ret) 569 return ret; 570 571 if(password != NULL && *password != '\0') { 572 ret = _kadm5_c_get_cred_cache(context, 573 client_name, 574 service_name, 575 password, prompter, keytab, ccache, &cc); 576 if(ret) 577 return ret; /* XXX */ 578 ccache = cc; 579 } 580 581 582 if (client_name != NULL) 583 ctx->client_name = strdup(client_name); 584 else 585 ctx->client_name = NULL; 586 if (service_name != NULL) 587 ctx->service_name = strdup(service_name); 588 else 589 ctx->service_name = NULL; 590 ctx->prompter = prompter; 591 ctx->keytab = keytab; 592 ctx->ccache = ccache; 593 /* maybe we should copy the params here */ 594 ctx->sock = -1; 595 596 *server_handle = ctx; 597 return 0; 598 } 599 600 static kadm5_ret_t 601 init_context(const char *client_name, 602 const char *password, 603 krb5_prompter_fct prompter, 604 const char *keytab, 605 krb5_ccache ccache, 606 const char *service_name, 607 kadm5_config_params *realm_params, 608 unsigned long struct_version, 609 unsigned long api_version, 610 void **server_handle) 611 { 612 krb5_context context; 613 kadm5_ret_t ret; 614 kadm5_server_context *ctx; 615 616 ret = krb5_init_context(&context); 617 if (ret) 618 return ret; 619 ret = kadm5_c_init_with_context(context, 620 client_name, 621 password, 622 prompter, 623 keytab, 624 ccache, 625 service_name, 626 realm_params, 627 struct_version, 628 api_version, 629 server_handle); 630 if(ret){ 631 krb5_free_context(context); 632 return ret; 633 } 634 ctx = *server_handle; 635 ctx->my_context = 1; 636 return 0; 637 } 638 639 kadm5_ret_t 640 kadm5_c_init_with_password_ctx(krb5_context context, 641 const char *client_name, 642 const char *password, 643 const char *service_name, 644 kadm5_config_params *realm_params, 645 unsigned long struct_version, 646 unsigned long api_version, 647 void **server_handle) 648 { 649 return kadm5_c_init_with_context(context, 650 client_name, 651 password, 652 krb5_prompter_posix, 653 NULL, 654 NULL, 655 service_name, 656 realm_params, 657 struct_version, 658 api_version, 659 server_handle); 660 } 661 662 kadm5_ret_t 663 kadm5_c_init_with_password(const char *client_name, 664 const char *password, 665 const char *service_name, 666 kadm5_config_params *realm_params, 667 unsigned long struct_version, 668 unsigned long api_version, 669 void **server_handle) 670 { 671 return init_context(client_name, 672 password, 673 krb5_prompter_posix, 674 NULL, 675 NULL, 676 service_name, 677 realm_params, 678 struct_version, 679 api_version, 680 server_handle); 681 } 682 683 kadm5_ret_t 684 kadm5_c_init_with_skey_ctx(krb5_context context, 685 const char *client_name, 686 const char *keytab, 687 const char *service_name, 688 kadm5_config_params *realm_params, 689 unsigned long struct_version, 690 unsigned long api_version, 691 void **server_handle) 692 { 693 return kadm5_c_init_with_context(context, 694 client_name, 695 NULL, 696 NULL, 697 keytab, 698 NULL, 699 service_name, 700 realm_params, 701 struct_version, 702 api_version, 703 server_handle); 704 } 705 706 707 kadm5_ret_t 708 kadm5_c_init_with_skey(const char *client_name, 709 const char *keytab, 710 const char *service_name, 711 kadm5_config_params *realm_params, 712 unsigned long struct_version, 713 unsigned long api_version, 714 void **server_handle) 715 { 716 return init_context(client_name, 717 NULL, 718 NULL, 719 keytab, 720 NULL, 721 service_name, 722 realm_params, 723 struct_version, 724 api_version, 725 server_handle); 726 } 727 728 kadm5_ret_t 729 kadm5_c_init_with_creds_ctx(krb5_context context, 730 const char *client_name, 731 krb5_ccache ccache, 732 const char *service_name, 733 kadm5_config_params *realm_params, 734 unsigned long struct_version, 735 unsigned long api_version, 736 void **server_handle) 737 { 738 return kadm5_c_init_with_context(context, 739 client_name, 740 NULL, 741 NULL, 742 NULL, 743 ccache, 744 service_name, 745 realm_params, 746 struct_version, 747 api_version, 748 server_handle); 749 } 750 751 kadm5_ret_t 752 kadm5_c_init_with_creds(const char *client_name, 753 krb5_ccache ccache, 754 const char *service_name, 755 kadm5_config_params *realm_params, 756 unsigned long struct_version, 757 unsigned long api_version, 758 void **server_handle) 759 { 760 return init_context(client_name, 761 NULL, 762 NULL, 763 NULL, 764 ccache, 765 service_name, 766 realm_params, 767 struct_version, 768 api_version, 769 server_handle); 770 } 771 772 #if 0 773 kadm5_ret_t 774 kadm5_init(char *client_name, char *pass, 775 char *service_name, 776 kadm5_config_params *realm_params, 777 unsigned long struct_version, 778 unsigned long api_version, 779 void **server_handle) 780 { 781 } 782 #endif 783 784