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