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