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