--- tacacs+-F4.0.4.27a/version.h.in	2013-08-04 18:00:10.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/version.h.in	2013-09-24 15:55:10.825598020 +0200
@@ -3,6 +3,6 @@
 #define VERSION_H
 
 char package[] = "tacacs+";
-char version[] = "F4.0.4.27a";
+char version[] = "F4.0.4.27a-pam-patch";
 
 #endif
--- tacacs+-F4.0.4.27a/config.c	2012-06-29 00:37:06.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/config.c	2013-09-24 16:46:49.407152180 +0200
@@ -39,12 +39,20 @@
 				<host_decl>
 
    <top_level_decl>	:=	<authen_default> |
+				<pam_decl> |
 				accounting file = <filename> |
 				accounting syslog |
 				key = <string> |
-				logging = <syslog_fac>
+				logging = <syslog_fac> |
+				recurse group value = (first|last)
 
-   <authen_default>	:=	default authentication = file <filename>
+   <authen_default>	:=	default authentication = (file <filename>|PAM)
+
+#ifdef HAVE_PAM
+   <pam_decl>		:=	PAM user enable = nopassword|
+				PAM user group match = <string>|
+				PAM user default group = <string>
+#endif
 
    <permission>		:=	permit | deny
 
@@ -130,7 +138,11 @@
 static int sym_line = 1;			/* current line number */
 static FILE *cf = NULL;				/* config file pointer */
 static int sym_error = 0;			/* a parsing error occurred */
+static int recurse_method = TAC_RECURSE_FIRST;	/* user first or last recursed value */
+static int pam_dflt_enable = 0;		/* default pam enable method */
+static int authen_method = 0;		/* default authentication method */
 static char *authen_default = NULL;	/* top level authentication default */
