1 /* 2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */ 3 4 #include "k5-int.h" 5 #include "com_err.h" 6 #include <admin.h> 7 #include <locale.h> 8 #include <syslog.h> 9 10 /* Solaris Kerberos: 11 * 12 * Change Password functionality is handled by the libkadm5clnt.so.1 library in 13 * Solaris Kerberos. In order to avoid a circular dependency between that lib 14 * and the kerberos mech lib, we use the #pragma weak compiler directive. 15 * This way, when applications link with the libkadm5clnt.so.1 lib the circular 16 * dependancy between the two libs will be resolved. 17 */ 18 19 #pragma weak kadm5_get_cpw_host_srv_name 20 #pragma weak kadm5_init_with_password 21 #pragma weak kadm5_chpass_principal_util 22 23 extern kadm5_ret_t kadm5_get_cpw_host_srv_name(krb5_context, const char *, 24 char **); 25 extern kadm5_ret_t kadm5_init_with_password(char *, char *, char *, 26 kadm5_config_params *, krb5_ui_4, krb5_ui_4, char **, 27 void **); 28 extern kadm5_ret_t kadm5_chpass_principal_util(void *, krb5_principal, 29 char *, char **, char *, unsigned int); 30 31 /* 32 * Solaris Kerberos: 33 * See the function's definition for the description of this interface. 34 */ 35 krb5_error_code __krb5_get_init_creds_password(krb5_context, 36 krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *, 37 krb5_deltat, char *, krb5_get_init_creds_opt *, krb5_kdc_rep **); 38 39 static krb5_error_code 40 krb5_get_as_key_password( 41 krb5_context context, 42 krb5_principal client, 43 krb5_enctype etype, 44 krb5_prompter_fct prompter, 45 void *prompter_data, 46 krb5_data *salt, 47 krb5_data *params, 48 krb5_keyblock *as_key, 49 void *gak_data) 50 { 51 krb5_data *password; 52 krb5_error_code ret; 53 krb5_data defsalt; 54 char *clientstr; 55 char promptstr[1024]; 56 krb5_prompt prompt; 57 krb5_prompt_type prompt_type; 58 59 password = (krb5_data *) gak_data; 60 61 /* If there's already a key of the correct etype, we're done. 62 If the etype is wrong, free the existing key, and make 63 a new one. 64 65 XXX This was the old behavior, and was wrong in hw preauth 66 cases. Is this new behavior -- always asking -- correct in all 67 cases? */ 68 69 if (as_key->length) { 70 if (as_key->enctype != etype) { 71 krb5_free_keyblock_contents (context, as_key); 72 as_key->length = 0; 73 } 74 } 75 76 if (password->data[0] == '\0') { 77 if (prompter == NULL) 78 prompter = krb5_prompter_posix; /* Solaris Kerberos */ 79 80 if ((ret = krb5_unparse_name(context, client, &clientstr))) 81 return(ret); 82 83 strcpy(promptstr, "Password for "); 84 strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1); 85 promptstr[sizeof(promptstr)-1] = '\0'; 86 87 free(clientstr); 88 89 prompt.prompt = promptstr; 90 prompt.hidden = 1; 91 prompt.reply = password; 92 prompt_type = KRB5_PROMPT_TYPE_PASSWORD; 93 94 /* PROMPTER_INVOCATION */ 95 krb5int_set_prompt_types(context, &prompt_type); 96 if ((ret = (((*prompter)(context, prompter_data, NULL, NULL, 97 1, &prompt))))) { 98 krb5int_set_prompt_types(context, 0); 99 return(ret); 100 } 101 krb5int_set_prompt_types(context, 0); 102 } 103 104 if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) { 105 if ((ret = krb5_principal2salt(context, client, &defsalt))) 106 return(ret); 107 108 salt = &defsalt; 109 } else { 110 defsalt.length = 0; 111 } 112 113 ret = krb5_c_string_to_key_with_params(context, etype, password, salt, 114 params->data?params:NULL, as_key); 115 116 if (defsalt.length) 117 krb5_xfree(defsalt.data); 118 119 return(ret); 120 } 121 122 krb5_error_code KRB5_CALLCONV 123 krb5_get_init_creds_password(krb5_context context, 124 krb5_creds *creds, 125 krb5_principal client, 126 char *password, 127 krb5_prompter_fct prompter, 128 void *data, 129 krb5_deltat start_time, 130 char *in_tkt_service, 131 krb5_get_init_creds_opt *options) 132 { 133 /* 134 * Solaris Kerberos: 135 * We call our own private function that returns the as_reply back to 136 * the caller. This structure contains information, such as 137 * key-expiration and last-req fields. Entities such as pam_krb5 can 138 * use this information to provide account/password expiration warnings. 139 * The original "prompter" interface is not granular enough for PAM, 140 * as it will perform all passes w/o coordination with other modules. 141 */ 142 return (__krb5_get_init_creds_password(context, creds, client, password, 143 prompter, data, start_time, in_tkt_service, options, NULL)); 144 } 145 146 /* 147 * Solaris Kerberos: 148 * See krb5_get_init_creds_password()'s comments for the justification of this 149 * private function. Caller must free ptr_as_reply if non-NULL. 150 */ 151 krb5_error_code KRB5_CALLCONV 152 __krb5_get_init_creds_password( 153 krb5_context context, 154 krb5_creds *creds, 155 krb5_principal client, 156 char *password, 157 krb5_prompter_fct prompter, 158 void *data, 159 krb5_deltat start_time, 160 char *in_tkt_service, 161 krb5_get_init_creds_opt *options, 162 krb5_kdc_rep **ptr_as_reply) 163 { 164 krb5_error_code ret, ret2; 165 int use_master; 166 krb5_kdc_rep *as_reply; 167 int tries; 168 krb5_creds chpw_creds; 169 krb5_data pw0, pw1; 170 char banner[1024], pw0array[1024], pw1array[1024]; 171 krb5_prompt prompt[2]; 172 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])]; 173 krb5_gic_opt_ext *opte = NULL; 174 krb5_gic_opt_ext *chpw_opte = NULL; 175 176 char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL; 177 kadm5_config_params params; 178 void *server_handle; 179 const char *err_msg_1 = NULL; 180 181 use_master = 0; 182 as_reply = NULL; 183 memset(&chpw_creds, 0, sizeof(chpw_creds)); 184 185 pw0.data = pw0array; 186 187 if (password && password[0]) { 188 if ((pw0.length = strlen(password)) > sizeof(pw0array)) { 189 ret = EINVAL; 190 goto cleanup; 191 } 192 strcpy(pw0.data, password); 193 } else { 194 pw0.data[0] = '\0'; 195 pw0.length = sizeof(pw0array); 196 } 197 198 pw1.data = pw1array; 199 pw1.data[0] = '\0'; 200 pw1.length = sizeof(pw1array); 201 202 ret = krb5int_gic_opt_to_opte(context, options, &opte, 1, 203 "krb5_get_init_creds_password"); 204 if (ret) 205 goto cleanup; 206 207 /* first try: get the requested tkt from any kdc */ 208 209 ret = krb5_get_init_creds(context, creds, client, prompter, data, 210 start_time, in_tkt_service, opte, 211 krb5_get_as_key_password, (void *) &pw0, 212 &use_master, &as_reply); 213 /* check for success */ 214 215 if (ret == 0) 216 goto cleanup; 217 218 /* If all the kdc's are unavailable, or if the error was due to a 219 user interrupt, or preauth errored out, fail */ 220 221 if ((ret == KRB5_KDC_UNREACH) || 222 (ret == KRB5_PREAUTH_FAILED) || 223 (ret == KRB5_LIBOS_PWDINTR) || 224 (ret == KRB5_REALM_CANT_RESOLVE)) 225 goto cleanup; 226 227 /* if the reply did not come from the master kdc, try again with 228 the master kdc */ 229 230 if (!use_master) { 231 use_master = 1; 232 233 if (as_reply) { 234 krb5_free_kdc_rep( context, as_reply); 235 as_reply = NULL; 236 } 237 238 err_msg_1 = krb5_get_error_message(context, ret); 239 240 ret2 = krb5_get_init_creds(context, creds, client, prompter, data, 241 start_time, in_tkt_service, opte, 242 krb5_get_as_key_password, (void *) &pw0, 243 &use_master, &as_reply); 244 245 if (ret2 == 0) { 246 ret = 0; 247 goto cleanup; 248 } 249 250 /* if the master is unreachable, return the error from the 251 slave we were able to contact or reset the use_master flag */ 252 253 if ((ret2 != KRB5_KDC_UNREACH) && 254 (ret2 != KRB5_REALM_CANT_RESOLVE) && 255 (ret2 != KRB5_REALM_UNKNOWN)) { 256 ret = ret2; 257 } else { 258 use_master = 0; 259 /* Solaris - if 2nd try failed, reset 1st err msg */ 260 if (ret2 && err_msg_1) { 261 krb5_set_error_message(context, ret, err_msg_1); 262 } 263 } 264 } 265 266 /* Solaris Kerberos: 163 resync */ 267 /* #ifdef USE_LOGIN_LIBRARY */ 268 if (ret == KRB5KDC_ERR_KEY_EXP) 269 goto cleanup; /* Login library will deal appropriately with this error */ 270 /* #endif */ 271 272 /* at this point, we have an error from the master. if the error 273 is not password expired, or if it is but there's no prompter, 274 return this error */ 275 276 if ((ret != KRB5KDC_ERR_KEY_EXP) || 277 (prompter == NULL)) 278 goto cleanup; 279 280 /* historically the default has been to prompt for password change. 281 * if the change password prompt option has not been set, we continue 282 * to prompt. Prompting is only disabled if the option has been set 283 * and the value has been set to false. 284 */ 285 if (!(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT)) 286 goto cleanup; 287 288 /* ok, we have an expired password. Give the user a few chances 289 to change it */ 290 291 292 /* 293 * Solaris Kerberos: 294 * Get the correct change password service principal name to use. 295 * This is necessary because SEAM based admin servers require 296 * a slightly different service principal name than MIT/MS servers. 297 */ 298 299 memset((char *) ¶ms, 0, sizeof (params)); 300 301 snprintf(admin_realm, sizeof (admin_realm), 302 krb5_princ_realm(context, client)->data); 303 params.mask |= KADM5_CONFIG_REALM; 304 params.realm = admin_realm; 305 306 ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service); 307 308 if (ret != KADM5_OK) { 309 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 310 "Kerberos mechanism library: Unable to get change password " 311 "service name for realm %s\n"), admin_realm); 312 goto cleanup; 313 } else { 314 ret=0; 315 } 316 317 /* extract the string version of the principal */ 318 if ((ret = krb5_unparse_name(context, client, &princ_str))) 319 goto cleanup; 320 321 ret = kadm5_init_with_password(princ_str, pw0array, cpw_service, 322 ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL, 323 &server_handle); 324 325 if (ret != 0) { 326 goto cleanup; 327 } 328 329 prompt[0].prompt = "Enter new password"; 330 prompt[0].hidden = 1; 331 prompt[0].reply = &pw0; 332 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD; 333 334 prompt[1].prompt = "Enter it again"; 335 prompt[1].hidden = 1; 336 prompt[1].reply = &pw1; 337 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN; 338 339 strcpy(banner, "Password expired. You must change it now."); 340 341 for (tries = 3; tries; tries--) { 342 pw0.length = sizeof(pw0array); 343 pw1.length = sizeof(pw1array); 344 345 /* PROMPTER_INVOCATION */ 346 krb5int_set_prompt_types(context, prompt_types); 347 if ((ret = ((*prompter)(context, data, 0, banner, 348 sizeof(prompt)/sizeof(prompt[0]), prompt)))) 349 goto cleanup; 350 krb5int_set_prompt_types(context, 0); 351 352 353 if (strcmp(pw0.data, pw1.data) != 0) { 354 ret = KRB5_LIBOS_BADPWDMATCH; 355 sprintf(banner, "%s. Please try again.", error_message(ret)); 356 } else if (pw0.length == 0) { 357 ret = KRB5_CHPW_PWDNULL; 358 sprintf(banner, "%s. Please try again.", error_message(ret)); 359 } else { 360 int result_code; 361 krb5_data code_string; 362 krb5_data result_string; 363 364 if ((ret = krb5_change_password(context, &chpw_creds, pw0array, 365 &result_code, &code_string, 366 &result_string))) 367 goto cleanup; 368 369 /* the change succeeded. go on */ 370 371 if (result_code == 0) { 372 krb5_xfree(result_string.data); 373 break; 374 } 375 376 /* set this in case the retry loop falls through */ 377 378 ret = KRB5_CHPW_FAIL; 379 380 if (result_code != KRB5_KPASSWD_SOFTERROR) { 381 krb5_xfree(result_string.data); 382 goto cleanup; 383 } 384 385 /* the error was soft, so try again */ 386 387 /* 100 is I happen to know that no code_string will be longer 388 than 100 chars */ 389 390 if (result_string.length > (sizeof(banner)-100)) 391 result_string.length = sizeof(banner)-100; 392 393 sprintf(banner, "%.*s%s%.*s. Please try again.\n", 394 (int) code_string.length, code_string.data, 395 result_string.length ? ": " : "", 396 (int) result_string.length, 397 result_string.data ? result_string.data : ""); 398 399 krb5_xfree(code_string.data); 400 krb5_xfree(result_string.data); 401 } 402 } 403 404 if (ret) 405 goto cleanup; 406 407 /* the password change was successful. Get an initial ticket 408 from the master. this is the last try. the return from this 409 is final. */ 410 411 ret = krb5_get_init_creds(context, creds, client, prompter, data, 412 start_time, in_tkt_service, opte, 413 krb5_get_as_key_password, (void *) &pw0, 414 &use_master, &as_reply); 415 416 cleanup: 417 if (err_msg_1) 418 free((void *)err_msg_1); 419 420 krb5int_set_prompt_types(context, 0); 421 /* if getting the password was successful, then check to see if the 422 password is about to expire, and warn if so */ 423 424 if (ret == 0) { 425 krb5_timestamp now; 426 krb5_last_req_entry **last_req; 427 int hours; 428 429 /* XXX 7 days should be configurable. This is all pretty ad hoc, 430 and could probably be improved if I was willing to screw around 431 with timezones, etc. */ 432 433 if (prompter && 434 (in_tkt_service && cpw_service && 435 (strcmp(in_tkt_service, cpw_service) != 0)) && 436 ((ret = krb5_timeofday(context, &now)) == 0) && 437 as_reply->enc_part2->key_exp && 438 ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) && 439 (hours >= 0)) { 440 if (hours < 1) 441 sprintf(banner, 442 "Warning: Your password will expire in less than one hour."); 443 else if (hours <= 48) 444 sprintf(banner, "Warning: Your password will expire in %d hour%s.", 445 hours, (hours == 1)?"":"s"); 446 else 447 sprintf(banner, "Warning: Your password will expire in %d days.", 448 hours/24); 449 450 /* ignore an error here */ 451 /* PROMPTER_INVOCATION */ 452 (*prompter)(context, data, 0, banner, 0, 0); 453 } else if (prompter && 454 (!in_tkt_service || 455 (strcmp(in_tkt_service, "kadmin/changepw") != 0)) && 456 as_reply->enc_part2 && as_reply->enc_part2->last_req) { 457 /* 458 * Check the last_req fields 459 */ 460 461 for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++) 462 if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME || 463 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) { 464 krb5_deltat delta; 465 char ts[256]; 466 467 if ((ret = krb5_timeofday(context, &now))) 468 break; 469 470 if ((ret = krb5_timestamp_to_string((*last_req)->value, 471 ts, sizeof(ts)))) 472 break; 473 474 delta = (*last_req)->value - now; 475 476 if (delta < 3600) 477 sprintf(banner, 478 "Warning: Your password will expire in less than one " 479 "hour on %s", ts); 480 else if (delta < 86400*2) 481 sprintf(banner, 482 "Warning: Your password will expire in %d hour%s on %s", 483 delta / 3600, delta < 7200 ? "" : "s", ts); 484 else 485 sprintf(banner, 486 "Warning: Your password will expire in %d days on %s", 487 delta / 86400, ts); 488 /* ignore an error here */ 489 /* PROMPTER_INVOCATION */ 490 (*prompter)(context, data, 0, banner, 0, 0); 491 } 492 } 493 } 494 495 free(cpw_service); 496 free(princ_str); 497 if (opte && krb5_gic_opt_is_shadowed(opte)) 498 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 499 memset(pw0array, 0, sizeof(pw0array)); 500 memset(pw1array, 0, sizeof(pw1array)); 501 krb5_free_cred_contents(context, &chpw_creds); 502 /* 503 * Solaris Kerberos: 504 * Argument, ptr_as_reply, being returned to caller if success and non-NULL. 505 */ 506 if (as_reply != NULL) { 507 if (ptr_as_reply == NULL) 508 krb5_free_kdc_rep(context, as_reply); 509 else 510 *ptr_as_reply = as_reply; 511 } 512 513 return(ret); 514 } 515 krb5_error_code krb5int_populate_gic_opt ( 516 krb5_context context, krb5_gic_opt_ext **opte, 517 krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes, 518 krb5_preauthtype *pre_auth_types, krb5_creds *creds) 519 { 520 int i; 521 krb5_int32 starttime; 522 krb5_get_init_creds_opt *opt; 523 524 525 krb5_get_init_creds_opt_init(opt); 526 if (addrs) 527 krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs); 528 if (ktypes) { 529 for (i=0; ktypes[i]; i++); 530 if (i) 531 krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i); 532 } 533 if (pre_auth_types) { 534 for (i=0; pre_auth_types[i]; i++); 535 if (i) 536 krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i); 537 } 538 if (options&KDC_OPT_FORWARDABLE) 539 krb5_get_init_creds_opt_set_forwardable(opt, 1); 540 else krb5_get_init_creds_opt_set_forwardable(opt, 0); 541 if (options&KDC_OPT_PROXIABLE) 542 krb5_get_init_creds_opt_set_proxiable(opt, 1); 543 else krb5_get_init_creds_opt_set_proxiable(opt, 0); 544 if (creds && creds->times.endtime) { 545 krb5_timeofday(context, &starttime); 546 if (creds->times.starttime) starttime = creds->times.starttime; 547 krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime); 548 } 549 return krb5int_gic_opt_to_opte(context, opt, opte, 0, 550 "krb5int_populate_gic_opt"); 551 } 552 553 /* 554 Rewrites get_in_tkt in terms of newer get_init_creds API. 555 Attempts to get an initial ticket for creds->client to use server 556 creds->server, (realm is taken from creds->client), with options 557 options, and using creds->times.starttime, creds->times.endtime, 558 creds->times.renew_till as from, till, and rtime. 559 creds->times.renew_till is ignored unless the RENEWABLE option is requested. 560 561 If addrs is non-NULL, it is used for the addresses requested. If it is 562 null, the system standard addresses are used. 563 564 If password is non-NULL, it is converted using the cryptosystem entry 565 point for a string conversion routine, seeded with the client's name. 566 If password is passed as NULL, the password is read from the terminal, 567 and then converted into a key. 568 569 A succesful call will place the ticket in the credentials cache ccache. 570 571 returns system errors, encryption errors 572 */ 573 krb5_error_code KRB5_CALLCONV 574 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options, 575 krb5_address *const *addrs, krb5_enctype *ktypes, 576 krb5_preauthtype *pre_auth_types, 577 const char *password, krb5_ccache ccache, 578 krb5_creds *creds, krb5_kdc_rep **ret_as_reply) 579 { 580 krb5_error_code retval; 581 krb5_data pw0; 582 char pw0array[1024]; 583 char * server; 584 krb5_principal server_princ, client_princ; 585 int use_master = 0; 586 krb5_gic_opt_ext *opte = NULL; 587 588 pw0array[0] = '\0'; 589 pw0.data = pw0array; 590 if (password) { 591 pw0.length = strlen(password); 592 if (pw0.length > sizeof(pw0array)) 593 return EINVAL; 594 strncpy(pw0.data, password, sizeof(pw0array)); 595 if (pw0.length == 0) 596 pw0.length = sizeof(pw0array); 597 } else { 598 pw0.length = sizeof(pw0array); 599 } 600 retval = krb5int_populate_gic_opt(context, &opte, 601 options, addrs, ktypes, 602 pre_auth_types, creds); 603 if (retval) 604 return (retval); 605 retval = krb5_unparse_name( context, creds->server, &server); 606 if (retval) { 607 return (retval); 608 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 609 } 610 server_princ = creds->server; 611 client_princ = creds->client; 612 retval = krb5_get_init_creds (context, 613 creds, creds->client, 614 krb5_prompter_posix, NULL, 615 0, server, opte, 616 krb5_get_as_key_password, &pw0, 617 &use_master, ret_as_reply); 618 krb5_free_unparsed_name( context, server); 619 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 620 if (retval) { 621 return (retval); 622 } 623 if (creds->server) 624 krb5_free_principal( context, creds->server); 625 if (creds->client) 626 krb5_free_principal( context, creds->client); 627 creds->client = client_princ; 628 creds->server = server_princ; 629 /* store it in the ccache! */ 630 if (ccache) 631 if ((retval = krb5_cc_store_cred(context, ccache, creds))) 632 return (retval); 633 return retval; 634 } 635 636