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>
53 /*---------------------------------------------------------------------*/
55 #define DEFAULT_LOGFILE "/var/log/faillog"
56 #define MODULE_NAME "pam_tally"
64 #define tally_t unsigned short int
65 #define TALLY_FMT "%hu"
66 #define TALLY_HI ((tally_t)~0L)
71 # define FILENAME_MAX MAXPATHLEN
75 struct faillog fs_faillog;
78 #endif /* ndef MAIN */
81 /*---------------------------------------------------------------------*/
85 static void _pam_log(int err, const char *format, ...)
88 va_start(args, format);
91 vfprintf(stderr,format,args);
94 openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
95 vsyslog(err, format, args);
101 /*---------------------------------------------------------------------*/
103 /* --- Support function: get uid (and optionally username) from PAM or
107 static char *cline_user=0; /* cline_user is used in the administration prog */
110 static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp )
112 const char *user = NULL;
118 pam_get_user( pamh, &user, NULL );
121 if ( !user || !*user ) {
122 _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?");
126 if ( ! ( pw = getpwnam( user ) ) ) {
127 _pam_log(LOG_ERR,MODULE_NAME ": pam_get_uid; no such user %s",user);
128 return PAM_USER_UNKNOWN;
131 if ( uid ) *uid = pw->pw_uid;
132 if ( userp ) *userp = user;
136 /*---------------------------------------------------------------------*/
138 /* --- Support function: open/create tallyfile and return tally for uid --- */
140 /* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */
141 /* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */
143 static int get_tally( tally_t *tally,
145 const char *filename,
149 struct stat fileinfo;
150 int lstat_ret = lstat(filename,&fileinfo);
152 if ( lstat_ret && *tally!=TALLY_HI ) {
153 int oldmask = umask(077);
154 *TALLY=fopen(filename, "a");
155 /* Create file, or append-open in pathological case. */
158 _pam_log(LOG_ALERT, "Couldn't create %s",filename);
161 lstat_ret = fstat(fileno(*TALLY),&fileinfo);
166 _pam_log(LOG_ALERT, "Couldn't stat %s",filename);
170 if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) {
171 /* If the file is world writable or is not a
172 normal file, return error */
174 "%s is either world writable or not a normal file",
179 if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) {
180 _pam_log(LOG_ALERT, "Error opening %s for update", filename);
182 /* Discovering why account service fails: e/uid are target user.
184 * perror(MODULE_NAME);
185 * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid());
190 if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
191 _pam_log(LOG_ALERT, "fseek failed %s", filename);
196 if ( fileinfo.st_size <= uid * sizeof(struct faillog) ) {
198 memset(fsp, 0, sizeof(struct faillog));
200 fsp->fs_faillog.fail_time = time(NULL);
202 } else if (( fread((char *) &fsp->fs_faillog,
203 sizeof(struct faillog), 1, *TALLY) )==0 ) {
205 *tally=0; /* Assuming a gappy filesystem */
209 *tally = fsp->fs_faillog.fail_cnt;
216 /*---------------------------------------------------------------------*/
218 /* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
220 static int set_tally( tally_t tally,
222 const char *filename,
226 if ( tally!=TALLY_HI )
228 if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
229 _pam_log(LOG_ALERT, "fseek failed %s", filename);
232 fsp->fs_faillog.fail_cnt = tally;
233 if (fwrite((char *) &fsp->fs_faillog,
234 sizeof(struct faillog), 1, *TALLY)==0 ) {
235 _pam_log(LOG_ALERT, "tally update (fwrite) failed.", filename);
240 if ( fclose(*TALLY) ) {
241 _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename);
248 /*---------------------------------------------------------------------*/
250 /* --- PAM bits --- */
254 #define PAM_FUNCTION(name) \
255 PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv)
257 #define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS))
259 /*---------------------------------------------------------------------*/
261 /* --- tally bump function: bump tally for uid by (signed) inc --- */
263 static int tally_bump (int inc,
271 fail_on_error = FALSE;
273 tally = 0; /* !TALLY_HI --> Log opened for update */
276 no_magic_root = FALSE;
279 filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
281 /* Should probably decode the parameters before anything else. */
284 for ( ; argc-- > 0; ++argv ) {
286 /* generic options.. um, ignored. :] */
288 if ( ! strcmp( *argv, "no_magic_root" ) ) {
289 no_magic_root = TRUE;
291 else if ( ! strncmp( *argv, "file=", 5 ) ) {
296 if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) {
298 MODULE_NAME ": filename not /rooted or too long; ",
300 RETURN_ERROR( PAM_AUTH_ERR );
302 while ( ( *to++ = *from++ ) );
304 else if ( ! strcmp( *argv, "onerr=fail" ) ) {
307 else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
311 _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
323 struct fail_s fs, *fsp = &fs;
325 int i=pam_get_uid(pamh, &uid, &user);
326 if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
328 i=get_tally( &tally, uid, filename, &TALLY, fsp );
330 /* to remember old fail time (for locktime) */
331 fsp->fs_fail_time = fsp->fs_faillog.fail_time;
332 fsp->fs_faillog.fail_time = time(NULL);
333 (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host);
336 (void) pam_get_item(pamh, PAM_TTY, (const void **)&cur_tty);
338 strncpy(fsp->fs_faillog.fail_line, "unknown",
339 sizeof(fsp->fs_faillog.fail_line) - 1);
340 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
342 strncpy(fsp->fs_faillog.fail_line, cur_tty,
343 sizeof(fsp->fs_faillog.fail_line)-1);
344 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
348 strncpy(fsp->fs_faillog.fail_line, remote_host,
349 (size_t)sizeof(fsp->fs_faillog.fail_line));
350 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
352 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
354 if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
358 if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */
360 _pam_log(LOG_ALERT,"Tally %sflowed for user %s",
361 (inc<0)?"under":"over",user);
365 i=set_tally( tally, uid, filename, &TALLY, fsp );
366 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
372 /*---------------------------------------------------------------------*/
374 /* --- authentication management functions (only) --- */
378 PAM_FUNCTION( pam_sm_authenticate ) {
379 return tally_bump( 1, pamh, flags, argc, argv);
382 /* --- Seems to need this function. Ho hum. --- */
384 PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; }
388 /*---------------------------------------------------------------------*/
390 /* --- session management functions (only) --- */
393 * Unavailable until .so files can be suid
396 #ifdef PAM_SM_SESSION
398 /* To maintain a balance-tally of successful login/outs */
400 PAM_FUNCTION( pam_sm_open_session ) {
401 return tally_bump( 1, pamh, flags, argc, argv);
404 PAM_FUNCTION( pam_sm_close_session ) {
405 return tally_bump(-1, pamh, flags, argc, argv);
410 /*---------------------------------------------------------------------*/
412 /* --- authentication management functions (only) --- */
416 /* To lock out a user with an unacceptably high tally */
418 PAM_FUNCTION( pam_sm_acct_mgmt ) {
423 fail_on_error = FALSE;
427 tally = 0; /* !TALLY_HI --> Log opened for update */
430 no_magic_root = FALSE,
431 even_deny_root_account = FALSE;
432 char per_user = FALSE; /* if true then deny=.fail_max for user */
433 char no_lock_time = FALSE; /* if true then don't use .fail_locktime */
439 reset = TALLY_RESET_DEFAULT;
442 filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
444 /* Should probably decode the parameters before anything else. */
447 for ( ; argc-- > 0; ++argv ) {
449 /* generic options.. um, ignored. :] */
451 if ( ! strcmp( *argv, "no_magic_root" ) ) {
452 no_magic_root = TRUE;
454 else if ( ! strcmp( *argv, "even_deny_root_account" ) ) {
455 even_deny_root_account = TRUE;
457 else if ( ! strcmp( *argv, "reset" ) ) {
458 reset = TALLY_RESET_RESET;
460 else if ( ! strcmp( *argv, "no_reset" ) ) {
461 reset = TALLY_RESET_NO_RESET;
463 else if ( ! strncmp( *argv, "file=", 5 ) ) {
468 if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) {
470 MODULE_NAME ": filename not /rooted or too long; ",
472 RETURN_ERROR( PAM_AUTH_ERR );
474 while ( ( *to++ = *from++ ) );
476 else if ( ! strncmp( *argv, "deny=", 5 ) ) {
477 if ( sscanf((*argv)+5,TALLY_FMT,&deny) != 1 ) {
478 _pam_log(LOG_ERR,"bad number supplied; %s",*argv);
479 RETURN_ERROR( PAM_AUTH_ERR );
482 else if ( ! strcmp( *argv, "onerr=fail" ) ) {
485 else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
488 else if ( ! strcmp( *argv, "per_user" ) )
492 else if ( ! strcmp( *argv, "no_lock_time") )
497 _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
503 struct fail_s fs, *fsp = &fs;
505 int i=pam_get_uid(pamh, &uid, &user);
506 if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
508 i=get_tally( &tally, uid, filename, &TALLY, fsp );
509 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
511 if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */
513 /* To deny or not to deny; that is the question */
515 /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */
517 if ( (fsp->fs_faillog.fail_max) && (per_user) ) {
518 deny = fsp->fs_faillog.fail_max;
520 if (fsp->fs_faillog.fail_locktime && fsp->fs_fail_time
523 if ( (fsp->fs_faillog.fail_locktime + fsp->fs_fail_time) > time(NULL) )
526 "user %s ("UID_FMT") has time limit [%lds left]"
527 " since last failure.",
529 fsp->fs_fail_time+fsp->fs_faillog.fail_locktime
537 ( deny != 0 ) && /* deny==0 means no deny */
538 ( tally > deny ) && /* tally>deny means exceeded */
539 ( even_deny_root_account || uid ) /* even_deny stops uid check */
541 _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT,
542 user, uid, tally, deny);
545 return PAM_AUTH_ERR; /* Only unconditional failure */
548 /* resets for explicit reset
549 * or by default if deny exists and not magic-root
552 if ( ( reset == TALLY_RESET_RESET ) ||
553 ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; }
555 else /* is magic root */ {
557 /* Magic root skips deny test... */
559 /* Magic root only resets on explicit reset, regardless of deny */
561 if ( reset == TALLY_RESET_RESET ) { tally=0; }
565 fsp->fs_faillog.fail_time = (time_t) 0;
566 strcpy(fsp->fs_faillog.fail_line, "");
568 i=set_tally( tally, uid, filename, &TALLY, fsp );
569 if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
575 #endif /* #ifdef PAM_SM_AUTH */
577 /*-----------------------------------------------------------------------*/
581 /* static module data */
583 struct pam_module _pam_tally_modstruct = {
592 #ifdef PAM_SM_ACCOUNT
597 #ifdef PAM_SM_SESSION
599 pam_sm_close_session,
604 #ifdef PAM_SM_PASSWORD
611 #endif /* #ifdef PAM_STATIC */
613 /*-----------------------------------------------------------------------*/
615 #else /* #ifndef MAIN */
617 static const char *cline_filename = DEFAULT_LOGFILE;
618 static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */
619 static int cline_quiet = 0;
622 * Not going to link with pamlib just for these.. :)
625 static const char * pam_errors( int i ) {
627 case PAM_AUTH_ERR: return "Authentication error";
628 case PAM_SERVICE_ERR: return "Service error";
629 case PAM_USER_UNKNOWN: return "Unknown user";
630 default: return "Unknown error";
634 static int getopts( int argc, char **argv ) {
635 const char *pname = *argv;
636 for ( ; *argv ; (void)(*argv && ++argv) ) {
637 if ( !strcmp (*argv,"--file") ) cline_filename=*++argv;
638 else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
639 else if ( !strcmp (*argv,"--user") ) cline_user=*++argv;
640 else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
641 else if ( !strcmp (*argv,"--reset") ) cline_reset=0;
642 else if ( !strncmp(*argv,"--reset=",8)) {
643 if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 )
644 fprintf(stderr,"%s: Bad number given to --reset=\n",pname), exit(0);
646 else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1;
648 fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv);
655 int main ( int argc, char **argv ) {
657 struct fail_s fs, *fsp = &fs;
659 if ( ! getopts( argc, argv+1 ) ) {
660 printf("%s: [--file rooted-filename] [--user username] "
661 "[--reset[=n]] [--quiet]\n",
669 * Major difference between individual user and all users:
670 * --user just handles one user, just like PAM.
671 * --user=* handles all users, sniffing cline_filename for nonzeros
676 tally_t tally=cline_reset;
678 int i=pam_get_uid( NULL, &uid, NULL);
679 if ( i != PAM_SUCCESS ) {
680 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
684 i=get_tally( &tally, uid, cline_filename, &TALLY, fsp );
685 if ( i != PAM_SUCCESS ) {
686 if (TALLY) fclose(TALLY);
687 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
692 printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid,
693 (cline_reset!=TALLY_HI)?"had":"has",tally);
695 i=set_tally( cline_reset, uid, cline_filename, &TALLY, fsp );
696 if ( i != PAM_SUCCESS ) {
697 if (TALLY) fclose(TALLY);
698 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
702 else /* !cline_user (ie, operate on all users) */ {
703 FILE *TALLY=fopen(cline_filename, "r");
705 if ( !TALLY ) perror(*argv), exit(0);
707 for ( ; !feof(TALLY); uid++ ) {
710 if ( ! fread((char *) &fsp->fs_faillog,
711 sizeof (struct faillog), 1, TALLY)
712 || ! fsp->fs_faillog.fail_cnt ) {
715 tally = fsp->fs_faillog.fail_cnt;
717 if ( ( pw=getpwuid(uid) ) ) {
718 printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",pw->pw_name,uid,
719 (cline_reset!=TALLY_HI)?"had":"has",tally);
722 printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid,
723 (cline_reset!=TALLY_HI)?"had":"has",tally);
727 if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
728 fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv);
730 else if ( !cline_reset ) {
731 TALLY=fopen(cline_filename, "w");
732 if ( !TALLY ) perror(*argv), exit(0);