freebsd-dev/lib/libpam/modules/pam_krb5/pam_krb5_auth.c
Mark Murray 3741d46458 Update to the same code as in the pam_krb5.so port.
According to Peter, the port works - this needs more testing.
2001-07-17 07:34:36 +00:00

476 lines
13 KiB
C

/*
* pam_krb5_auth.c
*
* PAM authentication management functions for pam_krb5
*
* $FreeBSD$
*/
static const char rcsid[] = "$Id: pam_krb5_auth.c,v 1.18 2000/01/04 08:44:08 fcusack Exp $";
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h> /* PATH_MAX */
#include <pwd.h> /* getpwnam */
#include <stdio.h> /* tmpnam */
#include <stdlib.h> /* malloc */
#include <strings.h> /* strchr */
#include <syslog.h> /* syslog */
#include <unistd.h> /* chown */
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <krb5.h>
#include <com_err.h>
#include "pam_krb5.h"
extern krb5_cc_ops krb5_mcc_ops;
/* A useful logging macro */
#define DLOG(error_func, error_msg) \
if (debug) \
syslog(LOG_DEBUG, "pam_krb5: pam_sm_authenticate(%s %s): %s: %s", \
service, name, error_func, error_msg)
/* Authenticate a user via krb5 */
int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
krb5_error_code krbret;
krb5_context pam_context;
krb5_creds creds;
krb5_principal princ;
krb5_ccache ccache, ccache_check;
krb5_get_init_creds_opt opts;
int pamret, i;
const char *name;
char *princ_name = NULL;
char *pass = NULL, *service = NULL;
char *prompt = NULL;
char cache_name[L_tmpnam + 8];
char lname[64]; /* local acct name */
struct passwd *pw;
int debug = 0, try_first_pass = 0, use_first_pass = 0;
int forwardable = 0, reuse_ccache = 0, no_ccache = 0;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0)
debug = 1;
else if (strcmp(argv[i], "try_first_pass") == 0)
try_first_pass = 1;
else if (strcmp(argv[i], "use_first_pass") == 0)
use_first_pass = 1;
else if (strcmp(argv[i], "forwardable") == 0)
forwardable = 1;
else if (strcmp(argv[i], "reuse_ccache") == 0)
reuse_ccache = 1;
else if (strcmp(argv[i], "no_ccache") == 0)
no_ccache = 1;
}
/* Get username */
if ((pamret = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS) {
return PAM_SERVICE_ERR;
}
/* Get service name */
(void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
if (!service)
service = "unknown";
DLOG("entry", "");
if ((krbret = krb5_init_context(&pam_context)) != 0) {
DLOG("krb5_init_context()", error_message(krbret));
return PAM_SERVICE_ERR;
}
krb5_get_init_creds_opt_init(&opts);
memset(&creds, 0, sizeof(krb5_creds));
memset(cache_name, 0, sizeof(cache_name));
memset(lname, 0, sizeof(lname));
if (forwardable)
krb5_get_init_creds_opt_set_forwardable(&opts, 1);
/* For CNS */
if ((krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE)) != 0) {
/* Solaris dtlogin doesn't call pam_end() on failure */
if (krbret != KRB5_CC_TYPE_EXISTS) {
DLOG("krb5_cc_register()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup3;
}
}
/* Get principal name */
if ((krbret = krb5_parse_name(pam_context, name, &princ)) != 0) {
DLOG("krb5_parse_name()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup3;
}
/* Now convert the principal name into something human readable */
if ((krbret = krb5_unparse_name(pam_context, princ, &princ_name)) != 0) {
DLOG("krb5_unparse_name()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
/* Get password */
prompt = malloc(16 + strlen(princ_name));
if (!prompt) {
DLOG("malloc()", "failure");
pamret = PAM_BUF_ERR;
goto cleanup2;
}
(void) sprintf(prompt, "Password for %s: ", princ_name);
if (try_first_pass || use_first_pass)
(void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
get_pass:
if (!pass) {
try_first_pass = 0;
if ((pamret = get_user_info(pamh, prompt, PAM_PROMPT_ECHO_OFF,
&pass)) != 0) {
DLOG("get_user_info()", pam_strerror(pamh, pamret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
/* We have to free pass. */
if ((pamret = pam_set_item(pamh, PAM_AUTHTOK, pass)) != 0) {
DLOG("pam_set_item()", pam_strerror(pamh, pamret));
free(pass);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
free(pass);
/* Now we get it back from the library. */
(void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
}
/* Verify the local user exists (AFTER getting the password) */
if (strchr(name, '@')) {
/* get a local account name for this principal */
if ((krbret = krb5_aname_to_localname(pam_context, princ,
sizeof(lname), lname)) != 0) {
DLOG("krb5_aname_to_localname()", error_message(krbret));
pamret = PAM_USER_UNKNOWN;
goto cleanup2;
}
DLOG("changing PAM_USER to", lname);
if ((pamret = pam_set_item(pamh, PAM_USER, lname)) != 0) {
DLOG("pam_set_item()", pam_strerror(pamh, pamret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
if ((pamret = pam_get_item(pamh, PAM_USER, (const void **) &name)
!= 0)) {
DLOG("pam_get_item()", pam_strerror(pamh, pamret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
}
pw = getpwnam(name);
if (!pw) {
DLOG("getpwnam()", lname);
pamret = PAM_USER_UNKNOWN;
goto cleanup2;
}
/* Get a TGT */
if ((krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
pass, pam_prompter, pamh, 0, NULL, &opts)) != 0) {
DLOG("krb5_get_init_creds_password()", error_message(krbret));
if (try_first_pass && krbret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
pass = NULL;
goto get_pass;
}
pamret = PAM_AUTH_ERR;
goto cleanup2;
}
/* Generate a unique cache_name */
strcpy(cache_name, "MEMORY:");
(void) tmpnam(&cache_name[7]);
if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache)) != 0) {
DLOG("krb5_cc_resolve()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup;
}
if ((krbret = krb5_cc_initialize(pam_context, ccache, princ)) != 0) {
DLOG("krb5_cc_initialize()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup;
}
if ((krbret = krb5_cc_store_cred(pam_context, ccache, &creds)) != 0) {
DLOG("krb5_cc_store_cred()", error_message(krbret));
(void) krb5_cc_destroy(pam_context, ccache);
pamret = PAM_SERVICE_ERR;
goto cleanup;
}
/* Verify it */
if (verify_krb_v5_tgt(pam_context, ccache, service, debug) == -1) {
(void) krb5_cc_destroy(pam_context, ccache);
pamret = PAM_AUTH_ERR;
goto cleanup;
}
/* A successful authentication, store ccache for sm_setcred() */
if (!pam_get_data(pamh, "ccache", (const void **) &ccache_check)) {
DLOG("pam_get_data()", "ccache data already present");
(void) krb5_cc_destroy(pam_context, ccache);
pamret = PAM_AUTH_ERR;
goto cleanup;
}
if ((pamret = pam_set_data(pamh, "ccache", ccache, cleanup_cache)) != 0) {
DLOG("pam_set_data()", pam_strerror(pamh, pamret));
(void) krb5_cc_destroy(pam_context, ccache);
pamret = PAM_SERVICE_ERR;
goto cleanup;
}
cleanup:
krb5_free_cred_contents(pam_context, &creds);
cleanup2:
krb5_free_principal(pam_context, princ);
cleanup3:
if (prompt)
free(prompt);
if (princ_name)
free(princ_name);
krb5_free_context(pam_context);
DLOG("exit", pamret ? "failure" : "success");
return pamret;
}
/* redefine this for pam_sm_setcred() */
#undef DLOG
#define DLOG(error_func, error_msg) \
if (debug) \
syslog(LOG_DEBUG, "pam_krb5: pam_sm_setcred(%s %s): %s: %s", \
service, name, error_func, error_msg)
/* Called after a successful authentication. Set user credentials. */
int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
krb5_error_code krbret;
krb5_context pam_context;
krb5_principal princ;
krb5_creds creds;
krb5_ccache ccache_temp, ccache_perm;
krb5_cc_cursor cursor;
int i, pamret;
char *name, *service = NULL;
char *cache_name = NULL, *cache_env_name;
struct passwd *pw = NULL;
int debug = 0;
uid_t euid;
gid_t egid;
if (flags == PAM_REINITIALIZE_CRED)
return PAM_SUCCESS; /* XXX Incorrect behavior */
if (flags != PAM_ESTABLISH_CRED)
return PAM_SERVICE_ERR;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0)
debug = 1;
else if (strcmp(argv[i], "no_ccache") == 0)
return PAM_SUCCESS;
else if (strstr(argv[i], "ccache=") == argv[i])
cache_name = (char *) &argv[i][7]; /* save for later */
}
/* Get username */
if (pam_get_item(pamh, PAM_USER, (const void **) &name)) {
return PAM_SERVICE_ERR;
}
/* Get service name */
(void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
if (!service)
service = "unknown";
DLOG("entry", "");
if ((krbret = krb5_init_context(&pam_context)) != 0) {
DLOG("krb5_init_context()", error_message(krbret));
return PAM_SERVICE_ERR;
}
euid = geteuid(); /* Usually 0 */
egid = getegid();
/* Retrieve the cache name */
if ((pamret = pam_get_data(pamh, "ccache", (const void **) &ccache_temp))
!= 0) {
DLOG("pam_get_data()", pam_strerror(pamh, pamret));
pamret = PAM_CRED_UNAVAIL;
goto cleanup3;
}
/* Get the uid. This should exist. */
pw = getpwnam(name);
if (!pw) {
DLOG("getpwnam()", name);
pamret = PAM_USER_UNKNOWN;
goto cleanup3;
}
/* Avoid following a symlink as root */
if (setegid(pw->pw_gid)) {
DLOG("setegid()", name); /* XXX should really log group name or id */
pamret = PAM_SERVICE_ERR;
goto cleanup3;
}
if (seteuid(pw->pw_uid)) {
DLOG("seteuid()", name);
pamret = PAM_SERVICE_ERR;
goto cleanup3;
}
/* Get the cache name */
if (!cache_name) {
cache_name = malloc(64); /* plenty big */
if (!cache_name) {
DLOG("malloc()", "failure");
pamret = PAM_BUF_ERR;
goto cleanup3;
}
sprintf(cache_name, "FILE:/tmp/krb5cc_%d", pw->pw_uid);
} else {
/* cache_name was supplied */
char *p = calloc(PATH_MAX + 10, 1); /* should be plenty */
char *q = cache_name;
if (!p) {
DLOG("malloc()", "failure");
pamret = PAM_BUF_ERR;
goto cleanup3;
}
cache_name = p;
/* convert %u and %p */
while (*q) {
if (*q == '%') {
q++;
if (*q == 'u') {
sprintf(p, "%d", pw->pw_uid);
p += strlen(p);
} else if (*q == 'p') {
sprintf(p, "%d", getpid());
p += strlen(p);
} else {
/* Not a special token */
*p++ = '%';
q--;
}
q++;
} else {
*p++ = *q++;
}
}
}
/* Initialize the new ccache */
if ((krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ))
!= 0) {
DLOG("krb5_cc_get_principal()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup3;
}
if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm))
!= 0) {
DLOG("krb5_cc_resolve()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
if ((krbret = krb5_cc_initialize(pam_context, ccache_perm, princ)) != 0) {
DLOG("krb5_cc_initialize()", error_message(krbret));
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
/* Prepare for iteration over creds */
if ((krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor))
!= 0) {
DLOG("krb5_cc_start_seq_get()", error_message(krbret));
(void) krb5_cc_destroy(pam_context, ccache_perm);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
/* Copy the creds (should be two of them) */
while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
&cursor, &creds) == 0)) {
if ((krbret = krb5_cc_store_cred(pam_context, ccache_perm,
&creds)) != 0) {
DLOG("krb5_cc_store_cred()", error_message(krbret));
(void) krb5_cc_destroy(pam_context, ccache_perm);
krb5_free_cred_contents(pam_context, &creds);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
krb5_free_cred_contents(pam_context, &creds);
}
(void) krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
if (strstr(cache_name, "FILE:") == cache_name) {
if (chown(&cache_name[5], pw->pw_uid, pw->pw_gid) == -1) {
DLOG("chown()", strerror(errno));
(void) krb5_cc_destroy(pam_context, ccache_perm);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
if (chmod(&cache_name[5], (S_IRUSR|S_IWUSR)) == -1) {
DLOG("chmod()", strerror(errno));
(void) krb5_cc_destroy(pam_context, ccache_perm);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
}
(void) krb5_cc_close(pam_context, ccache_perm);
cache_env_name = malloc(strlen(cache_name) + 12);
if (!cache_env_name) {
DLOG("malloc()", "failure");
(void) krb5_cc_destroy(pam_context, ccache_perm);
pamret = PAM_BUF_ERR;
goto cleanup2;
}
sprintf(cache_env_name, "KRB5CCNAME=%s", cache_name);
if ((pamret = pam_putenv(pamh, cache_env_name)) != 0) {
DLOG("pam_putenv()", pam_strerror(pamh, pamret));
(void) krb5_cc_destroy(pam_context, ccache_perm);
pamret = PAM_SERVICE_ERR;
goto cleanup2;
}
cleanup2:
krb5_free_principal(pam_context, princ);
cleanup3:
krb5_free_context(pam_context);
DLOG("exit", pamret ? "failure" : "success");
(void) seteuid(euid);
(void) setegid(egid);
return pamret;
}