17c478bd9Sstevel@tonic-gate /*
25e01956fSGlenn Barry  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */
37c478bd9Sstevel@tonic-gate 
4*d3b5f563SJohn Levon /*
5*d3b5f563SJohn Levon  * Copyright 2019 Joyent, Inc.
6*d3b5f563SJohn Levon  */
7*d3b5f563SJohn Levon 
8159d09a2SMark Phalan #include "k5-int.h"
9159d09a2SMark Phalan #include "com_err.h"
107c478bd9Sstevel@tonic-gate #include <admin.h>
117c478bd9Sstevel@tonic-gate #include <locale.h>
127c478bd9Sstevel@tonic-gate #include <syslog.h>
137c478bd9Sstevel@tonic-gate 
147c478bd9Sstevel@tonic-gate /* Solaris Kerberos:
157c478bd9Sstevel@tonic-gate  *
167c478bd9Sstevel@tonic-gate  * Change Password functionality is handled by the libkadm5clnt.so.1 library in
177c478bd9Sstevel@tonic-gate  * Solaris Kerberos. In order to avoid a circular dependency between that lib
187c478bd9Sstevel@tonic-gate  * and the kerberos mech lib, we use the #pragma weak compiler directive.
197c478bd9Sstevel@tonic-gate  * This way, when applications link with the libkadm5clnt.so.1 lib the circular
207c478bd9Sstevel@tonic-gate  * dependancy between the two libs will be resolved.
217c478bd9Sstevel@tonic-gate  */
227c478bd9Sstevel@tonic-gate 
237c478bd9Sstevel@tonic-gate #pragma weak kadm5_get_cpw_host_srv_name
247c478bd9Sstevel@tonic-gate #pragma weak kadm5_init_with_password
257c478bd9Sstevel@tonic-gate #pragma weak kadm5_chpass_principal_util
267c478bd9Sstevel@tonic-gate 
277c478bd9Sstevel@tonic-gate extern kadm5_ret_t kadm5_get_cpw_host_srv_name(krb5_context, const char *,
287c478bd9Sstevel@tonic-gate 			char **);
297c478bd9Sstevel@tonic-gate extern kadm5_ret_t kadm5_init_with_password(char *, char *, char *,
3054925bf6Swillf 			kadm5_config_params *, krb5_ui_4, krb5_ui_4, char **,
3154925bf6Swillf 			void **);
327c478bd9Sstevel@tonic-gate extern kadm5_ret_t kadm5_chpass_principal_util(void *, krb5_principal,
3356a424ccSmp 			char *, char **, char *, unsigned int);
347c478bd9Sstevel@tonic-gate 
353125ebfcSsemery /*
363125ebfcSsemery  * Solaris Kerberos:
373125ebfcSsemery  * See the function's definition for the description of this interface.
383125ebfcSsemery  */
393125ebfcSsemery krb5_error_code __krb5_get_init_creds_password(krb5_context,
403125ebfcSsemery 	krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
413125ebfcSsemery 	krb5_deltat, char *, krb5_get_init_creds_opt *, krb5_kdc_rep **);
423125ebfcSsemery 
437c478bd9Sstevel@tonic-gate static krb5_error_code
krb5_get_as_key_password(krb5_context context,krb5_principal client,krb5_enctype etype,krb5_prompter_fct prompter,void * prompter_data,krb5_data * salt,krb5_data * params,krb5_keyblock * as_key,void * gak_data)447c478bd9Sstevel@tonic-gate krb5_get_as_key_password(
45159d09a2SMark Phalan     krb5_context context,
46159d09a2SMark Phalan     krb5_principal client,
47159d09a2SMark Phalan     krb5_enctype etype,
48159d09a2SMark Phalan     krb5_prompter_fct prompter,
49159d09a2SMark Phalan     void *prompter_data,
50159d09a2SMark Phalan     krb5_data *salt,
51159d09a2SMark Phalan     krb5_data *params,
52159d09a2SMark Phalan     krb5_keyblock *as_key,
53159d09a2SMark Phalan     void *gak_data)
547c478bd9Sstevel@tonic-gate {
557c478bd9Sstevel@tonic-gate     krb5_data *password;
567c478bd9Sstevel@tonic-gate     krb5_error_code ret;
577c478bd9Sstevel@tonic-gate     krb5_data defsalt;
587c478bd9Sstevel@tonic-gate     char *clientstr;
597c478bd9Sstevel@tonic-gate     char promptstr[1024];
607c478bd9Sstevel@tonic-gate     krb5_prompt prompt;
617c478bd9Sstevel@tonic-gate     krb5_prompt_type prompt_type;
627c478bd9Sstevel@tonic-gate 
637c478bd9Sstevel@tonic-gate     password = (krb5_data *) gak_data;
647c478bd9Sstevel@tonic-gate 
657c478bd9Sstevel@tonic-gate     /* If there's already a key of the correct etype, we're done.
667c478bd9Sstevel@tonic-gate        If the etype is wrong, free the existing key, and make
677c478bd9Sstevel@tonic-gate        a new one.
687c478bd9Sstevel@tonic-gate 
697c478bd9Sstevel@tonic-gate        XXX This was the old behavior, and was wrong in hw preauth
707c478bd9Sstevel@tonic-gate        cases.  Is this new behavior -- always asking -- correct in all
717c478bd9Sstevel@tonic-gate        cases?  */
727c478bd9Sstevel@tonic-gate 
737c478bd9Sstevel@tonic-gate     if (as_key->length) {
747c478bd9Sstevel@tonic-gate 	if (as_key->enctype != etype) {
757c478bd9Sstevel@tonic-gate 	    krb5_free_keyblock_contents (context, as_key);
767c478bd9Sstevel@tonic-gate 	    as_key->length = 0;
777c478bd9Sstevel@tonic-gate 	}
787c478bd9Sstevel@tonic-gate     }
797c478bd9Sstevel@tonic-gate 
807c478bd9Sstevel@tonic-gate     if (password->data[0] == '\0') {
817c478bd9Sstevel@tonic-gate 	if (prompter == NULL)
82159d09a2SMark Phalan 		prompter = krb5_prompter_posix; /* Solaris Kerberos */
837c478bd9Sstevel@tonic-gate 
847c478bd9Sstevel@tonic-gate 	if ((ret = krb5_unparse_name(context, client, &clientstr)))
85159d09a2SMark Phalan 	  return(ret);
867c478bd9Sstevel@tonic-gate 
877c478bd9Sstevel@tonic-gate 	strcpy(promptstr, "Password for ");
887c478bd9Sstevel@tonic-gate 	strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1);
897c478bd9Sstevel@tonic-gate 	promptstr[sizeof(promptstr)-1] = '\0';
907c478bd9Sstevel@tonic-gate 
917c478bd9Sstevel@tonic-gate 	free(clientstr);
927c478bd9Sstevel@tonic-gate 
937c478bd9Sstevel@tonic-gate 	prompt.prompt = promptstr;
947c478bd9Sstevel@tonic-gate 	prompt.hidden = 1;
957c478bd9Sstevel@tonic-gate 	prompt.reply = password;
967c478bd9Sstevel@tonic-gate 	prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
977c478bd9Sstevel@tonic-gate 
987c478bd9Sstevel@tonic-gate 	/* PROMPTER_INVOCATION */
997c478bd9Sstevel@tonic-gate 	krb5int_set_prompt_types(context, &prompt_type);
100505d05c7Sgtb 	if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
101505d05c7Sgtb 				1, &prompt))))) {
1027c478bd9Sstevel@tonic-gate 	    krb5int_set_prompt_types(context, 0);
1037c478bd9Sstevel@tonic-gate 	    return(ret);
1047c478bd9Sstevel@tonic-gate 	}
1057c478bd9Sstevel@tonic-gate 	krb5int_set_prompt_types(context, 0);
1067c478bd9Sstevel@tonic-gate     }
1077c478bd9Sstevel@tonic-gate 
108159d09a2SMark Phalan     if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
1097c478bd9Sstevel@tonic-gate 	if ((ret = krb5_principal2salt(context, client, &defsalt)))
1107c478bd9Sstevel@tonic-gate 	    return(ret);
1117c478bd9Sstevel@tonic-gate 
1127c478bd9Sstevel@tonic-gate 	salt = &defsalt;
1137c478bd9Sstevel@tonic-gate     } else {
1147c478bd9Sstevel@tonic-gate 	defsalt.length = 0;
1157c478bd9Sstevel@tonic-gate     }
1167c478bd9Sstevel@tonic-gate 
1177c478bd9Sstevel@tonic-gate     ret = krb5_c_string_to_key_with_params(context, etype, password, salt,
118159d09a2SMark Phalan 					   params->data?params:NULL, as_key);
1197c478bd9Sstevel@tonic-gate 
1207c478bd9Sstevel@tonic-gate     if (defsalt.length)
1217c478bd9Sstevel@tonic-gate 	krb5_xfree(defsalt.data);
1227c478bd9Sstevel@tonic-gate 
1237c478bd9Sstevel@tonic-gate     return(ret);
1247c478bd9Sstevel@tonic-gate }
1257c478bd9Sstevel@tonic-gate 
1267c478bd9Sstevel@tonic-gate krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_password(krb5_context context,krb5_creds * creds,krb5_principal client,char * password,krb5_prompter_fct prompter,void * data,krb5_deltat start_time,char * in_tkt_service,krb5_get_init_creds_opt * options)127159d09a2SMark Phalan krb5_get_init_creds_password(krb5_context context,
128159d09a2SMark Phalan 			     krb5_creds *creds,
129159d09a2SMark Phalan 			     krb5_principal client,
130159d09a2SMark Phalan 			     char *password,
131159d09a2SMark Phalan 			     krb5_prompter_fct prompter,
132159d09a2SMark Phalan 			     void *data,
133159d09a2SMark Phalan 			     krb5_deltat start_time,
134159d09a2SMark Phalan 			     char *in_tkt_service,
135159d09a2SMark Phalan 			     krb5_get_init_creds_opt *options)
1363125ebfcSsemery {
1373125ebfcSsemery 	/*
1383125ebfcSsemery 	 * Solaris Kerberos:
1393125ebfcSsemery 	 * We call our own private function that returns the as_reply back to
1403125ebfcSsemery 	 * the caller.  This structure contains information, such as
1413125ebfcSsemery 	 * key-expiration and last-req fields.  Entities such as pam_krb5 can
1423125ebfcSsemery 	 * use this information to provide account/password expiration warnings.
1433125ebfcSsemery 	 * The original "prompter" interface is not granular enough for PAM,
1443125ebfcSsemery 	 * as it will perform all passes w/o coordination with other modules.
1453125ebfcSsemery 	 */
1463125ebfcSsemery 	return (__krb5_get_init_creds_password(context, creds, client, password,
1473125ebfcSsemery 		prompter, data, start_time, in_tkt_service, options, NULL));
1483125ebfcSsemery }
1493125ebfcSsemery 
1503125ebfcSsemery /*
1513125ebfcSsemery  * Solaris Kerberos:
1523125ebfcSsemery  * See krb5_get_init_creds_password()'s comments for the justification of this
1533125ebfcSsemery  * private function.  Caller must free ptr_as_reply if non-NULL.
1543125ebfcSsemery  */
1553125ebfcSsemery krb5_error_code KRB5_CALLCONV
__krb5_get_init_creds_password(krb5_context context,krb5_creds * creds,krb5_principal client,char * password,krb5_prompter_fct prompter,void * data,krb5_deltat start_time,char * in_tkt_service,krb5_get_init_creds_opt * options,krb5_kdc_rep ** ptr_as_reply)1563125ebfcSsemery __krb5_get_init_creds_password(
1573125ebfcSsemery      krb5_context context,
1583125ebfcSsemery      krb5_creds *creds,
1593125ebfcSsemery      krb5_principal client,
1603125ebfcSsemery      char *password,
1613125ebfcSsemery      krb5_prompter_fct prompter,
1623125ebfcSsemery      void *data,
1633125ebfcSsemery      krb5_deltat start_time,
1643125ebfcSsemery      char *in_tkt_service,
1653125ebfcSsemery      krb5_get_init_creds_opt *options,
1663125ebfcSsemery      krb5_kdc_rep **ptr_as_reply)
1677c478bd9Sstevel@tonic-gate {
1687c478bd9Sstevel@tonic-gate    krb5_error_code ret, ret2;
1697c478bd9Sstevel@tonic-gate    int use_master;
1707c478bd9Sstevel@tonic-gate    krb5_kdc_rep *as_reply;
1717c478bd9Sstevel@tonic-gate    int tries;
1727c478bd9Sstevel@tonic-gate    krb5_creds chpw_creds;
1737c478bd9Sstevel@tonic-gate    krb5_data pw0, pw1;
1747c478bd9Sstevel@tonic-gate    char banner[1024], pw0array[1024], pw1array[1024];
1757c478bd9Sstevel@tonic-gate    krb5_prompt prompt[2];
1767c478bd9Sstevel@tonic-gate    krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
177159d09a2SMark Phalan    krb5_gic_opt_ext *opte = NULL;
178159d09a2SMark Phalan    krb5_gic_opt_ext *chpw_opte = NULL;
1797c478bd9Sstevel@tonic-gate 
1807c478bd9Sstevel@tonic-gate    char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL;
1817c478bd9Sstevel@tonic-gate    kadm5_config_params  params;
1827c478bd9Sstevel@tonic-gate    void *server_handle;
1835e01956fSGlenn Barry    const char *err_msg_1 = NULL;
1847c478bd9Sstevel@tonic-gate 
1857c478bd9Sstevel@tonic-gate    use_master = 0;
1867c478bd9Sstevel@tonic-gate    as_reply = NULL;
1877c478bd9Sstevel@tonic-gate    memset(&chpw_creds, 0, sizeof(chpw_creds));
1887c478bd9Sstevel@tonic-gate 
1897c478bd9Sstevel@tonic-gate    pw0.data = pw0array;
1907c478bd9Sstevel@tonic-gate 
191159d09a2SMark Phalan    if (password && password[0]) {
1927c478bd9Sstevel@tonic-gate       if ((pw0.length = strlen(password)) > sizeof(pw0array)) {
1937c478bd9Sstevel@tonic-gate 	 ret = EINVAL;
1947c478bd9Sstevel@tonic-gate 	 goto cleanup;
1957c478bd9Sstevel@tonic-gate       }
1967c478bd9Sstevel@tonic-gate       strcpy(pw0.data, password);
1977c478bd9Sstevel@tonic-gate    } else {
1987c478bd9Sstevel@tonic-gate       pw0.data[0] = '\0';
1997c478bd9Sstevel@tonic-gate       pw0.length = sizeof(pw0array);
2007c478bd9Sstevel@tonic-gate    }
2017c478bd9Sstevel@tonic-gate 
2027c478bd9Sstevel@tonic-gate    pw1.data = pw1array;
2037c478bd9Sstevel@tonic-gate    pw1.data[0] = '\0';
2047c478bd9Sstevel@tonic-gate    pw1.length = sizeof(pw1array);
2057c478bd9Sstevel@tonic-gate 
206159d09a2SMark Phalan    ret = krb5int_gic_opt_to_opte(context, options, &opte, 1,
207159d09a2SMark Phalan 				 "krb5_get_init_creds_password");
208159d09a2SMark Phalan    if (ret)
209159d09a2SMark Phalan       goto cleanup;
210159d09a2SMark Phalan 
2117c478bd9Sstevel@tonic-gate    /* first try: get the requested tkt from any kdc */
2127c478bd9Sstevel@tonic-gate 
2137c478bd9Sstevel@tonic-gate    ret = krb5_get_init_creds(context, creds, client, prompter, data,
214159d09a2SMark Phalan 			     start_time, in_tkt_service, opte,
2157c478bd9Sstevel@tonic-gate 			     krb5_get_as_key_password, (void *) &pw0,
216505d05c7Sgtb 			     &use_master, &as_reply);
2177c478bd9Sstevel@tonic-gate    /* check for success */
2187c478bd9Sstevel@tonic-gate 
2197c478bd9Sstevel@tonic-gate    if (ret == 0)
2207c478bd9Sstevel@tonic-gate       goto cleanup;
2217c478bd9Sstevel@tonic-gate 
2227c478bd9Sstevel@tonic-gate    /* If all the kdc's are unavailable, or if the error was due to a
223505d05c7Sgtb       user interrupt, or preauth errored out, fail */
2247c478bd9Sstevel@tonic-gate 
2257c478bd9Sstevel@tonic-gate    if ((ret == KRB5_KDC_UNREACH) ||
2267c478bd9Sstevel@tonic-gate        (ret == KRB5_PREAUTH_FAILED) ||
2277c478bd9Sstevel@tonic-gate        (ret == KRB5_LIBOS_PWDINTR) ||
2285e01956fSGlenn Barry        (ret == KRB5_REALM_CANT_RESOLVE))
2297c478bd9Sstevel@tonic-gate       goto cleanup;
2307c478bd9Sstevel@tonic-gate 
2317c478bd9Sstevel@tonic-gate    /* if the reply did not come from the master kdc, try again with
2327c478bd9Sstevel@tonic-gate       the master kdc */
2337c478bd9Sstevel@tonic-gate 
2347c478bd9Sstevel@tonic-gate    if (!use_master) {
2357c478bd9Sstevel@tonic-gate       use_master = 1;
2367c478bd9Sstevel@tonic-gate 
2377c478bd9Sstevel@tonic-gate       if (as_reply) {
238159d09a2SMark Phalan 	  krb5_free_kdc_rep( context, as_reply);
239159d09a2SMark Phalan 	  as_reply = NULL;
2407c478bd9Sstevel@tonic-gate       }
2415e01956fSGlenn Barry 
2425e01956fSGlenn Barry       err_msg_1 = krb5_get_error_message(context, ret);
2435e01956fSGlenn Barry 
2447c478bd9Sstevel@tonic-gate       ret2 = krb5_get_init_creds(context, creds, client, prompter, data,
245159d09a2SMark Phalan 				 start_time, in_tkt_service, opte,
2467c478bd9Sstevel@tonic-gate 				 krb5_get_as_key_password, (void *) &pw0,
247505d05c7Sgtb 				 &use_master, &as_reply);
248159d09a2SMark Phalan 
2497c478bd9Sstevel@tonic-gate       if (ret2 == 0) {
2507c478bd9Sstevel@tonic-gate 	 ret = 0;
2517c478bd9Sstevel@tonic-gate 	 goto cleanup;
2527c478bd9Sstevel@tonic-gate       }
2537c478bd9Sstevel@tonic-gate 
2547c478bd9Sstevel@tonic-gate       /* if the master is unreachable, return the error from the
255159d09a2SMark Phalan 	 slave we were able to contact or reset the use_master flag */
256159d09a2SMark Phalan 
2575e01956fSGlenn Barry       if ((ret2 != KRB5_KDC_UNREACH) &&
2585e01956fSGlenn Barry 	(ret2 != KRB5_REALM_CANT_RESOLVE) &&
2595e01956fSGlenn Barry 	(ret2 != KRB5_REALM_UNKNOWN)) {
2605e01956fSGlenn Barry 	ret = ret2;
2615e01956fSGlenn Barry       } else {
2625e01956fSGlenn Barry 	use_master = 0;
2635e01956fSGlenn Barry 	/* Solaris - if 2nd try failed, reset 1st err msg */
2645e01956fSGlenn Barry 	if (ret2 && err_msg_1) {
2655e01956fSGlenn Barry 	  krb5_set_error_message(context, ret, err_msg_1);
2665e01956fSGlenn Barry 	}
2675e01956fSGlenn Barry       }
2687c478bd9Sstevel@tonic-gate    }
2697c478bd9Sstevel@tonic-gate 
270159d09a2SMark Phalan /* Solaris Kerberos: 163 resync */
271159d09a2SMark Phalan /* #ifdef USE_LOGIN_LIBRARY */
2727c478bd9Sstevel@tonic-gate 	if (ret == KRB5KDC_ERR_KEY_EXP)
273159d09a2SMark Phalan 		goto cleanup;	/* Login library will deal appropriately with this error */
274159d09a2SMark Phalan /* #endif */
2757c478bd9Sstevel@tonic-gate 
2767c478bd9Sstevel@tonic-gate    /* at this point, we have an error from the master.  if the error
2777c478bd9Sstevel@tonic-gate       is not password expired, or if it is but there's no prompter,
2787c478bd9Sstevel@tonic-gate       return this error */
2797c478bd9Sstevel@tonic-gate 
2807c478bd9Sstevel@tonic-gate    if ((ret != KRB5KDC_ERR_KEY_EXP) ||
2817c478bd9Sstevel@tonic-gate        (prompter == NULL))
2827c478bd9Sstevel@tonic-gate       goto cleanup;
2837c478bd9Sstevel@tonic-gate 
284159d09a2SMark Phalan     /* historically the default has been to prompt for password change.
285159d09a2SMark Phalan      * if the change password prompt option has not been set, we continue
286159d09a2SMark Phalan      * to prompt.  Prompting is only disabled if the option has been set
287159d09a2SMark Phalan      * and the value has been set to false.
288159d09a2SMark Phalan      */
289159d09a2SMark Phalan     if (!(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
290159d09a2SMark Phalan 	goto cleanup;
291159d09a2SMark Phalan 
292159d09a2SMark Phalan     /* ok, we have an expired password.  Give the user a few chances
2937c478bd9Sstevel@tonic-gate       to change it */
2947c478bd9Sstevel@tonic-gate 
2957c478bd9Sstevel@tonic-gate 
296159d09a2SMark Phalan    /*
297159d09a2SMark Phalan     * Solaris Kerberos:
2987c478bd9Sstevel@tonic-gate     * Get the correct change password service principal name to use.
2997c478bd9Sstevel@tonic-gate     * This is necessary because SEAM based admin servers require
3007c478bd9Sstevel@tonic-gate     * a slightly different service principal name than MIT/MS servers.
3017c478bd9Sstevel@tonic-gate     */
3027c478bd9Sstevel@tonic-gate 
3037c478bd9Sstevel@tonic-gate    memset((char *) &params, 0, sizeof (params));
3047c478bd9Sstevel@tonic-gate 
3057c478bd9Sstevel@tonic-gate    snprintf(admin_realm, sizeof (admin_realm),
3067c478bd9Sstevel@tonic-gate 	krb5_princ_realm(context, client)->data);
3077c478bd9Sstevel@tonic-gate    params.mask |= KADM5_CONFIG_REALM;
3087c478bd9Sstevel@tonic-gate    params.realm = admin_realm;
3097c478bd9Sstevel@tonic-gate 
3107c478bd9Sstevel@tonic-gate    ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service);
3117c478bd9Sstevel@tonic-gate 
3127c478bd9Sstevel@tonic-gate    if (ret != KADM5_OK) {
3137c478bd9Sstevel@tonic-gate 	syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
3147c478bd9Sstevel@tonic-gate 	    "Kerberos mechanism library: Unable to get change password "
3157c478bd9Sstevel@tonic-gate 	    "service name for realm %s\n"), admin_realm);
3167c478bd9Sstevel@tonic-gate 	goto cleanup;
3177c478bd9Sstevel@tonic-gate    } else {
3187c478bd9Sstevel@tonic-gate 	ret=0;
3197c478bd9Sstevel@tonic-gate    }
3207c478bd9Sstevel@tonic-gate 
3217c478bd9Sstevel@tonic-gate    /* extract the string version of the principal */
3227c478bd9Sstevel@tonic-gate    if ((ret = krb5_unparse_name(context, client, &princ_str)))
3237c478bd9Sstevel@tonic-gate 	goto cleanup;
3247c478bd9Sstevel@tonic-gate 
3257c478bd9Sstevel@tonic-gate    ret = kadm5_init_with_password(princ_str, pw0array, cpw_service,
32654925bf6Swillf 	&params, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL,
32754925bf6Swillf 	&server_handle);
3287c478bd9Sstevel@tonic-gate 
3297c478bd9Sstevel@tonic-gate    if (ret != 0) {
3307c478bd9Sstevel@tonic-gate 	goto cleanup;
3317c478bd9Sstevel@tonic-gate    }
3327c478bd9Sstevel@tonic-gate 
3337c478bd9Sstevel@tonic-gate    prompt[0].prompt = "Enter new password";
3347c478bd9Sstevel@tonic-gate    prompt[0].hidden = 1;
3357c478bd9Sstevel@tonic-gate    prompt[0].reply = &pw0;
3367c478bd9Sstevel@tonic-gate    prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
3377c478bd9Sstevel@tonic-gate 
3387c478bd9Sstevel@tonic-gate    prompt[1].prompt = "Enter it again";
3397c478bd9Sstevel@tonic-gate    prompt[1].hidden = 1;
3407c478bd9Sstevel@tonic-gate    prompt[1].reply = &pw1;
3417c478bd9Sstevel@tonic-gate    prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
3427c478bd9Sstevel@tonic-gate 
343159d09a2SMark Phalan    strcpy(banner, "Password expired.  You must change it now.");
3447c478bd9Sstevel@tonic-gate 
3457c478bd9Sstevel@tonic-gate    for (tries = 3; tries; tries--) {
3467c478bd9Sstevel@tonic-gate       pw0.length = sizeof(pw0array);
3477c478bd9Sstevel@tonic-gate       pw1.length = sizeof(pw1array);
3487c478bd9Sstevel@tonic-gate 
3497c478bd9Sstevel@tonic-gate       /* PROMPTER_INVOCATION */
3507c478bd9Sstevel@tonic-gate       krb5int_set_prompt_types(context, prompt_types);
351505d05c7Sgtb       if ((ret = ((*prompter)(context, data, 0, banner,
352159d09a2SMark Phalan 			      sizeof(prompt)/sizeof(prompt[0]), prompt))))
3537c478bd9Sstevel@tonic-gate 	 goto cleanup;
3547c478bd9Sstevel@tonic-gate       krb5int_set_prompt_types(context, 0);
3557c478bd9Sstevel@tonic-gate 
3567c478bd9Sstevel@tonic-gate 
3577c478bd9Sstevel@tonic-gate       if (strcmp(pw0.data, pw1.data) != 0) {
3587c478bd9Sstevel@tonic-gate 	 ret = KRB5_LIBOS_BADPWDMATCH;
3597c478bd9Sstevel@tonic-gate 	 sprintf(banner, "%s.  Please try again.", error_message(ret));
3607c478bd9Sstevel@tonic-gate       } else if (pw0.length == 0) {
3617c478bd9Sstevel@tonic-gate 	 ret = KRB5_CHPW_PWDNULL;
3627c478bd9Sstevel@tonic-gate 	 sprintf(banner, "%s.  Please try again.", error_message(ret));
3637c478bd9Sstevel@tonic-gate       } else {
3647c478bd9Sstevel@tonic-gate 	 int result_code;
365159d09a2SMark Phalan 	 krb5_data code_string;
366159d09a2SMark Phalan 	 krb5_data result_string;
3677c478bd9Sstevel@tonic-gate 
368159d09a2SMark Phalan 	 if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
369159d09a2SMark Phalan 					 &result_code, &code_string,
370159d09a2SMark Phalan 					 &result_string)))
371159d09a2SMark Phalan 	    goto cleanup;
3727c478bd9Sstevel@tonic-gate 
3737c478bd9Sstevel@tonic-gate 	 /* the change succeeded.  go on */
3747c478bd9Sstevel@tonic-gate 
3757c478bd9Sstevel@tonic-gate 	 if (result_code == 0) {
376159d09a2SMark Phalan 	    krb5_xfree(result_string.data);
3777c478bd9Sstevel@tonic-gate 	    break;
3787c478bd9Sstevel@tonic-gate 	 }
3797c478bd9Sstevel@tonic-gate 
3807c478bd9Sstevel@tonic-gate 	 /* set this in case the retry loop falls through */
3817c478bd9Sstevel@tonic-gate 
3827c478bd9Sstevel@tonic-gate 	 ret = KRB5_CHPW_FAIL;
3837c478bd9Sstevel@tonic-gate 
3847c478bd9Sstevel@tonic-gate 	 if (result_code != KRB5_KPASSWD_SOFTERROR) {
385159d09a2SMark Phalan 	    krb5_xfree(result_string.data);
3867c478bd9Sstevel@tonic-gate 	    goto cleanup;
3877c478bd9Sstevel@tonic-gate 	 }
388159d09a2SMark Phalan 
389159d09a2SMark Phalan 	 /* the error was soft, so try again */
390159d09a2SMark Phalan 
391159d09a2SMark Phalan 	 /* 100 is I happen to know that no code_string will be longer
392159d09a2SMark Phalan 	    than 100 chars */
393159d09a2SMark Phalan 
394159d09a2SMark Phalan 	 if (result_string.length > (sizeof(banner)-100))
395159d09a2SMark Phalan 	    result_string.length = sizeof(banner)-100;
396159d09a2SMark Phalan 
397159d09a2SMark Phalan 	 sprintf(banner, "%.*s%s%.*s.  Please try again.\n",
398159d09a2SMark Phalan 		 (int) code_string.length, code_string.data,
399159d09a2SMark Phalan 		 result_string.length ? ": " : "",
400159d09a2SMark Phalan 		 (int) result_string.length,
401159d09a2SMark Phalan 		 result_string.data ? result_string.data : "");
402159d09a2SMark Phalan 
403159d09a2SMark Phalan 	 krb5_xfree(code_string.data);
404159d09a2SMark Phalan 	 krb5_xfree(result_string.data);
4057c478bd9Sstevel@tonic-gate       }
4067c478bd9Sstevel@tonic-gate    }
4077c478bd9Sstevel@tonic-gate 
4087c478bd9Sstevel@tonic-gate    if (ret)
4097c478bd9Sstevel@tonic-gate       goto cleanup;
4107c478bd9Sstevel@tonic-gate 
4117c478bd9Sstevel@tonic-gate    /* the password change was successful.  Get an initial ticket
4127c478bd9Sstevel@tonic-gate       from the master.  this is the last try.  the return from this
4137c478bd9Sstevel@tonic-gate       is final.  */
4147c478bd9Sstevel@tonic-gate 
4157c478bd9Sstevel@tonic-gate    ret = krb5_get_init_creds(context, creds, client, prompter, data,
416159d09a2SMark Phalan 			     start_time, in_tkt_service, opte,
4177c478bd9Sstevel@tonic-gate 			     krb5_get_as_key_password, (void *) &pw0,
418505d05c7Sgtb 			     &use_master, &as_reply);
4197c478bd9Sstevel@tonic-gate 
4207c478bd9Sstevel@tonic-gate cleanup:
4215e01956fSGlenn Barry    if (err_msg_1)
4225e01956fSGlenn Barry      free((void *)err_msg_1);
4235e01956fSGlenn Barry 
4247c478bd9Sstevel@tonic-gate    krb5int_set_prompt_types(context, 0);
4257c478bd9Sstevel@tonic-gate    /* if getting the password was successful, then check to see if the
4267c478bd9Sstevel@tonic-gate       password is about to expire, and warn if so */
4277c478bd9Sstevel@tonic-gate 
4287c478bd9Sstevel@tonic-gate    if (ret == 0) {
4297c478bd9Sstevel@tonic-gate       krb5_timestamp now;
4307c478bd9Sstevel@tonic-gate       krb5_last_req_entry **last_req;
4317c478bd9Sstevel@tonic-gate       int hours;
4327c478bd9Sstevel@tonic-gate 
4337c478bd9Sstevel@tonic-gate       /* XXX 7 days should be configurable.  This is all pretty ad hoc,
4347c478bd9Sstevel@tonic-gate 	 and could probably be improved if I was willing to screw around
4357c478bd9Sstevel@tonic-gate 	 with timezones, etc. */
4367c478bd9Sstevel@tonic-gate 
4377c478bd9Sstevel@tonic-gate       if (prompter &&
4387c478bd9Sstevel@tonic-gate 	  (in_tkt_service && cpw_service &&
4397c478bd9Sstevel@tonic-gate 	   (strcmp(in_tkt_service, cpw_service) != 0)) &&
4407c478bd9Sstevel@tonic-gate 	  ((ret = krb5_timeofday(context, &now)) == 0) &&
4417c478bd9Sstevel@tonic-gate 	  as_reply->enc_part2->key_exp &&
4427c478bd9Sstevel@tonic-gate 	  ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
4437c478bd9Sstevel@tonic-gate 	  (hours >= 0)) {
4447c478bd9Sstevel@tonic-gate 	 if (hours < 1)
4457c478bd9Sstevel@tonic-gate 	    sprintf(banner,
4467c478bd9Sstevel@tonic-gate 		    "Warning: Your password will expire in less than one hour.");
4477c478bd9Sstevel@tonic-gate 	 else if (hours <= 48)
4487c478bd9Sstevel@tonic-gate 	    sprintf(banner, "Warning: Your password will expire in %d hour%s.",
4497c478bd9Sstevel@tonic-gate 		    hours, (hours == 1)?"":"s");
4507c478bd9Sstevel@tonic-gate 	 else
4517c478bd9Sstevel@tonic-gate 	    sprintf(banner, "Warning: Your password will expire in %d days.",
4527c478bd9Sstevel@tonic-gate 		    hours/24);
4537c478bd9Sstevel@tonic-gate 
4547c478bd9Sstevel@tonic-gate 	 /* ignore an error here */
4557c478bd9Sstevel@tonic-gate          /* PROMPTER_INVOCATION */
4567c478bd9Sstevel@tonic-gate 	 (*prompter)(context, data, 0, banner, 0, 0);
457159d09a2SMark Phalan       } else if (prompter &&
458159d09a2SMark Phalan 		 (!in_tkt_service ||
459159d09a2SMark Phalan 		  (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
460159d09a2SMark Phalan 		 as_reply->enc_part2 && as_reply->enc_part2->last_req) {
461159d09a2SMark Phalan 	 /*
462159d09a2SMark Phalan 	  * Check the last_req fields
463159d09a2SMark Phalan 	  */
464159d09a2SMark Phalan 
465159d09a2SMark Phalan 	 for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
466159d09a2SMark Phalan 	    if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
467159d09a2SMark Phalan 		(*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
468159d09a2SMark Phalan 	       krb5_deltat delta;
469159d09a2SMark Phalan 	       char ts[256];
470159d09a2SMark Phalan 
471159d09a2SMark Phalan 	       if ((ret = krb5_timeofday(context, &now)))
472159d09a2SMark Phalan 		  break;
473159d09a2SMark Phalan 
474159d09a2SMark Phalan 	       if ((ret = krb5_timestamp_to_string((*last_req)->value,
475159d09a2SMark Phalan 						   ts, sizeof(ts))))
476159d09a2SMark Phalan 		  break;
477159d09a2SMark Phalan 
478159d09a2SMark Phalan 	       delta = (*last_req)->value - now;
479159d09a2SMark Phalan 
480159d09a2SMark Phalan 	       if (delta < 3600)
481159d09a2SMark Phalan 		  sprintf(banner,
482159d09a2SMark Phalan 		    "Warning: Your password will expire in less than one "
483159d09a2SMark Phalan 		     "hour on %s", ts);
484159d09a2SMark Phalan 	       else if (delta < 86400*2)
485159d09a2SMark Phalan 		  sprintf(banner,
486159d09a2SMark Phalan 		     "Warning: Your password will expire in %d hour%s on %s",
487159d09a2SMark Phalan 		     delta / 3600, delta < 7200 ? "" : "s", ts);
488159d09a2SMark Phalan 	       else
489159d09a2SMark Phalan 		  sprintf(banner,
490159d09a2SMark Phalan 		     "Warning: Your password will expire in %d days on %s",
491159d09a2SMark Phalan 		     delta / 86400, ts);
492159d09a2SMark Phalan 	       /* ignore an error here */
493159d09a2SMark Phalan 	       /* PROMPTER_INVOCATION */
494159d09a2SMark Phalan 	       (*prompter)(context, data, 0, banner, 0, 0);
495159d09a2SMark Phalan 	    }
496159d09a2SMark Phalan       }
4977c478bd9Sstevel@tonic-gate    }
4987c478bd9Sstevel@tonic-gate 
4997c478bd9Sstevel@tonic-gate    free(cpw_service);
5007c478bd9Sstevel@tonic-gate    free(princ_str);
50133a8868fSWill Fiveash    if (opte && krb5_gic_opt_is_shadowed(opte))
50233a8868fSWill Fiveash       krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
5037c478bd9Sstevel@tonic-gate    memset(pw0array, 0, sizeof(pw0array));
5047c478bd9Sstevel@tonic-gate    memset(pw1array, 0, sizeof(pw1array));
5057c478bd9Sstevel@tonic-gate    krb5_free_cred_contents(context, &chpw_creds);
5063125ebfcSsemery    /*
5073125ebfcSsemery     * Solaris Kerberos:
5083125ebfcSsemery     * Argument, ptr_as_reply, being returned to caller if success and non-NULL.
5093125ebfcSsemery     */
5103125ebfcSsemery    if (as_reply != NULL) {
5113125ebfcSsemery 	if (ptr_as_reply == NULL)
5123125ebfcSsemery       	   krb5_free_kdc_rep(context, as_reply);
5133125ebfcSsemery 	else
5143125ebfcSsemery 	   *ptr_as_reply = as_reply;
5153125ebfcSsemery    }
5167c478bd9Sstevel@tonic-gate 
5177c478bd9Sstevel@tonic-gate    return(ret);
5187c478bd9Sstevel@tonic-gate }
krb5int_populate_gic_opt(krb5_context context,krb5_gic_opt_ext ** opte,krb5_flags options,krb5_address * const * addrs,krb5_enctype * ktypes,krb5_preauthtype * pre_auth_types,krb5_creds * creds)519159d09a2SMark Phalan krb5_error_code krb5int_populate_gic_opt (
520159d09a2SMark Phalan     krb5_context context, krb5_gic_opt_ext **opte,
521505d05c7Sgtb     krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes,
522505d05c7Sgtb     krb5_preauthtype *pre_auth_types, krb5_creds *creds)
523505d05c7Sgtb {
524505d05c7Sgtb   int i;
525505d05c7Sgtb   krb5_int32 starttime;
526*d3b5f563SJohn Levon   krb5_get_init_creds_opt opt;
527159d09a2SMark Phalan 
528505d05c7Sgtb 
529*d3b5f563SJohn Levon     krb5_get_init_creds_opt_init(&opt);
530505d05c7Sgtb     if (addrs)
531*d3b5f563SJohn Levon       krb5_get_init_creds_opt_set_address_list(&opt, (krb5_address **) addrs);
532505d05c7Sgtb     if (ktypes) {
533505d05c7Sgtb 	for (i=0; ktypes[i]; i++);
534505d05c7Sgtb 	if (i)
535*d3b5f563SJohn Levon 	    krb5_get_init_creds_opt_set_etype_list(&opt, ktypes, i);
536505d05c7Sgtb     }
537505d05c7Sgtb     if (pre_auth_types) {
538505d05c7Sgtb 	for (i=0; pre_auth_types[i]; i++);
539505d05c7Sgtb 	if (i)
540*d3b5f563SJohn Levon 	    krb5_get_init_creds_opt_set_preauth_list(&opt, pre_auth_types, i);
541505d05c7Sgtb     }
542505d05c7Sgtb     if (options&KDC_OPT_FORWARDABLE)
543*d3b5f563SJohn Levon 	krb5_get_init_creds_opt_set_forwardable(&opt, 1);
544*d3b5f563SJohn Levon     else krb5_get_init_creds_opt_set_forwardable(&opt, 0);
545505d05c7Sgtb     if (options&KDC_OPT_PROXIABLE)
546*d3b5f563SJohn Levon 	krb5_get_init_creds_opt_set_proxiable(&opt, 1);
547*d3b5f563SJohn Levon     else krb5_get_init_creds_opt_set_proxiable(&opt, 0);
548505d05c7Sgtb     if (creds && creds->times.endtime) {
549505d05c7Sgtb         krb5_timeofday(context, &starttime);
550