From c75c3ff9f2c3d221aabe89b8d0779f041e71e30c Mon Sep 17 00:00:00 2001 From: Tomas Mraz Date: Fri, 7 Jan 2005 13:52:42 +0000 Subject: Relevant BUGIDs: Red Hat bz 60930 Purpose of commit: bugfix, new feature Commit summary: --------------- major rewrite of the pam_tally module --- CHANGELOG | 1 + doc/modules/pam_tally.sgml | 131 +++++----- modules/pam_tally/README | 86 ++++--- modules/pam_tally/pam_tally.c | 555 ++++++++++++++++++++++++------------------ 4 files changed, 444 insertions(+), 329 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d618e325..bb78fde9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -70,6 +70,7 @@ BerliOS Bugs are marked with (BerliOS #XXXX). changes the password (Bug 872945 - kukuk) * pam_limits: support for new Linux kernel 2.6 limits (from toby cabot - t8m) +* pam_tally: major rewrite of the module (t8m) 0.78: Do Nov 18 14:48:36 CET 2004 diff --git a/doc/modules/pam_tally.sgml b/doc/modules/pam_tally.sgml index eeb05518..44c6f4ed 100644 --- a/doc/modules/pam_tally.sgml +++ b/doc/modules/pam_tally.sgml @@ -18,6 +18,7 @@ pam_tally Author[s]: Tim Baverstock +Tomas Mraz Maintainer: @@ -61,9 +62,7 @@ want to use the supplied appliction.

Note, there are some outstanding issues with this module: pam_tally is very dependant on getpw*() - a database -of usernames would be much more flexible; the `keep a count of current -logins' bit has been #ifdef'd out and you can only reset the -counter on successful authentication, for now. +of usernames would be much more flexible Generic options accepted by both components

@@ -84,23 +83,46 @@ counter on successful authentication, for now. Recognized arguments: onerr=(succeed|fail); file=/where/to/keep/counts; -no_magic_root +deny=n; +lock_time=n; +unlock_time=n; +magic_root; +even_deny_root_account; +per_user; +no_lock_time +no_reset; Description:

-The authentication component of this module increments the attempted -login counter. +The authentication component first checks if the user should be denied +access and if not it increments attempted login counter. +Then on call to pam_setcred it resets the attempts counter +if the user is NOT magic root.

Examples/suggested usage:

-The module argument no_magic_root is used to indicate that if -the module is invoked by a user with uid=0, then the counter is -incremented. The sys-admin should use this for daemon-launched -services, like telnet/rsh/login. For user -launched services, like su, this argument should be omitted. +The deny=n option is used to deny access if tally +for this user exceeds n. + +

+The lock_time=n option is used to always deny access +for at least n seconds after a failed attempt. + +

+The unlock_time=n option is used to allow access after +n seconds after the last failed attempt with exceeded tally. +If this option is used the user will be locked out only for the specified +amount of time after he exceeded his maximum allowed attempts. Otherwise +the lock is removed only by a manual intervention of the system administrator. + +

+The magic_root option is used to indicate that if +the module is invoked by a user with uid=0, then the counter is not +incremented. The sys-admin should use this for user launched services, +like su, otherwise this argument should be omitted.

By way of more explanation, when a process already running as root @@ -109,9 +131,33 @@ bypasses pam_tally's checks: this is handy for suing from root into an account otherwise blocked. However, for services like telnet or login, which always effectively run from the root account, root (ie everyone) shouldn't be granted this -magic status, and the flag `no_magic_root' should be set in this +magic status, and the flag `magic_root' should not be set in this situation, as noted in the summary above. +

+Normally, failed attempts to access root will NOT cause the +root account to become blocked, to prevent denial-of-service: if your +users aren't given shell accounts and root may only login via +su or at the machine console (not +telnet/rsh, etc), this is safe. If you really want +root to be blocked for some given service, use +even_deny_root_account. + +

+If /var/log/faillog contains a non-zero .fail_max/.fail_locktime +field for this user then the per_user module argument will +ensure that the module uses this value and not the global +deny/lock_time=n parameter. + +

+The no_lock_time option is for ensuring that the module does +not use the .fail_locktime field in /var/log/faillog for this +user. + +

+The no_reset option is used to instruct the module to not reset +the count on successful entry. + Account component @@ -122,67 +168,28 @@ situation, as noted in the summary above. Recognized arguments: onerr=(succeed|fail); file=/where/to/keep/counts; -deny=n; -no_magic_root; -even_deny_root_account; -reset; +magic_root; no_reset; -per_user; -no_lock_time Description:

-The account component can deny access and/or reset the attempts -counter. It also checks to make sure that the counts file is a plain -file and not world writable. +The account component resets attempts counter if the user is NOT +magic root. This phase can be used optionaly for services which don't call +pam_setcred correctly or if the reset should be done regardless +of the failure of the account phase of other modules. Examples/suggested usage:

-The deny=n option is used to deny access if tally -for this user exceeds n. The presence of -deny=n changes the default for -reset/no_reset to reset, unless the user -trying to gain access is root and the no_magic_root option -has NOT been specified. +The magic_root option is used to indicate that if +the module is invoked by a user with uid=0, then the counter is not +decremented/reset. The sys-admin should use this for user launched services, +like su, otherwise this argument should be omitted.

-The no_magic_root option ensures that access attempts by root -DON'T ignore deny. Use this for daemon-based stuff, like -telnet/rsh/login. - -

-The even_deny_root_account option is used to ensure that the -root account can become unavailable. Note that magic root -trying to gain root bypasses this, but normal users can be locked out. - -

-The reset option instructs the module to reset count to 0 on -successful entry, even for magic root. The no_reset option is -used to instruct the module to not reset the count on successful -entry. This is the default unless deny exists and the user -attempting access is NOT magic root. - -

-If /var/log/faillog contains a non-zero .fail_max -field for this user then the per_user module argument will -ensure that the module uses this value and not the global -deny=n parameter. - -

-The no_lock_time option is for ensuring that the module does -not use the .fail_locktime field in /var/log/faillog for this -user. - -

-Normally, failed attempts to access root will NOT cause the -root account to become blocked, to prevent denial-of-service: if your -users aren't given shell accounts and root may only login via -su or at the machine console (not -telnet/rsh, etc), this is safe. If you really want -root to be blocked for some given service, use -even_deny_root_account. +The no_reset option is used to instruct the module to not reset +the count on successful entry. diff --git a/modules/pam_tally/README b/modules/pam_tally/README index 4c421648..6c7d87f4 100644 --- a/modules/pam_tally/README +++ b/modules/pam_tally/README @@ -1,5 +1,5 @@ SUMMARY: - pam_tally: + pam_tally.so: Maintains a count of attempted accesses, can reset count on success, can deny access if too many attempts fail. @@ -11,41 +11,54 @@ SUMMARY: * file=/where/to/keep/counts (default /var/log/faillog) (auth) - Authentication phase increments attempted login counter. - * no_magic_root (root DOES increment counter. Use for - daemon-based stuff, like telnet/rsh/login) + Authentication phase first checks if user should be denied access + and if not it increments attempted login counter. Then on call to + pam_setcred it resets the attempts counter if the user is NOT + magic root. + * deny=n (deny access if tally for this user exceeds n) + + * lock_time=n (always deny for n seconds after failed attempt) + + * unlock_time=n (allow access after n seconds after the last + failed attempt with exceeded tally) - (account) - Account phase can deny access and/or reset attempts counter. - * deny=n (deny access if tally for this user exceeds n; - The presence of deny=n changes the default for - reset/no_reset to reset, unless the user trying to - gain access is root and the no_magic_root option - has NOT been specified.) - - * no_magic_root (access attempts by root DON'T ignore deny. - Use this for daemon-based stuff, like telnet/rsh/login) + * magic_root (access attempts by root as requesting user ignore + deny and don't change counter. + Use this for su and similar services.) + * even_deny_root_account (Root can become unavailable. BEWARE. Note that magic root trying to gain root bypasses this, but normal users can be locked out.) - * reset (reset count to 0 on successful entry, even for - magic root) - * no_reset (don't reset count on successful entry) - This is the default unless deny exists and the - user attempting access is NOT magic root. - * per_user (If /var/log/faillog contains a non-zero - .fail_max field for this user then use it - instead of deny=n parameter) + .fail_max/.fail_locktime field for this user then use it + instead of deny=n/lock_time=n parameter.) * no_lock_time (Don't use .fail_locktime filed in /var/log/faillog for this user) + * no_reset (don't reset count on successful entry, + only decrement) + + + (account) + Account phase resets attempts counter if the user is NOT magic root. + This phase can be used optionaly for services which don't call + pam_setcred correctly or if the reset should be done regardless + of the failure of the account phase of other modules. + + * magic_root (access attempts by root as requesting user + don't change counter. + Use this for su and similar services.) + + * no_reset (don't reset count on successful entry, + only decrement) + Also checks to make sure that the counts file is a plain file and not world writable. - Tim Baverstock , v0.1 5 March 1997 + - Tomas Mraz , v0.2 5 January 2005 LONGER: @@ -53,20 +66,20 @@ pam_tally comes in two parts: pam_tally.so and pam_tally. pam_tally.so sits in a pam config file, in the auth and account sections. -In the auth section, it increments a per-uid counter for each attempted -login, in the account section, it denies access if attempted logins -exceed some threashold and/or resets that counter to zero on successful -login. +In the auth section, it denies access if attempted logins exceed some +threshold and it increments a per-uid counter for each attempted login, +in the account section, it resets that counter to zero on successful +login. If the module isn't used in the account section it resets the counter +to zero on call to pam_setcred. Root is treated specially: -1. When a process already running as root tries to access some service, the -access is `magic', and bypasses pam_tally's checks: handy for `su'ing from -root into an account otherwise blocked. However, for services like telnet or -login which always effectively run from the root account, root (ie everyone) -shouldn't be granted this magic status, and the flag `no_magic_root' should -be set in this situation, as noted in the summary above. [This option may -be obsolete, with `sufficient root' processing.] +1. When a process already running as root tries to access some service and the +'magic_root' flag is set, the access is `magic', and bypasses pam_tally's +checks: handy for `su'ing from root into an account otherwise blocked. +NOTE: This was changed from the previous version of pam_tally where the default +was to treat root as magic and there were the 'no_magic_root' flag. However +for most of services the current default make sense. 2. Normally, failed attempts to access root will NOT cause the root account to become blocked, to prevent denial-of-service: if your users aren't @@ -93,3 +106,10 @@ The (4.0 Redhat) utilities seem to do funny things with uid, and I'm not wholly sure I understood what I should have been doing anyway so the `keep a count of current logins' bit has been #ifdef'd out and you can only reset the counter on successful authentication, for now. + +IMPORTANT NOTICE: +In the original version of pam_tally there was a bug where the information +if the password was correct or not was leaked by returning error from +different pam management phases. This was solved by moving the denying +functionality to the auth phase. However it's necessary to update the pam +configuration by moving the required options (as deny=N) to the auth phase. diff --git a/modules/pam_tally/pam_tally.c b/modules/pam_tally/pam_tally.c index 341f448e..134f7f32 100644 --- a/modules/pam_tally/pam_tally.c +++ b/modules/pam_tally/pam_tally.c @@ -9,6 +9,8 @@ * 5 March 1997 * * Stuff stolen from pam_rootok and pam_listfile + * + * Changes by Tomas Mraz 5 January 2005 */ #include @@ -56,12 +58,6 @@ #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) @@ -79,6 +75,27 @@ struct fail_s { #endif /* ndef MAIN */ }; +struct tally_options { + const char *filename; + tally_t deny; + long lock_time; + long 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_PER_USER 010 +#define OPT_NO_LOCK_TIME 020 +#define OPT_NO_RESET 040 + + /*---------------------------------------------------------------------*/ /* some syslogging */ @@ -101,6 +118,91 @@ static void _pam_log(int err, const char *format, ...) /*---------------------------------------------------------------------*/ +/* --- Support function: parse arguments --- */ + +static void log_phase_no_auth( int phase, const char *argv ) +{ + if ( phase != PHASE_AUTH ) { + _pam_log(LOG_ERR, + MODULE_NAME ": option %s allowed in auth phase only", argv); + } +} + +static int tally_parse_args( struct tally_options *opts, int phase, + int argc, const char **argv ) +{ + memset(opts, 0, sizeof(*opts)); + opts->filename = DEFAULT_LOGFILE; + + for ( ; argc-- > 0; ++argv ) { + + if ( ! strncmp( *argv, "file=", 5 ) ) { + const char *from = *argv + 5; + if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) { + _pam_log(LOG_ERR, + MODULE_NAME ": filename not /rooted or too long; ", + *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" ) ) { + log_phase_no_auth(phase, *argv); + opts->ctrl |= OPT_DENY_ROOT; + } + else if ( ! strncmp( *argv, "deny=", 5 ) ) { + log_phase_no_auth(phase, *argv); + if ( sscanf((*argv)+5,TALLY_FMT,&opts->deny) != 1 ) { + _pam_log(LOG_ERR,"bad number supplied; %s",*argv); + return PAM_AUTH_ERR; + } + } + else if ( ! strncmp( *argv, "lock_time=", 10 ) ) { + log_phase_no_auth(phase, *argv); + if ( sscanf((*argv)+10,"%ld",&opts->lock_time) != 1 ) { + _pam_log(LOG_ERR,"bad number supplied; %s",*argv); + return PAM_AUTH_ERR; + } + } + else if ( ! strncmp( *argv, "unlock_time=", 12 ) ) { + log_phase_no_auth(phase, *argv); + if ( sscanf((*argv)+12,"%ld",&opts->unlock_time) != 1 ) { + _pam_log(LOG_ERR,"bad number supplied; %s",*argv); + return PAM_AUTH_ERR; + } + } + else if ( ! strcmp( *argv, "per_user" ) ) + { + log_phase_no_auth(phase, *argv); + opts->ctrl |= OPT_PER_USER; + } + else if ( ! strcmp( *argv, "no_lock_time") ) + { + log_phase_no_auth(phase, *argv); + opts->ctrl |= OPT_NO_LOCK_TIME; + } + else if ( ! strcmp( *argv, "no_reset" ) ) { + opts->ctrl |= OPT_NO_RESET; + } + else { + _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv); + } + } + + return PAM_SUCCESS; +} + +/*---------------------------------------------------------------------*/ + /* --- Support function: get uid (and optionally username) from PAM or cline_user --- */ @@ -136,6 +238,42 @@ static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp ) /*---------------------------------------------------------------------*/ +/* --- Support functions: set/get tally data --- */ + +static void _cleanup( pam_handle_t *pamh, void *data, int error_status ) + { + 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 && oldtime != NULL ) { + *oldtime = *(const time_t *)data; + pam_set_data(pamh, MODULE_NAME, NULL, NULL); + } + else { + rv = -1; + *oldtime = 0; + } + return rv; + } + +/*---------------------------------------------------------------------*/ + /* --- Support function: open/create tallyfile and return tally for uid --- */ /* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */ @@ -255,82 +393,42 @@ static int set_tally( tally_t tally, #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)) +#define RETURN_ERROR(i) return ((opts->ctrl & OPT_FAIL_ON_ERROR)?(i):(PAM_SUCCESS)) /*---------------------------------------------------------------------*/ /* --- tally bump function: bump tally for uid by (signed) inc --- */ -static int tally_bump (int inc, +static int tally_bump (int inc, time_t *oldtime, pam_handle_t *pamh, - int flags, - int argc, - const char **argv) { - uid_t uid; - - int - fail_on_error = FALSE; + uid_t uid, + const char *user, + struct tally_options *opts) { 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, *cur_tty = NULL; struct fail_s fs, *fsp = &fs; + int i; - int i=pam_get_uid(pamh, &uid, &user); - if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); - - i=get_tally( &tally, uid, filename, &TALLY, fsp ); + i=get_tally( &tally, uid, opts->filename, &TALLY, fsp ); /* to remember old fail time (for locktime) */ fsp->fs_fail_time = fsp->fs_faillog.fail_time; - fsp->fs_faillog.fail_time = time(NULL); + if ( inc > 0 ) { + if ( oldtime ) { + *oldtime = fsp->fs_faillog.fail_time; + } + fsp->fs_faillog.fail_time = time(NULL); + } else { + if ( oldtime ) { + fsp->fs_faillog.fail_time = *oldtime; + } + } (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host); if (!remote_host) { @@ -352,7 +450,7 @@ static int tally_bump (int inc, } if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } - if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + if ( !(opts->ctrl & OPT_MAGIC_ROOT) || getuid() ) { /* magic_root doesn't change tally */ tally+=inc; @@ -363,217 +461,215 @@ static int tally_bump (int inc, } } - i=set_tally( tally, uid, filename, &TALLY, fsp ); + i=set_tally( tally, uid, opts->filename, &TALLY, fsp ); if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } - } - return PAM_SUCCESS; + 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; +static int tally_check (time_t oldtime, + pam_handle_t *pamh, + uid_t uid, + const char *user, + struct tally_options *opts) { tally_t - deny = 0; + deny = opts->deny; tally_t tally = 0; /* !TALLY_HI --> Log opened for update */ + long + lock_time = opts->lock_time; - 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() */ - } - - { struct fail_s fs, *fsp = &fs; FILE *TALLY=0; - int i=pam_get_uid(pamh, &uid, &user); - if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); + int i; - i=get_tally( &tally, uid, filename, &TALLY, fsp ); - if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + i=get_tally( &tally, uid, opts->filename, &TALLY, fsp ); + if (TALLY) fclose(TALLY); + if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); } - if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + if ( !(opts->ctrl & OPT_MAGIC_ROOT) || getuid() ) { /* magic_root skips tally check */ /* 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 ( (fsp->fs_faillog.fail_max) && (per_user) ) { + if ( (fsp->fs_faillog.fail_max) && (opts->ctrl & OPT_PER_USER) ) { deny = fsp->fs_faillog.fail_max; } - if (fsp->fs_faillog.fail_locktime && fsp->fs_fail_time - && (!no_lock_time) ) + if ( (fsp->fs_faillog.fail_locktime) && (opts->ctrl & OPT_PER_USER) ) { + lock_time = fsp->fs_faillog.fail_locktime; + } + if (lock_time && oldtime + && !(opts->ctrl & OPT_NO_LOCK_TIME) ) { - if ( (fsp->fs_faillog.fail_locktime + fsp->fs_fail_time) > time(NULL) ) + if ( lock_time + oldtime > time(NULL) ) { _pam_log(LOG_NOTICE, "user %s ("UID_FMT") has time limit [%lds left]" " since last failure.", user,uid, - fsp->fs_fail_time+fsp->fs_faillog.fail_locktime + oldtime+lock_time -time(NULL)); - if (TALLY) - fclose(TALLY); return PAM_AUTH_ERR; } } + if (opts->unlock_time && oldtime) + { + if ( opts->unlock_time + oldtime <= time(NULL) ) + { /* ignore deny check after unlock_time elapsed */ + return PAM_SUCCESS; + } + } if ( ( deny != 0 ) && /* deny==0 means no deny */ ( tally > deny ) && /* tally>deny means exceeded */ - ( even_deny_root_account || uid ) /* even_deny stops uid check */ + ( ((opts->ctrl & OPT_DENY_ROOT) || uid) ) /* even_deny stops uid check */ ) { _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT, user, uid, tally, deny); - if (TALLY) - fclose(TALLY); 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... */ + return PAM_SUCCESS; +} + +static int tally_reset (pam_handle_t *pamh, + uid_t uid, + const char *user, + struct tally_options *opts) { + tally_t + tally = 0; /* !TALLY_HI --> Log opened for update */ + + struct fail_s fs, *fsp = &fs; + FILE *TALLY=0; + int i; + + i=get_tally( &tally, uid, opts->filename, &TALLY, fsp ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + + /* resets if not magic root + */ - /* Magic root only resets on explicit reset, regardless of deny */ + if ( (!(opts->ctrl & OPT_MAGIC_ROOT) || getuid()) + && !(opts->ctrl & OPT_NO_RESET) ) + { tally=0; } - if ( reset == TALLY_RESET_RESET ) { tally=0; } - } if (tally == 0) { fsp->fs_faillog.fail_time = (time_t) 0; strcpy(fsp->fs_faillog.fail_line, ""); } - i=set_tally( tally, uid, filename, &TALLY, fsp ); + + i=set_tally( tally, uid, opts->filename, &TALLY, fsp ); if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } - } - return PAM_SUCCESS; + return PAM_SUCCESS; +} + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_AUTH + +PAM_FUNCTION( pam_sm_authenticate ) { + int + rvcheck, rvbump; + time_t + oldtime = 0; + struct tally_options + options, *opts = &options; + uid_t + uid; + const char + *user; + + rvcheck = tally_parse_args(opts, PHASE_AUTH, argc, argv); + if ( rvcheck != PAM_SUCCESS ) + RETURN_ERROR( rvcheck ); + + rvcheck = pam_get_uid(pamh, &uid, &user); + if ( rvcheck != PAM_SUCCESS ) + RETURN_ERROR( rvcheck ); + + rvbump = tally_bump(1, &oldtime, pamh, uid, user, opts); + rvcheck = tally_check(oldtime, pamh, uid, user, opts); + + tally_set_data(pamh, oldtime); + + return rvcheck != PAM_SUCCESS ? rvcheck : rvbump; +} + +PAM_FUNCTION( pam_sm_setcred ) { + int + rv; + time_t + oldtime = 0; + struct tally_options + options, *opts = &options; + uid_t + uid; + const char + *user; + + rv = tally_parse_args(opts, PHASE_AUTH, argc, argv); + if ( rv != PAM_SUCCESS ) + RETURN_ERROR( rv ); + + rv = pam_get_uid(pamh, &uid, &user); + if ( rv != PAM_SUCCESS ) + RETURN_ERROR( rv ); + + if ( tally_get_data(pamh, &oldtime) != 0 ) + /* no data found */ + return PAM_SUCCESS; + + if ( (rv=tally_bump(-1, &oldtime, pamh, uid, user, opts)) != PAM_SUCCESS ) + return rv; + return tally_reset(pamh, uid, user, opts); +} + +#endif + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_ACCOUNT + +/* To reset failcount of user on successfull login */ + +PAM_FUNCTION( pam_sm_acct_mgmt ) { + int + rv; + time_t + oldtime = 0; + struct tally_options + options, *opts = &options; + uid_t + uid; + const char + *user; + + rv = tally_parse_args(opts, PHASE_ACCOUNT, argc, argv); + if ( rv != PAM_SUCCESS ) + RETURN_ERROR( rv ); + + rv = pam_get_uid(pamh, &uid, &user); + if ( rv != PAM_SUCCESS ) + RETURN_ERROR( rv ); + + if ( tally_get_data(pamh, &oldtime) != 0 ) + /* no data found */ + return PAM_SUCCESS; + + if ( (rv=tally_bump(-1, &oldtime, pamh, uid, user, opts)) != PAM_SUCCESS ) + return rv; + return tally_reset(pamh, uid, user, opts); } -#endif /* #ifdef PAM_SM_AUTH */ +#endif /* #ifdef PAM_SM_ACCOUNT */ /*-----------------------------------------------------------------------*/ @@ -595,18 +691,9 @@ struct pam_module _pam_tally_modstruct = { #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 */ -- cgit v1.2.3