1 /* 2 * Copyright 2010 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_data pw0, pw1; 173 char banner[1024], pw0array[1024], pw1array[1024]; 174 krb5_prompt prompt[2]; 175 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])]; 176 krb5_gic_opt_ext *opte = NULL; 177 krb5_gic_opt_ext *chpw_opte = NULL; 178 179 char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL; 180 kadm5_config_params params; 181 void *server_handle; 182 183 use_master = 0; 184 as_reply = NULL; 185 memset(&chpw_creds, 0, sizeof(chpw_creds)); 186 187 pw0.data = pw0array; 188 189 if (password && password[0]) { 190 if ((pw0.length = strlen(password)) > sizeof(pw0array)) { 191 ret = EINVAL; 192 goto cleanup; 193 } 194 strcpy(pw0.data, password); 195 } else { 196 pw0.data[0] = '\0'; 197 pw0.length = sizeof(pw0array); 198 } 199 200 pw1.data = pw1array; 201 pw1.data[0] = '\0'; 202 pw1.length = sizeof(pw1array); 203 204 ret = krb5int_gic_opt_to_opte(context, options, &opte, 1, 205 "krb5_get_init_creds_password"); 206 if (ret) 207 goto cleanup; 208 209 /* first try: get the requested tkt from any kdc */ 210 211 ret = krb5_get_init_creds(context, creds, client, prompter, data, 212 start_time, in_tkt_service, opte, 213 krb5_get_as_key_password, (void *) &pw0, 214 &use_master, &as_reply); 215 216 /* check for success */ 217 218 if (ret == 0) 219 goto cleanup; 220 221 /* If all the kdc's are unavailable, or if the error was due to a 222 user interrupt, or preauth errored out, fail */ 223 224 if ((ret == KRB5_KDC_UNREACH) || 225 (ret == KRB5_PREAUTH_FAILED) || 226 (ret == KRB5_LIBOS_PWDINTR) || 227 (ret == KRB5_REALM_CANT_RESOLVE)) 228 goto cleanup; 229 230 /* if the reply did not come from the master kdc, try again with 231 the master kdc */ 232 233 if (!use_master) { 234 use_master = 1; 235 236 if (as_reply) { 237 krb5_free_kdc_rep( context, as_reply); 238 as_reply = NULL; 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 } 260 261 /* Solaris Kerberos: 163 resync */ 262 /* #ifdef USE_LOGIN_LIBRARY */ 263 if (ret == KRB5KDC_ERR_KEY_EXP) 264 goto cleanup; /* Login library will deal appropriately with this error */ 265 /* #endif */ 266 267 /* at this point, we have an error from the master. if the error 268 is not password expired, or if it is but there's no prompter, 269 return this error */ 270 271 if ((ret != KRB5KDC_ERR_KEY_EXP) || 272 (prompter == NULL)) 273 goto cleanup; 274 275 /* historically the default has been to prompt for password change. 276 * if the change password prompt option has not been set, we continue 277 * to prompt. Prompting is only disabled if the option has been set 278 * and the value has been set to false. 279 */ 280 if (!(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT)) 281 goto cleanup; 282 283 /* ok, we have an expired password. Give the user a few chances 284 to change it */ 285 286 287 /* 288 * Solaris Kerberos: 289 * Get the correct change password service principal name to use. 290 * This is necessary because SEAM based admin servers require 291 * a slightly different service principal name than MIT/MS servers. 292 */ 293 294 memset((char *) ¶ms, 0, sizeof (params)); 295 296 snprintf(admin_realm, sizeof (admin_realm), 297 krb5_princ_realm(context, client)->data); 298 params.mask |= KADM5_CONFIG_REALM; 299 params.realm = admin_realm; 300 301 ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service); 302 303 if (ret != KADM5_OK) { 304 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 305 "Kerberos mechanism library: Unable to get change password " 306 "service name for realm %s\n"), admin_realm); 307 goto cleanup; 308 } else { 309 ret=0; 310 } 311 312 /* extract the string version of the principal */ 313 if ((ret = krb5_unparse_name(context, client, &princ_str))) 314 goto cleanup; 315 316 ret = kadm5_init_with_password(princ_str, pw0array, cpw_service, 317 ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL, 318 &server_handle); 319 320 if (ret != 0) { 321 goto cleanup; 322 } 323 324 prompt[0].prompt = "Enter new password"; 325 prompt[0].hidden = 1; 326 prompt[0].reply = &pw0; 327 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD; 328 329 prompt[1].prompt = "Enter it again"; 330 prompt[1].hidden = 1; 331 prompt[1].reply = &pw1; 332 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN; 333 334 strcpy(banner, "Password expired. You must change it now."); 335 336 for (tries = 3; tries; tries--) { 337 pw0.length = sizeof(pw0array); 338 pw1.length = sizeof(pw1array); 339 340 /* PROMPTER_INVOCATION */ 341 krb5int_set_prompt_types(context, prompt_types); 342 if ((ret = ((*prompter)(context, data, 0, banner, 343 sizeof(prompt)/sizeof(prompt[0]), prompt)))) 344 goto cleanup; 345 krb5int_set_prompt_types(context, 0); 346 347 348 if (strcmp(pw0.data, pw1.data) != 0) { 349 ret = KRB5_LIBOS_BADPWDMATCH; 350 sprintf(banner, "%s. Please try again.", error_message(ret)); 351 } else if (pw0.length == 0) { 352 ret = KRB5_CHPW_PWDNULL; 353 sprintf(banner, "%s. Please try again.", error_message(ret)); 354 } else { 355 int result_code; 356 krb5_data code_string; 357 krb5_data result_string; 358 359 if ((ret = krb5_change_password(context, &chpw_creds, pw0array, 360 &result_code, &code_string, 361 &result_string))) 362 goto cleanup; 363 364 /* the change succeeded. go on */ 365 366 if (result_code == 0) { 367 krb5_xfree(result_string.data); 368 break; 369 } 370 371 /* set this in case the retry loop falls through */ 372 373 ret = KRB5_CHPW_FAIL; 374 375 if (result_code != KRB5_KPASSWD_SOFTERROR) { 376 krb5_xfree(result_string.data); 377 goto cleanup; 378 } 379 380 /* the error was soft, so try again */ 381 382 /* 100 is I happen to know that no code_string will be longer 383 than 100 chars */ 384 385 if (result_string.length > (sizeof(banner)-100)) 386 result_string.length = sizeof(banner)-100; 387 388 sprintf(banner, "%.*s%s%.*s. Please try again.\n", 389 (int) code_string.length, code_string.data, 390 result_string.length ? ": " : "", 391 (int) result_string.length, 392 result_string.data ? result_string.data : ""); 393 394 krb5_xfree(code_string.data); 395 krb5_xfree(result_string.data); 396 } 397 } 398 399 if (ret) 400 goto cleanup; 401 402 /* the password change was successful. Get an initial ticket 403 from the master. this is the last try. the return from this 404 is final. */ 405 406 ret = krb5_get_init_creds(context, creds, client, prompter, data, 407 start_time, in_tkt_service, opte, 408 krb5_get_as_key_password, (void *) &pw0, 409 &use_master, &as_reply); 410 411 cleanup: 412 krb5int_set_prompt_types(context, 0); 413 /* if getting the password was successful, then check to see if the 414 password is about to expire, and warn if so */ 415 416 if (ret == 0) { 417 krb5_timestamp now; 418 krb5_last_req_entry **last_req; 419 int hours; 420 421 /* XXX 7 days should be configurable. This is all pretty ad hoc, 422 and could probably be improved if I was willing to screw around 423 with timezones, etc. */ 424 425 if (prompter && 426 (in_tkt_service && cpw_service && 427 (strcmp(in_tkt_service, cpw_service) != 0)) && 428 ((ret = krb5_timeofday(context, &now)) == 0) && 429 as_reply->enc_part2->key_exp && 430 ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) && 431 (hours >= 0)) { 432 if (hours < 1) 433 sprintf(banner, 434 "Warning: Your password will expire in less than one hour."); 435 else if (hours <= 48) 436 sprintf(banner, "Warning: Your password will expire in %d hour%s.", 437 hours, (hours == 1)?"":"s"); 438 else 439 sprintf(banner, "Warning: Your password will expire in %d days.", 440 hours/24); 441 442 /* ignore an error here */ 443 /* PROMPTER_INVOCATION */ 444 (*prompter)(context, data, 0, banner, 0, 0); 445 } else if (prompter && 446 (!in_tkt_service || 447 (strcmp(in_tkt_service, "kadmin/changepw") != 0)) && 448 as_reply->enc_part2 && as_reply->enc_part2->last_req) { 449 /* 450 * Check the last_req fields 451 */ 452 453 for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++) 454 if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME || 455 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) { 456 krb5_deltat delta; 457 char ts[256]; 458 459 if ((ret = krb5_timeofday(context, &now))) 460 break; 461 462 if ((ret = krb5_timestamp_to_string((*last_req)->value, 463 ts, sizeof(ts)))) 464 break; 465 466 delta = (*last_req)->value - now; 467 468 if (delta < 3600) 469 sprintf(banner, 470 "Warning: Your password will expire in less than one " 471 "hour on %s", ts); 472 else if (delta < 86400*2) 473 sprintf(banner, 474 "Warning: Your password will expire in %d hour%s on %s", 475 delta / 3600, delta < 7200 ? "" : "s", ts); 476 else 477 sprintf(banner, 478 "Warning: Your password will expire in %d days on %s", 479 delta / 86400, ts); 480 /* ignore an error here */ 481 /* PROMPTER_INVOCATION */ 482 (*prompter)(context, data, 0, banner, 0, 0); 483 } 484 } 485 } 486 487 free(cpw_service); 488 free(princ_str); 489 if (opte && krb5_gic_opt_is_shadowed(opte)) 490 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 491 memset(pw0array, 0, sizeof(pw0array)); 492 memset(pw1array, 0, sizeof(pw1array)); 493 krb5_free_cred_contents(context, &chpw_creds); 494 /* 495 * Solaris Kerberos: 496 * Argument, ptr_as_reply, being returned to caller if success and non-NULL. 497 */ 498 if (as_reply != NULL) { 499 if (ptr_as_reply == NULL) 500 krb5_free_kdc_rep(context, as_reply); 501 else 502 *ptr_as_reply = as_reply; 503 } 504 505 return(ret); 506 } 507 krb5_error_code krb5int_populate_gic_opt ( 508 krb5_context context, krb5_gic_opt_ext **opte, 509 krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes, 510 krb5_preauthtype *pre_auth_types, krb5_creds *creds) 511 { 512 int i; 513 krb5_int32 starttime; 514 krb5_get_init_creds_opt *opt; 515 516 517 krb5_get_init_creds_opt_init(opt); 518 if (addrs) 519 krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs); 520 if (ktypes) { 521 for (i=0; ktypes[i]; i++); 522 if (i) 523 krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i); 524 } 525 if (pre_auth_types) { 526 for (i=0; pre_auth_types[i]; i++); 527 if (i) 528 krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i); 529 } 530 if (options&KDC_OPT_FORWARDABLE) 531 krb5_get_init_creds_opt_set_forwardable(opt, 1); 532 else krb5_get_init_creds_opt_set_forwardable(opt, 0); 533 if (options&KDC_OPT_PROXIABLE) 534 krb5_get_init_creds_opt_set_proxiable(opt, 1); 535 else krb5_get_init_creds_opt_set_proxiable(opt, 0); 536 if (creds && creds->times.endtime) { 537 krb5_timeofday(context, &starttime); 538 if (creds->times.starttime) starttime = creds->times.starttime; 539 krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime); 540 } 541 return krb5int_gic_opt_to_opte(context, opt, opte, 0, 542 "krb5int_populate_gic_opt"); 543 } 544 545 /* 546 Rewrites get_in_tkt in terms of newer get_init_creds API. 547 Attempts to get an initial ticket for creds->client to use server 548 creds->server, (realm is taken from creds->client), with options 549 options, and using creds->times.starttime, creds->times.endtime, 550 creds->times.renew_till as from, till, and rtime. 551 creds->times.renew_till is ignored unless the RENEWABLE option is requested. 552 553 If addrs is non-NULL, it is used for the addresses requested. If it is 554 null, the system standard addresses are used. 555 556 If password is non-NULL, it is converted using the cryptosystem entry 557 point for a string conversion routine, seeded with the client's name. 558 If password is passed as NULL, the password is read from the terminal, 559 and then converted into a key. 560 561 A succesful call will place the ticket in the credentials cache ccache. 562 563 returns system errors, encryption errors 564 */ 565 krb5_error_code KRB5_CALLCONV 566 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options, 567 krb5_address *const *addrs, krb5_enctype *ktypes, 568 krb5_preauthtype *pre_auth_types, 569 const char *password, krb5_ccache ccache, 570 krb5_creds *creds, krb5_kdc_rep **ret_as_reply) 571 { 572 krb5_error_code retval; 573 krb5_data pw0; 574 char pw0array[1024]; 575 char * server; 576 krb5_principal server_princ, client_princ; 577 int use_master = 0; 578 krb5_gic_opt_ext *opte = NULL; 579 580 pw0array[0] = '\0'; 581 pw0.data = pw0array; 582 if (password) { 583 pw0.length = strlen(password); 584 if (pw0.length > sizeof(pw0array)) 585 return EINVAL; 586 strncpy(pw0.data, password, sizeof(pw0array)); 587 if (pw0.length == 0) 588 pw0.length = sizeof(pw0array); 589 } else { 590 pw0.length = sizeof(pw0array); 591 } 592 retval = krb5int_populate_gic_opt(context, &opte, 593 options, addrs, ktypes, 594 pre_auth_types, creds); 595 if (retval) 596 return (retval); 597 retval = krb5_unparse_name( context, creds->server, &server); 598 if (retval) { 599 return (retval); 600 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 601 } 602 server_princ = creds->server; 603 client_princ = creds->client; 604 retval = krb5_get_init_creds (context, 605 creds, creds->client, 606 krb5_prompter_posix, NULL, 607 0, server, opte, 608 krb5_get_as_key_password, &pw0, 609 &use_master, ret_as_reply); 610 krb5_free_unparsed_name( context, server); 611 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte); 612 if (retval) { 613 return (retval); 614 } 615 if (creds->server) 616 krb5_free_principal( context, creds->server); 617 if (creds->client) 618 krb5_free_principal( context, creds->client); 619 creds->client = client_princ; 620 creds->server = server_princ; 621 /* store it in the ccache! */ 622 if (ccache) 623 if ((retval = krb5_cc_store_cred(context, ccache, creds))) 624 return (retval); 625 return retval; 626 } 627 628