From ea488580c42e8918445a945484de3c8a5addc761 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 20 Jun 2000 22:10:38 +0000 Subject: Initial revision --- modules/pam_tally/pam_tally.c | 689 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 689 insertions(+) create mode 100644 modules/pam_tally/pam_tally.c (limited to 'modules/pam_tally/pam_tally.c') diff --git a/modules/pam_tally/pam_tally.c b/modules/pam_tally/pam_tally.c new file mode 100644 index 00000000..ff75697d --- /dev/null +++ b/modules/pam_tally/pam_tally.c @@ -0,0 +1,689 @@ +/* + * pam_tally.c + * + * $Id$ + */ + + +/* By Tim Baverstock , Multi Media Machine Ltd. + * 5 March 1997 + * + * Stuff stolen from pam_rootok and pam_listfile + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "faillog.h" + +#ifndef TRUE +#define TRUE 1L +#define FALSE 0L +#endif + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +/* #define PAM_SM_SESSION */ +/* #define PAM_SM_PASSWORD */ + +#include + +/*---------------------------------------------------------------------*/ + +#define DEFAULT_LOGFILE "/var/log/faillog" +#define MODULE_NAME "pam_tally" + +enum TALLY_RESET { + TALLY_RESET_DEFAULT, + TALLY_RESET_RESET, + TALLY_RESET_NO_RESET +}; + +#define tally_t unsigned short int +#define TALLY_FMT "%hu" +#define TALLY_HI ((tally_t)~0L) + +#define UID_FMT "%hu" + +#ifndef FILENAME_MAX +# define FILENAME_MAX MAXPATHLEN +#endif + +static struct faillog faillog; +static time_t fail_time; + +/*---------------------------------------------------------------------*/ + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef MAIN + vfprintf(stderr,format,args); +#else + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + closelog(); +#endif + va_end(args); +} + +/*---------------------------------------------------------------------*/ + +/* --- Support function: get uid (and optionally username) from PAM or + cline_user --- */ + +#ifdef MAIN +static char *cline_user=0; /* cline_user is used in the administration prog */ +#endif + +static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp ) + { + const char *user; + struct passwd *pw; + +#ifdef MAIN + user = cline_user; +#else + pam_get_user( pamh, &user, NULL ); +#endif + + if ( !user || !*user ) { + _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?"); + return PAM_AUTH_ERR; + } + + if ( ! ( pw = getpwnam( user ) ) ) { + _pam_log(LOG_ERR,MODULE_NAME ": pam_get_uid; no such user %s",user); + return PAM_USER_UNKNOWN; + } + + if ( uid ) *uid = pw->pw_uid; + if ( userp ) *userp = user; + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- Support function: open/create tallyfile and return tally for uid --- */ + +/* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */ +/* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */ + +static int get_tally( tally_t *tally, + uid_t uid, + const char *filename, + FILE **TALLY ) + { + struct stat fileinfo; + int lstat_ret = lstat(filename,&fileinfo); + + if ( lstat_ret && *tally!=TALLY_HI ) { + int oldmask = umask(077); + *TALLY=fopen(filename, "a"); + /* Create file, or append-open in pathological case. */ + umask(oldmask); + if ( !*TALLY ) { + _pam_log(LOG_ALERT, "Couldn't create %s",filename); + return PAM_AUTH_ERR; + } + lstat_ret = fstat(fileno(*TALLY),&fileinfo); + fclose(*TALLY); + } + + if ( lstat_ret ) { + _pam_log(LOG_ALERT, "Couldn't stat %s",filename); + return PAM_AUTH_ERR; + } + + if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) { + /* If the file is world writable or is not a + normal file, return error */ + _pam_log(LOG_ALERT, + "%s is either world writable or not a normal file", + filename); + return PAM_AUTH_ERR; + } + + if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) { + _pam_log(LOG_ALERT, "Error opening %s for update", filename); + +/* Discovering why account service fails: e/uid are target user. + * + * perror(MODULE_NAME); + * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid()); + */ + return PAM_AUTH_ERR; + } + + if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) { + _pam_log(LOG_ALERT, "fseek failed %s", filename); + return PAM_AUTH_ERR; + } + + + if ( ( fread((char *) &faillog, sizeof faillog, 1, *TALLY) )==0 ) { + *tally=0; /* Assuming a gappy filesystem */ + } + *tally = faillog.fail_cnt; + + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */ + +static int set_tally( tally_t tally, + uid_t uid, + const char *filename, + FILE **TALLY ) + { + if ( tally!=TALLY_HI ) + { + if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) { + _pam_log(LOG_ALERT, "fseek failed %s", filename); + return PAM_AUTH_ERR; + } + faillog.fail_cnt = tally; + if ( fwrite((char *) &faillog, sizeof faillog, 1, *TALLY)==0 ) { + _pam_log(LOG_ALERT, "tally update (fputc) failed.", filename); + return PAM_AUTH_ERR; + } + } + + if ( fclose(*TALLY) ) { + _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename); + return PAM_AUTH_ERR; + } + *TALLY=NULL; + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- PAM bits --- */ + +#ifndef MAIN + +#define PAM_FUNCTION(name) \ + PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv) + +#define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS)) + +/*---------------------------------------------------------------------*/ + +/* --- tally bump function: bump tally for uid by (signed) inc --- */ + +static int tally_bump (int inc, + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { + uid_t uid; + + int + fail_on_error = FALSE; + tally_t + tally = 0; /* !TALLY_HI --> Log opened for update */ + + char + no_magic_root = FALSE; + + char + filename[ FILENAME_MAX ] = DEFAULT_LOGFILE; + + /* Should probably decode the parameters before anything else. */ + + { + for ( ; argc-- > 0; ++argv ) { + + /* generic options.. um, ignored. :] */ + + if ( ! strcmp( *argv, "no_magic_root" ) ) { + no_magic_root = TRUE; + } + else if ( ! strncmp( *argv, "file=", 5 ) ) { + char const + *from = (*argv)+5; + char + *to = filename; + if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) { + _pam_log(LOG_ERR, + MODULE_NAME ": filename not /rooted or too long; ", + *argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + while ( ( *to++ = *from++ ) ); + } + else if ( ! strcmp( *argv, "onerr=fail" ) ) { + fail_on_error=TRUE; + } + else if ( ! strcmp( *argv, "onerr=succeed" ) ) { + fail_on_error=FALSE; + } + else { + _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv); + } + } /* for() */ + } + + { + FILE + *TALLY = NULL; + const char + *user = NULL, + *remote_host = NULL; + + int i=pam_get_uid(pamh, &uid, &user); + if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); + + i=get_tally( &tally, uid, filename, &TALLY ); + fail_time = faillog.fail_time; /* to remember old fail time (for locktime) */ + faillog.fail_time = (time_t) time( (time_t *) 0); + (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host); + if (!remote_host) + { + strcpy(faillog.fail_line, "unknown"); + } + else + { + strncpy(faillog.fail_line, remote_host, (size_t)sizeof(faillog.fail_line)); + faillog.fail_line[sizeof(faillog.fail_line)-1] = 0; + } + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + + if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + + tally+=inc; + + if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */ + tally-=inc; + _pam_log(LOG_ALERT,"Tally %sflowed for user %s", + (inc<0)?"under":"over",user); + } + } + + i=set_tally( tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + } + + return PAM_SUCCESS; +} + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_AUTH + +PAM_FUNCTION( pam_sm_authenticate ) { + return tally_bump( 1, pamh, flags, argc, argv); +} + +/* --- Seems to need this function. Ho hum. --- */ + +PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; } + +#endif + +/*---------------------------------------------------------------------*/ + +/* --- session management functions (only) --- */ + +/* + * Unavailable until .so files can be suid + */ + +#ifdef PAM_SM_SESSION + +/* To maintain a balance-tally of successful login/outs */ + +PAM_FUNCTION( pam_sm_open_session ) { + return tally_bump( 1, pamh, flags, argc, argv); +} + +PAM_FUNCTION( pam_sm_close_session ) { + return tally_bump(-1, pamh, flags, argc, argv); +} + +#endif + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_AUTH + +/* To lock out a user with an unacceptably high tally */ + +PAM_FUNCTION( pam_sm_acct_mgmt ) { + uid_t + uid; + + int + fail_on_error = FALSE; + tally_t + deny = 0; + tally_t + tally = 0; /* !TALLY_HI --> Log opened for update */ + + char + no_magic_root = FALSE, + even_deny_root_account = FALSE; + char per_user = FALSE; /* if true then deny=.fail_max for user */ + char no_lock_time = FALSE; /* if true then don't use .fail_locktime */ + + const char + *user = NULL; + + enum TALLY_RESET + reset = TALLY_RESET_DEFAULT; + + char + filename[ FILENAME_MAX ] = DEFAULT_LOGFILE; + + /* Should probably decode the parameters before anything else. */ + + { + for ( ; argc-- > 0; ++argv ) { + + /* generic options.. um, ignored. :] */ + + if ( ! strcmp( *argv, "no_magic_root" ) ) { + no_magic_root = TRUE; + } + else if ( ! strcmp( *argv, "even_deny_root_account" ) ) { + even_deny_root_account = TRUE; + } + else if ( ! strcmp( *argv, "reset" ) ) { + reset = TALLY_RESET_RESET; + } + else if ( ! strcmp( *argv, "no_reset" ) ) { + reset = TALLY_RESET_NO_RESET; + } + else if ( ! strncmp( *argv, "file=", 5 ) ) { + char const + *from = (*argv)+5; + char + *to = filename; + if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) { + _pam_log(LOG_ERR, + MODULE_NAME ": filename not /rooted or too long; ", + *argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + while ( ( *to++ = *from++ ) ); + } + else if ( ! strncmp( *argv, "deny=", 5 ) ) { + if ( sscanf((*argv)+5,TALLY_FMT,&deny) != 1 ) { + _pam_log(LOG_ERR,"bad number supplied; %s",*argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + } + else if ( ! strcmp( *argv, "onerr=fail" ) ) { + fail_on_error=TRUE; + } + else if ( ! strcmp( *argv, "onerr=succeed" ) ) { + fail_on_error=FALSE; + } + else if ( ! strcmp( *argv, "per_user" ) ) + { + per_user = TRUE; + } + else if ( ! strcmp( *argv, "no_lock_time") ) + { + no_lock_time = TRUE; + } + else { + _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv); + } + } /* for() */ + } + + { + FILE *TALLY=0; + int i=pam_get_uid(pamh, &uid, &user); + if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); + + i=get_tally( &tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + + if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + + /* To deny or not to deny; that is the question */ + + /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */ + + if ( (faillog.fail_max) && (per_user) ) {deny = faillog.fail_max;} + if (faillog.fail_locktime && fail_time && (!no_lock_time) ) + { + if ( (faillog.fail_locktime + fail_time) > (time_t)time((time_t)0) ) + { + _pam_log(LOG_NOTICE,"user %s ("UID_FMT") has time limit [%lds left] since last failure.",user,uid,fail_time+faillog.fail_locktime-(time_t)time((time_t)0)); + return PAM_AUTH_ERR; + } + } + if ( + ( deny != 0 ) && /* deny==0 means no deny */ + ( tally > deny ) && /* tally>deny means exceeded */ + ( even_deny_root_account || uid ) /* even_deny stops uid check */ + ) { + _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT, + user, uid, tally, deny); + return PAM_AUTH_ERR; /* Only unconditional failure */ + } + + /* resets for explicit reset + * or by default if deny exists and not magic-root + */ + + if ( ( reset == TALLY_RESET_RESET ) || + ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; } + } + else /* is magic root */ { + + /* Magic root skips deny test... */ + + /* Magic root only resets on explicit reset, regardless of deny */ + + if ( reset == TALLY_RESET_RESET ) { tally=0; } + } + if (tally == 0) + { + faillog.fail_time = (time_t) 0; + strcpy(faillog.fail_line, ""); + } + i=set_tally( tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + } + + return PAM_SUCCESS; +} + +#endif /* #ifdef PAM_SM_AUTH */ + +/*-----------------------------------------------------------------------*/ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_tally_modstruct = { + MODULE_NAME, +#ifdef PAM_SM_AUTH + pam_sm_authenticate, + pam_sm_setcred, +#else + NULL, + NULL, +#endif +#ifdef PAM_SM_ACCOUNT + pam_sm_acct_mgmt, +#else + NULL, +#endif +#ifdef PAM_SM_SESSION + pam_sm_open_session, + pam_sm_close_session, +#else + NULL, + NULL, +#endif +#ifdef PAM_SM_PASSWORD + pam_sm_chauthtok, +#else + NULL, +#endif +}; + +#endif /* #ifdef PAM_STATIC */ + +/*-----------------------------------------------------------------------*/ + +#else /* #ifndef MAIN */ + +static const char *cline_filename = DEFAULT_LOGFILE; +static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */ +static int cline_quiet = 0; + +/* + * Not going to link with pamlib just for these.. :) + */ + +static const char * pam_errors( int i ) { + switch (i) { + case PAM_AUTH_ERR: return "Authentication error"; + case PAM_SERVICE_ERR: return "Service error"; + case PAM_USER_UNKNOWN: return "Unknown user"; + default: return "Unknown error"; + } +} + +static int getopts( int argc, char **argv ) { + const char *pname = *argv; + for ( ; *argv ; (void)(*argv && ++argv) ) { + if ( !strcmp (*argv,"--file") ) cline_filename=*++argv; + else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7; + else if ( !strcmp (*argv,"--user") ) cline_user=*++argv; + else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7; + else if ( !strcmp (*argv,"--reset") ) cline_reset=0; + else if ( !strncmp(*argv,"--reset=",8)) { + if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 ) + fprintf(stderr,"%s: Bad number given to --reset=\n",pname), exit(0); + } + else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1; + else { + fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv); + return FALSE; + } + } + return TRUE; +} + +int main ( int argc, char **argv ) { + + if ( ! getopts( argc, argv+1 ) ) { + printf("%s: [--file rooted-filename] [--user username] " + "[--reset[=n]] [--quiet]\n", + *argv); + exit(0); + } + + umask(077); + + /* + * Major difference between individual user and all users: + * --user just handles one user, just like PAM. + * --user=* handles all users, sniffing cline_filename for nonzeros + */ + + if ( cline_user ) { + uid_t uid; + tally_t tally=cline_reset; + FILE *TALLY=0; + int i=pam_get_uid( NULL, &uid, NULL); + if ( i != PAM_SUCCESS ) { + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + + i=get_tally( &tally, uid, cline_filename, &TALLY ); + if ( i != PAM_SUCCESS ) { + if (TALLY) fclose(TALLY); + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + + if ( !cline_quiet ) + printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + + i=set_tally( cline_reset, uid, cline_filename, &TALLY ); + if ( i != PAM_SUCCESS ) { + if (TALLY) fclose(TALLY); + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + } + else /* !cline_user (ie, operate on all users) */ { + FILE *TALLY=fopen(cline_filename, "r"); + uid_t uid=0; + if ( !TALLY ) perror(*argv), exit(0); + + for ( ; !feof(TALLY); uid++ ) { + tally_t tally; + struct passwd *pw; + if ( ! fread((char *) &faillog, sizeof faillog, 1, TALLY) || ! faillog.fail_cnt ) { + tally=faillog.fail_cnt; + continue; + } + tally = faillog.fail_cnt; + + if ( ( pw=getpwuid(uid) ) ) { + printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",pw->pw_name,uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + } + else { + printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + } + } + fclose(TALLY); + if ( cline_reset!=0 && cline_reset!=TALLY_HI ) { + fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv); + } + else if ( !cline_reset ) { + TALLY=fopen(cline_filename, "w"); + if ( !TALLY ) perror(*argv), exit(0); + fclose(TALLY); + } + } + return 0; +} + + +#endif -- cgit v1.2.3