]> granicus.if.org Git - linux-pam/blob - modules/pam_tally/pam_tally.c
Relevant BUGIDs:
[linux-pam] / modules / pam_tally / pam_tally.c
1 /*
2  * pam_tally.c
3  * 
4  * $Id$
5  */
6
7
8 /* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
9  * 5 March 1997
10  *
11  * Stuff stolen from pam_rootok and pam_listfile
12  */
13
14 #include <security/_pam_aconf.h>
15
16 #if defined(MAIN) && defined(MEMORY_DEBUG)
17 # undef exit
18 #endif /* defined(MAIN) && defined(MEMORY_DEBUG) */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <syslog.h>
26 #include <pwd.h>
27 #include <time.h>
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/param.h>
32 #include "faillog.h"
33
34 #ifndef TRUE
35 #define TRUE  1L
36 #define FALSE 0L
37 #endif
38
39 /*
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.
44  */
45
46 #define PAM_SM_AUTH
47 #define PAM_SM_ACCOUNT
48 /* #define PAM_SM_SESSION  */
49 /* #define PAM_SM_PASSWORD */
50
51 #include <security/pam_modules.h>
52
53 /*---------------------------------------------------------------------*/
54
55 #define DEFAULT_LOGFILE "/var/log/faillog"
56 #define MODULE_NAME     "pam_tally"
57
58 enum TALLY_RESET {
59   TALLY_RESET_DEFAULT,
60   TALLY_RESET_RESET,
61   TALLY_RESET_NO_RESET
62 };
63
64 #define tally_t    unsigned short int
65 #define TALLY_FMT  "%hu"
66 #define TALLY_HI   ((tally_t)~0L)
67
68 #define UID_FMT    "%hu"
69
70 #ifndef FILENAME_MAX
71 # define FILENAME_MAX MAXPATHLEN
72 #endif
73
74 struct fail_s {
75     struct faillog fs_faillog;
76 #ifndef MAIN
77     time_t fs_fail_time;
78 #endif /* ndef MAIN */
79 };
80
81 /*---------------------------------------------------------------------*/
82
83 /* some syslogging */
84
85 static void _pam_log(int err, const char *format, ...)
86 {
87     va_list args;
88     va_start(args, format);
89
90 #ifdef MAIN
91     vfprintf(stderr,format,args);
92     fprintf(stderr,"\n");
93 #else
94     openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
95     vsyslog(err, format, args);
96     closelog();
97 #endif
98     va_end(args);
99 }
100
101 /*---------------------------------------------------------------------*/
102
103 /* --- Support function: get uid (and optionally username) from PAM or
104         cline_user --- */
105
106 #ifdef MAIN
107 static char *cline_user=0;  /* cline_user is used in the administration prog */
108 #endif
109
110 static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp ) 
111   {
112     const char *user = NULL;
113     struct passwd *pw;
114
115 #ifdef MAIN
116     user = cline_user;
117 #else
118     pam_get_user( pamh, &user, NULL );
119 #endif
120
121     if ( !user || !*user ) {
122       _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?");
123       return PAM_AUTH_ERR;
124     }
125
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;
129     }
130     
131     if ( uid )   *uid   = pw->pw_uid;
132     if ( userp ) *userp = user;
133     return PAM_SUCCESS;
134   }
135
136 /*---------------------------------------------------------------------*/
137
138 /* --- Support function: open/create tallyfile and return tally for uid --- */
139
140 /* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */
141 /* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */
142
143 static int get_tally( tally_t *tally, 
144                               uid_t uid, 
145                               const char *filename, 
146                               FILE **TALLY,
147                               struct fail_s *fsp) 
148   {
149     struct stat fileinfo;
150     int lstat_ret = lstat(filename,&fileinfo);
151
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. */
156       umask(oldmask);
157       if ( !*TALLY ) {
158         _pam_log(LOG_ALERT, "Couldn't create %s",filename);
159         return PAM_AUTH_ERR;
160       }
161       lstat_ret = fstat(fileno(*TALLY),&fileinfo);
162       fclose(*TALLY);
163     }
164
165     if ( lstat_ret ) {
166       _pam_log(LOG_ALERT, "Couldn't stat %s",filename);
167       return PAM_AUTH_ERR;
168     }
169
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 */
173       _pam_log(LOG_ALERT,
174                "%s is either world writable or not a normal file",
175                filename);
176       return PAM_AUTH_ERR;
177     }
178
179     if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) {
180       _pam_log(LOG_ALERT, "Error opening %s for update", filename);
181
182 /* Discovering why account service fails: e/uid are target user.
183  *
184  *      perror(MODULE_NAME);
185  *      fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid());
186  */
187       return PAM_AUTH_ERR;
188     }
189
190     if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
191           _pam_log(LOG_ALERT, "fseek failed %s", filename);
192           fclose(*TALLY);
193           return PAM_AUTH_ERR;
194     }
195                     
196     if ( fileinfo.st_size <= uid * sizeof(struct faillog) ) {
197
198         memset(fsp, 0, sizeof(struct faillog));
199         *tally=0;
200         fsp->fs_faillog.fail_time = time(NULL);
201
202     } else if (( fread((char *) &fsp->fs_faillog,
203                        sizeof(struct faillog), 1, *TALLY) )==0 ) {
204
205         *tally=0; /* Assuming a gappy filesystem */
206
207     } else {
208
209         *tally = fsp->fs_faillog.fail_cnt;
210
211     }
212               
213     return PAM_SUCCESS;
214   }
215
216 /*---------------------------------------------------------------------*/
217
218 /* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
219
220 static int set_tally( tally_t tally, 
221                               uid_t uid,
222                               const char *filename, 
223                               FILE **TALLY,
224                               struct fail_s *fsp) 
225   {
226     if ( tally!=TALLY_HI ) 
227       {
228         if ( fseek( *TALLY, uid * sizeof(struct faillog), SEEK_SET ) ) {
229                   _pam_log(LOG_ALERT, "fseek failed %s", filename);
230                             return PAM_AUTH_ERR;
231         }
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);
236             return PAM_AUTH_ERR;
237         }
238       }
239     
240     if ( fclose(*TALLY) ) {
241       _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename);
242       return PAM_AUTH_ERR;
243     }
244     *TALLY=NULL;
245     return PAM_SUCCESS;
246   }
247
248 /*---------------------------------------------------------------------*/
249
250 /* --- PAM bits --- */
251
252 #ifndef MAIN
253
254 #define PAM_FUNCTION(name) \
255  PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv)
256
257 #define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS))
258
259 /*---------------------------------------------------------------------*/
260
261 /* --- tally bump function: bump tally for uid by (signed) inc --- */
262
263 static int tally_bump (int inc,
264                            pam_handle_t *pamh,
265                            int flags,
266                            int argc,
267                            const char **argv) {
268   uid_t uid;
269
270   int 
271     fail_on_error = FALSE;
272   tally_t
273     tally         = 0;  /* !TALLY_HI --> Log opened for update */
274
275   char
276     no_magic_root          = FALSE;
277
278   char 
279     filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
280
281   /* Should probably decode the parameters before anything else. */
282
283   {
284     for ( ; argc-- > 0; ++argv ) {
285
286       /* generic options.. um, ignored. :] */
287       
288       if ( ! strcmp( *argv, "no_magic_root" ) ) {
289         no_magic_root = TRUE;
290       }
291       else if ( ! strncmp( *argv, "file=", 5 ) ) {
292         char const 
293           *from = (*argv)+5;
294         char
295           *to   = filename;
296         if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) {
297           _pam_log(LOG_ERR,
298                    MODULE_NAME ": filename not /rooted or too long; ",
299                    *argv);
300           RETURN_ERROR( PAM_AUTH_ERR );
301         }
302         while ( ( *to++ = *from++ ) );        
303       }
304       else if ( ! strcmp( *argv, "onerr=fail" ) ) {
305         fail_on_error=TRUE;
306       }
307       else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
308         fail_on_error=FALSE;
309       }
310       else {
311         _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
312       }
313     } /* for() */
314   }
315
316   {
317     FILE
318       *TALLY = NULL;
319     const char
320       *user  = NULL,
321       *remote_host = NULL,
322       *cur_tty = NULL;
323     struct fail_s fs, *fsp = &fs;
324
325     int i=pam_get_uid(pamh, &uid, &user);
326     if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
327
328     i=get_tally( &tally, uid, filename, &TALLY, fsp );
329
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);
334     if (!remote_host) {
335
336         (void) pam_get_item(pamh, PAM_TTY, (const void **)&cur_tty);
337         if (!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;
341         } else {
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;
345         }
346
347     } else {
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;
351     }
352     if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
353     
354     if ( no_magic_root || getuid() ) {       /* no_magic_root kills uid test */
355
356       tally+=inc;
357       
358       if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */
359         tally-=inc;
360         _pam_log(LOG_ALERT,"Tally %sflowed for user %s",
361                  (inc<0)?"under":"over",user);
362       }
363     }
364     
365     i=set_tally( tally, uid, filename, &TALLY, fsp );
366     if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
367   }
368
369   return PAM_SUCCESS;
370
371
372 /*---------------------------------------------------------------------*/
373
374 /* --- authentication management functions (only) --- */
375
376 #ifdef PAM_SM_AUTH
377
378 PAM_FUNCTION( pam_sm_authenticate ) {
379   return tally_bump( 1, pamh, flags, argc, argv);
380 }
381
382 /* --- Seems to need this function. Ho hum. --- */
383
384 PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; }
385
386 #endif
387
388 /*---------------------------------------------------------------------*/
389
390 /* --- session management functions (only) --- */
391
392 /*
393  *  Unavailable until .so files can be suid
394  */
395
396 #ifdef PAM_SM_SESSION
397
398 /* To maintain a balance-tally of successful login/outs */
399
400 PAM_FUNCTION( pam_sm_open_session ) {
401   return tally_bump( 1, pamh, flags, argc, argv);
402 }
403
404 PAM_FUNCTION( pam_sm_close_session ) {
405   return tally_bump(-1, pamh, flags, argc, argv);
406 }
407
408 #endif
409
410 /*---------------------------------------------------------------------*/
411
412 /* --- authentication management functions (only) --- */
413
414 #ifdef PAM_SM_AUTH
415
416 /* To lock out a user with an unacceptably high tally */
417
418 PAM_FUNCTION( pam_sm_acct_mgmt ) {
419   uid_t 
420     uid;
421
422   int 
423     fail_on_error = FALSE;
424   tally_t
425     deny          = 0;
426   tally_t
427     tally         = 0;  /* !TALLY_HI --> Log opened for update */
428
429   char
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 */
434
435   const char
436     *user                  = NULL;
437
438   enum TALLY_RESET 
439     reset         = TALLY_RESET_DEFAULT;
440
441   char 
442     filename[ FILENAME_MAX ] = DEFAULT_LOGFILE;
443
444   /* Should probably decode the parameters before anything else. */
445
446   {
447     for ( ; argc-- > 0; ++argv ) {
448
449       /* generic options.. um, ignored. :] */
450       
451       if ( ! strcmp( *argv, "no_magic_root" ) ) {
452         no_magic_root = TRUE;
453       }
454       else if ( ! strcmp( *argv, "even_deny_root_account" ) ) {
455         even_deny_root_account = TRUE;
456       }
457       else if ( ! strcmp( *argv, "reset" ) ) {
458         reset = TALLY_RESET_RESET;
459       }
460       else if ( ! strcmp( *argv, "no_reset" ) ) {
461         reset = TALLY_RESET_NO_RESET;
462       }
463       else if ( ! strncmp( *argv, "file=", 5 ) ) {
464         char const 
465           *from = (*argv)+5;
466         char
467           *to   = filename;
468         if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) {
469           _pam_log(LOG_ERR,
470                    MODULE_NAME ": filename not /rooted or too long; ",
471                    *argv);
472           RETURN_ERROR( PAM_AUTH_ERR );
473         }
474         while ( ( *to++ = *from++ ) );        
475       }
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 );
480         }
481       }
482       else if ( ! strcmp( *argv, "onerr=fail" ) ) {
483         fail_on_error=TRUE;
484       }
485       else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
486         fail_on_error=FALSE;
487       }
488       else if ( ! strcmp( *argv, "per_user" ) )
489       {
490         per_user = TRUE;
491       }
492       else if ( ! strcmp( *argv, "no_lock_time") )
493       {
494         no_lock_time = TRUE;
495       }
496       else {
497         _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
498       }
499     } /* for() */
500   }
501   
502   {
503     struct fail_s fs, *fsp = &fs;
504     FILE *TALLY=0;
505     int i=pam_get_uid(pamh, &uid, &user);
506     if ( i != PAM_SUCCESS ) RETURN_ERROR( i );
507     
508     i=get_tally( &tally, uid, filename, &TALLY, fsp );
509     if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
510     
511     if ( no_magic_root || getuid() ) {       /* no_magic_root kills uid test */
512       
513       /* To deny or not to deny; that is the question */
514       
515       /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */
516       
517       if ( (fsp->fs_faillog.fail_max) && (per_user) ) {
518           deny = fsp->fs_faillog.fail_max;
519       }
520       if (fsp->fs_faillog.fail_locktime && fsp->fs_fail_time
521           && (!no_lock_time) )
522       {
523         if ( (fsp->fs_faillog.fail_locktime + fsp->fs_fail_time) > time(NULL) )
524         { 
525                 _pam_log(LOG_NOTICE,
526                          "user %s ("UID_FMT") has time limit [%lds left]"
527                          " since last failure.",
528                          user,uid,
529                          fsp->fs_fail_time+fsp->fs_faillog.fail_locktime
530                          -time(NULL));
531                 if (TALLY)
532                         fclose(TALLY);
533                 return PAM_AUTH_ERR;
534         }
535       }
536       if (
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    */
540         ) {
541         _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT,
542                  user, uid, tally, deny);
543                 if (TALLY)
544                         fclose(TALLY);
545         return PAM_AUTH_ERR;                 /* Only unconditional failure   */
546       }
547       
548       /* resets for explicit reset
549        * or by default if deny exists and not magic-root
550        */
551       
552       if ( ( reset == TALLY_RESET_RESET ) ||
553            ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; }
554     }
555     else /* is magic root */ {
556       
557       /* Magic root skips deny test... */
558       
559       /* Magic root only resets on explicit reset, regardless of deny */
560       
561       if ( reset == TALLY_RESET_RESET ) { tally=0; }
562     }
563     if (tally == 0)
564     {
565         fsp->fs_faillog.fail_time = (time_t) 0;
566         strcpy(fsp->fs_faillog.fail_line, "");  
567     }
568     i=set_tally( tally, uid, filename, &TALLY, fsp );
569     if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); }
570   }
571   
572   return PAM_SUCCESS;
573
574
575 #endif  /* #ifdef PAM_SM_AUTH */
576
577 /*-----------------------------------------------------------------------*/
578
579 #ifdef PAM_STATIC
580
581 /* static module data */
582
583 struct pam_module _pam_tally_modstruct = {
584      MODULE_NAME,
585 #ifdef PAM_SM_AUTH
586      pam_sm_authenticate,
587      pam_sm_setcred,
588 #else
589      NULL,
590      NULL,
591 #endif
592 #ifdef PAM_SM_ACCOUNT
593      pam_sm_acct_mgmt,
594 #else
595      NULL,
596 #endif
597 #ifdef PAM_SM_SESSION
598      pam_sm_open_session,
599      pam_sm_close_session,
600 #else
601      NULL,
602      NULL,
603 #endif
604 #ifdef PAM_SM_PASSWORD
605      pam_sm_chauthtok,
606 #else
607      NULL,
608 #endif
609 };
610
611 #endif   /* #ifdef PAM_STATIC */
612
613 /*-----------------------------------------------------------------------*/
614
615 #else   /* #ifndef MAIN */
616
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;
620
621 /*
622  *  Not going to link with pamlib just for these.. :)
623  */
624
625 static const char * pam_errors( int i ) {
626   switch (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";
631   }
632 }
633
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);
645     }
646     else if ( !strcmp (*argv,"--quiet")   ) cline_quiet=1;
647     else {
648       fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv);
649       return FALSE;
650     }
651   }
652   return TRUE;
653 }
654
655 int main ( int argc, char **argv ) {
656
657   struct fail_s fs, *fsp = &fs;
658
659   if ( ! getopts( argc, argv+1 ) ) {
660     printf("%s: [--file rooted-filename] [--user username] "
661            "[--reset[=n]] [--quiet]\n",
662            *argv);
663     exit(0);
664   }
665
666   umask(077);
667
668   /* 
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
672    */
673
674   if ( cline_user ) {
675     uid_t uid;
676     tally_t tally=cline_reset;
677     FILE *TALLY=0;
678     int i=pam_get_uid( NULL, &uid, NULL);
679     if ( i != PAM_SUCCESS ) { 
680       fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
681       exit(0);
682     }
683     
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));
688       exit(0);
689     }
690     
691     if ( !cline_quiet ) 
692       printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid,
693              (cline_reset!=TALLY_HI)?"had":"has",tally);
694     
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));
699       exit(0);
700     }
701   }
702   else /* !cline_user (ie, operate on all users) */ {
703     FILE *TALLY=fopen(cline_filename, "r");
704     uid_t uid=0;
705     if ( !TALLY ) perror(*argv), exit(0);
706     
707     for ( ; !feof(TALLY); uid++ ) {
708       tally_t tally;
709       struct passwd *pw;
710       if ( ! fread((char *) &fsp->fs_faillog,
711                    sizeof (struct faillog), 1, TALLY)
712            || ! fsp->fs_faillog.fail_cnt ) {
713         continue;
714         }
715       tally = fsp->fs_faillog.fail_cnt; 
716       
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);
720       }
721       else {
722         printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid,
723                (cline_reset!=TALLY_HI)?"had":"has",tally);
724       }
725     }
726     fclose(TALLY);
727     if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
728       fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv);
729     }
730     else if ( !cline_reset ) {
731       TALLY=fopen(cline_filename, "w");
732       if ( !TALLY ) perror(*argv), exit(0);
733       fclose(TALLY);
734     }
735   }
736   return 0;
737 }
738
739
740 #endif