--- /dev/null 2006-01-18 09:11:26.108932750 +0100 +++ pam_ccreds-3/cc_validate.c 2006-01-04 16:21:15.000000000 +0100 @@ -0,0 +1,268 @@ +/* + * This program is designed to run setuid(root) or with sufficient + * privilege to read the cached password database. 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. + * + */ + +#ifdef MEMORY_DEBUG +# undef exit +# undef strdup +# undef free +#endif /* MEMORY_DEBUG */ + +#include <stdarg.h> +#include <signal.h> +#include <sys/types.h> +#include <pwd.h> +#include <syslog.h> + +#include "cc_private.h" + +#define MAXPASS 200 /* the maximum length of a password */ + +#define CCREDS_PASSED 0 +#define CCREDS_FAILED 1 + +/* syslogging function for errors and other information */ + +static void _log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog("ccreds_validate", LOG_CONS | LOG_PID, LOG_AUTH); + 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 _ccreds_verify_password(const char *service, const char *name, + const char *p) +{ + int rc, retval = CCREDS_FAILED; + pam_cc_handle_t *pamcch; + + rc = pam_cc_start(service, name, NULL, CC_FLAGS_READ_ONLY, + &pamcch); + if (rc != PAM_SUCCESS) { + _log_err(LOG_DEBUG, "error initializing"); + retval = CCREDS_FAILED; + goto _return; + } + + rc = pam_cc_validate_credentials(pamcch, PAM_CC_TYPE_DEFAULT, p, + strlen(p)); + if (rc != PAM_SUCCESS) { + _log_err(LOG_DEBUG, "error reading cached credentials"); + retval = CCREDS_FAILED; + goto _return; + } + + retval = CCREDS_PASSED; + + pam_cc_end(&pamcch); + +_return: + 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; +} + +int main(int argc, char *argv[]) +{ + char pass[MAXPASS + 1]; + int npass; + int force_failure = 0; + int retval = CCREDS_FAILED; + char *user; + char *user_arg; + char *service = NULL; + + /* + * 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)) { + + _log_err(LOG_NOTICE + ,"inappropriate use of ccreds helper binary [UID=%d,tty]" + ,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 CCREDS_FAILED; + } + + /* + * determine the current user's name is + */ + user = getuidname(getuid()); + + if (argc == 2) { + user_arg = argv[1]; + service = NULL; + } else if (argc == 3) { + user_arg = argv[1]; + service = argv[2]; + } else { + _log_err(LOG_NOTICE + ,"inappropriate use of ccreds helper binary [UID=%d,bad argv]" + ,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 CCREDS_FAILED; + } + + /* if the caller specifies the username, verify that user matches it */ + if (strcmp(user, user_arg)) { + force_failure = 1; + } + + /* read the password from stdin (a pipe from the pam_ccreds module) */ + + npass = read(STDIN_FILENO, pass, MAXPASS); + + + 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 blank */ + + retval = _ccreds_verify_password(service, user, ""); + } else { + /* does pass agree with the official one? */ + + pass[npass] = '\0'; /* NUL terminate */ + retval = _ccreds_verify_password(service, user, pass); + } + } + + memset(pass, '\0', MAXPASS); /* clear memory of the password */ + + /* return pass or fail */ + + if ((retval != CCREDS_PASSED) || force_failure) { + return CCREDS_FAILED; + } else { + return CCREDS_PASSED; + } +} + +/* + * This program is based on unix_chkpwd by Andrew G. Morgan. + * + * The modifications are Copyright (c) W. Michael Petullo, 2005. + * All rights reserved. + * + * See below for the original unix_chkpwd copyright notice. + * + * Copyright (c) Andrew G. Morgan, 1996. 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. + */ --- pam_ccreds-3/Makefile.am.chkpwd 2005-10-29 03:21:50.000000000 +0200 +++ pam_ccreds-3/Makefile.am 2006-01-18 16:30:58.000000000 +0100 @@ -1,8 +1,10 @@ noinst_PROGRAMS = pam_ccreds.so cc_test cc_dump +sbin_PROGRAMS = ccreds_validate EXTRA_DIST = COPYING.LIB CVSVersionInfo.txt ChangeLog README \ ldap.conf pam.conf pam_ccreds.spec AM_CFLAGS = -fno-strict-aliasing +INCLUDES = -DCCREDS_VALIDATE="\"$(sbindir)/ccreds_validate\"" pam_ccreds_so_SOURCES = cc_db.c cc_lib.c cc_pam.c cc.h pam_ccreds_so_LDFLAGS = @pam_ccreds_so_LDFLAGS@ @@ -13,6 +15,10 @@ cc_dump_SOURCES = cc_dump.c cc_dump_LDFLAGS = -Wl,-rpath -Wl,$(libdir)/security pam_ccreds.so -lpam -lpam_misc +ccreds_validate_SOURCES = cc_validate.c +ccreds_validate_CFLAGS = -fPIE $(AM_CFLAGS) +ccreds_validate_LDFLAGS = -pie -Wl,-rpath -Wl,$(libdir)/security pam_ccreds.so -lpam -lpam_misc + DEFS = @DEFS@ if USE_NATIVE_LINKER --- pam_ccreds-3/cc_pam.c.chkpwd 2005-10-29 03:21:50.000000000 +0200 +++ pam_ccreds-3/cc_pam.c 2006-01-04 16:21:15.000000000 +0100 @@ -131,10 +131,12 @@ const char *authtok; pam_cc_handle_t *pamcch; - rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), + if (! geteuid()) { + rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), ccredsfile, CC_FLAGS_READ_ONLY, &pamcch); - if (rc != PAM_SUCCESS) { - return rc; + if (rc != PAM_SUCCESS) { + return rc; + } } authtok = NULL; @@ -146,24 +148,18 @@ if (rc == PAM_SUCCESS) { if (authtok == NULL) authtok = ""; - - rc = pam_cc_validate_credentials(pamcch, PAM_CC_TYPE_DEFAULT, - authtok, strlen(authtok)); } if ((sm_flags & SM_FLAGS_USE_FIRST_PASS) || (rc == PAM_SUCCESS)) break; case 0: rc = _pam_sm_interact(pamh, flags, &authtok); if (rc != PAM_SUCCESS) { - pam_cc_end(&pamcch); - return rc; + break; } if (authtok == NULL) authtok = ""; - rc = pam_cc_validate_credentials(pamcch, PAM_CC_TYPE_DEFAULT, - authtok, strlen(authtok)); break; default: syslog(LOG_ERR, "pam_ccreds: internal error."); @@ -171,47 +167,63 @@ } if (rc == PAM_SUCCESS) { + if (! geteuid()) + rc = pam_cc_validate_credentials(pamcch, PAM_CC_TYPE_DEFAULT, + authtok, strlen(authtok)); + else + rc = pam_cc_run_helper_binary(pamh, CCREDS_VALIDATE, authtok, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0)); + } + + if (rc == PAM_SUCCESS) { _pam_sm_display_message(pamh, "You have been logged on using cached credentials.", PAM_TEXT_INFO, flags); } - pam_cc_end(&pamcch); + if (! geteuid()) + pam_cc_end(&pamcch); return rc; } static int _pam_sm_store_cached_credentials(pam_handle_t *pamh, - int flags, unsigned int sm_flags, - const char *ccredsfile) + int flags, unsigned int sm_flags, + const char *ccredsfile) { - int rc; - const char *authtok; - pam_cc_handle_t *pamcch; - - rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), - ccredsfile, 0, &pamcch); - if (rc != PAM_SUCCESS) { - return rc; - } - - authtok = NULL; - - rc = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&authtok); - if (rc != PAM_SUCCESS) { - pam_cc_end(&pamcch); - return rc; + int rc; + const char *authtok; + pam_cc_handle_t *pamcch; + + if (! geteuid()) { + rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), + ccredsfile, 0, &pamcch); + if (rc != PAM_SUCCESS) { + return rc; + } } - if (authtok == NULL) - authtok = ""; + authtok = NULL; - rc = pam_cc_store_credentials(pamcch, PAM_CC_TYPE_DEFAULT, - authtok, strlen(authtok)); + rc = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&authtok); + if (rc != PAM_SUCCESS) { + pam_cc_end(&pamcch); + return rc; + } + + if (authtok == NULL) + authtok = ""; + + if (! geteuid()) + rc = pam_cc_store_credentials(pamcch, PAM_CC_TYPE_DEFAULT, + authtok, strlen(authtok)); + else + /* Unable to perform when not root; just return success. */ + rc = PAM_SUCCESS; - pam_cc_end(&pamcch); + if (! geteuid()) + pam_cc_end(&pamcch); - return rc; + return rc; } static int _pam_sm_update_cached_credentials(pam_handle_t *pamh, @@ -222,27 +234,36 @@ const char *authtok; pam_cc_handle_t *pamcch; - rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), - ccredsfile, 0, &pamcch); - if (rc != PAM_SUCCESS) { - return rc; - } - authtok = NULL; + /* FIXME: the logic of this function is a little difficult. + * It may be wiser to provide an alternate implementation of the + * pam_cc_db_* interface. + */ + if (! geteuid()) { + rc = pam_cc_start_ex(pamh, ((sm_flags & SM_FLAGS_SERVICE_SPECIFIC) != 0), + ccredsfile, 0, &pamcch); + if (rc != PAM_SUCCESS) { + return rc; + } + } + rc = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&authtok); - if (rc != PAM_SUCCESS) { - pam_cc_end(&pamcch); - return rc; - } + if (rc == PAM_SUCCESS) { - if (authtok == NULL) - authtok = ""; + if (authtok == NULL) + authtok = ""; - rc = pam_cc_delete_credentials(pamcch, PAM_CC_TYPE_DEFAULT, - authtok, strlen(authtok)); + if (! geteuid()) + rc = pam_cc_delete_credentials(pamcch, PAM_CC_TYPE_DEFAULT, + authtok, strlen(authtok)); + else + /* Unable to perform when not root; just return success. */ + rc = PAM_SUCCESS; + } - pam_cc_end(&pamcch); + if (! geteuid()) + pam_cc_end(&pamcch); return rc; } --- pam_ccreds-3/README.chkpwd 2005-10-29 03:21:50.000000000 +0200 +++ pam_ccreds-3/README 2006-01-04 16:21:15.000000000 +0100 @@ -35,14 +35,13 @@ These are configured by the "action=" module option. The following module options are also recognized: - use_first_pass do not prompt for the password if - the existing PAM authentication - token does not validate - - try_first_pass prompt for the password if the - existing PAM authentication token - does not validate + use_first_pass use an existing PAM authentication + token if there is any, fail otherwise + try_first_pass use an existing PAM authentication + token if there is any, prompt for + the password otherwise + service_specific only check cached credentials for this specific service --- pam_ccreds-3/cc_lib.c.chkpwd 2005-10-29 03:21:50.000000000 +0200 +++ pam_ccreds-3/cc_lib.c 2006-01-04 16:21:15.000000000 +0100 @@ -15,6 +15,9 @@ #include <errno.h> #include <limits.h> #include <syslog.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> #include <openssl/sha.h> @@ -597,3 +601,78 @@ return rc; } +int pam_cc_run_helper_binary(pam_handle_t *pamh, const char *helper, + const char *passwd, int service_specific) +{ + int retval, child, fds[2], rc; + void (*sighandler)(int) = NULL; + const void *service, *user; + + rc = pam_get_item(pamh, PAM_USER, &user); + if (rc != PAM_SUCCESS) { + syslog(LOG_WARNING, "pam_ccreds: failed to lookup user"); + return PAM_AUTH_ERR; + } + + if (service_specific) { + rc = pam_get_item(pamh, PAM_SERVICE, &service); + if (rc != PAM_SUCCESS) { + syslog(LOG_WARNING, "pam_ccreds: failed to lookup service"); + return PAM_AUTH_ERR; + } + } else + service = NULL; + + /* create a pipe for the password */ + if (pipe(fds) != 0) { + syslog(LOG_WARNING, "pam_ccreds: failed to create pipe"); + return PAM_AUTH_ERR; + } + + sighandler = signal(SIGCHLD, SIG_DFL); + + /* fork */ + child = fork(); + if (child == 0) { + static char *envp[] = { NULL }; + char *args[] = { NULL, NULL, NULL, NULL }; + + /* XXX - should really tidy up PAM here too */ + + /* reopen stdin as pipe */ + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + + /* exec binary helper */ + args[0] = x_strdup(helper); + args[1] = x_strdup(user); + if (service) + args[2] = x_strdup(service); + + execve(helper, args, envp); + + /* should not get here: exit with error */ + syslog(LOG_WARNING, "pam_ccreds: helper binary is not available"); + exit(PAM_AUTHINFO_UNAVAIL); + } else if (child > 0) { + if (passwd != NULL) { /* send the password to the child */ + write(fds[1], passwd, strlen(passwd)+1); + passwd = NULL; + } else { + write(fds[1], "", 1); /* blank password */ + } + close(fds[0]); /* close here to avoid possible SIGPIPE above */ + close(fds[1]); + (void) waitpid(child, &retval, 0); /* wait for helper to complete */ + retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR; + } else { + syslog(LOG_WARNING, "pam_ccreds: fork failed"); + retval = PAM_AUTH_ERR; + } + + if (sighandler != NULL) { + (void) signal(SIGCHLD, sighandler); /* restore old signal handler */ + } + + return retval; +} --- pam_ccreds-3/cc.h.chkpwd 2005-10-29 03:21:50.000000000 +0200 +++ pam_ccreds-3/cc.h 2006-01-04 16:21:15.000000000 +0100 @@ -89,5 +89,9 @@ /* Dump contents - for debugging only */ int pam_cc_dump(pam_cc_handle_t *pamcch, FILE *fp); +/* Execute ccreds_* */ +int pam_cc_run_helper_binary(pam_handle_t *pamh, const char *helper, + const char *passwd, int service_specific); + #endif /* _PAM_CC_H_ */