1 /* 2 * Copyright 2005 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 *, int); 37 38 static krb5_error_code 39 krb5_get_as_key_password( 40 krb5_context context, 41 krb5_principal client, 42 krb5_enctype etype, 43 krb5_prompter_fct prompter, 44 void *prompter_data, 45 krb5_data *salt, 46 krb5_data *params, 47 krb5_keyblock *as_key, 48 void *gak_data) 49 { 50 krb5_data *password; 51 krb5_error_code ret; 52 krb5_data defsalt; 53 char *clientstr; 54 char promptstr[1024]; 55 krb5_prompt prompt; 56 krb5_prompt_type prompt_type; 57 58 password = (krb5_data *) gak_data; 59 60 /* If there's already a key of the correct etype, we're done. 61 If the etype is wrong, free the existing key, and make 62 a new one. 63 64 XXX This was the old behavior, and was wrong in hw preauth 65 cases. Is this new behavior -- always asking -- correct in all 66 cases? */ 67 68 if (as_key->length) { 69 if (as_key->enctype != etype) { 70 krb5_free_keyblock_contents (context, as_key); 71 as_key->length = 0; 72 } 73 } 74 75 if (password->data[0] == '\0') { 76 if (prompter == NULL) 77 prompter = krb5_prompter_posix; 78 79 if ((ret = krb5_unparse_name(context, client, &clientstr))) 80 return(ret); 81 82 strcpy(promptstr, "Password for "); 83 strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1); 84 promptstr[sizeof(promptstr)-1] = '\0'; 85 86 free(clientstr); 87 88 prompt.prompt = promptstr; 89 prompt.hidden = 1; 90 prompt.reply = password; 91 prompt_type = KRB5_PROMPT_TYPE_PASSWORD; 92 93 /* PROMPTER_INVOCATION */ 94 krb5int_set_prompt_types(context, &prompt_type); 95 if ((ret = (((*prompter)(context, prompter_data, NULL, NULL, 96 1, &prompt))))) { 97 krb5int_set_prompt_types(context, 0); 98 return(ret); 99 } 100 krb5int_set_prompt_types(context, 0); 101 } 102 103 if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && 104 (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( 124 krb5_context context, 125 krb5_creds *creds, 126 krb5_principal client, 127 char *password, 128 krb5_prompter_fct prompter, 129 void *data, 130 krb5_deltat start_time, 131 char *in_tkt_service, 132 krb5_get_init_creds_opt *options) 133 { 134 krb5_error_code ret, ret2; 135 int use_master; 136 krb5_kdc_rep *as_reply; 137 int tries; 138 krb5_creds chpw_creds; 139 krb5_get_init_creds_opt chpw_opts; 140 krb5_data pw0, pw1; 141 char banner[1024], pw0array[1024], pw1array[1024]; 142 krb5_prompt prompt[2]; 143 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])]; 144 145 char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL; 146 kadm5_config_params params; 147 void *server_handle; 148 149 use_master = 0; 150 as_reply = NULL; 151 memset(&chpw_creds, 0, sizeof(chpw_creds)); 152 153 pw0.data = pw0array; 154 155 if (password) { 156 if ((pw0.length = strlen(password)) > sizeof(pw0array)) { 157 ret = EINVAL; 158 goto cleanup; 159 } 160 strcpy(pw0.data, password); 161 } else { 162 pw0.data[0] = '\0'; 163 pw0.length = sizeof(pw0array); 164 } 165 166 pw1.data = pw1array; 167 pw1.data[0] = '\0'; 168 pw1.length = sizeof(pw1array); 169 170 /* first try: get the requested tkt from any kdc */ 171 172 ret = krb5_get_init_creds(context, creds, client, prompter, data, 173 start_time, in_tkt_service, options, 174 krb5_get_as_key_password, (void *) &pw0, 175 &use_master, &as_reply); 176 177 /* check for success */ 178 179 if (ret == 0) 180 goto cleanup; 181 182 /* If all the kdc's are unavailable, or if the error was due to a 183 user interrupt, or preauth errored out, fail */ 184 185 if ((ret == KRB5_KDC_UNREACH) || 186 (ret == KRB5_PREAUTH_FAILED) || 187 (ret == KRB5_LIBOS_PWDINTR) || 188 (ret == KRB5_REALM_CANT_RESOLVE)) 189 goto cleanup; 190 191 /* if the reply did not come from the master kdc, try again with 192 the master kdc */ 193 194 if (!use_master) { 195 use_master = 1; 196 197 if (as_reply) { 198 krb5_free_kdc_rep( context, as_reply); 199 as_reply = NULL; 200 } 201 202 ret2 = krb5_get_init_creds(context, creds, client, prompter, data, 203 start_time, in_tkt_service, options, 204 krb5_get_as_key_password, (void *) &pw0, 205 &use_master, &as_reply); 206 207 if (ret2 == 0) { 208 ret = 0; 209 goto cleanup; 210 } 211 212 /* if the master is unreachable, return the error from the 213 slave we were able to contact */ 214 215 if ((ret2 == KRB5_KDC_UNREACH) || 216 (ret2 == KRB5_REALM_CANT_RESOLVE) || 217 (ret2 == KRB5_REALM_UNKNOWN)) 218 goto cleanup; 219 220 ret = ret2; 221 } 222 223 #ifdef USE_LOGIN_LIBRARY 224 if (ret == KRB5KDC_ERR_KEY_EXP) 225 goto cleanup; /* Login library will deal appropriately with this error */ 226 #endif 227 228 /* at this point, we have an error from the master. if the error 229 is not password expired, or if it is but there's no prompter, 230 return this error */ 231 232 if ((ret != KRB5KDC_ERR_KEY_EXP) || 233 (prompter == NULL)) 234 goto cleanup; 235 236 /* ok, we have an expired password. Give the user a few chances 237 to change it */ 238 239 240 /* Solaris Kerberos: 241 * 242 * Get the correct change password service principal name to use. 243 * This is necessary because SEAM based admin servers require 244 * a slightly different service principal name than MIT/MS servers. 245 */ 246 247 memset((char *) ¶ms, 0, sizeof (params)); 248 249 snprintf(admin_realm, sizeof (admin_realm), 250 krb5_princ_realm(context, client)->data); 251 params.mask |= KADM5_CONFIG_REALM; 252 params.realm = admin_realm; 253 254 ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service); 255 256 if (ret != KADM5_OK) { 257 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 258 "Kerberos mechanism library: Unable to get change password " 259 "service name for realm %s\n"), admin_realm); 260 goto cleanup; 261 } else { 262 ret=0; 263 } 264 265 /* extract the string version of the principal */ 266 if ((ret = krb5_unparse_name(context, client, &princ_str))) 267 goto cleanup; 268 269 ret = kadm5_init_with_password(princ_str, pw0array, cpw_service, 270 ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, &server_handle); 271 272 if (ret != 0) { 273 goto cleanup; 274 } 275 276 prompt[0].prompt = "Enter new password"; 277 prompt[0].hidden = 1; 278 prompt[0].reply = &pw0; 279 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD; 280 281 prompt[1].prompt = "Enter it again"; 282 prompt[1].hidden = 1; 283 prompt[1].reply = &pw1; 284 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN; 285 286 strcpy(banner, "Password expired. You must change it now."); 287 288 for (tries = 3; tries; tries--) { 289 pw0.length = sizeof(pw0array); 290 pw1.length = sizeof(pw1array); 291 292 /* PROMPTER_INVOCATION */ 293 krb5int_set_prompt_types(context, prompt_types); 294 if ((ret = ((*prompter)(context, data, 0, banner, 295 sizeof(prompt)/sizeof(prompt[0]), prompt)))) 296 goto cleanup; 297 krb5int_set_prompt_types(context, 0); 298 299 300 if (strcmp(pw0.data, pw1.data) != 0) { 301 ret = KRB5_LIBOS_BADPWDMATCH; 302 sprintf(banner, "%s. Please try again.", error_message(ret)); 303 } else if (pw0.length == 0) { 304 ret = KRB5_CHPW_PWDNULL; 305 sprintf(banner, "%s. Please try again.", error_message(ret)); 306 } else { 307 int result_code; 308 309 result_code = kadm5_chpass_principal_util(server_handle, client, 310 pw0.data, 311 NULL /* don't need pw back */, 312 banner, 313 sizeof(banner)); 314 315 /* the change succeeded. go on */ 316 317 if (result_code == 0) { 318 break; 319 } 320 321 /* set this in case the retry loop falls through */ 322 323 ret = KRB5_CHPW_FAIL; 324 325 if (result_code != KRB5_KPASSWD_SOFTERROR) { 326 goto cleanup; 327 } 328 } 329 } 330 331 if (ret) 332 goto cleanup; 333 334 /* the password change was successful. Get an initial ticket 335 from the master. this is the last try. the return from this 336 is final. */ 337 338 ret = krb5_get_init_creds(context, creds, client, prompter, data, 339 start_time, in_tkt_service, options, 340 krb5_get_as_key_password, (void *) &pw0, 341 &use_master, &as_reply); 342 343 cleanup: 344 krb5int_set_prompt_types(context, 0); 345 /* if getting the password was successful, then check to see if the 346 password is about to expire, and warn if so */ 347 348 if (ret == 0) { 349 krb5_timestamp now; 350 krb5_last_req_entry **last_req; 351 int hours; 352 353 /* XXX 7 days should be configurable. This is all pretty ad hoc, 354 and could probably be improved if I was willing to screw around 355 with timezones, etc. */ 356 357 if (prompter && 358 (in_tkt_service && cpw_service && 359 (strcmp(in_tkt_service, cpw_service) != 0)) && 360 ((ret = krb5_timeofday(context, &now)) == 0) && 361 as_reply->enc_part2->key_exp && 362 ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) && 363 (hours >= 0)) { 364 if (hours < 1) 365 sprintf(banner, 366 "Warning: Your password will expire in less than one hour."); 367 else if (hours <= 48) 368 sprintf(banner, "Warning: Your password will expire in %d hour%s.", 369 hours, (hours == 1)?"":"s"); 370 else 371 sprintf(banner, "Warning: Your password will expire in %d days.", 372 hours/24); 373 374 /* ignore an error here */ 375 /* PROMPTER_INVOCATION */ 376 (*prompter)(context, data, 0, banner, 0, 0); 377 } else if (prompter && 378 (!in_tkt_service || 379 (strcmp(in_tkt_service, "kadmin/changepw") != 0)) && 380 as_reply->enc_part2 && as_reply->enc_part2->last_req) { 381 /* 382 * Check the last_req fields 383 */ 384 385 for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++) 386 if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME || 387 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) { 388 krb5_deltat delta; 389 char ts[256]; 390 391 if ((ret = krb5_timeofday(context, &now))) 392 break; 393 394 if ((ret = krb5_timestamp_to_string((*last_req)->value, 395 ts, sizeof(ts)))) 396 break; 397 delta = (*last_req)->value - now; 398 399 if (delta < 3600) 400 sprintf(banner, 401 "Warning: Your password will expire in less than one " 402 "hour on %s", ts); 403 else if (delta < 86400*2) 404 sprintf(banner, 405 "Warning: Your password will expire in %d hour%s on %s", 406 delta / 3600, delta < 7200 ? "" : "s", ts); 407 else 408 sprintf(banner, 409 "Warning: Your password will expire in %d days on %s", 410 delta / 86400, ts); 411 /* ignore an error here */ 412 /* PROMPTER_INVOCATION */ 413 (*prompter)(context, data, 0, banner, 0, 0); 414 } 415 } /* prompter && !in_tkt_service */ 416 } 417 418 free(cpw_service); 419 free(princ_str); 420 memset(pw0array, 0, sizeof(pw0array)); 421 memset(pw1array, 0, sizeof(pw1array)); 422 krb5_free_cred_contents(context, &chpw_creds); 423 if (as_reply) 424 krb5_free_kdc_rep(context, as_reply); 425 426 return(ret); 427 } 428 429 void krb5int_populate_gic_opt ( 430 krb5_context context, krb5_get_init_creds_opt *opt, 431 krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes, 432 krb5_preauthtype *pre_auth_types, krb5_creds *creds) 433 { 434 int i; 435 krb5_int32 starttime; 436 437 krb5_get_init_creds_opt_init(opt); 438 if (addrs) 439 krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs); 440 if (ktypes) { 441 for (i=0; ktypes[i]; i++); 442 if (i) 443 krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i); 444 } 445 if (pre_auth_types) { 446 for (i=0; pre_auth_types[i]; i++); 447 if (i) 448 krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i); 449 } 450 if (options&KDC_OPT_FORWARDABLE) 451 krb5_get_init_creds_opt_set_forwardable(opt, 1); 452 else krb5_get_init_creds_opt_set_forwardable(opt, 0); 453 if (options&KDC_OPT_PROXIABLE) 454 krb5_get_init_creds_opt_set_proxiable(opt, 1); 455 else krb5_get_init_creds_opt_set_proxiable(opt, 0); 456 if (creds && creds->times.endtime) { 457 krb5_timeofday(context, &starttime); 458 if (creds->times.starttime) starttime = creds->times.starttime; 459 krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime); 460 } 461 } 462 463 /* 464 Rewrites get_in_tkt in terms of newer get_init_creds API. 465 Attempts to get an initial ticket for creds->client to use server 466 creds->server, (realm is taken from creds->client), with options 467 options, and using creds->times.starttime, creds->times.endtime, 468 creds->times.renew_till as from, till, and rtime. 469 creds->times.renew_till is ignored unless the RENEWABLE option is requested. 470 471 If addrs is non-NULL, it is used for the addresses requested. If it is 472 null, the system standard addresses are used. 473 474 If password is non-NULL, it is converted using the cryptosystem entry 475 point for a string conversion routine, seeded with the client's name. 476 If password is passed as NULL, the password is read from the terminal, 477 and then converted into a key. 478 479 A succesful call will place the ticket in the credentials cache ccache. 480 481 returns system errors, encryption errors 482 */ 483 krb5_error_code KRB5_CALLCONV 484 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options, 485 krb5_address *const *addrs, krb5_enctype *ktypes, 486 krb5_preauthtype *pre_auth_types, 487 const char *password, krb5_ccache ccache, 488 krb5_creds *creds, krb5_kdc_rep **ret_as_reply) 489 { 490 krb5_error_code retval; 491 krb5_data pw0; 492 char pw0array[1024]; 493 krb5_get_init_creds_opt opt; 494 char * server; 495 krb5_principal server_princ, client_princ; 496 int use_master = 0; 497 498 pw0array[0] = '\0'; 499 pw0.data = pw0array; 500 if (password) { 501 pw0.length = strlen(password); 502 if (pw0.length > sizeof(pw0array)) 503 return EINVAL; 504 strncpy(pw0.data, password, sizeof(pw0array)); 505 if (pw0.length == 0) 506 pw0.length = sizeof(pw0array); 507 } else { 508 pw0.length = sizeof(pw0array); 509 } 510 krb5int_populate_gic_opt(context, &opt, 511 options, addrs, ktypes, 512 pre_auth_types, creds); 513 retval = krb5_unparse_name( context, creds->server, &server); 514 if (retval) 515 return (retval); 516 server_princ = creds->server; 517 client_princ = creds->client; 518 retval = krb5_get_init_creds (context, 519 creds, creds->client, 520 krb5_prompter_posix, NULL, 521 0, server, &opt, 522 krb5_get_as_key_password, &pw0, 523 &use_master, ret_as_reply); 524 krb5_free_unparsed_name( context, server); 525 if (retval) { 526 return (retval); 527 } 528 if (creds->server) 529 krb5_free_principal( context, creds->server); 530 if (creds->client) 531 krb5_free_principal( context, creds->client); 532 creds->client = client_princ; 533 creds->server = server_princ; 534 /* store it in the ccache! */ 535 if (ccache) 536 if ((retval = krb5_cc_store_cred(context, ccache, creds))) 537 return (retval); 538 return retval; 539 } 540