+static char *group_default = NULL;	/* default group */
 static char *nopasswd_str = "nopassword";
 
 /*
@@ -215,6 +227,17 @@
 } ACL;
 #endif
 
+#if HAVE_PAM
+typedef struct matchlist {
+	char *pattern;
+	regex_t regex;
+	struct matchlist *prev;
+	struct matchlist *next;
+} MATCHLIST;
+
+static MATCHLIST *matchlist = NULL;
+#endif
+
 /*
  * Only the first 2 fields (name and hash) are used by the hash table
  * routines to hash structures into a table.
@@ -242,6 +265,7 @@
 static void	free_cmd_matches(NODE *);
 static void	free_hoststruct(HOST *);
 static void	free_svcs(NODE *);
+static void	free_matchlist(MATCHLIST **);
 static void	free_userstruct(USER *);
 static void	getsym(void);
 static VALUE	get_value(USER *, int);
@@ -258,6 +282,9 @@
 static int	parse_permission(void);
 static NODE	*parse_svcs(void);
 static int	parse_user(void);
+#if HAVE_PAM
+static USER*	cfg_setup_pam_user(char*);
+#endif
 static void	rch(void);
 static void	sym_get(void);
 
@@ -454,6 +481,28 @@
 }
 
 static void
+free_matchlist(MATCHLIST **list)
+{
+    MATCHLIST *next, *p;
+
+    if(!list || !*list)
+       return;
+
+    p = *list;
+
+    for(next = 0; p && next != *list; p = next) {
+
+	next = p->next;
+
+	regfree((regex_t *)&p->regex);
+	if(p->pattern)
+		free(p->pattern);
+	free(p);
+    }
+    *list = NULL;
+}
+
+static void
 free_userstruct(USER *user)
 {
     if (debug & DEBUG_CLEAN_FLAG)
@@ -519,11 +568,20 @@
     ACL *aentry, *anext;
 #endif
 
+    pam_dflt_enable = 0;
+    authen_method = 0;
+    recurse_method = TAC_RECURSE_FIRST;
+
     if (authen_default) {
 	free(authen_default);
 	authen_default = NULL;
     }
 
+    if (group_default) {
+	free(group_default);
+	group_default = NULL;
+    }
+
     if (session.key) {
 	free(session.key);
 	session.key = NULL;
@@ -584,6 +642,8 @@
 	}
 	usertable[i] = NULL;
     }
+
+    free_matchlist(&matchlist);
 }
 
 static int
@@ -795,19 +855,171 @@
 		return(1);
 
 	    case S_authentication:
-		if (authen_default) {
-		    parse_error("Multiply defined authentication default on "
+		if (authen_method) {
+		    parse_error("Multiple defined authentication default on "
 				"line %d", sym_line);
 		    return(1);
 		}
 		parse(S_authentication);
 		parse(S_separator);
-		parse(S_file);
-		authen_default = tac_strdup(sym_buf);
+
+	    	switch (sym_code) {
+	    	default:
+			parse_error("Unexpected method on line %d (%s)"
+				, sym_line, sym_buf);
+			return(1);
+
+	    	case S_file:
+			authen_method = sym_code;
+			sym_get();
+			authen_default = tac_strdup(sym_buf);
+			break;
+			
+	    	case S_pam:
+#if HAVE_PAM
+			authen_method = sym_code;
+#else
+			parse_error("Line %d (%s) PAM not available"
+				, sym_line, sym_buf);
+			return(1);
+#endif
+			break;
+		}
 		sym_get();
+
+    		if (debug & DEBUG_PARSE_FLAG)
+			report(LOG_DEBUG, "Authentication method '%s' (%s)",
+	       		codestring(authen_method),
+			authen_default ? authen_default : "");
 		continue;
 	    }
 
+	case S_recurse:
+	     sym_get();
+	     parse(S_group);
+	     parse(S_value);
+	     parse(S_separator);
+
+	     switch(sym_code) {
+	     default:
+		parse_error("Unexpected token on line %d (%s)"
+			, sym_line, sym_buf);
+		return(1);
+	     case S_first:
+			recurse_method = TAC_RECURSE_FIRST;
+			break;
+	     case S_last:
+			recurse_method = TAC_RECURSE_LAST;
+			break;
+	     }
+	     sym_get();
+	     continue;
+
+#if HAVE_PAM
+	case S_pam:
+	    sym_get();
+	    parse(S_user);
+
+	    switch(sym_code) {
+	    default:
+		parse_error("Unexpected token on line %d (%s)"
+			, sym_line, sym_buf);
+		return(1);
+
+	    case S_enable:
+
+		sym_get();
+	    	parse(S_separator);
+	    	switch (sym_code) {
+	    	default:
+			parse_error("Unexpected token on line %d (%s)"
+				, sym_line, sym_buf);
+			return(1);
+
+	     	case S_nopasswd:
+				pam_dflt_enable = sym_code;
+			break;
+	     	}
+		break;
+
+	    case S_default:
+		sym_get();
+	    	parse(S_group);
+	    	parse(S_separator);
+
+		if(group_default) {
+			parse_error("Redefinition of default group on line %d"
+				, sym_line);
+			return(1);
+		}
+		if(sym_code != S_string) {
+			parse_error("Expected a string on line %d (%s)"
+				, sym_line, sym_buf);
+			return(1);
+		}
+
+		group_default = tac_strdup(sym_buf);
+		sym_get();
+		continue;
+
+	    case S_group: {
+
+    		int ecode;
+		MATCHLIST *p;
+
+		sym_get();
+	    	parse(S_match);
+	    	parse(S_separator);
+		if(sym_code != S_string) {
+			parse_error("Expected a string on line %d (%s)"
+				, sym_line, sym_buf);
+			return(1);
+		}
+		p = (MATCHLIST*)tac_malloc(sizeof(MATCHLIST));
+		if(!matchlist)
+		{
+			matchlist = p;
+			p-> next = matchlist;
+			p-> prev = matchlist;
+		}
+		else
+		{
+			p->prev = matchlist->prev;
+			matchlist->prev->next = p;
+			matchlist->prev = p;
+			p->next = matchlist;
+		}
+		p->pattern = tac_strdup(sym_buf);
+
+    		ecode = regcomp((regex_t *)&p->regex, (char *)p->pattern,
+		    (REG_EXTENDED|REG_NOSUB));
+    		if (ecode) {
+			char buf[256];
+			regerror(ecode, (regex_t *)&p->regex, buf, 256);
+			report(LOG_ERR, "in regex %s on line %d", sym_buf, sym_line);
+			report(LOG_ERR, "regex compile failed: %s", buf);
+			tac_exit(1);
+    		}
+
+    		if (debug & DEBUG_PARSE_FLAG)
+		{
+			p = matchlist;
+			do {
+				if(p)
+				{
+					report(LOG_DEBUG, "match group '%s'",
+						p->pattern);
+					p = p->next;
+				}
+			} while(p != matchlist);
+		}
+		}
+		break;
+	     }
+	     sym_get();
+	     continue;
+#endif
+
 	case S_key:
 	    /* Process a key declaration. */
 	    sym_get();
