From 114ed318bea9b5859ab89144261946716776e2ed Mon Sep 17 00:00:00 2001
From: Tomas Mraz <tm@t8m.info>
Date: Fri, 17 Oct 2008 11:29:55 +0000
Subject: Relevant BUGIDs:

Purpose of commit: new feature

Commit summary:
---------------
2008-10-17  Tomas Mraz <t8m@centrum.cz>

        * configure.in: Add modules/pam_tally2/Makefile.
        * doc/sag/Linux-PAM_SAG.xml: Include pam_tally2.xml.
        * doc/sag/pam_tally2.xml: New.
        * libpam/pam_static_modules.h: Add pam_tally2 static struct.
        * modules/Makefile.am: Add pam_tally2 directory.
        * modules/pam_tally2/Makefile.am: New.
        * modules/pam_tally2/README.xml: New.
        * modules/pam_tally2/tallylog.h: New.
        * modules/pam_tally2/pam_tally2.8.xml: New.
        * modules/pam_tally2/pam_tally2.c: New.
        * modules/pam_tally2/pam_tally2_app.c: New.
        * modules/pam_tally2/tst-pam_tally2: New.
        * po/POTFILES.in: Add pam_tally2 sources.
---
 modules/Makefile.am                 |   2 +-
 modules/pam_tally2/.cvsignore       |   9 +
 modules/pam_tally2/Makefile.am      |  40 ++
 modules/pam_tally2/README.xml       |  46 ++
 modules/pam_tally2/pam_tally2.8.xml | 439 ++++++++++++++++
 modules/pam_tally2/pam_tally2.c     | 985 ++++++++++++++++++++++++++++++++++++
 modules/pam_tally2/pam_tally2_app.c |   7 +
 modules/pam_tally2/tallylog.h       |  52 ++
 modules/pam_tally2/tst-pam_tally2   |   2 +
 9 files changed, 1581 insertions(+), 1 deletion(-)
 create mode 100644 modules/pam_tally2/.cvsignore
 create mode 100644 modules/pam_tally2/Makefile.am
 create mode 100644 modules/pam_tally2/README.xml
 create mode 100644 modules/pam_tally2/pam_tally2.8.xml
 create mode 100644 modules/pam_tally2/pam_tally2.c
 create mode 100644 modules/pam_tally2/pam_tally2_app.c
 create mode 100644 modules/pam_tally2/tallylog.h
 create mode 100755 modules/pam_tally2/tst-pam_tally2

(limited to 'modules')

diff --git a/modules/Makefile.am b/modules/Makefile.am
index f21d52e8..37d5a739 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -9,7 +9,7 @@ SUBDIRS = pam_access pam_cracklib pam_debug pam_deny pam_echo \
 	pam_mkhomedir pam_motd pam_namespace pam_nologin \
 	pam_permit pam_pwhistory pam_rhosts pam_rootok pam_securetty \
 	pam_selinux pam_sepermit pam_shells pam_stress \
-	pam_succeed_if pam_tally pam_time pam_tty_audit pam_umask \
+	pam_succeed_if pam_tally pam_tally2 pam_time pam_tty_audit pam_umask \
 	pam_unix pam_userdb pam_warn pam_wheel pam_xauth
 
 CLEANFILES = *~
