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