/* * Copyright (c) 1995 - 2001 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include RCSID("$Id: pam.c,v 1.28 2002/09/09 15:57:24 joda Exp $"); #endif #include #include #include #include #include #include #include #include #include #ifndef PAM_AUTHTOK_RECOVERY_ERR /* Fix linsux typo. */ #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR #endif #include #include #include #if 0 /* Debugging PAM modules is a royal pain, truss helps. */ #define DEBUG(msg) (access(msg " at line", __LINE__)) #endif static void psyslog(int level, const char *format, ...) { va_list args; va_start(args, format); openlog("pam_krb4", LOG_PID, LOG_AUTH); vsyslog(level, format, args); va_end(args); closelog(); } enum { KRB4_DEBUG, KRB4_USE_FIRST_PASS, KRB4_TRY_FIRST_PASS, KRB4_IGNORE_ROOT, KRB4_NO_VERIFY, KRB4_REAFSLOG, KRB4_CTRLS /* Number of ctrl arguments defined. */ }; #define KRB4_DEFAULTS 0 static int ctrl_flags = KRB4_DEFAULTS; #define ctrl_on(x) (krb4_args[x].flag & ctrl_flags) #define ctrl_off(x) (!ctrl_on(x)) typedef struct { const char *token; unsigned int flag; } krb4_ctrls_t; static krb4_ctrls_t krb4_args[KRB4_CTRLS] = { /* KRB4_DEBUG */ { "debug", 0x01 }, /* KRB4_USE_FIRST_PASS */ { "use_first_pass", 0x02 }, /* KRB4_TRY_FIRST_PASS */ { "try_first_pass", 0x04 }, /* KRB4_IGNORE_ROOT */ { "ignore_root", 0x08 }, /* KRB4_NO_VERIFY */ { "no_verify", 0x10 }, /* KRB4_REAFSLOG */ { "reafslog", 0x20 }, }; static void parse_ctrl(int argc, const char **argv) { int i, j; ctrl_flags = KRB4_DEFAULTS; for (i = 0; i < argc; i++) { for (j = 0; j < KRB4_CTRLS; j++) if (strcmp(argv[i], krb4_args[j].token) == 0) break; if (j >= KRB4_CTRLS) psyslog(LOG_ALERT, "unrecognized option [%s]", *argv); else ctrl_flags |= krb4_args[j].flag; } } static void pdeb(const char *format, ...) { va_list args; if (ctrl_off(KRB4_DEBUG)) return; va_start(args, format); openlog("pam_krb4", LOG_PID, LOG_AUTH); vsyslog(LOG_DEBUG, format, args); va_end(args); closelog(); } #define ENTRY(func) pdeb("%s() flags = %d ruid = %d euid = %d", func, flags, getuid(), geteuid()) static void set_tkt_string(uid_t uid) { char buf[128]; snprintf(buf, sizeof(buf), "%s%u", TKT_ROOT, (unsigned)uid); krb_set_tkt_string(buf); #if 0 /* pam_set_data+pam_get_data are not guaranteed to work, grr. */ pam_set_data(pamh, "KRBTKFILE", strdup(t), cleanup); if (pam_get_data(pamh, "KRBTKFILE", (const void**)&tkt) == PAM_SUCCESS) { pam_putenv(pamh, var); } #endif /* We don't want to inherit this variable. * If we still do, it must have a sane value. */ if (getenv("KRBTKFILE") != 0) { char *var = malloc(sizeof(buf)); snprintf(var, sizeof(buf), "KRBTKFILE=%s", tkt_string()); putenv(var); /* free(var); XXX */ } } static int verify_pass(pam_handle_t *pamh, const char *name, const char *inst, const char *pass) { char realm[REALM_SZ]; int ret, krb_verify, old_euid, old_ruid; krb_get_lrealm(realm, 1); if (ctrl_on(KRB4_NO_VERIFY)) krb_verify = KRB_VERIFY_SECURE_FAIL; else krb_verify = KRB_VERIFY_SECURE; old_ruid = getuid(); old_euid = geteuid(); setreuid(0, 0); ret = krb_verify_user(name, inst, realm, pass, krb_verify, NULL); pdeb("krb_verify_user(`%s', `%s', `%s', pw, %d, NULL) returns %s", name, inst, realm, krb_verify, krb_get_err_text(ret)); setreuid(old_ruid, old_euid); if (getuid() != old_ruid || geteuid() != old_euid) { psyslog(LOG_ALERT , "setreuid(%d, %d) failed at line %d", old_ruid, old_euid, __LINE__); exit(1); } switch(ret) { case KSUCCESS: return PAM_SUCCESS; case KDC_PR_UNKNOWN: return PAM_USER_UNKNOWN; case SKDC_CANT: case SKDC_RETRY: case RD_AP_TIME: return PAM_AUTHINFO_UNAVAIL; default: return PAM_AUTH_ERR; } } static int krb4_auth(pam_handle_t *pamh, int flags, const char *name, const char *inst, struct pam_conv *conv) { struct pam_response *resp; char prompt[128]; struct pam_message msg, *pmsg = &msg; int ret; if (ctrl_on(KRB4_TRY_FIRST_PASS) || ctrl_on(KRB4_USE_FIRST_PASS)) { char *pass = 0; ret = pam_get_item(pamh, PAM_AUTHTOK, (void **) &pass); if (ret != PAM_SUCCESS) { psyslog(LOG_ERR , "pam_get_item returned error to get-password"); return ret; } else if (pass != 0 && verify_pass(pamh, name, inst, pass) == PAM_SUCCESS) return PAM_SUCCESS; else if (ctrl_on(KRB4_USE_FIRST_PASS)) return PAM_AUTHTOK_RECOVERY_ERR; /* Wrong password! */ else /* We tried the first password but it didn't work, cont. */; } msg.msg_style = PAM_PROMPT_ECHO_OFF; if (*inst == 0) snprintf(prompt, sizeof(prompt), "%s's Password: ", name); else snprintf(prompt, sizeof(prompt), "%s.%s's Password: ", name, inst); msg.msg = prompt; ret = conv->conv(1, &pmsg, &resp, conv->appdata_ptr); if (ret != PAM_SUCCESS) return ret; ret = verify_pass(pamh, name, inst, resp->resp); if (ret == PAM_SUCCESS) { memset(resp->resp, 0, strlen(resp->resp)); /* Erase password! */ free(resp->resp); free(resp); } else { pam_set_item(pamh, PAM_AUTHTOK, resp->resp); /* Save password. */ /* free(resp->resp); XXX */ /* free(resp); XXX */ } return ret; } int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { char *user; int ret; struct pam_conv *conv; struct passwd *pw; uid_t uid = -1; const char *name, *inst; char realm[REALM_SZ]; realm[0] = 0; parse_ctrl(argc, argv); ENTRY("pam_sm_authenticate"); ret = pam_get_user(pamh, &user, "login: "); if (ret != PAM_SUCCESS) return ret; if (ctrl_on(KRB4_IGNORE_ROOT) && strcmp(user, "root") == 0) return PAM_AUTHINFO_UNAVAIL; ret = pam_get_item(pamh, PAM_CONV, (void*)&conv); if (ret != PAM_SUCCESS) return ret; pw = getpwnam(user); if (pw != 0) { uid = pw->pw_uid; set_tkt_string(uid); } if (strcmp(user, "root") == 0 && getuid() != 0) { pw = getpwuid(getuid()); if (pw != 0) { name = strdup(pw->pw_name); inst = "root"; } } else { name = user; inst = ""; } ret = krb4_auth(pamh, flags, name, inst, conv); /* * The realm was lost inside krb_verify_user() so we can't simply do * a krb_kuserok() when inst != "". */ if (ret == PAM_SUCCESS && inst[0] != 0) { uid_t old_euid = geteuid(); uid_t old_ruid = getuid(); setreuid(0, 0); /* To read ticket file. */ if (krb_get_tf_fullname(tkt_string(), 0, 0, realm) != KSUCCESS) ret = PAM_SERVICE_ERR; else if (krb_kuserok(name, inst, realm, user) != KSUCCESS) { setreuid(0, uid); /* To read ~/.klogin. */ if (krb_kuserok(name, inst, realm, user) != KSUCCESS) ret = PAM_PERM_DENIED; } if (ret != PAM_SUCCESS) { dest_tkt(); /* Passwd known, ok to kill ticket. */ psyslog(LOG_NOTICE, "%s.%s@%s is not allowed to log in as %s", name, inst, realm, user); } setreuid(old_ruid, old_euid); if (getuid() != old_ruid || geteuid() != old_euid) { psyslog(LOG_ALERT , "setreuid(%d, %d) failed at line %d", old_ruid, old_euid, __LINE__); exit(1); } } if (ret == PAM_SUCCESS) { psyslog(LOG_INFO, "%s.%s@%s authenticated as user %s", name, inst, realm, user); if (chown(tkt_string(), uid, -1) == -1) { dest_tkt(); psyslog(LOG_ALERT , "chown(%s, %d, -1) failed", tkt_string(), uid); exit(1); } } /* * Kludge alert!!! Sun dtlogin unlock screen fails to call * pam_setcred(3) with PAM_REFRESH_CRED after a successful * authentication attempt, sic. * * This hack is designed as a workaround to that problem. */ if (ctrl_on(KRB4_REAFSLOG)) if (ret == PAM_SUCCESS) pam_sm_setcred(pamh, PAM_REFRESH_CRED, argc, argv); return ret; } int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { parse_ctrl(argc, argv); ENTRY("pam_sm_setcred"); switch (flags & ~PAM_SILENT) { case 0: case PAM_ESTABLISH_CRED: if (k_hasafs()) k_setpag(); /* Fall through, fill PAG with credentials below. */ case PAM_REINITIALIZE_CRED: case PAM_REFRESH_CRED: if (k_hasafs()) { void *user = 0; if (pam_get_item(pamh, PAM_USER, &user) == PAM_SUCCESS) { struct passwd *pw = getpwnam((char *)user); if (pw != 0) krb_afslog_uid_home(/*cell*/ 0,/*realm_hint*/ 0, pw->pw_uid, pw->pw_dir); } } break; case PAM_DELETE_CRED: dest_tkt(); if (k_hasafs()) k_unlog(); break; default: psyslog(LOG_ALERT , "pam_sm_setcred: unknown flags 0x%x", flags); break; } return PAM_SUCCESS; } int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { parse_ctrl(argc, argv); ENTRY("pam_sm_open_session"); return PAM_SUCCESS; } int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char**argv) { parse_ctrl(argc, argv); ENTRY("pam_sm_close_session"); /* This isn't really kosher, but it's handy. */ pam_sm_setcred(pamh, PAM_DELETE_CRED, argc, argv); return PAM_SUCCESS; }