diff --git a/modules/pam_tally2/.cvsignore b/modules/pam_tally2/.cvsignore
new file mode 100644
index 00000000..c20ebb92
--- /dev/null
+++ b/modules/pam_tally2/.cvsignore
@@ -0,0 +1,9 @@
+*.la
+*.lo
+.deps
+.libs
+Makefile
+Makefile.in
+pam_tally2
+README
+pam_tally2.8
diff --git a/modules/pam_tally2/Makefile.am b/modules/pam_tally2/Makefile.am
new file mode 100644
index 00000000..6f843e1f
--- /dev/null
+++ b/modules/pam_tally2/Makefile.am
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2005, 2006, 2007 Thorsten Kukuk <kukuk@thkukuk.de>
+# Copyright (c) 2008 Red Hat, Inc.
+#
+
+CLEANFILES = *~
+
+EXTRA_DIST = README $(MANS) $(XMLS) tst-pam_tally2
+
+man_MANS = pam_tally2.8
+XMLS = README.xml pam_tally2.8.xml
+
+TESTS = tst-pam_tally2
+
+securelibdir = $(SECUREDIR)
+secureconfdir = $(SCONFIGDIR)
+
+noinst_HEADERS = tallylog.h
+
+AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include
+
+pam_tally2_la_LDFLAGS = -no-undefined -avoid-version -module
+pam_tally2_la_LIBADD = -L$(top_builddir)/libpam -lpam $(LIBAUDIT)
+if HAVE_VERSIONING
+  pam_tally2_la_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
+endif
+
+pam_tally2_LDADD = $(LIBAUDIT)
+
+securelib_LTLIBRARIES = pam_tally2.la
+sbin_PROGRAMS = pam_tally2
+
+pam_tally2_la_SOURCES = pam_tally2.c
+pam_tally2_SOURCES = pam_tally2_app.c
+
+if ENABLE_REGENERATE_MAN
+noinst_DATA = README
+README: pam_tally2.8.xml
+-include $(top_srcdir)/Make.xml.rules
+endif
diff --git a/modules/pam_tally2/README.xml b/modules/pam_tally2/README.xml
new file mode 100644
index 00000000..aa470570
--- /dev/null
+++ b/modules/pam_tally2/README.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+"http://www.docbook.org/xml/4.3/docbookx.dtd"
+[
+<!--
+<!ENTITY pamaccess SYSTEM "pam_tally2.8.xml">
+-->
+]>
+
+<article>
+
+  <articleinfo>
+
+    <title>
+      <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refnamediv[@id = "pam_tally2-name"]/*)'/>
+    </title>
+
+  </articleinfo>
+
+  <section>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refsect1[@id = "pam_tally2-description"]/*)'/>
+  </section>
+
+  <section>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refsect1[@id = "pam_tally2-options"]/*)'/>
+  </section>
+
+  <section>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refsect1[@id = "pam_tally2-notes"]/*)'/>
+  </section>
+
+  <section>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refsect1[@id = "pam_tally2-examples"]/*)'/>
+  </section>
+
+  <section>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+      href="pam_tally2.8.xml" xpointer='xpointer(//refsect1[@id = "pam_tally2-author"]/*)'/>
+  </section>
+
+</article>
diff --git a/modules/pam_tally2/pam_tally2.8.xml b/modules/pam_tally2/pam_tally2.8.xml
new file mode 100644
index 00000000..dc284a1d
--- /dev/null
+++ b/modules/pam_tally2/pam_tally2.8.xml
@@ -0,0 +1,439 @@
+<?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="pam_tally2">
+
+  <refmeta>
+    <refentrytitle>pam_tally2</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo>
+  </refmeta>
+
+  <refnamediv id="pam_tally2-name">
+    <refname>pam_tally2</refname>
+    <refpurpose>The login counter (tallying) module</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis id="pam_tally2-cmdsynopsis1">
+      <command>pam_tally2.so</command>
+      <arg choice="opt">
+	file=<replaceable>/path/to/counter</replaceable>
+      </arg>
+      <arg choice="opt">
+        onerr=[<replaceable>fail</replaceable>|<replaceable>succeed</replaceable>]
+      </arg>
+      <arg choice="opt">
+        magic_root
+      </arg>
+      <arg choice="opt">
+        even_deny_root
+      </arg>
+      <arg choice="opt">
+        deny=<replaceable>n</replaceable>
+      </arg>
+      <arg choice="opt">
+        lock_time=<replaceable>n</replaceable>
+      </arg>
+      <arg choice="opt">
+        unlock_time=<replaceable>n</replaceable>
+      </arg>
+      <arg choice="opt">
+        root_unlock_time=<replaceable>n</replaceable>
+      </arg>
+      <arg choice="opt">
+        audit
+      </arg>
+      <arg choice="opt">
+        silent
+      </arg>
+      <arg choice="opt">
+        no_log_info
+      </arg>
+    </cmdsynopsis>
+    <cmdsynopsis id="pam_tally2-cmdsynopsis2">
+      <command>pam_tally2</command>
+      <arg choice="opt">
+	--file <replaceable>/path/to/counter</replaceable>
+      </arg>
+      <arg choice="opt">
+	--user <replaceable>username</replaceable>
+      </arg>
+      <arg choice="opt">
+	--reset[=<replaceable>n</replaceable>]
+      </arg>
+      <arg choice="opt">
+        --quiet
+      </arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1 id="pam_tally2-description">
+
+    <title>DESCRIPTION</title>
+
+    <para>
+      This module maintains a count of attempted accesses, can
+      reset count on success, can deny access if too many attempts fail.
+    </para>
+    <para>
+      pam_tally2 comes in two parts:
+      <emphasis remap='B'>pam_tally2.so</emphasis> and
+      <command>pam_tally2</command>. The former is the PAM module and
+      the latter, a stand-alone program. <command>pam_tally2</command>
+      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. For example, one might find it useful to clear all counts
+      every midnight from a cron job.
+    </para>
+    <para>
+      Normally, failed attempts to access <emphasis>root</emphasis> will
+      <emphasis remap='B'>not</emphasis> 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 <command>su</command> or
+      at the machine console (not telnet/rsh, etc), this is safe.
+    </para>
+  </refsect1>
+
+  <refsect1 id="pam_tally2-options">
+
+    <title>OPTIONS</title>
+    <variablelist>
+      <varlistentry>
+        <term>
+          GLOBAL OPTIONS
+        </term>
+        <listitem>
+          <para>
+            This can be used for <emphasis>auth</emphasis> and
+            <emphasis>account</emphasis> module types.
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <option>onerr=[<replaceable>fail</replaceable>|<replaceable>succeed</replaceable>]</option>
+              </term>
+              <listitem>
+                <para>
+                  If something weird happens (like unable to open the file),
+                  return with <errorcode>PAM_SUCESS</errorcode> if
+                  <option>onerr=<replaceable>succeed</replaceable></option>
+                  is given, else with the corresponding PAM error code.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>file=<replaceable>/path/to/counter</replaceable></option>
+              </term>
+              <listitem>
+                <para>
+                  File where to keep counts. Default is
+                  <filename>/var/log/tallylog</filename>.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>audit</option>
+              </term>
+              <listitem>
+                <para>
+                  Will log the user name into the system log if the user is not found.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>silent</option>
+              </term>
+              <listitem>
+                <para>
+                  Don't print informative messages.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>no_log_info</option>
+              </term>
+              <listitem>
+                <para>
+                  Don't log informative messages via <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          AUTH OPTIONS
+        </term>
+        <listitem>
+          <para>
+            Authentication phase first increments attempted login counter and
+            checks if user should be denied access. If the user is authenticated
+            and the login process continues on call to <citerefentry>
+	      <refentrytitle>pam_setcred</refentrytitle><manvolnum>3</manvolnum>
+            </citerefentry> it resets the attempts counter.
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <option>deny=<replaceable>n</replaceable></option>
+              </term>
+              <listitem>
+                <para>
+                  Deny access if tally for this user exceeds
+                  <replaceable>n</replaceable>.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>lock_time=<replaceable>n</replaceable></option>
+              </term>
+              <listitem>
+                <para>
+                  Always deny for <replaceable>n</replaceable> seconds
+                  after failed attempt.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>unlock_time=<replaceable>n</replaceable></option>
+              </term>
+              <listitem>
+                <para>
+                  Allow access after <replaceable>n</replaceable> seconds
+                  after failed attempt. If this option is used the user will
+                  be locked out for the specified amount of time after he
+                  exceeded his maximum allowed attempts. Otherwise the
+                  account is locked until the lock is removed by a manual
+                  intervention of the system administrator.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>magic_root</option>
+              </term>
+              <listitem>
+                <para>
+                  If the module is invoked by a user with uid=0 the
+                  counter is not incremented. The sys-admin should use this
+                  for user launched services, like <command>su</command>,
+                  otherwise this argument should be omitted.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>no_lock_time</option>
+              </term>
+              <listitem>
+                <para>
+                  Do not use the .fail_locktime field in
+                  <filename>/var/log/faillog</filename> for this user.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>no_reset</option>
+              </term>
+              <listitem>
+                <para>
+                  Don't reset count on successful entry, only decrement.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>even_deny_root</option>
+              </term>
+              <listitem>
+                <para>
+                  Root account can become unavailable.
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <option>root_unlock_time=<replaceable>n</replaceable></option>
+              </term>
+              <listitem>
+                <para>
+                  This option implies <option>even_deny_root</option> option.
+                  Allow access after <replaceable>n</replaceable> seconds
+                  to root acccount after failed attempt. If this option is used
+                  the root user will be locked out for the specified amount of
+                  time after he exceeded his maximum allowed attempts.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
+        <term>
+          ACCOUNT OPTIONS
+        </term>
+        <listitem>
+          <para>
+            Account phase resets attempts counter if the user is
+            <emphasis remap='B'>not</emphasis> magic root.
+            This phase can be used optionaly for services which don't call
+            <citerefentry>
+	      <refentrytitle>pam_setcred</refentrytitle><manvolnum>3</manvolnum>
+            </citerefentry> correctly or if the reset should be done regardless
+            of the failure of the account phase of other modules.
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <option>magic_root</option>
+              </term>
+              <listitem>
+                <para>
+                  If the module is invoked by a user with uid=0 the
+                  counter is not changed. The sys-admin should use this
+                  for user launched services, like <command>su</command>,
+                  otherwise this argument should be omitted.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id="pam_tally2-types">
+    <title>MODULE TYPES PROVIDED</title>
+    <para>
+      The <option>auth</option> and <option>account</option>
+      module types are provided.
+    </para>
+  </refsect1>
+
+  <refsect1 id='pam_tally2-return_values'>
+    <title>RETURN VALUES</title>
+    <variablelist>
+      <varlistentry>
+        <term>PAM_AUTH_ERR</term>
+        <listitem>
+          <para>
+            A invalid option was given, the module was not able
+            to retrive the user name, no valid counter file
+            was found, or too many failed logins.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>PAM_SUCCESS</term>
+        <listitem>
+          <para>
+            Everything was successfull.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>PAM_USER_UNKNOWN</term>
+        <listitem>
+          <para>
+	    User not known.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id='pam_tally2-notes'>
+    <title>NOTES</title>
+    <para>
+      pam_tally2 is not compatible with the old pam_tally faillog file format.
+      This is caused by requirement of compatibility of the tallylog file
+      format between 32bit and 64bit architectures on multiarch systems.
+    </para>
+    <para>
+      There is no setuid wrapper for access to the data file such as when the
+      <emphasis remap='B'>pam_tally2.so</emphasis> module is called from
+      xscreensaver. As this would make it impossible to share PAM configuration
+      with such services the following workaround is used: If the data file
+      cannot be opened because of insufficient permissions
+      (<errorcode>EPERM</errorcode>) the module returns
+      <errorcode>PAM_IGNORE</errorcode>.
+    </para>
+  </refsect1>
+
+  <refsect1 id='pam_tally2-examples'>
+    <title>EXAMPLES</title>
+    <para>
+      Add the following line to <filename>/etc/pam.d/login</filename> to
+      lock the account after 4 failed logins. Root account will be locked
+      as well. The accounts will be automatically unlocked after 20 minutes.
+      The module does not have to be called in the account phase because the
+      <command>login</command> calls <citerefentry>
+	<refentrytitle>pam_setcred</refentrytitle><manvolnum>3</manvolnum>
+      </citerefentry> correctly.
+    </para>
+    <programlisting>
+auth     required       pam_securetty.so
+auth     required       pam_tally2.so deny=4 even_deny_root unlock_time=1200
+auth     required       pam_env.so
+auth     required       pam_unix.so
+auth     required       pam_nologin.so
+account  required       pam_unix.so
+password required       pam_unix.so
+session  required       pam_limits.so
+session  required       pam_unix.so
+session  required       pam_lastlog.so nowtmp
+session  optional       pam_mail.so standard
+    </programlisting>
+  </refsect1>
+
+  <refsect1 id="pam_tally2-files">
+    <title>FILES</title>
+    <variablelist>
+      <varlistentry>
+        <term><filename>/var/log/tallylog</filename></term>
+        <listitem>
+          <para>failure count logging file</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id='pam_tally2-see_also'>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+	<refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+	<refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+	<refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>
+    </para>
+  </refsect1>
+
+  <refsect1 id='pam_tally2-author'>
+    <title>AUTHOR</title>
+      <para>
+        pam_tally was written by Tim Baverstock and Tomas Mraz.
+      </para>
+  </refsect1>
+
+</refentry>
+
diff --git a/modules/pam_tally2/pam_tally2.c b/modules/pam_tally2/pam_tally2.c
new file mode 100644
index 00000000..9ae3180d
--- /dev/null
+++ b/modules/pam_tally2/pam_tally2.c
@@ -0,0 +1,985 @@
+/*
+ * pam_tally2.c
+ *
+ */
+
+
+/* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
+ * 5 March 1997
+ *
+ * Stuff stolen from pam_rootok and pam_listfile
+ *
+ * Changes by Tomas Mraz <tmraz@redhat.com> 5 January 2005, 26 January 2006
+ * Audit option added for Tomas patch by Sebastien Tricaud <toady@gscore.org> 13 January 2005
+ * Portions Copyright 2006, Red Hat, Inc.
+ * Portions Copyright 1989 - 1993, 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.
+ */
+
+#include "config.h"
+
+#if defined(MAIN) && defined(MEMORY_DEBUG)
+# undef exit
+#endif /* defined(MAIN) && defined(MEMORY_DEBUG) */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <time.h>
+#include <stdint.h>
+#include <errno.h>
+#ifdef HAVE_LIBAUDIT
+#include <libaudit.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include "tallylog.h"
+
+#ifndef TRUE
+#define TRUE  1L
+#define FALSE 0L
+#endif
+
+#ifndef HAVE_FSEEKO
+#define fseeko fseek
+#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.
+ */
+
+#ifndef MAIN
+#define PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+/* #define PAM_SM_SESSION  */
+/* #define PAM_SM_PASSWORD */
+
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
+#endif
+#include <security/pam_modules.h>
+
+/*---------------------------------------------------------------------*/
+
+#define DEFAULT_LOGFILE "/var/log/tallylog"
+#define MODULE_NAME     "pam_tally2"
+
+#define tally_t    uint16_t
+#define TALLY_HI   ((tally_t)~0L)
+
+struct tally_options {
+    const char *filename;
+    tally_t deny;
+    long lock_time;
+    long unlock_time;
+    long root_unlock_time;
+    unsigned int ctrl;
+};
+
+#define PHASE_UNKNOWN 0
+#define PHASE_AUTH    1
+#define PHASE_ACCOUNT 2
+#define PHASE_SESSION 3
+
+#define OPT_MAGIC_ROOT			  01
+#define OPT_FAIL_ON_ERROR		  02
+#define OPT_DENY_ROOT			  04
+#define OPT_QUIET                        040
+#define OPT_AUDIT                       0100
+#define OPT_NOLOGNOTICE                 0400
+
+
+/*---------------------------------------------------------------------*/
+
+/* some syslogging */
+
+#ifdef MAIN
+#define pam_syslog tally_log
+static void
+tally_log (const pam_handle_t *pamh UNUSED, int priority UNUSED,
+	    const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	fprintf(stderr, "%s: ", MODULE_NAME);
+	vfprintf(stderr, fmt, args);
+	fprintf(stderr,"\n");
+	va_end(args);
+}
+
+#define pam_modutil_getpwnam(pamh, user) getpwnam(user)
+#endif
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: parse arguments --- */
+
+#ifndef MAIN
+
+static void
+log_phase_no_auth(pam_handle_t *pamh, int phase, const char *argv)
+{
+    if ( phase != PHASE_AUTH ) {
+    	pam_syslog(pamh, LOG_ERR,
+		   "option %s allowed in auth phase only", argv);
+    }
+}
+
+static int
+tally_parse_args(pam_handle_t *pamh, struct tally_options *opts,
+		    int phase, int argc, const char **argv)
+{
+    memset(opts, 0, sizeof(*opts));
+    opts->filename = DEFAULT_LOGFILE;
+    opts->ctrl = OPT_FAIL_ON_ERROR;
+    opts->root_unlock_time = -1;
+
+    for ( ; argc-- > 0; ++argv ) {
+
+      if ( ! strncmp( *argv, "file=", 5 ) ) {
+	const char *from = *argv + 5;
+        if ( *from!='/' ) {
+          pam_syslog(pamh, LOG_ERR,
+		     "filename not /rooted; %s", *argv);
+          return PAM_AUTH_ERR;
+        }
+        opts->filename = from;
+      }
+      else if ( ! strcmp( *argv, "onerr=fail" ) ) {
+        opts->ctrl |= OPT_FAIL_ON_ERROR;
+      }
+      else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
+        opts->ctrl &= ~OPT_FAIL_ON_ERROR;
+      }
+      else if ( ! strcmp( *argv, "magic_root" ) ) {
+        opts->ctrl |= OPT_MAGIC_ROOT;
+      }
+      else if ( ! strcmp( *argv, "even_deny_root_account" ) ||
+                ! strcmp( *argv, "even_deny_root" ) ) {
+	log_phase_no_auth(pamh, phase, *argv);
+        opts->ctrl |= OPT_DENY_ROOT;
+      }
+      else if ( ! strncmp( *argv, "deny=", 5 ) ) {
+	log_phase_no_auth(pamh, phase, *argv);
+        if ( sscanf((*argv)+5,"%hu",&opts->deny) != 1 ) {
+          pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
+          return PAM_AUTH_ERR;
+        }
+      }
+      else if ( ! strncmp( *argv, "lock_time=", 10 ) ) {
+	log_phase_no_auth(pamh, phase, *argv);
+        if ( sscanf((*argv)+10,"%ld",&opts->lock_time) != 1 ) {
+          pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
+          return PAM_AUTH_ERR;
+        }
+      }
+      else if ( ! strncmp( *argv, "unlock_time=", 12 ) ) {
+	log_phase_no_auth(pamh, phase, *argv);
+        if ( sscanf((*argv)+12,"%ld",&opts->unlock_time) != 1 ) {
+          pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
+          return PAM_AUTH_ERR;
+        }
+      }
+      else if ( ! strncmp( *argv, "root_unlock_time=", 17 ) ) {
+	log_phase_no_auth(pamh, phase, *argv);	
+        if ( sscanf((*argv)+17,"%ld",&opts->root_unlock_time) != 1 ) {
+          pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
+          return PAM_AUTH_ERR;
+        }
+        opts->ctrl |= OPT_DENY_ROOT; /* even_deny_root implied */
+      }
+      else if ( ! strcmp( *argv, "quiet" ) ||
+		! strcmp ( *argv, "silent")) {
+        opts->ctrl |= OPT_QUIET;
+      }
+      else if ( ! strcmp ( *argv, "no_log_info") ) {
+	opts->ctrl |= OPT_NOLOGNOTICE;
+      }
+      else if ( ! strcmp ( *argv, "audit") ) {
+	opts->ctrl |= OPT_AUDIT;
+      }
+      else {
+        pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
+      }
+    }
+
+    if (opts->root_unlock_time == -1)
+	opts->root_unlock_time = opts->unlock_time;
+
+    return PAM_SUCCESS;
+}
+
+#endif   /* #ifndef MAIN */
+
+/*---------------------------------------------------------------------*/
+
+/* --- 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, struct tally_options *opts)
+{
+    const char *user = NULL;
+    struct passwd *pw;
+
+#ifdef MAIN
+    user = cline_user;
+#else
+    if ((pam_get_user( pamh, &user, NULL )) != PAM_SUCCESS) {
+      user = NULL;
+    }
+#endif
+
+    if ( !user || !*user ) {
+      pam_syslog(pamh, LOG_ERR, "pam_get_uid; user?");
+      return PAM_AUTH_ERR;
+    }
+
+    if ( ! ( pw = pam_modutil_getpwnam( pamh, user ) ) ) {
+      opts->ctrl & OPT_AUDIT ?
+	      pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user %s", user) :
+	      pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user");
+      return PAM_USER_UNKNOWN;
+    }
+
+    if ( uid )   *uid   = pw->pw_uid;
+    if ( userp ) *userp = user;
+    return PAM_SUCCESS;
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support functions: set/get tally data --- */
+
+#ifndef MAIN
+
+static void
+_cleanup(pam_handle_t *pamh UNUSED, void *data, int error_status UNUSED)
+{
+    free(data);
+}
+
+
+static void
+tally_set_data( pam_handle_t *pamh, time_t oldtime )
+{
+    time_t *data;
+
+    if ( (data=malloc(sizeof(time_t))) != NULL ) {
+        *data = oldtime;
+        pam_set_data(pamh, MODULE_NAME, (void *)data, _cleanup);
+    }
+}
+
+static int
+tally_get_data( pam_handle_t *pamh, time_t *oldtime )
+{
+    int rv;
+    const void *data;
+
+    rv = pam_get_data(pamh, MODULE_NAME, &data);
+    if ( rv == PAM_SUCCESS && data != NULL && oldtime != NULL ) {
+      *oldtime = *(const time_t *)data;
+      pam_set_data(pamh, MODULE_NAME, NULL, NULL);
+    }
+    else {
+      rv = -1;
+      *oldtime = 0;
+    }
+    return rv;
+}
+#endif   /* #ifndef MAIN */
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: open/create tallyfile and return tally for uid --- */
+
+/* If on entry tallyfile doesn't exist, creation is attempted. */
+
+static int
+get_tally(pam_handle_t *pamh, uid_t uid, const char *filename,
+        FILE **tfile, struct tallylog *tally)
+{
+    struct stat fileinfo;
+    int lstat_ret;
+
+    lstat_ret = lstat(filename, &fileinfo);
+    if (lstat_ret) {
+      int save_errno;
+      int oldmask = umask(077);
+      *tfile=fopen(filename, "a");
+      save_errno = errno;
+      /* Create file, or append-open in pathological case. */
+      umask(oldmask);
+      if ( !*tfile ) {
+#ifndef MAIN
+        if (save_errno == EPERM) {
+	    return PAM_IGNORE; /* called with insufficient access rights */
+	}
+#endif
+	errno = save_errno;
+        pam_syslog(pamh, LOG_ALERT, "Couldn't create %s: %m", filename);
+        return PAM_AUTH_ERR;
+      }
+      lstat_ret = fstat(fileno(*tfile),&fileinfo);
+      fclose(*tfile);
+      *tfile = NULL;
+    }
+
+    if ( lstat_ret ) {
+      pam_syslog(pamh, 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_syslog(pamh, LOG_ALERT,
+               "%s is either world writable or not a normal file",
+               filename);
+      return PAM_AUTH_ERR;
+    }
+
+    if (!(*tfile = fopen(filename, "r+"))) {
+#ifndef MAIN
+      if (errno == EPERM) /* called with insufficient access rights */
+      	  return PAM_IGNORE;
+#endif 
+      pam_syslog(pamh, LOG_ALERT, "Error opening %s for update: %m", filename);
+
+      return PAM_AUTH_ERR;
+    }
+
+    if (fseeko(*tfile, (off_t)uid*(off_t)sizeof(*tally), SEEK_SET)) {
+        pam_syslog(pamh, LOG_ALERT, "fseek failed for %s: %m", filename);
+        fclose(*tfile);
+        *tfile = NULL;
+        return PAM_AUTH_ERR;
+    }
+
+    if (fileinfo.st_size < (off_t)(uid+1)*(off_t)sizeof(*tally)) {
+	memset(tally, 0, sizeof(*tally));
+    } else if (fread(tally, sizeof(*tally), 1, *tfile) == 0) {
+	memset(tally, 0, sizeof(*tally));
+	/* Shouldn't happen */
+    }
+
+    tally->fail_line[sizeof(tally->fail_line)-1] = '\0';
+    
+    return PAM_SUCCESS;
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
+
+static int
+set_tally(pam_handle_t *pamh, uid_t uid,
+	  const char *filename, FILE **tfile, struct tallylog *tally)
+{
+    if (tally->fail_cnt != TALLY_HI) {
+        if (fseeko(*tfile, (off_t)uid * sizeof(*tally), SEEK_SET)) {
+                  pam_syslog(pamh, LOG_ALERT, "fseek failed for %s: %m", filename);
+                            return PAM_AUTH_ERR;
+        }
+        if (fwrite(tally, sizeof(*tally), 1, *tfile) == 0) {
+	    pam_syslog(pamh, LOG_ALERT, "update (fwrite) failed for %s: %m", filename);
+	    return PAM_AUTH_ERR;
+        }
+    }
+
+    if (fclose(*tfile)) {
+      *tfile = NULL;
+      pam_syslog(pamh, LOG_ALERT, "update (fclose) failed for %s: %m", filename);
+      return PAM_AUTH_ERR;
+    }
+    *tfile=NULL;
+    return PAM_SUCCESS;
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- PAM bits --- */
+
+#ifndef MAIN
+
+#define RETURN_ERROR(i) return ((opts->ctrl & OPT_FAIL_ON_ERROR)?(i):(PAM_SUCCESS))
+
+/*---------------------------------------------------------------------*/
+
+static int
+tally_check (tally_t oldcnt, time_t oldtime, pam_handle_t *pamh, uid_t uid,
+             const char *user, struct tally_options *opts,
+	     struct tallylog *tally)
+{
+    int rv = PAM_SUCCESS;
+#ifdef HAVE_LIBAUDIT
+    char buf[64];
+    int audit_fd = -1;
+#endif
+    
+    if ((opts->ctrl & OPT_MAGIC_ROOT) && getuid() == 0) {
+      return PAM_SUCCESS;
+    }
+    /* magic_root skips tally check */
+#ifdef HAVE_LIBAUDIT
+    audit_fd = audit_open();
+    /* If there is an error & audit support is in the kernel report error */
+    if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
+                            errno == EAFNOSUPPORT))
+         return PAM_SYSTEM_ERR;
+#endif
+    if (opts->deny != 0 &&                        /* deny==0 means no deny        */
+        tally->fail_cnt > opts->deny &&           /* tally>deny means exceeded    */
+        ((opts->ctrl & OPT_DENY_ROOT) || uid)) {  /* even_deny stops uid check    */
+#ifdef HAVE_LIBAUDIT
+        if (tally->fail_cnt == opts->deny+1) {
+            /* First say that max number was hit. */
+            snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
+            audit_log_user_message(audit_fd, AUDIT_ANOM_LOGIN_FAILURES, buf,
+                                   NULL, NULL, NULL, 1);
+        }
+#endif
+        if (uid) {      
+            /* Unlock time check */
+            if (opts->unlock_time && oldtime) {
+                if (opts->unlock_time + oldtime <= time(NULL)) 	{
+                    /* ignore deny check after unlock_time elapsed */
+#ifdef HAVE_LIBAUDIT
+                    snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
+                    audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
+                                   NULL, NULL, NULL, 1);
+#endif
+      	            rv = PAM_SUCCESS;
+      		    goto cleanup;
+      	        }
+            }
+        } else {
+            /* Root unlock time check */
+            if (opts->root_unlock_time && oldtime) {
+                if (opts->root_unlock_time + oldtime <= time(NULL)) {
+      	            /* ignore deny check after unlock_time elapsed */
+#ifdef HAVE_LIBAUDIT
+                    snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
+                    audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
+                                   NULL, NULL, NULL, 1);
+#endif
+      	            rv = PAM_SUCCESS;
+      	            goto cleanup;
+      	        }
+            }
+        }
+
+#ifdef HAVE_LIBAUDIT
+        if (tally->fail_cnt == opts->deny+1) {
+            /* First say that max number was hit. */
+            audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_LOCK, buf,
+                                   NULL, NULL, NULL, 1);
+        }
+#endif
+
+        if (!(opts->ctrl & OPT_QUIET)) {
+            pam_info(pamh, _("Account locked due to %hu failed logins"),
+		    tally->fail_cnt);
+        }
+	if (!(opts->ctrl & OPT_NOLOGNOTICE)) {
+            pam_syslog(pamh, LOG_NOTICE,
+                   "user %s (%lu) tally %hu, deny %hu",
+		   user, (unsigned long)uid, tally->fail_cnt, opts->deny);
+	}
+        rv = PAM_AUTH_ERR;                 /* Only unconditional failure   */
+        goto cleanup;
+    }
+
+    /* Lock time check */
+    if (opts->lock_time && oldtime) {
+        if (opts->lock_time + oldtime > time(NULL)) {
+	    /* don't increase fail_cnt or update fail_time when
+	       lock_time applies */
+	    tally->fail_cnt = oldcnt;
+	    tally->fail_time = oldtime;
+
+	    if (!(opts->ctrl & OPT_QUIET)) {
+	        pam_info(pamh, _("Account temporary locked (%ld seconds left)"),
+                         oldtime+opts->lock_time-time(NULL));
+            }
+	    if (!(opts->ctrl & OPT_NOLOGNOTICE)) {
+      	    	pam_syslog(pamh, LOG_NOTICE,
+	               "user %s (%lu) has time limit [%lds left]"
+	               " since last failure.",
+                       user, (unsigned long)uid,
+	               oldtime+opts->lock_time-time(NULL));
+	    }
+	    rv = PAM_AUTH_ERR;
+	    goto cleanup;
+      	}
+    }
+
+cleanup:
+#ifdef HAVE_LIBAUDIT
+    if (audit_fd != -1) {
+        close(audit_fd);
+    }
+#endif
+    return rv;
+}
+
+/* --- tally bump function: bump tally for uid by (signed) inc --- */
+
+static int
+tally_bump (int inc, time_t *oldtime, pam_handle_t *pamh,
+            uid_t uid, const char *user, struct tally_options *opts) 
+{
+    struct tallylog tally;
+    tally_t oldcnt;
+    FILE *tfile = NULL;
+    const void *remote_host = NULL;
+    int i, rv;
+
+    tally.fail_cnt = 0;  /* !TALLY_HI --> Log opened for update */
+    
+    i = get_tally(pamh, uid, opts->filename, &tfile, &tally);
+    if (i != PAM_SUCCESS) {
+        if (tfile)
+            fclose(tfile);
+        RETURN_ERROR(i);
+    }
+
+    /* to remember old fail time (for locktime) */
+    if (oldtime) {
+        *oldtime = (time_t)tally.fail_time;
+    }
+    
+    tally.fail_time = time(NULL);
+
+    (void) pam_get_item(pamh, PAM_RHOST, &remote_host);
+    if (!remote_host) {
+    	(void) pam_get_item(pamh, PAM_TTY, &remote_host);
+	if (!remote_host) {
+    	    remote_host = "unknown";
+    	}
+    }
+    
+    strncpy(tally.fail_line, remote_host,
+		    sizeof(tally.fail_line)-1);
+    tally.fail_line[sizeof(tally.fail_line)-1] = 0;
+
+    oldcnt = tally.fail_cnt;
+    
+    if (!(opts->ctrl & OPT_MAGIC_ROOT) || getuid()) {
+      /* magic_root doesn't change tally */
+      tally.fail_cnt += inc;
+
+      if (tally.fail_cnt == TALLY_HI) { /* Overflow *and* underflow. :) */
+          tally.fail_cnt -= inc;
+          pam_syslog(pamh, LOG_ALERT, "Tally %sflowed for user %s",
+                 (inc<0)?"under":"over",user);
+      }
+    }
+
+    rv = tally_check(oldcnt, *oldtime, pamh, uid, user, opts, &tally);
+
+    i = set_tally(pamh, uid, opts->filename, &tfile, &tally);
+    if (i != PAM_SUCCESS) {
+        if (tfile)
+            fclose(tfile);
+        if (rv == PAM_SUCCESS)
+	    RETURN_ERROR( i );
+	/* fallthrough */
+    }
+
+    return rv;
+}
+
+static int
+tally_reset (pam_handle_t *pamh, uid_t uid, struct tally_options *opts)
+{
+    struct tallylog tally; 
+    FILE *tfile = NULL;
+    int i;
+    
+    /* resets only if not magic root */
+
+    if ((opts->ctrl & OPT_MAGIC_ROOT) && getuid() == 0) {
+        return PAM_SUCCESS; 
+    }
+
+    tally.fail_cnt = 0;  /* !TALLY_HI --> Log opened for update */
+
+    i=get_tally(pamh, uid, opts->filename, &tfile, &tally);
+    if (i != PAM_SUCCESS) {
+        if (tfile) 
+            fclose(tfile);
+        RETURN_ERROR(i);
+    }
+    
+    memset(&tally, 0, sizeof(tally));
+    
+    i=set_tally(pamh, uid, opts->filename, &tfile, &tally);
+    if (i != PAM_SUCCESS) {
+        if (tfile) 
+            fclose(tfile);
+        RETURN_ERROR(i);
+    }
+
+    return PAM_SUCCESS;
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- authentication management functions (only) --- */
+
+PAM_EXTERN int
+pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
+		    int argc, const char **argv)
+{
+  int
+    rv;
+  time_t
+    oldtime = 0;
+  struct tally_options
+    options, *opts = &options;
+  uid_t
+    uid;
+  const char
+    *user;
+
+  rv = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
+  if (rv != PAM_SUCCESS)
+      RETURN_ERROR(rv);
+
+  if (flags & PAM_SILENT)
+    opts->ctrl |= OPT_QUIET;
+
+  rv = pam_get_uid(pamh, &uid, &user, opts);
+  if (rv != PAM_SUCCESS)
+      RETURN_ERROR(rv);
+
+  rv = tally_bump(1, &oldtime, pamh, uid, user, opts);
+
+  tally_set_data(pamh, oldtime);
+
+  return rv;
+}
+
+PAM_EXTERN int
+pam_sm_setcred(pam_handle_t *pamh, int flags UNUSED,
+	       int argc, const char **argv)
+{
+  int
+    rv;
+  time_t
+    oldtime = 0;
+  struct tally_options
+    options, *opts = &options;
+  uid_t
+    uid;
+  const char
+    *user;
+
+  rv = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
+  if ( rv != PAM_SUCCESS )
+      RETURN_ERROR( rv );
+
+  rv = pam_get_uid(pamh, &uid, &user, opts);
+  if ( rv != PAM_SUCCESS )
+      RETURN_ERROR( rv );
+
+  if ( tally_get_data(pamh, &oldtime) != 0 )
+  /* no data found */
+      return PAM_SUCCESS;
+
+  return tally_reset(pamh, uid, opts);
+}
+
+/*---------------------------------------------------------------------*/
+
+/* --- authentication management functions (only) --- */
+
+/* To reset failcount of user on successfull login */
+
+PAM_EXTERN int
+pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
+		 int argc, const char **argv)
+{
+  int
+    rv;
+  time_t
+    oldtime = 0;
+  struct tally_options
+    options, *opts = &options;
+  uid_t
+    uid;
+  const char
+    *user;
+
+  rv = tally_parse_args(pamh, opts, PHASE_ACCOUNT, argc, argv);
+  if ( rv != PAM_SUCCESS )
+      RETURN_ERROR( rv );
+
+  rv = pam_get_uid(pamh, &uid, &user, opts);
+  if ( rv != PAM_SUCCESS )
+      RETURN_ERROR( rv );
+
+  if ( tally_get_data(pamh, &oldtime) != 0 )
+  /* no data found */
+      return PAM_SUCCESS;
+
+  return tally_reset(pamh, uid, opts);
+}
+
+/*-----------------------------------------------------------------------*/
+
+#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
+     NULL,
+     NULL,
+     NULL,
+};
+
+#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( char **argv ) 
+{
+  const char *pname = *argv;
+  for ( ; *argv ; (void)(*argv && ++argv) ) {
+    if      ( !strcmp (*argv,"--file")    ) cline_filename=*++argv;
+    else if ( !strcmp(*argv,"-f")         ) cline_filename=*++argv;
+    else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
+    else if ( !strcmp (*argv,"--user")    ) cline_user=*++argv;
+    else if ( !strcmp (*argv,"-u")        ) cline_user=*++argv;
+    else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
+    else if ( !strcmp (*argv,"--reset")   ) cline_reset=0;
+    else if ( !strcmp (*argv,"-r")        ) cline_reset=0;
+    else if ( !strncmp(*argv,"--reset=",8)) {
+      if ( sscanf(*argv+8,"%hu",&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;
+}
+
+static void
+print_one(const struct tallylog *tally, uid_t uid)
+{
+   static int once;
+   char *cp;
+   time_t fail_time;
+   struct tm *tm;
+   struct passwd *pwent;
+   const char *username = "[NONAME]";
+   char ptime[80];
+
+   pwent = getpwuid(uid);
+   fail_time = tally->fail_time;
+   tm = localtime(&fail_time);
+   strftime (ptime, sizeof (ptime), "%D %H:%M:%S", tm);
+   cp = ptime;
+   if (pwent) {
+        username = pwent->pw_name;
+   }
+   if (!once) {
+        printf (_("Login           Failures Latest failure     From\n"));
+        once++;
+   }
+   printf ("%-15.15s %5hu    ", username, tally->fail_cnt);
+   if (tally->fail_time) {
+        printf ("%-17.17s  %s", cp, tally->fail_line);
+   }
+   putchar ('\n');
+}
+
+int 
+main( int argc UNUSED, char **argv )
+{
+  struct tallylog tally;
+
+  if ( ! getopts( argv+1 ) ) {
+    printf(_("%s: [-f rooted-filename] [--file rooted-filename]\n"
+             "   [-u username] [--user username]\n"
+	     "   [-r] [--reset[=n]] [--quiet]\n"),
+           *argv);
+    exit(2);
+  }
+
+  umask(077);
+
+  /*
+   * Major difference between individual user and all users:
+   *  --user just handles one user, just like PAM.
+   *  without --user it handles all users, sniffing cline_filename for nonzeros
+   */
+
+  if ( cline_user ) {
+    uid_t uid;
+    FILE *tfile=0;
+    struct tally_options opts;
+    int i;
+
+    memset(&opts, 0, sizeof(opts));
+    opts.ctrl = OPT_AUDIT;
+    i=pam_get_uid(NULL, &uid, NULL, &opts);
+    if ( i != PAM_SUCCESS ) {
+      fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
+      exit(1);
+    }
+
+    i=get_tally(NULL, uid, cline_filename, &tfile, &tally);
+    if ( i != PAM_SUCCESS ) {
+      if (tfile)
+          fclose(tfile);
+      fprintf(stderr, "%s: %s\n", *argv, pam_errors(i));
+      exit(1);
+    }
+
+    if ( !cline_quiet )
+      print_one(&tally, uid);
+
+    if (cline_reset != TALLY_HI) {
+#ifdef HAVE_LIBAUDIT
+        char buf[64];
+        int audit_fd = audit_open();
+        snprintf(buf, sizeof(buf), "pam_tally2 uid=%u reset=%hu", uid, cline_reset);
+        audit_log_user_message(audit_fd, AUDIT_USER_ACCT,
+                buf, NULL, NULL, NULL, 1);
+        if (audit_fd >=0)
+                close(audit_fd);
+#endif
+        if (cline_reset == 0) {
+            memset(&tally, 0, sizeof(tally));
+        } else {
+            tally.fail_cnt = cline_reset;
+        }
+        i=set_tally(NULL, uid, cline_filename, &tfile, &tally);
+        if (i != PAM_SUCCESS) {
+            if (tfile) fclose(tfile);
+            fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
+            exit(1);
+        }
+    } else {
+        fclose(tfile);
+    }
+  }
+  else /* !cline_user (ie, operate on all users) */ {
+    FILE *tfile=fopen(cline_filename, "r");
+    uid_t uid=0;
+    if (!tfile  && cline_reset != 0) {
+    	perror(*argv);
+    	exit(1);
+    }
+
+    for ( ; tfile && !feof(tfile); uid++ ) {
+      if ( !fread(&tally, sizeof(tally), 1, tfile)
+	   || !tally.fail_cnt ) {
+      	 continue;
+      }
+      print_one(&tally, uid);
+    }
+    if (tfile)
+      fclose(tfile);
+    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 ) {
+#ifdef HAVE_LIBAUDIT
+      char buf[64];
+      int audit_fd = audit_open();
+      snprintf(buf, sizeof(buf), "pam_tally2 uid=all reset=0");
+      audit_log_user_message(audit_fd, AUDIT_USER_ACCT,
+              buf, NULL, NULL, NULL, 1);
+      if (audit_fd >=0)
+              close(audit_fd);
+#endif
+      tfile=fopen(cline_filename, "w");
+      if ( !tfile ) perror(*argv), exit(0);
+      fclose(tfile);
+    }
+  }
+  return 0;
+}
+
+
+#endif   /* #ifndef MAIN */
diff --git a/modules/pam_tally2/pam_tally2_app.c b/modules/pam_tally2/pam_tally2_app.c
new file mode 100644
index 00000000..681ed690
--- /dev/null
+++ b/modules/pam_tally2/pam_tally2_app.c
@@ -0,0 +1,7 @@
+/*
+ #  This seemed like such a good idea at the time. :)
+ */
+
+#define MAIN
+#include "pam_tally2.c"
+
diff --git a/modules/pam_tally2/tallylog.h b/modules/pam_tally2/tallylog.h
new file mode 100644
index 00000000..596b1dac
--- /dev/null
+++ b/modules/pam_tally2/tallylog.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2006, Red Hat, Inc.
+ * 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 Red Hat, Inc. 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 RED HAT, INC. 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.
+ */
+
+/*
+ * tallylog.h - login failure data file format
+ *
+ * The new login failure file is not compatible with the old faillog(8) format
+ * Each record in the file represents a separate UID and the file
+ * is indexed in that fashion.
+ */
+
+
+#ifndef _TALLYLOG_H
+#define _TALLYLOG_H
+
+#include <stdint.h>
+
+struct	tallylog {
+	char		fail_line[52];	/* rhost or tty of last failure */
+	uint16_t	reserved;	/* reserved for future use */
+	uint16_t	fail_cnt;	/* failures since last success */
+	uint64_t	fail_time;	/* time of last failure */
+};
+/* 64 bytes / entry */
+
+#endif
diff --git a/modules/pam_tally2/tst-pam_tally2 b/modules/pam_tally2/tst-pam_tally2
new file mode 100755
index 00000000..83c71f41
--- /dev/null
+++ b/modules/pam_tally2/tst-pam_tally2
@@ -0,0 +1,2 @@
+#!/bin/sh
+../../tests/tst-dlopen .libs/pam_tally2.so
-- 
cgit v1.2.3