/*
* 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 <libintl.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <syslog.h>
#include <libintl.h>
#include <k5-int.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <com_err.h>
#include "utils.h"
#include "krb5_repository.h"
#define PAMTXD "SUNW_OST_SYSOSPAM"
#define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
extern void krb5_cleanup(pam_handle_t *, void *, int);
static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
static int attempt_delete_initcred(krb5_module_data_t *);
static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
krb5_principal, int);
extern uint_t kwarn_add_warning(char *, int);
extern uint_t kwarn_del_warning(char *);
/*
* pam_sm_setcred
*/
int
pam_sm_setcred(
pam_handle_t *pamh,
int flags,
int argc,
const char **argv)
{
int i;
int err = 0;
int debug = 0;
krb5_module_data_t *kmd = NULL;
char *user = NULL;
krb5_repository_data_t *krb5_data = NULL;
pam_repository_t *rep_data = NULL;
for (i = 0; i < argc; i++) {
if (strcasecmp(argv[i], "debug") == 0)
debug = 1;
else if (strcasecmp(argv[i], "nowarn") == 0)
flags = flags | PAM_SILENT;
}
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x",
flags & PAM_SILENT ? 1 : 0, flags);
/* make sure flags are valid */
if (flags &&
!(flags & PAM_ESTABLISH_CRED) &&
!(flags & PAM_REINITIALIZE_CRED) &&
!(flags & PAM_REFRESH_CRED) &&
!(flags & PAM_DELETE_CRED) &&
!(flags & PAM_SILENT)) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): illegal flag %d", flags);
err = PAM_SYSTEM_ERR;
goto out;
}
(void) pam_get_item(pamh, PAM_USER, (void**) &user);
if (user == NULL || *user == '\0')
return (PAM_USER_UNKNOWN);
if (pam_get_data(pamh, KRB5_DATA, (const void**)&kmd) != PAM_SUCCESS) {
if (debug) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): kmd get failed, kmd=0x%p",
kmd);
}
/*
* User doesn't need to authenticate for PAM_REFRESH_CRED
* or for PAM_DELETE_CRED
*/
if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): inst kmd structure");
kmd = calloc(1, sizeof (krb5_module_data_t));
if (kmd == NULL)
return (PAM_BUF_ERR);
/*
* Need to initialize auth_status here to
* PAM_AUTHINFO_UNAVAIL else there is a false positive
* of PAM_SUCCESS.
*/
kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
if ((err = pam_set_data(pamh, KRB5_DATA,
kmd, &krb5_cleanup)) != PAM_SUCCESS) {
free(kmd);
return (PAM_SYSTEM_ERR);
}
} else {
/*
* This could mean that we are not the account authority
* for the authenticated user. Therefore we should
* return PAM_IGNORE in order to not affect the
* login process of said user.
*/
err = PAM_IGNORE;
goto out;
}
} else { /* pam_get_data success */
if (kmd == NULL) {
if (debug) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): kmd structure"
" gotten but is NULL for user %s", user);
}
err = PAM_SYSTEM_ERR;
goto out;
}
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): kmd auth_status: %s",
pam_strerror(pamh, kmd->auth_status));
/*
* pam_auth has set status to ignore, so we also return ignore
*/
if (kmd->auth_status == PAM_IGNORE) {
err = PAM_IGNORE;
goto out;
}
}
kmd->debug = debug;
/*
* User must have passed pam_authenticate()
* in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED
*/
if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) &&
(kmd->auth_status != PAM_SUCCESS)) {
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): unable to "
"setcreds, not authenticated!");
return (PAM_CRED_UNAVAIL);
}
/*
* We cannot assume that kmd->kcontext being non-NULL
* means it is valid. Other pam_krb5 mods may have
* freed it but not reset it to NULL.
* Log a message when debugging to track down memory
* leaks.
*/
if (kmd->kcontext != NULL && kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): kcontext != NULL, "
"possible memory leak.");
/*
* Use the authenticated and validated user, if applicable.
*/
if (kmd->user != NULL)
user = kmd->user;
/*
* If auth was short-circuited we will not have anything to
* renew, so just return here.
*/
(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 (setcred): 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 (setcred): "
"Principal %s already "
"authenticated, "
"cannot setcred",
krb5_data->principal);
return (PAM_SUCCESS);
}
}
}
if (flags & PAM_REINITIALIZE_CRED)
err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED);
else if (flags & PAM_REFRESH_CRED)
err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED);
else if (flags & PAM_DELETE_CRED)
err = attempt_delete_initcred(kmd);
else {
/*
* Default case: PAM_ESTABLISH_CRED
*/
err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED);
}
if (err != PAM_SUCCESS)
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): pam_setcred failed "
"for %s (%s).", user, pam_strerror(pamh, err));
out:
if (kmd && kmd->kcontext) {
/*
* free 'kcontext' field if it is allocated,
* kcontext is local to the operation being performed
* not considered global to the entire pam module.
*/
krb5_free_context(kmd->kcontext);
kmd->kcontext = NULL;
}
/*
* 'kmd' is not freed here, it is handled in krb5_cleanup
*/
if (debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): end: %s",
pam_strerror(pamh, err));
return (err);
}
static int
attempt_refresh_cred(
krb5_module_data_t *kmd,
char *user,
int flag)
{
krb5_principal me;
krb5_principal server;
krb5_error_code code;
char kuser[2*MAXHOSTNAMELEN];
krb5_data tgtname = {
0,
KRB5_TGS_NAME_SIZE,
KRB5_TGS_NAME
};
/* Create a new context here. */
if (krb5_init_secure_context(&kmd->kcontext) != 0) {
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): unable to "
"initialize krb5 context");
return (PAM_SYSTEM_ERR);
}
if (krb5_cc_default(kmd->kcontext, &kmd->ccache) != 0) {
return (PAM_SYSTEM_ERR);
}
if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
2*MAXHOSTNAMELEN)) != 0) {
return (code);
}
if (krb5_parse_name(kmd->kcontext, kuser, &me) != 0) {
return (PAM_SYSTEM_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)) {
krb5_free_principal(kmd->kcontext, me);
return (PAM_SYSTEM_ERR);
}
code = krb5_renew_tgt(kmd, me, server, flag);
krb5_free_principal(kmd->kcontext, server);
krb5_free_principal(kmd->kcontext, me);
if (code) {
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5(setcred): krb5_renew_tgt() "
"failed: %s", error_message((errcode_t)code));
return (PAM_CRED_ERR);
} else {
return (PAM_SUCCESS);
}
}
/*
* This code will update the credential matching "server" in the user's
* credential cache. The flag may be set to one of:
* PAM_REINITIALIZE_CRED/PAM_ESTABLISH_CRED - If we have new credentials then
* create a new cred cache with these credentials else return failure.
* PAM_REFRESH_CRED - If we have new credentials then create a new cred cache
* with these credentials else attempt to renew the credentials.
*
* Note for any of the flags that if a new credential does exist from the
* previous auth pass then this will overwrite any existing credentials in the
* credential cache.
*/
static krb5_error_code
krb5_renew_tgt(
krb5_module_data_t *kmd,
krb5_principal me,
krb5_principal server,
int flag)
{
krb5_error_code retval;
krb5_creds creds;
krb5_creds *renewed_cred = NULL;
char *client_name = NULL;
char *username = NULL;
#define my_creds (kmd->initcreds)
if ((flag != PAM_REFRESH_CRED) &&
(flag != PAM_REINITIALIZE_CRED) &&
(flag != PAM_ESTABLISH_CRED))
return (KRB5KRB_ERR_GENERIC);
/* this is needed only for the ktkt_warnd */
if ((retval = krb5_unparse_name(kmd->kcontext, me, &client_name)) != 0)
return (retval);
(void) memset(&creds, 0, sizeof (krb5_creds));
if ((retval = krb5_copy_principal(kmd->kcontext,
server, &creds.server))) {
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): krb5_copy_principal "
"failed: %s",
error_message((errcode_t)retval));
goto cleanup_creds;
}
/* obtain ticket & session key */
retval = krb5_cc_get_principal(kmd->kcontext,
kmd->ccache, &creds.client);
if (retval && (kmd->debug))
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): User not in cred "
"cache (%s)", error_message((errcode_t)retval));
/*
* We got here either with the ESTABLISH | REINIT | REFRESH flag and
* auth_status returns SUCCESS or REFRESH and auth_status failure.
*
* Rules:
* - If the prior auth pass was successful then store the new
* credentials in the cache, regardless of which flag.
*
* - Else if REFRESH flag is used and there are no new
* credentials then attempt to refresh the existing credentials.
*
* - Note, refresh will not work if "R" flag is not set in
* original credential. We don't want to 2nd guess the
* intention of the person who created the existing credential.
*/
if (kmd->auth_status == PAM_SUCCESS) {
/*
* Create a fresh ccache, and store the credentials
* we got from pam_authenticate()
*/
if ((retval = krb5_cc_initialize(kmd->kcontext,
kmd->ccache, me)) != 0) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): krb5_cc_initialize "
"failed: %s",
error_message((errcode_t)retval));
} else if ((retval = krb5_cc_store_cred(kmd->kcontext,
kmd->ccache, &my_creds)) != 0) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): krb5_cc_store_cred "
"failed: %s",
error_message((errcode_t)retval));
}
} else if ((retval == 0) && (flag & PAM_REFRESH_CRED)) {
/*
* If we only wanted to refresh the creds but failed
* due to expiration, lack of "R" flag, or other
* problems, return an error.
*/
if (retval = krb5_get_credentials_renew(kmd->kcontext,
0, kmd->ccache, &creds, &renewed_cred)) {
if (kmd->debug) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): "
"krb5_get_credentials"
"_renew(update) failed: %s",
error_message((errcode_t)retval));
}
}
} else {
/*
* We failed to get the user's credentials.
* This might be due to permission error on the cache,
* or maybe we are looking in the wrong cache file!
*/
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): Cannot find creds"
" for %s (%s)",
client_name ? client_name : "(unknown)",
error_message((errcode_t)retval));
}
cleanup_creds:
if ((retval == 0) && (client_name != NULL)) {
/*
* Credential update was successful!
*
* We now chown the ccache to the appropriate uid/gid
* combination, if its a FILE based ccache.
*/
if (!kmd->env || strstr(kmd->env, "FILE:")) {
uid_t uuid;
gid_t ugid;
char *tmpname = NULL;
char *filepath = NULL;
username = strdup(client_name);
if (username == NULL) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): Out of memory");
retval = KRB5KRB_ERR_GENERIC;
goto error;
}
if ((tmpname = strchr(username, '@')))
*tmpname = '\0';
if (get_pw_uid(username, &uuid) == 0 ||
get_pw_gid(username, &ugid) == 0) {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): Unable to "
"find matching uid/gid pair for user `%s'",
username);
retval = KRB5KRB_ERR_GENERIC;
goto error;
}
if (!kmd->env) {
char buffer[512];
if (snprintf(buffer, sizeof (buffer),
"%s=FILE:/tmp/krb5cc_%d", KRB5_ENV_CCNAME,
(int)uuid) >= sizeof (buffer)) {
retval = KRB5KRB_ERR_GENERIC;
goto error;
}
/*
* We MUST copy this to the heap for the putenv
* to work!
*/
kmd->env = strdup(buffer);
if (!kmd->env) {
retval = ENOMEM;
goto error;
} else {
if (putenv(kmd->env)) {
retval = ENOMEM;
goto error;
}
}
}
/*
* We know at this point that kmd->env must start
* with the literal string "FILE:". Set filepath
* character string to point to ":"
*/
filepath = strchr(kmd->env, ':');
/*
* Now check if first char after ":" is null char
*/
if (filepath[1] == '\0') {
__pam_log(LOG_AUTH | LOG_ERR,
"PAM-KRB5 (setcred): Invalid pathname "
"for credential cache of user `%s'",
username);
retval = KRB5KRB_ERR_GENERIC;
goto error;
}
if (chown(filepath+1, uuid, ugid)) {
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): chown to user "
"`%s' failed for FILE=%s",
username, filepath);
}
}
}
error:
if (retval == 0) {
krb5_timestamp endtime;
if (renewed_cred && renewed_cred->times.endtime != 0)
endtime = renewed_cred->times.endtime;
else
endtime = my_creds.times.endtime;
if (kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): delete/add warning");
if (kwarn_del_warning(client_name) != 0) {
__pam_log(LOG_AUTH | LOG_NOTICE,
"PAM-KRB5 (setcred): kwarn_del_warning"
" failed: ktkt_warnd(1M) down?");
}
if (kwarn_add_warning(client_name, endtime) != 0) {
__pam_log(LOG_AUTH | LOG_NOTICE,
"PAM-KRB5 (setcred): kwarn_add_warning"
" failed: ktkt_warnd(1M) down?");
}
}
if (renewed_cred != NULL)
krb5_free_creds(kmd->kcontext, renewed_cred);
if (client_name != NULL)
free(client_name);
if (username)
free(username);
krb5_free_cred_contents(kmd->kcontext, &creds);
return (retval);
}
/*
* Delete the user's credentials for this session
*/
static int
attempt_delete_initcred(krb5_module_data_t *kmd)
{
if (kmd == NULL)
return (PAM_SUCCESS);
if (kmd->debug) {
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): deleting user's "
"credentials (initcreds)");
}
krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
return (PAM_SUCCESS);
}