/*
* 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
* 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
*/
/*
*/
#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 <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 KRB5_DEFAULT_OPTIONS 0
int forwardable_flag = 0;
int renewable_flag = 0;
int proxiable_flag = 0;
int no_address_flag = 0;
{ "forwardable", &forwardable_flag, 0 },
{ "renewable", &renewable_flag, 0 },
{ "proxiable", &proxiable_flag, 0 },
{ "no_addresses", &no_address_flag, 0 },
};
char *renew_timeval;
char *life_timeval;
{ "max_life", &life_timeval, 0 },
{ "max_renewable_life", &renew_timeval, 0 },
};
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);
krb5_deltat, char *, krb5_get_init_creds_opt *,
krb5_kdc_rep **);
/*
* pam_sm_authenticate - Authenticate user
*/
int
int flags,
int argc,
const char **argv)
{
int err;
/* pam.conf options */
int debug = 0;
/* return an error on password expire */
int err_on_exp = 0;
int i;
for (i = 0; i < argc; i++) {
debug = 1;
warn = 0;
err_on_exp = 1;
} else {
"PAM-KRB5 (auth) unrecognized option %s", argv[i]);
}
}
if (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
*/
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.
*/
/*
* 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-KRB5 (auth): stacked more than"
" two times, clearing kmd");
}
if (err != PAM_SUCCESS) {
goto out;
}
/*
* 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-KRB5 (auth): PKINIT succeeded "
"earlier so returning PAM_IGNORE");
}
return (PAM_IGNORE);
}
}
if (do_pkinit) {
/*
* If doing PKINIT it is okay to prompt for the user
* name.
*/
PAM_SUCCESS) {
if (debug) {
"PAM-KRB5 (auth): get user failed: "
}
return (err);
}
} else {
if (debug)
"PAM-KRB5 (auth): user empty or null");
return (PAM_USER_UNKNOWN);
}
}
/* make sure a password entry exists for this user */
return (PAM_USER_UNKNOWN);
goto out;
}
if (err != PAM_SUCCESS) {
goto out;
}
}
goto out;
}
/* we MUST copy this to the heap for the putenv to work! */
goto out;
} else {
goto out;
}
}
}
goto out;
}
/*
* 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.
*/
if (debug)
"PAM-KRB5 (auth): wrong"
"repository found (%s), returning "
return (PAM_IGNORE);
}
if (debug)
"PAM-KRB5 (auth): Principal "
"%s already authenticated",
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 (debug)
"PAM-KRB5 (auth): "
"key for '%s' in keytab, returning IGNORE", user);
result = PAM_IGNORE;
goto out;
}
out:
if (kmd) {
if (debug)
"PAM-KRB5 (auth): pam_sm_auth finalize"
" ccname env, result =%d, env ='%s',"
" age = %d, status = %d",
if (result == PAM_SUCCESS) {
/*
* Put ccname into the pamh so that login
* apps can pick this up when they run
* pam_getenvlist().
*/
!= PAM_SUCCESS) {
/* should not happen but... */
"PAM-KRB5 (auth):"
" pam_putenv failed: result: %d",
result);
goto cleanupccname;
}
} else {
/* for lack of a Solaris unputenv() */
}
}
}
if (debug)
return (result);
}
static krb5_error_code
void *data,
/* ARGSUSED1 */
const char *name,
const char *banner,
int num_prompts,
{
int i;
if (prompts) {
assert(num_prompts > 0);
}
/*
* Because this function should never be used for password prompts,
* disallow password prompts.
*/
for (i = 0; i < num_prompts; i++) {
switch (prompt_type[i]) {
return (rc);
}
}
return (rc);
}
return (rc);
}
sizeof (struct pam_message));
return (rc);
}
for (i = 0; i < num_prompts; i++) {
/* convert krb prompt style to PAM style */
} else {
}
/*
* krb expects the prompting function to append ": " to the
* prompt string.
*/
goto cleanup;
}
goto cleanup;
}
}
/*
* Call PAM conv function to display the prompt.
*/
for (i = 0; i < num_prompts; i++) {
/* convert PAM response to krb prompt reply format */
"Reply too long: "));
goto cleanup;
} else {
char *retp;
/*
* newline must be replaced with \0 terminator
*/
*retp = '\0';
/* NULL terminator should not be counted */
}
}
rc = 0;
}
for (i = 0; i < num_prompts; i++) {
}
/* 0 out sensitive data before free() */
}
}
return (rc);
}
int
char *user,
char **krb5_pass,
{
0,
};
/*
* "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.
*/
"PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
/* need to free context with krb5_free_context */
"PAM-KRB5 (auth): Error initializing "
"krb5: %s",
return (PAM_SYSTEM_ERR);
}
2*MAXHOSTNAMELEN)) != 0) {
/* get_kmd_kuser returns proper PAM error statuses */
return (code);
}
return (PAM_SYSTEM_ERR);
}
/* call krb5_free_cred_contents() on error */
if ((code =
goto out_err;
}
"PAM-KRB5 (auth): attempt_krb5_auth: "
"krb5_build_princ_ext failed: %s",
goto out;
}
goto out_err;
}
"PAM-KRB5 (auth): attempt_krb5_auth: "
"krb5_timeofday failed: %s",
goto out;
}
/*
* set the values for lifetime and rlife to be the maximum
* possible
*/
/*
* Let us get the values for various options
* from Kerberos configuration file
*/
if (renew_timeval) {
"PAM-KRB5 (auth): Bad max_renewable_life "
" value '%s' in Kerberos config file",
goto out;
}
}
if (life_timeval) {
"lifetime value '%s' in Kerberos config file",
goto out;
}
}
/* start timer when request gets to KDC */
if (options & KDC_OPT_RENEWABLE) {
} else
if (code != 0) {
"Error allocating gic opts: %s",
goto out;
}
if (proxiable_flag) { /* Set in config file */
"PAM-KRB5 (auth): Proxiable tickets "
"requested");
}
if (forwardable_flag) {
"PAM-KRB5 (auth): Forwardable tickets "
"requested");
}
if (renewable_flag) {
"PAM-KRB5 (auth): Renewable tickets "
"requested");
}
if (no_address_flag) {
"PAM-KRB5 (auth): Addressless tickets "
"requested");
}
/*
* 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.
*/
/*
* 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.
*/
};
/* treat the krb5_pass as a PIN */
}
if (!code) {
me,
NULL, /* clear text passwd */
pam_krb5_prompter, /* prompter */
pamh, /* prompter data */
0, /* start time */
NULL, /* defaults to krbtgt@REALM */
opts,
&as_reply);
}
} else {
/* invalid PIN */
}
} 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).
*
* Note, do not specify use of KRB5_PADATA_ENC_TIMESTAMP here
* because we don't know what enctypes the client princ has; we
* need the KDC to tell us that.
*/
} else {
/*
* We call our own private version of gic_pwd, because
* expiration, that is found in the as_reply. The
* "prompter" interface is not granular enough for PAM
* to make use of.
*/
me,
*krb5_pass, /* clear text passwd */
NULL, /* prompter */
NULL, /* data */
0, /* start time */
NULL, /* defaults to krbtgt@REALM */
opts,
&as_reply);
}
}
"PAM-KRB5 (auth): attempt_krb5_auth: "
"krb5_get_init_creds_password returns: %s",
switch (code) {
case 0:
/* got a tgt, let's verify it */
if (verify_tik) {
char *fqdn;
NULL,
NULL,
&vopts);
if (code) {
/*
* Give a better error message when the
* keytable entry isn't found or the keytab
* file cannot be found.
*/
fqdn = "<fqdn>";
else
sizeof (kt_name)))
"default keytab",
sizeof (kt_name));
switch (code) {
case KRB5_KT_NOTFOUND:
"PAM-KRB5 (auth): "
"krb5_verify_init_creds failed:"
" Key table entry \"host/%s\""
" not found in %s",
break;
case ENOENT:
"PAM-KRB5 (auth): "
"krb5_verify_init_creds failed:"
" Keytab file \"%s\""
" does not exist.\n",
kt_name);
break;
default:
"PAM-KRB5 (auth): "
"krb5_verify_init_creds failed:"
" %s",
break;
}
if (sp)
}
}
if (code == 0)
break;
/*
* Since this principal is not part of the local
* Kerberos realm, we just return PAM_USER_UNKNOWN.
*/
"PAM-KRB5 (auth): attempt_krb5_auth:"
" User is not part of the local Kerberos"
break;
/*
* 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.
*/
"PAM-KRB5 (auth): "
"attempt_krb5_auth: "
"verifypw %d", code);
}
if (code == 0) {
/*
* pw is good, set age status for
* acct_mgmt.
*/
}
}
}
break;
default:
"PAM-KRB5 (auth): error %d - %s",
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.
*/
"Cannot strdup password");
goto out_err;
}
goto out;
}
/* jump (or reach) here if error and cred cache has been init */
"PAM-KRB5 (auth): clearing initcreds in "
"pam_authenticate()");
out:
if (server)
if (me)
if (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 (opts)
"PAM-KRB5 (auth): attempt_krb5_auth returning %d",
result);
}
/*ARGSUSED*/
void
{
return;
"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.
*/
}
}