/* * pam_tally2.c * */ /* By Tim Baverstock , Multi Media Machine Ltd. * 5 March 1997 * * Stuff stolen from pam_rootok and pam_listfile * * Changes by Tomas Mraz 5 January 2005, 26 January 2006 * Audit option added for Tomas patch by Sebastien Tricaud 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 #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBAUDIT #include #endif #include #include #include #include #include #include #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 #endif #include #include /*---------------------------------------------------------------------*/ #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 #define OPT_SERIALIZE 01000 #define MAX_LOCK_WAITING_TIME 10 /*---------------------------------------------------------------------*/ /* 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, "serialize" ) ) { opts->ctrl |= OPT_SERIALIZE; } 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 struct tally_data { time_t time; int tfile; }; static void _cleanup(pam_handle_t *pamh UNUSED, void *void_data, int error_status UNUSED) { struct tally_data *data = void_data; if (data->tfile != -1) close(data->tfile); free(data); } static void tally_set_data( pam_handle_t *pamh, time_t oldtime, int tfile ) { struct tally_data *data; if ( (data=malloc(sizeof(*data))) != NULL ) { data->time = oldtime; data->tfile = tfile; pam_set_data(pamh, MODULE_NAME, (void *)data, _cleanup); } } static int tally_get_data( pam_handle_t *pamh, time_t *oldtime, int *tfile ) { int rv; const void *void_data; const struct tally_data *data; rv = pam_get_data(pamh, MODULE_NAME, &void_data); if ( rv == PAM_SUCCESS && void_data != NULL && oldtime != NULL ) { data = void_data; *oldtime = data->time; *tfile = data->tfile; } 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 void alarm_handler(int sig UNUSED) { /* we just need to ignore it */ } static int get_tally(pam_handle_t *pamh, uid_t uid, const char *filename, int *tfile, struct tallylog *tally, unsigned int ctrl) { struct stat fileinfo; int lstat_ret; void *void_tally = tally; int preopened = 0; if (*tfile != -1) { preopened = 1; goto skip_open; } lstat_ret = lstat(filename, &fileinfo); if (lstat_ret) { *tfile=open(filename, O_APPEND|O_CREAT, S_IRUSR|S_IWUSR); /* Create file, or append-open in pathological case. */ if (*tfile == -1) { #ifndef MAIN if (errno == EACCES) { return PAM_IGNORE; /* called with insufficient access rights */ } #endif pam_syslog(pamh, LOG_ALERT, "Couldn't create %s: %m", filename); return PAM_AUTH_ERR; } lstat_ret = fstat(*tfile, &fileinfo); close(*tfile); } *tfile = -1; 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 = open(filename, O_RDWR)) == -1) { #ifndef MAIN if (errno == EACCES) /* 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; } skip_open: if (lseek(*tfile, (off_t)uid*(off_t)sizeof(*tally), SEEK_SET) == (off_t)-1) { pam_syslog(pamh, LOG_ALERT, "lseek failed for %s: %m", filename); if (!preopened) { close(*tfile); *tfile = -1; } return PAM_AUTH_ERR; } if (!preopened && (ctrl & OPT_SERIALIZE)) { /* this code is not thread safe as it uses fcntl locks and alarm() so never use serialize with multithreaded services */ struct sigaction newsa, oldsa; unsigned int oldalarm; int rv; memset(&newsa, '\0', sizeof(newsa)); newsa.sa_handler = alarm_handler; sigaction(SIGALRM, &newsa, &oldsa); oldalarm = alarm(MAX_LOCK_WAITING_TIME); rv = lockf(*tfile, F_LOCK, sizeof(*tally)); /* lock failure is not fatal, we attempt to read the tally anyway */ /* reinstate the eventual old alarm handler */ if (rv == -1 && errno == EINTR) { if (oldalarm > MAX_LOCK_WAITING_TIME) { oldalarm -= MAX_LOCK_WAITING_TIME; } else if (oldalarm > 0) { oldalarm = 1; } } sigaction(SIGALRM, &oldsa, NULL); alarm(oldalarm); } if (fileinfo.st_size < (off_t)(uid+1)*(off_t)sizeof(*tally)) { memset(tally, 0, sizeof(*tally)); } else if (pam_modutil_read(*tfile, void_tally, sizeof(*tally)) != sizeof(*tally)) { memset(tally, 0, sizeof(*tally)); /* Shouldn't happen */ } tally->fail_line[sizeof(tally->fail_line)-1] = '\0'; return PAM_SUCCESS; } /*---------------------------------------------------------------------*/ /* --- Support function: update tallyfile with tally!=TALLY_HI --- */ static int set_tally(pam_handle_t *pamh, uid_t uid, const char *filename, int *tfile, struct tallylog *tally) { void *void_tally = tally; if (tally->fail_cnt != TALLY_HI) { if (lseek(*tfile, (off_t)uid * sizeof(*tally), SEEK_SET) == (off_t)-1) { pam_syslog(pamh, LOG_ALERT, "lseek failed for %s: %m", filename); return PAM_AUTH_ERR; } if (pam_modutil_write(*tfile, void_tally, sizeof(*tally)) != sizeof(*tally)) { pam_syslog(pamh, LOG_ALERT, "update (write) failed for %s: %m", filename); return PAM_AUTH_ERR; } } if (fsync(*tfile)) { pam_syslog(pamh, LOG_ALERT, "update (fsync) failed for %s: %m", filename); return PAM_AUTH_ERR; } 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; const void *rhost = NULL, *tty = NULL; #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; (void)pam_get_item(pamh, PAM_TTY, &tty); (void)pam_get_item(pamh, PAM_RHOST, &rhost); #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, rhost, NULL, tty, 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, rhost, NULL, tty, 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, rhost, NULL, tty, 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, rhost, NULL, tty, 1); } #endif if (!(opts->ctrl & OPT_QUIET)) { pam_info(pamh, _("Account locked due to %u failed logins"), (unsigned int)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, int *tfile) { struct tallylog tally; tally_t oldcnt; 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, opts->ctrl); if (i != PAM_SUCCESS) { if (*tfile != -1) { close(*tfile); *tfile = -1; } 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 != -1) { close(*tfile); *tfile = -1; } if (rv == PAM_SUCCESS) RETURN_ERROR( i ); /* fallthrough */ } else if (!(opts->ctrl & OPT_SERIALIZE)) { close(*tfile); *tfile = -1; } return rv; } static int tally_reset (pam_handle_t *pamh, uid_t uid, struct tally_options *opts, int old_tfile) { struct tallylog tally; int tfile = old_tfile; 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, opts->ctrl); if (i != PAM_SUCCESS) { if (tfile != old_tfile) /* the descriptor is not owned by pam data */ close(tfile); RETURN_ERROR(i); } memset(&tally, 0, sizeof(tally)); i=set_tally(pamh, uid, opts->filename, &tfile, &tally); if (i != PAM_SUCCESS) { if (tfile != old_tfile) /* the descriptor is not owned by pam data */ close(tfile); RETURN_ERROR(i); } if (tfile != old_tfile) close(tfile); 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, tfile = -1; 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, &tfile); tally_set_data(pamh, oldtime, tfile); return rv; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { int rv, tfile = -1; 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, &tfile) != 0 ) /* no data found */ return PAM_SUCCESS; rv = tally_reset(pamh, uid, opts, tfile); pam_set_data(pamh, MODULE_NAME, NULL, NULL); return rv; } /*---------------------------------------------------------------------*/ /* --- 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, tfile = -1; 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, &tfile) != 0 ) /* no data found */ return PAM_SUCCESS; rv = tally_reset(pamh, uid, opts, tfile); pam_set_data(pamh, MODULE_NAME, NULL, NULL); return rv; } /*-----------------------------------------------------------------------*/ #ifdef PAM_STATIC /* static module data */ struct pam_module _pam_tally2_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; int tfile = -1; 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, 0); if ( i != PAM_SUCCESS ) { if (tfile != -1) close(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, ttyname(STDIN_FILENO), 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); close(tfile); if (i != PAM_SUCCESS) { fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); exit(1); } } else { close(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, ttyname(STDIN_FILENO), 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 */