@@ -1220,6 +1432,11 @@
 		    user->enable = tac_strdup(sym_buf);
 		    break;
 #endif
+#if HAVE_PAM
+		case S_pam:
+		    user->enable = tac_strdup(sym_buf);
+		    break;
+#endif
 
 		default:
 		    parse_error("expecting 'file', 'cleartext', 'nopassword', "
@@ -1835,7 +2052,7 @@
 	    char *groupname = entry->member;
 
 	    if (debug & DEBUG_PARSE_FLAG)
-		report(LOG_DEBUG, "\tmember of group %s",
+		report(LOG_DEBUG, " member of group %s",
 		       groupname ? groupname : "<none>");
 
 
@@ -1888,7 +2105,14 @@
 cfg_get_value(char *name, int isuser, int attr, int recurse)
 {
     USER *user, *group;
-    VALUE value;
+    VALUE value, lastval;
+
+#if HAVE_PAM
+    static defpam = -1;
+
+     if(defpam < 0)
+         defpam = cfg_get_authen_method() == S_pam;
+#endif
 
     memset(&value, 0, sizeof(VALUE));
 
@@ -1902,7 +2126,12 @@
     if (!user) {
 	if (debug & DEBUG_CONFIG_FLAG)
 	    report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name);
-	return(value);
+
+#if HAVE_PAM
+	/* do we need to setup a 'virtual' PAM user? */
+	if(!isuser || !defpam || !(user = cfg_setup_pam_user(name)))
+		return(value);
+#endif
     }
 
     /* found the entry. Lookup value from attr=value */
@@ -1917,13 +2146,18 @@
     else
 	group = NULL;
 
+    memset(&lastval, 0, sizeof(VALUE));
+
     while (group) {
 	if (debug & DEBUG_CONFIG_FLAG)
 	    report(LOG_DEBUG, "cfg_get_value: recurse group = %s", group->name);
 
 	value = get_value(group, attr);
 	if (value.pval) {
-	    return(value);
+	    if(recurse_method == TAC_RECURSE_FIRST) 
+	       return(value);
+
+	    lastval = value;
 	}
 	/* still nothing. Check containing group and so on */
 
@@ -1934,8 +2168,7 @@
     }
 
     /* no value for this user or her containing groups */
-    memset(&value, 0, sizeof(VALUE));
-    return(value);
+    return(lastval);
 }
 
 /* For getting host values */
@@ -2462,12 +2695,127 @@
     }
 }
 
+int
+cfg_get_authen_method(void)
+{
+    return(authen_method);
+}
+
 char *
 cfg_get_authen_default(void)
 {
     return(authen_default);
 }
 
