summaryrefslogtreecommitdiff
path: root/modules/pam_tally
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2000-06-20 22:10:38 +0000
committerAndrew G. Morgan <morgan@kernel.org>2000-06-20 22:10:38 +0000
commitea488580c42e8918445a945484de3c8a5addc761 (patch)
treec992f3ba699caafedfadc16af38e6359c3c24698 /modules/pam_tally
Initial revision
Diffstat (limited to 'modules/pam_tally')
-rw-r--r--modules/pam_tally/.cvsignore2
-rw-r--r--modules/pam_tally/Makefile109
-rw-r--r--modules/pam_tally/README95
-rw-r--r--modules/pam_tally/faillog.h55
-rw-r--r--modules/pam_tally/pam_tally.c689
-rw-r--r--modules/pam_tally/pam_tally_app.c7
6 files changed, 957 insertions, 0 deletions
diff --git a/modules/pam_tally/.cvsignore b/modules/pam_tally/.cvsignore
new file mode 100644
index 00000000..e1a4f48f
--- /dev/null
+++ b/modules/pam_tally/.cvsignore
@@ -0,0 +1,2 @@
+dynamic
+pam_tally
diff --git a/modules/pam_tally/Makefile b/modules/pam_tally/Makefile
new file mode 100644
index 00000000..78ce621d
--- /dev/null
+++ b/modules/pam_tally/Makefile
@@ -0,0 +1,109 @@
+#
+# $Id$
+#
+# This Makefile controls a build process of $(TITLE) module and
+# application for Linux-PAM. You should not modify this Makefile
+# (unless you know what you are doing!).
+#
+#
+
+TITLE=pam_tally
+
+#
+## Additional rules for making (and moving) the application added.
+## Assuming that all modules' applications are called $TITLE
+#
+
+LIBSRC = $(TITLE).c
+LIBOBJ = $(TITLE).o
+LIBOBJD = $(addprefix dynamic/,$(LIBOBJ))
+LIBOBJS = $(addprefix static/,$(LIBOBJ))
+
+APPSRC = $(TITLE)_app.c
+APPOBJ = $(TITLE)_app.o
+APPOBJD = $(addprefix dynamic/,$(APPOBJ))
+APPOBJS = $(addprefix static/,$(APPOBJ))
+
+dynamic/%.o : %.c
+ $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+static/%.o : %.c
+ $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+
+ifdef DYNAMIC
+LIBSHARED = $(TITLE).so
+endif
+
+ifdef STATIC
+LIBSTATIC = lib$(TITLE).o
+endif
+
+APPLICATION = $(TITLE)
+APPMODE = 755
+
+####################### don't edit below #######################
+
+dummy:
+ @echo "**** This is not a top-level Makefile "
+ exit
+
+all: dirs $(LIBSHARED) $(LIBSTATIC) register
+# we're not yet in a position to build this. If you want it, build it
+# separately...
+# $(APPLICATION)
+
+dirs:
+ifdef DYNAMIC
+ $(MKDIR) ./dynamic
+endif
+ifdef STATIC
+ $(MKDIR) ./static
+endif
+
+register:
+ifdef STATIC
+ ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) )
+endif
+
+ifdef DYNAMIC
+$(LIBOBJD): $(LIBSRC)
+
+$(LIBSHARED): $(LIBOBJD)
+ $(LD_D) -o $@ $(LIBOBJD)
+
+$(APPLICATION): $(APPOBJD)
+ $(CC) $(CFLAGS) -o $@ $< $(LOADLIBES)
+
+endif
+
+ifdef STATIC
+$(LIBOBJS): $(LIBSRC)
+
+$(LIBSTATIC): $(LIBOBJS)
+ $(LD) -r -o $@ $(LIBOBJS)
+
+$(APPLICATION): $(APPOBJS)
+ $(CC) $(CFLAGS) -o $@ $< $(LOADLIBES)
+endif
+
+install: all
+ $(MKDIR) $(FAKEROOT)$(SECUREDIR)
+ifdef DYNAMIC
+ $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)
+endif
+ $(MKDIR) $(FAKEROOT)$(SUPLEMENTED)
+# $(INSTALL) -m $(APPMODE) $(APPLICATION) $(FAKEROOT)$(SUPLEMENTED)
+
+remove:
+ rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so
+ rm -f $(FAKEROOT)$(SUPLEMENTED)/$(TITLE)
+
+clean:
+ rm -f $(LIBOBJD) $(LIBOBJS) $(APPOBJD) $(APPOBJS) core *~
+
+extraclean: clean
+ rm -f *.a *.o *.so *.bak dynamic/* static/* $(APPLICATION)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
diff --git a/modules/pam_tally/README b/modules/pam_tally/README
new file mode 100644
index 00000000..b58b24e4
--- /dev/null
+++ b/modules/pam_tally/README
@@ -0,0 +1,95 @@
+SUMMARY:
+ pam_tally:
+
+ Maintains a count of attempted accesses, can reset count on success,
+ can deny access if too many attempts fail.
+
+ Options:
+
+ * onerr=[succeed|fail] (if something weird happens
+ such as unable to open the file, what to do?)
+ * file=/where/to/keep/counts (default /var/log/faillog)
+
+ (auth)
+ Authentication phase increments attempted login counter.
+ * no_magic_root (root DOES increment counter. Use for
+ daemon-based stuff, like telnet/rsh/login)
+
+ (account)
+ Account phase can deny access and/or reset attempts counter.
+ * deny=n (deny access if tally for this user exceeds n;
+ The presence of deny=n changes the default for
+ reset/no_reset to reset, unless the user trying to
+ gain access is root and the no_magic_root option
+ has NOT been specified.)
+
+ * no_magic_root (access attempts by root DON'T ignore deny.
+ Use this for daemon-based stuff, like telnet/rsh/login)
+ * even_deny_root_account (Root can become unavailable. BEWARE.
+ Note that magic root trying to gain root bypasses this,
+ but normal users can be locked out.)
+
+ * reset (reset count to 0 on successful entry, even for
+ magic root)
+ * no_reset (don't reset count on successful entry)
+ This is the default unless deny exists and the
+ user attempting access is NOT magic root.
+
+ * per_user (If /var/log/faillog contains a non-zero
+ .fail_max field for this user then use it
+ instead of deny=n parameter)
+
+ * no_lock_time (Don't use .fail_locktime filed in
+ /var/log/faillog for this user)
+
+ Also checks to make sure that the counts file is a plain
+ file and not world writable.
+
+ - Tim Baverstock <warwick@sable.demon.co.uk>, v0.1 5 March 1997
+
+LONGER:
+
+pam_tally comes in two parts: pam_tally.so and pam_tally.
+
+pam_tally.so sits in a pam config file, in the auth and account sections.
+
+In the auth section, it increments a per-uid counter for each attempted
+login, in the account section, it denies access if attempted logins
+exceed some threashold and/or resets that counter to zero on successful
+login.
+
+Root is treated specially:
+
+1. When a process already running as root tries to access some service, the
+access is `magic', and bypasses pam_tally's checks: handy for `su'ing from
+root into an account otherwise blocked. However, for services like telnet or
+login which always effectively run from the root account, root (ie everyone)
+shouldn't be granted this magic status, and the flag `no_magic_root' should
+be set in this situation, as noted in the summary above. [This option may
+be obsolete, with `sufficient root' processing.]
+
+2. Normally, failed attempts to access root will NOT cause the root
+account to become blocked, to prevent denial-of-service: if your users aren't
+given shell accounts and root may only login via `su' or at the machine
+console (not telnet/rsh, etc), this is safe. If you really want root to be
+blocked for some given service, use even_deny_root_account.
+
+pam_tally is an (optional) application which can be used to interrogate and
+manipulate the counter file. It can display users' counts, set individual
+counts, or clear all counts. Setting artificially high counts may be useful
+for blocking users without changing their passwords. I found it useful to
+clear all counts every midnight from a cron..
+
+The counts file is organised as a binary-word array, indexed by uid. You
+can probably make sense of it with `od', if you don't want to use the
+supplied appliction.
+
+BUGS:
+
+pam_tally is very dependant on getpw*(): a database of usernames
+would be much more flexible.
+
+The (4.0 Redhat) utilities seem to do funny things with uid, and I'm
+not wholly sure I understood what I should have been doing anyway so
+the `keep a count of current logins' bit has been #ifdef'd out and you
+can only reset the counter on successful authentication, for now.
diff --git a/modules/pam_tally/faillog.h b/modules/pam_tally/faillog.h
new file mode 100644
index 00000000..7f704713
--- /dev/null
+++ b/modules/pam_tally/faillog.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1989 - 1994, Julianne Frances Haugh
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * faillog.h - login failure logging file format
+ *
+ * $Id$
+ *
+ * The login failure file is maintained by login(1) and faillog(8)
+ * Each record in the file represents a separate UID and the file
+ * is indexed in that fashion.
+ */
+
+#ifndef _FAILLOG_H
+#define _FAILLOG_H
+
+struct faillog {
+ short fail_cnt; /* failures since last success */
+ short fail_max; /* failures before turning account off */
+ char fail_line[12]; /* last failure occured here */
+ time_t fail_time; /* last failure occured then */
+ /*
+ * If nonzero, the account will be re-enabled if there are no
+ * failures for fail_locktime seconds since last failure.
+ */
+ long fail_locktime;
+};
+
+#endif
diff --git a/modules/pam_tally/pam_tally.c b/modules/pam_tally/pam_tally.c
new file mode 100644
index 00000000..ff75697d
--- /dev/null
+++ b/modules/pam_tally/pam_tally.c
@@ -0,0 +1,689 @@
+/*
+ * pam_tally.c
+ *
+ * $Id$
+ */
+
+
+/* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
+ * 5 March 1997
+ *
+ * Stuff stolen from pam_rootok and pam_listfile
+ */
+
+#ifdef linux
+# define _GNU_SOURCE
+# include <features.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include "faillog.h"
+
+#ifndef TRUE
+#define TRUE 1L
+#define FALSE 0L
+#endif
+
+/*
+ * here, we make a definition for the externally accessible function
+ * in this file (this definition is required for static a module
+ * but strongly encouraged generally) it is used to instruct the
+ * modules include file to define the function prototypes.
+ */
+
+#define PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+/* #define PAM_SM_SESSION */
+/* #define PAM_SM_PASSWORD */
+
+#include <security/pam_modules.h>
+
+/*---------------------------------------------------------------------*/
+
+#define DEFAULT_LOGFILE "/var/log/faillog"
+#define MODULE_NAME "pam_tally"
+
+enum TALLY_RESET {
+ TALLY_RESET_DEFAULT,
+ TALLY_RESET_RESET,
+ TALLY_RESET_NO_RESET
+};
+
+#define tally_t unsigned short int
+#define TALLY_FMT "%hu"
+#define TALLY_HI ((tally_t)~0L)
+
+#define UID_FMT "%hu"
+
+#ifndef FILENAME_MAX
+# define FILENAME_MAX MAXPATHLEN
+#endif
+
+static struct faillog faillog;
+static time_t fail_time;
+
+/*---------------------------------------------------------------------*/
+
+/* some syslogging */
+
+static void _pam_log(int err, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+
+#ifdef MAIN
+ vfprintf(stderr,format,args);
+#else
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
+ vsyslog(err, format, args);
+ closelog();
+#endif
+ va_end(args);
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: get uid (and optionally username) from PAM or
+ cline_user --- */
+
+#ifdef MAIN
+static char *cline_user=0; /* cline_user is used in the administration prog */
+#endif
+
+static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp )
+ {
+ const char *user;
+ struct passwd *pw;
+
+#ifdef MAIN
+ user = cline_user;
+#else
+ pam_get_user( pamh, &user, NULL );
+#endif
+
+ if ( !user || !*user ) {
+ _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?");
+ return PAM_AUTH_ERR;
+ }
+
+ if ( ! ( pw = getpwnam( user ) ) ) {
+ _pam_log(LOG_ERR,MODULE_NAME ": pam_get_uid; no such user %s",user);
+ return PAM_USER_UNKNOWN;
+ }
+
+ if ( uid ) *uid = pw->pw_uid;
+ if ( userp ) *userp = user;
+ return PAM_SUCCESS;
+ }
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: open/create tallyfile and return tally for uid --- */
+
+/* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */
+/* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */
+
+static int get_tally( tally_t *tally,
+ uid_t uid,
+ const char *filename,
+ FILE **TALLY )
+ {
+ struct stat fileinfo;
+ int lstat_ret = lstat(filename,&fileinfo);
+
+ if ( lstat_ret && *tally!=TALLY_HI ) {
+ int oldmask = umask(077);
+ *TALLY=fopen(filename, "a");
+ /* Create file, or append-open in pathological case. */
+ umask(oldmask);
+ if ( !*TALLY ) {
+ _pam_log(LOG_ALERT, "Couldn't create %s",filename);
+ return PAM_AUTH_ERR;
+ }
+ lstat_ret = fstat(fileno(*TALLY),&fileinfo);
+ fclose(*TALLY);
+ }
+
+ if ( lstat_ret ) {
+ _pam_log(LOG_ALERT, "Couldn't stat %s",filename);
+ return PAM_AUTH_ERR;
+ }
+
+ if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) {
+ /* If the file is world writable or is not a
+ normal file, return error */
+ _pam_log(LOG_ALERT,
+ "%s is either world writable or not a normal file",
+ filename);
+ return PAM_AUTH_ERR;
+ }
+
+ if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) {
+ _pam_log(LOG_ALERT, "Error opening %s for update", filename);
+
+/* Discovering why account service fails: e/uid are target user.
+ *
+ * perror(MODULE_NAME);
+ * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid());
+ */
+ return PAM_AUTH_ERR;
+ }
+
+ if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) {
+ _pam_log(LOG_ALERT, "fseek failed %s", filename);
+ return PAM_AUTH_ERR;
+ }
+
+
+ if ( ( fread((char *) &faillog, sizeof faillog, 1, *TALLY) )==0 ) {
+ *tally=0; /* Assuming a gappy filesystem */
+ }
+ *tally = faillog.fail_cnt;
+
+ return PAM_SUCCESS;
+ }
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
+
+static int set_tally( tally_t tally,
+ uid_t uid,
+ const char *filename,
+ FILE **TALLY )
+ {
+ if ( tally!=TALLY_HI )
+ {
+ if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) {
+ _pam_log(LOG_ALERT, "fseek failed %s", filename);
+ return PAM_AUTH_ERR;
+ }
+ faillog.fail_cnt = tally;
+ if ( fwrite((char *) &faillog, sizeof faillog, 1, *TALLY)==0 ) {
+ _pam_log(LOG_ALERT, "tally update (fputc) failed.", filename);
+ return PAM_AUTH_ERR;
+ }
+ }
+
+ if ( fclose(*TALLY) ) {
+ _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename);
+ return PAM_AUTH_ERR;
+ }
+ *TALLY=NULL;
+ return PAM_SUCCESS;
+ }
+
+/*---------------------------------------------------------------------*/
+
+/* --- PAM bits --- */
+
+#ifndef MAIN
+
+#define PAM_FUNCTION(name) \
+ PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv)
+
+#define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS))
+
+/*---------------------------------------------------------------------*/
+
+/* --- tally bump function: bump tally for uid by (signed) inc --- */
+
+static int tally_bump (int inc,
+ pam_handle_t *pamh,
+ int flags,
+ int argc,
+ const char **argv) {
+ uid_t uid;
+
+ int
+ fail_on_error = FALSE;
+ tally_t
+ tally = 0; /* !TALLY_HI --> Log opened for update */
+
+ char
+ no_magic_root = FALSE;
+
+ char
+ filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
+
+ /* Should probably decode the parameters before anything else. */
+
+ {
+ for ( ; argc-- > 0; ++argv ) {
+
+ /* generic options.. um, ignored. :] */
+
+ if ( ! strcmp( *argv, "no_magic_root" ) ) {
+ no_magic_root = TRUE;
+ }
+ else if ( ! strncmp( *argv, "file=", 5 ) ) {
+ char const
+ *from = (*argv)+5;
+ char
+ *to = filename;
+ if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) {
+ _pam_log(LOG_ERR,
+ MODULE_NAME ": filename not /rooted or too long; ",
+ *argv);
+ RETURN_ERROR( PAM_AUTH_ERR );
+ }
+ while ( ( *to++ = *from++ ) );
+ }
+ else if ( ! strcmp( *argv, "onerr=fail" ) ) {
+ fail_on_error=TRUE;
+ }
+ else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
+ fail_on_error=FALSE;
+ }
+ else {
+ _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
+ }
+ } /* for() */
+ }
+
+ {
+ FILE
+ *TALLY = NULL;
+ const char
+ *user = NULL,
+ *remote_host = NULL;
+
+ int i=pam_get_uid(pamh, &uid, &user);
+ if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
+
+ i=get_tally( &tally, uid, filename, &TALLY );
+ fail_time = faillog.fail_time; /* to remember old fail time (for locktime) */
+ faillog.fail_time = (time_t) time( (time_t *) 0);
+ (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host);
+ if (!remote_host)
+ {
+ strcpy(faillog.fail_line, "unknown");
+ }
+ else
+ {
+ strncpy(faillog.fail_line, remote_host, (size_t)sizeof(faillog.fail_line));
+ faillog.fail_line[sizeof(faillog.fail_line)-1] = 0;
+ }
+ if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
+
+ if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
+
+ tally+=inc;
+
+ if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */
+ tally-=inc;
+ _pam_log(LOG_ALERT,"Tally %sflowed for user %s",
+ (inc<0)?"under":"over",user);
+ }
+ }
+
+ i=set_tally( tally, uid, filename, &TALLY );
+ if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
+ }
+
+ return PAM_SUCCESS;
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- authentication management functions (only) --- */
+
+#ifdef PAM_SM_AUTH
+
+PAM_FUNCTION( pam_sm_authenticate ) {
+ return tally_bump( 1, pamh, flags, argc, argv);
+}
+
+/* --- Seems to need this function. Ho hum. --- */
+
+PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; }
+
+#endif
+
+/*---------------------------------------------------------------------*/
+
+/* --- session management functions (only) --- */
+
+/*
+ * Unavailable until .so files can be suid
+ */
+
+#ifdef PAM_SM_SESSION
+
+/* To maintain a balance-tally of successful login/outs */
+
+PAM_FUNCTION( pam_sm_open_session ) {
+ return tally_bump( 1, pamh, flags, argc, argv);
+}
+
+PAM_FUNCTION( pam_sm_close_session ) {
+ return tally_bump(-1, pamh, flags, argc, argv);
+}
+
+#endif
+
+/*---------------------------------------------------------------------*/
+
+/* --- authentication management functions (only) --- */
+
+#ifdef PAM_SM_AUTH
+
+/* To lock out a user with an unacceptably high tally */
+
+PAM_FUNCTION( pam_sm_acct_mgmt ) {
+ uid_t
+ uid;
+
+ int
+ fail_on_error = FALSE;
+ tally_t
+ deny = 0;
+ tally_t
+ tally = 0; /* !TALLY_HI --> Log opened for update */
+
+ char
+ no_magic_root = FALSE,
+ even_deny_root_account = FALSE;
+ char per_user = FALSE; /* if true then deny=.fail_max for user */
+ char no_lock_time = FALSE; /* if true then don't use .fail_locktime */
+
+ const char
+ *user = NULL;
+
+ enum TALLY_RESET
+ reset = TALLY_RESET_DEFAULT;
+
+ char
+ filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
+
+ /* Should probably decode the parameters before anything else. */
+
+ {
+ for ( ; argc-- > 0; ++argv ) {
+
+ /* generic options.. um, ignored. :] */
+
+ if ( ! strcmp( *argv, "no_magic_root" ) ) {
+ no_magic_root = TRUE;
+ }
+ else if ( ! strcmp( *argv, "even_deny_root_account" ) ) {
+ even_deny_root_account = TRUE;
+ }
+ else if ( ! strcmp( *argv, "reset" ) ) {
+ reset = TALLY_RESET_RESET;
+ }
+ else if ( ! strcmp( *argv, "no_reset" ) ) {
+ reset = TALLY_RESET_NO_RESET;
+ }
+ else if ( ! strncmp( *argv, "file=", 5 ) ) {
+ char const
+ *from = (*argv)+5;
+ char
+ *to = filename;
+ if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) {
+ _pam_log(LOG_ERR,
+ MODULE_NAME ": filename not /rooted or too long; ",
+ *argv);
+ RETURN_ERROR( PAM_AUTH_ERR );
+ }
+ while ( ( *to++ = *from++ ) );
+ }
+ else if ( ! strncmp( *argv, "deny=", 5 ) ) {
+ if ( sscanf((*argv)+5,TALLY_FMT,&deny) != 1 ) {
+ _pam_log(LOG_ERR,"bad number supplied; %s",*argv);
+ RETURN_ERROR( PAM_AUTH_ERR );
+ }
+ }
+ else if ( ! strcmp( *argv, "onerr=fail" ) ) {
+ fail_on_error=TRUE;
+ }
+ else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
+ fail_on_error=FALSE;
+ }
+ else if ( ! strcmp( *argv, "per_user" ) )
+ {
+ per_user = TRUE;
+ }
+ else if ( ! strcmp( *argv, "no_lock_time") )
+ {
+ no_lock_time = TRUE;
+ }
+ else {
+ _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
+ }
+ } /* for() */
+ }
+
+ {
+ FILE *TALLY=0;
+ int i=pam_get_uid(pamh, &uid, &user);
+ if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
+
+ i=get_tally( &tally, uid, filename, &TALLY );
+ if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
+
+ if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
+
+ /* To deny or not to deny; that is the question */
+
+ /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */
+
+ if ( (faillog.fail_max) && (per_user) ) {deny = faillog.fail_max;}
+ if (faillog.fail_locktime && fail_time && (!no_lock_time) )
+ {
+ if ( (faillog.fail_locktime + fail_time) > (time_t)time((time_t)0) )
+ {
+ _pam_log(LOG_NOTICE,"user %s ("UID_FMT") has time limit [%lds left] since last failure.",user,uid,fail_time+faillog.fail_locktime-(time_t)time((time_t)0));
+ return PAM_AUTH_ERR;
+ }
+ }
+ if (
+ ( deny != 0 ) && /* deny==0 means no deny */
+ ( tally > deny ) && /* tally>deny means exceeded */
+ ( even_deny_root_account || uid ) /* even_deny stops uid check */
+ ) {
+ _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT,
+ user, uid, tally, deny);
+ return PAM_AUTH_ERR; /* Only unconditional failure */
+ }
+
+ /* resets for explicit reset
+ * or by default if deny exists and not magic-root
+ */
+
+ if ( ( reset == TALLY_RESET_RESET ) ||
+ ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; }
+ }
+ else /* is magic root */ {
+
+ /* Magic root skips deny test... */
+
+ /* Magic root only resets on explicit reset, regardless of deny */
+
+ if ( reset == TALLY_RESET_RESET ) { tally=0; }
+ }
+ if (tally == 0)
+ {
+ faillog.fail_time = (time_t) 0;
+ strcpy(faillog.fail_line, "");
+ }
+ i=set_tally( tally, uid, filename, &TALLY );
+ if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
+ }
+
+ return PAM_SUCCESS;
+}
+
+#endif /* #ifdef PAM_SM_AUTH */
+
+/*-----------------------------------------------------------------------*/
+
+#ifdef PAM_STATIC
+
+/* static module data */
+
+struct pam_module _pam_tally_modstruct = {
+ MODULE_NAME,
+#ifdef PAM_SM_AUTH
+ pam_sm_authenticate,
+ pam_sm_setcred,
+#else
+ NULL,
+ NULL,
+#endif
+#ifdef PAM_SM_ACCOUNT
+ pam_sm_acct_mgmt,
+#else
+ NULL,
+#endif
+#ifdef PAM_SM_SESSION
+ pam_sm_open_session,
+ pam_sm_close_session,
+#else
+ NULL,
+ NULL,
+#endif
+#ifdef PAM_SM_PASSWORD
+ pam_sm_chauthtok,
+#else
+ NULL,
+#endif
+};
+
+#endif /* #ifdef PAM_STATIC */
+
+/*-----------------------------------------------------------------------*/
+
+#else /* #ifndef MAIN */
+
+static const char *cline_filename = DEFAULT_LOGFILE;
+static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */
+static int cline_quiet = 0;
+
+/*
+ * Not going to link with pamlib just for these.. :)
+ */
+
+static const char * pam_errors( int i ) {
+ switch (i) {
+ case PAM_AUTH_ERR: return "Authentication error";
+ case PAM_SERVICE_ERR: return "Service error";
+ case PAM_USER_UNKNOWN: return "Unknown user";
+ default: return "Unknown error";
+ }
+}
+
+static int getopts( int argc, char **argv ) {
+ const char *pname = *argv;
+ for ( ; *argv ; (void)(*argv && ++argv) ) {
+ if ( !strcmp (*argv,"--file") ) cline_filename=*++argv;
+ else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
+ else if ( !strcmp (*argv,"--user") ) cline_user=*++argv;
+ else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
+ else if ( !strcmp (*argv,"--reset") ) cline_reset=0;
+ else if ( !strncmp(*argv,"--reset=",8)) {
+ if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 )
+ fprintf(stderr,"%s: Bad number given to --reset=\n",pname), exit(0);
+ }
+ else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1;
+ else {
+ fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+int main ( int argc, char **argv ) {
+
+ if ( ! getopts( argc, argv+1 ) ) {
+ printf("%s: [--file rooted-filename] [--user username] "
+ "[--reset[=n]] [--quiet]\n",
+ *argv);
+ exit(0);
+ }
+
+ umask(077);
+
+ /*
+ * Major difference between individual user and all users:
+ * --user just handles one user, just like PAM.
+ * --user=* handles all users, sniffing cline_filename for nonzeros
+ */
+
+ if ( cline_user ) {
+ uid_t uid;
+ tally_t tally=cline_reset;
+ FILE *TALLY=0;
+ int i=pam_get_uid( NULL, &uid, NULL);
+ if ( i != PAM_SUCCESS ) {
+ fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
+ exit(0);
+ }
+
+ i=get_tally( &tally, uid, cline_filename, &TALLY );
+ if ( i != PAM_SUCCESS ) {
+ if (TALLY) fclose(TALLY);
+ fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
+ exit(0);
+ }
+
+ if ( !cline_quiet )
+ printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid,
+ (cline_reset!=TALLY_HI)?"had":"has",tally);
+
+ i=set_tally( cline_reset, uid, cline_filename, &TALLY );
+ if ( i != PAM_SUCCESS ) {
+ if (TALLY) fclose(TALLY);
+ fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
+ exit(0);
+ }
+ }
+ else /* !cline_user (ie, operate on all users) */ {
+ FILE *TALLY=fopen(cline_filename, "r");
+ uid_t uid=0;
+ if ( !TALLY ) perror(*argv), exit(0);
+
+ for ( ; !feof(TALLY); uid++ ) {
+ tally_t tally;
+ struct passwd *pw;
+ if ( ! fread((char *) &faillog, sizeof faillog, 1, TALLY) || ! faillog.fail_cnt ) {
+ tally=faillog.fail_cnt;
+ continue;
+ }
+ tally = faillog.fail_cnt;
+
+ if ( ( pw=getpwuid(uid) ) ) {
+ printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",pw->pw_name,uid,
+ (cline_reset!=TALLY_HI)?"had":"has",tally);
+ }
+ else {
+ printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid,
+ (cline_reset!=TALLY_HI)?"had":"has",tally);
+ }
+ }
+ fclose(TALLY);
+ if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
+ fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv);
+ }
+ else if ( !cline_reset ) {
+ TALLY=fopen(cline_filename, "w");
+ if ( !TALLY ) perror(*argv), exit(0);
+ fclose(TALLY);
+ }
+ }
+ return 0;
+}
+
+
+#endif
diff --git a/modules/pam_tally/pam_tally_app.c b/modules/pam_tally/pam_tally_app.c
new file mode 100644
index 00000000..9e6e1faf
--- /dev/null
+++ b/modules/pam_tally/pam_tally_app.c
@@ -0,0 +1,7 @@
+/*
+ # This seemed like such a good idea at the time. :)
+ */
+
+#define MAIN
+#include "pam_tally.c"
+