1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
4 #include "init_creds_ctx.h"
9 krb5_get_as_key_password(krb5_context context,
10 krb5_principal client,
12 krb5_prompter_fct prompter,
16 krb5_keyblock *as_key,
18 k5_response_items *ritems)
20 struct gak_password *gp = gak_data;
24 char promptstr[1024], pwbuf[1024];
27 krb5_prompt_type prompt_type;
30 /* If we need to get the AS key via the responder, ask for it. */
32 if (gp->password != NULL)
35 return k5_response_items_ask_question(ritems,
36 KRB5_RESPONDER_QUESTION_PASSWORD,
40 /* If there's already a key of the correct etype, we're done.
41 If the etype is wrong, free the existing key, and make
44 XXX This was the old behavior, and was wrong in hw preauth
45 cases. Is this new behavior -- always asking -- correct in all
49 if (as_key->enctype != etype) {
50 krb5_free_keyblock_contents (context, as_key);
55 if (gp->password == NULL) {
56 /* Check the responder for the password. */
57 rpass = k5_response_items_get_answer(ritems,
58 KRB5_RESPONDER_QUESTION_PASSWORD);
60 ret = alloc_data(&gp->storage, strlen(rpass));
63 memcpy(gp->storage.data, rpass, strlen(rpass));
64 gp->password = &gp->storage;
68 if (gp->password == NULL) {
72 if ((ret = krb5_unparse_name(context, client, &clientstr)))
75 snprintf(promptstr, sizeof(promptstr), _("Password for %s"),
79 pw = make_data(pwbuf, sizeof(pwbuf));
80 prompt.prompt = promptstr;
83 prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
85 /* PROMPTER_INVOCATION */
86 k5_set_prompt_types(context, &prompt_type);
87 ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt);
88 k5_set_prompt_types(context, 0);
92 ret = krb5int_copy_data_contents(context, &pw, &gp->storage);
93 zap(pw.data, pw.length);
96 gp->password = &gp->storage;
100 if ((ret = krb5_principal2salt(context, client, &defsalt)))
108 ret = krb5_c_string_to_key_with_params(context, etype, gp->password, salt,
109 params->data?params:NULL, as_key);
117 krb5_error_code KRB5_CALLCONV
118 krb5_init_creds_set_password(krb5_context context,
119 krb5_init_creds_context ctx,
120 const char *password)
124 s = strdup(password);
128 zapfree(ctx->gakpw.storage.data, ctx->gakpw.storage.length);
129 ctx->gakpw.storage = string2data(s);
130 ctx->gakpw.password = &ctx->gakpw.storage;
131 ctx->gak_fct = krb5_get_as_key_password;
132 ctx->gak_data = &ctx->gakpw;
136 /* Return the password expiry time indicated by enc_part2. Set *is_last_req
137 * if the information came from a last_req value. */
139 get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp,
140 krb5_timestamp *acct_exp, krb5_boolean *is_last_req)
142 krb5_last_req_entry **last_req;
147 *is_last_req = FALSE;
149 /* Look for last-req entries for password or account expiration. */
150 if (enc_part2->last_req) {
151 for (last_req = enc_part2->last_req; *last_req; last_req++) {
152 lr_type = (*last_req)->lr_type;
153 if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
154 lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
156 *pw_exp = (*last_req)->value;
157 } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME ||
158 lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) {
160 *acct_exp = (*last_req)->value;
165 /* If we didn't find any, use the ambiguous key_exp field. */
166 if (*is_last_req == FALSE)
167 *pw_exp = enc_part2->key_exp;
171 * Send an appropriate warning prompter if as_reply indicates that the password
172 * is going to expire soon. If an expire callback was provided, use that
176 warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options,
177 krb5_prompter_fct prompter, void *data,
178 const char *in_tkt_service, krb5_kdc_rep *as_reply)
181 krb5_expire_callback_func expire_cb;
183 krb5_timestamp pw_exp, acct_exp, now;
184 krb5_boolean is_last_req;
186 char ts[256], banner[1024];
188 get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req);
190 k5_gic_opt_get_expire_cb(options, &expire_cb, &expire_data);
191 if (expire_cb != NULL) {
192 /* Invoke the expire callback and don't send prompter warnings. */
193 (*expire_cb)(context, expire_data, pw_exp, acct_exp, is_last_req);
197 /* Don't warn if no password expiry value was sent. */
201 /* Don't warn if the password is being changed. */
202 if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
206 * If the expiry time came from a last_req field, assume the KDC wants us
207 * to warn. Otherwise, warn only if the expiry time is less than a week
210 ret = krb5_timeofday(context, &now);
214 (ts_after(now, pw_exp) || ts_delta(pw_exp, now) > 7 * 24 * 60 * 60))
220 ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts));
224 delta = ts_delta(pw_exp, now);
226 snprintf(banner, sizeof(banner),
227 _("Warning: Your password will expire in less than one hour "
229 } else if (delta < 86400*2) {
230 snprintf(banner, sizeof(banner),
231 _("Warning: Your password will expire in %d hour%s on %s"),
232 delta / 3600, delta < 7200 ? "" : "s", ts);
234 snprintf(banner, sizeof(banner),
235 _("Warning: Your password will expire in %d days on %s"),
239 /* PROMPTER_INVOCATION */
240 (*prompter)(context, data, 0, banner, 0, 0);
244 * Create a temporary options structure for getting a kadmin/changepw ticket,
245 * based on the appplication-specified options. Propagate all application
246 * options which affect preauthentication, but not options which affect the
247 * resulting ticket or how it is stored. Set lifetime and flags appropriate
248 * for a ticket which we will use immediately and then discard.
250 * The caller should free the result with free().
252 static krb5_error_code
253 make_chpw_options(krb5_context context, krb5_get_init_creds_opt *in,
254 krb5_get_init_creds_opt **out)
256 krb5_get_init_creds_opt *opt;
259 opt = k5_gic_opt_shallow_copy(in);
263 /* Get a non-forwardable, non-proxiable, short-lifetime ticket. */
264 krb5_get_init_creds_opt_set_tkt_life(opt, 5 * 60);
265 krb5_get_init_creds_opt_set_renew_life(opt, 0);
266 krb5_get_init_creds_opt_set_forwardable(opt, 0);
267 krb5_get_init_creds_opt_set_proxiable(opt, 0);
269 /* Unset options which should only apply to the actual ticket. */
270 opt->flags &= ~KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST;
271 opt->flags &= ~KRB5_GET_INIT_CREDS_OPT_ANONYMOUS;
273 /* The output ccache should only be used for the actual ticket. */
274 krb5_get_init_creds_opt_set_out_ccache(context, opt, NULL);
280 krb5_error_code KRB5_CALLCONV
281 krb5_get_init_creds_password(krb5_context context,
283 krb5_principal client,
284 const char *password,
285 krb5_prompter_fct prompter,
287 krb5_deltat start_time,
288 const char *in_tkt_service,
289 krb5_get_init_creds_opt *options)
293 krb5_kdc_rep *as_reply;
295 krb5_creds chpw_creds;
296 krb5_get_init_creds_opt *chpw_opts = NULL;
297 struct gak_password gakpw;
299 char banner[1024], pw0array[1024], pw1array[1024];
300 krb5_prompt prompt[2];
301 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
302 struct errinfo errsave = EMPTY_ERRINFO;
307 memset(&chpw_creds, 0, sizeof(chpw_creds));
308 memset(&gakpw, 0, sizeof(gakpw));
310 if (password != NULL) {
311 pw0 = string2data((char *)password);
312 gakpw.password = &pw0;
315 /* first try: get the requested tkt from any kdc */
317 ret = k5_get_init_creds(context, creds, client, prompter, data, start_time,
318 in_tkt_service, options, krb5_get_as_key_password,
319 &gakpw, &use_master, &as_reply);
321 /* check for success */
326 /* If all the kdc's are unavailable, or if the error was due to a
327 user interrupt, fail */
329 if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
330 ret == KRB5_LIBOS_PWDINTR || ret == KRB5_LIBOS_CANTREADPWD)
333 /* if the reply did not come from the master kdc, try again with
337 TRACE_GIC_PWD_MASTER(context);
340 k5_save_ctx_error(context, ret, &errsave);
342 krb5_free_kdc_rep( context, as_reply);
345 ret = k5_get_init_creds(context, creds, client, prompter, data,
346 start_time, in_tkt_service, options,
347 krb5_get_as_key_password, &gakpw, &use_master,
353 /* If the master is unreachable, return the error from the replica we
354 * were able to contact and reset the use_master flag. */
355 if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
356 ret == KRB5_REALM_UNKNOWN) {
357 ret = k5_restore_ctx_error(context, &errsave);
362 /* at this point, we have an error from the master. if the error
363 is not password expired, or if it is but there's no prompter,
366 if ((ret != KRB5KDC_ERR_KEY_EXP) ||
370 /* historically the default has been to prompt for password change.
371 * if the change password prompt option has not been set, we continue
372 * to prompt. Prompting is only disabled if the option has been set
373 * and the value has been set to false.
375 if (options && !(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
377 TRACE_GIC_PWD_EXPIRED(context);
379 /* ok, we have an expired password. Give the user a few chances
382 ret = make_chpw_options(context, options, &chpw_opts);
385 ret = k5_get_init_creds(context, &chpw_creds, client, prompter, data,
386 start_time, "kadmin/changepw", chpw_opts,
387 krb5_get_as_key_password, &gakpw, &use_master,
394 pw0.length = sizeof(pw0array);
395 prompt[0].prompt = _("Enter new password");
396 prompt[0].hidden = 1;
397 prompt[0].reply = &pw0;
398 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
402 pw1.length = sizeof(pw1array);
403 prompt[1].prompt = _("Enter it again");
404 prompt[1].hidden = 1;
405 prompt[1].reply = &pw1;
406 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
408 strlcpy(banner, _("Password expired. You must change it now."),
411 for (tries = 3; tries; tries--) {
412 TRACE_GIC_PWD_CHANGEPW(context, tries);
413 pw0.length = sizeof(pw0array);
414 pw1.length = sizeof(pw1array);
416 /* PROMPTER_INVOCATION */
417 k5_set_prompt_types(context, prompt_types);
418 ret = (*prompter)(context, data, 0, banner,
419 sizeof(prompt)/sizeof(prompt[0]), prompt);
420 k5_set_prompt_types(context, 0);
424 if (strcmp(pw0.data, pw1.data) != 0) {
425 ret = KRB5_LIBOS_BADPWDMATCH;
426 snprintf(banner, sizeof(banner),
427 _("%s. Please try again."), error_message(ret));
428 } else if (pw0.length == 0) {
429 ret = KRB5_CHPW_PWDNULL;
430 snprintf(banner, sizeof(banner),
431 _("%s. Please try again."), error_message(ret));
434 krb5_data code_string;
435 krb5_data result_string;
437 if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
438 &result_code, &code_string,
442 /* the change succeeded. go on */
444 if (result_code == 0) {
445 free(code_string.data);
446 free(result_string.data);
450 /* set this in case the retry loop falls through */
452 ret = KRB5_CHPW_FAIL;
454 if (result_code != KRB5_KPASSWD_SOFTERROR) {
455 free(code_string.data);
456 free(result_string.data);
460 /* the error was soft, so try again */
462 if (krb5_chpw_message(context, &result_string, &message) != 0)
465 /* 100 is I happen to know that no code_string will be longer
468 if (message != NULL && strlen(message) > (sizeof(banner) - 100))
469 message[sizeof(banner) - 100] = '\0';
471 snprintf(banner, sizeof(banner),
472 _("%.*s%s%s. Please try again.\n"),
473 (int) code_string.length, code_string.data,
474 message ? ": " : "", message ? message : "");
477 free(code_string.data);
478 free(result_string.data);
485 /* the password change was successful. Get an initial ticket
486 from the master. this is the last try. the return from this
489 TRACE_GIC_PWD_CHANGED(context);
490 gakpw.password = &pw0;
491 ret = k5_get_init_creds(context, creds, client, prompter, data,
492 start_time, in_tkt_service, options,
493 krb5_get_as_key_password, &gakpw, &use_master,
500 warn_pw_expiry(context, options, prompter, data, in_tkt_service,
503 zapfree(gakpw.storage.data, gakpw.storage.length);
504 memset(pw0array, 0, sizeof(pw0array));
505 memset(pw1array, 0, sizeof(pw1array));
506 krb5_free_cred_contents(context, &chpw_creds);
508 krb5_free_kdc_rep(context, as_reply);
509 k5_clear_error(&errsave);
515 Rewrites get_in_tkt in terms of newer get_init_creds API.
516 Attempts to get an initial ticket for creds->client to use server
517 creds->server, (realm is taken from creds->client), with options
518 options, and using creds->times.starttime, creds->times.endtime,
519 creds->times.renew_till as from, till, and rtime.
520 creds->times.renew_till is ignored unless the RENEWABLE option is requested.
522 If addrs is non-NULL, it is used for the addresses requested. If it is
523 null, the system standard addresses are used.
525 If password is non-NULL, it is converted using the cryptosystem entry
526 point for a string conversion routine, seeded with the client's name.
527 If password is passed as NULL, the password is read from the terminal,
528 and then converted into a key.
530 A succesful call will place the ticket in the credentials cache ccache.
532 returns system errors, encryption errors
534 krb5_error_code KRB5_CALLCONV
535 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
536 krb5_address *const *addrs, krb5_enctype *ktypes,
537 krb5_preauthtype *pre_auth_types,
538 const char *password, krb5_ccache ccache,
539 krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
541 krb5_error_code retval;
542 struct gak_password gakpw;
545 krb5_principal server_princ, client_princ;
547 krb5_get_init_creds_opt *opts = NULL;
549 memset(&gakpw, 0, sizeof(gakpw));
550 if (password != NULL) {
551 pw = string2data((char *)password);
552 gakpw.password = &pw;
554 retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
555 pre_auth_types, creds);
558 retval = krb5_unparse_name( context, creds->server, &server);
560 krb5_get_init_creds_opt_free(context, opts);
563 server_princ = creds->server;
564 client_princ = creds->client;
565 retval = k5_get_init_creds(context, creds, creds->client,
566 krb5_prompter_posix, NULL, 0, server, opts,
567 krb5_get_as_key_password, &gakpw, &use_master,
569 krb5_free_unparsed_name( context, server);
570 krb5_get_init_creds_opt_free(context, opts);
571 zapfree(gakpw.storage.data, gakpw.storage.length);
575 krb5_free_principal( context, creds->server);
576 krb5_free_principal( context, creds->client);
577 creds->client = client_princ;
578 creds->server = server_princ;
579 /* store it in the ccache! */
581 if ((retval = krb5_cc_store_cred(context, ccache, creds)))