]> granicus.if.org Git - linux-pam/blob - modules/pam_tally2/pam_tally2.c
Relevant BUGIDs:
[linux-pam] / modules / pam_tally2 / pam_tally2.c
1 /*
2  * pam_tally2.c
3  *
4  */
5
6
7 /* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
8  * 5 March 1997
9  *
10  * Stuff stolen from pam_rootok and pam_listfile
11  *
12  * Changes by Tomas Mraz <tmraz@redhat.com> 5 January 2005, 26 January 2006
13  * Audit option added for Tomas patch by Sebastien Tricaud <toady@gscore.org> 13 January 2005
14  * Portions Copyright 2006, Red Hat, Inc.
15  * Portions Copyright 1989 - 1993, Julianne Frances Haugh
16  * All rights reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  * 1. Redistributions of source code must retain the above copyright
22  *    notice, this list of conditions and the following disclaimer.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
27  *    may be used to endorse or promote products derived from this software
28  *    without specific prior written permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40  * SUCH DAMAGE.
41  */
42
43 #include "config.h"
44
45 #if defined(MAIN) && defined(MEMORY_DEBUG)
46 # undef exit
47 #endif /* defined(MAIN) && defined(MEMORY_DEBUG) */
48
49 #include <stdio.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <stdarg.h>
53 #include <stdlib.h>
54 #include <syslog.h>
55 #include <pwd.h>
56 #include <time.h>
57 #include <stdint.h>
58 #include <errno.h>
59 #ifdef HAVE_LIBAUDIT
60 #include <libaudit.h>
61 #endif
62
63 #include <sys/types.h>
64 #include <sys/stat.h>
65 #include <sys/param.h>
66 #include "tallylog.h"
67
68 #ifndef TRUE
69 #define TRUE  1L
70 #define FALSE 0L
71 #endif
72
73 #ifndef HAVE_FSEEKO
74 #define fseeko fseek
75 #endif
76
77 /*
78  * here, we make a definition for the externally accessible function
79  * in this file (this definition is required for static a module
80  * but strongly encouraged generally) it is used to instruct the
81  * modules include file to define the function prototypes.
82  */
83
84 #ifndef MAIN
85 #define PAM_SM_AUTH
86 #define PAM_SM_ACCOUNT
87 /* #define PAM_SM_SESSION  */
88 /* #define PAM_SM_PASSWORD */
89
90 #include <security/pam_modutil.h>
91 #include <security/pam_ext.h>
92 #endif
93 #include <security/pam_modules.h>
94
95 /*---------------------------------------------------------------------*/
96
97 #define DEFAULT_LOGFILE "/var/log/tallylog"
98 #define MODULE_NAME     "pam_tally2"
99
100 #define tally_t    uint16_t
101 #define TALLY_HI   ((tally_t)~0L)
102
103 struct tally_options {
104     const char *filename;
105     tally_t deny;
106     long lock_time;
107     long unlock_time;
108     long root_unlock_time;
109     unsigned int ctrl;
110 };
111
112 #define PHASE_UNKNOWN 0
113 #define PHASE_AUTH    1
114 #define PHASE_ACCOUNT 2
115 #define PHASE_SESSION 3
116
117 #define OPT_MAGIC_ROOT                    01
118 #define OPT_FAIL_ON_ERROR                 02
119 #define OPT_DENY_ROOT                     04
120 #define OPT_QUIET                        040
121 #define OPT_AUDIT                       0100
122 #define OPT_NOLOGNOTICE                 0400
123
124
125 /*---------------------------------------------------------------------*/
126
127 /* some syslogging */
128
129 #ifdef MAIN
130 #define pam_syslog tally_log
131 static void
132 tally_log (const pam_handle_t *pamh UNUSED, int priority UNUSED,
133             const char *fmt, ...)
134 {
135         va_list args;
136
137         va_start(args, fmt);
138         fprintf(stderr, "%s: ", MODULE_NAME);
139         vfprintf(stderr, fmt, args);
140         fprintf(stderr,"\n");
141         va_end(args);
142 }
143
144 #define pam_modutil_getpwnam(pamh, user) getpwnam(user)
145 #endif
146
147 /*---------------------------------------------------------------------*/
148
149 /* --- Support function: parse arguments --- */
150
151 #ifndef MAIN
152
153 static void
154 log_phase_no_auth(pam_handle_t *pamh, int phase, const char *argv)
155 {
156     if ( phase != PHASE_AUTH ) {
157         pam_syslog(pamh, LOG_ERR,
158                    "option %s allowed in auth phase only", argv);
159     }
160 }
161
162 static int
163 tally_parse_args(pam_handle_t *pamh, struct tally_options *opts,
164                     int phase, int argc, const char **argv)
165 {
166     memset(opts, 0, sizeof(*opts));
167     opts->filename = DEFAULT_LOGFILE;
168     opts->ctrl = OPT_FAIL_ON_ERROR;
169     opts->root_unlock_time = -1;
170
171     for ( ; argc-- > 0; ++argv ) {
172
173       if ( ! strncmp( *argv, "file=", 5 ) ) {
174         const char *from = *argv + 5;
175         if ( *from!='/' ) {
176           pam_syslog(pamh, LOG_ERR,
177                      "filename not /rooted; %s", *argv);
178           return PAM_AUTH_ERR;
179         }
180         opts->filename = from;
181       }
182       else if ( ! strcmp( *argv, "onerr=fail" ) ) {
183         opts->ctrl |= OPT_FAIL_ON_ERROR;
184       }
185       else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
186         opts->ctrl &= ~OPT_FAIL_ON_ERROR;
187       }
188       else if ( ! strcmp( *argv, "magic_root" ) ) {
189         opts->ctrl |= OPT_MAGIC_ROOT;
190       }
191       else if ( ! strcmp( *argv, "even_deny_root_account" ) ||
192                 ! strcmp( *argv, "even_deny_root" ) ) {
193         log_phase_no_auth(pamh, phase, *argv);
194         opts->ctrl |= OPT_DENY_ROOT;
195       }
196       else if ( ! strncmp( *argv, "deny=", 5 ) ) {
197         log_phase_no_auth(pamh, phase, *argv);
198         if ( sscanf((*argv)+5,"%hu",&opts->deny) != 1 ) {
199           pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
200           return PAM_AUTH_ERR;
201         }
202       }
203       else if ( ! strncmp( *argv, "lock_time=", 10 ) ) {
204         log_phase_no_auth(pamh, phase, *argv);
205         if ( sscanf((*argv)+10,"%ld",&opts->lock_time) != 1 ) {
206           pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
207           return PAM_AUTH_ERR;
208         }
209       }
210       else if ( ! strncmp( *argv, "unlock_time=", 12 ) ) {
211         log_phase_no_auth(pamh, phase, *argv);
212         if ( sscanf((*argv)+12,"%ld",&opts->unlock_time) != 1 ) {
213           pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
214           return PAM_AUTH_ERR;
215         }
216       }
217       else if ( ! strncmp( *argv, "root_unlock_time=", 17 ) ) {
218         log_phase_no_auth(pamh, phase, *argv);  
219         if ( sscanf((*argv)+17,"%ld",&opts->root_unlock_time) != 1 ) {
220           pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
221           return PAM_AUTH_ERR;
222         }
223         opts->ctrl |= OPT_DENY_ROOT; /* even_deny_root implied */
224       }
225       else if ( ! strcmp( *argv, "quiet" ) ||
226                 ! strcmp ( *argv, "silent")) {
227         opts->ctrl |= OPT_QUIET;
228       }
229       else if ( ! strcmp ( *argv, "no_log_info") ) {
230         opts->ctrl |= OPT_NOLOGNOTICE;
231       }
232       else if ( ! strcmp ( *argv, "audit") ) {
233         opts->ctrl |= OPT_AUDIT;
234       }
235       else {
236         pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
237       }
238     }
239
240     if (opts->root_unlock_time == -1)
241         opts->root_unlock_time = opts->unlock_time;
242
243     return PAM_SUCCESS;
244 }
245
246 #endif   /* #ifndef MAIN */
247
248 /*---------------------------------------------------------------------*/
249
250 /* --- Support function: get uid (and optionally username) from PAM or
251         cline_user --- */
252
253 #ifdef MAIN
254 static char *cline_user=0;  /* cline_user is used in the administration prog */
255 #endif
256
257 static int
258 pam_get_uid(pam_handle_t *pamh, uid_t *uid, const char **userp, struct tally_options *opts)
259 {
260     const char *user = NULL;
261     struct passwd *pw;
262
263 #ifdef MAIN
264     user = cline_user;
265 #else
266     if ((pam_get_user( pamh, &user, NULL )) != PAM_SUCCESS) {
267       user = NULL;
268     }
269 #endif
270
271     if ( !user || !*user ) {
272       pam_syslog(pamh, LOG_ERR, "pam_get_uid; user?");
273       return PAM_AUTH_ERR;
274     }
275
276     if ( ! ( pw = pam_modutil_getpwnam( pamh, user ) ) ) {
277       opts->ctrl & OPT_AUDIT ?
278               pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user %s", user) :
279               pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user");
280       return PAM_USER_UNKNOWN;
281     }
282
283     if ( uid )   *uid   = pw->pw_uid;
284     if ( userp ) *userp = user;
285     return PAM_SUCCESS;
286 }
287
288 /*---------------------------------------------------------------------*/
289
290 /* --- Support functions: set/get tally data --- */
291
292 #ifndef MAIN
293
294 static void
295 _cleanup(pam_handle_t *pamh UNUSED, void *data, int error_status UNUSED)
296 {
297     free(data);
298 }
299
300
301 static void
302 tally_set_data( pam_handle_t *pamh, time_t oldtime )
303 {
304     time_t *data;
305
306     if ( (data=malloc(sizeof(time_t))) != NULL ) {
307         *data = oldtime;
308         pam_set_data(pamh, MODULE_NAME, (void *)data, _cleanup);
309     }
310 }
311
312 static int
313 tally_get_data( pam_handle_t *pamh, time_t *oldtime )
314 {
315     int rv;
316     const void *data;
317
318     rv = pam_get_data(pamh, MODULE_NAME, &data);
319     if ( rv == PAM_SUCCESS && data != NULL && oldtime != NULL ) {
320       *oldtime = *(const time_t *)data;
321       pam_set_data(pamh, MODULE_NAME, NULL, NULL);
322     }
323     else {
324       rv = -1;
325       *oldtime = 0;
326     }
327     return rv;
328 }
329 #endif   /* #ifndef MAIN */
330
331 /*---------------------------------------------------------------------*/
332
333 /* --- Support function: open/create tallyfile and return tally for uid --- */
334
335 /* If on entry tallyfile doesn't exist, creation is attempted. */
336
337 static int
338 get_tally(pam_handle_t *pamh, uid_t uid, const char *filename,
339         FILE **tfile, struct tallylog *tally)
340 {
341     struct stat fileinfo;
342     int lstat_ret;
343
344     lstat_ret = lstat(filename, &fileinfo);
345     if (lstat_ret) {
346       int save_errno;
347       int oldmask = umask(077);
348       *tfile=fopen(filename, "a");
349       save_errno = errno;
350       /* Create file, or append-open in pathological case. */
351       umask(oldmask);
352       if ( !*tfile ) {
353 #ifndef MAIN
354         if (save_errno == EACCES) {
355             return PAM_IGNORE; /* called with insufficient access rights */
356         }
357 #endif
358         errno = save_errno;
359         pam_syslog(pamh, LOG_ALERT, "Couldn't create %s: %m", filename);
360         return PAM_AUTH_ERR;
361       }
362       lstat_ret = fstat(fileno(*tfile),&fileinfo);
363       fclose(*tfile);
364       *tfile = NULL;
365     }
366
367     if ( lstat_ret ) {
368       pam_syslog(pamh, LOG_ALERT, "Couldn't stat %s", filename);
369       return PAM_AUTH_ERR;
370     }
371
372     if ((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) {
373       /* If the file is world writable or is not a
374          normal file, return error */
375       pam_syslog(pamh, LOG_ALERT,
376                "%s is either world writable or not a normal file",
377                filename);
378       return PAM_AUTH_ERR;
379     }
380
381     if (!(*tfile = fopen(filename, "r+"))) {
382 #ifndef MAIN
383       if (errno == EACCES) /* called with insufficient access rights */
384           return PAM_IGNORE;
385 #endif 
386       pam_syslog(pamh, LOG_ALERT, "Error opening %s for update: %m", filename);
387
388       return PAM_AUTH_ERR;
389     }
390
391     if (fseeko(*tfile, (off_t)uid*(off_t)sizeof(*tally), SEEK_SET)) {
392         pam_syslog(pamh, LOG_ALERT, "fseek failed for %s: %m", filename);
393         fclose(*tfile);
394         *tfile = NULL;
395         return PAM_AUTH_ERR;
396     }
397
398     if (fileinfo.st_size < (off_t)(uid+1)*(off_t)sizeof(*tally)) {
399         memset(tally, 0, sizeof(*tally));
400     } else if (fread(tally, sizeof(*tally), 1, *tfile) == 0) {
401         memset(tally, 0, sizeof(*tally));
402         /* Shouldn't happen */
403     }
404
405     tally->fail_line[sizeof(tally->fail_line)-1] = '\0';
406     
407     return PAM_SUCCESS;
408 }
409
410 /*---------------------------------------------------------------------*/
411
412 /* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
413
414 static int
415 set_tally(pam_handle_t *pamh, uid_t uid,
416           const char *filename, FILE **tfile, struct tallylog *tally)
417 {
418     if (tally->fail_cnt != TALLY_HI) {
419         if (fseeko(*tfile, (off_t)uid * sizeof(*tally), SEEK_SET)) {
420                   pam_syslog(pamh, LOG_ALERT, "fseek failed for %s: %m", filename);
421                             return PAM_AUTH_ERR;
422         }
423         if (fwrite(tally, sizeof(*tally), 1, *tfile) == 0) {
424             pam_syslog(pamh, LOG_ALERT, "update (fwrite) failed for %s: %m", filename);
425             return PAM_AUTH_ERR;
426         }
427     }
428
429     if (fclose(*tfile)) {
430       *tfile = NULL;
431       pam_syslog(pamh, LOG_ALERT, "update (fclose) failed for %s: %m", filename);
432       return PAM_AUTH_ERR;
433     }
434     *tfile=NULL;
435     return PAM_SUCCESS;
436 }
437
438 /*---------------------------------------------------------------------*/
439
440 /* --- PAM bits --- */
441
442 #ifndef MAIN
443
444 #define RETURN_ERROR(i) return ((opts->ctrl & OPT_FAIL_ON_ERROR)?(i):(PAM_SUCCESS))
445
446 /*---------------------------------------------------------------------*/
447
448 static int
449 tally_check (tally_t oldcnt, time_t oldtime, pam_handle_t *pamh, uid_t uid,
450              const char *user, struct tally_options *opts,
451              struct tallylog *tally)
452 {
453     int rv = PAM_SUCCESS;
454 #ifdef HAVE_LIBAUDIT
455     char buf[64];
456     int audit_fd = -1;
457 #endif
458     
459     if ((opts->ctrl & OPT_MAGIC_ROOT) && getuid() == 0) {
460       return PAM_SUCCESS;
461     }
462     /* magic_root skips tally check */
463 #ifdef HAVE_LIBAUDIT
464     audit_fd = audit_open();
465     /* If there is an error & audit support is in the kernel report error */
466     if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
467                             errno == EAFNOSUPPORT))
468          return PAM_SYSTEM_ERR;
469 #endif
470     if (opts->deny != 0 &&                        /* deny==0 means no deny        */
471         tally->fail_cnt > opts->deny &&           /* tally>deny means exceeded    */
472         ((opts->ctrl & OPT_DENY_ROOT) || uid)) {  /* even_deny stops uid check    */
473 #ifdef HAVE_LIBAUDIT
474         if (tally->fail_cnt == opts->deny+1) {
475             /* First say that max number was hit. */
476             snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
477             audit_log_user_message(audit_fd, AUDIT_ANOM_LOGIN_FAILURES, buf,
478                                    NULL, NULL, NULL, 1);
479         }
480 #endif
481         if (uid) {      
482             /* Unlock time check */
483             if (opts->unlock_time && oldtime) {
484                 if (opts->unlock_time + oldtime <= time(NULL))  {
485                     /* ignore deny check after unlock_time elapsed */
486 #ifdef HAVE_LIBAUDIT
487                     snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
488                     audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
489                                    NULL, NULL, NULL, 1);
490 #endif
491                     rv = PAM_SUCCESS;
492                     goto cleanup;
493                 }
494             }
495         } else {
496             /* Root unlock time check */
497             if (opts->root_unlock_time && oldtime) {
498                 if (opts->root_unlock_time + oldtime <= time(NULL)) {
499                     /* ignore deny check after unlock_time elapsed */
500 #ifdef HAVE_LIBAUDIT
501                     snprintf(buf, sizeof(buf), "pam_tally2 uid=%u ", uid);
502                     audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
503                                    NULL, NULL, NULL, 1);
504 #endif
505                     rv = PAM_SUCCESS;
506                     goto cleanup;
507                 }
508             }
509         }
510
511 #ifdef HAVE_LIBAUDIT
512         if (tally->fail_cnt == opts->deny+1) {
513             /* First say that max number was hit. */
514             audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_LOCK, buf,
515                                    NULL, NULL, NULL, 1);
516         }
517 #endif
518
519         if (!(opts->ctrl & OPT_QUIET)) {
520             pam_info(pamh, _("Account locked due to %u failed logins"),
521                     (unsigned int)tally->fail_cnt);
522         }
523         if (!(opts->ctrl & OPT_NOLOGNOTICE)) {
524             pam_syslog(pamh, LOG_NOTICE,
525                    "user %s (%lu) tally %hu, deny %hu",
526                    user, (unsigned long)uid, tally->fail_cnt, opts->deny);
527         }
528         rv = PAM_AUTH_ERR;                 /* Only unconditional failure   */
529         goto cleanup;
530     }
531
532     /* Lock time check */
533     if (opts->lock_time && oldtime) {
534         if (opts->lock_time + oldtime > time(NULL)) {
535             /* don't increase fail_cnt or update fail_time when
536                lock_time applies */
537             tally->fail_cnt = oldcnt;
538             tally->fail_time = oldtime;
539
540             if (!(opts->ctrl & OPT_QUIET)) {
541                 pam_info(pamh, _("Account temporary locked (%ld seconds left)"),
542                          oldtime+opts->lock_time-time(NULL));
543             }
544             if (!(opts->ctrl & OPT_NOLOGNOTICE)) {
545                 pam_syslog(pamh, LOG_NOTICE,
546                        "user %s (%lu) has time limit [%lds left]"
547                        " since last failure.",
548                        user, (unsigned long)uid,
549                        oldtime+opts->lock_time-time(NULL));
550             }
551             rv = PAM_AUTH_ERR;
552             goto cleanup;
553         }
554     }
555
556 cleanup:
557 #ifdef HAVE_LIBAUDIT
558     if (audit_fd != -1) {
559         close(audit_fd);
560     }
561 #endif
562     return rv;
563 }
564
565 /* --- tally bump function: bump tally for uid by (signed) inc --- */
566
567 static int
568 tally_bump (int inc, time_t *oldtime, pam_handle_t *pamh,
569             uid_t uid, const char *user, struct tally_options *opts) 
570 {
571     struct tallylog tally;
572     tally_t oldcnt;
573     FILE *tfile = NULL;
574     const void *remote_host = NULL;
575     int i, rv;
576
577     tally.fail_cnt = 0;  /* !TALLY_HI --> Log opened for update */
578     
579     i = get_tally(pamh, uid, opts->filename, &tfile, &tally);
580     if (i != PAM_SUCCESS) {
581         if (tfile)
582             fclose(tfile);
583         RETURN_ERROR(i);
584     }
585
586     /* to remember old fail time (for locktime) */
587     if (oldtime) {
588         *oldtime = (time_t)tally.fail_time;
589     }
590     
591     tally.fail_time = time(NULL);
592
593     (void) pam_get_item(pamh, PAM_RHOST, &remote_host);
594     if (!remote_host) {
595         (void) pam_get_item(pamh, PAM_TTY, &remote_host);
596         if (!remote_host) {
597             remote_host = "unknown";
598         }
599     }
600     
601     strncpy(tally.fail_line, remote_host,
602                     sizeof(tally.fail_line)-1);
603     tally.fail_line[sizeof(tally.fail_line)-1] = 0;
604
605     oldcnt = tally.fail_cnt;
606     
607     if (!(opts->ctrl & OPT_MAGIC_ROOT) || getuid()) {
608       /* magic_root doesn't change tally */
609       tally.fail_cnt += inc;
610
611       if (tally.fail_cnt == TALLY_HI) { /* Overflow *and* underflow. :) */
612           tally.fail_cnt -= inc;
613           pam_syslog(pamh, LOG_ALERT, "Tally %sflowed for user %s",
614                  (inc<0)?"under":"over",user);
615       }
616     }
617
618     rv = tally_check(oldcnt, *oldtime, pamh, uid, user, opts, &tally);
619
620     i = set_tally(pamh, uid, opts->filename, &tfile, &tally);
621     if (i != PAM_SUCCESS) {
622         if (tfile)
623             fclose(tfile);
624         if (rv == PAM_SUCCESS)
625             RETURN_ERROR( i );
626         /* fallthrough */
627     }
628
629     return rv;
630 }
631
632 static int
633 tally_reset (pam_handle_t *pamh, uid_t uid, struct tally_options *opts)
634 {
635     struct tallylog tally; 
636     FILE *tfile = NULL;
637     int i;
638     
639     /* resets only if not magic root */
640
641     if ((opts->ctrl & OPT_MAGIC_ROOT) && getuid() == 0) {
642         return PAM_SUCCESS; 
643     }
644
645     tally.fail_cnt = 0;  /* !TALLY_HI --> Log opened for update */
646
647     i=get_tally(pamh, uid, opts->filename, &tfile, &tally);
648     if (i != PAM_SUCCESS) {
649         if (tfile) 
650             fclose(tfile);
651         RETURN_ERROR(i);
652     }
653     
654     memset(&tally, 0, sizeof(tally));
655     
656     i=set_tally(pamh, uid, opts->filename, &tfile, &tally);
657     if (i != PAM_SUCCESS) {
658         if (tfile) 
659             fclose(tfile);
660         RETURN_ERROR(i);
661     }
662
663     return PAM_SUCCESS;
664 }
665
666 /*---------------------------------------------------------------------*/
667
668 /* --- authentication management functions (only) --- */
669
670 PAM_EXTERN int
671 pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
672                     int argc, const char **argv)
673 {
674   int
675     rv;
676   time_t
677     oldtime = 0;
678   struct tally_options
679     options, *opts = &options;
680   uid_t
681     uid;
682   const char
683     *user;
684
685   rv = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
686   if (rv != PAM_SUCCESS)
687       RETURN_ERROR(rv);
688
689   if (flags & PAM_SILENT)
690     opts->ctrl |= OPT_QUIET;
691
692   rv = pam_get_uid(pamh, &uid, &user, opts);
693   if (rv != PAM_SUCCESS)
694       RETURN_ERROR(rv);
695
696   rv = tally_bump(1, &oldtime, pamh, uid, user, opts);
697
698   tally_set_data(pamh, oldtime);
699
700   return rv;
701 }
702
703 PAM_EXTERN int
704 pam_sm_setcred(pam_handle_t *pamh, int flags UNUSED,
705                int argc, const char **argv)
706 {
707   int
708     rv;
709   time_t
710     oldtime = 0;
711   struct tally_options
712     options, *opts = &options;
713   uid_t
714     uid;
715   const char
716     *user;
717
718   rv = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
719   if ( rv != PAM_SUCCESS )
720       RETURN_ERROR( rv );
721
722   rv = pam_get_uid(pamh, &uid, &user, opts);
723   if ( rv != PAM_SUCCESS )
724       RETURN_ERROR( rv );
725
726   if ( tally_get_data(pamh, &oldtime) != 0 )
727   /* no data found */
728       return PAM_SUCCESS;
729
730   return tally_reset(pamh, uid, opts);
731 }
732
733 /*---------------------------------------------------------------------*/
734
735 /* --- authentication management functions (only) --- */
736
737 /* To reset failcount of user on successfull login */
738
739 PAM_EXTERN int
740 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
741                  int argc, const char **argv)
742 {
743   int
744     rv;
745   time_t
746     oldtime = 0;
747   struct tally_options
748     options, *opts = &options;
749   uid_t
750     uid;
751   const char
752     *user;
753
754   rv = tally_parse_args(pamh, opts, PHASE_ACCOUNT, argc, argv);
755   if ( rv != PAM_SUCCESS )
756       RETURN_ERROR( rv );
757
758   rv = pam_get_uid(pamh, &uid, &user, opts);
759   if ( rv != PAM_SUCCESS )
760       RETURN_ERROR( rv );
761
762   if ( tally_get_data(pamh, &oldtime) != 0 )
763   /* no data found */
764       return PAM_SUCCESS;
765
766   return tally_reset(pamh, uid, opts);
767 }
768
769 /*-----------------------------------------------------------------------*/
770
771 #ifdef PAM_STATIC
772
773 /* static module data */
774
775 struct pam_module _pam_tally_modstruct = {
776      MODULE_NAME,
777 #ifdef PAM_SM_AUTH
778      pam_sm_authenticate,
779      pam_sm_setcred,
780 #else
781      NULL,
782      NULL,
783 #endif
784 #ifdef PAM_SM_ACCOUNT
785      pam_sm_acct_mgmt,
786 #else
787      NULL,
788 #endif
789      NULL,
790      NULL,
791      NULL,
792 };
793
794 #endif   /* #ifdef PAM_STATIC */
795
796 /*-----------------------------------------------------------------------*/
797
798 #else   /* #ifndef MAIN */
799
800 static const char *cline_filename = DEFAULT_LOGFILE;
801 static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */
802 static int cline_quiet =  0;
803
804 /*
805  *  Not going to link with pamlib just for these.. :)
806  */
807
808 static const char *
809 pam_errors( int i ) 
810 {
811   switch (i) {
812   case PAM_AUTH_ERR:     return _("Authentication error");
813   case PAM_SERVICE_ERR:  return _("Service error");
814   case PAM_USER_UNKNOWN: return _("Unknown user");
815   default:               return _("Unknown error");
816   }
817 }
818
819 static int
820 getopts( char **argv ) 
821 {
822   const char *pname = *argv;
823   for ( ; *argv ; (void)(*argv && ++argv) ) {
824     if      ( !strcmp (*argv,"--file")    ) cline_filename=*++argv;
825     else if ( !strcmp(*argv,"-f")         ) cline_filename=*++argv;
826     else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
827     else if ( !strcmp (*argv,"--user")    ) cline_user=*++argv;
828     else if ( !strcmp (*argv,"-u")        ) cline_user=*++argv;
829     else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
830     else if ( !strcmp (*argv,"--reset")   ) cline_reset=0;
831     else if ( !strcmp (*argv,"-r")        ) cline_reset=0;
832     else if ( !strncmp(*argv,"--reset=",8)) {
833       if ( sscanf(*argv+8,"%hu",&cline_reset) != 1 )
834         fprintf(stderr,_("%s: Bad number given to --reset=\n"),pname), exit(0);
835     }
836     else if ( !strcmp (*argv,"--quiet")   ) cline_quiet=1;
837     else {
838       fprintf(stderr,_("%s: Unrecognised option %s\n"),pname,*argv);
839       return FALSE;
840     }
841   }
842   return TRUE;
843 }
844
845 static void
846 print_one(const struct tallylog *tally, uid_t uid)
847 {
848    static int once;
849    char *cp;
850    time_t fail_time;
851    struct tm *tm;
852    struct passwd *pwent;
853    const char *username = "[NONAME]";
854    char ptime[80];
855
856    pwent = getpwuid(uid);
857    fail_time = tally->fail_time;
858    tm = localtime(&fail_time);
859    strftime (ptime, sizeof (ptime), "%D %H:%M:%S", tm);
860    cp = ptime;
861    if (pwent) {
862         username = pwent->pw_name;
863    }
864    if (!once) {
865         printf (_("Login           Failures Latest failure     From\n"));
866         once++;
867    }
868    printf ("%-15.15s %5hu    ", username, tally->fail_cnt);
869    if (tally->fail_time) {
870         printf ("%-17.17s  %s", cp, tally->fail_line);
871    }
872    putchar ('\n');
873 }
874
875 int 
876 main( int argc UNUSED, char **argv )
877 {
878   struct tallylog tally;
879
880   if ( ! getopts( argv+1 ) ) {
881     printf(_("%s: [-f rooted-filename] [--file rooted-filename]\n"
882              "   [-u username] [--user username]\n"
883              "   [-r] [--reset[=n]] [--quiet]\n"),
884            *argv);
885     exit(2);
886   }
887
888   umask(077);
889
890   /*
891    * Major difference between individual user and all users:
892    *  --user just handles one user, just like PAM.
893    *  without --user it handles all users, sniffing cline_filename for nonzeros
894    */
895
896   if ( cline_user ) {
897     uid_t uid;
898     FILE *tfile=0;
899     struct tally_options opts;
900     int i;
901
902     memset(&opts, 0, sizeof(opts));
903     opts.ctrl = OPT_AUDIT;
904     i=pam_get_uid(NULL, &uid, NULL, &opts);
905     if ( i != PAM_SUCCESS ) {
906       fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
907       exit(1);
908     }
909
910     i=get_tally(NULL, uid, cline_filename, &tfile, &tally);
911     if ( i != PAM_SUCCESS ) {
912       if (tfile)
913           fclose(tfile);
914       fprintf(stderr, "%s: %s\n", *argv, pam_errors(i));
915       exit(1);
916     }
917
918     if ( !cline_quiet )
919       print_one(&tally, uid);
920
921     if (cline_reset != TALLY_HI) {
922 #ifdef HAVE_LIBAUDIT
923         char buf[64];
924         int audit_fd = audit_open();
925         snprintf(buf, sizeof(buf), "pam_tally2 uid=%u reset=%hu", uid, cline_reset);
926         audit_log_user_message(audit_fd, AUDIT_USER_ACCT,
927                 buf, NULL, NULL, NULL, 1);
928         if (audit_fd >=0)
929                 close(audit_fd);
930 #endif
931         if (cline_reset == 0) {
932             memset(&tally, 0, sizeof(tally));
933         } else {
934             tally.fail_cnt = cline_reset;
935         }
936         i=set_tally(NULL, uid, cline_filename, &tfile, &tally);
937         if (i != PAM_SUCCESS) {
938             if (tfile) fclose(tfile);
939             fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
940             exit(1);
941         }
942     } else {
943         fclose(tfile);
944     }
945   }
946   else /* !cline_user (ie, operate on all users) */ {
947     FILE *tfile=fopen(cline_filename, "r");
948     uid_t uid=0;
949     if (!tfile  && cline_reset != 0) {
950         perror(*argv);
951         exit(1);
952     }
953
954     for ( ; tfile && !feof(tfile); uid++ ) {
955       if ( !fread(&tally, sizeof(tally), 1, tfile)
956            || !tally.fail_cnt ) {
957          continue;
958       }
959       print_one(&tally, uid);
960     }
961     if (tfile)
962       fclose(tfile);
963     if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
964       fprintf(stderr,_("%s: Can't reset all users to non-zero\n"),*argv);
965     }
966     else if ( !cline_reset ) {
967 #ifdef HAVE_LIBAUDIT
968       char buf[64];
969       int audit_fd = audit_open();
970       snprintf(buf, sizeof(buf), "pam_tally2 uid=all reset=0");
971       audit_log_user_message(audit_fd, AUDIT_USER_ACCT,
972               buf, NULL, NULL, NULL, 1);
973       if (audit_fd >=0)
974               close(audit_fd);
975 #endif
976       tfile=fopen(cline_filename, "w");
977       if ( !tfile ) perror(*argv), exit(0);
978       fclose(tfile);
979     }
980   }
981   return 0;
982 }
983
984
985 #endif   /* #ifndef MAIN */