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