1623 lines
50 KiB
Diff
1623 lines
50 KiB
Diff
diff -up pam/configure.in.pt2 pam/configure.in
|
|
--- pam/configure.in.pt2 2008-10-16 16:12:18.000000000 +0200
|
|
+++ pam/configure.in 2008-10-15 10:28:46.000000000 +0200
|
|
@@ -548,6 +548,7 @@ AC_CONFIG_FILES([Makefile libpam/Makefil
|
|
modules/pam_sepermit/Makefile \
|
|
modules/pam_shells/Makefile modules/pam_stress/Makefile \
|
|
modules/pam_succeed_if/Makefile modules/pam_tally/Makefile \
|
|
+ modules/pam_tally2/Makefile \
|
|
modules/pam_time/Makefile modules/pam_tty_audit/Makefile \
|
|
modules/pam_umask/Makefile \
|
|
modules/pam_unix/Makefile modules/pam_userdb/Makefile \
|
|
diff -up pam/modules/Makefile.am.pt2 pam/modules/Makefile.am
|
|
--- pam/modules/Makefile.am.pt2 2008-10-16 16:12:18.000000000 +0200
|
|
+++ pam/modules/Makefile.am 2008-10-15 10:28:13.000000000 +0200
|
|
@@ -9,7 +9,7 @@ SUBDIRS = pam_access pam_cracklib pam_de
|
|
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 -up pam/modules/pam_tally2/tallylog.h.pt2 pam/modules/pam_tally2/tallylog.h
|
|
--- pam/modules/pam_tally2/tallylog.h.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/tallylog.h 2008-02-27 17:08:50.000000000 +0100
|
|
@@ -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 -up pam/modules/pam_tally2/pam_tally.c.pt2 pam/modules/pam_tally2/pam_tally.c
|
|
--- pam/modules/pam_tally2/pam_tally.c.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/pam_tally.c 2008-10-15 12:07:54.000000000 +0200
|
|
@@ -0,0 +1,985 @@
|
|
+/*
|
|
+ * pam_tally.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 -up pam/modules/pam_tally2/README.xml.pt2 pam/modules/pam_tally2/README.xml
|
|
--- pam/modules/pam_tally2/README.xml.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/README.xml 2008-10-15 11:14:27.000000000 +0200
|
|
@@ -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 -up pam/modules/pam_tally2/tst-pam_tally2.pt2 pam/modules/pam_tally2/tst-pam_tally2
|
|
--- pam/modules/pam_tally2/tst-pam_tally2.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/tst-pam_tally2 2008-10-15 10:23:18.000000000 +0200
|
|
@@ -0,0 +1,2 @@
|
|
+#!/bin/sh
|
|
+../../tests/tst-dlopen .libs/pam_tally2.so
|
|
diff -up pam/modules/pam_tally2/pam_tally2.8.xml.pt2 pam/modules/pam_tally2/pam_tally2.8.xml
|
|
--- pam/modules/pam_tally2/pam_tally2.8.xml.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/pam_tally2.8.xml 2008-10-15 11:36:00.000000000 +0200
|
|
@@ -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 -up pam/modules/pam_tally2/Makefile.am.pt2 pam/modules/pam_tally2/Makefile.am
|
|
--- pam/modules/pam_tally2/Makefile.am.pt2 2008-10-15 12:13:43.000000000 +0200
|
|
+++ pam/modules/pam_tally2/Makefile.am 2008-10-15 11:31:41.000000000 +0200
|
|
@@ -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_tally.c
|
|
+pam_tally2_SOURCES = pam_tally_app.c
|
|
+
|
|
+if ENABLE_REGENERATE_MAN
|
|
+noinst_DATA = README
|
|
+README: pam_tally2.8.xml
|
|
+-include $(top_srcdir)/Make.xml.rules
|
|
+endif
|
|
diff -up pam/modules/pam_tally2/pam_tally_app.c.pt2 pam/modules/pam_tally2/pam_tally_app.c
|
|
--- pam/modules/pam_tally2/pam_tally_app.c.pt2 2008-10-15 12:14:21.000000000 +0200
|
|
+++ pam/modules/pam_tally2/pam_tally_app.c 2008-02-27 17:08:50.000000000 +0100
|
|
@@ -0,0 +1,7 @@
|
|
+/*
|
|
+ # This seemed like such a good idea at the time. :)
|
|
+ */
|
|
+
|
|
+#define MAIN
|
|
+#include "pam_tally.c"
|
|
+
|