+#if HAVE_PAM
+/*
+ * if default authentication = PAM and if no user entry exists
+ * create then create a usertabel slot with minimal information
+ */
+#define MAX_NR_GROUPS	25
+
+static USER*
+cfg_setup_pam_user(char *username)
+{
+	USER *user;
+	struct passwd *pwd;
+	struct group *grp;
+	gid_t thegid, groups[MAX_NR_GROUPS];
+	int i, n;
+
+	if(debug & DEBUG_CONFIG_FLAG)
+		report(LOG_DEBUG, "cfg_setup_pam_user: user '%s'", username);
+
+	/* find the user/group entry */
+	user = (USER *)hash_lookup(usertable, username);
+	if(user) {
+		if (debug & DEBUG_CONFIG_FLAG)
+			report(LOG_DEBUG, "cfg_setup_pam_user: %s already exists", 
+				username);
+		return user;
+	}
+
+	if(!(pwd = getpwnam(username))) {
+	   if (debug & DEBUG_CONFIG_FLAG)
+		report(LOG_DEBUG, "cfg_setup_pam_user: %s not in directory", 
+			username);
+	   return NULL;
+	}
+
+	n = MAX_NR_GROUPS;
+	thegid = pwd->pw_gid;
+	(void) getgrouplist(username, thegid, (gid_t *) &groups[0], &n);
+
+	/* Create a runtime PAM user entry */
+	user = (USER *)tac_malloc(sizeof(USER)); 
+
+	memset(user, 0, sizeof(USER));
+
+	/* determine group membership, if no matchlist then no group */
+	if(matchlist) {
+		MATCHLIST *p = matchlist;
+		do {
+			int i;
+
+			for(i=0; i < n; i++)
+			{
+				if(!(grp = getgrgid(groups[i])))
+					goto leave;
+
+				if(regexec((regex_t *)&p->regex, grp->gr_name, 
+					0, NULL, 0) == REG_OK) {
+					user->member = tac_strdup(grp->gr_name);
+					break;
+				}
+			}
+			p = p->next;
+		
+		} while (p != matchlist);
+	}
+	if (debug & DEBUG_CONFIG_FLAG)
+		report(LOG_DEBUG, "cfg_setup_pam_user: %s group %s", 
+			username, user->member ? user->member : "<none>");
+
+	if(!user->member) {
+	   if(!group_default) {
+		report(LOG_DEBUG, "cfg_setup_pam_user: %s no default group", 
+			username);
+		goto leave;
+	   }
+	   user->member = tac_strdup(group_default);
+	}
+
+	/* is the users group defined? */
+	if(!(USER *)hash_lookup(grouptable, user->member)) {
+		report(LOG_DEBUG, "cfg_setup_pam_user: %s group %s not defined",
+			username, user->member);
+		goto leave;
+	}
+
+	user->flags = FLAG_ISUSER;
+	user->name = tac_strdup(username);
+	user->login = tac_strdup(codestring(S_pam));
+	user->noenablepwd = pam_dflt_enable;
+
+	if(hash_add_entry(usertable, (void *)user)) {
+		report(LOG_ERR, "cannot add user '%s' to usertable",
+			username);
+leave:
+		free_userstruct(user);
+		free(user);
+
+		return NULL;
+
+	} 
+
+	if (debug & DEBUG_CONFIG_FLAG)
+		report(LOG_DEBUG, "cfg_setup_pam_user: %s added to hashtable", 
+				username);
+
+	return user;
+}
+#endif /* HAVE_PAM */
+
 /*
  * Return 1 if this user has any ppp services configured. Used for
  * authorizing ppp/lcp requests
--- tacacs+-F4.0.4.27a/enable.c	2012-03-27 20:40:57.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/enable.c	2013-09-16 17:18:32.664537915 +0200
@@ -53,7 +53,17 @@
     /* if the user has a user-specific enable password, check it */
     cfg_passwd = cfg_get_enable_secret(username, TAC_PLUS_RECURSE);
     if (cfg_passwd != NULL) {
-	if ((verify_pwd(username, passwd, data, cfg_passwd))) {
+
+	if(!strcmp(cfg_passwd, codestring(S_pam))) {
+	    if(!pam_verify(username, passwd))
+		goto FAIL;
+
+	    data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+	    exp_date = cfg_get_expires(username, TAC_PLUS_RECURSE);
+	    set_expiration_status(exp_date, data);
+	    goto SUCCESS;
+	
+	} else if ((verify_pwd(username, passwd, data, cfg_passwd))) {
 	    exp_date = cfg_get_expires(username, TAC_PLUS_RECURSE);
 	    set_expiration_status(exp_date, data);
 	    goto SUCCESS;
--- tacacs+-F4.0.4.27a/parse.c	2012-06-29 00:37:06.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/parse.c	2013-09-20 15:56:36.602166871 +0200
@@ -90,6 +90,10 @@
     declare("expires", S_expires);
     declare("file", S_file);
     declare("group", S_group);
+    declare("recurse", S_recurse);
+    declare("value", S_value);
+    declare("first", S_first);
+    declare("last", S_last);
     declare("global", S_global);
     declare("host", S_host);
     declare("ip", S_ip);
@@ -119,6 +123,7 @@
     declare("logging", S_logging);
 #ifdef HAVE_PAM
     declare("PAM", S_pam);
+    declare("match", S_match);
 #endif
     declare("syslog", S_syslog);
 }
@@ -168,6 +173,14 @@
 	return("user");
     case S_group:
 	return("group");
+    case S_recurse:
+	return("recurse");
+    case S_value:
+	return("value");
+    case S_first:
+	return("first");
+    case S_last:
+	return("last");
     case S_host:
 	return("host");
     case S_file:
@@ -263,6 +276,8 @@
 #ifdef HAVE_PAM
     case S_pam:
 	return("PAM");
+    case S_match:
+	return("match");
 #endif
     case S_syslog:
 	return("syslog");
--- tacacs+-F4.0.4.27a/parse.h	2012-06-29 00:37:06.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/parse.h	2013-09-20 15:57:43.001795326 +0200
@@ -88,6 +88,11 @@
 #define S_logging	48
 #ifdef HAVE_PAM
 # define S_pam		49
+# define S_match	50
 #endif
-#define	S_syslog	50
-#define S_aceclnt	51
+#define	S_syslog	51
+#define S_aceclnt	52
+#define S_recurse	53
+#define S_value		54
+#define S_first		55
+#define S_last		56
--- tacacs+-F4.0.4.27a/pwlib.c	2013-08-04 17:56:50.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/pwlib.c	2013-09-16 17:10:32.546490183 +0200
@@ -50,7 +50,7 @@
 static int etc_passwd_file_verify(char *, char *, struct authen_data *);
 static int des_verify(char *, char *);
 #if HAVE_PAM
-static int pam_verify(char *, char *);
+int pam_verify(char *, char *);
 #endif
 static int passwd_file_verify(char *, char *, struct authen_data *, char *);
 
@@ -143,32 +143,49 @@
      * has been issued, attempt to use this password file
      */
     if (cfg_passwd == NULL) {
-	char *file = cfg_get_authen_default();
-	if (file) {
-	    return(passwd_file_verify(name, passwd, data, file));
-	}
+	int method = cfg_get_authen_method();
 
-	/* otherwise, we fail */
-	data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
-	return(0);
+	switch(method)
+	{
+	default:
+		data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+		return 0;
+
+	case S_file:
+		{
+		char *file = cfg_get_authen_default();
+		if (file) 
+	    		return(passwd_file_verify(name, passwd, data, file));
+
+		return 0;
+		}
+
+#if HAVE_PAM
+	case S_pam:
+		cfg_passwd = codestring(S_pam);
+		break;
+#endif
+	}
     }
 
-    /* We have a configured password. Deal with it depending on its type */
 #if HAVE_PAM
-    if (strcmp(cfg_passwd, "PAM") == 0) {
+   if(!strcmp(cfg_passwd, codestring(S_pam))) {
 	/* try to verify the password via PAM */
 	if (!pam_verify(name, passwd)) {
-	    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
-	    return(0);
-	} else
-	    data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+		data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+	    	return 0;
+	} 
+
+	data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
 
 	exp_date = cfg_get_expires(name, recurse);
 	set_expiration_status(exp_date, data);
-	return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
-    }
+	return 1;
+   }
 #endif
 
+    /* We have a configured password. Deal with it depending on its type */
+
     p = tac_find_substring("cleartext ", cfg_passwd);
     if (p != NULL) {
 	if (debug & DEBUG_PASSWD_FLAG)
@@ -595,7 +612,7 @@
  * verify a provided user/password via PAM.
  * return 1 if verified, 0 otherwise.
  */
-static int
+int
 pam_verify(char *user, char *passwd)
 {
     int			err;
--- tacacs+-F4.0.4.27a/tac_plus.h	2012-06-29 00:37:06.000000000 +0200
+++ tacacs+-F4.0.4.27a-t2/tac_plus.h	2013-09-20 15:43:24.246871089 +0200
@@ -39,6 +39,7 @@
 #include <errno.h>
 #include <netdb.h>
 #include <pwd.h>
+#include <grp.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -280,6 +281,8 @@
 #define TAC_IS_USER           1
 #define TAC_PLUS_RECURSE      1
 #define TAC_PLUS_NORECURSE    0
+#define TAC_RECURSE_FIRST     0
+#define TAC_RECURSE_LAST      1
 
 #define DEFAULT_USERNAME "DEFAULT"
 
