[tac_plus] tac_plus AFL (Auth Fail Lock)
Mark Ellzey Thomas
mark.thomas at corp.aol.com
Mon Jun 23 19:11:54 UTC 2008
Greetings all,
Recently we have had the need for tac_plus to temporarily disable user
accounts based on the number of authentication failures the user has had
in a defined window of time.
Attached is a patch against F4.0.4.15 with the previously submitted
acct+syslog patch (if this is a problem please inform me and I will
patch against the base F4.0.4.15 tree).
The following global configuration parameter has been added:
auth-fail-lock $int1 $int2 $int3
Where $int1 is the number of authentication failures
Where $int2 is the window (in seconds) in which to watch for auth fails
Where $int3 is the number of seconds to disable the user.
An example would be:
# Watch for 10 authentication failures within 60 seconds, if triggered
# disable user for 120 seconds.
auth-fail-lock 10 60 120
The tac_plus daemon will log when a trigger is hit, and when the account
has been re-enabled:
Jun 23 14:51:36 192.168.0.1 tac_plus[27731]: User mark has been disabled for 120 seconds
Jun 23 14:53:46 192.168.0.1 tac_plus[28244]: Re-enabling account: mark
Unfortunately since tac_plus is a forked architecture, I had to achieve
persistence of data via IPC. I understand that some may be weary of this
mechanism so they can turn the feature off at compile time by passing
the --disable-afl flag to configure.
Thanks for the project!
--
Mark E. Thomas - AOL Network Security
-------------- next part --------------
diff -Naur ../tacacs_orig/authen.c ./authen.c
--- ../tacacs_orig/authen.c 2008-06-18 18:24:09.000000000 -0400
+++ ./authen.c 2008-06-19 18:24:01.000000000 -0400
@@ -326,10 +326,21 @@
return;
}
- if ((*func) (datap)) {
+#ifdef AFL
+ if ((session.afl_cfg) &&
+ cfg_is_user_disabled(datap->NAS_id->username) == 1)
+ datap->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+ else if ((*func) (datap)) {
send_authen_error("Unexpected authentication function failure");
return;
}
+#else
+ if((*func) (datap))
+ {
+ send_authen_error("Unexpected authentication function failure");
+ return;
+ }
+#endif
switch (datap->status) {
@@ -359,6 +370,10 @@
case TAC_PLUS_AUTHEN_STATUS_FAIL:
/* An invalid user/password combination */
+#ifdef AFL
+ if(session.afl_cfg)
+ cfg_increment_failure(datap->NAS_id->username);
+#endif
send_authen_reply(TAC_PLUS_AUTHEN_STATUS_FAIL,
datap->server_msg,
datap->server_msg ? strlen(datap->server_msg) : 0,
diff -Naur ../tacacs_orig/config.c ./config.c
--- ../tacacs_orig/config.c 2008-06-18 18:24:09.000000000 -0400
+++ ./config.c 2008-06-22 13:38:15.000000000 -0400
@@ -113,6 +113,11 @@
static int no_user_dflt = 0; /* default if user doesn't exist */
static char *authen_default = NULL; /* top level authentication default */
static char *nopasswd_str = "nopassword";
+#ifdef AFL
+static unsigned int user_count = 0; /* the number of users in the config */
+static key_t failed_key; /* the shm key for AFL */
+#endif
+
/*
* A host definition structure.
@@ -176,6 +181,9 @@
#ifdef MAXSESS
int maxsess; /* Max sessions/user */
#endif /* MAXSESS */
+#ifdef AFL
+ int shm_offset; /* where the user is stored in shared memory */
+#endif
};
typedef struct user USER;
@@ -214,6 +222,16 @@
typedef union hash HASH;
+#ifdef AFL
+struct failed_node {
+ char username[65];
+ unsigned int failures;
+ time_t first_failure;
+ time_t locked_time; /* the time the lock was set */
+ char disabled;
+};
+#endif
+
void *grouptable[HASH_TAB_SIZE];/* Table of group declarations */
void *usertable[HASH_TAB_SIZE]; /* Table of user declarations */
#ifdef ACLS
@@ -521,6 +539,14 @@
session.acctfile = NULL;
}
+#ifdef AFL
+ if (session.afl_cfg) {
+ cfg_destroy_failure_shm();
+ free(session.afl_cfg);
+ session.afl_cfg = NULL;
+ }
+#endif
+
#ifdef ACLS
/* clean the acltable */
for (i = 0; i < HASH_TAB_SIZE; i++) {
@@ -746,7 +772,23 @@
switch (sym_code) {
case S_eof:
return(0);
-
+#ifdef AFL
+ case S_auth_fail_lock:
+ /* faillock a b c
+ * a = number of failures
+ * b = in this many seconds
+ * c = lock for this many seconds */
+ session.afl_cfg = (struct afl_cfg *)tac_malloc(sizeof(struct afl_cfg));
+ bzero(session.afl_cfg, sizeof(struct afl_cfg));
+ sym_get();
+ session.afl_cfg->num_failures = atoi(sym_buf);
+ sym_get();
+ session.afl_cfg->seconds = atoi(sym_buf);
+ sym_get();
+ session.afl_cfg->lock_time = atoi(sym_buf);
+ sym_get();
+ break;
+#endif
case S_accounting:
sym_get();
@@ -2388,6 +2430,306 @@
return(authen_default);
}
+#ifdef AFL
+static unsigned int fetch_user_count(void)
+{
+ int i;
+ unsigned int count;
+ USER *entry;
+
+ count = 0;
+
+ for (i=0; i < HASH_TAB_SIZE; i++)
+ {
+ entry = (USER *)usertable[i];
+ while (entry)
+ {
+ count++;
+ entry = entry->hash;
+ }
+ }
+ return count;
+}
+
+/*
+ * Create a user-table in shared memory for AFL.
+ */
+void cfg_create_failure_shm(const char *path, int id)
+{
+ unsigned int shm_sz;
+ int offset;
+ int i, shmid;
+ char *shm = NULL;
+
+ user_count = shm_sz = offset = 0;
+
+ user_count = fetch_user_count();
+ shm_sz = user_count * sizeof(struct failed_node);
+
+ if((failed_key = ftok(path, id))<0)
+ {
+ report(LOG_ERR, "ftok unable to create key: %s",
+ strerror(errno));
+ tac_exit(1);
+ }
+
+ if ((shmid = shmget(failed_key, shm_sz, IPC_CREAT|0666)) < 0)
+ {
+ report(LOG_ERR, "shmget unable to get memory: %s",
+ strerror(errno));
+ tac_exit(1);
+ }
+
+ if ((shm = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
+ {
+ report(LOG_ERR, "shmat: %s", strerror(errno));
+ tac_exit(1);
+ }
+
+ /* iterate over all the users and add them a failed_node
+ * structure to the shared memory */
+ for (i=0; i < HASH_TAB_SIZE; i++)
+ {
+ struct failed_node *failed_node;
+ USER *entry = (USER *)usertable[i];
+
+ while(entry)
+ {
+ entry->shm_offset = offset;
+ failed_node = (struct failed_node *)&shm[offset];
+ bzero(failed_node, sizeof(struct failed_node));
+
+ if (strlen(entry->name) > 64)
+ {
+ report(LOG_ERR, "username %s > length of 64",
+ entry->name);
+ tac_exit(1);
+ }
+
+ strncpy(failed_node->username, entry->name, 64);
+ offset += sizeof(struct failed_node);
+
+ if (offset > user_count * sizeof(struct failed_node))
+ {
+ report(LOG_ERR, "More users than previously allocated");
+ tac_exit(1);
+ }
+
+ entry = entry->hash;
+ }
+ }
+
+ /* now create a semaphore for locking */
+ if(semget(failed_key, 1, 0666 | IPC_CREAT) < 0)
+ {
+ report(LOG_ERR, "Unable to create semaphore: %s", strerror(errno));
+ tac_exit(1);
+ }
+}
+
+char *shm_fetch_and_lock(key_t key, unsigned int sz)
+{
+ int shmid, semid;
+ struct sembuf sem[2];
+ char *shm = NULL;
+
+ if((semid = semget(key, 1, 0666)) < 0)
+ {
+ report(LOG_ERR, "unable to fetch semaphore: %s", strerror(errno));
+ tac_exit(1);
+ }
+
+ sem[0].sem_num = 0;
+ sem[1].sem_num = 0;
+ sem[0].sem_flg = SEM_UNDO;
+ sem[1].sem_flg = SEM_UNDO;
+ sem[0].sem_op = 0;
+ sem[1].sem_op = 1;
+
+ semop(semid, sem, 2);
+
+ if((shmid = shmget(key, sz, 0666)) < 0)
+ {
+ report(LOG_ERR, "shm_fetchnlock unable to get shmid: %s",
+ strerror(errno));
+ tac_exit(1);
+ }
+
+ if ((shm = shmat(shmid, NULL, 0)) == (char *) -1)
+ {
+ report(LOG_ERR, "shm_fetchnlock Unable to find shm segment: %s",
+ strerror(errno));
+ tac_exit(1);
+ }
+
+ return shm;
+}
+
+static void shm_unlock(key_t key)
+{
+ int semid;
+ struct sembuf sem;
+
+ if ((semid = semget(key, 1, 0666)) < 0)
+ tac_exit(1);
+
+ sem.sem_num = 0;
+ sem.sem_flg = SEM_UNDO;
+ sem.sem_op = -1;
+
+ semop(semid, &sem, 1);
+}
+
+static void destroy_semaphore(key_t key)
+{
+ int semid;
+ semid = semget(key, 0, 0666);
+ semctl(semid, 0, IPC_RMID, NULL);
+}
+
+static void destroy_shm(key_t key)
+{
+ int shmid;
+ shmid = shmget(key, 1, 0666);
+ shmctl(shmid, IPC_RMID, NULL);
+}
+
+
+void cfg_destroy_failure_shm(void)
+{
+ destroy_semaphore(failed_key);
+ destroy_shm(failed_key);
+}
+
+static int shm_failed_offset(char *username, void *arg)
+{
+ USER *user;
+
+ if (arg == NULL)
+ user = (USER *)hash_lookup(usertable, username);
+ else
+ user = (USER *)arg;
+
+ return (user ? user->shm_offset:-1);
+}
+
+void cfg_increment_failure(char *username)
+{
+ USER *user;
+ int offset;
+ char *data;
+ struct failed_node *node;
+ time_t now;
+
+ user = hash_lookup(usertable, username);
+
+ if (user == NULL)
+ return;
+
+ if ((offset = shm_failed_offset(username, user)) < 0)
+ return;
+
+ data = shm_fetch_and_lock(failed_key,
+ user_count * sizeof(struct failed_node));
+
+ node = (struct failed_node *)&data[user->shm_offset];
+
+ if (strcmp(node->username, username) != 0)
+ {
+ report(LOG_ERR, "Shared memory has something amiss (%s!=%s)",
+ node->username, username);
+ shm_unlock(failed_key);
+ return;
+ }
+
+ time(&now);
+
+ if (!node->first_failure)
+ node->first_failure = now;
+
+ /* determine if this fail has exceeded the number of failures within
+ * the time window. If it has then lock the account.
+ */
+ if((!node->disabled) && ++node->failures >= session.afl_cfg->num_failures)
+ {
+ report(LOG_WARNING, "User %s has been disabled for %d seconds",
+ username, session.afl_cfg->lock_time);
+ node->locked_time = now;
+ node->disabled = 1;
+ }
+
+ shm_unlock(failed_key);
+}
+
+/*
+ * Attempt to determine whether a user is locked out or not,
+ * this function also does timer expiration.
+ */
+int cfg_is_user_disabled(char *username)
+{
+ USER *user;
+ int offset;
+ char *data;
+ struct failed_node *node;
+ int ret = 0;
+ time_t now;
+
+ user = hash_lookup(usertable, username);
+
+ if (user == NULL)
+ return -1;
+
+ if ((offset = shm_failed_offset(username, user))<0)
+ return -1;
+
+ data = shm_fetch_and_lock(failed_key,
+ user_count * sizeof(struct failed_node));
+
+ node = (struct failed_node *)&data[user->shm_offset];
+
+ /* check to make sure what we have is true */
+ if (strcmp(node->username, username) != 0)
+ {
+ report(LOG_ERR, "Shared memory has something amiss (%s!=%s)",
+ node->username, username);
+ shm_unlock(failed_key);
+ return -1;
+ }
+
+ ret = node->disabled?1:0;
+ time(&now);
+
+ if (ret)
+ {
+ /* Check locked account expiration. Unlock if expired. */
+ if (difftime(now, node->locked_time) > session.afl_cfg->lock_time)
+ {
+ report(LOG_WARNING, "Re-enabling account: %s", username);
+ node->first_failure = 0;
+ node->disabled = 0;
+ node->failures = 0;
+ node->locked_time = 0;
+ ret = 0;
+ }
+ }
+ else {
+ /* Check to see if the auth-fail window has expired. */
+ if ((node->first_failure) &&
+ difftime(now, node->first_failure) > session.afl_cfg->seconds)
+ {
+ report(LOG_INFO,"Resetting failure clock for user: %s\n", username);
+ node->first_failure = 0;
+ node->disabled = 0;
+ node->failures = 0;
+ node->locked_time = 0;
+ }
+ }
+
+ shm_unlock(failed_key);
+ return ret;
+}
+#endif /* AFL */
+
/*
* Return 1 if this user has any ppp services configured. Used for
* authorizing ppp/lcp requests
diff -Naur ../tacacs_orig/config.h.in ./config.h.in
--- ../tacacs_orig/config.h.in 2008-06-18 18:24:09.000000000 -0400
+++ ./config.h.in 2008-06-19 18:05:43.000000000 -0400
@@ -210,5 +210,7 @@
# define socklen_t int
#endif
+#undef AFL
+
#endif /* CONFIG_H */
diff -Naur ../tacacs_orig/configure ./configure
--- ../tacacs_orig/configure 2008-06-18 18:24:09.000000000 -0400
+++ ./configure 2008-06-19 18:29:11.000000000 -0400
@@ -722,6 +722,7 @@
TACPLUS_LOGFILE
PROFLAGS
PROFLIBS
+AFL
TAR
INST_PROGS
PERLV_PATH
@@ -1327,6 +1328,7 @@
--enable-uenable tacacs config per-user enable support (default)
--enable-maxsess Enforce a limit on maximum sessions per user
--enable-finger finger NAS for number of sessions a user is using
+ --enable-afl Enable AFL support (default)
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@@ -6112,6 +6114,52 @@
+{ echo "$as_me:$LINENO: checking whether to enable AFL support" >&5
+echo $ECHO_N "checking whether to enable AFL support... $ECHO_C" >&6; }
+
+
+# Check whether --enable-afl was given.
+if test "${enable_afl+set}" = set; then
+ enableval=$enable_afl; case "$enable_afl" in
+ no)
+ { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+ use_afl=0
+ ;;
+ yes)
+ { echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+ cat >>confdefs.h <<\_ACEOF
+#define AFL 1
+_ACEOF
+
+ use_afl=1
+ ;;
+ *)
+ { echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+ cat >>confdefs.h <<\_ACEOF
+#define AFL 1
+_ACEOF
+
+ use_afl=1
+ ;;
+ esac
+else
+ { echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+ cat >>confdefs.h <<\_ACEOF
+#define AFL 1
+_ACEOF
+
+ use_afl=1
+
+fi
+
+
+
+
+
# look for PAM
@@ -7953,6 +8001,7 @@
TACPLUS_LOGFILE!$TACPLUS_LOGFILE$ac_delim
PROFLAGS!$PROFLAGS$ac_delim
PROFLIBS!$PROFLIBS$ac_delim
+AFL!$AFL$ac_delim
TAR!$TAR$ac_delim
INST_PROGS!$INST_PROGS$ac_delim
PERLV_PATH!$PERLV_PATH$ac_delim
@@ -7961,7 +8010,7 @@
LTLIBOBJS!$LTLIBOBJS$ac_delim
_ACEOF
- if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 17; then
+ if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 18; then
break
elif $ac_last_try; then
{ { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
diff -Naur ../tacacs_orig/configure.in ./configure.in
--- ../tacacs_orig/configure.in 2008-06-18 18:24:09.000000000 -0400
+++ ./configure.in 2008-06-19 18:29:07.000000000 -0400
@@ -557,6 +557,37 @@
AC_SUBST(PROFLAGS)
AC_SUBST(PROFLIBS)
+dnl
+dnl DISABLE AFL Support (Authentication Failure Locking)
+dnl
+AC_MSG_CHECKING(whether to enable AFL support)
+AH_TEMPLATE(AFL, [define this to disable Authentication Failure Locking support])
+AC_ARG_ENABLE(afl,
+[ --enable-afl Enable AFL support (default)],
+[ case "$enable_afl" in
+ no)
+ AC_MSG_RESULT(no)
+ use_afl=0
+ ;;
+ yes)
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(AFL)
+ use_afl=1
+ ;;
+ *)
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(AFL)
+ use_afl=1
+ ;;
+ esac ],
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(AFL)
+ use_afl=1
+)
+AC_SUBST(AFL)
+
+
+
# look for PAM
AH_TEMPLATE(HAVE_PAM, [define if your system has libpam])
AC_CHECK_LIB([pam], [pam_start],
diff -Naur ../tacacs_orig/parse.c ./parse.c
--- ../tacacs_orig/parse.c 2008-06-18 18:24:10.000000000 -0400
+++ ./parse.c 2008-06-19 18:19:00.000000000 -0400
@@ -118,6 +118,9 @@
declare("PAM", S_pam);
#endif
declare("syslog", S_syslog);
+#ifdef AFL
+ declare("auth-fail-lock", S_auth_fail_lock);
+#endif
}
/* Return a keyword code if a keyword is recognized. 0 otherwise */
diff -Naur ../tacacs_orig/parse.h ./parse.h
--- ../tacacs_orig/parse.h 2008-06-18 18:24:10.000000000 -0400
+++ ./parse.h 2008-06-19 18:19:13.000000000 -0400
@@ -87,3 +87,6 @@
# define S_pam 49
#endif
#define S_syslog 50
+#ifdef AFL
+#define S_auth_fail_lock 51
+#endif
diff -Naur ../tacacs_orig/tac_plus.c ./tac_plus.c
--- ../tacacs_orig/tac_plus.c 2008-06-18 18:24:10.000000000 -0400
+++ ./tac_plus.c 2008-06-23 14:39:14.000000000 -0400
@@ -90,6 +90,10 @@
report(LOG_NOTICE, "Received signal %d, shutting down", signum);
if (childpid > 0)
unlink(pidfilebuf);
+
+#ifdef AFL
+ cfg_destroy_failure_shm();
+#endif
tac_exit(0);
}
@@ -102,7 +106,9 @@
report(LOG_NOTICE, "Reading config");
session.acctfile = NULL;
- //session.acctfile = tac_strdup(TACPLUS_ACCTFILE);
+#ifdef AFL
+ session.afl_cfg = NULL;
+#endif
if (!session.cfgfile) {
report(LOG_ERR, "no config file specified");
@@ -115,6 +121,11 @@
tac_exit(1);
}
+#ifdef AFL
+ if(session.afl_cfg)
+ cfg_create_failure_shm(session.cfgfile, 'A');
+#endif
+
initialised++;
report(LOG_NOTICE, "Version %s Initialized %d", version, initialised);
@@ -326,6 +337,9 @@
signal(SIGUSR1, handler);
signal(SIGHUP, handler);
signal(SIGTERM, die);
+#ifdef AFL
+ signal(SIGINT, die);
+#endif
signal(SIGPIPE, SIG_IGN);
if (parse_only)
@@ -863,6 +877,9 @@
#if __STDC__
fprintf(stdout, "__STDC__\n");
#endif
+#if AFL
+ fprintf(stdout, "AFL\n");
+#endif
return;
}
diff -Naur ../tacacs_orig/tac_plus.h ./tac_plus.h
--- ../tacacs_orig/tac_plus.h 2008-06-18 18:24:10.000000000 -0400
+++ ./tac_plus.h 2008-06-19 18:23:13.000000000 -0400
@@ -177,6 +177,12 @@
# include <fcntl.h>
#endif
+#ifdef AFL
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#endif
+
#if ! HAVE_STRCHR
# define index strchr
#endif
@@ -321,6 +327,14 @@
#define NAS_PORT_MAX_LEN 255
+#ifdef AFL
+struct afl_cfg {
+ int num_failures;
+ int seconds;
+ int lock_time;
+};
+#endif
+
struct session {
int session_id; /* host specific unique session id */
int aborted; /* have we received an abort flag? */
@@ -336,6 +350,9 @@
char port[NAS_PORT_MAX_LEN+1]; /* For error reporting */
u_char version; /* version of last packet read */
u_char acct_syslog; /* syslog the accounting data */
+#ifdef AFL
+ struct afl_cfg *afl_cfg; /* authentication failure lock cfg */
+#endif
};
extern struct session session; /* the session */
@@ -689,6 +706,12 @@
int cfg_read_config(char *);
int cfg_user_exists(char *);
int cfg_user_svc_default_is_permit(char *);
+#ifdef AFL
+void cfg_create_failure_shm(const char *, int);
+void cfg_destroy_failure_shm(void);
+void cfg_increment_failure(char *);
+int cfg_is_user_disabled(char *);
+#endif
/* default_fn.c */
int default_fn(struct authen_data *);
More information about the tac_plus
mailing list