From cf8e7437ecde1c4ca79fd2985085e525c86e7aaa Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Mar 11 2004 23:59:19 +0000 Subject: * pam_krb5afs.c: recognize bare boolean options as enabling them. * pam_krb5afs.c: recognize numeric options on the command line correctly. * pam_krb5afs.c: obtain v5 and v4 stash credentials with the new password after successful password change operations. * pam_krb5afs.c: don't warn about expired passwords if we were passed the "no_warn" option on the command line OR the PAM_SILENT flag was passed to the function (previously, only PAM_SILENT was honored). --- diff --git a/ChangeLog b/ChangeLog index 873cd43..1c2b259 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2004-03-11 nalin + * pam_krb5afs.c: recognize bare boolean options as enabling them. + * pam_krb5afs.c: recognize numeric options on the command line + correctly. + * pam_krb5afs.c: obtain v5 and v4 stash credentials with the new + password after successful password change operations. + * pam_krb5afs.c: don't warn about expired passwords if we were passed + the "no_warn" option on the command line OR the PAM_SILENT flag was + passed to the function (previously, only PAM_SILENT was honored). + 2003-08-14 nalin * pam_krb5afs.c: fix a syntax error on systems which provide krb5_os_hostaddr, krb5_os_localaddr and so on. diff --git a/configure.in b/configure.in index 1173de7..7d876d7 100644 --- a/configure.in +++ b/configure.in @@ -122,6 +122,7 @@ if test x$have_krb_h != x; then AC_CHECK_LIB(krb,in_tkt))) AC_CHECK_FUNC(krb524_convert_creds_kdc,, AC_CHECK_LIB(krb524,krb524_convert_creds_kdc)) + AC_CHECK_FUNCS(krb524_convert_creds_kdc krb5_524_convert_creds) AC_MSG_CHECKING([whether we are using KTH Kerberos IV]) AC_TRY_COMPILE([#include <$have_krb_h>], [ char *tmp = krb4_version; ], diff --git a/pam_krb5afs.c b/pam_krb5afs.c index 55e6c6f..ee777db 100644 --- a/pam_krb5afs.c +++ b/pam_krb5afs.c @@ -3,7 +3,7 @@ Kerberos 5 ticket to a Kerberos 4 ticket, and use it to grab AFS tokens for specified cells if possible. - Copyright 2000-2003 Red Hat, Inc. + Copyright 2000-2004 Red Hat, Inc. Portions Copyright 1999 Nalin Dahyabhai. This is free software; you can redistribute it and/or modify it @@ -201,6 +201,7 @@ struct config { int setcred; int user_check; int validate; + int warn_expiry; krb5_get_init_creds_opt creds_opt; int ticket_lifetime; int renew_lifetime; @@ -678,6 +679,13 @@ appdefault_boolean(krb5_context context, const char *option, found = TRUE; } } + /* A bare option name means the option is enabled. */ + if (buflen > 0) { + if (strcmp(argv[i], option) == 0) { + *ret_value = TRUE; + found = TRUE; + } + } /* Check for a match with "no_" prefixed, which negates it. */ if (strncmp(argv[i], "no_", 3) == 0) { if (strcmp(argv[i] + 3, option) == 0) { @@ -739,8 +747,9 @@ appdefault_integer(krb5_context context, const char *option, for (i = 0; i < argc; i++) { if (strncmp(argv[i], buf, buflen) == 0) { ival = strtol(argv[i] + buflen, &p, 0); - if ((p == NULL) || (*p == '\0')) { + if ((p != NULL) && (*p == '\0')) { *ret_value = ival; + found = TRUE; } } } @@ -1008,6 +1017,11 @@ get_config(krb5_context context, int argc, const char **argv) FALSE, &ret->validate); DEBUG("validate %s", ret->validate ? "true" : "false"); + /* Whether or not to warn about expired passwords. */ + appdefault_boolean(context, "warn", argc, argv, + TRUE, &ret->warn_expiry); + DEBUG("warn %s", ret->warn_expiry ? "true" : "false"); + /* Warning period before the user's password expires. */ appdefault_integer(context, "warn_period", argc, argv, 604800, &i); ret->warn_period = i; @@ -1016,10 +1030,6 @@ get_config(krb5_context context, int argc, const char **argv) /* Parse the rest of the arguments which don't fit the above * scheme very well. */ for (i = 0; i < argc; i++) { - /* Required argument that we don't use but need to recognize.*/ - if (strcmp(argv[i], "no_warn") == 0) { - continue; - } /* Try the first password. */ if (strcmp(argv[i], "try_first_pass") == 0) { ret->try_first_pass = 1; @@ -1368,6 +1378,275 @@ get_pw(const char *user, uid_t *uid, gid_t *gid) } #endif +#if (defined(HAVE_LIBKRB524) && defined(HAVE_KRB524_CONVERT_CREDS_KDC)) || \ + defined(HAVE_KRB5_524_CONVERT_CREDS) +static int +get_v4_tgt_conv(krb5_context context, krb5_principal principal, + krb5_ccache ccache, krb5_creds *creds, + struct config *config, CREDENTIALS *v4_creds) +{ + int i, enctypes[] = {ENCTYPE_DES_CBC_CRC,}, ret, fd; + char ccfile[PATH_MAX], ccname[PATH_MAX + 5]; + krb5_ccache use_ccache; + krb5_creds mcreds, *tmp; + + ret = -1; + + if (ccache == NULL) { + use_ccache = NULL; + strcpy(ccfile, "/tmp/pam_krb5_XXXXXX"); + fd = mkstemp(ccfile); + if (fd == -1) { + INFO("error creating temporary file: %s", + strerror(errno)); + return -1; + } + sprintf(ccname, "FILE:%s", ccfile); + if (krb5_cc_resolve(context, ccname, &use_ccache) != KSUCCESS) { + unlink(ccfile); + close(fd); + INFO("error resolving file to ccache"); + return -1; + } + if (krb5_cc_initialize(context, use_ccache, principal) != KSUCCESS) { + krb5_cc_destroy(context, use_ccache); + unlink(ccfile); + close(fd); + INFO("error creating temporary ccache"); + return -1; + } + krb5_cc_store_cred(context, use_ccache, creds); + INFO("created temporary ccache"); + close(fd); + } else { + use_ccache = ccache; + } + tmp = NULL; + for (i = 0; + i < sizeof(enctypes) / sizeof(enctypes[0]); + i++) { + mcreds = *creds; + mcreds.keyblock.enctype = enctypes[i]; + if (krb5_get_credentials(context, 0, use_ccache, + &mcreds, &tmp) == KSUCCESS) { + break; + } + } + if (i >= sizeof(enctypes) / sizeof(enctypes[0])) { + INFO("unable to obtain v4-compatible TGT"); + } + if (tmp != NULL) { + if (krb524_convert_creds_kdc(context, + tmp, + v4_creds) == 0) { + ret = 0; + } + krb5_free_creds(context, tmp); + } + if (use_ccache != ccache) { + krb5_cc_destroy(context, use_ccache); + } + return ret; +} +#else +static int +get_v4_tgt_conv(krb5_context context, krb5_principal principal, + krb5_ccache ccache, krb5_creds *creds, + struct config *config, CREDENTIALS *v4_creds) +{ + return -1; +} +#endif + +static int +get_v4_tgt_init(char *principal, char *instance, char *realm, + char *password, struct config *config, CREDENTIALS *v4_creds) +{ + char v4name[ANAME_SZ], v4inst[INST_SZ], v4realm[REALM_SZ]; + char sname[ANAME_SZ], sinst[INST_SZ]; + + KTEXT_ST ciphertext_st; + KTEXT ciphertext = &ciphertext_st; + des_cblock key; + des_key_schedule key_schedule; + int k4rc; + + /* Request a TGT for this realm. */ + memset(v4name, '\0', sizeof(v4name)); + strncpy(v4name, principal, sizeof(v4name) - 1); + memset(v4inst, '\0', sizeof(v4inst)); + strncpy(v4inst, instance, sizeof(v4inst) - 1); + memset(v4realm, '\0', sizeof(v4realm)); + strncpy(v4realm, realm, sizeof(v4realm) - 1); + memset(sname, '\0', sizeof(sname)); + strncpy(sname, "krbtgt", sizeof(sname) - 1); + memset(sinst, '\0', sizeof(sinst)); + strncpy(sinst, realm, sizeof(sinst) - 1); + + /* Note: the lifetime is measured in multiples of 5m. */ + k4rc = INTK_ERR; +#ifdef HAVE_KRB_MK_IN_TKT_PREAUTH + if (k4rc != KSUCCESS) { + k4rc = krb_mk_in_tkt_preauth(v4name, + v4inst, + v4realm, + sname, sinst, + config->ticket_lifetime + / 60 / 5, + NULL, 0, + ciphertext); + } +#endif +#ifdef HAVE_KRB_MK_IN_TKT + if (k4rc != KSUCCESS) { + k4rc = krb_mk_in_tkt(v4name, + v4inst, + v4realm, + sname, sinst, + config->ticket_lifetime + / 60 / 5, + NULL, 0, + ciphertext); + } +#endif + if (k4rc != KSUCCESS) { + INFO("couldn't get v4 TGT for %s%s%s@%s (%s), " + "continuing", v4name, + strlen(v4inst) ? ".": "", v4inst, v4realm, + krb_get_err_text(k4rc)); + } + if (k4rc == KSUCCESS) { + unsigned char *p = ciphertext->dat; + int len; + + /* Convert the password to a v4 key. */ + des_string_to_key(password, key); + des_key_sched(key, key_schedule); + + /* Decrypt the TGT. */ + k4rc = des_pcbc_encrypt((C_Block*)ciphertext->dat, + (C_Block*)ciphertext->dat, + ciphertext->length, + key_schedule, + (C_Block*)key, + 0); + if (k4rc == KSUCCESS) { + memset(key, 0, sizeof(key)); + memset(key_schedule, 0, sizeof(key_schedule)); + + /* Decompose the returned data. Now I know + * why Kerberos 5 uses ASN.1 encoding.... */ + memset(v4_creds, 0, sizeof(*v4_creds)); + + /* Initial values. */ + strncpy(v4_creds->pname, v4name, + sizeof(v4_creds->pname) - 1); + strncpy(v4_creds->pinst, v4inst, + sizeof(v4_creds->pinst) - 1); + + /* Session key. */ + len = ciphertext->length; + DEBUG("ciphertext length in TGT = %d", len); + + memcpy(v4_creds->session, p, 8); + p += 8; + len -= 8; + + /* Service name. */ + if (xstrnlen(p, len) > 0) { + strncpy(v4_creds->service, p, + sizeof(v4_creds->service) - 1); + } else { + INFO("service name in v4 TGT too long: " + "%.8s", p); + } + p += (strlen(v4_creds->service) + 1); + len -= (strlen(v4_creds->service) + 1); + + /* Service instance. */ + if (xstrnlen(p, len) > 0) { + strncpy(v4_creds->instance, p, + sizeof(v4_creds->instance) - 1); + } + p += (strlen(v4_creds->instance) + 1); + len -= (strlen(v4_creds->instance) + 1); + + /* Service realm. */ + if (xstrnlen(p, len) > 0) { + strncpy(v4_creds->realm, p, + sizeof(v4_creds->realm) - 1); + } + p += (strlen(v4_creds->realm) + 1); + len -= (strlen(v4_creds->realm) + 1); + + /* Lifetime, kvno, length. */ + if (len >= 3) { + v4_creds->lifetime = p[0]; + v4_creds->kvno = p[1]; + v4_creds->ticket_st.length = p[2]; + } + p += 3; + len -= 3; + + /* Ticket data. */ + if (len >= v4_creds->ticket_st.length) { + memcpy(v4_creds->ticket_st.dat, p, + v4_creds->ticket_st.length); + } + p += v4_creds->ticket_st.length; + len -= v4_creds->ticket_st.length; + + /* Timestamp. */ + if (len >= 4) { + memcpy(&v4_creds->issue_date, p, 4); + /* We can't tell if we need to byte-swap + * or not, so just make up an issue date + * that looks reasonable. */ + v4_creds->issue_date = time(NULL); + } + p += 4; + len -= 4; + + DEBUG("Got v4 TGT for `%s%s%s@%s'", + v4_creds->service, + strlen(v4_creds->instance) ? + "." : "", + v4_creds->instance, + v4_creds->realm); + + /* Sanity checks. */ + if (len != 0) { + INFO("Got %d extra bytes in v4 TGT", + ciphertext->length - len); + DEBUG("Extra data = %c%c%c%c%c%c%c%c", + p[0], p[1], p[2], p[3], + p[4], p[5], p[6], p[7]); + DEBUG("Extra data = %c%c%c%c%c%c%c%c", + p[9], p[10], p[11], p[12], + p[13], p[14], p[15], p[16]); + } + } + } + return k4rc; +} + +static int +get_v4_tgt_init_v5(krb5_context context, krb5_principal principal, + char *password, struct config *config, CREDENTIALS *v4_creds) +{ + int k4rc; + char v4name[ANAME_SZ], v4inst[INST_SZ], v4realm[REALM_SZ]; + + k4rc = krb5_524_conv_principal(context, principal, + v4name, v4inst, v4realm); + if (k4rc == KSUCCESS) { + return get_v4_tgt_init(v4name, v4inst, v4realm, password, + config, v4_creds); + } + + return k4rc; +} + /* Big authentication module. */ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) @@ -1538,8 +1817,8 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) /* If we don't have a password, and we're not configured to * prompt for one, we're done. */ if ((password == NULL) && - (config->try_first_pass) && - (!config->try_second_pass)) { + (config->try_first_pass) && + (!config->try_second_pass)) { authenticated = TRUE; krc = KRB5_LIBOS_CANTREADPWD; } @@ -1561,7 +1840,8 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) stash->have_v5_creds = TRUE; } else if (krc == KRB5KDC_ERR_NAME_EXP) { - if (!(flags & PAM_SILENT)) { + if (!(flags & PAM_SILENT) && + config->warn_expiry) { pam_prompt_for(pamh, PAM_ERROR_MSG, "Account expired. Please contact your system administrator.", @@ -1582,7 +1862,9 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) PASSWORD_CHANGING_SERVICE, NULL); if (krc == KRB5_SUCCESS) { - if (!(flags & PAM_SILENT)) { + stash->have_v5_creds = TRUE; + if (!(flags & PAM_SILENT) && + config->warn_expiry) { pam_prompt_for(pamh, PAM_ERROR_MSG, "Password expired. You must change it now.", @@ -1644,7 +1926,9 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) PASSWORD_CHANGING_SERVICE, NULL); if (krc == KRB5_SUCCESS) { - if (!(flags & PAM_SILENT)) { + stash->have_v5_creds = TRUE; + if (!(flags & PAM_SILENT) && + config->warn_expiry) { pam_prompt_for(pamh, PAM_ERROR_MSG, "Password expired. You must change it now.", @@ -1717,178 +2001,21 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) /* Get Kerberos IV credentials if we are supposed to. */ if (RC_OK && config->krb4_convert && stash->have_v5_creds) { const void *goodpass = NULL; - char v4name[ANAME_SZ], v4inst[INST_SZ], v4realm[REALM_SZ]; - char sname[ANAME_SZ], sinst[INST_SZ]; /* Get the authtok that succeeded. We may need it. */ pam_get_item(pamh, PAM_AUTHTOK, &goodpass); - memset(v4name, '\0', sizeof(v4name)); - memset(v4inst, '\0', sizeof(v4inst)); - memset(v4realm, '\0', sizeof(v4realm)); - memset(sname, '\0', sizeof(sname)); - memset(sinst, '\0', sizeof(sinst)); - - if (krb5_524_conv_principal(context, principal, v4name, v4inst, - v4realm) == KSUCCESS) { - KTEXT_ST ciphertext_st; - KTEXT ciphertext = &ciphertext_st; - des_cblock key; - des_key_schedule key_schedule; - int k4rc; - - /* Request a TGT for this realm. */ - strncpy(sname, "krbtgt", sizeof(sname) - 1); - strncpy(sinst, realm, sizeof(sinst) - 1); - - /* Note: the lifetime is measured in multiples of 5m. */ - k4rc = INTK_ERR; -#ifdef HAVE_KRB_MK_IN_TKT_PREAUTH - if (k4rc != KSUCCESS) { - k4rc = krb_mk_in_tkt_preauth(v4name, - v4inst, - v4realm, - sname, sinst, - config->ticket_lifetime - / 60 / 5, - NULL, 0, - ciphertext); - } -#endif -#ifdef HAVE_KRB_MK_IN_TKT - if (k4rc != KSUCCESS) { - k4rc = krb_mk_in_tkt(v4name, - v4inst, - v4realm, - sname, sinst, - config->ticket_lifetime - / 60 / 5, - NULL, 0, - ciphertext); - } -#endif - if (k4rc != KSUCCESS) { - INFO("couldn't get v4 TGT for %s%s%s@%s (%s), " - "continuing", v4name, - strlen(v4inst) ? ".": "", v4inst, v4realm, - krb_get_err_text(k4rc)); - } - if (k4rc == KSUCCESS) { - unsigned char *p = ciphertext->dat; - int len; - - /* Convert the password to a v4 key. */ - des_string_to_key((char*)goodpass, key); - des_key_sched(key, key_schedule); - - /* Decrypt the TGT. */ - des_pcbc_encrypt((C_Block*)ciphertext->dat, - (C_Block*)ciphertext->dat, - ciphertext->length, - key_schedule, - (C_Block*)key, - 0); - memset(key, 0, sizeof(key)); - memset(key_schedule, 0, sizeof(key_schedule)); - - /* Decompose the returned data. Now I know - * why Kerberos 5 uses ASN.1 encoding.... */ - memset(&stash->v4_creds, 0, - sizeof(stash->v4_creds)); - - /* Initial values. */ - strncpy((char*)&stash->v4_creds.pname, v4name, - sizeof(stash->v4_creds.pname) - 1); - strncpy((char*)&stash->v4_creds.pinst, v4inst, - sizeof(stash->v4_creds.pinst) - 1); - - /* Session key. */ - len = ciphertext->length; - DEBUG("ciphertext length in TGT = %d", len); - - memcpy(&stash->v4_creds.session, p, 8); - p += 8; - len -= 8; - - /* Service name. */ - if (xstrnlen(p, len) > 0) { - strncpy(stash->v4_creds.service, p, - sizeof(stash->v4_creds.service) - - 1); - } else { - INFO("service name in v4 TGT too long: " - "%.8s", p); - } - p += (strlen(stash->v4_creds.service) + 1); - len -= (strlen(stash->v4_creds.service) + 1); - - /* Service instance. */ - if (xstrnlen(p, len) > 0) { - strncpy(stash->v4_creds.instance, p, - sizeof(stash->v4_creds.instance) - - 1); - } - p += (strlen(stash->v4_creds.instance) + 1); - len -= (strlen(stash->v4_creds.instance) + 1); - - /* Service realm. */ - if (xstrnlen(p, len) > 0) { - strncpy(stash->v4_creds.realm, p, - sizeof(stash->v4_creds.realm) - - 1); - } - p += (strlen(stash->v4_creds.realm) + 1); - len -= (strlen(stash->v4_creds.realm) + 1); - - /* Lifetime, kvno, length. */ - if (len >= 3) { - stash->v4_creds.lifetime = p[0]; - stash->v4_creds.kvno = p[1]; - stash->v4_creds.ticket_st.length = p[2]; - } - p += 3; - len -= 3; - - /* Ticket data. */ - if (len >= stash->v4_creds.ticket_st.length) { - memcpy(stash->v4_creds.ticket_st.dat, p, - stash->v4_creds.ticket_st.length); - } - p += stash->v4_creds.ticket_st.length; - len -= stash->v4_creds.ticket_st.length; - - /* Timestamp. */ - if (len >= 4) { - memcpy(&stash->v4_creds.issue_date, - p, 4); - /* We can't tell if we need to byte-swap - * or not, so just make up an issue date - * that looks reasonable. */ - stash->v4_creds.issue_date = time(NULL); - } - p += 4; - len -= 4; - - - DEBUG("Got v4 TGT for `%s%s%s@%s'", - stash->v4_creds.service, - strlen(stash->v4_creds.instance) ? - "." : "", - stash->v4_creds.instance, - stash->v4_creds.realm); + /* First try to use krb524 to get one. */ + if (get_v4_tgt_conv(context, principal, NULL, + &stash->v5_creds, config, + &stash->v4_creds) == KSUCCESS) { + stash->have_v4_creds = TRUE; + } else { + /* Else try to use a v4 KDC to get one. */ + if (get_v4_tgt_init_v5(context, principal, + (char*) goodpass, config, + &stash->v4_creds) == KSUCCESS) { stash->have_v4_creds = TRUE; - - /* Sanity checks. */ - if (len != 0) { - INFO("Got %d extra bytes in v4 TGT", - ciphertext->length - len); - DEBUG("Extra data = %c%c%c%c%c%c%c%c", - p[0], p[1], p[2], p[3], - p[4], p[5], p[6], p[7]); - DEBUG("Extra data = %c%c%c%c%c%c%c%c", - p[9], p[10], p[11], p[12], - p[13], p[14], p[15], p[16]); - } } } } @@ -2027,6 +2154,7 @@ pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) prc = pam_get_data(pamh, stash_name, (void*)&stash); free(stash_name); stash_name = NULL; + if (prc == PAM_SUCCESS) { DEBUG("credentials retrieved"); @@ -2099,37 +2227,15 @@ pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) prc = PAM_CRED_UNAVAIL; } -#ifdef HAVE_LIBKRB524 +#if (defined(HAVE_LIBKRB524) && defined(HAVE_KRB524_CONVERT_CREDS_KDC)) || \ + defined(HAVE_KRB5_524_CONVERT_CREDS) /* Get Kerberos 4 credentials if we haven't already. */ if (RC_OK && config->krb4_convert) { if (!stash->have_v4_creds) { - DEBUG("converting credentials for `%s'", user); - - krc = krb524_convert_creds_kdc(context, - &stash->v5_creds, - &stash->v4_creds); - - DEBUG("krb524_convert_creds returned `%s' for " - "`%s'", - krc ? - error_message(krc) : - "Success", - user); - - if (krc == KRB5_SUCCESS) { - INFO("v4 ticket conversion succeeded " - "for `%s'", user); + if (get_v4_tgt_conv(context, principal, NULL, + &stash->v5_creds, config, + &stash->v4_creds) == KSUCCESS) { stash->have_v4_creds = TRUE; - } else { - /* This shouldn't happen. Either - * krb524d isn't running on the KDC or - * the module is misconfigured, or - * something weirder still happened: - * we succeeded. */ - CRIT("v4 ticket conversion failed for " - "`%s': %d (%s)", user, krc, - error_message(krc)); - krc = KRB5_SUCCESS; } } } @@ -2521,6 +2627,7 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) break; default: krc = KRB5_SUCCESS; + break; } } else { prc = convert_kerror(krc); @@ -2783,8 +2890,44 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) &result_string); if ((krc == KRB5_SUCCESS) && (result_code == KRB5_KPASSWD_SUCCESS)) { + char *stash_name, *unparsed_name = NULL; + struct stash *stash = NULL; + INFO("%s's %s password has been changed", user, config->banner); + /* Get a fresh TGT, in case this is a change- + * password-at-login situation. */ + if (krb5_unparse_name(context, principal, + &unparsed_name) != KSUCCESS) { + unparsed_name = NULL; + } + if (unparsed_name != NULL) { + stash_name = module_stash_name(unparsed_name); + pam_get_data(pamh, stash_name, + (void*)&stash); + free(stash_name); + stash_name = NULL; + } + if (stash != NULL) { + DEBUG("stash retrieved"); + krc = krb5_get_init_creds_password(context, + &stash->v5_creds, + principal, + (char*)authtok, + NULL, + NULL, + 0, + NULL, + &config->creds_opt); + if (krc == PAM_SUCCESS) { + DEBUG("obtained v5 TGT with new password"); + if (config->krb4_convert) { + if (get_v4_tgt_conv(context, principal, NULL, &stash->v5_creds, config, &stash->v4_creds) != 0) { + get_v4_tgt_init_v5(context, principal, authtok, config, &stash->v4_creds) != 0; + } + } + } + } } else { INFO("changing %s's %s password failed: %*s " "(%d: %*s)",