8 /* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
11 * Stuff stolen from pam_rootok and pam_listfile
14 #include <security/_pam_aconf.h>
16 #if defined(MAIN) && defined(MEMORY_DEBUG)
18 #endif /* defined(MAIN) && defined(MEMORY_DEBUG) */
29 #include <sys/types.h>
31 #include <sys/param.h>
40 * here, we make a definition for the externally accessible function
41 * in this file (this definition is required for static a module
42 * but strongly encouraged generally) it is used to instruct the
43 * modules include file to define the function prototypes.
47 #define PAM_SM_ACCOUNT
48 /* #define PAM_SM_SESSION */
49 /* #define PAM_SM_PASSWORD */
51 #include <security/pam_modules.h>
52 #include <security/_pam_modutil.h>
54 /*---------------------------------------------------------------------*/
56 #define DEFAULT_LOGFILE "/var/log/faillog"
57 #define MODULE_NAME "pam_tally"
65 #define tally_t unsigned short int
66 #define TALLY_FMT "%hu"
67 #define TALLY_HI ((tally_t)~0L)
72 # define FILENAME_MAX MAXPATHLEN
76 struct faillog fs_faillog;
79 #endif /* ndef MAIN */
82 /*---------------------------------------------------------------------*/
86 static void _pam_log(int err, const char *format, ...)
89 va_start(args, format);
92 vfprintf(stderr,format,args);
95 openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
96 vsyslog(err, format, args);
102 /*---------------------------------------------------------------------*/
104 /* --- Support function: get uid (and optionally username) from PAM or
108 static char *cline_user=0; /* cline_user is used in the administration prog */
111 static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp )
113 const char *user = NULL;
119 pam_get_user( pamh, &user, NULL );
122 if ( !user || !*user ) {
123 _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?");
127 if ( ! ( pw = _pammodutil_getpwnam( pamh, user ) ) ) {
128 _pam_log(LOG_ERR,MODULE_NAME ": pam_get_uid; no such user %s",user);
129 return PAM_USER_UNKNOWN;
132 if ( uid ) *uid = pw->pw_uid;
133 if ( userp ) *userp = user;
137 /*---------------------------------------------------------------------*/
139 /* --- Support function: open/create tallyfile and return tally for uid --- */
141 /* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */
142 /* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */
144 static int get_tally( tally_t *tally,
146 const char *filename,
150 struct stat fileinfo;
151 int lstat_ret = lstat(filename,&fileinfo);
153 if ( lstat_ret && *tally!=TALLY_HI ) {
154 int oldmask = umask(077);
155 *TALLY=fopen(filename, "a");
156 /* Create file, or append-open in pathological case. */
159 _pam_log(LOG_ALERT, "Couldn't create %s",filename);
162 lstat_ret = fstat(fileno(*TALLY),&fileinfo);
167 _pam_log(LOG_ALERT, "Couldn't stat %s",filename);
171 if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) {
172 /* If the file is world writable or is not a
173 normal file, return error */
175 "%s is either world writable or not a normal file",
180 if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) {
181 _pam_log(LOG_ALERT, "Error opening %s for update", filename);
183 /* Discovering why account service fails: e/uid are target user.
185 * perror(MODULE_NAME);
186 * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid());
191 if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
192 _pam_log(LOG_ALERT, "fseek failed %s", filename);
197 if ( fileinfo.st_size <= uid * sizeof(struct faillog) ) {
199 memset(fsp, 0, sizeof(struct faillog));
201 fsp->fs_faillog.fail_time = time(NULL);
203 } else if (( fread((char *) &fsp->fs_faillog,
204 sizeof(struct faillog), 1, *TALLY) )==0 ) {
206 *tally=0; /* Assuming a gappy filesystem */
210 *tally = fsp->fs_faillog.fail_cnt;
217 /*---------------------------------------------------------------------*/
219 /* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
221 static int set_tally( tally_t tally,
223 const char *filename,
227 if ( tally!=TALLY_HI )
229 if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
230 _pam_log(LOG_ALERT, "fseek failed %s", filename);
233 fsp->fs_faillog.fail_cnt = tally;
234 if (fwrite((char *) &fsp->fs_faillog,
235 sizeof(struct faillog), 1, *TALLY)==0 ) {
236 _pam_log(LOG_ALERT, "tally update (fwrite) failed.", filename);
241 if ( fclose(*TALLY) ) {
242 _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename);
249 /*---------------------------------------------------------------------*/
251 /* --- PAM bits --- */
255 #define PAM_FUNCTION(name) \
256 PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv)
258 #define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS))
260 /*---------------------------------------------------------------------*/
262 /* --- tally bump function: bump tally for uid by (signed) inc --- */
264 static int tally_bump (int inc,
272 fail_on_error = FALSE;
274 tally = 0; /* !TALLY_HI --> Log opened for update */
277 no_magic_root = FALSE;
280 filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
282 /* Should probably decode the parameters before anything else. */
285 for ( ; argc-- > 0; ++argv ) {
287 /* generic options.. um, ignored. :] */
289 if ( ! strcmp( *argv, "no_magic_root" ) ) {
290 no_magic_root = TRUE;
292 else if ( ! strncmp( *argv, "file=", 5 ) ) {
297 if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) {
299 MODULE_NAME ": filename not /rooted or too long; ",
301 RETURN_ERROR( PAM_AUTH_ERR );
303 while ( ( *to++ = *from++ ) );
305 else if ( ! strcmp( *argv, "onerr=fail" ) ) {
308 else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
312 _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
324 struct fail_s fs, *fsp = &fs;
326 int i=pam_get_uid(pamh, &uid, &user);
327 if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
329 i=get_tally( &tally, uid, filename, &TALLY, fsp );
331 /* to remember old fail time (for locktime) */
332 fsp->fs_fail_time = fsp->fs_faillog.fail_time;
333 fsp->fs_faillog.fail_time = time(NULL);
334 (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host);
337 (void) pam_get_item(pamh, PAM_TTY, (const void **)&cur_tty);
339 strncpy(fsp->fs_faillog.fail_line, "unknown",
340 sizeof(fsp->fs_faillog.fail_line) - 1);
341 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
343 strncpy(fsp->fs_faillog.fail_line, cur_tty,
344 sizeof(fsp->fs_faillog.fail_line)-1);
345 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
349 strncpy(fsp->fs_faillog.fail_line, remote_host,
350 (size_t)sizeof(fsp->fs_faillog.fail_line));
351 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
353 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
355 if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
359 if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */
361 _pam_log(LOG_ALERT,"Tally %sflowed for user %s",
362 (inc<0)?"under":"over",user);
366 i=set_tally( tally, uid, filename, &TALLY, fsp );
367 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
373 /*---------------------------------------------------------------------*/
375 /* --- authentication management functions (only) --- */
379 PAM_FUNCTION( pam_sm_authenticate ) {
380 return tally_bump( 1, pamh, flags, argc, argv);
383 /* --- Seems to need this function. Ho hum. --- */
385 PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; }
389 /*---------------------------------------------------------------------*/
391 /* --- session management functions (only) --- */
394 * Unavailable until .so files can be suid
397 #ifdef PAM_SM_SESSION
399 /* To maintain a balance-tally of successful login/outs */
401 PAM_FUNCTION( pam_sm_open_session ) {
402 return tally_bump( 1, pamh, flags, argc, argv);
405 PAM_FUNCTION( pam_sm_close_session ) {
406 return tally_bump(-1, pamh, flags, argc, argv);
411 /*---------------------------------------------------------------------*/
413 /* --- authentication management functions (only) --- */
417 /* To lock out a user with an unacceptably high tally */
419 PAM_FUNCTION( pam_sm_acct_mgmt ) {
424 fail_on_error = FALSE;
428 tally = 0; /* !TALLY_HI --> Log opened for update */
431 no_magic_root = FALSE,
432 even_deny_root_account = FALSE;
433 char per_user = FALSE; /* if true then deny=.fail_max for user */
434 char no_lock_time = FALSE; /* if true then don't use .fail_locktime */
440 reset = TALLY_RESET_DEFAULT;
443 filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
445 /* Should probably decode the parameters before anything else. */
448 for ( ; argc-- > 0; ++argv ) {
450 /* generic options.. um, ignored. :] */
452 if ( ! strcmp( *argv, "no_magic_root" ) ) {
453 no_magic_root = TRUE;
455 else if ( ! strcmp( *argv, "even_deny_root_account" ) ) {
456 even_deny_root_account = TRUE;
458 else if ( ! strcmp( *argv, "reset" ) ) {
459 reset = TALLY_RESET_RESET;
461 else if ( ! strcmp( *argv, "no_reset" ) ) {
462 reset = TALLY_RESET_NO_RESET;
464 else if ( ! strncmp( *argv, "file=", 5 ) ) {
469 if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) {
471 MODULE_NAME ": filename not /rooted or too long; ",
473 RETURN_ERROR( PAM_AUTH_ERR );
475 while ( ( *to++ = *from++ ) );
477 else if ( ! strncmp( *argv, "deny=", 5 ) ) {
478 if ( sscanf((*argv)+5,TALLY_FMT,&deny) != 1 ) {
479 _pam_log(LOG_ERR,"bad number supplied; %s",*argv);
480 RETURN_ERROR( PAM_AUTH_ERR );
483 else if ( ! strcmp( *argv, "onerr=fail" ) ) {
486 else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
489 else if ( ! strcmp( *argv, "per_user" ) )
493 else if ( ! strcmp( *argv, "no_lock_time") )
498 _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
504 struct fail_s fs, *fsp = &fs;
506 int i=pam_get_uid(pamh, &uid, &user);
507 if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
509 i=get_tally( &tally, uid, filename, &TALLY, fsp );
510 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
512 if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
514 /* To deny or not to deny; that is the question */
516 /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */
518 if ( (fsp->fs_faillog.fail_max) && (per_user) ) {
519 deny = fsp->fs_faillog.fail_max;
521 if (fsp->fs_faillog.fail_locktime && fsp->fs_fail_time
524 if ( (fsp->fs_faillog.fail_locktime + fsp->fs_fail_time) > time(NULL) )
527 "user %s ("UID_FMT") has time limit [%lds left]"
528 " since last failure.",
530 fsp->fs_fail_time+fsp->fs_faillog.fail_locktime
538 ( deny != 0 ) && /* deny==0 means no deny */
539 ( tally > deny ) && /* tally>deny means exceeded */
540 ( even_deny_root_account || uid ) /* even_deny stops uid check */
542 _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT,
543 user, uid, tally, deny);
546 return PAM_AUTH_ERR; /* Only unconditional failure */
549 /* resets for explicit reset
550 * or by default if deny exists and not magic-root
553 if ( ( reset == TALLY_RESET_RESET ) ||
554 ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; }
556 else /* is magic root */ {
558 /* Magic root skips deny test... */
560 /* Magic root only resets on explicit reset, regardless of deny */
562 if ( reset == TALLY_RESET_RESET ) { tally=0; }
566 fsp->fs_faillog.fail_time = (time_t) 0;
567 strcpy(fsp->fs_faillog.fail_line, "");
569 i=set_tally( tally, uid, filename, &TALLY, fsp );
570 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
576 #endif /* #ifdef PAM_SM_AUTH */
578 /*-----------------------------------------------------------------------*/
582 /* static module data */
584 struct pam_module _pam_tally_modstruct = {
593 #ifdef PAM_SM_ACCOUNT
598 #ifdef PAM_SM_SESSION
600 pam_sm_close_session,
605 #ifdef PAM_SM_PASSWORD
612 #endif /* #ifdef PAM_STATIC */
614 /*-----------------------------------------------------------------------*/
616 #else /* #ifndef MAIN */
618 static const char *cline_filename = DEFAULT_LOGFILE;
619 static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */
620 static int cline_quiet = 0;
623 * Not going to link with pamlib just for these.. :)
626 static const char * pam_errors( int i ) {
628 case PAM_AUTH_ERR: return "Authentication error";
629 case PAM_SERVICE_ERR: return "Service error";
630 case PAM_USER_UNKNOWN: return "Unknown user";
631 default: return "Unknown error";
635 static int getopts( int argc, char **argv ) {
636 const char *pname = *argv;
637 for ( ; *argv ; (void)(*argv && ++argv) ) {
638 if ( !strcmp (*argv,"--file") ) cline_filename=*++argv;
639 else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
640 else if ( !strcmp (*argv,"--user") ) cline_user=*++argv;
641 else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
642 else if ( !strcmp (*argv,"--reset") ) cline_reset=0;
643 else if ( !strncmp(*argv,"--reset=",8)) {
644 if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 )
645 fprintf(stderr,"%s: Bad number given to --reset=\n",pname), exit(0);
647 else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1;
649 fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv);
656 int main ( int argc, char **argv ) {
658 struct fail_s fs, *fsp = &fs;
660 if ( ! getopts( argc, argv+1 ) ) {
661 printf("%s: [--file rooted-filename] [--user username] "
662 "[--reset[=n]] [--quiet]\n",
670 * Major difference between individual user and all users:
671 * --user just handles one user, just like PAM.
672 * --user=* handles all users, sniffing cline_filename for nonzeros
677 tally_t tally=cline_reset;
679 int i=pam_get_uid( NULL, &uid, NULL);
680 if ( i != PAM_SUCCESS ) {
681 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
685 i=get_tally( &tally, uid, cline_filename, &TALLY, fsp );
686 if ( i != PAM_SUCCESS ) {
687 if (TALLY) fclose(TALLY);
688 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
693 printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid,
694 (cline_reset!=TALLY_HI)?"had":"has",tally);
696 i=set_tally( cline_reset, uid, cline_filename, &TALLY, fsp );
697 if ( i != PAM_SUCCESS ) {
698 if (TALLY) fclose(TALLY);
699 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
703 else /* !cline_user (ie, operate on all users) */ {
704 FILE *TALLY=fopen(cline_filename, "r");
706 if ( !TALLY ) perror(*argv), exit(0);
708 for ( ; !feof(TALLY); uid++ ) {
711 if ( ! fread((char *) &fsp->fs_faillog,
712 sizeof (struct faillog), 1, TALLY)
713 || ! fsp->fs_faillog.fail_cnt ) {
716 tally = fsp->fs_faillog.fail_cnt;
718 if ( ( pw=getpwuid(uid) ) ) {
719 printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",pw->pw_name,uid,
720 (cline_reset!=TALLY_HI)?"had":"has",tally);
723 printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid,
724 (cline_reset!=TALLY_HI)?"had":"has",tally);
728 if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
729 fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv);
731 else if ( !cline_reset ) {
732 TALLY=fopen(cline_filename, "w");
733 if ( !TALLY ) perror(*argv), exit(0);