summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomas Mraz <tm@t8m.info>2008-01-04 17:46:45 +0000
committerTomas Mraz <tm@t8m.info>2008-01-04 17:46:45 +0000
commitc8c84e2bf94a4172ad104212e0cd739dbf12eb49 (patch)
tree5c1603c33f36e90308db8d0cb7cb21570774730d
parentc6912ac61ece224b03cf81499cf6d76add564c8a (diff)
downloadlinux-pam-git-pam_unix_ref_branch.tar.gz
Relevant BUGIDs:pam_unix_ref_branch
Purpose of commit: new feature Commit summary: --------------- * modules/pam_unix/Makefile.am: Add unix_update helper. * modules/pam_unix/pam_unix_passwd.c: Move functions i64c(), crypt_md5_wrapper(), save_old_password(), _update_passwd() and _update_shadow() to passverify.c file. Rename _unix_run_shadow_binary() to _unix_run_update_binary(), which also verifies old password and does all writing. (_do_setpass, pam_sm_chauthtok): lckpwdf()->lock_pwdf(), the same for unlock. Call _unix_run_update_binary() appropriately. _update_passwd()->unix_update_passwd(), the same for shadow. * modules/pam_unix/passverify.c: Add new functions moved from pam_unix_passwd.c and unix_chkpwd.c. * modules/pam_unix/passverify.h: Likewise. * modules/pam_unix/unix_chkpwd.c: Remove SELinux checks. Move su_sighandler(), setup_signals(), getuidname() to passverify.c. (main): Remove 'shadow' option. Refactor out read_passwords() and call it. More strict checking how the binary is called. * modules/pam_unix/unix_update.c: New helper binary - non-setuid, called from SELinux confined apps only.
-rw-r--r--modules/pam_unix/Makefile.am11
-rw-r--r--modules/pam_unix/pam_unix_passwd.c561
-rw-r--r--modules/pam_unix/passverify.c687
-rw-r--r--modules/pam_unix/passverify.h44
-rw-r--r--modules/pam_unix/unix_chkpwd.c277
-rw-r--r--modules/pam_unix/unix_update.c194
6 files changed, 984 insertions, 790 deletions
diff --git a/modules/pam_unix/Makefile.am b/modules/pam_unix/Makefile.am
index 1770a375..34c5d8d2 100644
--- a/modules/pam_unix/Makefile.am
+++ b/modules/pam_unix/Makefile.am
@@ -16,7 +16,8 @@ securelibdir = $(SECUREDIR)
secureconfdir = $(SCONFIGDIR)
AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \
- -DCHKPWD_HELPER=\"$(sbindir)/unix_chkpwd\"
+ -DCHKPWD_HELPER=\"$(sbindir)/unix_chkpwd\" \
+ -DUPDATE_HELPER=\"$(sbindir)/unix_update\"
if HAVE_LIBSELINUX
AM_CFLAGS += -D"WITH_SELINUX"
@@ -36,7 +37,7 @@ securelib_LTLIBRARIES = pam_unix.la
noinst_HEADERS = md5.h support.h yppasswd.h bigcrypt.h passverify.h
-sbin_PROGRAMS = unix_chkpwd
+sbin_PROGRAMS = unix_chkpwd unix_update
noinst_PROGRAMS = bigcrypt
@@ -54,6 +55,12 @@ unix_chkpwd_CFLAGS = $(AM_CFLAGS) @PIE_CFLAGS@ -DHELPER_COMPILE=\"unix_chkpwd\"
unix_chkpwd_LDFLAGS = @PIE_LDFLAGS@
unix_chkpwd_LDADD = @LIBCRYPT@ @LIBSELINUX@
+unix_update_SOURCES = unix_update.c md5_good.c md5_broken.c bigcrypt.c \
+ passverify.c
+unix_update_CFLAGS = $(AM_CFLAGS) @PIE_CFLAGS@ -DHELPER_COMPILE=\"unix_update\"
+unix_update_LDFLAGS = @PIE_LDFLAGS@
+unix_update_LDADD = @LIBCRYPT@ @LIBSELINUX@
+
if ENABLE_REGENERATE_MAN
noinst_DATA = README
README: pam_unix.8.xml
diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c
index d61d2ce6..18b3d7b5 100644
--- a/modules/pam_unix/pam_unix_passwd.c
+++ b/modules/pam_unix/pam_unix_passwd.c
@@ -2,6 +2,7 @@
* Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
* Copyright (C) 1996.
* Copyright (c) Jan Rêkorajski, 1999.
+ * Copyright (c) Red Hat, Inc., 2007, 2008.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -63,7 +64,6 @@
#ifdef WITH_SELINUX
static int selinux_enabled=-1;
#include <selinux/selinux.h>
-static security_context_t prev_context=NULL;
#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
#endif
@@ -93,15 +93,6 @@ extern int getrpcport(const char *host, unsigned long prognum,
#endif /* GNU libc 2.1 */
/*
- * PAM framework looks for these entry-points to pass control to the
- * password changing module.
- */
-
-#if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
-# include "./lckpwdf.-c"
-#endif
-
-/*
How it works:
Gets in username (has to be done) from the calling program
Does authentication of user (only if we are not running as root)
@@ -109,82 +100,15 @@ extern int getrpcport(const char *host, unsigned long prognum,
Sets it.
*/
-/* passwd/salt conversion macros */
-
-#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
-#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
-
/* data tokens */
#define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
#define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
#define MAX_PASSWD_TRIES 3
-#define PW_TMPFILE "/etc/npasswd"
-#define SH_TMPFILE "/etc/nshadow"
#ifndef CRACKLIB_DICTS
#define CRACKLIB_DICTS NULL
#endif
-#define OPW_TMPFILE "/etc/security/nopasswd"
-#define OLD_PASSWORDS_FILE "/etc/security/opasswd"
-
-/*
- * i64c - convert an integer to a radix 64 character
- */
-static int i64c(int i)
-{
- if (i < 0)
- return ('.');
- else if (i > 63)
- return ('z');
- if (i == 0)
- return ('.');
- if (i == 1)
- return ('/');
- if (i >= 2 && i <= 11)
- return ('0' - 2 + i);
- if (i >= 12 && i <= 37)
- return ('A' - 12 + i);
- if (i >= 38 && i <= 63)
- return ('a' - 38 + i);
- return ('\0');
-}
-
-static char *crypt_md5_wrapper(const char *pass_new)
-{
- /*
- * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
- * removed use of static variables (AGM)
- */
-
- struct timeval tv;
- MD5_CTX ctx;
- unsigned char result[16];
- char *cp = (char *) result;
- unsigned char tmp[16];
- int i;
- char *x = NULL;
-
- GoodMD5Init(&ctx);
- gettimeofday(&tv, (struct timezone *) 0);
- GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
- i = getpid();
- GoodMD5Update(&ctx, (void *) &i, sizeof i);
- i = clock();
- GoodMD5Update(&ctx, (void *) &i, sizeof i);
- GoodMD5Update(&ctx, result, sizeof result);
- GoodMD5Final(tmp, &ctx);
- strcpy(cp, "$1$"); /* magic for the MD5 */
- cp += strlen(cp);
- for (i = 0; i < 8; i++)
- *cp++ = i64c(tmp[i] & 077);
- *cp = '\0';
-
- /* no longer need cleartext */
- x = Goodcrypt_md5(pass_new, (const char *) result);
-
- return x;
-}
static char *getNISserver(pam_handle_t *pamh)
{
@@ -217,8 +141,8 @@ static char *getNISserver(pam_handle_t *pamh)
}
#ifdef WITH_SELINUX
-
-static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
+static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user,
+ const char *fromwhat, const char *towhat, int remember)
{
int retval, child, fds[2];
void (*sighandler)(int) = NULL;
@@ -248,7 +172,8 @@ static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const
size_t i=0;
struct rlimit rlim;
static char *envp[] = { NULL };
- char *args[] = { NULL, NULL, NULL, NULL };
+ char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
+ char buffer[16];
/* XXX - should really tidy up PAM here too */
@@ -271,11 +196,18 @@ static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const
}
/* exec binary helper */
- args[0] = x_strdup(CHKPWD_HELPER);
+ args[0] = x_strdup(UPDATE_HELPER);
args[1] = x_strdup(user);
- args[2] = x_strdup("shadow");
+ args[2] = x_strdup("update");
+ if (on(UNIX_SHADOW, ctrl))
+ args[3] = x_strdup("1");
+ else
+ args[3] = x_strdup("0");
- execve(CHKPWD_HELPER, args, envp);
+ snprintf(buffer, sizeof(buffer), "%d", remember);
+ args[4] = x_strdup(buffer);
+
+ execve(UPDATE_HELPER, args, envp);
/* should not get here: exit with error */
D(("helper binary is not available"));
@@ -298,7 +230,7 @@ static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const
close(fds[1]);
rc=waitpid(child, &retval, 0); /* wait for helper to complete */
if (rc<0) {
- pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
+ pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m");
retval = PAM_AUTH_ERR;
} else {
retval = WEXITSTATUS(retval);
@@ -355,393 +287,6 @@ static int check_old_password(const char *forwho, const char *newpass)
return retval;
}
-static int save_old_password(pam_handle_t *pamh,
- const char *forwho, const char *oldpass,
- int howmany)
-{
- static char buf[16384];
- static char nbuf[16384];
- char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
- int npas;
- FILE *pwfile, *opwfile;
- int err = 0;
- int oldmask;
- int found = 0;
- struct passwd *pwd = NULL;
- struct stat st;
-
- if (howmany < 0) {
- return PAM_SUCCESS;
- }
-
- if (oldpass == NULL) {
- return PAM_SUCCESS;
- }
-
- oldmask = umask(077);
-
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- security_context_t passwd_context=NULL;
- if (getfilecon("/etc/passwd",&passwd_context)<0) {
- return PAM_AUTHTOK_ERR;
- };
- if (getfscreatecon(&prev_context)<0) {
- freecon(passwd_context);
- return PAM_AUTHTOK_ERR;
- }
- if (setfscreatecon(passwd_context)) {
- freecon(passwd_context);
- freecon(prev_context);
- return PAM_AUTHTOK_ERR;
- }
- freecon(passwd_context);
- }
-#endif
- pwfile = fopen(OPW_TMPFILE, "w");
- umask(oldmask);
- if (pwfile == NULL) {
- err = 1;
- goto done;
- }
-
- opwfile = fopen(OLD_PASSWORDS_FILE, "r");
- if (opwfile == NULL) {
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fstat(fileno(opwfile), &st) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
- if (fchmod(fileno(pwfile), st.st_mode) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- while (fgets(buf, 16380, opwfile)) {
- if (!strncmp(buf, forwho, strlen(forwho))) {
- char *sptr;
- buf[strlen(buf) - 1] = '\0';
- s_luser = strtok_r(buf, ":", &sptr);
- s_uid = strtok_r(NULL, ":", &sptr);
- s_npas = strtok_r(NULL, ":", &sptr);
- s_pas = strtok_r(NULL, ":", &sptr);
- npas = strtol(s_npas, NULL, 10) + 1;
- while (npas > howmany) {
- s_pas = strpbrk(s_pas, ",");
- if (s_pas != NULL)
- s_pas++;
- npas--;
- }
- pass = crypt_md5_wrapper(oldpass);
- if (s_pas == NULL)
- snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
- s_luser, s_uid, npas, pass);
- else
- snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
- s_luser, s_uid, npas, s_pas, pass);
- _pam_delete(pass);
- if (fputs(nbuf, pwfile) < 0) {
- err = 1;
- break;
- }
- found = 1;
- } else if (fputs(buf, pwfile) < 0) {
- err = 1;
- break;
- }
- }
- fclose(opwfile);
-
- if (!found) {
- pwd = pam_modutil_getpwnam(pamh, forwho);
- if (pwd == NULL) {
- err = 1;
- } else {
- pass = crypt_md5_wrapper(oldpass);
- snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n",
- forwho, (unsigned long)pwd->pw_uid, pass);
- _pam_delete(pass);
- if (fputs(nbuf, pwfile) < 0) {
- err = 1;
- }
- }
- }
-
- if (fclose(pwfile)) {
- D(("error writing entries to old passwords file: %m"));
- err = 1;
- }
-
-done:
- if (!err) {
- if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
- err = 1;
- }
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- if (setfscreatecon(prev_context)) {
- err = 1;
- }
- if (prev_context)
- freecon(prev_context);
- prev_context=NULL;
- }
-#endif
- if (!err) {
- return PAM_SUCCESS;
- } else {
- unlink(OPW_TMPFILE);
- return PAM_AUTHTOK_ERR;
- }
-}
-
-static int _update_passwd(pam_handle_t *pamh,
- const char *forwho, const char *towhat)
-{
- struct passwd *tmpent = NULL;
- struct stat st;
- FILE *pwfile, *opwfile;
- int err = 1;
- int oldmask;
-
- oldmask = umask(077);
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- security_context_t passwd_context=NULL;
- if (getfilecon("/etc/passwd",&passwd_context)<0) {
- return PAM_AUTHTOK_ERR;
- };
- if (getfscreatecon(&prev_context)<0) {
- freecon(passwd_context);
- return PAM_AUTHTOK_ERR;
- }
- if (setfscreatecon(passwd_context)) {
- freecon(passwd_context);
- freecon(prev_context);
- return PAM_AUTHTOK_ERR;
- }
- freecon(passwd_context);
- }
-#endif
- pwfile = fopen(PW_TMPFILE, "w");
- umask(oldmask);
- if (pwfile == NULL) {
- err = 1;
- goto done;
- }
-
- opwfile = fopen("/etc/passwd", "r");
- if (opwfile == NULL) {
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fstat(fileno(opwfile), &st) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
- if (fchmod(fileno(pwfile), st.st_mode) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- tmpent = fgetpwent(opwfile);
- while (tmpent) {
- if (!strcmp(tmpent->pw_name, forwho)) {
- /* To shut gcc up */
- union {
- const char *const_charp;
- char *charp;
- } assigned_passwd;
- assigned_passwd.const_charp = towhat;
-
- tmpent->pw_passwd = assigned_passwd.charp;
- err = 0;
- }
- if (putpwent(tmpent, pwfile)) {
- D(("error writing entry to password file: %m"));
- err = 1;
- break;
- }
- tmpent = fgetpwent(opwfile);
- }
- fclose(opwfile);
-
- if (fclose(pwfile)) {
- D(("error writing entries to password file: %m"));
- err = 1;
- }
-
-done:
- if (!err) {
- if (!rename(PW_TMPFILE, "/etc/passwd"))
- pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
- else
- err = 1;
- }
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- if (setfscreatecon(prev_context)) {
- err = 1;
- }
- if (prev_context)
- freecon(prev_context);
- prev_context=NULL;
- }
-#endif
- if (!err) {
- return PAM_SUCCESS;
- } else {
- unlink(PW_TMPFILE);
- return PAM_AUTHTOK_ERR;
- }
-}
-
-static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
-{
- struct spwd *spwdent = NULL, *stmpent = NULL;
- struct stat st;
- FILE *pwfile, *opwfile;
- int err = 1;
- int oldmask;
-
- spwdent = getspnam(forwho);
- if (spwdent == NULL) {
- return PAM_USER_UNKNOWN;
- }
- oldmask = umask(077);
-
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- security_context_t shadow_context=NULL;
- if (getfilecon("/etc/shadow",&shadow_context)<0) {
- return PAM_AUTHTOK_ERR;
- };
- if (getfscreatecon(&prev_context)<0) {
- freecon(shadow_context);
- return PAM_AUTHTOK_ERR;
- }
- if (setfscreatecon(shadow_context)) {
- freecon(shadow_context);
- freecon(prev_context);
- return PAM_AUTHTOK_ERR;
- }
- freecon(shadow_context);
- }
-#endif
- pwfile = fopen(SH_TMPFILE, "w");
- umask(oldmask);
- if (pwfile == NULL) {
- err = 1;
- goto done;
- }
-
- opwfile = fopen("/etc/shadow", "r");
- if (opwfile == NULL) {
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fstat(fileno(opwfile), &st) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
- if (fchmod(fileno(pwfile), st.st_mode) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- stmpent = fgetspent(opwfile);
- while (stmpent) {
-
- if (!strcmp(stmpent->sp_namp, forwho)) {
- stmpent->sp_pwdp = towhat;
- stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
- err = 0;
- D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
- }
-
- if (putspent(stmpent, pwfile)) {
- D(("error writing entry to shadow file: %m"));
- err = 1;
- break;
- }
-
- stmpent = fgetspent(opwfile);
- }
- fclose(opwfile);
-
- if (fclose(pwfile)) {
- D(("error writing entries to shadow file: %m"));
- err = 1;
- }
-
- done:
- if (!err) {
- if (!rename(SH_TMPFILE, "/etc/shadow"))
- pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
- else
- err = 1;
- }
-
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- if (setfscreatecon(prev_context)) {
- err = 1;
- }
- if (prev_context)
- freecon(prev_context);
- prev_context=NULL;
- }
-#endif
-
- if (!err) {
- return PAM_SUCCESS;
- } else {
- unlink(SH_TMPFILE);
- return PAM_AUTHTOK_ERR;
- }
-}
-
static int _do_setpass(pam_handle_t* pamh, const char *forwho,
const char *fromwhat,
char *towhat, unsigned int ctrl, int remember)
@@ -769,9 +314,7 @@ static int _do_setpass(pam_handle_t* pamh, const char *forwho,
enum clnt_stat err;
/* Unlock passwd file to avoid deadlock */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ unlock_pwdf();
unlocked = 1;
/* Initialize password information */
@@ -831,48 +374,33 @@ static int _do_setpass(pam_handle_t* pamh, const char *forwho,
}
if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
-#ifdef USE_LCKPWDF
if(unlocked) {
- int i = 0;
- /* These values for the number of attempts and the sleep time
- are, of course, completely arbitrary.
- My reading of the PAM docs is that, once pam_chauthtok() has been
- called with PAM_UPDATE_AUTHTOK, we are obliged to take any
- reasonable steps to make sure the token is updated; so retrying
- for 1/10 sec. isn't overdoing it. */
- while((retval = lckpwdf()) != 0 && i < 100) {
- usleep(1000);
- i++;
- }
- if(retval != 0) {
+ if (lock_pwdf() != PAM_SUCCESS) {
return PAM_AUTHTOK_LOCK_BUSY;
}
}
+#ifdef WITH_SELINUX
+ if (unix_selinux_confined())
+ return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember);
#endif
/* first, save old password */
- if (save_old_password(pamh, forwho, fromwhat, remember)) {
+ if (save_old_password(forwho, fromwhat, remember)) {
retval = PAM_AUTHTOK_ERR;
goto done;
}
if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) {
- retval = _update_shadow(pamh, forwho, towhat);
-#ifdef WITH_SELINUX
- if (retval != PAM_SUCCESS && SELINUX_ENABLED)
- retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
-#endif
+ retval = unix_update_shadow(pamh, forwho, towhat);
if (retval == PAM_SUCCESS)
if (!is_pwd_shadowed(pwd))
- retval = _update_passwd(pamh, forwho, "x");
+ retval = unix_update_passwd(pamh, forwho, "x");
} else {
- retval = _update_passwd(pamh, forwho, towhat);
+ retval = unix_update_passwd(pamh, forwho, towhat);
}
}
done:
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ unlock_pwdf();
return retval;
}
@@ -970,7 +498,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
int argc, const char **argv)
{
unsigned int ctrl, lctrl;
- int retval, i;
+ int retval;
int remember = -1;
/* <DO NOT free() THESE> */
@@ -1189,40 +717,23 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
pass_new = pass_old = NULL; /* tidy up */
return retval;
}
-#ifdef USE_LCKPWDF
- /* These values for the number of attempts and the sleep time
- are, of course, completely arbitrary.
- My reading of the PAM docs is that, once pam_chauthtok() has been
- called with PAM_UPDATE_AUTHTOK, we are obliged to take any
- reasonable steps to make sure the token is updated; so retrying
- for 1/10 sec. isn't overdoing it. */
- i=0;
- while((retval = lckpwdf()) != 0 && i < 100) {
- usleep(1000);
- i++;
- }
- if(retval != 0) {
+ if (lock_pwdf() != PAM_SUCCESS) {
return PAM_AUTHTOK_LOCK_BUSY;
}
-#endif
if (pass_old) {
retval = _unix_verify_password(pamh, user, pass_old, ctrl);
if (retval != PAM_SUCCESS) {
pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ unlock_pwdf();
return retval;
}
}
retval = _unix_verify_shadow(pamh, user, ctrl);
if (retval != PAM_SUCCESS) {
- pam_syslog(pamh, LOG_NOTICE, "user not authenticated 2");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired");
+ unlock_pwdf();
return retval;
}
@@ -1231,9 +742,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
pam_syslog(pamh, LOG_NOTICE,
"new password not acceptable 2");
pass_new = pass_old = NULL; /* tidy up */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ unlock_pwdf();
return retval;
}
@@ -1275,9 +784,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
pam_syslog(pamh, LOG_CRIT,
"out of memory for password");
pass_new = pass_old = NULL; /* tidy up */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ unlock_pwdf();
return PAM_BUF_ERR;
}
/* copy first 8 bytes of password */
@@ -1299,7 +806,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
remember);
- /* _do_setpass has called ulckpwdf for us */
+ /* _do_setpass has called unlock_pwdf for us */
_pam_delete(tpass);
pass_old = pass_new = NULL;
diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c
index 9a3d72e6..e379cf62 100644
--- a/modules/pam_unix/passverify.c
+++ b/modules/pam_unix/passverify.c
@@ -13,7 +13,12 @@
#include <shadow.h>
#include <syslog.h>
#include <stdarg.h>
+#include <signal.h>
+#include <errno.h>
#include <time.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include "md5.h"
#include "bigcrypt.h"
@@ -35,6 +40,10 @@
#include <security/pam_ext.h>
#endif
+#if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
+# include "./lckpwdf.-c"
+#endif
+
int
verify_pwd_hash(const char *p, const char *hash, unsigned int nullok)
{
@@ -256,6 +265,588 @@ check_shadow_expiry(pam_handle_t *pamh, struct spwd *spent, int *daysleft)
}
+/* passwd/salt conversion macros */
+
+#define PW_TMPFILE "/etc/npasswd"
+#define SH_TMPFILE "/etc/nshadow"
+#define OPW_TMPFILE "/etc/security/nopasswd"
+
+/*
+ * i64c - convert an integer to a radix 64 character
+ */
+static int
+i64c(int i)
+{
+ if (i < 0)
+ return ('.');
+ else if (i > 63)
+ return ('z');
+ if (i == 0)
+ return ('.');
+ if (i == 1)
+ return ('/');
+ if (i >= 2 && i <= 11)
+ return ('0' - 2 + i);
+ if (i >= 12 && i <= 37)
+ return ('A' - 12 + i);
+ if (i >= 38 && i <= 63)
+ return ('a' - 38 + i);
+ return ('\0');
+}
+
+char *
+crypt_md5_wrapper(const char *pass_new)
+{
+ /*
+ * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
+ * removed use of static variables (AGM)
+ */
+
+ struct timeval tv;
+ MD5_CTX ctx;
+ unsigned char result[16];
+ char *cp = (char *) result;
+ unsigned char tmp[16];
+ int i;
+ char *x = NULL;
+
+ GoodMD5Init(&ctx);
+ gettimeofday(&tv, (struct timezone *) 0);
+ GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
+ i = getpid();
+ GoodMD5Update(&ctx, (void *) &i, sizeof i);
+ i = clock();
+ GoodMD5Update(&ctx, (void *) &i, sizeof i);
+ GoodMD5Update(&ctx, result, sizeof result);
+ GoodMD5Final(tmp, &ctx);
+ strcpy(cp, "$1$"); /* magic for the MD5 */
+ cp += strlen(cp);
+ for (i = 0; i < 8; i++)
+ *cp++ = i64c(tmp[i] & 077);
+ *cp = '\0';
+
+ /* no longer need cleartext */
+ x = Goodcrypt_md5(pass_new, (const char *) result);
+
+ return x;
+}
+
+#ifdef WITH_SELINUX
+int
+unix_selinux_confined(void)
+{
+ static int confined = -1;
+ int fd;
+ char tempfile[]="/etc/.pwdXXXXXX";
+
+ if (confined != -1)
+ return confined;
+
+ /* cannot be confined without SELinux enabled */
+ if (!SELINUX_ENABLED){
+ confined = 0;
+ return confined;
+ }
+
+ /* let's try opening shadow read only */
+ if ((fd=open("/etc/shadow", O_RDONLY)) != -1) {
+ close(fd);
+ confined = 0;
+ return confined;
+ }
+
+ if (errno == EACCES) {
+ confined = 1;
+ return confined;
+ }
+
+ /* shadow opening failed because of other reasons let's try
+ creating a file in /etc */
+ if ((fd=mkstemp(tempfile)) != -1) {
+ unlink(tempfile);
+ close(fd);
+ confined = 0;
+ return confined;
+ }
+
+ confined = 1;
+ return confined;
+}
+
+#else
+int
+unix_selinux_confined(void)
+{
+ return 0;
+}
+#endif
+
+#ifdef USE_LCKPWDF
+int
+lock_pwdf(void)
+{
+ int i;
+ int retval;
+
+#ifndef HELPER_COMPILE
+ if (unix_selinux_confined()) {
+ return PAM_SUCCESS;
+ }
+#endif
+ /* These values for the number of attempts and the sleep time
+ are, of course, completely arbitrary.
+ My reading of the PAM docs is that, once pam_chauthtok() has been
+ called with PAM_UPDATE_AUTHTOK, we are obliged to take any
+ reasonable steps to make sure the token is updated; so retrying
+ for 1/10 sec. isn't overdoing it. */
+ i=0;
+ while((retval = lckpwdf()) != 0 && i < 100) {
+ usleep(1000);
+ i++;
+ }
+ if(retval != 0) {
+ return PAM_AUTHTOK_LOCK_BUSY;
+ }
+ return PAM_SUCCESS;
+}
+
+void
+unlock_pwdf(void)
+{
+#ifndef HELPER_COMPILE
+ if (unix_selinux_confined()) {
+ return;
+ }
+#endif
+ ulckpwdf();
+}
+#else
+int
+lock_pwdf(void)
+{
+ return PAM_SUCCESS;
+}
+
+void
+unlock_pwdf(void)
+{
+ return;
+}
+#endif
+
+int
+save_old_password(const char *forwho, const char *oldpass,
+ int howmany)
+{
+ static char buf[16384];
+ static char nbuf[16384];
+ char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
+ int npas;
+ FILE *pwfile, *opwfile;
+ int err = 0;
+ int oldmask;
+ int found = 0;
+ struct passwd *pwd = NULL;
+ struct stat st;
+ security_context_t prev_context=NULL;
+
+ if (howmany < 0) {
+ return PAM_SUCCESS;
+ }
+
+ if (oldpass == NULL) {
+ return PAM_SUCCESS;
+ }
+
+ oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ security_context_t passwd_context=NULL;
+ if (getfilecon("/etc/passwd",&passwd_context)<0) {
+ return PAM_AUTHTOK_ERR;
+ };
+ if (getfscreatecon(&prev_context)<0) {
+ freecon(passwd_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ if (setfscreatecon(passwd_context)) {
+ freecon(passwd_context);
+ freecon(prev_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ freecon(passwd_context);
+ }
+#endif
+ pwfile = fopen(OPW_TMPFILE, "w");
+ umask(oldmask);
+ if (pwfile == NULL) {
+ err = 1;
+ goto done;
+ }
+
+ opwfile = fopen(OLD_PASSWORDS_FILE, "r");
+ if (opwfile == NULL) {
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fstat(fileno(opwfile), &st) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+ if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ while (fgets(buf, 16380, opwfile)) {
+ if (!strncmp(buf, forwho, strlen(forwho))) {
+ char *sptr = NULL;
+ found = 1;
+ if (howmany == 0)
+ continue;
+ buf[strlen(buf) - 1] = '\0';
+ s_luser = strtok_r(buf, ":", &sptr);
+ s_uid = strtok_r(NULL, ":", &sptr);
+ s_npas = strtok_r(NULL, ":", &sptr);
+ s_pas = strtok_r(NULL, ":", &sptr);
+ npas = strtol(s_npas, NULL, 10) + 1;
+ while (npas > howmany) {
+ s_pas = strpbrk(s_pas, ",");
+ if (s_pas != NULL)
+ s_pas++;
+ npas--;
+ }
+ pass = crypt_md5_wrapper(oldpass);
+ if (s_pas == NULL)
+ snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
+ s_luser, s_uid, npas, pass);
+ else
+ snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
+ s_luser, s_uid, npas, s_pas, pass);
+ _pam_delete(pass);
+ if (fputs(nbuf, pwfile) < 0) {
+ err = 1;
+ break;
+ }
+ } else if (fputs(buf, pwfile) < 0) {
+ err = 1;
+ break;
+ }
+ }
+ fclose(opwfile);
+
+ if (!found) {
+ pwd = getpwnam(forwho);
+ if (pwd == NULL) {
+ err = 1;
+ } else {
+ pass = crypt_md5_wrapper(oldpass);
+ snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n",
+ forwho, (unsigned long)pwd->pw_uid, pass);
+ _pam_delete(pass);
+ if (fputs(nbuf, pwfile) < 0) {
+ err = 1;
+ }
+ }
+ }
+
+ if (fclose(pwfile)) {
+ D(("error writing entries to old passwords file: %m"));
+ err = 1;
+ }
+
+done:
+ if (!err) {
+ if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
+ err = 1;
+ }
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ if (setfscreatecon(prev_context)) {
+ err = 1;
+ }
+ if (prev_context)
+ freecon(prev_context);
+ prev_context=NULL;
+ }
+#endif
+ if (!err) {
+ return PAM_SUCCESS;
+ } else {
+ unlink(OPW_TMPFILE);
+ return PAM_AUTHTOK_ERR;
+ }
+}
+
+#ifdef HELPER_COMPILE
+int
+unix_update_passwd(const char *forwho, const char *towhat)
+#else
+int
+unix_update_passwd(pam_handle_t *pamh, const char *forwho, const char *towhat)
+#endif
+{
+ struct passwd *tmpent = NULL;
+ struct stat st;
+ FILE *pwfile, *opwfile;
+ int err = 1;
+ int oldmask;
+ security_context_t prev_context=NULL;
+
+ oldmask = umask(077);
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ security_context_t passwd_context=NULL;
+ if (getfilecon("/etc/passwd",&passwd_context)<0) {
+ return PAM_AUTHTOK_ERR;
+ };
+ if (getfscreatecon(&prev_context)<0) {
+ freecon(passwd_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ if (setfscreatecon(passwd_context)) {
+ freecon(passwd_context);
+ freecon(prev_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ freecon(passwd_context);
+ }
+#endif
+ pwfile = fopen(PW_TMPFILE, "w");
+ umask(oldmask);
+ if (pwfile == NULL) {
+ err = 1;
+ goto done;
+ }
+
+ opwfile = fopen("/etc/passwd", "r");
+ if (opwfile == NULL) {
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fstat(fileno(opwfile), &st) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+ if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ tmpent = fgetpwent(opwfile);
+ while (tmpent) {
+ if (!strcmp(tmpent->pw_name, forwho)) {
+ /* To shut gcc up */
+ union {
+ const char *const_charp;
+ char *charp;
+ } assigned_passwd;
+ assigned_passwd.const_charp = towhat;
+
+ tmpent->pw_passwd = assigned_passwd.charp;
+ err = 0;
+ }
+ if (putpwent(tmpent, pwfile)) {
+ D(("error writing entry to password file: %m"));
+ err = 1;
+ break;
+ }
+ tmpent = fgetpwent(opwfile);
+ }
+ fclose(opwfile);
+
+ if (fclose(pwfile)) {
+ D(("error writing entries to password file: %m"));
+ err = 1;
+ }
+
+done:
+ if (!err) {
+ if (!rename(PW_TMPFILE, "/etc/passwd"))
+#ifdef HELPER_COMPILE
+ helper_log_err(
+#else
+ pam_syslog(pamh,
+#endif
+ LOG_NOTICE, "password changed for %s", forwho);
+ else
+ err = 1;
+ }
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ if (setfscreatecon(prev_context)) {
+ err = 1;
+ }
+ if (prev_context)
+ freecon(prev_context);
+ prev_context=NULL;
+ }
+#endif
+ if (!err) {
+ return PAM_SUCCESS;
+ } else {
+ unlink(PW_TMPFILE);
+ return PAM_AUTHTOK_ERR;
+ }
+}
+
+#ifdef HELPER_COMPILE
+int
+unix_update_shadow(const char *forwho, char *towhat)
+#else
+int
+unix_update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
+#endif
+{
+ struct spwd *spwdent = NULL, *stmpent = NULL;
+ struct stat st;
+ FILE *pwfile, *opwfile;
+ int err = 1;
+ int oldmask;
+ security_context_t prev_context=NULL;
+
+ spwdent = getspnam(forwho);
+ if (spwdent == NULL) {
+ return PAM_USER_UNKNOWN;
+ }
+ oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ security_context_t shadow_context=NULL;
+ if (getfilecon("/etc/shadow",&shadow_context)<0) {
+ return PAM_AUTHTOK_ERR;
+ };
+ if (getfscreatecon(&prev_context)<0) {
+ freecon(shadow_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ if (setfscreatecon(shadow_context)) {
+ freecon(shadow_context);
+ freecon(prev_context);
+ return PAM_AUTHTOK_ERR;
+ }
+ freecon(shadow_context);
+ }
+#endif
+ pwfile = fopen(SH_TMPFILE, "w");
+ umask(oldmask);
+ if (pwfile == NULL) {
+ err = 1;
+ goto done;
+ }
+
+ opwfile = fopen("/etc/shadow", "r");
+ if (opwfile == NULL) {
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fstat(fileno(opwfile), &st) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+ if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+ fclose(opwfile);
+ fclose(pwfile);
+ err = 1;
+ goto done;
+ }
+
+ stmpent = fgetspent(opwfile);
+ while (stmpent) {
+
+ if (!strcmp(stmpent->sp_namp, forwho)) {
+ stmpent->sp_pwdp = towhat;
+ stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
+ err = 0;
+ D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
+ }
+
+ if (putspent(stmpent, pwfile)) {
+ D(("error writing entry to shadow file: %m"));
+ err = 1;
+ break;
+ }
+
+ stmpent = fgetspent(opwfile);
+ }
+ fclose(opwfile);
+
+ if (fclose(pwfile)) {
+ D(("error writing entries to shadow file: %m"));
+ err = 1;
+ }
+
+ done:
+ if (!err) {
+ if (!rename(SH_TMPFILE, "/etc/shadow"))
+#ifdef HELPER_COMPILE
+ helper_log_err(
+#else
+ pam_syslog(pamh,
+#endif
+ LOG_NOTICE, "password changed for %s", forwho);
+ else
+ err = 1;
+ }
+
+#ifdef WITH_SELINUX
+ if (SELINUX_ENABLED) {
+ if (setfscreatecon(prev_context)) {
+ err = 1;
+ }
+ if (prev_context)
+ freecon(prev_context);
+ prev_context=NULL;
+ }
+#endif
+
+ if (!err) {
+ return PAM_SUCCESS;
+ } else {
+ unlink(SH_TMPFILE);
+ return PAM_AUTHTOK_ERR;
+ }
+}
+
#ifdef HELPER_COMPILE
int
@@ -296,13 +887,107 @@ helper_log_err(int err, const char *format, ...)
closelog();
}
+static void
+su_sighandler(int sig)
+{
+#ifndef SA_RESETHAND
+ /* emulate the behaviour of the SA_RESETHAND flag */
+ if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
+ signal(sig, SIG_DFL);
+#endif
+ if (sig > 0) {
+ _exit(sig);
+ }
+}
+
+void
+setup_signals(void)
+{
+ struct sigaction action; /* posix signal structure */
+
+ /*
+ * Setup signal handlers
+ */
+ (void) memset((void *) &action, 0, sizeof(action));
+ action.sa_handler = su_sighandler;
+#ifdef SA_RESETHAND
+ action.sa_flags = SA_RESETHAND;
+#endif
+ (void) sigaction(SIGILL, &action, NULL);
+ (void) sigaction(SIGTRAP, &action, NULL);
+ (void) sigaction(SIGBUS, &action, NULL);
+ (void) sigaction(SIGSEGV, &action, NULL);
+ action.sa_handler = SIG_IGN;
+ action.sa_flags = 0;
+ (void) sigaction(SIGTERM, &action, NULL);
+ (void) sigaction(SIGHUP, &action, NULL);
+ (void) sigaction(SIGINT, &action, NULL);
+ (void) sigaction(SIGQUIT, &action, NULL);
+}
+
+char *
+getuidname(uid_t uid)
+{
+ struct passwd *pw;
+ static char username[256];
+
+ pw = getpwuid(uid);
+ if (pw == NULL)
+ return NULL;
+
+ strncpy(username, pw->pw_name, sizeof(username));
+ username[sizeof(username) - 1] = '\0';
+
+ return username;
+}
+
+int
+read_passwords(int fd, int npass, char **passwords)
+{
+ int rbytes = 0;
+ int offset = 0;
+ int i = 0;
+ char *pptr;
+ while (npass > 0) {
+ rbytes = read(fd, passwords[i]+offset, MAXPASS-offset);
+
+ if (rbytes < 0) {
+ if (errno == EINTR) continue;
+ break;
+ }
+ if (rbytes == 0)
+ break;
+
+ while (npass > 0 && (pptr=memchr(passwords[i]+offset, '\0', rbytes))
+ != NULL) {
+ rbytes -= pptr - (passwords[i]+offset) + 1;
+ i++;
+ offset = 0;
+ npass--;
+ if (rbytes > 0) {
+ if (npass > 0)
+ memcpy(passwords[i], pptr+1, rbytes);
+ memset(pptr+1, '\0', rbytes);
+ }
+ }
+ offset += rbytes;
+ }
+
+ /* clear up */
+ if (offset > 0 && npass > 0) {
+ memset(passwords[i], '\0', offset);
+ }
+
+ return i;
+}
+
#endif
/* ****************************************************************** *
* Copyright (c) Jan Rêkorajski 1999.
* Copyright (c) Andrew G. Morgan 1996-8.
* Copyright (c) Alex O. Yuriev, 1996.
* Copyright (c) Cristian Gafton 1996.
- * Copyright (c) Red Hat, Inc. 2007.
+ * Copyright (c) Red Hat, Inc. 1996, 2007, 2008.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/modules/pam_unix/passverify.h b/modules/pam_unix/passverify.h
index 6b8c0c3e..55007a31 100644
--- a/modules/pam_unix/passverify.h
+++ b/modules/pam_unix/passverify.h
@@ -2,17 +2,41 @@
* Copyright information at end of file.
*/
+#include <sys/types.h>
#include <pwd.h>
#include <security/pam_modules.h>
#define PAM_UNIX_RUN_HELPER PAM_CRED_INSUFFICIENT
+#define MAXPASS 200 /* the maximum length of a password */
+
+#define OLD_PASSWORDS_FILE "/etc/security/opasswd"
+
+#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
+#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
+
int
verify_pwd_hash(const char *p, const char *hash, unsigned int nullok);
int
is_pwd_shadowed(const struct passwd *pwd);
+char *
+crypt_md5_wrapper(const char *pass_new);
+
+int
+unix_selinux_confined(void);
+
+int
+lock_pwdf(void);
+
+void
+unlock_pwdf(void);
+
+int
+save_old_password(const char *forwho, const char *oldpass,
+ int howmany);
+
#ifdef HELPER_COMPILE
void
helper_log_err(int err, const char *format,...);
@@ -20,6 +44,15 @@ helper_log_err(int err, const char *format,...);
int
helper_verify_password(const char *name, const char *p, int nullok);
+void
+setup_signals(void);
+
+char *
+getuidname(uid_t uid);
+
+int
+read_passwords(int fd, int npass, char **passwords);
+
int
get_account_info(const char *name,
struct passwd **pwd, struct spwd **spwdent);
@@ -31,6 +64,11 @@ get_pwd_hash(const char *name,
int
check_shadow_expiry(struct spwd *spent, int *daysleft);
+int
+unix_update_passwd(const char *forwho, const char *towhat);
+
+int
+unix_update_shadow(const char *forwho, char *towhat);
#else
int
get_account_info(pam_handle_t *pamh, const char *name,
@@ -42,6 +80,12 @@ get_pwd_hash(pam_handle_t *pamh, const char *name,
int
check_shadow_expiry(pam_handle_t *pamh, struct spwd *spent, int *daysleft);
+
+int
+unix_update_passwd(pam_handle_t *pamh, const char *forwho, const char *towhat);
+
+int
+unix_update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat);
#endif
/* ****************************************************************** *
diff --git a/modules/pam_unix/unix_chkpwd.c b/modules/pam_unix/unix_chkpwd.c
index b6508b87..11ac3aac 100644
--- a/modules/pam_unix/unix_chkpwd.c
+++ b/modules/pam_unix/unix_chkpwd.c
@@ -24,61 +24,12 @@
#include <shadow.h>
#include <signal.h>
#include <time.h>
-#ifdef WITH_SELINUX
-#include <selinux/selinux.h>
-#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
-static security_context_t prev_context=NULL;
-static int selinux_enabled=-1;
-#else
-#define SELINUX_ENABLED 0
-#endif
-
-#define MAXPASS 200 /* the maximum length of a password */
#include <security/_pam_types.h>
#include <security/_pam_macros.h>
#include "passverify.h"
-/* syslogging function for errors and other information */
-
-static void su_sighandler(int sig)
-{
-#ifndef SA_RESETHAND
- /* emulate the behaviour of the SA_RESETHAND flag */
- if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
- signal(sig, SIG_DFL);
-#endif
- if (sig > 0) {
- helper_log_err(LOG_NOTICE, "caught signal %d.", sig);
- exit(sig);
- }
-}
-
-static void setup_signals(void)
-{
- struct sigaction action; /* posix signal structure */
-
- /*
- * Setup signal handlers
- */
- (void) memset((void *) &action, 0, sizeof(action));
- action.sa_handler = su_sighandler;
-#ifdef SA_RESETHAND
- action.sa_flags = SA_RESETHAND;
-#endif
- (void) sigaction(SIGILL, &action, NULL);
- (void) sigaction(SIGTRAP, &action, NULL);
- (void) sigaction(SIGBUS, &action, NULL);
- (void) sigaction(SIGSEGV, &action, NULL);
- action.sa_handler = SIG_IGN;
- action.sa_flags = 0;
- (void) sigaction(SIGTERM, &action, NULL);
- (void) sigaction(SIGHUP, &action, NULL);
- (void) sigaction(SIGINT, &action, NULL);
- (void) sigaction(SIGQUIT, &action, NULL);
-}
-
static int _check_expiry(const char *uname)
{
struct spwd *spent;
@@ -103,190 +54,15 @@ static int _check_expiry(const char *uname)
return retval;
}
-static char *getuidname(uid_t uid)
-{
- struct passwd *pw;
- static char username[32];
-
- pw = getpwuid(uid);
- if (pw == NULL)
- return NULL;
-
- strncpy(username, pw->pw_name, sizeof(username));
- username[sizeof(username) - 1] = '\0';
-
- return username;
-}
-
-#define SH_TMPFILE "/etc/nshadow"
-static int _update_shadow(const char *forwho)
-{
- struct spwd *spwdent = NULL, *stmpent = NULL;
- FILE *pwfile, *opwfile;
- int err = 1;
- int oldmask;
- struct stat st;
- char pass[MAXPASS + 1];
- char towhat[MAXPASS + 1];
- int npass=0;
-
- /* read the password from stdin (a pipe from the pam_unix module) */
-
- npass = read(STDIN_FILENO, pass, MAXPASS);
-
- if (npass < 0) { /* is it a valid password? */
-
- helper_log_err(LOG_DEBUG, "no password supplied");
- return PAM_AUTHTOK_ERR;
-
- } else if (npass >= MAXPASS) {
-
- helper_log_err(LOG_DEBUG, "password too long");
- return PAM_AUTHTOK_ERR;
-
- } else {
- /* does pass agree with the official one? */
- int retval=0;
- pass[npass] = '\0'; /* NUL terminate */
- retval = helper_verify_password(forwho, pass, 0);
- if (retval != PAM_SUCCESS) {
- return retval;
- }
- }
-
- /* read the password from stdin (a pipe from the pam_unix module) */
-
- npass = read(STDIN_FILENO, towhat, MAXPASS);
-
- if (npass < 0) { /* is it a valid password? */
-
- helper_log_err(LOG_DEBUG, "no new password supplied");
- return PAM_AUTHTOK_ERR;
-
- } else if (npass >= MAXPASS) {
-
- helper_log_err(LOG_DEBUG, "new password too long");
- return PAM_AUTHTOK_ERR;
-
- }
-
- towhat[npass] = '\0'; /* NUL terminate */
- spwdent = getspnam(forwho);
- if (spwdent == NULL) {
- return PAM_USER_UNKNOWN;
- }
- oldmask = umask(077);
-
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- security_context_t shadow_context=NULL;
- if (getfilecon("/etc/shadow",&shadow_context)<0) {
- return PAM_AUTHTOK_ERR;
- };
- if (getfscreatecon(&prev_context)<0) {
- freecon(shadow_context);
- return PAM_AUTHTOK_ERR;
- }
- if (setfscreatecon(shadow_context)) {
- freecon(shadow_context);
- freecon(prev_context);
- return PAM_AUTHTOK_ERR;
- }
- freecon(shadow_context);
- }
-#endif
- pwfile = fopen(SH_TMPFILE, "w");
- umask(oldmask);
- if (pwfile == NULL) {
- err = 1;
- goto done;
- }
-
- opwfile = fopen("/etc/shadow", "r");
- if (opwfile == NULL) {
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fstat(fileno(opwfile), &st) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
- if (fchmod(fileno(pwfile), st.st_mode) == -1) {
- fclose(opwfile);
- fclose(pwfile);
- err = 1;
- goto done;
- }
-
- stmpent = fgetspent(opwfile);
- while (stmpent) {
-
- if (!strcmp(stmpent->sp_namp, forwho)) {
- stmpent->sp_pwdp = towhat;
- stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
- err = 0;
- D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
- }
-
- if (putspent(stmpent, pwfile)) {
- D(("error writing entry to shadow file: %m"));
- err = 1;
- break;
- }
-
- stmpent = fgetspent(opwfile);
- }
- fclose(opwfile);
-
- if (fclose(pwfile)) {
- D(("error writing entries to shadow file: %m"));
- err = 1;
- }
-
- done:
- if (!err) {
- if (rename(SH_TMPFILE, "/etc/shadow"))
- err = 1;
- }
-
-#ifdef WITH_SELINUX
- if (SELINUX_ENABLED) {
- if (setfscreatecon(prev_context)) {
- err = 1;
- }
- if (prev_context)
- freecon(prev_context);
- prev_context=NULL;
- }
-#endif
-
- if (!err) {
- return PAM_SUCCESS;
- } else {
- unlink(SH_TMPFILE);
- return PAM_AUTHTOK_ERR;
- }
-}
-
int main(int argc, char *argv[])
{
char pass[MAXPASS + 1];
char *option;
int npass, nullok;
- int blankpass;
+ int blankpass = 0;
int retval = PAM_AUTH_ERR;
char *user;
+ char *passwords[] = { pass };
/*
* Catch or ignore as many signal as possible.
@@ -331,51 +107,32 @@ int main(int argc, char *argv[])
option=argv[2];
- if (strncmp(argv[2], "chkexpiry", 8) == 0) {
+ if (strcmp(option, "chkexpiry") == 0)
/* Check account information from the shadow file */
- return _check_expiry(argv[1]);
-
- }
-
- if (strncmp(option, "shadow", 8) == 0) {
- /* Attempting to change the password */
- return _update_shadow(argv[1]);
- }
-
+ return _check_expiry(argv[1]);
/* read the nullok/nonull option */
- if (strncmp(option, "nullok", 8) == 0)
+ else if (strcmp(option, "nullok") == 0)
nullok = 1;
- else
+ else if (strcmp(option, "nonull") == 0)
nullok = 0;
+ else
+ return PAM_SYSTEM_ERR;
/* read the password from stdin (a pipe from the pam_unix module) */
- npass = read(STDIN_FILENO, pass, MAXPASS);
-
- if (npass < 0) { /* is it a valid password? */
+ npass = read_passwords(STDIN_FILENO, 1, passwords);
+ if (npass != 1) { /* is it a valid password? */
helper_log_err(LOG_DEBUG, "no password supplied");
+ *pass = '\0';
+ }
- } else if (npass >= MAXPASS) {
-
- helper_log_err(LOG_DEBUG, "password too long");
-
- } else {
- if (npass == 0) {
- /* the password is NULL */
- blankpass = 1;
- retval = helper_verify_password(user, NULL, nullok);
-
- } else {
- /* does pass agree with the official one? */
- if (pass[0] == '\0')
- blankpass = 1;
- pass[npass] = '\0'; /* NUL terminate */
- retval = helper_verify_password(user, pass, nullok);
-
- }
+ if (*pass == '\0') {
+ blankpass = 1;
}
+ retval = helper_verify_password(user, pass, nullok);
+
memset(pass, '\0', MAXPASS); /* clear memory of the password */
/* return pass or fail */
@@ -392,7 +149,7 @@ int main(int argc, char *argv[])
/*
* Copyright (c) Andrew G. Morgan, 1996. All rights reserved
- * Copyright (c) Red Hat, Inc., 2007. All rights reserved
+ * Copyright (c) Red Hat, Inc., 2007,2008. All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/modules/pam_unix/unix_update.c b/modules/pam_unix/unix_update.c
new file mode 100644
index 00000000..6dc8ace4
--- /dev/null
+++ b/modules/pam_unix/unix_update.c
@@ -0,0 +1,194 @@
+/*
+ * This program is designed to run setuid(root) or with sufficient
+ * privilege to read all of the unix password databases. It is designed
+ * to provide a mechanism for the current user (defined by this
+ * process' uid) to verify their own password.
+ *
+ * The password is read from the standard input. The exit status of
+ * this program indicates whether the user is authenticated or not.
+ *
+ * Copyright information is located at the end of the file.
+ *
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/time.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
+static int selinux_enabled=-1;
+#else
+#define SELINUX_ENABLED 0
+#endif
+
+#include <security/_pam_types.h>
+#include <security/_pam_macros.h>
+
+#include "passverify.h"
+
+static int
+set_password(const char *forwho, const char *shadow, const char *remember)
+{
+ struct passwd *pwd = NULL;
+ int retval;
+ char pass[MAXPASS + 1];
+ char towhat[MAXPASS + 1];
+ int npass = 0;
+ /* we don't care about number format errors because the helper
+ should be called internally only */
+ int doshadow = atoi(shadow);
+ int nremember = atoi(remember);
+ char *passwords[] = { pass, towhat };
+
+ /* read the password from stdin (a pipe from the pam_unix module) */
+
+ npass = read_passwords(STDIN_FILENO, 2, passwords);
+
+ if (npass != 2) { /* is it a valid password? */
+ if (npass == 1) {
+ helper_log_err(LOG_DEBUG, "no new password supplied");
+ memset(pass, '\0', MAXPASS);
+ } else {
+ helper_log_err(LOG_DEBUG, "no valid passwords supplied");
+ }
+ return PAM_AUTHTOK_ERR;
+ }
+
+ if (lock_pwdf() != PAM_SUCCESS)
+ return PAM_AUTHTOK_LOCK_BUSY;
+
+ pwd = getpwnam(forwho);
+
+ if (pwd == NULL) {
+ retval = PAM_USER_UNKNOWN;
+ goto done;
+ }
+
+ /* does pass agree with the official one?
+ we always allow change from null pass */
+ retval = helper_verify_password(forwho, pass, 1);
+ if (retval != PAM_SUCCESS) {
+ goto done;
+ }
+
+ /* first, save old password */
+ if (save_old_password(forwho, pass, nremember)) {
+ retval = PAM_AUTHTOK_ERR;
+ goto done;
+ }
+
+ if (doshadow || is_pwd_shadowed(pwd)) {
+ retval = unix_update_shadow(forwho, towhat);
+ if (retval == PAM_SUCCESS)
+ if (!is_pwd_shadowed(pwd))
+ retval = unix_update_passwd(forwho, "x");
+ } else {
+ retval = unix_update_passwd(forwho, towhat);
+ }
+
+done:
+ memset(pass, '\0', MAXPASS);
+ memset(towhat, '\0', MAXPASS);
+
+ unlock_pwdf();
+
+ if (retval == PAM_SUCCESS) {
+ return PAM_SUCCESS;
+ } else {
+ return PAM_AUTHTOK_ERR;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ char *option;
+
+ /*
+ * Catch or ignore as many signal as possible.
+ */
+ setup_signals();
+
+ /*
+ * we establish that this program is running with non-tty stdin.
+ * this is to discourage casual use. It does *NOT* prevent an
+ * intruder from repeatadly running this program to determine the
+ * password of the current user (brute force attack, but one for
+ * which the attacker must already have gained access to the user's
+ * account).
+ */
+
+ if (isatty(STDIN_FILENO) || argc != 5 ) {
+ helper_log_err(LOG_NOTICE
+ ,"inappropriate use of Unix helper binary [UID=%d]"
+ ,getuid());
+ fprintf(stderr
+ ,"This binary is not designed for running in this way\n"
+ "-- the system administrator has been informed\n");
+ sleep(10); /* this should discourage/annoy the user */
+ return PAM_SYSTEM_ERR;
+ }
+
+ /* We must be root to read/update shadow.
+ */
+ if (geteuid() != 0) {
+ return PAM_CRED_INSUFFICIENT;
+ }
+
+ option = argv[2];
+
+ if (strcmp(option, "update") == 0) {
+ /* Attempting to change the password */
+ return set_password(argv[1], argv[3], argv[4]);
+ }
+
+ return PAM_SYSTEM_ERR;
+}
+
+/*
+ * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
+ * Copyright (c) Red Hat, Inc., 2007, 2008. 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, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 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. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */