2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A
2N/A/*
2N/A * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include "k5-int.h"
2N/A#include "com_err.h"
2N/A#include "init_creds_ctx.h"
2N/A/* Solaris Kerberos begin */
2N/A#include <admin.h>
2N/A#include <locale.h>
2N/A#include <syslog.h>
2N/A/* Solaris Kerberos end */
2N/A
2N/A/*
2N/A * Solaris Kerberos:
2N/A * See the function's definition for the description of this interface.
2N/A */
2N/Akrb5_error_code
2N/A__krb5_get_init_creds_password(krb5_context, krb5_creds *,
2N/A krb5_principal, char *,
2N/A krb5_prompter_fct, void *,
2N/A krb5_deltat, char *,
2N/A krb5_get_init_creds_opt *,
2N/A krb5_kdc_rep **);
2N/A
2N/Akrb5_error_code
2N/Akrb5_get_as_key_password(krb5_context context,
2N/A krb5_principal client,
2N/A krb5_enctype etype,
2N/A krb5_prompter_fct prompter,
2N/A void *prompter_data,
2N/A krb5_data *salt,
2N/A krb5_data *params,
2N/A krb5_keyblock *as_key,
2N/A void *gak_data)
2N/A{
2N/A krb5_data *password;
2N/A krb5_error_code ret;
2N/A krb5_data defsalt;
2N/A char *clientstr;
2N/A char promptstr[1024];
2N/A krb5_prompt prompt;
2N/A krb5_prompt_type prompt_type;
2N/A
2N/A password = (krb5_data *) gak_data;
2N/A
2N/A /* If there's already a key of the correct etype, we're done.
2N/A If the etype is wrong, free the existing key, and make
2N/A a new one.
2N/A
2N/A XXX This was the old behavior, and was wrong in hw preauth
2N/A cases. Is this new behavior -- always asking -- correct in all
2N/A cases? */
2N/A
2N/A if (as_key->length) {
2N/A if (as_key->enctype != etype) {
2N/A krb5_free_keyblock_contents (context, as_key);
2N/A as_key->length = 0;
2N/A }
2N/A }
2N/A
2N/A if (password->length == 0 || password->data[0] == '\0') {
2N/A if (prompter == NULL)
2N/A prompter = krb5_prompter_posix; /* Solaris Kerberos CR 5034141 */
2N/A
2N/A if ((ret = krb5_unparse_name(context, client, &clientstr)))
2N/A return(ret);
2N/A
2N/A snprintf(promptstr, sizeof(promptstr), "Password for %s", clientstr);
2N/A free(clientstr);
2N/A
2N/A prompt.prompt = promptstr;
2N/A prompt.hidden = 1;
2N/A prompt.reply = password;
2N/A prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
2N/A
2N/A /* PROMPTER_INVOCATION */
2N/A krb5int_set_prompt_types(context, &prompt_type);
2N/A if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
2N/A 1, &prompt))))) {
2N/A krb5int_set_prompt_types(context, 0);
2N/A return(ret);
2N/A }
2N/A krb5int_set_prompt_types(context, 0);
2N/A }
2N/A
2N/A if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
2N/A if ((ret = krb5_principal2salt(context, client, &defsalt)))
2N/A return(ret);
2N/A
2N/A salt = &defsalt;
2N/A } else {
2N/A defsalt.length = 0;
2N/A }
2N/A
2N/A ret = krb5_c_string_to_key_with_params(context, etype, password, salt,
2N/A params->data?params:NULL, as_key);
2N/A
2N/A if (defsalt.length)
2N/A free(defsalt.data);
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_set_password(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A const char *password)
2N/A{
2N/A char *s;
2N/A
2N/A s = strdup(password);
2N/A if (s == NULL)
2N/A return ENOMEM;
2N/A
2N/A if (ctx->password.data != NULL) {
2N/A zap(ctx->password.data, ctx->password.length);
2N/A krb5_free_data_contents(context, &ctx->password);
2N/A }
2N/A
2N/A ctx->password.data = s;
2N/A ctx->password.length = strlen(s);
2N/A ctx->gak_fct = krb5_get_as_key_password;
2N/A ctx->gak_data = &ctx->password;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_init_creds_password(krb5_context context,
2N/A krb5_creds *creds,
2N/A krb5_principal client,
2N/A char *password,
2N/A krb5_prompter_fct prompter,
2N/A void *data,
2N/A krb5_deltat start_time,
2N/A char *in_tkt_service,
2N/A krb5_get_init_creds_opt *options)
2N/A{
2N/A /*
2N/A * Solaris Kerberos:
2N/A * We call our own private function that returns the as_reply back to
2N/A * the caller. This structure contains information, such as
2N/A * key-expiration and last-req fields. Entities such as pam_krb5 can
2N/A * use this information to provide account/password expiration warnings.
2N/A * The original "prompter" interface is not granular enough for PAM,
2N/A * as it will perform all passes w/o coordination with other modules.
2N/A */
2N/A return (__krb5_get_init_creds_password(context, creds, client, password,
2N/A prompter, data, start_time,
2N/A in_tkt_service, options, NULL));
2N/A}
2N/A
2N/A/*
2N/A * Solaris Kerberos:
2N/A * See krb5_get_init_creds_password()'s comments for the justification of this
2N/A * private function. Caller must free ptr_as_reply if non-NULL.
2N/A */
2N/Akrb5_error_code KRB5_CALLCONV
2N/A__krb5_get_init_creds_password(krb5_context context,
2N/A krb5_creds *creds,
2N/A krb5_principal client,
2N/A char *password,
2N/A krb5_prompter_fct prompter,
2N/A void *data,
2N/A krb5_deltat start_time,
2N/A char *in_tkt_service,
2N/A krb5_get_init_creds_opt *options,
2N/A krb5_kdc_rep **ptr_as_reply)
2N/A{
2N/A krb5_error_code ret, ret2;
2N/A int use_master;
2N/A krb5_kdc_rep *as_reply;
2N/A int tries;
2N/A krb5_creds chpw_creds;
2N/A /* krb5_get_init_creds_opt *chpw_opts = NULL; */ /* Solaris Kerberos */
2N/A krb5_data pw0, pw1;
2N/A char banner[1024], pw0array[1024], pw1array[1024];
2N/A krb5_prompt prompt[2];
2N/A krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
2N/A /* Solaris Kerberos begin */
2N/A char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL;
2N/A kadm5_config_params params;
2N/A void *server_handle;
2N/A const char *err_msg_1 = NULL;
2N/A /* Solaris Kerberos end */
2N/A
2N/A use_master = 0;
2N/A as_reply = NULL;
2N/A memset(&chpw_creds, 0, sizeof(chpw_creds));
2N/A
2N/A pw0.data = pw0array;
2N/A
2N/A if (password && password[0]) {
2N/A if (strlcpy(pw0.data, password, sizeof(pw0array)) >= sizeof(pw0array)) {
2N/A ret = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A pw0.length = strlen(password);
2N/A } else {
2N/A pw0.data[0] = '\0';
2N/A pw0.length = sizeof(pw0array);
2N/A }
2N/A
2N/A pw1.data = pw1array;
2N/A pw1.data[0] = '\0';
2N/A pw1.length = sizeof(pw1array);
2N/A
2N/A /* first try: get the requested tkt from any kdc */
2N/A
2N/A ret = krb5int_get_init_creds(context, creds, client, prompter, data,
2N/A start_time, in_tkt_service, options,
2N/A krb5_get_as_key_password, (void *) &pw0,
2N/A &use_master, &as_reply);
2N/A
2N/A /* check for success */
2N/A
2N/A if (ret == 0)
2N/A goto cleanup;
2N/A
2N/A /* If all the kdc's are unavailable, or if the error was due to a
2N/A user interrupt, fail */
2N/A
2N/A if ((ret == KRB5_KDC_UNREACH) ||
2N/A (ret == KRB5_LIBOS_PWDINTR) ||
2N/A (ret == KRB5_REALM_CANT_RESOLVE))
2N/A goto cleanup;
2N/A
2N/A /* if the reply did not come from the master kdc, try again with
2N/A the master kdc */
2N/A
2N/A if (!use_master) {
2N/A use_master = 1;
2N/A
2N/A if (as_reply) {
2N/A krb5_free_kdc_rep( context, as_reply);
2N/A as_reply = NULL;
2N/A }
2N/A
2N/A /* Solaris Kerberos */
2N/A err_msg_1 = krb5_get_error_message(context, ret);
2N/A
2N/A ret2 = krb5int_get_init_creds(context, creds, client, prompter, data,
2N/A start_time, in_tkt_service, options,
2N/A krb5_get_as_key_password, (void *) &pw0,
2N/A &use_master, &as_reply);
2N/A
2N/A if (ret2 == 0) {
2N/A ret = 0;
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* if the master is unreachable, return the error from the
2N/A slave we were able to contact or reset the use_master flag */
2N/A
2N/A if ((ret2 != KRB5_KDC_UNREACH) &&
2N/A (ret2 != KRB5_REALM_CANT_RESOLVE) &&
2N/A (ret2 != KRB5_REALM_UNKNOWN))
2N/A ret = ret2;
2N/A else {
2N/A use_master = 0;
2N/A /* Solaris - if 2nd try failed, reset 1st err msg */
2N/A if (ret2 && err_msg_1) {
2N/A krb5_set_error_message(context, ret, err_msg_1);
2N/A }
2N/A }
2N/A }
2N/A
2N/A#ifdef USE_KIM
2N/A if (ret == KRB5KDC_ERR_KEY_EXP)
2N/A goto cleanup; /* Login library will deal appropriately with this error */
2N/A#endif
2N/A
2N/A /* at this point, we have an error from the master. if the error
2N/A is not password expired, or if it is but there's no prompter,
2N/A return this error */
2N/A
2N/A if ((ret != KRB5KDC_ERR_KEY_EXP) ||
2N/A (prompter == NULL))
2N/A goto cleanup;
2N/A
2N/A /* historically the default has been to prompt for password change.
2N/A * if the change password prompt option has not been set, we continue
2N/A * to prompt. Prompting is only disabled if the option has been set
2N/A * and the value has been set to false.
2N/A */
2N/A if (options && !(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
2N/A goto cleanup;
2N/A
2N/A /* ok, we have an expired password. Give the user a few chances
2N/A to change it */
2N/A
2N/A /* use a minimal set of options */
2N/A
2N/A prompt[0].prompt = "Enter new password";
2N/A prompt[0].hidden = 1;
2N/A prompt[0].reply = &pw0;
2N/A prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
2N/A
2N/A prompt[1].prompt = "Enter it again";
2N/A prompt[1].hidden = 1;
2N/A prompt[1].reply = &pw1;
2N/A prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
2N/A
2N/A strlcpy(banner, "Password expired. You must change it now.",
2N/A sizeof(banner));
2N/A
2N/A for (tries = 3; tries; tries--) {
2N/A pw0.length = sizeof(pw0array);
2N/A pw1.length = sizeof(pw1array);
2N/A
2N/A /* PROMPTER_INVOCATION */
2N/A krb5int_set_prompt_types(context, prompt_types);
2N/A if ((ret = ((*prompter)(context, data, 0, banner,
2N/A sizeof(prompt)/sizeof(prompt[0]), prompt))))
2N/A goto cleanup;
2N/A krb5int_set_prompt_types(context, 0);
2N/A
2N/A
2N/A if (strcmp(pw0.data, pw1.data) != 0) {
2N/A ret = KRB5_LIBOS_BADPWDMATCH;
2N/A snprintf(banner, sizeof(banner),
2N/A "%s. Please try again.", error_message(ret));
2N/A } else if (pw0.length == 0) {
2N/A ret = KRB5_CHPW_PWDNULL;
2N/A snprintf(banner, sizeof(banner),
2N/A "%s. Please try again.", error_message(ret));
2N/A } else {
2N/A int result_code;
2N/A krb5_data code_string;
2N/A krb5_data result_string;
2N/A
2N/A if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
2N/A &result_code, &code_string,
2N/A &result_string)))
2N/A goto cleanup;
2N/A
2N/A /* the change succeeded. go on */
2N/A
2N/A if (result_code == 0) {
2N/A free(result_string.data);
2N/A break;
2N/A }
2N/A
2N/A /* set this in case the retry loop falls through */
2N/A
2N/A ret = KRB5_CHPW_FAIL;
2N/A
2N/A if (result_code != KRB5_KPASSWD_SOFTERROR) {
2N/A free(result_string.data);
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* the error was soft, so try again */
2N/A
2N/A /* 100 is I happen to know that no code_string will be longer
2N/A than 100 chars */
2N/A
2N/A if (result_string.length > (sizeof(banner)-100))
2N/A result_string.length = sizeof(banner)-100;
2N/A
2N/A snprintf(banner, sizeof(banner), "%.*s%s%.*s. Please try again.\n",
2N/A (int) code_string.length, code_string.data,
2N/A result_string.length ? ": " : "",
2N/A (int) result_string.length,
2N/A result_string.data ? result_string.data : "");
2N/A
2N/A free(code_string.data);
2N/A free(result_string.data);
2N/A }
2N/A }
2N/A
2N/A if (ret)
2N/A goto cleanup;
2N/A
2N/A /* the password change was successful. Get an initial ticket
2N/A from the master. this is the last try. the return from this
2N/A is final. */
2N/A
2N/A ret = krb5int_get_init_creds(context, creds, client, prompter, data,
2N/A start_time, in_tkt_service, options,
2N/A krb5_get_as_key_password, (void *) &pw0,
2N/A &use_master, &as_reply);
2N/A
2N/Acleanup:
2N/A /* Solaris Kerberos */
2N/A if (err_msg_1)
2N/A krb5_free_error_message(context, err_msg_1);
2N/A
2N/A krb5int_set_prompt_types(context, 0);
2N/A /* if getting the password was successful, then check to see if the
2N/A password is about to expire, and warn if so */
2N/A
2N/A if (ret == 0) {
2N/A krb5_timestamp now;
2N/A krb5_last_req_entry **last_req;
2N/A int hours;
2N/A
2N/A /* XXX 7 days should be configurable. This is all pretty ad hoc,
2N/A and could probably be improved if I was willing to screw around
2N/A with timezones, etc. */
2N/A /*
2N/A * Solaris Kerberos: replacing "kadmin/changepw" with cpw_service
2N/A * below.
2N/A */
2N/A if (prompter &&
2N/A (!in_tkt_service ||
2N/A (cpw_service && (strcmp(in_tkt_service, cpw_service) != 0))) &&
2N/A ((ret = krb5_timeofday(context, &now)) == 0) &&
2N/A as_reply->enc_part2->key_exp &&
2N/A ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
2N/A (hours >= 0)) {
2N/A if (hours < 1)
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in less than one hour.");
2N/A else if (hours <= 48)
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in %d hour%s.",
2N/A hours, (hours == 1)?"":"s");
2N/A else
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in %d days.",
2N/A hours/24);
2N/A
2N/A /* ignore an error here */
2N/A /* PROMPTER_INVOCATION */
2N/A (*prompter)(context, data, 0, banner, 0, 0);
2N/A } else if (prompter &&
2N/A (!in_tkt_service ||
2N/A (cpw_service && (strcmp(in_tkt_service, cpw_service) != 0))) &&
2N/A as_reply->enc_part2 && as_reply->enc_part2->last_req) {
2N/A /*
2N/A * Check the last_req fields
2N/A */
2N/A
2N/A for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
2N/A if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
2N/A (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
2N/A krb5_deltat delta;
2N/A char ts[256];
2N/A
2N/A if ((ret = krb5_timeofday(context, &now)))
2N/A break;
2N/A
2N/A if ((ret = krb5_timestamp_to_string((*last_req)->value,
2N/A ts, sizeof(ts))))
2N/A break;
2N/A
2N/A delta = (*last_req)->value - now;
2N/A if (delta < 3600)
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in less than one hour on %s",
2N/A ts);
2N/A else if (delta < 86400*2)
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in %d hour%s on %s",
2N/A delta / 3600, delta < 7200 ? "" : "s", ts);
2N/A else
2N/A snprintf(banner, sizeof(banner),
2N/A "Warning: Your password will expire in %d days on %s",
2N/A delta / 86400, ts);
2N/A /* ignore an error here */
2N/A /* PROMPTER_INVOCATION */
2N/A (*prompter)(context, data, 0, banner, 0, 0);
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* Solaris Kerberos begin */
2N/A free(cpw_service);
2N/A free(princ_str);
2N/A /* if (chpw_opts) */
2N/A /* krb5_get_init_creds_opt_free(context, chpw_opts); */
2N/A /* Solaris Kerberos end */
2N/A memset(pw0array, 0, sizeof(pw0array));
2N/A memset(pw1array, 0, sizeof(pw1array));
2N/A krb5_free_cred_contents(context, &chpw_creds);
2N/A /*
2N/A * Solaris Kerberos:
2N/A * Argument, ptr_as_reply, being returned to caller if success and non-NULL.
2N/A */
2N/A if (as_reply != NULL) {
2N/A if (ptr_as_reply == NULL)
2N/A krb5_free_kdc_rep(context, as_reply);
2N/A else
2N/A *ptr_as_reply = as_reply;
2N/A }
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/A/*
2N/A Rewrites get_in_tkt in terms of newer get_init_creds API.
2N/A Attempts to get an initial ticket for creds->client to use server
2N/A creds->server, (realm is taken from creds->client), with options
2N/A options, and using creds->times.starttime, creds->times.endtime,
2N/A creds->times.renew_till as from, till, and rtime.
2N/A creds->times.renew_till is ignored unless the RENEWABLE option is requested.
2N/A
2N/A If addrs is non-NULL, it is used for the addresses requested. If it is
2N/A null, the system standard addresses are used.
2N/A
2N/A If password is non-NULL, it is converted using the cryptosystem entry
2N/A point for a string conversion routine, seeded with the client's name.
2N/A If password is passed as NULL, the password is read from the terminal,
2N/A and then converted into a key.
2N/A
2N/A A succesful call will place the ticket in the credentials cache ccache.
2N/A
2N/A returns system errors, encryption errors
2N/A*/
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
2N/A krb5_address *const *addrs, krb5_enctype *ktypes,
2N/A krb5_preauthtype *pre_auth_types,
2N/A const char *password, krb5_ccache ccache,
2N/A krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_data pw0;
2N/A char pw0array[1024];
2N/A char * server;
2N/A krb5_principal server_princ, client_princ;
2N/A int use_master = 0;
2N/A krb5_get_init_creds_opt *opts = NULL;
2N/A
2N/A pw0.data = pw0array;
2N/A if (password && password[0]) {
2N/A if (strlcpy(pw0.data, password, sizeof(pw0array)) >= sizeof(pw0array))
2N/A return EINVAL;
2N/A pw0.length = strlen(password);
2N/A } else {
2N/A pw0.data[0] = '\0';
2N/A pw0.length = sizeof(pw0array);
2N/A }
2N/A retval = krb5int_populate_gic_opt(context, &opts,
2N/A options, addrs, ktypes,
2N/A pre_auth_types, creds);
2N/A if (retval)
2N/A return (retval);
2N/A retval = krb5_unparse_name( context, creds->server, &server);
2N/A if (retval) {
2N/A krb5_get_init_creds_opt_free(context, opts);
2N/A return (retval);
2N/A }
2N/A server_princ = creds->server;
2N/A client_princ = creds->client;
2N/A retval = krb5int_get_init_creds(context, creds, creds->client,
2N/A krb5_prompter_posix, NULL,
2N/A 0, server, opts,
2N/A krb5_get_as_key_password, &pw0,
2N/A &use_master, ret_as_reply);
2N/A krb5_free_unparsed_name( context, server);
2N/A krb5_get_init_creds_opt_free(context, opts);
2N/A if (retval) {
2N/A return (retval);
2N/A }
2N/A krb5_free_principal( context, creds->server);
2N/A krb5_free_principal( context, creds->client);
2N/A creds->client = client_princ;
2N/A creds->server = server_princ;
2N/A /* store it in the ccache! */
2N/A if (ccache)
2N/A if ((retval = krb5_cc_store_cred(context, ccache, creds)))
2N/A return (retval);
2N/A return retval;
2N/A}