/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "profile/prof_int.h" #include #include #include "utils.h" #include "krb5_repository.h" #define KRB5_DEFAULT_OPTIONS 0 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(pam_handle_t *, krb5_module_data_t *, char *, char **, 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(char *, char *, int); extern krb5_error_code krb5_verify_init_creds(krb5_context, krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *, krb5_verify_init_creds_opt *); extern krb5_error_code __krb5_get_init_creds_password(krb5_context, krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *, krb5_deltat, char *, krb5_get_init_creds_opt *, krb5_kdc_rep **); /* * pam_sm_authenticate - Authenticate user */ int pam_sm_authenticate( pam_handle_t *pamh, int flags, int argc, const char **argv) { char *user = NULL; 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 i; 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; boolean_t do_pkinit = FALSE; 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 if (strcmp(argv[i], "pkinit") == 0) { do_pkinit = TRUE; } else { __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth) unrecognized option %s", argv[i]); } } if (flags & PAM_SILENT) warn = 0; if (debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): pam_sm_authenticate flags=%d", flags); /* * 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_SYSTEM_ERR); /* * If pam_krb5 was stacked higher in the auth stack and did PKINIT * preauth sucessfully then this instance is a fallback to password * based preauth and should just return PAM_IGNORE. * * The else clause is handled further down. */ if (kmd != NULL) { if (++(kmd->auth_calls) > 2) { /* * pam_krb5 has been stacked > 2 times in the auth * stack. Clear out the current kmd and proceed as if * this is the first time pam_krb5 auth has been called. */ if (debug) { __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): stacked more than" " two times, clearing kmd"); } /* clear out/free current kmd */ err = pam_set_data(pamh, KRB5_DATA, NULL, NULL); if (err != PAM_SUCCESS) { krb5_cleanup(pamh, kmd, err); result = err; goto out; } kmd = NULL; } else if (kmd->auth_calls == 2 && kmd->auth_status == PAM_SUCCESS) { /* * The previous instance of pam_krb5 succeeded and this * instance was a fall back in case it didn't succeed so * return ignore. */ if (debug) { __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): PKINIT succeeded " "earlier so returning PAM_IGNORE"); } return (PAM_IGNORE); } } (void) pam_get_item(pamh, PAM_USER, (void**) &user); if (user == NULL || *user == '\0') { if (do_pkinit) { /* * If doing PKINIT it is okay to prompt for the user * name. */ if ((err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) { if (debug) { __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): get user failed: " "%s", pam_strerror(pamh, err)); } return (err); } } else { if (debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): user empty or null"); return (PAM_USER_UNKNOWN); } } /* make sure a password entry exists for this user */ if (!get_pw_uid(user, &pw_uid)) return (PAM_USER_UNKNOWN); 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; } } } if (kmd->user != NULL) free(kmd->user); if ((kmd->user = strdup(user)) == NULL) { result = PAM_BUF_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)); kmd->auth_calls = 1; kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD; /* * 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. */ (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); if (rep_data != NULL) { if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { if (debug) __pam_log(LOG_AUTH | 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) __pam_log(LOG_AUTH | 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) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): " "key for '%s' in keytab, returning IGNORE", user); result = PAM_IGNORE; goto out; } (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password); result = attempt_krb5_auth(pamh, kmd, user, &password, 1); out: if (kmd) { if (debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): pam_sm_auth finalize" " ccname env, result =%d, env ='%s'," " age = %d, status = %d", result, kmd->env ? kmd->env : "", 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... */ __pam_log(LOG_AUTH | LOG_ERR, "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) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result)); return (result); } static krb5_error_code pam_krb5_prompter( krb5_context ctx, void *data, /* ARGSUSED1 */ const char *name, const char *banner, int num_prompts, krb5_prompt prompts[]) { krb5_error_code rc; pam_handle_t *pamh = (pam_handle_t *)data; struct pam_conv *pam_convp; struct pam_message *msgs; struct pam_response *ret_respp; int i; krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx); char tmpbuf[PAM_MAX_MSG_SIZE]; /* * Because this function should never be used for password prompts, * disallow password prompts. */ for (i = 0; i < num_prompts; i++) { if (prompt_type[i] == KRB5_PROMPT_TYPE_PASSWORD) return (KRB5_LIBOS_CANTREADPWD); } if (num_prompts == 0) { if (prompts) { /* This shouldn't happen */ return (PAM_SYSTEM_ERR); } else { /* no prompts so return */ return (0); } } if ((rc = pam_get_item(pamh, PAM_CONV, (void **)&pam_convp)) != PAM_SUCCESS) { return (rc); } if (pam_convp == NULL) { return (PAM_SYSTEM_ERR); } msgs = (struct pam_message *)calloc(num_prompts, sizeof (struct pam_message)); if (msgs == NULL) { return (PAM_BUF_ERR); } (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts); for (i = 0; i < num_prompts; i++) { /* convert krb prompt style to PAM style */ if (prompts[i].hidden) { msgs[i].msg_style = PAM_PROMPT_ECHO_OFF; } else { msgs[i].msg_style = PAM_PROMPT_ECHO_ON; } /* * krb expects the prompting function to append ": " to the * prompt string. */ if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ", prompts[i].prompt) < 0) { rc = PAM_BUF_ERR; goto cleanup; } msgs[i].msg = strdup(tmpbuf); if (msgs[i].msg == NULL) { rc = PAM_BUF_ERR; goto cleanup; } } /* * Call PAM conv function to display the prompt. */ rc = (pam_convp->conv)(num_prompts, &msgs, &ret_respp, pam_convp->appdata_ptr); if (rc == PAM_SUCCESS) { for (i = 0; i < num_prompts; i++) { /* convert PAM response to krb prompt reply format */ prompts[i].reply->length = strlen(ret_respp[i].resp) + 1; /* adding 1 for NULL terminator */ prompts[i].reply->data = ret_respp[i].resp; } /* * Note, just free ret_respp, not the resp data since that is * being referenced by the krb prompt reply data pointer. */ free(ret_respp); } cleanup: for (i = 0; i < num_prompts; i++) { if (msgs[i].msg) { free(msgs[i].msg); } } free(msgs); return (rc); } int attempt_krb5_auth( pam_handle_t *pamh, krb5_module_data_t *kmd, char *user, char **krb5_pass, boolean_t verify_tik) { krb5_principal me = NULL, clientp = NULL; krb5_principal server = NULL, serverp = NULL; krb5_creds *my_creds; krb5_timestamp now; krb5_error_code code = 0; char kuser[2*MAXHOSTNAMELEN]; 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 }; krb5_get_init_creds_opt opts; krb5_kdc_rep *as_reply = NULL; /* * "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) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'", user ? user : ""); krb5_get_init_creds_opt_init(&opts); /* need to free context with krb5_free_context */ if (code = krb5_init_secure_context(&kmd->kcontext)) { __pam_log(LOG_AUTH | LOG_ERR, "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_SYSTEM_ERR); } /* call krb5_free_cred_contents() on error */ my_creds = &kmd->initcreds; if ((code = krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) { result = PAM_SYSTEM_ERR; goto out_err; } clientp = my_creds->client; 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)) { __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth): attempt_krb5_auth: " "krb5_build_princ_ext failed: %s", error_message(code)); result = PAM_SYSTEM_ERR; goto out; } if (code = krb5_copy_principal(kmd->kcontext, server, &my_creds->server)) { result = PAM_SYSTEM_ERR; goto out_err; } serverp = my_creds->server; if (code = krb5_timeofday(kmd->kcontext, &now)) { __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth): attempt_krb5_auth: " "krb5_timeofday failed: %s", error_message(code)); result = PAM_SYSTEM_ERR; 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) { __pam_log(LOG_AUTH | LOG_ERR, "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) { __pam_log(LOG_AUTH | LOG_ERR, "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) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): Proxiable tickets " "requested"); krb5_get_init_creds_opt_set_proxiable(&opts, TRUE); } if (forwardable_flag) { if (kmd->debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): Forwardable tickets " "requested"); krb5_get_init_creds_opt_set_forwardable(&opts, TRUE); } if (renewable_flag) { if (kmd->debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): Renewable tickets " "requested"); krb5_get_init_creds_opt_set_renew_life(&opts, rlife); } if (no_address_flag) { if (kmd->debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): Addressless tickets " "requested"); krb5_get_init_creds_opt_set_address_list(&opts, NULL); } /* * 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. * * Note, the logic now is that if the preauth_type is PKINIT then * provide a proper PAMcentric prompt function that the underlying * PKINIT preauth plugin will use to prompt for the PIN. */ if (kmd->preauth_type == KRB_PKINIT) { /* * Do PKINIT preauth * * Note: we want to limit preauth types to just those for PKINIT * but krb5_get_init_creds() doesn't support that at this point. * Instead we rely on pam_krb5_prompter() to limit prompts to * non-password types. So all we can do here is set the preauth * list so krb5_get_init_creds() will try that first. */ krb5_preauthtype pk_pa_list[] = { KRB5_PADATA_PK_AS_REQ, KRB5_PADATA_PK_AS_REQ_OLD }; krb5_get_init_creds_opt_set_preauth_list(&opts, pk_pa_list, 2); if (*krb5_pass == NULL) { /* let preauth plugin prompt for PIN */ code = __krb5_get_init_creds_password(kmd->kcontext, my_creds, me, NULL, /* clear text passwd */ pam_krb5_prompter, /* prompter */ pamh, /* prompter data */ 0, /* start time */ NULL, /* defaults to krbtgt@REALM */ &opts, &as_reply); } else { /* * krb pkinit does not support setting the PIN so we * punt on trying to use krb5_pass as the PIN for now. * Note that once this is supported by pkinit the code * should make sure krb5_pass isn't empty and if it is * then that's an error. */ code = KRB5KRB_AP_ERR_BAD_INTEGRITY; } } else { /* * Do password based preauths * * See earlier PKINIT comment. We are doing something similar * here but we do not pass in a prompter (we assume * pam_authtok_get has already prompted for that). */ if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { code = KRB5KRB_AP_ERR_BAD_INTEGRITY; } else { krb5_preauthtype pk_pa_list[] = { KRB5_PADATA_ENC_TIMESTAMP }; krb5_get_init_creds_opt_set_preauth_list(&opts, pk_pa_list, 1); /* * We call our own private version of gic_pwd, because * we need more information, such as password/account * expiration, that is found in the as_reply. The * "prompter" interface is not granular enough for PAM * to make use of. */ 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, &as_reply); } } if (kmd->debug) __pam_log(LOG_AUTH | 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_principal sp = NULL; char kt_name[MAX_KEYTAB_NAME_LEN]; char *fqdn; 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; /* * Give a better error message when the * keytable entry isn't found or the keytab * file cannot be found. */ if (krb5_sname_to_principal(kmd->kcontext, NULL, NULL, KRB5_NT_SRV_HST, &sp)) fqdn = ""; else fqdn = sp->data[1].data; if (krb5_kt_default_name(kmd->kcontext, kt_name, sizeof (kt_name))) (void) strlcpy(kt_name, "default keytab", sizeof (kt_name)); switch (code) { case KRB5_KT_NOTFOUND: __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth): " "krb5_verify_init_creds failed:" " Key table entry \"host/%s\"" " not found in %s", fqdn, kt_name); break; case ENOENT: __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth): " "krb5_verify_init_creds failed:" " Keytab file \"%s\"" " does not exist.\n", kt_name); break; default: __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (auth): " "krb5_verify_init_creds failed:" " %s", error_message(code)); break; } if (sp) krb5_free_principal(kmd->kcontext, sp); } } if (code == 0) kmd->expiration = as_reply->enc_part2->key_exp; 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) __pam_log(LOG_AUTH | 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... */ 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. If PKINIT is being done it * is possible that *krb5_pass may be NULL so check for * that. If that is the case this function will return * an error. */ if (*krb5_pass != NULL) { code = krb5_verifypw(kuser, *krb5_pass, kmd->debug); if (kmd->debug) { __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): " "attempt_krb5_auth: " "verifypw %d", code); } if (code == 0) { /* * pw is good, set age status for * acct_mgmt. */ kmd->age_status = PAM_NEW_AUTHTOK_REQD; } } } break; default: result = PAM_SYSTEM_ERR; if (kmd->debug) __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): error %d - %s", code, error_message(code)); break; } if (code == 0) { /* * success for the entered pw or PKINIT succeeded. * * 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. Note that *krb5_pass may be NULL if we're * doing PKINIT. */ if (*krb5_pass != NULL && !(kmd->password = strdup(*krb5_pass))) { __pam_log(LOG_AUTH | 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) __pam_log(LOG_AUTH | 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 (as_reply) krb5_free_kdc_rep(kmd->kcontext, as_reply); /* * clientp or serverp could be NULL in certain error cases in this * function. mycreds->[client|server] could also be NULL in case * of error in this function, see out_err above. The pointers clientp * and serverp reference the input argument in my_creds for * get_init_creds and must be freed if the input argument does not * match the output argument, which occurs during a successful call * to get_init_creds. */ if (clientp && my_creds->client && clientp != my_creds->client) krb5_free_principal(kmd->kcontext, clientp); if (serverp && my_creds->server && serverp != my_creds->server) krb5_free_principal(kmd->kcontext, serverp); if (kmd->kcontext) { krb5_free_context(kmd->kcontext); kmd->kcontext = NULL; } if (kmd->debug) __pam_log(LOG_AUTH | 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) { __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (auth): krb5_cleanup auth_status = %d", kmd->auth_status); } /* * Apps could be calling pam_end here, so we should always clean * up regardless of success or failure here. */ if (kmd->ccache) (void) krb5_cc_close(kmd->kcontext, kmd->ccache); if (kmd->password) { (void) memset(kmd->password, 0, strlen(kmd->password)); free(kmd->password); } if (kmd->user) free(kmd->user); if (kmd->env) free(kmd->env); krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); free(kmd); }