2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <security/pam_appl.h>
2N/A#include <security/pam_modules.h>
2N/A#include <security/pam_impl.h>
2N/A#include <string.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <sys/types.h>
2N/A#include <sys/stat.h>
2N/A#include <pwd.h>
2N/A#include <syslog.h>
2N/A#include <libintl.h>
2N/A#include <k5-int.h>
2N/A#include "profile/prof_int.h"
2N/A#include <netdb.h>
2N/A#include <ctype.h>
2N/A#include "utils.h"
2N/A#include "krb5_repository.h"
2N/A
2N/A#define KRB5_DEFAULT_OPTIONS 0
2N/A
2N/Aint forwardable_flag = 0;
2N/Aint renewable_flag = 0;
2N/Aint proxiable_flag = 0;
2N/Aint no_address_flag = 0;
2N/Aprofile_options_boolean config_option[] = {
2N/A { "forwardable", &forwardable_flag, 0 },
2N/A { "renewable", &renewable_flag, 0 },
2N/A { "proxiable", &proxiable_flag, 0 },
2N/A { "no_addresses", &no_address_flag, 0 },
2N/A { NULL, NULL, 0 }
2N/A};
2N/Achar *renew_timeval;
2N/Achar *life_timeval;
2N/Aprofile_option_strings config_times[] = {
2N/A { "max_life", &life_timeval, 0 },
2N/A { "max_renewable_life", &renew_timeval, 0 },
2N/A { NULL, NULL, 0 }
2N/A};
2N/Achar *realmdef[] = { "realms", NULL, NULL, NULL };
2N/Achar *appdef[] = { "appdefaults", "kinit", NULL };
2N/A
2N/A#define krb_realm (*(realmdef + 1))
2N/A
2N/Aint attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *,
2N/A char **, boolean_t);
2N/Avoid krb5_cleanup(pam_handle_t *, void *, int);
2N/A
2N/Aextern errcode_t profile_get_options_boolean();
2N/Aextern errcode_t profile_get_options_string();
2N/Aextern int krb5_verifypw(char *, char *, int);
2N/Aextern krb5_error_code krb5_verify_init_creds(krb5_context,
2N/A krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *,
2N/A krb5_verify_init_creds_opt *);
2N/Aextern krb5_error_code __krb5_get_init_creds_password(krb5_context,
2N/A krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
2N/A krb5_deltat, char *, krb5_get_init_creds_opt *,
2N/A krb5_kdc_rep **);
2N/A
2N/A/*
2N/A * pam_sm_authenticate - Authenticate user
2N/A */
2N/Aint
2N/Apam_sm_authenticate(
2N/A pam_handle_t *pamh,
2N/A int flags,
2N/A int argc,
2N/A const char **argv)
2N/A{
2N/A char *user = NULL;
2N/A int err;
2N/A int result = PAM_AUTH_ERR;
2N/A /* pam.conf options */
2N/A int debug = 0;
2N/A int warn = 1;
2N/A /* return an error on password expire */
2N/A int err_on_exp = 0;
2N/A int i;
2N/A char *password = NULL;
2N/A uid_t pw_uid;
2N/A krb5_module_data_t *kmd = NULL;
2N/A krb5_repository_data_t *krb5_data = NULL;
2N/A pam_repository_t *rep_data = NULL;
2N/A boolean_t do_pkinit = FALSE;
2N/A
2N/A for (i = 0; i < argc; i++) {
2N/A if (strcmp(argv[i], "debug") == 0) {
2N/A debug = 1;
2N/A } else if (strcmp(argv[i], "nowarn") == 0) {
2N/A warn = 0;
2N/A } else if (strcmp(argv[i], "err_on_exp") == 0) {
2N/A err_on_exp = 1;
2N/A } else if (strcmp(argv[i], "pkinit") == 0) {
2N/A do_pkinit = TRUE;
2N/A } else {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth) unrecognized option %s", argv[i]);
2N/A }
2N/A }
2N/A if (flags & PAM_SILENT) warn = 0;
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): pam_sm_authenticate flags=%d",
2N/A flags);
2N/A
2N/A /*
2N/A * pam_get_data could fail if we are being called for the first time
2N/A * or if the module is not found, PAM_NO_MODULE_DATA is not an error
2N/A */
2N/A err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd);
2N/A if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA))
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A /*
2N/A * If pam_krb5 was stacked higher in the auth stack and did PKINIT
2N/A * preauth sucessfully then this instance is a fallback to password
2N/A * based preauth and should just return PAM_IGNORE.
2N/A *
2N/A * The else clause is handled further down.
2N/A */
2N/A if (kmd != NULL) {
2N/A if (++(kmd->auth_calls) > 2) {
2N/A /*
2N/A * pam_krb5 has been stacked > 2 times in the auth
2N/A * stack. Clear out the current kmd and proceed as if
2N/A * this is the first time pam_krb5 auth has been called.
2N/A */
2N/A if (debug) {
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): stacked more than"
2N/A " two times, clearing kmd");
2N/A }
2N/A /* clear out/free current kmd */
2N/A err = pam_set_data(pamh, KRB5_DATA, NULL, NULL);
2N/A if (err != PAM_SUCCESS) {
2N/A krb5_cleanup(pamh, kmd, err);
2N/A result = err;
2N/A goto out;
2N/A }
2N/A kmd = NULL;
2N/A } else if (kmd->auth_calls == 2 &&
2N/A kmd->auth_status == PAM_SUCCESS) {
2N/A /*
2N/A * The previous instance of pam_krb5 succeeded and this
2N/A * instance was a fall back in case it didn't succeed so
2N/A * return ignore.
2N/A */
2N/A if (debug) {
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): PKINIT succeeded "
2N/A "earlier so returning PAM_IGNORE");
2N/A }
2N/A return (PAM_IGNORE);
2N/A }
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_USER, (void**) &user);
2N/A
2N/A if (user == NULL || *user == '\0') {
2N/A if (do_pkinit) {
2N/A /*
2N/A * If doing PKINIT it is okay to prompt for the user
2N/A * name.
2N/A */
2N/A if ((err = pam_get_user(pamh, &user, NULL)) !=
2N/A PAM_SUCCESS) {
2N/A if (debug) {
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): get user failed: "
2N/A "%s", pam_strerror(pamh, err));
2N/A }
2N/A return (err);
2N/A }
2N/A } else {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): user empty or null");
2N/A return (PAM_USER_UNKNOWN);
2N/A }
2N/A }
2N/A
2N/A /* make sure a password entry exists for this user */
2N/A if (!get_pw_uid(user, &pw_uid))
2N/A return (PAM_USER_UNKNOWN);
2N/A
2N/A if (kmd == NULL) {
2N/A kmd = calloc(1, sizeof (krb5_module_data_t));
2N/A if (kmd == NULL) {
2N/A result = PAM_BUF_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup);
2N/A if (err != PAM_SUCCESS) {
2N/A free(kmd);
2N/A result = err;
2N/A goto out;
2N/A }
2N/A }
2N/A
2N/A if (!kmd->env) {
2N/A char buffer[512];
2N/A
2N/A if (snprintf(buffer, sizeof (buffer),
2N/A "%s=FILE:/tmp/krb5cc_%d",
2N/A KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) {
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A /* we MUST copy this to the heap for the putenv to work! */
2N/A kmd->env = strdup(buffer);
2N/A if (!kmd->env) {
2N/A result = PAM_BUF_ERR;
2N/A goto out;
2N/A } else {
2N/A if (putenv(kmd->env)) {
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A }
2N/A }
2N/A
2N/A if (kmd->user != NULL)
2N/A free(kmd->user);
2N/A if ((kmd->user = strdup(user)) == NULL) {
2N/A result = PAM_BUF_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A kmd->auth_status = PAM_AUTH_ERR;
2N/A kmd->debug = debug;
2N/A kmd->warn = warn;
2N/A kmd->err_on_exp = err_on_exp;
2N/A kmd->ccache = NULL;
2N/A kmd->kcontext = NULL;
2N/A kmd->password = NULL;
2N/A kmd->age_status = PAM_SUCCESS;
2N/A (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
2N/A kmd->auth_calls = 1;
2N/A kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD;
2N/A
2N/A /*
2N/A * For apps that already did krb5 auth exchange...
2N/A * Now that we've created the kmd structure, we can
2N/A * return SUCCESS. 'kmd' may be needed later by other
2N/A * PAM functions, thats why we wait until this point to
2N/A * return.
2N/A */
2N/A (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
2N/A
2N/A if (rep_data != NULL) {
2N/A if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): wrong"
2N/A "repository found (%s), returning "
2N/A "PAM_IGNORE", rep_data->type);
2N/A return (PAM_IGNORE);
2N/A }
2N/A if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
2N/A krb5_data = (krb5_repository_data_t *)rep_data->scope;
2N/A
2N/A if (krb5_data->flags ==
2N/A SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
2N/A krb5_data->principal != NULL &&
2N/A strlen(krb5_data->principal)) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): Principal "
2N/A "%s already authenticated",
2N/A krb5_data->principal);
2N/A kmd->auth_status = PAM_SUCCESS;
2N/A return (PAM_SUCCESS);
2N/A }
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * if root key exists in the keytab, it's a random key so no
2N/A * need to prompt for pw and we just return IGNORE.
2N/A *
2N/A * note we don't need to force a prompt for pw as authtok_get
2N/A * is required to be stacked above this module.
2N/A */
2N/A if ((strcmp(user, ROOT_UNAME) == 0) &&
2N/A key_in_keytab(user, debug)) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): "
2N/A "key for '%s' in keytab, returning IGNORE", user);
2N/A result = PAM_IGNORE;
2N/A goto out;
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
2N/A
2N/A result = attempt_krb5_auth(pamh, kmd, user, &password, 1);
2N/A
2N/Aout:
2N/A if (kmd) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): pam_sm_auth finalize"
2N/A " ccname env, result =%d, env ='%s',"
2N/A " age = %d, status = %d",
2N/A result, kmd->env ? kmd->env : "<null>",
2N/A kmd->age_status, kmd->auth_status);
2N/A
2N/A if (kmd->env &&
2N/A !(kmd->age_status == PAM_NEW_AUTHTOK_REQD &&
2N/A kmd->auth_status == PAM_SUCCESS)) {
2N/A
2N/A
2N/A if (result == PAM_SUCCESS) {
2N/A /*
2N/A * Put ccname into the pamh so that login
2N/A * apps can pick this up when they run
2N/A * pam_getenvlist().
2N/A */
2N/A if ((result = pam_putenv(pamh, kmd->env))
2N/A != PAM_SUCCESS) {
2N/A /* should not happen but... */
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth):"
2N/A " pam_putenv failed: result: %d",
2N/A result);
2N/A goto cleanupccname;
2N/A }
2N/A } else {
2N/A cleanupccname:
2N/A /* for lack of a Solaris unputenv() */
2N/A krb5_unsetenv(KRB5_ENV_CCNAME);
2N/A free(kmd->env);
2N/A kmd->env = NULL;
2N/A }
2N/A }
2N/A kmd->auth_status = result;
2N/A }
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result));
2N/A
2N/A return (result);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Apam_krb5_prompter(
2N/A krb5_context ctx,
2N/A void *data,
2N/A /* ARGSUSED1 */
2N/A const char *name,
2N/A const char *banner,
2N/A int num_prompts,
2N/A krb5_prompt prompts[])
2N/A{
2N/A krb5_error_code rc = KRB5_LIBOS_CANTREADPWD;
2N/A pam_handle_t *pamh = (pam_handle_t *)data;
2N/A struct pam_conv *pam_convp;
2N/A struct pam_message *msgs = NULL;
2N/A struct pam_response *ret_respp = NULL;
2N/A int i;
2N/A krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx);
2N/A char tmpbuf[PAM_MAX_MSG_SIZE];
2N/A
2N/A if (prompts) {
2N/A assert(num_prompts > 0);
2N/A }
2N/A /*
2N/A * Because this function should never be used for password prompts,
2N/A * disallow password prompts.
2N/A */
2N/A for (i = 0; i < num_prompts; i++) {
2N/A switch (prompt_type[i]) {
2N/A case KRB5_PROMPT_TYPE_PASSWORD:
2N/A case KRB5_PROMPT_TYPE_NEW_PASSWORD:
2N/A case KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN:
2N/A return (rc);
2N/A }
2N/A }
2N/A
2N/A if (pam_get_item(pamh, PAM_CONV, (void **)&pam_convp) != PAM_SUCCESS) {
2N/A return (rc);
2N/A }
2N/A if (pam_convp == NULL) {
2N/A return (rc);
2N/A }
2N/A
2N/A msgs = (struct pam_message *)calloc(num_prompts,
2N/A sizeof (struct pam_message));
2N/A if (msgs == NULL) {
2N/A return (rc);
2N/A }
2N/A (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts);
2N/A
2N/A for (i = 0; i < num_prompts; i++) {
2N/A /* convert krb prompt style to PAM style */
2N/A if (prompts[i].hidden) {
2N/A msgs[i].msg_style = PAM_PROMPT_ECHO_OFF;
2N/A } else {
2N/A msgs[i].msg_style = PAM_PROMPT_ECHO_ON;
2N/A }
2N/A /*
2N/A * krb expects the prompting function to append ": " to the
2N/A * prompt string.
2N/A */
2N/A if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ",
2N/A prompts[i].prompt) < 0) {
2N/A goto cleanup;
2N/A }
2N/A msgs[i].msg = strdup(tmpbuf);
2N/A if (msgs[i].msg == NULL) {
2N/A goto cleanup;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Call PAM conv function to display the prompt.
2N/A */
2N/A
2N/A if ((pam_convp->conv)(num_prompts, &msgs, &ret_respp,
2N/A pam_convp->appdata_ptr) == PAM_SUCCESS) {
2N/A for (i = 0; i < num_prompts; i++) {
2N/A /* convert PAM response to krb prompt reply format */
2N/A assert(prompts[i].reply->data != NULL);
2N/A assert(ret_respp[i].resp != NULL);
2N/A
2N/A if (strlcpy(prompts[i].reply->data,
2N/A ret_respp[i].resp, prompts[i].reply->length) >=
2N/A prompts[i].reply->length) {
2N/A char errmsg[1][PAM_MAX_MSG_SIZE];
2N/A
2N/A (void) snprintf(errmsg[0], PAM_MAX_MSG_SIZE,
2N/A "%s", dgettext(TEXT_DOMAIN,
2N/A "Reply too long: "));
2N/A (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
2N/A 1, errmsg, NULL);
2N/A goto cleanup;
2N/A } else {
2N/A char *retp;
2N/A
2N/A /*
2N/A * newline must be replaced with \0 terminator
2N/A */
2N/A retp = strchr(prompts[i].reply->data, '\n');
2N/A if (retp != NULL)
2N/A *retp = '\0';
2N/A /* NULL terminator should not be counted */
2N/A prompts[i].reply->length =
2N/A strlen(prompts[i].reply->data);
2N/A }
2N/A }
2N/A rc = 0;
2N/A }
2N/A
2N/Acleanup:
2N/A for (i = 0; i < num_prompts; i++) {
2N/A if (msgs[i].msg) {
2N/A free(msgs[i].msg);
2N/A }
2N/A if (ret_respp[i].resp) {
2N/A /* 0 out sensitive data before free() */
2N/A (void) memset(ret_respp[i].resp, 0,
2N/A strlen(ret_respp[i].resp));
2N/A free(ret_respp[i].resp);
2N/A }
2N/A }
2N/A free(msgs);
2N/A free(ret_respp);
2N/A return (rc);
2N/A}
2N/A
2N/Aint
2N/Aattempt_krb5_auth(
2N/A pam_handle_t *pamh,
2N/A krb5_module_data_t *kmd,
2N/A char *user,
2N/A char **krb5_pass,
2N/A boolean_t verify_tik)
2N/A{
2N/A krb5_principal me = NULL, clientp = NULL;
2N/A krb5_principal server = NULL, serverp = NULL;
2N/A krb5_creds *my_creds;
2N/A krb5_timestamp now;
2N/A krb5_error_code code = 0;
2N/A char kuser[2*MAXHOSTNAMELEN];
2N/A krb5_deltat lifetime;
2N/A krb5_deltat rlife;
2N/A krb5_deltat krb5_max_duration;
2N/A int options = KRB5_DEFAULT_OPTIONS;
2N/A krb5_data tgtname = {
2N/A 0,
2N/A KRB5_TGS_NAME_SIZE,
2N/A KRB5_TGS_NAME
2N/A };
2N/A krb5_get_init_creds_opt *opts = NULL;
2N/A krb5_kdc_rep *as_reply = NULL;
2N/A /*
2N/A * "result" should not be assigned PAM_SUCCESS unless
2N/A * authentication has succeeded and there are no other errors.
2N/A *
2N/A * "code" is sometimes used for PAM codes, sometimes for krb5
2N/A * codes. Be careful.
2N/A */
2N/A int result = PAM_AUTH_ERR;
2N/A
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
2N/A user ? user : "<null>");
2N/A
2N/A /* need to free context with krb5_free_context */
2N/A if (code = krb5_init_secure_context(&kmd->kcontext)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): Error initializing "
2N/A "krb5: %s",
2N/A error_message(code));
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
2N/A 2*MAXHOSTNAMELEN)) != 0) {
2N/A /* get_kmd_kuser returns proper PAM error statuses */
2N/A return (code);
2N/A }
2N/A
2N/A if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) {
2N/A krb5_free_context(kmd->kcontext);
2N/A kmd->kcontext = NULL;
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A /* call krb5_free_cred_contents() on error */
2N/A my_creds = &kmd->initcreds;
2N/A
2N/A if ((code =
2N/A krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) {
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out_err;
2N/A }
2N/A clientp = my_creds->client;
2N/A
2N/A if (code = krb5_build_principal_ext(kmd->kcontext, &server,
2N/A krb5_princ_realm(kmd->kcontext, me)->length,
2N/A krb5_princ_realm(kmd->kcontext, me)->data,
2N/A tgtname.length, tgtname.data,
2N/A krb5_princ_realm(kmd->kcontext, me)->length,
2N/A krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth: "
2N/A "krb5_build_princ_ext failed: %s",
2N/A error_message(code));
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A if (code = krb5_copy_principal(kmd->kcontext, server,
2N/A &my_creds->server)) {
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out_err;
2N/A }
2N/A serverp = my_creds->server;
2N/A
2N/A if (code = krb5_timeofday(kmd->kcontext, &now)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth: "
2N/A "krb5_timeofday failed: %s",
2N/A error_message(code));
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A /*
2N/A * set the values for lifetime and rlife to be the maximum
2N/A * possible
2N/A */
2N/A krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
2N/A lifetime = krb5_max_duration;
2N/A rlife = krb5_max_duration;
2N/A
2N/A /*
2N/A * Let us get the values for various options
2N/A * from Kerberos configuration file
2N/A */
2N/A
2N/A krb_realm = krb5_princ_realm(kmd->kcontext, me)->data;
2N/A (void) profile_get_options_boolean(kmd->kcontext->profile,
2N/A realmdef, config_option);
2N/A (void) profile_get_options_boolean(kmd->kcontext->profile,
2N/A appdef, config_option);
2N/A (void) profile_get_options_string(kmd->kcontext->profile,
2N/A realmdef, config_times);
2N/A (void) profile_get_options_string(kmd->kcontext->profile,
2N/A appdef, config_times);
2N/A
2N/A if (renew_timeval) {
2N/A code = krb5_string_to_deltat(renew_timeval, &rlife);
2N/A if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): Bad max_renewable_life "
2N/A " value '%s' in Kerberos config file",
2N/A renew_timeval);
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A }
2N/A if (life_timeval) {
2N/A code = krb5_string_to_deltat(life_timeval, &lifetime);
2N/A if (code != 0 || lifetime == 0 ||
2N/A lifetime > krb5_max_duration) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "lifetime value '%s' in Kerberos config file",
2N/A life_timeval);
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A }
2N/A /* start timer when request gets to KDC */
2N/A my_creds->times.starttime = 0;
2N/A my_creds->times.endtime = now + lifetime;
2N/A
2N/A if (options & KDC_OPT_RENEWABLE) {
2N/A my_creds->times.renew_till = now + rlife;
2N/A } else
2N/A my_creds->times.renew_till = 0;
2N/A
2N/A code = krb5_get_init_creds_opt_alloc(kmd->kcontext, &opts);
2N/A if (code != 0) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "Error allocating gic opts: %s",
2N/A error_message(code));
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A krb5_get_init_creds_opt_set_tkt_life(opts, lifetime);
2N/A
2N/A if (proxiable_flag) { /* Set in config file */
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): Proxiable tickets "
2N/A "requested");
2N/A krb5_get_init_creds_opt_set_proxiable(opts, TRUE);
2N/A }
2N/A if (forwardable_flag) {
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): Forwardable tickets "
2N/A "requested");
2N/A krb5_get_init_creds_opt_set_forwardable(opts, TRUE);
2N/A }
2N/A if (renewable_flag) {
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): Renewable tickets "
2N/A "requested");
2N/A krb5_get_init_creds_opt_set_renew_life(opts, rlife);
2N/A }
2N/A if (no_address_flag) {
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): Addressless tickets "
2N/A "requested");
2N/A krb5_get_init_creds_opt_set_address_list(opts, NULL);
2N/A }
2N/A
2N/A /*
2N/A * mech_krb5 interprets empty passwords as NULL passwords and tries to
2N/A * read a password from stdin. Since we are in pam this is bad and
2N/A * should not be allowed.
2N/A *
2N/A * Note, the logic now is that if the preauth_type is PKINIT then
2N/A * provide a proper PAMcentric prompt function that the underlying
2N/A * PKINIT preauth plugin will use to prompt for the PIN.
2N/A */
2N/A if (kmd->preauth_type == KRB_PKINIT) {
2N/A /*
2N/A * Do PKINIT preauth
2N/A *
2N/A * Note: we want to limit preauth types to just those for PKINIT
2N/A * but krb5_get_init_creds() doesn't support that at this point.
2N/A * Instead we rely on pam_krb5_prompter() to limit prompts to
2N/A * non-password types. So all we can do here is set the preauth
2N/A * list so krb5_get_init_creds() will try that first.
2N/A */
2N/A krb5_preauthtype pk_pa_list[] = {
2N/A KRB5_PADATA_PK_AS_REQ,
2N/A KRB5_PADATA_PK_AS_REQ_OLD
2N/A };
2N/A krb5_get_init_creds_opt_set_preauth_list(opts, pk_pa_list, 2);
2N/A
2N/A if (*krb5_pass == NULL || strlen(*krb5_pass) != 0) {
2N/A if (*krb5_pass != NULL) {
2N/A /* treat the krb5_pass as a PIN */
2N/A code = krb5_get_init_creds_opt_set_pa(
2N/A kmd->kcontext, opts, "PIN", *krb5_pass);
2N/A }
2N/A
2N/A if (!code) {
2N/A code = __krb5_get_init_creds_password(
2N/A kmd->kcontext,
2N/A my_creds,
2N/A me,
2N/A NULL, /* clear text passwd */
2N/A pam_krb5_prompter, /* prompter */
2N/A pamh, /* prompter data */
2N/A 0, /* start time */
2N/A NULL, /* defaults to krbtgt@REALM */
2N/A opts,
2N/A &as_reply);
2N/A }
2N/A } else {
2N/A /* invalid PIN */
2N/A code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
2N/A }
2N/A } else {
2N/A /*
2N/A * Do password based preauths
2N/A *
2N/A * See earlier PKINIT comment. We are doing something similar
2N/A * here but we do not pass in a prompter (we assume
2N/A * pam_authtok_get has already prompted for that).
2N/A *
2N/A * Note, do not specify use of KRB5_PADATA_ENC_TIMESTAMP here
2N/A * because we don't know what enctypes the client princ has; we
2N/A * need the KDC to tell us that.
2N/A */
2N/A if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) {
2N/A code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
2N/A } else {
2N/A /*
2N/A * We call our own private version of gic_pwd, because
2N/A * we need more information, such as password/account
2N/A * expiration, that is found in the as_reply. The
2N/A * "prompter" interface is not granular enough for PAM
2N/A * to make use of.
2N/A */
2N/A code = __krb5_get_init_creds_password(kmd->kcontext,
2N/A my_creds,
2N/A me,
2N/A *krb5_pass, /* clear text passwd */
2N/A NULL, /* prompter */
2N/A NULL, /* data */
2N/A 0, /* start time */
2N/A NULL, /* defaults to krbtgt@REALM */
2N/A opts,
2N/A &as_reply);
2N/A }
2N/A }
2N/A
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth: "
2N/A "krb5_get_init_creds_password returns: %s",
2N/A code == 0 ? "SUCCESS" : error_message(code));
2N/A
2N/A switch (code) {
2N/A case 0:
2N/A /* got a tgt, let's verify it */
2N/A if (verify_tik) {
2N/A krb5_verify_init_creds_opt vopts;
2N/A
2N/A krb5_principal sp = NULL;
2N/A char kt_name[MAX_KEYTAB_NAME_LEN];
2N/A char *fqdn;
2N/A
2N/A krb5_verify_init_creds_opt_init(&vopts);
2N/A
2N/A code = krb5_verify_init_creds(kmd->kcontext,
2N/A my_creds,
2N/A NULL, /* defaults to host/localhost@REALM */
2N/A NULL,
2N/A NULL,
2N/A &vopts);
2N/A
2N/A if (code) {
2N/A result = PAM_SYSTEM_ERR;
2N/A
2N/A /*
2N/A * Give a better error message when the
2N/A * keytable entry isn't found or the keytab
2N/A * file cannot be found.
2N/A */
2N/A if (krb5_sname_to_principal(kmd->kcontext, NULL,
2N/A NULL, KRB5_NT_SRV_HST, &sp))
2N/A fqdn = "<fqdn>";
2N/A else
2N/A fqdn = sp->data[1].data;
2N/A
2N/A if (krb5_kt_default_name(kmd->kcontext, kt_name,
2N/A sizeof (kt_name)))
2N/A (void) strlcpy(kt_name,
2N/A "default keytab",
2N/A sizeof (kt_name));
2N/A
2N/A switch (code) {
2N/A case KRB5_KT_NOTFOUND:
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): "
2N/A "krb5_verify_init_creds failed:"
2N/A " Key table entry \"host/%s\""
2N/A " not found in %s",
2N/A fqdn, kt_name);
2N/A break;
2N/A case ENOENT:
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): "
2N/A "krb5_verify_init_creds failed:"
2N/A " Keytab file \"%s\""
2N/A " does not exist.\n",
2N/A kt_name);
2N/A break;
2N/A default:
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (auth): "
2N/A "krb5_verify_init_creds failed:"
2N/A " %s",
2N/A error_message(code));
2N/A break;
2N/A }
2N/A
2N/A if (sp)
2N/A krb5_free_principal(kmd->kcontext, sp);
2N/A }
2N/A }
2N/A
2N/A if (code == 0)
2N/A kmd->expiration = as_reply->enc_part2->key_exp;
2N/A
2N/A break;
2N/A
2N/A case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
2N/A /*
2N/A * Since this principal is not part of the local
2N/A * Kerberos realm, we just return PAM_USER_UNKNOWN.
2N/A */
2N/A result = PAM_USER_UNKNOWN;
2N/A
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth:"
2N/A " User is not part of the local Kerberos"
2N/A " realm: %s", error_message(code));
2N/A break;
2N/A
2N/A case KRB5KDC_ERR_PREAUTH_FAILED:
2N/A case KRB5KRB_AP_ERR_BAD_INTEGRITY:
2N/A /*
2N/A * We could be trying the password from a previous
2N/A * pam authentication module, but we don't want to
2N/A * generate an error if the unix password is different
2N/A * than the Kerberos password...
2N/A */
2N/A break;
2N/A
2N/A case KRB5KDC_ERR_KEY_EXP:
2N/A if (!kmd->err_on_exp) {
2N/A /*
2N/A * Request a tik for changepw service and it will tell
2N/A * us if pw is good or not. If PKINIT is being done it
2N/A * is possible that *krb5_pass may be NULL so check for
2N/A * that. If that is the case this function will return
2N/A * an error.
2N/A */
2N/A if (*krb5_pass != NULL) {
2N/A code = krb5_verifypw(kuser, *krb5_pass,
2N/A kmd->debug);
2N/A if (kmd->debug) {
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): "
2N/A "attempt_krb5_auth: "
2N/A "verifypw %d", code);
2N/A }
2N/A if (code == 0) {
2N/A /*
2N/A * pw is good, set age status for
2N/A * acct_mgmt.
2N/A */
2N/A kmd->age_status = PAM_NEW_AUTHTOK_REQD;
2N/A }
2N/A }
2N/A
2N/A }
2N/A break;
2N/A
2N/A default:
2N/A result = PAM_SYSTEM_ERR;
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): error %d - %s",
2N/A code, error_message(code));
2N/A break;
2N/A }
2N/A
2N/A if (code == 0) {
2N/A /*
2N/A * success for the entered pw or PKINIT succeeded.
2N/A *
2N/A * we can't rely on the pw in PAM_AUTHTOK
2N/A * to be the (correct) krb5 one so
2N/A * store krb5 pw in module data for
2N/A * use in acct_mgmt. Note that *krb5_pass may be NULL if we're
2N/A * doing PKINIT.
2N/A */
2N/A if (*krb5_pass != NULL &&
2N/A !(kmd->password = strdup(*krb5_pass))) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "Cannot strdup password");
2N/A result = PAM_BUF_ERR;
2N/A goto out_err;
2N/A }
2N/A
2N/A result = PAM_SUCCESS;
2N/A goto out;
2N/A }
2N/A
2N/Aout_err:
2N/A /* jump (or reach) here if error and cred cache has been init */
2N/A
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): clearing initcreds in "
2N/A "pam_authenticate()");
2N/A
2N/A krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
2N/A (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
2N/A
2N/Aout:
2N/A if (server)
2N/A krb5_free_principal(kmd->kcontext, server);
2N/A if (me)
2N/A krb5_free_principal(kmd->kcontext, me);
2N/A if (as_reply)
2N/A krb5_free_kdc_rep(kmd->kcontext, as_reply);
2N/A
2N/A /*
2N/A * clientp or serverp could be NULL in certain error cases in this
2N/A * function. mycreds->[client|server] could also be NULL in case
2N/A * of error in this function, see out_err above. The pointers clientp
2N/A * and serverp reference the input argument in my_creds for
2N/A * get_init_creds and must be freed if the input argument does not
2N/A * match the output argument, which occurs during a successful call
2N/A * to get_init_creds.
2N/A */
2N/A if (clientp && my_creds->client && clientp != my_creds->client)
2N/A krb5_free_principal(kmd->kcontext, clientp);
2N/A if (serverp && my_creds->server && serverp != my_creds->server)
2N/A krb5_free_principal(kmd->kcontext, serverp);
2N/A
2N/A if (kmd->kcontext) {
2N/A krb5_free_context(kmd->kcontext);
2N/A kmd->kcontext = NULL;
2N/A }
2N/A if (opts)
2N/A krb5_get_init_creds_opt_free(kmd->kcontext, opts);
2N/A
2N/A if (kmd->debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): attempt_krb5_auth returning %d",
2N/A result);
2N/A
2N/A return (kmd->auth_status = result);
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Avoid
2N/Akrb5_cleanup(pam_handle_t *pamh, void *data, int pam_status)
2N/A{
2N/A krb5_module_data_t *kmd = (krb5_module_data_t *)data;
2N/A
2N/A if (kmd == NULL)
2N/A return;
2N/A
2N/A if (kmd->debug) {
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): krb5_cleanup auth_status = %d",
2N/A kmd->auth_status);
2N/A }
2N/A
2N/A /*
2N/A * Apps could be calling pam_end here, so we should always clean
2N/A * up regardless of success or failure here.
2N/A */
2N/A if (kmd->ccache)
2N/A (void) krb5_cc_close(kmd->kcontext, kmd->ccache);
2N/A
2N/A if (kmd->password) {
2N/A (void) memset(kmd->password, 0, strlen(kmd->password));
2N/A free(kmd->password);
2N/A }
2N/A
2N/A if (kmd->user)
2N/A free(kmd->user);
2N/A
2N/A if (kmd->env)
2N/A free(kmd->env);
2N/A
2N/A krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
2N/A (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
2N/A
2N/A free(kmd);
2N/A}