diff options
author | Tomas Mraz <tm@t8m.info> | 2008-01-23 15:35:12 +0000 |
---|---|---|
committer | Tomas Mraz <tm@t8m.info> | 2008-01-23 15:35:12 +0000 |
commit | 459e97431e99fa2c32e30e957993f95794b98dd0 (patch) | |
tree | dcf013e6644eba5ee5bdbaf6b2f78999bf43dc9b | |
parent | ca2cb12dd3165ab006c674d673a2d596d642c875 (diff) | |
download | pam-459e97431e99fa2c32e30e957993f95794b98dd0.tar.gz pam-459e97431e99fa2c32e30e957993f95794b98dd0.tar.bz2 pam-459e97431e99fa2c32e30e957993f95794b98dd0.zip |
Relevant BUGIDs:
Purpose of commit: cleanup, new feature
Commit summary:
---------------
Merging the the refactorization pam_unix_ref branch into the trunk.
Added support for sha256 and sha512 password hashes to pam_unix
when the libcrypt supports them.
-rw-r--r-- | ChangeLog | 63 | ||||
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | configure.in | 11 | ||||
-rw-r--r-- | modules/pam_unix/.cvsignore | 3 | ||||
-rw-r--r-- | modules/pam_unix/Makefile.am | 17 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix.8.xml | 37 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_acct.c | 221 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_auth.c | 2 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_passwd.c | 704 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_sess.c | 4 | ||||
-rw-r--r-- | modules/pam_unix/passverify.c | 970 | ||||
-rw-r--r-- | modules/pam_unix/passverify.h | 83 | ||||
-rw-r--r-- | modules/pam_unix/support.c | 216 | ||||
-rw-r--r-- | modules/pam_unix/support.h | 16 | ||||
-rw-r--r-- | modules/pam_unix/unix_chkpwd.8 | 80 | ||||
-rw-r--r-- | modules/pam_unix/unix_chkpwd.8.xml | 67 | ||||
-rw-r--r-- | modules/pam_unix/unix_chkpwd.c | 395 | ||||
-rw-r--r-- | modules/pam_unix/unix_update.8.xml | 67 | ||||
-rw-r--r-- | modules/pam_unix/unix_update.c | 194 |
19 files changed, 1779 insertions, 1375 deletions
@@ -1,3 +1,66 @@ +2008-01-23 Tomas Mraz <t8m@centrum.cz> + + * modules/pam_unix/Makefile.am: Add unix_update.8 manpage generated from + XML, generate also unix_chkpwd.8 from XML. + * modules/pam_unix/pam_unix_acct.c: Add rounds parameter to _set_ctrl(). + * modules/pam_unix/pam_unix_auth.c: Likewise. + * modules/pam_unix/pam_unix_sess.c: Likewise. + * modules/pam_unix/pam_unix_passwd.c: Likewise. + * modules/pam_unix/support.c(_set_ctrl): Likewise. + * modules/pam_unix/support.h: Likewise. Add UNIX_SHA256_PASS, + UNIX_SHA512_PASS, and UNIX_ALGO_ROUNDS ctrls. + (pam_sm_chauthtok): Refactor out new password encryption. + * modules/pam_unix/passverify.c(crypt_make_salt): New function. + (crypt_md5_wrapper): Call crypt_make_salt(). + (create_password_hash): New function refactored out of + pam_sm_chauthtok(). Support for new password hashes. + * modules/pam_unix/passverify.h: Drop ascii_to_bin() and bin_to_ascii() + macros. Add prototype for create_password_hash(). + * modules/pam_unix/unix_update.8.xml: New file. + * modules/pam_unix/unix_chkpwd.8.xml: Likewise. + + * 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. + + * modules/pam_unix/pam_unix_acct.c (_unix_run_verify_binary): Return + status and daysleft instead of fake shadow entry. + (pam_sm_acct_mgmt): Call _unix_run_verify_binary() appropriately. + * modules/pam_unix/pam_unix_passwd.c (_unix_verify_shadow): Call + get_account_info() and check_shadow_expiry(). + * modules/pam_unix/support.h: Adjust _unix_run_verify_binary() + prototype. + * modules/pam_unix/support.c (_unix_run_helper_binary): Remove check + on selinux enabled/disabled. + * modules/pam_unix/unix_chkpwd.c (_verify_account): Rename to + _check_expiry(), now checks shadow expiry info. + (main): Remove check on selinux enabled/disabled. Check shadow + expiry through _check_expiry(). + + * modules/pam_unix/pam_unix_acct.c (pam_sm_acct_mgmt): Call + get_account_info() and check_shadow_expiry(). + * modules/pam_unix/passverify.c: Add get_account_info() to + obtain shadow and passwd entry. Add check_shadow_expiry() to + for shadow password expiry check. + (get_pwd_hash): Call get_account_info(). + * modules/pam_unix/passverify.h: Add prototypes for get_account_info() + and check_shadow_expiry(). + 2008-01-08 Thorsten Kukuk <kukuk@thkukuk.de> * doc/man/Makefile.am: Fix manual page dependencies, @@ -4,6 +4,10 @@ Linux-PAM NEWS -- history of user-visible changes. * New module pam_tty_audit.so for enabling and disabling tty auditing. * New PAM items PAM_XDISPLAY and PAM_XAUTHDATA. +* Auditing login denials based by origin (pam_access), time (pam_time), + and number of sessions (pam_limits) to the Linux audit subsystem. +* Support sha256 and sha512 algorithms in pam_unix when they are supported + by crypt(). Release 0.99.9.0 * misc_conv no longer blocks SIGINT; applications that don't want diff --git a/configure.in b/configure.in index 146e177a..cd92f80a 100644 --- a/configure.in +++ b/configure.in @@ -352,9 +352,20 @@ AM_CONDITIONAL([HAVE_AUDIT_TTY_STATUS], BACKUP_LIBS=$LIBS AC_SEARCH_LIBS([crypt],[xcrypt crypt], LIBCRYPT="-l$ac_lib", LIBCRYPT="") +AC_CHECK_FUNCS(crypt_r) LIBS=$BACKUP_LIBS AC_SUBST(LIBCRYPT) +AC_ARG_WITH([randomdev], AC_HELP_STRING([--with-randomdev=(<path>|yes|no)], [use specified random device instead of /dev/urandom or 'no' to disable]), opt_randomdev=$withval) +if test "$opt_randomdev" = yes -o -z "$opt_randomdev"; then + opt_randomdev="/dev/urandom" +elif test "$opt_randomdev" = no; then + opt_randomdev= +fi +if test -n "$opt_randomdev"; then + AC_DEFINE_UNQUOTED(PAM_PATH_RANDOMDEV, "$opt_randomdev", [Random device path.]) +fi + dnl check for libdb or libndbm as fallback. Some libndbm compat dnl libraries are unuseable, so try libdb first. AC_ARG_ENABLE([db], diff --git a/modules/pam_unix/.cvsignore b/modules/pam_unix/.cvsignore index 905ba473..01819c28 100644 --- a/modules/pam_unix/.cvsignore +++ b/modules/pam_unix/.cvsignore @@ -7,5 +7,8 @@ Makefile Makefile.in bigcrypt unix_chkpwd +unix_update README pam_unix.8 +unix_chkpwd.8 +unix_update.8 diff --git a/modules/pam_unix/Makefile.am b/modules/pam_unix/Makefile.am index a74d9762..4d2c58b8 100644 --- a/modules/pam_unix/Makefile.am +++ b/modules/pam_unix/Makefile.am @@ -7,8 +7,8 @@ CLEANFILES = *~ EXTRA_DIST = README md5.c md5_crypt.c lckpwdf.-c $(MANS) CHANGELOG \ tst-pam_unix $(XMLS) -man_MANS = pam_unix.8 unix_chkpwd.8 -XMLS = README.xml pam_unix.8.xml +man_MANS = pam_unix.8 unix_chkpwd.8 unix_update.8 +XMLS = README.xml pam_unix.8.xml unix_chkpwd.8.xml unix_update.8.xml TESTS = tst-pam_unix @@ -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 @@ -50,10 +51,16 @@ bigcrypt_LDADD = @LIBCRYPT@ unix_chkpwd_SOURCES = unix_chkpwd.c md5_good.c md5_broken.c bigcrypt.c \ passverify.c -unix_chkpwd_CFLAGS = $(AM_CFLAGS) @PIE_CFLAGS@ +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.8.xml b/modules/pam_unix/pam_unix.8.xml index 41757977..290cb2b9 100644 --- a/modules/pam_unix/pam_unix.8.xml +++ b/modules/pam_unix/pam_unix.8.xml @@ -260,6 +260,43 @@ </varlistentry> <varlistentry> <term> + <option>sha256</option> + </term> + <listitem> + <para> + When a user changes their password next, + encrypt it with the SHA256 algorithm. If the + SHA256 algorithm is not known to the libcrypt, + fall back to MD5. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>sha512</option> + </term> + <listitem> + <para> + When a user changes their password next, + encrypt it with the SHA512 algorithm. If the + SHA512 algorithm is not known to the libcrypt, + fall back to MD5. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>rounds=<replaceable>n</replaceable></option> + </term> + <listitem> + <para> + Set the optional number of rounds of the SHA256 and SHA512 + password hashing algorithms to <replaceable>n</replaceable>. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> <option>broken_shadow</option> </term> <listitem> diff --git a/modules/pam_unix/pam_unix_acct.c b/modules/pam_unix/pam_unix_acct.c index aeecb132..c09bc175 100644 --- a/modules/pam_unix/pam_unix_acct.c +++ b/modules/pam_unix/pam_unix_acct.c @@ -47,10 +47,6 @@ #include <time.h> /* for time() */ #include <errno.h> #include <sys/wait.h> -#ifdef WITH_SELINUX -#include <selinux/selinux.h> -#define SELINUX_ENABLED is_selinux_enabled()>0 -#endif #include <security/_pam_macros.h> @@ -65,11 +61,8 @@ #include "support.h" #include "passverify.h" -#ifdef WITH_SELINUX - -struct spwd spwd; - -struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user) +int _unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, + const char *user, int *daysleft) { int retval=0, child, fds[2]; void (*sighandler)(int) = NULL; @@ -79,7 +72,7 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons if (pipe(fds) != 0) { D(("could not make pipe")); pam_syslog(pamh, LOG_ERR, "Could not make pipe: %m"); - return NULL; + return PAM_AUTH_ERR; } D(("called.")); @@ -118,7 +111,7 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons } } - if (SELINUX_ENABLED && geteuid() == 0) { + if (geteuid() == 0) { /* must set the real uid to 0 so the helper will not error out if pam is called from setuid binary (su, sudo...) */ setuid(0); @@ -127,7 +120,7 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons /* exec binary helper */ args[0] = x_strdup(CHKPWD_HELPER); args[1] = x_strdup(user); - args[2] = x_strdup("verify"); + args[2] = x_strdup("chkexpiry"); execve(CHKPWD_HELPER, args, envp); @@ -135,11 +128,12 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons /* should not get here: exit with error */ close (fds[1]); D(("helper binary is not available")); + printf("-1\n"); exit(PAM_AUTHINFO_UNAVAIL); } else { close(fds[1]); if (child > 0) { - char buf[1024]; + char buf[32]; int rc=0; rc=waitpid(child, &retval, 0); /* wait for helper to complete */ if (rc<0) { @@ -147,22 +141,16 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons retval = PAM_AUTH_ERR; } else { retval = WEXITSTATUS(retval); - if (retval != PAM_AUTHINFO_UNAVAIL) { - rc = pam_modutil_read(fds[0], buf, sizeof(buf) - 1); - if(rc > 0) { + rc = pam_modutil_read(fds[0], buf, sizeof(buf) - 1); + if(rc > 0) { buf[rc] = '\0'; - if (sscanf(buf,"%ld:%ld:%ld:%ld:%ld:%ld", - &spwd.sp_lstchg, /* last password change */ - &spwd.sp_min, /* days until change allowed. */ - &spwd.sp_max, /* days before change required */ - &spwd.sp_warn, /* days warning for expiration */ - &spwd.sp_inact, /* days before account inactive */ - &spwd.sp_expire) /* date when account expires */ != 6 ) retval = PAM_AUTH_ERR; + if (sscanf(buf,"%d", daysleft) != 1 ) + retval = PAM_AUTH_ERR; } - else { - pam_syslog(pamh, LOG_ERR, " ERROR %d: %m", rc); retval = PAM_AUTH_ERR; + else { + pam_syslog(pamh, LOG_ERR, "read unix_chkpwd output error %d: %m", rc); + retval = PAM_AUTH_ERR; } - } } } else { pam_syslog(pamh, LOG_ERR, "Fork failed: %m"); @@ -175,15 +163,9 @@ struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, cons (void) signal(SIGCHLD, sighandler); /* restore old signal handler */ } D(("Returning %d",retval)); - if (retval != PAM_SUCCESS) { - return NULL; - } - return &spwd; + return retval; } -#endif - - /* * PAM framework looks for this entry-point to pass control to the * account management module. @@ -196,14 +178,13 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, const void *void_uname; const char *uname; int retval, daysleft; - time_t curdays; struct spwd *spent; struct passwd *pwent; char buf[256]; D(("called.")); - ctrl = _set_ctrl(pamh, flags, NULL, argc, argv); + ctrl = _set_ctrl(pamh, flags, NULL, NULL, argc, argv); retval = pam_get_item(pamh, PAM_USER, &void_uname); uname = void_uname; @@ -215,134 +196,90 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, return PAM_USER_UNKNOWN; } - pwent = pam_modutil_getpwnam(pamh, uname); - if (!pwent) { + retval = get_account_info(pamh, uname, &pwent, &spent); + if (retval == PAM_USER_UNKNOWN) { pam_syslog(pamh, LOG_ALERT, "could not identify user (from getpwnam(%s))", uname); - return PAM_USER_UNKNOWN; + return retval; } - if (!strcmp( pwent->pw_passwd, "*NP*" )) { /* NIS+ */ - uid_t save_euid, save_uid; - - save_euid = geteuid(); - save_uid = getuid(); - if (save_uid == pwent->pw_uid) - setreuid( save_euid, save_uid ); - else { - setreuid( 0, -1 ); - if (setreuid( -1, pwent->pw_uid ) == -1) { - setreuid( -1, 0 ); - setreuid( 0, -1 ); - if(setreuid( -1, pwent->pw_uid ) == -1) - return PAM_CRED_INSUFFICIENT; - } - } - spent = pam_modutil_getspnam (pamh, uname); - if (save_uid == pwent->pw_uid) - setreuid( save_uid, save_euid ); - else { - if (setreuid( -1, 0 ) == -1) - setreuid( save_uid, -1 ); - setreuid( -1, save_euid ); - } - - } else if (_unix_shadowed (pwent)) - spent = pam_modutil_getspnam (pamh, uname); - else + if (retval == PAM_SUCCESS && spent == NULL) return PAM_SUCCESS; -#ifdef WITH_SELINUX - if (!spent && SELINUX_ENABLED ) - spent = _unix_run_verify_binary(pamh, ctrl, uname); -#endif - - if (!spent) + if (retval == PAM_UNIX_RUN_HELPER) { + retval = _unix_run_verify_binary(pamh, ctrl, uname, &daysleft); + if (retval == PAM_AUTHINFO_UNAVAIL && + on(UNIX_BROKEN_SHADOW, ctrl)) + return PAM_SUCCESS; + } else if (retval != PAM_SUCCESS) { if (on(UNIX_BROKEN_SHADOW,ctrl)) return PAM_SUCCESS; + else + return retval; + } else + retval = check_shadow_expiry(pamh, spent, &daysleft); - if (!spent) - return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */ - - curdays = time(NULL) / (60 * 60 * 24); - D(("today is %d, last change %d", curdays, spent->sp_lstchg)); - if ((curdays > spent->sp_expire) && (spent->sp_expire != -1)) { + switch (retval) { + case PAM_ACCT_EXPIRED: pam_syslog(pamh, LOG_NOTICE, - "account %s has expired (account expired)", - uname); + "account %s has expired (account expired)", + uname); _make_remark(pamh, ctrl, PAM_ERROR_MSG, - _("Your account has expired; please contact your system administrator")); - D(("account expired")); - return PAM_ACCT_EXPIRED; - } - if (spent->sp_lstchg == 0) { - pam_syslog(pamh, LOG_NOTICE, - "expired password for user %s (root enforced)", - uname); - _make_remark(pamh, ctrl, PAM_ERROR_MSG, - _("You are required to change your password immediately (root enforced)")); - D(("need a new password")); - return PAM_NEW_AUTHTOK_REQD; - } - if (curdays < spent->sp_lstchg) { - pam_syslog(pamh, LOG_DEBUG, - "account %s has password changed in future", - uname); - return PAM_SUCCESS; - } - if ((curdays - spent->sp_lstchg > spent->sp_max) - && (curdays - spent->sp_lstchg > spent->sp_inact) - && (curdays - spent->sp_lstchg > spent->sp_max + spent->sp_inact) - && (spent->sp_max != -1) && (spent->sp_inact != -1)) { + _("Your account has expired; please contact your system administrator")); + break; + case PAM_NEW_AUTHTOK_REQD: + if (daysleft == 0) { + pam_syslog(pamh, LOG_NOTICE, + "expired password for user %s (root enforced)", + uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + _("You are required to change your password immediately (root enforced)")); + } else { + pam_syslog(pamh, LOG_DEBUG, + "expired password for user %s (password aged)", + uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + _("You are required to change your password immediately (password aged)")); + } + break; + case PAM_AUTHTOK_EXPIRED: pam_syslog(pamh, LOG_NOTICE, - "account %s has expired (failed to change password)", - uname); - _make_remark(pamh, ctrl, PAM_ERROR_MSG, - _("Your account has expired; please contact your system administrator")); - D(("account expired 2")); - return PAM_ACCT_EXPIRED; - } - if ((curdays - spent->sp_lstchg > spent->sp_max) && (spent->sp_max != -1)) { - pam_syslog(pamh, LOG_DEBUG, - "expired password for user %s (password aged)", - uname); + "account %s has expired (failed to change password)", + uname); _make_remark(pamh, ctrl, PAM_ERROR_MSG, - _("You are required to change your password immediately (password aged)")); - D(("need a new password 2")); - return PAM_NEW_AUTHTOK_REQD; - } - if ((curdays - spent->sp_lstchg > spent->sp_max - spent->sp_warn) - && (spent->sp_max != -1) && (spent->sp_warn != -1)) { - daysleft = (spent->sp_lstchg + spent->sp_max) - curdays; - pam_syslog(pamh, LOG_DEBUG, - "password for user %s will expire in %d days", - uname, daysleft); + _("Your account has expired; please contact your system administrator")); + break; + case PAM_SUCCESS: + if (daysleft >= 0) { + pam_syslog(pamh, LOG_DEBUG, + "password for user %s will expire in %d days", + uname, daysleft); #if defined HAVE_DNGETTEXT && defined ENABLE_NLS - snprintf (buf, sizeof (buf), - dngettext(PACKAGE, - "Warning: your password will expire in %d day", - "Warning: your password will expire in %d days", - daysleft), - daysleft); + snprintf (buf, sizeof (buf), + dngettext(PACKAGE, + "Warning: your password will expire in %d day", + "Warning: your password will expire in %d days", + daysleft), + daysleft); #else - if (daysleft == 1) - snprintf(buf, sizeof (buf), - _("Warning: your password will expire in %d day"), - daysleft); - else - snprintf(buf, sizeof (buf), - /* TRANSLATORS: only used if dngettext is not support -ed */ - _("Warning: your password will expire in %d days"), - daysleft); + if (daysleft == 1) + snprintf(buf, sizeof (buf), + _("Warning: your password will expire in %d day"), + daysleft); + else + snprintf(buf, sizeof (buf), + /* TRANSLATORS: only used if dngettext is not supported */ + _("Warning: your password will expire in %d days"), + daysleft); #endif - _make_remark(pamh, ctrl, PAM_TEXT_INFO, buf); + _make_remark(pamh, ctrl, PAM_TEXT_INFO, buf); + } } D(("all done")); - return PAM_SUCCESS; + return retval; } diff --git a/modules/pam_unix/pam_unix_auth.c b/modules/pam_unix/pam_unix_auth.c index 3004bee8..dfedd608 100644 --- a/modules/pam_unix/pam_unix_auth.c +++ b/modules/pam_unix/pam_unix_auth.c @@ -111,7 +111,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags D(("called.")); - ctrl = _set_ctrl(pamh, flags, NULL, argc, argv); + ctrl = _set_ctrl(pamh, flags, NULL, NULL, argc, argv); /* Get a few bytes so we can pass our return value to pam_sm_setcred(). */ diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c index 3a61925e..432f687f 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) { @@ -218,7 +142,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 +173,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 +197,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 +231,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 +288,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 +315,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,129 +375,63 @@ 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) || _unix_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 + if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) { + retval = unix_update_shadow(pamh, forwho, towhat); if (retval == PAM_SUCCESS) - if (!_unix_shadowed(pwd)) - retval = _update_passwd(pamh, forwho, "x"); + if (!is_pwd_shadowed(pwd)) + 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; } static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl) { - struct passwd *pwd = NULL; /* Password and shadow password */ - struct spwd *spwdent = NULL; /* file entries for the user */ - time_t curdays; - int retval = PAM_SUCCESS; + struct passwd *pwent = NULL; /* Password and shadow password */ + struct spwd *spent = NULL; /* file entries for the user */ + int daysleft; + int retval; - /* UNIX passwords area */ - pwd = getpwnam(user); /* Get password file entry... */ - if (pwd == NULL) - return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */ + retval = get_account_info(pamh, user, &pwent, &spent); + if (retval == PAM_USER_UNKNOWN) { + return retval; + } - if (_unix_shadowed(pwd)) { - /* ...and shadow password file entry for this user, if shadowing - is enabled */ - setspent(); - spwdent = getspnam(user); - endspent(); + if (retval == PAM_SUCCESS && spent == NULL) + return PAM_SUCCESS; -#ifdef WITH_SELINUX - if (spwdent == NULL && SELINUX_ENABLED ) - spwdent = _unix_run_verify_binary(pamh, ctrl, user); -#endif - if (spwdent == NULL) - return PAM_AUTHINFO_UNAVAIL; - } else { - if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */ - uid_t save_uid; - - save_uid = geteuid(); - seteuid (pwd->pw_uid); - spwdent = getspnam( user ); - seteuid (save_uid); - - if (spwdent == NULL) - return PAM_AUTHINFO_UNAVAIL; - } else - spwdent = NULL; + if (retval == PAM_UNIX_RUN_HELPER) { + retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft); + if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN) + return retval; } + else if (retval == PAM_SUCCESS) + retval = check_shadow_expiry(pamh, spent, &daysleft); + + if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD) + return PAM_SUCCESS; - if (spwdent != NULL) { - /* We have the user's information, now let's check if their account - has expired (60 * 60 * 24 = number of seconds in a day) */ - - if (off(UNIX__IAMROOT, ctrl)) { - /* Get the current number of days since 1970 */ - curdays = time(NULL) / (60 * 60 * 24); - if (curdays < spwdent->sp_lstchg) { - pam_syslog(pamh, LOG_DEBUG, - "account %s has password changed in future", - user); - curdays = spwdent->sp_lstchg; - } - if ((curdays - spwdent->sp_lstchg < spwdent->sp_min) - && (spwdent->sp_min != -1)) - /* - * The last password change was too recent. - */ - retval = PAM_AUTHTOK_ERR; - else if ((curdays - spwdent->sp_lstchg > spwdent->sp_max) - && (curdays - spwdent->sp_lstchg > spwdent->sp_inact) - && (curdays - spwdent->sp_lstchg > - spwdent->sp_max + spwdent->sp_inact) - && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1) - && (spwdent->sp_lstchg != 0)) - /* - * Their password change has been put off too long, - */ - retval = PAM_ACCT_EXPIRED; - else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1) - && (spwdent->sp_lstchg != 0)) - /* - * OR their account has just plain expired - */ - retval = PAM_ACCT_EXPIRED; - } - } return retval; } @@ -1021,8 +499,9 @@ 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; + int rounds = -1; /* <DO NOT free() THESE> */ const char *user; @@ -1031,7 +510,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, D(("called.")); - ctrl = _set_ctrl(pamh, flags, &remember, argc, argv); + ctrl = _set_ctrl(pamh, flags, &remember, &rounds, argc, argv); /* * First get the name of a user @@ -1240,40 +719,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; } @@ -1282,9 +744,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; } @@ -1297,51 +757,13 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, * First we encrypt the new password. */ - if (on(UNIX_MD5_PASS, ctrl)) { - tpass = crypt_md5_wrapper(pass_new); - } else { - /* - * Salt manipulation is stolen from Rick Faith's passwd - * program. Sorry Rick :) -- alex - */ - - time_t tm; - char salt[3]; - - time(&tm); - salt[0] = bin_to_ascii(tm & 0x3f); - salt[1] = bin_to_ascii((tm >> 6) & 0x3f); - salt[2] = '\0'; - - if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) { - /* - * to avoid using the _extensions_ of the bigcrypt() - * function we truncate the newly entered password - * [Problems that followed from this are fixed as per - * Bug 521314.] - */ - char *temp = malloc(9); - - if (temp == NULL) { - pam_syslog(pamh, LOG_CRIT, - "out of memory for password"); - pass_new = pass_old = NULL; /* tidy up */ -#ifdef USE_LCKPWDF - ulckpwdf(); -#endif - return PAM_BUF_ERR; - } - /* copy first 8 bytes of password */ - strncpy(temp, pass_new, 8); - temp[8] = '\0'; - - /* no longer need cleartext */ - tpass = bigcrypt(temp, salt); - - _pam_delete(temp); /* tidy up */ - } else { - tpass = bigcrypt(pass_new, salt); - } + tpass = create_password_hash(pass_new, ctrl, rounds); + if (tpass == NULL) { + pam_syslog(pamh, LOG_CRIT, + "out of memory for password"); + pass_new = pass_old = NULL; /* tidy up */ + unlock_pwdf(); + return PAM_BUF_ERR; } D(("password processed")); @@ -1350,7 +772,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/pam_unix_sess.c b/modules/pam_unix/pam_unix_sess.c index d8d96687..e984578c 100644 --- a/modules/pam_unix/pam_unix_sess.c +++ b/modules/pam_unix/pam_unix_sess.c @@ -73,7 +73,7 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t * pamh, int flags, D(("called.")); - ctrl = _set_ctrl(pamh, flags, NULL, argc, argv); + ctrl = _set_ctrl(pamh, flags, NULL, NULL, argc, argv); retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); if (user_name == NULL || *user_name == '\0' || retval != PAM_SUCCESS) { @@ -107,7 +107,7 @@ PAM_EXTERN int pam_sm_close_session(pam_handle_t * pamh, int flags, D(("called.")); - ctrl = _set_ctrl(pamh, flags, NULL, argc, argv); + ctrl = _set_ctrl(pamh, flags, NULL, NULL, argc, argv); retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); if (user_name == NULL || *user_name == '\0' || retval != PAM_SUCCESS) { diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c index 6587bace..6fc4dcce 100644 --- a/modules/pam_unix/passverify.c +++ b/modules/pam_unix/passverify.c @@ -7,12 +7,43 @@ #include "support.h" #include <stdio.h> #include <string.h> +#include <sys/types.h> #include <unistd.h> +#include <pwd.h> +#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" #include "passverify.h" +#ifdef WITH_SELINUX +#include <selinux/selinux.h> +#define SELINUX_ENABLED is_selinux_enabled()>0 +#else +#define SELINUX_ENABLED 0 +#endif + +#ifdef HELPER_COMPILE +#define pam_modutil_getpwnam(h,n) getpwnam(n) +#define pam_modutil_getspnam(h,n) getspnam(n) +#define pam_syslog(h,a,b,c) helper_log_err(a,b,c) +#else +#include <security/pam_modutil.h> +#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) { @@ -70,7 +101,8 @@ verify_pwd_hash(const char *p, const char *hash, unsigned int nullok) return retval; } -int _unix_shadowed(const struct passwd *pwd) +int +is_pwd_shadowed(const struct passwd *pwd) { if (pwd != NULL) { if (strcmp(pwd->pw_passwd, "x") == 0) { @@ -85,12 +117,946 @@ int _unix_shadowed(const struct passwd *pwd) return 0; } +#ifdef HELPER_COMPILE +int +get_account_info(const char *name, + struct passwd **pwd, struct spwd **spwdent) +#else +int +get_account_info(pam_handle_t *pamh, const char *name, + struct passwd **pwd, struct spwd **spwdent) +#endif +{ + /* UNIX passwords area */ + *pwd = pam_modutil_getpwnam(pamh, name); /* Get password file entry... */ + *spwdent = NULL; + + if (*pwd != NULL) { + if (strcmp((*pwd)->pw_passwd, "*NP*") == 0) + { /* NIS+ */ +#ifdef HELPER_COMPILE + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == (*pwd)->pw_uid) + setreuid(save_euid, save_uid); + else { + setreuid(0, -1); + if (setreuid(-1, (*pwd)->pw_uid) == -1) { + setreuid(-1, 0); + setreuid(0, -1); + if(setreuid(-1, (*pwd)->pw_uid) == -1) + return PAM_CRED_INSUFFICIENT; + } + } + + *spwdent = pam_modutil_getspnam(pamh, name); + if (save_uid == (*pwd)->pw_uid) + setreuid(save_uid, save_euid); + else { + setreuid(-1, 0); + setreuid(save_uid, -1); + setreuid(-1, save_euid); + } + + if (*spwdent == NULL || (*spwdent)->sp_pwdp == NULL) + return PAM_AUTHINFO_UNAVAIL; +#else + /* we must run helper for NIS+ passwords */ + return PAM_UNIX_RUN_HELPER; +#endif + } else if (is_pwd_shadowed(*pwd)) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ +#ifndef HELPER_COMPILE + if (geteuid() || SELINUX_ENABLED) + return PAM_UNIX_RUN_HELPER; +#endif + *spwdent = pam_modutil_getspnam(pamh, name); + if (*spwdent == NULL || (*spwdent)->sp_pwdp == NULL) + return PAM_AUTHINFO_UNAVAIL; + } + } else { + return PAM_USER_UNKNOWN; + } + return PAM_SUCCESS; +} + +#ifdef HELPER_COMPILE +int +get_pwd_hash(const char *name, + struct passwd **pwd, char **hash) +#else +int +get_pwd_hash(pam_handle_t *pamh, const char *name, + struct passwd **pwd, char **hash) +#endif +{ + int retval; + struct spwd *spwdent = NULL; + +#ifdef HELPER_COMPILE + retval = get_account_info(name, pwd, &spwdent); +#else + retval = get_account_info(pamh, name, pwd, &spwdent); +#endif + if (retval != PAM_SUCCESS) { + return retval; + } + + if (spwdent) + *hash = x_strdup(spwdent->sp_pwdp); + else + *hash = x_strdup((*pwd)->pw_passwd); + if (*hash == NULL) + return PAM_BUF_ERR; + + return PAM_SUCCESS; +} + +#ifdef HELPER_COMPILE +int +check_shadow_expiry(struct spwd *spent, int *daysleft) +#else +int +check_shadow_expiry(pam_handle_t *pamh, struct spwd *spent, int *daysleft) +#endif +{ + long int curdays; + *daysleft = -1; + curdays = (long int)(time(NULL) / (60 * 60 * 24)); + D(("today is %d, last change %d", curdays, spent->sp_lstchg)); + if ((curdays > spent->sp_expire) && (spent->sp_expire != -1)) { + D(("account expired")); + return PAM_ACCT_EXPIRED; + } + if (spent->sp_lstchg == 0) { + D(("need a new password")); + *daysleft = 0; + return PAM_NEW_AUTHTOK_REQD; + } + if (curdays < spent->sp_lstchg) { + pam_syslog(pamh, LOG_DEBUG, + "account %s has password changed in future", + spent->sp_namp); + return PAM_SUCCESS; + } + if ((curdays - spent->sp_lstchg > spent->sp_max) + && (curdays - spent->sp_lstchg > spent->sp_inact) + && (curdays - spent->sp_lstchg > spent->sp_max + spent->sp_inact) + && (spent->sp_max != -1) && (spent->sp_inact != -1)) { + *daysleft = (int)((spent->sp_lstchg + spent->sp_max) - curdays); + D(("authtok expired")); + return PAM_AUTHTOK_EXPIRED; + } + if ((curdays - spent->sp_lstchg > spent->sp_max) && (spent->sp_max != -1)) { + D(("need a new password 2")); + return PAM_NEW_AUTHTOK_REQD; + } + if ((curdays - spent->sp_lstchg > spent->sp_max - spent->sp_warn) + && (spent->sp_max != -1) && (spent->sp_warn != -1)) { + *daysleft = (int)((spent->sp_lstchg + spent->sp_max) - curdays); + D(("warn before expiry")); + } + return PAM_SUCCESS; + +} + +/* 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'); +} + +/* <where> must point to a buffer of at least <length>+1 length */ +static void +crypt_make_salt(char *where, int length) +{ + struct timeval tv; + MD5_CTX ctx; + unsigned char tmp[16]; + unsigned char *src = (unsigned char *)where; + int i; +#ifdef PAM_PATH_RANDOMDEV + int fd; + int rv; + + if ((rv = fd = open(PAM_PATH_RANDOMDEV, O_RDONLY)) != -1) { + while ((rv = read(fd, where, length)) != length && errno == EINTR); + close (fd); + } + if (rv != length) { +#endif + /* + * Code lifted from Marek Michalkiewicz's shadow suite. (CG) + * removed use of static variables (AGM) + * + * will work correctly only for length <= 16 */ + src = tmp; + 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, src, length); + GoodMD5Final(tmp, &ctx); +#ifdef PAM_PATH_RANDOMDEV + } +#endif + for (i = 0; i < length; i++) + *where++ = i64c(src[i] & 077); + *where = '\0'; +} + +char * +crypt_md5_wrapper(const char *pass_new) +{ + unsigned char result[16]; + char *cp = (char *) result; + + cp = stpcpy(cp, "$1$"); /* magic for the MD5 */ + crypt_make_salt(cp, 8); + + /* no longer need cleartext */ + cp = Goodcrypt_md5(pass_new, (const char *) result); + pass_new = NULL; + + return cp; +} + +char * +create_password_hash(const char *password, unsigned int ctrl, int rounds) +{ + const char *algoid; + char salt[64]; /* contains rounds number + max 16 bytes of salt + algo id */ + char *sp; + + if (on(UNIX_MD5_PASS, ctrl)) { + return crypt_md5_wrapper(password); + } + if (on(UNIX_SHA256_PASS, ctrl)) { + algoid = "$5$"; + } else if (on(UNIX_SHA512_PASS, ctrl)) { + algoid = "$6$"; + } else { /* must be crypt/bigcrypt */ + char tmppass[9]; + char *crypted; + + crypt_make_salt(salt, 2); + if (off(UNIX_BIGCRYPT, ctrl) && strlen(password) > 8) { + strncpy(tmppass, password, sizeof(tmppass)-1); + tmppass[sizeof(tmppass)-1] = '\0'; + password = tmppass; + } + crypted = bigcrypt(password, salt); + memset(tmppass, '\0', sizeof(tmppass)); + password = NULL; + return crypted; + } + + sp = stpcpy(salt, algoid); + if (on(UNIX_ALGO_ROUNDS, ctrl)) { + sp += snprintf(sp, sizeof(salt) - 3, "rounds=%u$", rounds); + } + crypt_make_salt(sp, 8); + /* For now be conservative so the resulting hashes + * are not too long. 8 bytes of salt prevents dictionary + * attacks well enough. */ + sp = crypt(password, salt); + if (strncmp(algoid, sp, strlen(algoid)) != 0) { + /* libc doesn't know the algorithm, use MD5 */ + memset(sp, '\0', strlen(sp)); + return crypt_md5_wrapper(password); + } + + return x_strdup(sp); +} + +#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 +helper_verify_password(const char *name, const char *p, int nullok) +{ + struct passwd *pwd = NULL; + char *salt = NULL; + int retval; + + retval = get_pwd_hash(name, &pwd, &salt); + + if (pwd == NULL || salt == NULL) { + helper_log_err(LOG_WARNING, "check pass; user unknown"); + retval = PAM_USER_UNKNOWN; + } else { + retval = verify_pwd_hash(p, salt, nullok); + } + + if (salt) { + _pam_overwrite(salt); + _pam_drop(salt); + } + + p = NULL; /* no longer needed here */ + + return retval; +} + +void +helper_log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog(HELPER_COMPILE, LOG_CONS | LOG_PID, LOG_AUTHPRIV); + vsyslog(err, format, args); + va_end(args); + 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 a3ae9210..196e0e33 100644 --- a/modules/pam_unix/passverify.h +++ b/modules/pam_unix/passverify.h @@ -1,11 +1,92 @@ /* * 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" + int verify_pwd_hash(const char *p, const char *hash, unsigned int nullok); int -_unix_shadowed(const struct passwd *pwd); +is_pwd_shadowed(const struct passwd *pwd); + +char * +crypt_md5_wrapper(const char *pass_new); + +char * +create_password_hash(const char *password, unsigned int ctrl, int rounds); + +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,...); + +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); + +int +get_pwd_hash(const char *name, + struct passwd **pwd, char **hash); + +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, + struct passwd **pwd, struct spwd **spwdent); + +int +get_pwd_hash(pam_handle_t *pamh, const char *name, + struct passwd **pwd, char **hash); + +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 /* ****************************************************************** * * Copyright (c) Red Hat, Inc. 2007. diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c index 60acc958..b82cad26 100644 --- a/modules/pam_unix/support.c +++ b/modules/pam_unix/support.c @@ -52,8 +52,8 @@ int _make_remark(pam_handle_t * pamh, unsigned int ctrl, * set the control flags for the UNIX module. */ -int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int argc, - const char **argv) +int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int *rounds, + int argc, const char **argv) { unsigned int ctrl; @@ -109,6 +109,16 @@ int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int argc, *remember = 400; } } + if (rounds != NULL) { + if (j == UNIX_ALGO_ROUNDS) { + *rounds = strtol(*argv + 7, NULL, 10); + if ((*rounds < 1000) || (*rounds == INT_MAX)) + /* don't care about bogus values */ + unset(UNIX_ALGO_ROUNDS, ctrl); + if (*rounds >= 10000000) + *rounds = 9999999; + } + } } ++argv; /* step to next argument */ @@ -376,95 +386,6 @@ int _unix_comesfromsource(pam_handle_t *pamh, } /* - * _unix_blankpasswd() is a quick check for a blank password - * - * returns TRUE if user does not have a password - * - to avoid prompting for one in such cases (CG) - */ - -int -_unix_blankpasswd (pam_handle_t *pamh, unsigned int ctrl, const char *name) -{ - struct passwd *pwd = NULL; - struct spwd *spwdent = NULL; - char *salt = NULL; - int retval; - - D(("called")); - - /* - * This function does not have to be too smart if something goes - * wrong, return FALSE and let this case to be treated somewhere - * else (CG) - */ - - if (on(UNIX__NONULL, ctrl)) - return 0; /* will fail but don't let on yet */ - - /* UNIX passwords area */ - - /* Get password file entry... */ - pwd = pam_modutil_getpwnam (pamh, name); - - if (pwd != NULL) { - if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) - { /* NIS+ */ - uid_t save_euid, save_uid; - - save_euid = geteuid(); - save_uid = getuid(); - if (save_uid == pwd->pw_uid) - setreuid( save_euid, save_uid ); - else { - setreuid( 0, -1 ); - if (setreuid( -1, pwd->pw_uid ) == -1) { - setreuid( -1, 0 ); - setreuid( 0, -1 ); - if(setreuid( -1, pwd->pw_uid ) == -1) - /* Will fail elsewhere. */ - return 0; - } - } - - spwdent = pam_modutil_getspnam (pamh, name); - if (save_uid == pwd->pw_uid) - setreuid( save_uid, save_euid ); - else { - if (setreuid( -1, 0 ) == -1) - setreuid( save_uid, -1 ); - setreuid( -1, save_euid ); - } - } else if (_unix_shadowed(pwd)) { - /* - * ...and shadow password file entry for this user, - * if shadowing is enabled - */ - spwdent = pam_modutil_getspnam(pamh, name); - } - if (spwdent) - salt = x_strdup(spwdent->sp_pwdp); - else - salt = x_strdup(pwd->pw_passwd); - } - /* Does this user have a password? */ - if (salt == NULL) { - retval = 0; - } else { - if (strlen(salt) == 0) - retval = 1; - else - retval = 0; - } - - /* tidy up */ - - if (salt) - _pam_delete(salt); - - return retval; -} - -/* * verify the password of a user */ @@ -518,7 +439,7 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd, } } - if (SELINUX_ENABLED && geteuid() == 0) { + if (geteuid() == 0) { /* must set the real uid to 0 so the helper will not error out if pam is called from setuid binary (su, sudo...) */ setuid(0); @@ -572,11 +493,65 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd, return retval; } +/* + * _unix_blankpasswd() is a quick check for a blank password + * + * returns TRUE if user does not have a password + * - to avoid prompting for one in such cases (CG) + */ + +int +_unix_blankpasswd (pam_handle_t *pamh, unsigned int ctrl, const char *name) +{ + struct passwd *pwd = NULL; + char *salt = NULL; + int retval; + + D(("called")); + + /* + * This function does not have to be too smart if something goes + * wrong, return FALSE and let this case to be treated somewhere + * else (CG) + */ + + if (on(UNIX__NONULL, ctrl)) + return 0; /* will fail but don't let on yet */ + + /* UNIX passwords area */ + + retval = get_pwd_hash(pamh, name, &pwd, &salt); + + if (retval == PAM_UNIX_RUN_HELPER) { + /* salt will not be set here so we can return immediately */ + if (_unix_run_helper_binary(pamh, NULL, ctrl, name) == PAM_SUCCESS) + return 1; + else + return 0; + } + + /* Does this user have a password? */ + if (salt == NULL) { + retval = 0; + } else { + if (strlen(salt) == 0) + retval = 1; + else + retval = 0; + } + + /* tidy up */ + + if (salt) + _pam_delete(salt); + + return retval; +} + int _unix_verify_password(pam_handle_t * pamh, const char *name ,const char *p, unsigned int ctrl) { struct passwd *pwd = NULL; - struct spwd *spwdent = NULL; char *salt = NULL; char *data_name; int retval; @@ -595,48 +570,7 @@ int _unix_verify_password(pam_handle_t * pamh, const char *name D(("locating user's record")); - /* UNIX passwords area */ - pwd = pam_modutil_getpwnam (pamh, name); /* Get password file entry... */ - - if (pwd != NULL) { - if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) - { /* NIS+ */ - uid_t save_euid, save_uid; - - save_euid = geteuid(); - save_uid = getuid(); - if (save_uid == pwd->pw_uid) - setreuid( save_euid, save_uid ); - else { - setreuid( 0, -1 ); - if (setreuid( -1, pwd->pw_uid ) == -1) { - setreuid( -1, 0 ); - setreuid( 0, -1 ); - if(setreuid( -1, pwd->pw_uid ) == -1) - return PAM_CRED_INSUFFICIENT; - } - } - - spwdent = pam_modutil_getspnam (pamh, name); - if (save_uid == pwd->pw_uid) - setreuid( save_uid, save_euid ); - else { - if (setreuid( -1, 0 ) == -1) - setreuid( save_uid, -1 ); - setreuid( -1, save_euid ); - } - } else if (_unix_shadowed(pwd)) { - /* - * ...and shadow password file entry for this user, - * if shadowing is enabled - */ - spwdent = pam_modutil_getspnam (pamh, name); - } - if (spwdent) - salt = x_strdup(spwdent->sp_pwdp); - else - salt = x_strdup(pwd->pw_passwd); - } + retval = get_pwd_hash(pamh, name, &pwd, &salt); data_name = (char *) malloc(sizeof(FAIL_PREFIX) + strlen(name)); if (data_name == NULL) { @@ -646,20 +580,13 @@ int _unix_verify_password(pam_handle_t * pamh, const char *name strcpy(data_name + sizeof(FAIL_PREFIX) - 1, name); } - retval = PAM_SUCCESS; - if (pwd == NULL || salt == NULL || !strcmp(salt, "x") || ((salt[0] == '#') && (salt[1] == '#') && !strcmp(salt + 2, name))) { - - if (pwd != NULL && (geteuid() || SELINUX_ENABLED)) { - /* we are not root perhaps this is the reason? Run helper */ + if (retval != PAM_SUCCESS) { + if (retval == PAM_UNIX_RUN_HELPER) { D(("running helper binary")); retval = _unix_run_helper_binary(pamh, p, ctrl, name); } else { D(("user's record unavailable")); p = NULL; - if (pwd == NULL) - retval = PAM_USER_UNKNOWN; - else - retval = PAM_AUTHINFO_UNAVAIL; if (on(UNIX_AUDIT, ctrl)) { /* this might be a typo and the user has given a password instead of a username. Careful with this. */ @@ -931,6 +858,7 @@ int _unix_read_password(pam_handle_t * pamh * Copyright (c) Andrew G. Morgan 1996-8. * Copyright (c) Alex O. Yuriev, 1996. * Copyright (c) Cristian Gafton 1996. + * Copyright (c) Red Hat, Inc. 2007. * * 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/support.h b/modules/pam_unix/support.h index 94a9b393..9d4f8b85 100644 --- a/modules/pam_unix/support.h +++ b/modules/pam_unix/support.h @@ -84,8 +84,12 @@ typedef struct { #define UNIX_NOREAP 21 /* don't reap child process */ #define UNIX_BROKEN_SHADOW 22 /* ignore errors reading password aging * information during acct management */ +#define UNIX_SHA256_PASS 23 /* new password hashes will use SHA256 */ +#define UNIX_SHA512_PASS 24 /* new password hashes will use SHA512 */ +#define UNIX_ALGO_ROUNDS 25 /* optional number of rounds for new + password hash algorithms */ /* -------------- */ -#define UNIX_CTRLS_ 23 /* number of ctrl arguments defined */ +#define UNIX_CTRLS_ 26 /* number of ctrl arguments defined */ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = @@ -116,6 +120,9 @@ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = /* UNIX_REMEMBER_PASSWD */ {"remember=", _ALL_ON_, 02000000}, /* UNIX_NOREAP */ {"noreap", _ALL_ON_, 04000000}, /* UNIX_BROKEN_SHADOW */ {"broken_shadow", _ALL_ON_, 010000000}, +/* UNIX_SHA256_PASS */ {"sha256", _ALL_ON_^(040420000), 020000000}, +/* UNIX_SHA512_PASS */ {"sha512", _ALL_ON_^(020420000), 040000000}, +/* UNIX_ALGO_ROUNDS */ {"rounds=", _ALL_ON_, 0100000000}, }; #define UNIX_DEFAULTS (unix_args[UNIX__NONULL].flag) @@ -131,8 +138,8 @@ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = extern int _make_remark(pam_handle_t * pamh, unsigned int ctrl ,int type, const char *text); -extern int _set_ctrl(pam_handle_t * pamh, int flags, int *remember, int argc, - const char **argv); +extern int _set_ctrl(pam_handle_t * pamh, int flags, int *remember, int *rounds, + int argc, const char **argv); extern int _unix_getpwnam (pam_handle_t *pamh, const char *name, int files, int nis, struct passwd **ret); @@ -150,5 +157,6 @@ extern int _unix_read_password(pam_handle_t * pamh ,const char *data_name ,const void **pass); -extern struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user); +extern int _unix_run_verify_binary(pam_handle_t *pamh, + unsigned int ctrl, const char *user, int *daysleft); #endif /* _PAM_UNIX_SUPPORT_H */ diff --git a/modules/pam_unix/unix_chkpwd.8 b/modules/pam_unix/unix_chkpwd.8 deleted file mode 100644 index 02ccfe4a..00000000 --- a/modules/pam_unix/unix_chkpwd.8 +++ /dev/null @@ -1,80 +0,0 @@ -.\" Copyright (C) 2003 International Business Machines Corporation -.\" This file is distributed according to the GNU General Public License. -.\" See the file COPYING in the top level source directory for details. -.\" -.de Sh \" Subsection -.br -.if t .Sp -.ne 5 -.PP -\fB\\$1\fR -.PP -.. -.de Sp \" Vertical space (when we can't use .PP) -.if t .sp .5v -.if n .sp -.. -.de Ip \" List item -.br -.ie \\n(.$>=3 .ne \\$3 -.el .ne 3 -.IP "\\$1" \\$2 -.. -.TH "UNIX_CHKPWD" 8 "2003-03-21" "Linux-PAM 0.76" "Linux-PAM Manual" -.SH NAME -unix_chkpwd \- helper binary that verifies the password of the current user -.SH "SYNOPSIS" -.ad l -.hy 0 - -/sbin/unix_chkpwd [\fIusername\fR] -.sp -.ad -.hy -.SH "DESCRIPTION" -.PP -\fBunix_chkpwd\fR is a helper program for the pam_unix module that verifies -the password of the current user. It is not intended to be run directly from -the command line and logs a security violation if done so. - -It is typically installed setuid root or setgid shadow. - -.SH "OPTIONS" -.PP -unix_pwdchk optionally takes the following argument: -.TP -\fIusername\fR -The username of the user whose password you want to check: this must match the current user id. - -.SH "INPUTS" -.PP -unix_pwdchk expects the following inputs via stdin: -.TP -\fIoption\fR -Either nullok or nonull, depending on whether the user can have an empty password. -.TP -\fIpassword\fR -The password to verify. - -.SH "RETURN CODES" -.PP -\fBunix_chkpwd\fR has the following return codes: -.TP -1 -unix_chkpwd was inappropriately called from the command line or the password is incorrect. - -.TP -0 -The password is correct. - -.SH "HISTORY" -Written by Andrew Morgan - -.SH "SEE ALSO" - -.PP -\fBpam\fR(8) - -.SH AUTHOR -Emily Ratliff. - diff --git a/modules/pam_unix/unix_chkpwd.8.xml b/modules/pam_unix/unix_chkpwd.8.xml new file mode 100644 index 00000000..a10dbe33 --- /dev/null +++ b/modules/pam_unix/unix_chkpwd.8.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding='UTF-8'?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" + "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"> + +<refentry id="unix_chkpwd"> + + <refmeta> + <refentrytitle>unix_chkpwd</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo> + </refmeta> + + <refnamediv id="unix_chkpwd-name"> + <refname>unix_chkpwd</refname> + <refpurpose>Helper binary that verifies the password of the current user</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis id="unix_chkpwd-cmdsynopsis"> + <command>unix_chkpwd</command> + <arg choice="opt"> + ... + </arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1 id="unix_chkpwd-description"> + + <title>DESCRIPTION</title> + + <para> + <emphasis>unix_chkpwd</emphasis> is a helper program for the + <emphasis>pam_unix</emphasis> module that verifies the + password of the current user. It also checks password and account + expiration dates in <emphasis>shadow</emphasis>. It is not intended to + be run directly from the command line and logs a security violation if + done so. + </para> + + <para> + It is typically installed setuid root or setgid shadow. + </para> + + <para> + The interface of the helper - command line options, and input/output + data format are internal to the <emphasis>pam_unix</emphasis> + module and it should not be called directly from applications. + </para> + </refsect1> + + <refsect1 id='unix_chkpwd-see_also'> + <title>SEE ALSO</title> + <para> + <citerefentry> + <refentrytitle>pam_unix</refentrytitle><manvolnum>8</manvolnum> + </citerefentry> + </para> + </refsect1> + + <refsect1 id='unix_chkpwd-author'> + <title>AUTHOR</title> + <para> + Written by Andrew Morgan and other various people. + </para> + </refsect1> + +</refentry> diff --git a/modules/pam_unix/unix_chkpwd.c b/modules/pam_unix/unix_chkpwd.c index 1e8944e9..11ac3aac 100644 --- a/modules/pam_unix/unix_chkpwd.c +++ b/modules/pam_unix/unix_chkpwd.c @@ -13,7 +13,6 @@ #include "config.h" -#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -25,329 +24,34 @@ #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 _log_err(int err, const char *format,...) -{ - va_list args; - - va_start(args, format); - openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV); - vsyslog(err, format, args); - va_end(args); - 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) { - _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 _verify_account(const char * const uname) +static int _check_expiry(const char *uname) { struct spwd *spent; struct passwd *pwent; - - pwent = getpwnam(uname); - if (!pwent) { - _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname); - return PAM_USER_UNKNOWN; - } - - spent = getspnam( uname ); - if (!spent) { - _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname); - return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */ - } - printf("%ld:%ld:%ld:%ld:%ld:%ld", - spent->sp_lstchg, /* last password change */ - spent->sp_min, /* days until change allowed. */ - spent->sp_max, /* days before change required */ - spent->sp_warn, /* days warning for expiration */ - spent->sp_inact, /* days before account inactive */ - spent->sp_expire); /* date when account expires */ - - return PAM_SUCCESS; -} - -static int _unix_verify_password(const char *name, const char *p, int nullok) -{ - struct passwd *pwd = NULL; - struct spwd *spwdent = NULL; - char *salt = NULL; - int retval = PAM_AUTH_ERR; - - /* UNIX passwords area */ - setpwent(); - pwd = getpwnam(name); /* Get password file entry... */ - endpwent(); - if (pwd != NULL) { - if (_unix_shadowed(pwd)) { - /* - * ...and shadow password file entry for this user, - * if shadowing is enabled - */ - setspent(); - spwdent = getspnam(name); - endspent(); - if (spwdent != NULL) - salt = x_strdup(spwdent->sp_pwdp); - else - pwd = NULL; - } else { - if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */ - uid_t save_uid; - - save_uid = geteuid(); - seteuid(pwd->pw_uid); - spwdent = getspnam(name); - seteuid(save_uid); - - salt = x_strdup(spwdent->sp_pwdp); - } else { - salt = x_strdup(pwd->pw_passwd); - } - } + int retval; + int daysleft; + + retval = get_account_info(uname, &pwent, &spent); + if (retval != PAM_SUCCESS) { + helper_log_err(LOG_ALERT, "could not obtain user info (%s)", uname); + printf("-1\n"); + return retval; } - if (pwd == NULL || salt == NULL) { - _log_err(LOG_WARNING, "check pass; user unknown"); - retval = PAM_USER_UNKNOWN; - } else { - retval = verify_pwd_hash(p, salt, nullok); - } - - if (salt) { - _pam_overwrite(salt); - _pam_drop(salt); + + if (spent == NULL) { + printf("-1\n"); + return retval; } - p = NULL; /* no longer needed here */ - - 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? */ - - _log_err(LOG_DEBUG, "no password supplied"); - return PAM_AUTHTOK_ERR; - - } else if (npass >= MAXPASS) { - - _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 = _unix_verify_password(forwho, pass, 0); - if (retval != PAM_SUCCESS) { + retval = check_shadow_expiry(spent, &daysleft); + printf("%d\n", daysleft); 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? */ - - _log_err(LOG_DEBUG, "no new password supplied"); - return PAM_AUTHTOK_ERR; - - } else if (npass >= MAXPASS) { - - _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[]) @@ -355,9 +59,10 @@ int main(int argc, char *argv[]) char pass[MAXPASS + 1]; char *option; int npass, nullok; - int force_failure = 0; + int blankpass = 0; int retval = PAM_AUTH_ERR; char *user; + char *passwords[] = { pass }; /* * Catch or ignore as many signal as possible. @@ -374,7 +79,7 @@ int main(int argc, char *argv[]) */ if (isatty(STDIN_FILENO) || argc != 3 ) { - _log_err(LOG_NOTICE + helper_log_err(LOG_NOTICE ,"inappropriate use of Unix helper binary [UID=%d]" ,getuid()); fprintf(stderr @@ -386,11 +91,9 @@ int main(int argc, char *argv[]) /* * Determine what the current user's name is. - * On a SELinux enabled system with a strict policy leaving the - * existing check prevents shadow password authentication from working. * We must thus skip the check if the real uid is 0. */ - if (SELINUX_ENABLED && getuid() == 0) { + if (getuid() == 0) { user=argv[1]; } else { @@ -404,63 +107,49 @@ int main(int argc, char *argv[]) option=argv[2]; - if (strncmp(argv[2], "verify", 8) == 0) { - /* Get the account information from the shadow file */ - return _verify_account(argv[1]); - } - - if (strncmp(option, "shadow", 8) == 0) { - /* Attempting to change the password */ - return _update_shadow(argv[1]); - } - + if (strcmp(option, "chkexpiry") == 0) + /* Check account information from the shadow file */ + 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); + npass = read_passwords(STDIN_FILENO, 1, passwords); - if (npass < 0) { /* is it a valid password? */ - - _log_err(LOG_DEBUG, "no password supplied"); - - } else if (npass >= MAXPASS) { - - _log_err(LOG_DEBUG, "password too long"); - - } else { - if (npass == 0) { - /* the password is NULL */ - - retval = _unix_verify_password(user, NULL, nullok); - - } else { - /* does pass agree with the official one? */ - - pass[npass] = '\0'; /* NUL terminate */ - retval = _unix_verify_password(user, pass, nullok); + if (npass != 1) { /* is it a valid password? */ + helper_log_err(LOG_DEBUG, "no password supplied"); + *pass = '\0'; + } - } + 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 */ - if ((retval != PAM_SUCCESS) || force_failure) { - _log_err(LOG_NOTICE, "password check failed for user (%s)", user); - return PAM_AUTH_ERR; + if (retval != PAM_SUCCESS) { + if (!nullok || !blankpass) + /* no need to log blank pass test */ + helper_log_err(LOG_NOTICE, "password check failed for user (%s)", user); + return PAM_AUTH_ERR; } else { - return PAM_SUCCESS; + return PAM_SUCCESS; } } /* * 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 diff --git a/modules/pam_unix/unix_update.8.xml b/modules/pam_unix/unix_update.8.xml new file mode 100644 index 00000000..07695951 --- /dev/null +++ b/modules/pam_unix/unix_update.8.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding='UTF-8'?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" + "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"> + +<refentry id="unix_update"> + + <refmeta> + <refentrytitle>unix_update</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo> + </refmeta> + + <refnamediv id="unix_update-name"> + <refname>unix_update</refname> + <refpurpose>Helper binary that updates the password of a given user</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis id="unix_update-cmdsynopsis"> + <command>unix_update</command> + <arg choice="opt"> + ... + </arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1 id="unix_update-description"> + + <title>DESCRIPTION</title> + + <para> + <emphasis>unix_update</emphasis> is a helper program for the + <emphasis>pam_unix</emphasis> module that updates the + password of a given user. It is not intended to be run directly + from the command line and logs a security violation if done so. + </para> + + <para> + The purpose of the helper is to enable tighter confinement of + login and password changing services. The helper is thus called only + when SELinux is enabled and in the enforcing mode on the system. + </para> + + <para> + The interface of the helper - command line options, and input/output + data format are internal to the <emphasis>pam_unix</emphasis> + module and it should not be called directly from applications. + </para> + </refsect1> + + <refsect1 id='unix_update-see_also'> + <title>SEE ALSO</title> + <para> + <citerefentry> + <refentrytitle>pam_unix</refentrytitle><manvolnum>8</manvolnum> + </citerefentry> + </para> + </refsect1> + + <refsect1 id='unix_update-author'> + <title>AUTHOR</title> + <para> + Written by Tomas Mraz and other various people. + </para> + </refsect1> + +</refentry> 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. + */ |