This patch tailors pam-abl to my neeeds. It does the following: - for some reason, the method pam_abl uses to find out aboput failed login attempts, i.e. the PAM cleanup function does not work for me on Debian Etch. The error code is always PAM_DATA_REPLACE, no matter whether the authentication was sucessful or not So I added two more options: record_attempt and check_only. record_attempt causes pam_abl to always record a failed attempt and return AUTH_ERR. check_only is the opposite -- it only checks the attempt but never records it. I then use pam_abl like this: auth requisite /usr/local/lib/security/pam_abl.so config=/etc/security/pam_abl.conf check_only auth sufficient /lib/security/pam_unix.so nullok_secure likeauth auth optional /usr/local/lib/security/pam_abl.so config=/etc/security/pam_abl.conf record_attempt auth required /lib/security/pam_deny.so The first call to pam_abl checks whether this attempt should be blacklisted. Note that it is defined as "requisite", so if it fails, processing stops and the attempt (which fails because of pam_abl) does not get recorded. Which is a good thing, IMO, because a legitimate user need not know what the failure limits are and can retry connecting from time to time, without risking having her ban extended because yet another failed attempts. pam_unix is defined as "sufficient", so if it succeedes, processing stops here, and we never get to the second call of pam_abl. the second call of pam_abl merely logs a failed attempt. If it were not a failed attempt, the processing would have stopped at pam_unix. This works great for me on Debian Etch, unlike the original approach. YMMV. - Changes the sematics of host rules a bit. The user part of the rules do not apply to the user, but to the host instead. I need this to be able to whitelist specific hosts that should never be blacklisted. E.g. this rule in the config file: host_rule=!192.168.0.2:5/5m,10/1h,20/3h,30/1d will blacklist hosts trying to bruteforce my machine, except 192.168.0.2. This is something I really need and frankly, I did not find a good reason to specify users in host rules. - Changes the syntax of host_rule and user_rule to use '=' instead of ':' The ':' conflicted with IPv6 addresses, and because the prevous change makes the host_rule match agains IP addresses instead of usernames, this was a problem. - changed the installation paths to be under /usr/local (I don't like out-of-distro files anywhere else) Feel free to use any part of this patch. Regards, Jiri Bohac (http://jikos.cz/~jbohac) diff -Naurp pam_abl.orig/Makefile pam_abl/Makefile --- pam_abl.orig/Makefile 2005-10-12 21:22:25.000000000 +0200 +++ pam_abl/Makefile 2008-04-12 12:50:45.000000000 +0200 @@ -2,7 +2,7 @@ # $Id: Makefile,v 1.1.1.1 2005/10/12 19:22:25 tagishandy Exp $ CFLAGS=-Wall -fPIC -PAMDIR=/lib/security +PAMDIR=/usr/local/lib/security/ CONFDIR=/etc/security DBDIR=/var/lib/abl LIBS=-ldb -lpthread diff -Naurp pam_abl.orig/config.c pam_abl/config.c --- pam_abl.orig/config.c 2005-10-12 21:22:25.000000000 +0200 +++ pam_abl/config.c 2008-04-12 12:50:45.000000000 +0200 @@ -95,6 +95,9 @@ void config_clear(pam_handle_t *pamh, ab args->user_purge = USER_PURGE; args->strs = NULL; + + args->record_attempt = 0; + args->check_only = 0; } static int parse_arg(const char *arg, abl_args *args) { @@ -132,6 +135,10 @@ static int parse_arg(const char *arg, ab } } else if (v = is_arg("config", arg), NULL != v) { config_parse_file(v, args); + } else if (!strcmp(arg, "record_attempt")) { + args->record_attempt = 1; + } else if (!strcmp(arg, "check_only")) { + args->check_only = 1; } else { log_out(args, LOG_ERR, "Illegal option: %s", arg); return EINVAL; diff -Naurp pam_abl.orig/pam_abl.c pam_abl/pam_abl.c --- pam_abl.orig/pam_abl.c 2005-10-12 21:22:26.000000000 +0200 +++ pam_abl/pam_abl.c 2008-04-12 12:50:45.000000000 +0200 @@ -291,7 +291,7 @@ static int check_host(const abl_args *ar } if (NULL != rhost) { log_debug(args, "Checking host %s", rhost); - return check(args, args->host_db, user, service, args->host_rule, rhost, tm, rv); + return check(args, args->host_db, rhost, service, args->host_rule, rhost, tm, rv); } else { log_debug(args, "PAM_RHOST is NULL"); return 0; @@ -344,7 +344,7 @@ static void cleanup(pam_handle_t *pamh, abl_args *args = data; log_debug(args, "In cleanup, err is %08x", err); - if (err && (err & PAM_DATA_REPLACE) == 0) { + if (err && (err & PAM_DATA_REPLACE) == 0 && !args->check_only) { record_attempt(args); } config_free(args); @@ -371,7 +371,12 @@ PAM_EXTERN int pam_sm_authenticate(pam_h goto fail; } - check_attempt(args, &rv); + if (args->record_attempt) { + record_attempt(args); + return PAM_AUTH_ERR; + } + + check_attempt(args, &rv); if (rv) { const char *rhost, *user, *service; if (PAM_SUCCESS == pam_get_item(args->pamh, PAM_RHOST, (const void **) &rhost ) && diff -Naurp pam_abl.orig/pam_abl.h pam_abl/pam_abl.h --- pam_abl.orig/pam_abl.h 2005-10-12 21:22:27.000000000 +0200 +++ pam_abl/pam_abl.h 2008-04-12 12:50:45.000000000 +0200 @@ -100,6 +100,9 @@ typedef struct { const char *user_rule; long user_purge; + int record_attempt; + int check_only; + /* Storage */ abl_string *strs; } abl_args; diff -Naurp pam_abl.orig/rule.c pam_abl/rule.c --- pam_abl.orig/rule.c 2005-10-12 21:22:27.000000000 +0200 +++ pam_abl/rule.c 2008-04-12 12:50:50.000000000 +0200 @@ -117,7 +117,7 @@ static int wordlen(const char *rp) { while (*rp != '\0' && *rp != '/' && *rp != '|' && - *rp != ':' && + *rp != '=' && !isspace(*rp)) { rp++; l++; @@ -230,7 +230,7 @@ static int check_clause(const abl_args * log_debug(args, "Name matched, next char is '%c'", **rp); /* The name part matches so now check the trigger clauses */ - if (**rp != ':') { + if (**rp != '=') { return 0; } @@ -277,13 +277,13 @@ int rule_purge(DBT *rec, long maxage, ti * trigger ::= number '/' period * triglist ::= trigger * | trigger ',' triglist - * userclause ::= userspec ':' triglist + * userclause ::= userspec '=' triglist * rule ::= userclause * | userclause /\s+/ rule * * This gives rise to rules like * - * !root|admin/sshd:10/1m,100/1d root:10/3m + * !root|admin/sshd=10/1m,100/1d root=10/3m * * which means for accounts other than 'root' or 'admin' trigger if there were ten * or more events in the last minute or 100 or more events in the last day. For diff -Naurp pam_abl.orig/tools/Makefile pam_abl/tools/Makefile --- pam_abl.orig/tools/Makefile 2005-10-12 21:22:27.000000000 +0200 +++ pam_abl/tools/Makefile 2008-04-12 12:50:45.000000000 +0200 @@ -4,7 +4,7 @@ CFLAGS=-Wall LIBS=-ldb -lpthread TARGET=pam_abl OBJ=log.o config.o rule.o pam_abl.o -INSTDIR=/usr/bin +INSTDIR=/usr/local/sbin all : $(TARGET) diff -Naurp pam_abl.orig/tools/pam_abl.c pam_abl/tools/pam_abl.c --- pam_abl.orig/tools/pam_abl.c 2005-10-12 21:22:28.000000000 +0200 +++ pam_abl/tools/pam_abl.c 2008-04-12 12:50:50.000000000 +0200 @@ -153,7 +153,7 @@ static void showblocking(const abl_args int op = 0; while (*rule) { const char *up; - const char *colon = strchr(rule, ':'); + const char *colon = strchr(rule, '='); if (NULL == colon) { break; }