diff options
Diffstat (limited to 'usr/src/lib/pam_modules/krb5/krb5_authenticate.c')
-rw-r--r-- | usr/src/lib/pam_modules/krb5/krb5_authenticate.c | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/usr/src/lib/pam_modules/krb5/krb5_authenticate.c b/usr/src/lib/pam_modules/krb5/krb5_authenticate.c new file mode 100644 index 0000000000..862b2b4ac0 --- /dev/null +++ b/usr/src/lib/pam_modules/krb5/krb5_authenticate.c @@ -0,0 +1,781 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <security/pam_appl.h> +#include <security/pam_modules.h> +#include <security/pam_impl.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include <syslog.h> +#include <libintl.h> +#include <k5-int.h> +#include "profile/prof_int.h" +#include <netdb.h> +#include <ctype.h> +#include "utils.h" +#include "krb5_repository.h" + +#define PAMTXD "SUNW_OST_SYSOSPAM" +#define SLEEPTIME 4 + +#define KRB5_DEFAULT_OPTIONS 0 +#define QUIET 0 +#define VERBOSE 1 + +int forwardable_flag = 0; +int renewable_flag = 0; +int proxiable_flag = 0; +int no_address_flag = 0; +profile_options_boolean config_option[] = { + { "forwardable", &forwardable_flag, 0 }, + { "renewable", &renewable_flag, 0 }, + { "proxiable", &proxiable_flag, 0 }, + { "no_addresses", &no_address_flag, 0 }, + { NULL, NULL, 0 } +}; +char *renew_timeval; +char *life_timeval; +profile_option_strings config_times[] = { + { "max_life", &life_timeval, 0 }, + { "max_renewable_life", &renew_timeval, 0 }, + { NULL, NULL, 0 } +}; +char *realmdef[] = { "realms", NULL, NULL, NULL }; +char *appdef[] = { "appdefaults", "kinit", NULL }; + +#define krb_realm (*(realmdef + 1)) + +int attempt_krb5_auth(void *, krb5_module_data_t *, char *, char **, + boolean_t, boolean_t); +void krb5_cleanup(pam_handle_t *, void *, int); + +extern errcode_t profile_get_options_boolean(); +extern errcode_t profile_get_options_string(); +extern int krb5_verifypw(pam_handle_t *, char *, char *, boolean_t, int); +extern krb5_error_code krb5_verify_init_creds(krb5_context, + krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *, + krb5_verify_init_creds_opt *); + +/* + * pam_sm_authenticate - Authenticate user + */ +int +pam_sm_authenticate( + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + char *user; + struct pam_conv *pam_convp; + int err; + int result = PAM_AUTH_ERR; + /* pam.conf options */ + int debug = 0; + int warn = 1; + /* return an error on password expire */ + int err_on_exp = 0; + int invalid_user = 0; + int i; + char *firstpass = NULL; + char *password = NULL; + uid_t pw_uid; + krb5_module_data_t *kmd = NULL; + krb5_repository_data_t *krb5_data = NULL; + pam_repository_t *rep_data = NULL; + + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + } else if (strcmp(argv[i], "nowarn") == 0) { + warn = 0; + } else if (strcmp(argv[i], "err_on_exp") == 0) { + err_on_exp = 1; + } else { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth) unrecognized option %s"), + argv[i]); + } + } + if (flags & PAM_SILENT) warn = 0; + + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): pam_sm_authenticate flags=%d", + flags); + + err = pam_get_item(pamh, PAM_USER, (void**) &user); + if (err != PAM_SUCCESS) + return (err); + + /* Prompt for user name if it is not already available */ + if (user == NULL || !user[0]) { + if (debug) + syslog(LOG_DEBUG, "PAM-KRB5 (auth): user empty " + "or null"); + if ((err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) + return (err); + + if (user == NULL || !user[0]) + return (PAM_USER_UNKNOWN); + } + + err = pam_get_item(pamh, PAM_CONV, (void**) &pam_convp); + if (err != PAM_SUCCESS) + return (err); + + /* make sure a password entry exists for this user */ + if (!get_pw_uid(user, &pw_uid)) { + invalid_user = 1; + } + + /* + * pam_get_data could fail if we are being called for the first time + * or if the module is not found, PAM_NO_MODULE_DATA is not an error + */ + err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd); + if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA)) + return (PAM_AUTH_ERR); + + if (kmd == NULL) { + kmd = calloc(1, sizeof (krb5_module_data_t)); + if (kmd == NULL) { + result = PAM_BUF_ERR; + goto out; + } + + err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup); + if (err != PAM_SUCCESS) { + free(kmd); + result = err; + goto out; + } + } + + if (!kmd->env) { + char buffer[512]; + + if (snprintf(buffer, sizeof (buffer), + "%s=FILE:/tmp/krb5cc_%d", + KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) { + result = PAM_SYSTEM_ERR; + goto out; + } + + /* we MUST copy this to the heap for the putenv to work! */ + kmd->env = strdup(buffer); + if (!kmd->env) { + result = PAM_BUF_ERR; + goto out; + } else { + if (putenv(kmd->env)) { + result = PAM_SYSTEM_ERR; + goto out; + } + } + } + + kmd->auth_status = PAM_AUTH_ERR; + kmd->debug = debug; + kmd->warn = warn; + kmd->err_on_exp = err_on_exp; + kmd->ccache = NULL; + kmd->kcontext = NULL; + kmd->password = NULL; + kmd->age_status = PAM_SUCCESS; + (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); + + /* + * For apps that already did krb5 auth exchange... + * Now that we've created the kmd structure, we can + * return SUCCESS. 'kmd' may be needed later by other + * PAM functions, thats why we wait until this point to + * return. + */ + err = pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); + + if (rep_data != NULL) { + if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { + if (debug) + syslog(LOG_DEBUG, "PAM-KRB5 (auth): wrong" + "repository found (%s), returning " + "PAM_IGNORE", rep_data->type); + return (PAM_IGNORE); + } + if (rep_data->scope_len == sizeof (krb5_repository_data_t)) { + krb5_data = (krb5_repository_data_t *)rep_data->scope; + + if (krb5_data->flags == + SUNW_PAM_KRB5_ALREADY_AUTHENTICATED && + krb5_data->principal != NULL && + strlen(krb5_data->principal)) { + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): Principal " + "%s already authenticated", + krb5_data->principal); + kmd->auth_status = PAM_SUCCESS; + return (PAM_SUCCESS); + } + } + } + + /* + * if root key exists in the keytab, it's a random key so no + * need to prompt for pw and we just return IGNORE. + * + * note we don't need to force a prompt for pw as authtok_get + * is required to be stacked above this module. + */ + if ((strcmp(user, ROOT_UNAME) == 0) && + key_in_keytab(user, debug)) { + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): " + "key for '%s' in keytab, returning IGNORE", user); + result = PAM_IGNORE; + goto out; + } + + err = pam_get_item(pamh, PAM_AUTHTOK, (void **) &firstpass); + + if (firstpass != NULL && invalid_user) + goto out; + + result = attempt_krb5_auth(pamh, kmd, user, &firstpass, 1, QUIET); + if (result != PAM_AUTH_ERR) { + goto out; + } + + /* + * Get the password from the user + */ + + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): prompting for password"); + + /* + * one last ditch attempt to login to KRB5 + * + * we have to prompt for password even if the user does not exist + * so as not to reveal the existence/non-existence of an account + */ + result = attempt_krb5_auth(pamh, kmd, user, &password, 1, VERBOSE); + + if (invalid_user) + goto out; + + if (result == PAM_SUCCESS) { + /* + * Even if this password is expired, this saves + * us from having to enter it again! + */ + (void) pam_set_item(pamh, PAM_AUTHTOK, password); + } + +out: + + if (password != NULL) + (void) memset(password, 0, strlen(password)); + + if (invalid_user) + result = PAM_USER_UNKNOWN; + + if (kmd) { + + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): pam_sm_auth finalize" + " ccname env, result =%d, env ='%s'," + " age = %d, status = %d", + result, kmd->env ? kmd->env : "<null>", + kmd->age_status, kmd->auth_status); + + if (kmd->env && + !(kmd->age_status == PAM_NEW_AUTHTOK_REQD && + kmd->auth_status == PAM_SUCCESS)) { + + + if (result == PAM_SUCCESS) { + /* + * Put ccname into the pamh so that login + * apps can pick this up when they run + * pam_getenvlist(). + */ + if ((result = pam_putenv(pamh, kmd->env)) + != PAM_SUCCESS) { + /* should not happen but... */ + syslog(LOG_ERR, + dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth):" + " pam_putenv failed: result: %d"), + result); + goto cleanupccname; + } + } else { + cleanupccname: + /* for lack of a Solaris unputenv() */ + krb5_unsetenv(KRB5_ENV_CCNAME); + free(kmd->env); + kmd->env = NULL; + } + } + kmd->auth_status = result; + } + + if (debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result)); + + return (result); +} + +int +attempt_krb5_auth( + void *pamh, + krb5_module_data_t *kmd, + char *user, + char **krb5_pass, + boolean_t verify_tik, + boolean_t verbose) +{ + krb5_principal me = NULL; + krb5_principal server = NULL; + krb5_creds *my_creds; + krb5_timestamp now; + krb5_error_code code = 0; + char kuser[2*MAXHOSTNAMELEN]; + char passprompt[MAX_CANON]; + krb5_deltat lifetime; + krb5_deltat rlife; + krb5_deltat krb5_max_duration; + int options = KRB5_DEFAULT_OPTIONS; + krb5_data tgtname = { + 0, + KRB5_TGS_NAME_SIZE, + KRB5_TGS_NAME + }; + char krb5_auth_messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; + krb5_get_init_creds_opt opts; + /* + * "result" should not be assigned PAM_SUCCESS unless + * authentication has succeeded and there are no other errors. + * + * "code" is sometimes used for PAM codes, sometimes for krb5 + * codes. Be careful. + */ + int result = PAM_AUTH_ERR; + + if (kmd->debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'", + user ? user : "<null>"); + + krb5_get_init_creds_opt_init(&opts); + + /* need to free context with krb5_free_context */ + if (code = krb5_init_context(&kmd->kcontext)) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Error initializing " + "krb5: %s"), + error_message(code)); + return (PAM_SYSTEM_ERR); + } + + if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser, + 2*MAXHOSTNAMELEN)) != 0) { + /* get_kmd_kuser returns proper PAM error statuses */ + return (code); + } + + if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) { + krb5_free_context(kmd->kcontext); + kmd->kcontext = NULL; + return (PAM_AUTH_ERR); + } + + /* call krb5_free_cred_contents() on error */ + my_creds = &kmd->initcreds; + + if ((code = krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) + goto out_err; + + if (code = krb5_build_principal_ext(kmd->kcontext, &server, + krb5_princ_realm(kmd->kcontext, me)->length, + krb5_princ_realm(kmd->kcontext, me)->data, + tgtname.length, tgtname.data, + krb5_princ_realm(kmd->kcontext, me)->length, + krb5_princ_realm(kmd->kcontext, me)->data, 0)) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): attempt_krb5_auth: " + "krb5_build_princ_ext failed: %s"), + error_message(code)); + goto out; + } + + if (code = krb5_copy_principal(kmd->kcontext, server, + &my_creds->server)) { + goto out_err; + } + + if (code = krb5_timeofday(kmd->kcontext, &now)) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): attempt_krb5_auth: " + "krb5_timeofday failed: %s"), + error_message(code)); + goto out; + } + + /* + * set the values for lifetime and rlife to be the maximum + * possible + */ + krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60; + lifetime = krb5_max_duration; + rlife = krb5_max_duration; + + /* + * Let us get the values for various options + * from Kerberos configuration file + */ + + krb_realm = krb5_princ_realm(kmd->kcontext, me)->data; + profile_get_options_boolean(kmd->kcontext->profile, + realmdef, config_option); + profile_get_options_boolean(kmd->kcontext->profile, + appdef, config_option); + profile_get_options_string(kmd->kcontext->profile, + realmdef, config_times); + profile_get_options_string(kmd->kcontext->profile, + appdef, config_times); + + if (renew_timeval) { + code = krb5_string_to_deltat(renew_timeval, &rlife); + if (code != 0 || rlife == 0 || rlife > krb5_max_duration) { + syslog(LOG_ERR, + dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Bad max_renewable_life " + " value '%s' in Kerberos config file"), + renew_timeval); + result = PAM_SYSTEM_ERR; + goto out; + } + } + if (life_timeval) { + code = krb5_string_to_deltat(life_timeval, &lifetime); + if (code != 0 || lifetime == 0 || + lifetime > krb5_max_duration) { + syslog(LOG_ERR, + dgettext(TEXT_DOMAIN, "PAM-KRB5 (auth): Bad " + "lifetime value '%s' in Kerberos config file"), + life_timeval); + result = PAM_SYSTEM_ERR; + goto out; + } + } + /* start timer when request gets to KDC */ + my_creds->times.starttime = 0; + my_creds->times.endtime = now + lifetime; + + if (options & KDC_OPT_RENEWABLE) { + my_creds->times.renew_till = now + rlife; + } else + my_creds->times.renew_till = 0; + + krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime); + + if (proxiable_flag) { /* Set in config file */ + if (kmd->debug) + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Proxiable tickets " + "requested")); + krb5_get_init_creds_opt_set_proxiable(&opts, TRUE); + } + if (forwardable_flag) { + if (kmd->debug) + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Forwardable tickets " + "requested")); + krb5_get_init_creds_opt_set_forwardable(&opts, TRUE); + } + if (renewable_flag) { + if (kmd->debug) + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Renewable tickets " + "requested")); + krb5_get_init_creds_opt_set_renew_life(&opts, rlife); + } + if (no_address_flag) { + if (kmd->debug) + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): Addressless tickets " + "requested")); + krb5_get_init_creds_opt_set_address_list(&opts, NULL); + } + + if (*krb5_pass == NULL) { + (void) strlcpy(passprompt, dgettext(TEXT_DOMAIN, + "Enter Kerberos password for "), MAX_CANON); + (void) strlcat(passprompt, kuser, MAX_CANON); + (void) strlcat(passprompt, ": ", MAX_CANON); + /* + * Upon success do not assign the resulting value to "result", + * because this value is returned from this function when the + * subsequent functions could fail which may allow unauthorized + * login. + */ + code = __pam_get_authtok(pamh, PAM_PROMPT, PAM_AUTHTOK, + passprompt, krb5_pass); + if (code != PAM_SUCCESS) { + /* Set correct return value for use below. */ + result = code; + goto out; + } + } + + /* + * mech_krb5 interprets empty passwords as NULL passwords + * and tries to read a password from stdin. Since we are in + * pam this is bad and should not be allowed. + */ + if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } else { + code = krb5_get_init_creds_password(kmd->kcontext, + my_creds, + me, + *krb5_pass, /* clear text passwd */ + NULL, /* prompter */ + NULL, /* data */ + 0, /* start time */ + NULL, /* defaults to krbtgt@REALM */ + &opts); + } + + if (kmd->debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): attempt_krb5_auth: " + "krb5_get_init_creds_password returns: %s", + code == 0 ? "SUCCESS" : error_message(code)); + + switch (code) { + case 0: + /* got a tgt, let's verify it */ + if (verify_tik) { + krb5_verify_init_creds_opt vopts; + + krb5_verify_init_creds_opt_init(&vopts); + + code = krb5_verify_init_creds(kmd->kcontext, + my_creds, + NULL, /* defaults to host/localhost@REALM */ + NULL, + NULL, + &vopts); + + if (code) { + result = PAM_SYSTEM_ERR; + if (verbose) { + (void) snprintf(krb5_auth_messages[0], + sizeof (krb5_auth_messages[0]), + dgettext(TEXT_DOMAIN, + "authentication failed: " + "%s\n"), + error_message(code)); + (void) __pam_display_msg(pamh, + PAM_TEXT_INFO, 1, + krb5_auth_messages, NULL); + } + + syslog(LOG_ERR, + dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): " + "krb5_verify_init_creds failed: %s"), + error_message(code)); + } + } + break; + + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + /* + * Since this principal is not part of the local + * Kerberos realm, we just return PAM_USER_UNKNOWN. + */ + result = PAM_USER_UNKNOWN; + + if (kmd->debug) + syslog(LOG_DEBUG, "PAM-KRB5 (auth): attempt_krb5_auth:" + " User is not part of the local Kerberos" + " realm: %s", error_message(code)); + break; + + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + /* + * We could be trying the password from a previous + * pam authentication module, but we don't want to + * generate an error if the unix password is different + * than the Kerberos password... + */ + if (verbose) { + (void) snprintf(krb5_auth_messages[0], + sizeof (krb5_auth_messages[0]), + dgettext(TEXT_DOMAIN, + "Kerberos authentication failed\n")); + (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, + krb5_auth_messages, NULL); + } + break; + + case KRB5KDC_ERR_KEY_EXP: + if (!kmd->err_on_exp) { + /* + * Request a tik for changepw service + * and it will tell us if pw is good or not. + */ + code = krb5_verifypw(pamh, kuser, *krb5_pass, + 0, kmd->debug); + + if (kmd->debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): attempt_krb5_auth: " + "verifypw %s", error_message(code)); + + if (code == 0) { + /* pw is good, set age status for acct_mgmt */ + kmd->age_status = PAM_NEW_AUTHTOK_REQD; + } else if ((code == 2) && verbose) { + /* bad password */ + (void) snprintf(krb5_auth_messages[0], + sizeof (krb5_auth_messages[0]), + dgettext(TEXT_DOMAIN, + "password incorrect\n")); + (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, + krb5_auth_messages, NULL); + } + } + break; + + default: + result = PAM_SYSTEM_ERR; + if (kmd->debug) + syslog(LOG_DEBUG, "PAM-KRB5 (auth): error %d - %s", + code, error_message(code)); + break; + } + + if (code == 0) { + /* + * success for the entered pw + * + * we can't rely on the pw in PAM_AUTHTOK + * to be the (correct) krb5 one so + * store krb5 pw in module data for + * use in acct_mgmt + */ + if (!(kmd->password = strdup(*krb5_pass))) { + syslog(LOG_ERR, "Cannot strdup password"); + result = PAM_BUF_ERR; + goto out_err; + } + result = PAM_SUCCESS; + goto out; + } + +out_err: + /* jump (or reach) here if error and cred cache has been init */ + + if (kmd->debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): clearing initcreds in " + "pam_authenticate()"); + + krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); + (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); + +out: + if (server) + krb5_free_principal(kmd->kcontext, server); + if (me) + krb5_free_principal(kmd->kcontext, me); + if (kmd->kcontext) { + krb5_free_context(kmd->kcontext); + kmd->kcontext = NULL; + } + + if (kmd->debug) + syslog(LOG_DEBUG, + "PAM-KRB5 (auth): attempt_krb5_auth returning %d", + result); + + return (kmd->auth_status = result); +} + +/*ARGSUSED*/ +void +krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status) +{ + krb5_module_data_t *kmd = (krb5_module_data_t *)data; + + if (kmd == NULL) + return; + + if (kmd->debug) { + syslog(LOG_DEBUG, + dgettext(TEXT_DOMAIN, + "PAM-KRB5 (auth): krb5_cleanup auth_status = %d"), + kmd->auth_status); + } + + /* + * if pam_status is PAM_SUCCESS, clean up based on value in + * auth_status, otherwise just purge the context + */ + if ((pam_status == PAM_SUCCESS) && + (kmd->auth_status == PAM_SUCCESS) && kmd->ccache) + /* LINTED */ + krb5_cc_close(kmd->kcontext, kmd->ccache); + + if (kmd->password) { + (void) memset(kmd->password, 0, strlen(kmd->password)); + free(kmd->password); + } + + if ((pam_status != PAM_SUCCESS) || + (kmd->auth_status != PAM_SUCCESS)) { + krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); + (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); + } + + free(kmd); +} |