7 /* By Tim Baverstock <warwick@mmm.co.uk>, Multi Media Machine Ltd.
10 * Stuff stolen from pam_rootok and pam_listfile
12 * Changes by Tomas Mraz <tmraz@redhat.com> 5 January 2005
13 * Audit option added for Tomas patch by
14 * Sebastien Tricaud <toady@gscore.org> 13 January 2005
28 #include <sys/types.h>
30 #include <sys/param.h>
34 * here, we make a definition for the externally accessible function
35 * in this file (this definition is required for static a module
36 * but strongly encouraged generally) it is used to instruct the
37 * modules include file to define the function prototypes.
42 #define PAM_SM_ACCOUNT
43 /* #define PAM_SM_SESSION */
44 /* #define PAM_SM_PASSWORD */
46 #include <security/pam_modutil.h>
47 #include <security/pam_ext.h>
49 #include <security/pam_modules.h>
60 /*---------------------------------------------------------------------*/
62 #define DEFAULT_LOGFILE "/var/log/faillog"
63 #define MODULE_NAME "pam_tally"
65 #define tally_t unsigned short int
66 #define TALLY_FMT "%hu"
67 #define TALLY_HI ((tally_t)~0L)
70 # define FILENAME_MAX MAXPATHLEN
74 struct faillog fs_faillog;
77 #endif /* ndef MAIN */
80 struct tally_options {
88 #define PHASE_UNKNOWN 0
90 #define PHASE_ACCOUNT 2
91 #define PHASE_SESSION 3
93 #define OPT_MAGIC_ROOT 01
94 #define OPT_FAIL_ON_ERROR 02
95 #define OPT_DENY_ROOT 04
96 #define OPT_PER_USER 010
97 #define OPT_NO_LOCK_TIME 020
98 #define OPT_NO_RESET 040
99 #define OPT_AUDIT 0100
100 #define OPT_SILENT 0200
101 #define OPT_NOLOGNOTICE 0400
104 /*---------------------------------------------------------------------*/
106 /* some syslogging */
109 #define pam_syslog tally_log
111 tally_log (const pam_handle_t *pamh UNUSED, int priority UNUSED,
112 const char *fmt, ...)
117 fprintf(stderr, "%s: ", MODULE_NAME);
118 vfprintf(stderr, fmt, args);
119 fprintf(stderr,"\n");
123 #define pam_modutil_getpwnam(pamh,user) getpwnam(user)
127 /*---------------------------------------------------------------------*/
129 /* --- Support function: parse arguments --- */
134 log_phase_no_auth(pam_handle_t *pamh, int phase, const char *argv)
136 if ( phase != PHASE_AUTH ) {
137 pam_syslog(pamh, LOG_ERR,
138 "option %s allowed in auth phase only", argv);
143 tally_parse_args(pam_handle_t *pamh, struct tally_options *opts,
144 int phase, int argc, const char **argv)
146 memset(opts, 0, sizeof(*opts));
147 opts->filename = DEFAULT_LOGFILE;
149 for ( ; argc-- > 0; ++argv ) {
151 if ( ! strncmp( *argv, "file=", 5 ) ) {
152 const char *from = *argv + 5;
153 if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) {
154 pam_syslog(pamh, LOG_ERR,
155 "filename not /rooted or too long; %s", *argv);
158 opts->filename = from;
160 else if ( ! strcmp( *argv, "onerr=fail" ) ) {
161 opts->ctrl |= OPT_FAIL_ON_ERROR;
163 else if ( ! strcmp( *argv, "onerr=succeed" ) ) {
164 opts->ctrl &= ~OPT_FAIL_ON_ERROR;
166 else if ( ! strcmp( *argv, "magic_root" ) ) {
167 opts->ctrl |= OPT_MAGIC_ROOT;
169 else if ( ! strcmp( *argv, "even_deny_root_account" ) ) {
170 log_phase_no_auth(pamh, phase, *argv);
171 opts->ctrl |= OPT_DENY_ROOT;
173 else if ( ! strncmp( *argv, "deny=", 5 ) ) {
174 log_phase_no_auth(pamh, phase, *argv);
175 if ( sscanf((*argv)+5,TALLY_FMT,&opts->deny) != 1 ) {
176 pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
180 else if ( ! strncmp( *argv, "lock_time=", 10 ) ) {
181 log_phase_no_auth(pamh, phase, *argv);
182 if ( sscanf((*argv)+10,"%ld",&opts->lock_time) != 1 ) {
183 pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
187 else if ( ! strncmp( *argv, "unlock_time=", 12 ) ) {
188 log_phase_no_auth(pamh, phase, *argv);
189 if ( sscanf((*argv)+12,"%ld",&opts->unlock_time) != 1 ) {
190 pam_syslog(pamh, LOG_ERR, "bad number supplied: %s", *argv);
194 else if ( ! strcmp( *argv, "per_user" ) )
196 log_phase_no_auth(pamh, phase, *argv);
197 opts->ctrl |= OPT_PER_USER;
199 else if ( ! strcmp( *argv, "no_lock_time") )
201 log_phase_no_auth(pamh, phase, *argv);
202 opts->ctrl |= OPT_NO_LOCK_TIME;
204 else if ( ! strcmp( *argv, "no_reset" ) ) {
205 opts->ctrl |= OPT_NO_RESET;
207 else if ( ! strcmp ( *argv, "audit") ) {
208 opts->ctrl |= OPT_AUDIT;
210 else if ( ! strcmp ( *argv, "silent") ) {
211 opts->ctrl |= OPT_SILENT;
213 else if ( ! strcmp ( *argv, "no_log_info") ) {
214 opts->ctrl |= OPT_NOLOGNOTICE;
217 pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
224 #endif /* #ifndef MAIN */
226 /*---------------------------------------------------------------------*/
228 /* --- Support function: get uid (and optionally username) from PAM or
232 static char *cline_user=0; /* cline_user is used in the administration prog */
236 pam_get_uid(pam_handle_t *pamh, uid_t *uid, const char **userp, struct tally_options *opts)
238 const char *user = NULL;
244 if ((pam_get_user( pamh, &user, NULL )) != PAM_SUCCESS) {
245 pam_syslog(pamh, LOG_ERR, "pam_get_user; user?");
250 if ( !user || !*user ) {
251 pam_syslog(pamh, LOG_ERR, "pam_get_uid; user?");
255 if ( ! ( pw = pam_modutil_getpwnam( pamh, user ) ) ) {
256 opts->ctrl & OPT_AUDIT ?
257 pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user %s", user) :
258 pam_syslog(pamh, LOG_ERR, "pam_get_uid; no such user");
259 return PAM_USER_UNKNOWN;
262 if ( uid ) *uid = pw->pw_uid;
263 if ( userp ) *userp = user;
267 /*---------------------------------------------------------------------*/
269 /* --- Support functions: set/get tally data --- */
274 _cleanup(pam_handle_t *pamh UNUSED, void *data, int error_status UNUSED)
281 tally_set_data( pam_handle_t *pamh, time_t oldtime )
285 if ( (data=malloc(sizeof(time_t))) != NULL ) {
287 pam_set_data(pamh, MODULE_NAME, (void *)data, _cleanup);
292 tally_get_data( pam_handle_t *pamh, time_t *oldtime )
297 rv = pam_get_data(pamh, MODULE_NAME, &data);
298 if ( rv == PAM_SUCCESS && data != NULL && oldtime != NULL ) {
299 *oldtime = *(const time_t *)data;
300 pam_set_data(pamh, MODULE_NAME, NULL, NULL);
309 #endif /* #ifndef MAIN */
311 /*---------------------------------------------------------------------*/
313 /* --- Support function: open/create tallyfile and return tally for uid --- */
315 /* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */
316 /* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */
319 get_tally(pam_handle_t *pamh, tally_t *tally, uid_t uid,
320 const char *filename, FILE **TALLY, struct fail_s *fsp)
322 struct stat fileinfo;
323 int lstat_ret = lstat(filename,&fileinfo);
325 if ( lstat_ret && *tally!=TALLY_HI ) {
326 int oldmask = umask(077);
327 *TALLY=fopen(filename, "a");
328 /* Create file, or append-open in pathological case. */
331 pam_syslog(pamh, LOG_ALERT, "Couldn't create %s", filename);
334 lstat_ret = fstat(fileno(*TALLY),&fileinfo);
339 pam_syslog(pamh, LOG_ALERT, "Couldn't stat %s", filename);
343 if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) {
344 /* If the file is world writable or is not a
345 normal file, return error */
346 pam_syslog(pamh, LOG_ALERT,
347 "%s is either world writable or not a normal file",
352 if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) {
353 pam_syslog(pamh, LOG_ALERT, "Error opening %s for %s", filename, *tally!=TALLY_HI?"update":"read");
355 /* Discovering why account service fails: e/uid are target user.
357 * perror(MODULE_NAME);
358 * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid());
363 if ( fseeko( *TALLY, (off_t) uid * sizeof(struct faillog), SEEK_SET ) ) {
364 pam_syslog(pamh, LOG_ALERT, "fseek failed for %s", filename);
369 if ( (size_t)fileinfo.st_size <= uid * sizeof(struct faillog) ) {
371 memset(fsp, 0, sizeof(struct faillog));
373 fsp->fs_faillog.fail_time = time(NULL);
375 } else if (( fread((char *) &fsp->fs_faillog,
376 sizeof(struct faillog), 1, *TALLY) )==0 ) {
378 *tally=0; /* Assuming a gappy filesystem */
382 *tally = fsp->fs_faillog.fail_cnt;
389 /*---------------------------------------------------------------------*/
391 /* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */
394 set_tally(pam_handle_t *pamh, tally_t tally, uid_t uid,
395 const char *filename, FILE **TALLY, struct fail_s *fsp)
397 int retval = PAM_SUCCESS;
399 if ( tally!=TALLY_HI ) {
400 if ( fseeko( *TALLY, (off_t) uid * sizeof(struct faillog), SEEK_SET ) ) {
401 pam_syslog(pamh, LOG_ALERT, "fseek failed for %s", filename);
402 retval = PAM_AUTH_ERR;
404 fsp->fs_faillog.fail_cnt = tally;
405 if (fwrite((char *) &fsp->fs_faillog,
406 sizeof(struct faillog), 1, *TALLY)==0 ) {
407 pam_syslog(pamh, LOG_ALERT, "update (fwrite) failed for %s", filename);
408 retval = PAM_AUTH_ERR;
413 if ( fclose(*TALLY) ) {
414 pam_syslog(pamh, LOG_ALERT, "update (fclose) failed for %s", filename);
421 /*---------------------------------------------------------------------*/
423 /* --- PAM bits --- */
427 #define RETURN_ERROR(i) return ((opts->ctrl & OPT_FAIL_ON_ERROR)?(i):(PAM_SUCCESS))
429 /*---------------------------------------------------------------------*/
431 /* --- tally bump function: bump tally for uid by (signed) inc --- */
434 tally_bump (int inc, time_t *oldtime, pam_handle_t *pamh,
435 uid_t uid, const char *user, struct tally_options *opts)
438 tally = 0; /* !TALLY_HI --> Log opened for update */
445 struct fail_s fs, *fsp = &fs;
448 i=get_tally(pamh, &tally, uid, opts->filename, &TALLY, fsp);
449 if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); }
451 /* to remember old fail time (for locktime) */
452 fsp->fs_fail_time = fsp->fs_faillog.fail_time;
455 *oldtime = fsp->fs_faillog.fail_time;
457 fsp->fs_faillog.fail_time = time(NULL);
460 fsp->fs_faillog.fail_time = *oldtime;
463 (void) pam_get_item(pamh, PAM_RHOST, &remote_host);
466 (void) pam_get_item(pamh, PAM_TTY, &cur_tty);
468 strncpy(fsp->fs_faillog.fail_line, "unknown",
469 sizeof(fsp->fs_faillog.fail_line) - 1);
470 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
472 strncpy(fsp->fs_faillog.fail_line, cur_tty,
473 sizeof(fsp->fs_faillog.fail_line)-1);
474 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
478 strncpy(fsp->fs_faillog.fail_line, remote_host,
479 (size_t)sizeof(fsp->fs_faillog.fail_line));
480 fsp->fs_faillog.fail_line[sizeof(fsp->fs_faillog.fail_line)-1] = 0;
483 if ( !(opts->ctrl & OPT_MAGIC_ROOT) || getuid() ) { /* magic_root doesn't change tally */
487 if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */
489 pam_syslog(pamh, LOG_ALERT, "Tally %sflowed for user %s",
490 (inc<0)?"under":"over",user);
494 i=set_tally(pamh, tally, uid, opts->filename, &TALLY, fsp );
495 if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); }
501 tally_check (time_t oldtime, pam_handle_t *pamh, uid_t uid,
502 const char *user, struct tally_options *opts)
509 lock_time = opts->lock_time;
511 struct fail_s fs, *fsp = &fs;
515 i=get_tally(pamh, &tally, uid, opts->filename, &TALLY, fsp);
516 if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); }
518 if ( TALLY != NULL ) {
522 if ( !(opts->ctrl & OPT_MAGIC_ROOT) || getuid() ) { /* magic_root skips tally check */
524 /* To deny or not to deny; that is the question */
526 /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */
528 if ( (fsp->fs_faillog.fail_max) && (opts->ctrl & OPT_PER_USER) ) {
529 deny = fsp->fs_faillog.fail_max;
531 if ( (fsp->fs_faillog.fail_locktime) && (opts->ctrl & OPT_PER_USER) ) {
532 lock_time = fsp->fs_faillog.fail_locktime;
534 if (lock_time && oldtime
535 && !(opts->ctrl & OPT_NO_LOCK_TIME) )
537 if ( lock_time + oldtime > time(NULL) )
539 if (!(opts->ctrl & OPT_SILENT))
541 _("Account temporary locked (%ld seconds left)"),
542 oldtime+lock_time-time(NULL));
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 int) uid,
549 oldtime+lock_time-time(NULL));
553 if (opts->unlock_time && oldtime)
555 if ( opts->unlock_time + oldtime <= time(NULL) )
556 { /* ignore deny check after unlock_time elapsed */
561 ( deny != 0 ) && /* deny==0 means no deny */
562 ( tally > deny ) && /* tally>deny means exceeded */
563 ( ((opts->ctrl & OPT_DENY_ROOT) || uid) ) /* even_deny stops uid check */
565 if (!(opts->ctrl & OPT_SILENT))
566 pam_info (pamh, _("Account locked due to %u failed logins"),
567 (unsigned int)tally);
569 if (!(opts->ctrl & OPT_NOLOGNOTICE))
570 pam_syslog(pamh, LOG_NOTICE,
571 "user %s (%lu) tally "TALLY_FMT", deny "TALLY_FMT,
572 user, (unsigned long int) uid, tally, deny);
573 return PAM_AUTH_ERR; /* Only unconditional failure */
581 tally_reset (pam_handle_t *pamh, uid_t uid, struct tally_options *opts)
584 tally = 0; /* !TALLY_HI --> Log opened for update */
586 struct fail_s fs, *fsp = &fs;
590 i=get_tally(pamh, &tally, uid, opts->filename, &TALLY, fsp);
591 if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); }
593 /* resets if not magic root
596 if ( (!(opts->ctrl & OPT_MAGIC_ROOT) || getuid())
597 && !(opts->ctrl & OPT_NO_RESET) )
602 fsp->fs_faillog.fail_time = (time_t) 0;
603 strcpy(fsp->fs_faillog.fail_line, "");
606 i=set_tally(pamh, tally, uid, opts->filename, &TALLY, fsp);
607 if ( i != PAM_SUCCESS ) { RETURN_ERROR( i ); }
612 /*---------------------------------------------------------------------*/
614 /* --- authentication management functions (only) --- */
619 pam_sm_authenticate(pam_handle_t *pamh, int flags,
620 int argc, const char **argv)
627 options, *opts = &options;
633 rvcheck = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
634 if ( rvcheck != PAM_SUCCESS )
635 RETURN_ERROR( rvcheck );
637 if (flags & PAM_SILENT)
638 opts->ctrl |= OPT_SILENT;
640 rvcheck = pam_get_uid(pamh, &uid, &user, opts);
641 if ( rvcheck != PAM_SUCCESS )
642 RETURN_ERROR( rvcheck );
644 rvbump = tally_bump(1, &oldtime, pamh, uid, user, opts);
645 rvcheck = tally_check(oldtime, pamh, uid, user, opts);
647 tally_set_data(pamh, oldtime);
649 return rvcheck != PAM_SUCCESS ? rvcheck : rvbump;
653 pam_sm_setcred(pam_handle_t *pamh, int flags,
654 int argc, const char **argv)
661 options, *opts = &options;
667 rv = tally_parse_args(pamh, opts, PHASE_AUTH, argc, argv);
668 if ( rv != PAM_SUCCESS )
671 if (flags & PAM_SILENT)
672 opts->ctrl |= OPT_SILENT;
674 rv = pam_get_uid(pamh, &uid, &user, opts);
675 if ( rv != PAM_SUCCESS )
678 if ( tally_get_data(pamh, &oldtime) != 0 )
682 if ( (rv=tally_bump(-1, &oldtime, pamh, uid, user, opts)) != PAM_SUCCESS )
684 return tally_reset(pamh, uid, opts);
689 /*---------------------------------------------------------------------*/
691 /* --- authentication management functions (only) --- */
693 #ifdef PAM_SM_ACCOUNT
695 /* To reset failcount of user on successfull login */
698 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
699 int argc, const char **argv)
706 options, *opts = &options;
712 rv = tally_parse_args(pamh, opts, PHASE_ACCOUNT, argc, argv);
713 if ( rv != PAM_SUCCESS )
716 if (flags & PAM_SILENT)
717 opts->ctrl |= OPT_SILENT;
719 rv = pam_get_uid(pamh, &uid, &user, opts);
720 if ( rv != PAM_SUCCESS )
723 if ( tally_get_data(pamh, &oldtime) != 0 )
727 if ( (rv=tally_bump(-1, &oldtime, pamh, uid, user, opts)) != PAM_SUCCESS )
729 return tally_reset(pamh, uid, opts);
732 #endif /* #ifdef PAM_SM_ACCOUNT */
734 /*-----------------------------------------------------------------------*/
738 /* static module data */
740 struct pam_module _pam_tally_modstruct = {
749 #ifdef PAM_SM_ACCOUNT
759 #endif /* #ifdef PAM_STATIC */
761 /*-----------------------------------------------------------------------*/
763 #else /* #ifndef MAIN */
765 static const char *cline_filename = DEFAULT_LOGFILE;
766 static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */
767 static int cline_quiet = 0;
770 * Not going to link with pamlib just for these.. :)
777 case PAM_AUTH_ERR: return _("Authentication error");
778 case PAM_SERVICE_ERR: return _("Service error");
779 case PAM_USER_UNKNOWN: return _("Unknown user");
780 default: return _("Unknown error");
785 getopts( char **argv )
787 const char *pname = *argv;
788 for ( ; *argv ; (void)(*argv && ++argv) ) {
789 if ( !strcmp (*argv,"--file") ) cline_filename=*++argv;
790 else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7;
791 else if ( !strcmp (*argv,"--user") ) cline_user=*++argv;
792 else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7;
793 else if ( !strcmp (*argv,"--reset") ) cline_reset=0;
794 else if ( !strncmp(*argv,"--reset=",8)) {
795 if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 )
796 fprintf(stderr,_("%s: Bad number given to --reset=\n"),pname), exit(0);
798 else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1;
800 fprintf(stderr,_("%s: Unrecognised option %s\n"),pname,*argv);
807 int main ( int argc UNUSED, char **argv )
809 struct fail_s fs, *fsp = &fs;
811 if ( ! getopts( argv+1 ) ) {
812 printf(_("%s: [--file rooted-filename] [--user username] "
813 "[--reset[=n]] [--quiet]\n"),
821 * Major difference between individual user and all users:
822 * --user just handles one user, just like PAM.
823 * --user=* handles all users, sniffing cline_filename for nonzeros
828 tally_t tally=cline_reset;
830 struct tally_options opts;
833 memset(&opts, 0, sizeof(opts));
834 opts.ctrl = OPT_AUDIT;
835 i=pam_get_uid(NULL, &uid, NULL, &opts);
836 if ( i != PAM_SUCCESS ) {
837 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
841 i=get_tally(NULL, &tally, uid, cline_filename, &TALLY, fsp);
842 if ( i != PAM_SUCCESS ) {
843 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
848 printf("User %s\t(%lu)\t%s "TALLY_FMT"\n",cline_user,
849 (unsigned long int) uid,
850 (cline_reset!=TALLY_HI)?"had":"has",tally);
852 i=set_tally(NULL, cline_reset, uid, cline_filename, &TALLY, fsp);
853 if ( i != PAM_SUCCESS ) {
854 fprintf(stderr,"%s: %s\n",*argv,pam_errors(i));
858 else /* !cline_user (ie, operate on all users) */ {
859 FILE *TALLY=fopen(cline_filename, "r");
861 if ( !TALLY ) perror(*argv), exit(0);
863 for ( ; !feof(TALLY); uid++ ) {
866 if ( ! fread((char *) &fsp->fs_faillog,
867 sizeof (struct faillog), 1, TALLY)
868 || ! fsp->fs_faillog.fail_cnt ) {
871 tally = fsp->fs_faillog.fail_cnt;
873 if ( ( pw=getpwuid(uid) ) ) {
874 printf("User %s\t(%lu)\t%s "TALLY_FMT"\n",pw->pw_name,
875 (unsigned long int) uid,
876 (cline_reset!=TALLY_HI)?"had":"has",tally);
879 printf("User [NONAME]\t(%lu)\t%s "TALLY_FMT"\n",
880 (unsigned long int) uid,
881 (cline_reset!=TALLY_HI)?"had":"has",tally);
885 if ( cline_reset!=0 && cline_reset!=TALLY_HI ) {
886 fprintf(stderr,_("%s: Can't reset all users to non-zero\n"),*argv);
888 else if ( !cline_reset ) {
889 TALLY=fopen(cline_filename, "w");
890 if ( !TALLY ) perror(*argv), exit(0);
898 #endif /* #ifndef MAIN */