#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
extern void krb5_cleanup(pam_handle_t *, void *, int);
static int attempt_refresh_cred(krb5_module_data_t *, const 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 *);
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;
const char *user = NULL;
krb5_repository_data_t *krb5_data = NULL;
const 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);
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, (const 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);
}
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);
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 {
err = PAM_IGNORE;
goto out;
}
} else {
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));
if (kmd->auth_status == PAM_IGNORE) {
err = PAM_IGNORE;
goto out;
}
}
kmd->debug = debug;
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);
}
if (kmd->kcontext != NULL && kmd->debug)
__pam_log(LOG_AUTH | LOG_DEBUG,
"PAM-KRB5 (setcred): kcontext != NULL, "
"possible memory leak.");
if (kmd->user != NULL)
user = kmd->user;
(void) pam_get_item(pamh, PAM_REPOSITORY, (const 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 {
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) {
krb5_free_context(kmd->kcontext);
kmd->kcontext = NULL;
}
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,
const 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
};
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, 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);
}
}
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);
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;
}
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));
if (kmd->auth_status == PAM_SUCCESS) {
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 (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 {
__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)) {
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;
}
kmd->env = strdup(buffer);
if (!kmd->env) {
retval = ENOMEM;
goto error;
} else {
if (putenv(kmd->env)) {
retval = ENOMEM;
goto error;
}
}
}
filepath = strchr(kmd->env, ':');
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(8) down?");
}
if (kwarn_add_warning(client_name, endtime) != 0) {
__pam_log(LOG_AUTH | LOG_NOTICE,
"PAM-KRB5 (setcred): kwarn_add_warning"
" failed: ktkt_warnd(8) 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);
}
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);
}