"referenced entity .* not found" message fixed to be fatal
[tac_plus.git] / cfgfile.c
index 6eaef53..358e962 100644 (file)
--- a/cfgfile.c
+++ b/cfgfile.c
    FITNESS FOR A PARTICULAR PURPOSE.
 */
 
+
 #include "tac_plus.h"
+
 #include <stdio.h>
+#include <stdlib.h>
 #include <errno.h>
-#include "regexp.h"
+#include <string.h>
+
+#ifndef WITH_INCLUDED_REGEX
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+#endif
+
+#include "cfgfile.h"
+#include "report.h"
+#include "utils.h"
+#include "hash.h"
+#include "parse.h"
+#include "main.h"
+#include "do_author.h"                 /* for "struct identity" */
+
+#ifdef WITH_INCLUDED_REGEX
+#include "tac_regexp.h"
+#endif
+
+
+static void sym_get TAC_ARGS((void));
+static void when_expr_root_init TAC_ARGS((void));
+static void rch TAC_ARGS((void));
+static int parse_entity TAC_ARGS((int entity_type));
+static NODE *parse_svcs TAC_ARGS((void));
+static int parse_conditional_block TAC_ARGS((ENTITY *entity));
+static NODE *parse_cmd_matches TAC_ARGS((void));
+static NODE *parse_attrs TAC_ARGS((void));
+static void getsym TAC_ARGS((void));
+static ENTITY *new_entity TAC_ARGS((int type, char *name, int line));
+static enum eval_result entity_svc_default TAC_ARGS((ENTITY *entity));
+
 
 /*
-   <config>         := <decl>*
+   <config>           := <decl>*
 
-   <decl>           := <top_level_decl> | <user_decl>
+   <decl>             := <top_level_decl> | <entity_decl>
 
-   <top_level_decl> := <authen_default> |
-                       accounting file = <string>
-                       default authorization = permit |
-                       key = <string>
+   <top_level_decl>   := <authen_default> |
+                         accounting file = <string> |
+                         default authorization = permit |
+                         key = <string> |
+                         authorization = ( first | recursive )
 
-   <authen_default> := default authentication = file <filename> 
+   <authen_default>   := default authentication = file <filename>
 #if defined(DB)
-                   | db <string> )
+                         | db <string>
 #endif
-   
-<permission>     := permit | deny
 
-   <filename>       := <string>
+   <permission>       := permit | deny
+
+   <filename>         := <string>
 
-   <password>       := <string>
+   <password>         := <string>
 
-   <user_decl>      := user = <string> {
-                        [ default service = [ permit | deny ] ]
-                        <user_attr>*
-                        <svc>*
-                   }
+   <user_decl>        := user  = <string> {
+                           [ <service_default> ]
+                           <user_attr>*
+                           <svc>*
+                         }
 
-   <password_spec>  := file <filename> | 
-                      skey | 
-                      cleartext <password> |
-                      des <password> |
+   <host_decl>        := host  = <string> {
+                           [ <service_default> ]
+                           <host_attr>*
+                           <svc>*
+                         }
 
-#ifdef USE_PAM         
-                      pam <pam_service> |
-#endif                 
-#if defined(DB)                
-                       db <string>
+   <group_decl>       := group = <string> {
+                           [ <service_default> ]
+                           <group_attr>*
+                           <svc>*
+                         }
+
+   <service_default>  := default service = ( permit | deny | default )
+
+   <password_spec>    := file <filename> |
+                         skey |
+                         cleartext <password> |
+                         des <password> |
+
+#ifdef USE_PAM
+                         pam <pam_service> |
 #endif
-                      nopassword
+#if defined(DB)
+                         db <string>
+#endif
+                         nopassword
 
-   <user_attr>      :=   name     = <string> |
+   <user_attr>       :=  name     = <string> |
                          login    = <password_spec> |
-                        member   = <string> |
-                        expires  = <string> |
-                        arap     = cleartext <string> |
-                        chap     = cleartext <string> |
+                         member   = <string> |
+                         expires  = <string> |
+                         arap     = cleartext <string> |
+                         chap     = cleartext <string> |
 #ifdef MSCHAP
-                        ms-chap  = cleartext <string> |
+                         ms-chap  = cleartext <string> |
+#endif
+                         pap      = cleartext <string> |
+                         pap      = des <string> |
+#ifdef USE_PAM
+                         pap      = pam <pam_service> |
 #endif
-                        pap      = cleartext <string> |
-                        pap      = des <string> |
-#ifdef USE_PAM 
-                        pap      = pam <pam_service> |
-#endif                 
-                        opap     = cleartext <string> |
-                        global   = cleartext <string> |
-                        msg      = <string>
-                        before authorization = <string> |
-                        after authorization = <string>
+                         opap     = cleartext <string> |
+                         global   = cleartext <string> |
+                         msg      = <string>
+                         before authorization = <string> |
+                         after  authorization = <string> |
+                         <when_attr>
+
+   <host_attr>       :=  key      = <string> |
+                         <user_attr>
+
+   <group_attr>      :=  enlist   = <entity_spec> |
+                         key      = <string> |
+                         <user_attr>
+
+   <when_attr>       := member   = <string> |
+                        enlist   = <entity_spec> |
+                        <svc> |
+                        <when_attr_block>
+
+   <when_attr_block> := <when_decl> {
+                          <when_attr>*
+                        }
 
-   <svc>            := <svc_auth> | <cmd_auth>
+   <svc>             := <svc_auth> | <cmd_auth>
 
-   <cmd_auth>       := cmd = <string> {
-                        <cmd-match>*
-                    }
+   <cmd_auth>        := cmd = <string> {
+                          <when_match>*
+                        }
 
-   <cmd-match>      := <permission> <string>
+   <when_match>      := <permission> <string> |
+                        <when_match_block>
 
-   <svc_auth>       := service = ( exec | arap | slip | ppp protocol = <string> {
-                        [ default attribute = permit ]
-                        <attr_value_pair>*
-                    }
+   <when_match_block> := <when_decl> {
+                           <when_match>*
+                         }
 
-   <attr_value_pair> := [ optional ] <string> = <string>
+   <svc_auth>        := service = ( exec | arap | slip | ppp protocol = <string> ) {
+# first matching <svc_auth> is the FINAL one, no further graph scanning occurs!
+                          [ default attribute = permit ]
+                          <when_AV_pair>*
+                        }
 
+   <when_AV_pair>    := [ optional ] <string> = <string> |
+                        <when_AV_pair_block>
+
+   <when_AV_pair_block> := <when_decl> {
+                             <when_AV_pair>*
+                           }
+
+   <when_decl>       := when = <expr>
+
+# to avoid ambiguous precence by forbid of "or" & "and" without parentheses:
+   <expr>            := <entity_spec> |
+                        not <expr> |
+                        '(' <expr_or>  ')' |
+                        '(' <expr_and> ')'
+
+   <expr_or>         := <expr> |
+                        <expr> or  <expr_or>
+
+   <expr_and>        := <expr> |
+                        <expr> and <expr_and>
+
+   <entity_spec>     := ( user | host | group ) <string>
 */
 
 static char sym_buf[MAX_INPUT_LINE_LEN];       /* parse buffer */
@@ -107,102 +200,54 @@ static int sym_line = 1; /* current line number for parsing */
 static FILE *cf = NULL;                /* config file pointer */
 static int sym_error = 0;      /* a parsing error has occurred */
 static int no_user_dflt = 0;   /* default if user doesn't exist */
+                               /* ='default authorization': 0/S_permit */
+static int algorithm_recursive = 0;    /* use newer authorization alogrithm? */
+                               /* 1 if 'authorization = recursive' */
 static char *authen_default = NULL;    /* top level authentication default */
-static int authen_default_method = 0; /*For method check */
+                                       /* ='default authentication' */
+static int authen_default_method = 0;  /* For method check */
+                                       /* ='default authentication' symbol */
 static char *nopasswd_str = "nopassword";
 
-/* A host definition structure. Currently unused, but when we start
-   configuring host-specific information e.g. per-host keys, this is
-   where it should be kept.
-
-   The first 2 fields (name and hash) are used by the hash table
-   routines to hash this structure into a table.  Do not (re)move them */
-
-struct host {
-    char *name;                        /* host name */
-    void *hash;                        /* hash table next pointer */
-    int line;                  /* line number defined on */
-    char *key;                 /* host spesific key */
-    char *type;                        /* host type         */
-};
-
-/* A user or group definition
-
-   The first 2 fields (name and hash) are used by the hash table
-   routines to hash this structure into a table.  Move them at your
-   peril */
-
-struct user {
-    char *name;                        /* username */
-    void *hash;                        /* hash table next pointer */
-    int line;                  /* line number defined on */
-    long flags;                        /* flags field */
-
-#define FLAG_ISUSER  1         /* this structure represents a user */
-#define FLAG_ISGROUP 2         /* this structure represents a group */
-#define FLAG_SEEN    4         /* for circular definition detection */
-
-    char *full_name;           /* users full name */
-    char *login;               /* Login password */
-    int nopasswd;               /* user requires no password */
-    char *global;              /* password to use if none set */
-    char *member;              /* group we are a member of */
-    char *expires;             /* expiration date */
-    char *arap;                        /* our arap secret */
-    char *pap;                 /* our pap secret */
-    char *opap;                        /* our outbound pap secret */
-    char *chap;                        /* our chap secret */
-#ifdef MSCHAP
-    char *mschap;              /* our mschap secret */
-#endif /* MSCHAP */
-    char *msg;                 /* a message for this user */
-    char *before_author;       /* command to run before authorization */
-    char *after_author;                /* command to run after authorization */
-    int svc_dflt;              /* default authorization behaviour for svc or
-                                * cmd */
-    NODE *svcs;                        /* pointer to svc nodes */
-#ifdef MAXSESS
-    int maxsess;               /* Max sessions/user */
-#endif /* MAXSESS */
-    char *time;                /* Timestamp  */
-};
-
-typedef struct host HOST;
-typedef struct user USER;
 
 /* Only the first 2 fields (name and hash) are used by the hash table
    routines to hashh structures into a table.
 */
 
-union hash {
-    struct user u;
-    struct host h;
-};
-
-typedef union hash HASH;
+static void *grouptable[HASH_TAB_SIZE];        /* Table of group declarations */
+static void *usertable[HASH_TAB_SIZE]; /* Table of user declarations */
+static void *hosttable[HASH_TAB_SIZE]; /* Table of host declarations */
 
-void *grouptable[HASH_TAB_SIZE];/* Table of group declarations */
-void *usertable[HASH_TAB_SIZE];        /* Table of user declarations */
-void *hosttable[HASH_TAB_SIZE];        /* Table of host declarations */
 
+struct enlist_entity_item {
+    struct enlist_entity_item *next;
+    int parent_type; char *parent;
+    int  child_type; char * child;     /* will be created when not found (for "enlist") */
+    struct expr *when;
+    int line;
+};
+static struct enlist_entity_item * enlist_entity_list;
+static struct enlist_entity_item **enlist_entity_list_tailp = &enlist_entity_list;
 
-static void
- sym_get();
 
+static void parse_error TAC_ARGS((char *fmt,...)) G_GNUC_PRINTF(1, 2);
 
 #ifdef __STDC__
+
 #include <stdarg.h>            /* ANSI C, variable length args */
 static void
 parse_error(char *fmt,...)
-#else
+
+#else /* __STDC__ */
+
 #include <varargs.h>           /* has 'vararg' definitions */
 /* VARARGS2 */
 static void
 parse_error(fmt, va_alist)
 char *fmt;
-
 va_dcl                         /* no terminating semi-colon */
-#endif
+
+#endif /* __STDC__ */
 {
     char msg[256];             /* temporary string */
     va_list ap;
@@ -220,7 +265,9 @@ va_dcl                              /* no terminating semi-colon */
     tac_exit(1);
 }
 
-char *
+const char *cfg_nodestring TAC_ARGS((int type));
+
+const char *
 cfg_nodestring(type)
     int type;
 {
@@ -250,6 +297,54 @@ cfg_nodestring(type)
     }
 }
 
+const char *entity_type_to_string TAC_ARGS((int entity_type));
+
+const char *
+entity_type_to_string(entity_type)
+int entity_type;
+{
+    switch (entity_type) {
+    case S_user:
+       return ("user");
+    case S_host:
+       return ("host");
+    case S_group:
+       return ("group");
+    }
+    return (NULL);
+}
+
+static void **entity_type_to_hashtable TAC_ARGS((int entity_type));
+
+static void **
+entity_type_to_hashtable(entity_type)
+int entity_type;
+{
+    switch (entity_type) {
+    case S_user:
+       return (usertable);
+    case S_host:
+       return (hosttable);
+    case S_group:
+       return (grouptable);
+    }
+    return (NULL);
+}
+
+void scan_invalidate_entities TAC_ARGS((enum invalidate_scan what));
+
+void
+scan_invalidate_entities(what)
+enum invalidate_scan what;
+{
+    scan_invalidate_entities_hashtable( usertable, what);
+    scan_invalidate_entities_hashtable( hosttable, what);
+    scan_invalidate_entities_hashtable(grouptable, what);
+}
+
+
+static void free_attrs TAC_ARGS((NODE *node));
+
 static void
 free_attrs(node)
 NODE *node;
@@ -257,16 +352,20 @@ NODE *node;
     NODE *next;
 
     while (node) {
+       unlink_expr(node->when);
+       free_expr(node->when);
+       node->when = NULL;
+
        switch (node->type) {
        case N_optarg:
        case N_arg:
            if (debug & DEBUG_CLEAN_FLAG)
                report(LOG_DEBUG, "free_cmd_match %s %s",
                       cfg_nodestring(node->type),
-                      node->value);
+                      (const char *) node->value);
            break;
        default:
-           report(LOG_ERR, "Illegal node type %s for free_attrs", 
+           report(LOG_ERR, "Illegal node type %s for free_attrs",
                   cfg_nodestring(node->type));
            return;
        }
@@ -278,6 +377,8 @@ NODE *node;
     }
 }
 
+static void free_cmd_matches TAC_ARGS((NODE *node));
+
 static void
 free_cmd_matches(node)
 NODE *node;
@@ -288,16 +389,32 @@ NODE *node;
        if (debug & DEBUG_CLEAN_FLAG)
            report(LOG_DEBUG, "free_cmd_match %s %s",
                   cfg_nodestring(node->type),
-                  node->value);
+                  (const char *) node->value);
+
+       unlink_expr(node->when);
+       free_expr(node->when);
+       node->when = NULL;
 
        free(node->value);      /* text */
-       free(node->value1);     /* regexp compiled text */
+
+#ifdef WITH_INCLUDED_REGEX
+
+       free(node->value1);     /* tac_regexp compiled text */
+
+#else /* WITH_INCLUDED_REGEX */
+
+       regfree((regex_t *) node->value1);      /* POSIX regex compiled text */
+
+#endif /* WITH_INCLUDED_REGEX */
+
        next = node->next;
        free(node);
        node = next;
     }
 }
 
+static void free_svcs TAC_ARGS((NODE *node));
+
 static void
 free_svcs(node)
 NODE *node;
@@ -305,12 +422,16 @@ NODE *node;
     NODE *next;
 
     while (node) {
+       unlink_expr(node->when);
+       free_expr(node->when);
+       node->when = NULL;
 
        switch (node->type) {
        case N_svc_cmd:
            if (debug & DEBUG_CLEAN_FLAG)
                report(LOG_DEBUG, "free %s %s",
-                      cfg_nodestring(node->type), node->value);
+                      cfg_nodestring(node->type),
+                      (const char *) node->value);
            free(node->value);  /* cmd name */
            free_cmd_matches(node->value1);
            next = node->next;
@@ -340,86 +461,108 @@ NODE *node;
     }
 }
 
+static void free_enlist_entity_item TAC_ARGS((struct enlist_entity_item *item));
+
+static void
+free_enlist_entity_item(item)
+struct enlist_entity_item *item;
+{
+    free(item->parent);
+    free(item->child);
+    free_expr(item->when);
+}
+
+
+static void free_entity TAC_ARGS((ENTITY *entity));
+
 static void
-free_userstruct(user)
-USER *user;
+free_entity(entity)
+ENTITY *entity;
 {
     if (debug & DEBUG_CLEAN_FLAG)
        report(LOG_DEBUG, "free %s %s",
-              (user->flags & FLAG_ISUSER) ? "user" : "group",
-              user->name);
-
-    if (user->name)
-       free(user->name);
-    if (user->full_name)
-       free(user->full_name);
-    if (user->login)
-       free(user->login);
-    if (user->member)
-       free(user->member);
-    if (user->expires)
-       free(user->expires);
-    if (user->time)
-       free(user->time);
-    if (user->arap)
-       free(user->arap);
-    if (user->chap)
-       free(user->chap);
+               entity_type_to_string(entity->type), entity->name);
+
+    /* function MUST be called while the whole entity is still VALID! */
+    scan_free_entity(entity);
+
+    if (entity->name)
+       free(entity->name);
+    if (entity->full_name)
+       free(entity->full_name);
+    if (entity->login)
+       free(entity->login);
+    if (entity->expires)
+       free(entity->expires);
+    if (entity->time)
+       free(entity->time);
+    if (entity->arap)
+       free(entity->arap);
+    if (entity->chap)
+       free(entity->chap);
 #ifdef MSCHAP
-    if (user->mschap)
-       free(user->mschap);
+    if (entity->mschap)
+       free(entity->mschap);
 #endif /* MSCHAP */
-    if (user->pap)
-       free(user->pap);
-    if (user->opap)
-       free(user->opap);
-    if (user->global)
-       free(user->global);
-    if (user->msg)
-       free(user->msg);
-    if (user->before_author)
-       free(user->before_author);
-    if (user->after_author)
-       free(user->after_author);
-    free_svcs(user->svcs);
+    if (entity->pap)
+       free(entity->pap);
+    if (entity->opap)
+       free(entity->opap);
+    if (entity->global)
+       free(entity->global);
+    if (entity->msg)
+       free(entity->msg);
+    if (entity->before_author)
+       free(entity->before_author);
+    if (entity->after_author)
+       free(entity->after_author);
+    if (entity->key)
+       free(entity->key);
+    free_svcs(entity->svcs);
 }
 
+static void free_hashtable TAC_ARGS((void **hashtable));
+
 static void
-free_hoststruct(host)
-HOST *host;
+free_hashtable(hashtable)
+void **hashtable;
 {
-    if (debug & DEBUG_CLEAN_FLAG)
-       report(LOG_DEBUG, "free %s",
-               host->name);
+    int i;
+    ENTITY *entity,**entityp;
 
-    if (host->name)
-       free(host->name);
-    
-    if (host->key)
-       free(host->key);
-    
-    if (host->type)
-       free(host->type);
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       entityp = (ENTITY **) (hashtable+i);
+       while ((entity = *entityp)) {
+           *entityp = entity->hash;
+           free_entity(entity);
+           free(entity);
+       }
+    }
 }
 
+
 /*
  * Exported routines
  */
 
+void cfg_clean_config TAC_ARGS((void));
+
 /* Free all allocated structures preparatory to re-reading the config file */
 void
 cfg_clean_config()
 {
-    int i;
-    USER *entry, *next;
-    HOST *host_entry,*hn;
+    struct enlist_entity_item *enlist_entity_item;
 
     if (authen_default) {
        free(authen_default);
        authen_default = NULL;
     }
-   
-   if (authen_default_method) {
+
+    if (algorithm_recursive) {
+       algorithm_recursive = 0;
+    }
+
+    if (authen_default_method) {
        authen_default_method = 0;
     }
 
@@ -432,49 +575,27 @@ cfg_clean_config()
        free(session.acctfile);
        session.acctfile = NULL;
     }
-    
+
     if (session.db_acct) {
        free(session.db_acct);
        session.db_acct = NULL;
     }
 
-    /* clean the hosttable */
-    for (i = 0; i < HASH_TAB_SIZE; i++) {
-       host_entry = (HOST *) hosttable[i];
-       while (host_entry) {
-           hn = host_entry->hash;
-           free_hoststruct(host_entry);
-           free(host_entry);
-           host_entry = hn;
-       }
-       hosttable[i] = NULL;
-    }
-
-    /* the grouptable */
-    for (i = 0; i < HASH_TAB_SIZE; i++) {
-       entry = (USER *) grouptable[i];
-       while (entry) {
-           next = entry->hash;
-           free_userstruct(entry);
-           free(entry);
-           entry = next;
-       }
-       grouptable[i] = NULL;
-    }
+    free_hashtable( usertable);
+    free_hashtable( hosttable);
+    free_hashtable(grouptable);
 
-    /* the usertable */
-    for (i = 0; i < HASH_TAB_SIZE; i++) {
-       entry = (USER *) usertable[i];
-       while (entry) {
-           next = entry->hash;
-           free_userstruct(entry);
-           free(entry);
-           entry = next;
-       }
-       usertable[i] = NULL;
+    while (enlist_entity_list) {
+       enlist_entity_item = enlist_entity_list;
+       enlist_entity_list = enlist_entity_item->next;
+       free_enlist_entity_item(enlist_entity_item);
+       free(enlist_entity_item);
     }
+    enlist_entity_list_tailp = &enlist_entity_list;
 }
 
+static int parse_permission TAC_ARGS((void));
+
 static int
 parse_permission()
 {
@@ -490,6 +611,8 @@ parse_permission()
     return (symbol);
 }
 
+static int parse TAC_ARGS((int symbol));
+
 static int
 parse(symbol)
 int symbol;
@@ -505,9 +628,13 @@ int symbol;
     return (0);
 }
 
+static int parse_opt_svc_default TAC_ARGS((void));
+
 static int
 parse_opt_svc_default()
 {
+    int retval;
+
     if (sym_code != S_default) {
        return (0);
     }
@@ -515,14 +642,30 @@ parse_opt_svc_default()
     parse(S_default);
     parse(S_svc);
     parse(S_separator);
-    if (sym_code == S_permit) {
-       parse(S_permit);
-       return (S_permit);
+
+    switch (sym_code) {
+    default:
+       parse_error("expecting 'permit', 'deny' or 'default' but found '%s' on line %d",
+                   sym_buf, sym_line);
+       return (1);
+
+    case S_default:
+       if (!algorithm_recursive) {
+           parse_error("'default service = %s' supported only if set top level 'authorization = recursive', on line %d",
+                       sym_buf, sym_line);
+           return (1);
+       }
+       /* FALLTHRU */
+    case S_permit:
+    case S_deny:
+       retval = sym_code;
+       sym_get();
+       return (retval);
     }
-    parse(S_deny);
-    return (S_deny);
 }
 
+static int parse_opt_attr_default TAC_ARGS((void));
+
 static int
 parse_opt_attr_default()
 {
@@ -536,27 +679,26 @@ parse_opt_attr_default()
     return (S_permit);
 }
 
-static int parse_user();
-static int parse_host();
-
-static void
- rch();
-
 /*
    Parse lines in the config file, creating data structures
    Return 1 on error, otherwise 0 */
 
+static int parse_decls TAC_ARGS((void));
+
 static int
 parse_decls()
 {
-    no_user_dflt = 0; /* default if user doesn't exist */
+    no_user_dflt = 0;          /* default if user doesn't exist */
+    algorithm_recursive = 0;   /* use backward compatible alg. by default */
+    when_expr_root_init();
+    enlist_entity_list_tailp = &enlist_entity_list;
 
     sym_code = 0;
     rch();
 
     bzero(grouptable, sizeof(grouptable));
     bzero(usertable, sizeof(usertable));
-    bzero(hosttable, sizeof(hosttable)); 
+    bzero(hosttable, sizeof(hosttable));
 
     sym_get();
 
@@ -571,17 +713,17 @@ parse_decls()
            sym_get();
            parse(S_file);
            parse(S_separator);
-           if (session.acctfile) 
+           if (session.acctfile)
                free(session.acctfile);
            session.acctfile = tac_strdup(sym_buf);
            sym_get();
            continue;
 
-#ifdef DB      
+#ifdef DB
        case S_db_accounting:
            sym_get();
            parse(S_separator);
-           if (session.db_acct) 
+           if (session.db_acct)
                free(session.db_acct);
            session.db_acct = tac_strdup(sym_buf);
            sym_get();
@@ -608,13 +750,13 @@ parse_decls()
                parse(S_separator);
 
                switch(sym_code) {
-                
+
                case S_file:
 #ifdef DB
                case S_db:
 #endif
 #ifdef USE_LDAP
-               case S_ldap;
+               case S_ldap:
 #endif
 #ifdef USE_PAM
                case S_pam:
@@ -637,11 +779,31 @@ parse_decls()
                parse(S_separator);
                parse(S_permit);
                no_user_dflt = S_permit;
-               report(LOG_INFO, 
+               report(LOG_INFO,
                       "default authorization = permit is now deprecated. Please use user = DEFAULT instead");
                continue;
            }
 
+       case S_authorization:
+           sym_get();
+           parse(S_separator);
+           switch (sym_code) {
+           default:
+               parse_error("expecting 'first' or 'recursive' but found '%s' on line %d",
+                           sym_buf, sym_line);
+               return (1);
+
+           case S_first:
+               parse(S_first);
+               algorithm_recursive = 0;
+               continue;
+
+           case S_recursive:
+               parse(S_recursive);
+               algorithm_recursive = 1;
+               continue;
+           }
+
        case S_key:
            /* Process a key declaration. */
            sym_get();
@@ -655,18 +817,13 @@ parse_decls()
            session.keyline = sym_line;
            sym_get();
            continue;
-       
-       case S_host:
-           parse_host();
-           continue;
-       
+
        case S_user:
+       case S_host:
        case S_group:
-           parse_user();
+           parse_entity(sym_code);
            continue;
 
-           /* case S_host: parse_host(); continue; */
-
        default:
            parse_error("Unrecognised token %s on line %d", sym_buf, sym_line);
            return (1);
@@ -674,8 +831,6 @@ parse_decls()
     }
 }
 
-static NODE *parse_svcs();
-
 /* Assign a value to a field. Issue an error message and return 1 if
    it's already been assigned. This is a macro because I was sick of
    repeating the same code fragment over and over */
@@ -688,199 +843,563 @@ sym_get(); parse(S_separator); if (field) { \
     } \
     field = tac_strdup(sym_buf);
 
-static int
-parse_host()
-{
-    HOST *h;
-    HOST *host = (HOST *) tac_malloc(sizeof(HOST));
-    int save_sym;
-    char buf[MAX_INPUT_LINE_LEN];
+static struct expr *when_expr_root;
+#define WHEN_EXPR_ROOT_SANE() \
+       (when_expr_root && !when_expr_root->next && when_expr_root->type==S_and)
+#define WHEN_EXPR_ROOT_EMPTY() \
+       (WHEN_EXPR_ROOT_SANE() && !when_expr_root->u.and_or.child_first)
 
-    bzero(host, sizeof(HOST));
+static struct expr *parse_expr_node TAC_ARGS((int single_item));
 
-    sym_get();
-    parse(S_separator);
-    host->name = tac_strdup(sym_buf);
-    host->line = sym_line;
-    
-    h = hash_add_entry(hosttable, (void *) host);
-    
-    if (h) {
-        parse_error("multiply defined %s on lines %d and %d",
-                    host->name, h->line, sym_line);
-        return (1);
-    }
+static struct expr *
+parse_expr_node(single_item)
+int single_item;
+{
+    struct expr *expr_root = NULL;
+    struct expr **succ_exprp = &expr_root;
+    struct expr *expr;
 
-    sym_get();
-    parse(S_openbra);
-    
     while (1) {
        switch (sym_code) {
-        case S_eof:
-            return (0);
-       case S_key:
-           ASSIGN(host->key);
-            sym_get();
-            continue;
-       case S_type:
-           ASSIGN(host->type);
-            sym_get();
-            continue;
-       
-       case S_closebra:
-            parse(S_closebra);
-            return (0);
+
+       case S_not:
+           expr = (struct expr *) tac_malloc(sizeof(struct expr));
+           expr->line = sym_line;
+           *succ_exprp = expr;
+           expr->next = NULL;
+           succ_exprp = &expr->next;
+           expr->type = S_not;
+           sym_get();
+           expr->u.not.child = parse_expr_node(1 /* single_item */);
+           if (!expr->u.not.child) {
+               free_expr(expr_root);
+               return (NULL);
+           }
+           break;
+
+       case S_user:
+       case S_host:
+       case S_group:
+           expr = (struct expr *) tac_malloc(sizeof(struct expr));
+           expr->line = sym_line;
+           *succ_exprp = expr;
+           expr->next = NULL;
+           succ_exprp = &expr->next;
+           expr->type = sym_code;
+           sym_get();
+           expr->u.entity.name = tac_strdup(sym_buf);
+           sym_get();
+           expr->u.entity.entity = NULL;       /* not known yet */
+           break;
+
+       case S_openparen:
+           sym_get();
+           expr = parse_expr_node(0 /* single_item */);
+           *succ_exprp = expr;
+           parse(S_closeparen);
+
+           if (expr->next) {
+               report(LOG_ERR, "Illegal filled next field of parsed parenthesed expr");
+               free_expr(expr_root);
+               return (NULL);
+           }
+           succ_exprp = &expr->next;
+           break;
 
        default:
-           parse_error("Unrecognised keyword %s for host %s on line %d",
-                        sym_buf, host->name,sym_line);
+           parse_error("expecting 'not', 'user', 'host', 'group' or '(' but found '%s' on line %d",
+                   sym_buf, sym_line);
+           free_expr(expr_root);
+           return (NULL);
+       }
 
-            return (0);
-        }
-    } /* while */
-} /* finish parse_host */
+       if (single_item)                /* used by 'not' operator with high precedence */
+           return (expr_root);
 
+        switch (sym_code) {
 
-static int
-parse_user()
-{
-    USER *n;
-    int isuser;
-    USER *user = (USER *) tac_malloc(sizeof(USER));
-    int save_sym;
-    char **fieldp;
-    char buf[MAX_INPUT_LINE_LEN];
+       case S_and:
+       case S_or:
+           if (expr_root->type == (sym_code==S_and ? S_or : S_and)) {
+               parse_error("ambiguous use of 'and' together with 'or', parentheses required on line %d",
+                       sym_line);
+               free_expr(expr_root);
+               return (NULL);
+           }
+           if (expr_root->type != sym_code) {
+               expr = (struct expr *) tac_malloc(sizeof(struct expr));
+               expr->line = sym_line;
+               expr->next = NULL;
+               expr->type = sym_code;
+               expr->u.and_or.child_first = expr_root;
+               expr_root = expr;
+           }
+           sym_get();
+           continue;
+       }
 
-    bzero(user, sizeof(USER));
+       return(expr_root);
+    }
+}
 
-    isuser = (sym_code == S_user);
+static struct expr *parse_when_decl TAC_ARGS((void));
 
-    sym_get();
-    parse(S_separator);
-    user->name = tac_strdup(sym_buf);
-    user->line = sym_line;
-
-    if (isuser) {
-       user->flags |= FLAG_ISUSER;
-       n = hash_add_entry(usertable, (void *) user);
-    } else {
-       user->flags |= FLAG_ISGROUP;
-       n = hash_add_entry(grouptable, (void *) user);
+static struct expr *
+parse_when_decl()
+{
+    parse(S_when);
+    if (!algorithm_recursive) {
+       parse_error("'when' conditionals supported only if set top level 'authorization = recursive', on line %d",
+                   sym_line);
+       return (NULL);
     }
+    parse(S_separator);
+    return (parse_expr_node(0 /* single_item */));
+}
 
-    if (n) {
-       parse_error("multiply defined %s %s on lines %d and %d",
-                   isuser ? "user" : "group",
-                   user->name, n->line, sym_line);
+static void when_expr_root_init TAC_ARGS((void));
+
+static void
+when_expr_root_init()
+{
+    free_expr(when_expr_root);
+    when_expr_root = new_expr(S_and);
+}
+
+static int push_parsed_when_decl TAC_ARGS((void));
+
+static int
+push_parsed_when_decl()
+{
+    struct expr *parsed_expr;
+
+    parsed_expr = parse_when_decl();
+    if (!parsed_expr)
+       return (1);
+    if (!WHEN_EXPR_ROOT_SANE()) {
+       report(LOG_ERR, "INTERNAL: when_expr_root not valid during push_parsed_when_decl()!");
+       free_expr(parsed_expr);
        return (1);
     }
-    sym_get();
-    parse(S_openbra);
+    if (parsed_expr->next) {
+       report(LOG_ERR, "INTERNAL: Illegal filled next field of parsed expr");
+       free_expr(parsed_expr);
+       return (1);
+    }
+    parsed_expr->next = when_expr_root->u.and_or.child_first;
+    when_expr_root->u.and_or.child_first = parsed_expr;
+    when_expr_root->line = parsed_expr->line;
+    return (0);
+}
 
-    /* Is the default deny for svcs or cmds to be overridden? */
-    user->svc_dflt = parse_opt_svc_default();
+static int pop_when_decl TAC_ARGS((void));
 
-    while (1) {
-       switch (sym_code) {
-       case S_eof:
-           return (0);
-       
-       case S_time:
-          ASSIGN(user->time);
-          sym_get(); 
-          continue;
+static int
+pop_when_decl()
+{
+    struct expr *first_expr;
 
-       case S_before:
-           sym_get();
-           parse(S_authorization);
-           if (user->before_author)
-               free(user->before_author);
-           user->before_author = tac_strdup(sym_buf);
-           sym_get();
-           continue;
+    if (!WHEN_EXPR_ROOT_SANE()) {
+       report(LOG_ERR, "INTERNAL: when_expr_root not valid during pop_when_decl()!");
+       return (1);
+    }
+    first_expr = when_expr_root->u.and_or.child_first;
+    if (!first_expr) {
+       report(LOG_ERR, "No expr in stack and pop_when_decl() called");
+       return (1);
+    }
+    when_expr_root->u.and_or.child_first = first_expr->next;
+    first_expr->next = NULL;
+    free_expr(first_expr);
+    return (0);
+}
 
-       case S_after:
-           sym_get();
-           parse(S_authorization);
-           if (user->after_author)
-               free(user->after_author);
-           user->after_author = tac_strdup(sym_buf);
-           sym_get();
-           continue;
+static struct expr *copy_current_when_decl TAC_ARGS((void));
 
-       case S_svc:
-       case S_cmd:
-           
-           if (user->svcs) {   
-               /* 
-                * Already parsed some services/commands. Thanks to Gabor Kiss
-                * who found this bug.
-                */
-               NODE *p;
-               for (p=user->svcs; p->next; p=p->next) 
-                   /* NULL STMT */;
-               p->next = parse_svcs();
-           } else {
-               user->svcs = parse_svcs();
-           }
-           continue;
+static struct expr *
+copy_current_when_decl()
+{
+    return (dupl_expr(when_expr_root));
+}
 
-       case S_login:
-           if (user->login) {
-               parse_error("Duplicate value for %s %s and %s on line %d",
-                           codestring(sym_code), user->login,
-                           sym_buf, sym_line);
-               tac_exit(1);
-           }
-           sym_get();
-           parse(S_separator);
-           switch(sym_code) {
+static struct expr *when_expr_dungeon;
 
-           case S_skey:
-               user->login = tac_strdup(sym_buf);
-               break;
+static void starve_when_decl TAC_ARGS((void));
 
-           case S_nopasswd:
+static void
+starve_when_decl()
+{
+    if (!WHEN_EXPR_ROOT_SANE()) {
+       report(LOG_WARNING, "INTERNAL: when_expr_root not sane during starve_when_decl!");
+    }
+    when_expr_root->next = when_expr_dungeon;
+    when_expr_dungeon = when_expr_root;
+    when_expr_root = NULL;
+    when_expr_root_init();
+}
+
+static int feed_when_decl TAC_ARGS((void));
+
+static int
+feed_when_decl()
+{
+    if (!when_expr_dungeon) {
+       report(LOG_ERR, "INTERNAL: No expr in dungeon and feed_when_decl() called");
+       return (1);
+    }
+    if (!WHEN_EXPR_ROOT_EMPTY()) {
+       report(LOG_WARNING, "INTERNAL: Some 'when' expression found still pushed in dungeon during feed_when_decl()!");
+    }
+    free_expr(when_expr_root);
+    when_expr_root = when_expr_dungeon;
+    when_expr_dungeon = when_expr_dungeon->next;
+    when_expr_root->next = NULL;
+    return (0);
+}
+
+ENTITY *entity_lookup TAC_ARGS((int type, const char *name));
+
+ENTITY *
+entity_lookup(type, name)
+int type;
+const char *name;
+{
+    return (hash_lookup(entity_type_to_hashtable(type), name));
+}
+
+static int enlist_entity_connect TAC_ARGS((void));
+
+static int
+enlist_entity_connect()
+{
+    struct enlist_entity_item *item;
+    ENTITY *parent_entity, *child_entity;
+
+    while ((item=enlist_entity_list)) {
+
+       parent_entity = entity_lookup(item->parent_type, item->parent);
+       if (!parent_entity) {
+           parse_error("Entity %s %s not defined, referenced as parent on line %d",
+                   entity_type_to_string(item->parent_type), item->parent, item->line);
+           return (1);
+       }
+       child_entity = entity_lookup(item-> child_type, item-> child);
+       if (!child_entity) {
+           child_entity = new_entity(item->child_type, item->child, item->line);
+           if (!child_entity)
+               return (1);             /* 'hash_add_entry()' conflict */
+           item->child = NULL;         /* don't free string ref'ed from 'child_entity'! */
+       }
+
+       if (!enlist_entity_direct(parent_entity, child_entity, item->when))
+           return (1);         /* entities not found */
+
+       enlist_entity_list = item->next;
+       item->when = NULL;
+       free_enlist_entity_item(item);
+       free(item);
+    }
+    enlist_entity_list_tailp = &enlist_entity_list;
+    return (0);
+}
+
+static void enlist_entity TAC_ARGS((int parent_type, const char *parent, int child_type, const char *child));
+
+static void
+enlist_entity(parent_type, parent, child_type, child)
+int parent_type;
+const char *parent;
+int child_type;
+const char *child;
+{
+    struct enlist_entity_item *item =
+               (struct enlist_entity_item *) tac_malloc(sizeof(struct enlist_entity_item));
+
+    item->next = NULL;
+    *enlist_entity_list_tailp = item;
+    enlist_entity_list_tailp = &item->next;
+
+    item->parent_type = parent_type;
+    item->parent = tac_strdup(parent);
+    item-> child_type =  child_type;
+    item->child = tac_strdup(child);
+    item->when = copy_current_when_decl();
+    item->line = sym_line;
+}
+
+static int parse_entity_spec TAC_ARGS((void));
+
+/* returns 0 for error, otherwise S_user, S_host or S_group; sym_buf filled */
+static int
+parse_entity_spec()
+{
+    int retval;
+
+    if (sym_code != S_user
+     && sym_code != S_host
+     && sym_code != S_group
+       ) {
+       parse_error("Expecting 'user', 'host' or ' group' as entity specification, found %s on line %d",
+                   sym_buf, sym_line);
+       return (0);
+    }
+
+    retval = sym_code;
+    sym_get();
+
+    return (retval);
+}
+
+static int parse_conditional_block_item TAC_ARGS((ENTITY *entity));
+
+static int
+parse_conditional_block_item(entity)
+ENTITY *entity;
+{
+    switch (sym_code) {
+    case S_eof:
+       return (1);
+
+    /* case S_closebra: not needed, handled by our caller parse_conditional_block() */
+
+    default:
+       parse_error("Unrecognised keyword %s for entity on line %d",
+                   sym_buf, sym_line);
+       return (1);
+
+    case S_member:
+       sym_get();
+       parse(S_separator);
+       enlist_entity(S_group, sym_buf, entity->type, entity->name);
+       sym_get();
+       break;
+
+    case S_enlist: {
+       int parsed_entity_type;
+
+       if (entity->type != S_group) {
+           parse_error("'enlist' keyword allowed only in 'group' section on line %d",
+                       sym_line);
+           return (1);
+       }
+       sym_get();
+       parse(S_separator);
+       parsed_entity_type = parse_entity_spec();
+       if (!parsed_entity_type)
+           return (1);
+       enlist_entity(entity->type, entity->name, parsed_entity_type, sym_buf);
+       sym_get();
+       break;
+       }
+
+    case S_svc:
+    case S_cmd:
+
+       if (entity->svcs) {
+           /*
+            * Already parsed some services/commands. Thanks to Gabor Kiss
+            * who found this bug.
+            */
+           NODE *p;
+           for (p=entity->svcs; p->next; p=p->next)
+               /* NULL STMT */;
+           p->next = parse_svcs();
+       } else {
+           entity->svcs = parse_svcs();
+       }
+       break;
+
+    case S_when:
+       if (parse_conditional_block(entity))
+           return (1);
+       break;
+    }
+
+    return (0);
+}
+
+static int parse_conditional_block TAC_ARGS((ENTITY *entity));
+
+static int
+parse_conditional_block(entity)
+ENTITY *entity;
+{
+    int retval = -1 /* GCC paranoia */;
+
+    if (push_parsed_when_decl())
+       return (1);
+    parse(S_openbra);
+
+    while (1) {
+       if (sym_code == S_closebra) {
+           sym_get();
+           retval = 0;         /* success */
+           break;
+       }
+
+       if (parse_conditional_block_item(entity)) {
+           retval = 1;         /* failure */
+           break;
+       }
+    }
+
+    if (pop_when_decl())
+       return (1);
+
+    return (retval);
+}
+
+/* passed 'name' WILL be directly stored to returned ENTITY, don't touch it! */
+
+static ENTITY *new_entity TAC_ARGS((int type, char *name, int line));
+
+static ENTITY *
+new_entity(type, name, line)
+int type;
+char *name;
+int line;
+{
+    ENTITY *entity = (ENTITY *) tac_malloc(sizeof(ENTITY));
+    ENTITY *hash_conflict;
+
+    bzero(entity, sizeof(ENTITY));
+    tac_list_init(&entity->to_parent_membership_list);
+    tac_list_init(&entity->to_child_membership_list );
+    entity->to_child_membership_num = 0;
+    scan_init_entity(entity);
+
+    entity->type = type;
+    entity->name = name;
+    entity->line = line;
+
+    hash_conflict = hash_add_entry(entity_type_to_hashtable(type), (void *) entity);
+    if (hash_conflict) {
+       parse_error("multiply defined %s %s on lines %d and %d",
+                   entity_type_to_string(type),
+                   entity->name, hash_conflict->line, sym_line);
+       free (entity);
+       return (NULL);
+    }
+
+    return (entity);
+}
+
+static int parse_entity TAC_ARGS((int entity_type));
+
+static int
+parse_entity(entity_type)
+int entity_type;
+{
+    ENTITY *entity;
+    int save_sym;
+    char **fieldp = NULL /* GCC paranoia */;
+    char buf[MAX_INPUT_LINE_LEN];
+
+    sym_get();
+    parse(S_separator);
+
+    entity = new_entity(entity_type, tac_strdup(sym_buf) /* name */, sym_line /* line */);
+    if (!entity)
+       return (1);             /* 'hash_add_entry()' conflict, 'tac_strdup(sym_buf)' leaked! */
+
+    sym_get();
+    parse(S_openbra);
+
+    /* Is the default deny for svcs or cmds to be overridden? */
+    entity->svc_dflt = parse_opt_svc_default();
+
+    while (1) {
+       if (entity_type != S_user)
+           switch (sym_code) {
+           case S_key:
+               ASSIGN(entity->key);
+               sym_get();
+               continue;
+           }
+
+       switch (sym_code) {
+       case S_eof:
+           return (0);
+
+       case S_time:
+          ASSIGN(entity->time);
+          sym_get();
+          continue;
+
+       case S_before:
+           sym_get();
+           parse(S_authorization);
+           if (entity->before_author)
+               free(entity->before_author);
+           entity->before_author = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+       case S_after:
+           sym_get();
+           parse(S_authorization);
+           if (entity->after_author)
+               free(entity->after_author);
+           entity->after_author = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+       case S_login:
+           if (entity->login) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(sym_code), entity->login,
+                           sym_buf, sym_line);
+               tac_exit(1);
+           }
+           sym_get();
+           parse(S_separator);
+           switch(sym_code) {
+
+           case S_skey:
+               entity->login = tac_strdup(sym_buf);
+               break;
+
+           case S_nopasswd:
                /* set to dummy string, so that we detect a duplicate
                 * password definition attempt
                 */
-               user->login = tac_strdup(nopasswd_str);
-               user->nopasswd = 1;
+               entity->login = tac_strdup(nopasswd_str);
+               entity->nopasswd = 1;
                break;
-               
+
            case S_file:
            case S_cleartext:
            case S_des:
-#ifdef USE_PAM 
-           case S_pam: 
-#endif /* USE_PAM */           
+#ifdef USE_PAM
+           case S_pam:
+#endif /* USE_PAM */
 #ifdef DB
            case S_db:
 #endif /* USE DB */
                sprintf(buf, "%s ", sym_buf);
                sym_get();
                strcat(buf, sym_buf);
-               user->login = tac_strdup(buf);
+               entity->login = tac_strdup(buf);
                break;
-       
+
            default:
 #ifdef USE_PAM
                parse_error(
  "expecting 'file', 'cleartext', 'pam'.'nopassword', 'skey', or 'des' keyword after 'login =' on line %d",
                            sym_line);
-#else  
+#else
                parse_error(
- "expecting 'file', 'cleartext', 'nopassword', 'skey', or 'des' keyword after 'login =' on line %d", 
+ "expecting 'file', 'cleartext', 'nopassword', 'skey', or 'des' keyword after 'login =' on line %d",
                            sym_line);
-#endif /* USE_PAM */                   
+#endif /* USE_PAM */
            }
            sym_get();
            continue;
 
        case S_pap:
-           if (user->pap) {
+           if (entity->pap) {
                parse_error("Duplicate value for %s %s and %s on line %d",
-                           codestring(sym_code), user->pap,
+                           codestring(sym_code), entity->pap,
                            sym_buf, sym_line);
                tac_exit(1);
            }
@@ -890,17 +1409,17 @@ parse_user()
 
            case S_cleartext:
            case S_des:
-#ifdef USE_PAM 
+#ifdef USE_PAM
            case S_pam:
-#endif /*USE_PAM */                    
+#endif /*USE_PAM */
                sprintf(buf, "%s ", sym_buf);
                sym_get();
                strcat(buf, sym_buf);
-               user->pap = tac_strdup(buf);
-               break;  
+               entity->pap = tac_strdup(buf);
+               break;
 
                sprintf(buf, "%s ", sym_buf);
-               user->pap = tac_strdup(buf);
+               entity->pap = tac_strdup(buf);
                break;
 
            default:
@@ -910,7 +1429,7 @@ parse_user()
  sym_line);
 #else
                parse_error(
- "expecting 'cleartext', or 'des' keyword after 'pap =' on line %d", 
+ "expecting 'cleartext', or 'des' keyword after 'pap =' on line %d",
  sym_line);
 #endif /*USE_PAM */
            }
@@ -918,23 +1437,17 @@ parse_user()
            continue;
 
        case S_name:
-           ASSIGN(user->full_name);
-           sym_get();
-           continue;
-
-       case S_member:
-           ASSIGN(user->member);
+           ASSIGN(entity->full_name);
            sym_get();
            continue;
-       
 
        case S_expires:
-           ASSIGN(user->expires);
+           ASSIGN(entity->expires);
            sym_get();
            continue;
-       
+
        case S_message:
-           ASSIGN(user->msg);
+           ASSIGN(entity->msg);
            sym_get();
            continue;
 
@@ -946,26 +1459,38 @@ parse_user()
        case S_opap:
        case S_global:
            save_sym = sym_code;
-           sym_get(); 
-           parse(S_separator); 
+           sym_get();
+           parse(S_separator);
            sprintf(buf, "%s ", sym_buf);
            parse(S_cleartext);
            strcat(buf, sym_buf);
 
-           if (save_sym == S_arap)
-               fieldp = &user->arap;
-           if (save_sym == S_chap)
-               fieldp = &user->chap;
+           switch (save_sym) {
+           case S_arap:
+               fieldp = &entity->arap;
+               break;
+           case S_chap:
+               fieldp = &entity->chap;
+               break;
 #ifdef MSCHAP
-           if (save_sym == S_mschap)
-               fieldp = &user->mschap;
+           case S_mschap:
+               fieldp = &entity->mschap;
+               break;
 #endif /* MSCHAP */
-           if (save_sym == S_pap)
-               fieldp = &user->pap;
-           if (save_sym == S_opap)
-               fieldp = &user->opap;
-           if (save_sym == S_global)
-               fieldp = &user->global;
+           case S_pap:
+               fieldp = &entity->pap;
+               break;
+           case S_opap:
+               fieldp = &entity->opap;
+               break;
+           case S_global:
+               fieldp = &entity->global;
+               break;
+           default:
+               report(LOG_ERR, "INTERNAL: fieldp not recognized (on line %d)",
+                       sym_line);
+               continue;
+           }
 
            if (*fieldp) {
                parse_error("Duplicate value for %s %s and %s on line %d",
@@ -982,31 +1507,29 @@ parse_user()
 
 #ifdef MAXSESS
        case S_maxsess:
-           sym_get(); 
+           sym_get();
            parse(S_separator);
-           if (sscanf(sym_buf, "%d", &user->maxsess) != 1) {
+           if (sscanf(sym_buf, "%d", &entity->maxsess) != 1) {
                parse_error("expecting integer, found '%s' on line %d",
                    sym_buf, sym_line);
            }
            sym_get();
            continue;
 #endif /* MAXSESS */
+
        default:
            if (STREQ(sym_buf, "password")) {
                fprintf(stderr,
                        "\npassword = <string> is obsolete. Use login = des <string>\n");
            }
-           parse_error("Unrecognised keyword %s for user on line %d",
-                       sym_buf, sym_line);
 
-           return (0);
+           if (parse_conditional_block_item(entity))
+               return (0);             /* error message already printed */
        }
     }
 }
 
-static NODE *parse_attrs();
-static NODE *parse_cmd_matches();
+static NODE *parse_svcs TAC_ARGS((void));
 
 static NODE *
 parse_svcs()
@@ -1034,11 +1557,16 @@ parse_svcs()
 
        sym_get();
        parse(S_openbra);
-
+       starve_when_decl();
        result->value1 = parse_cmd_matches();
+       parse(S_closebra);
+       if (feed_when_decl())
+               tac_exit(1);            /* no error return possibility */
+
        result->type = N_svc_cmd;
+       result->when = copy_current_when_decl();
+       expr_sink_register(result->when);
 
-       parse(S_closebra);
        result->next = parse_svcs();
        return (result);
     }
@@ -1075,82 +1603,157 @@ parse_svcs()
        result->value1 = tac_strdup(sym_buf);
        break;
     }
+
     sym_get();
     parse(S_openbra);
+    starve_when_decl();
+
     result->dflt = parse_opt_attr_default();
     result->value = parse_attrs();
+
     parse(S_closebra);
+    feed_when_decl();
+
+    result->when = copy_current_when_decl();
+    expr_sink_register(result->when);
+
     result->next = parse_svcs();
     return (result);
 }
 
-/*  <cmd-match>         := <permission> <string> */
+/*  <cmd_match>         := <permission> <string> */
+
+static NODE *parse_cmd_matches TAC_ARGS((void));
 
 static NODE *
 parse_cmd_matches()
 {
+    NODE *retval = NULL, **succp = &retval;
     NODE *result;
 
-    if (sym_code != S_permit && sym_code != S_deny) {
-       return (NULL);
-    }
-    result = (NODE *) tac_malloc(sizeof(NODE));
+    for (;;) {
+       switch (sym_code) {
+       default:
+           return (retval);
 
-    bzero(result, sizeof(NODE));
-    result->line = sym_line;
+       case S_when:
+           if (push_parsed_when_decl())
+               tac_exit(1);            /* no error return possibility */
+           parse(S_openbra);
+           result = parse_cmd_matches();
+           parse(S_closebra);
+           if (pop_when_decl())
+               tac_exit(1);            /* no error return possibility */
+           break;
 
-    result->type = (parse_permission() == S_permit) ? N_permit : N_deny;
-    result->value = tac_strdup(sym_buf);
+       case S_permit:
+       case S_deny:
 
-    result->value1 = (void *) regcomp(result->value);
-    if (!result->value1) {
-       report(LOG_ERR, "in regular expression %s on line %d",
-              sym_buf, sym_line);
-       tac_exit(1);
-    }
-    sym_get();
+           result = (NODE *) tac_malloc(sizeof(NODE));
 
-    result->next = parse_cmd_matches();
+           bzero(result, sizeof(NODE));
+           result->line = sym_line;
 
-    return (result);
+           result->type = (parse_permission() == S_permit) ? N_permit : N_deny;
+           result->value = tac_strdup(sym_buf);
+
+#ifdef WITH_INCLUDED_REGEX
+
+           result->value1 = (void *) tac_regcomp(result->value);
+
+#else /* WITH_INCLUDED_REGEX */
+
+           result->value1 = tac_malloc(sizeof(regex_t));
+           if (regcomp(result->value1, result->value /* regex */, REG_NOSUB /* cflags */)) {
+               free(result->value1);
+               result->value1 = NULL;
+           }
+
+#endif /* WITH_INCLUDED_REGEX */
+
+           if (!result->value1) {
+               report(LOG_ERR, "in regular expression %s on line %d",
+                      sym_buf, sym_line);
+               tac_exit(1);
+           }
+           sym_get();
+
+           result->when = copy_current_when_decl();
+           expr_sink_register(result->when);
+
+           result->next = NULL;
+       }
+       *succp = result;
+       while (result->next)
+           result = result->next;      /* skip parsed chain from parse_cmd_matches() */
+       succp = &result->next;
+    }
+    /* NOTREACHED */
 }
 
+static NODE *parse_attrs TAC_ARGS((void));
+
 static NODE *
 parse_attrs()
 {
+    NODE *retval = NULL, **succp = &retval;
     NODE *result;
     char buf[MAX_INPUT_LINE_LEN];
-    int optional = 0;
+    int optional;
 
-    if (sym_code == S_closebra) {
-       return (NULL);
-    }
-    result = (NODE *) tac_malloc(sizeof(NODE));
+    for (;;) {
+       optional = 0;
 
-    bzero(result, sizeof(NODE));
-    result->line = sym_line;
+       switch (sym_code) {
+       case S_closebra:
+           return (retval);
 
-    if (sym_code == S_optional) {
-       optional++;
-       sym_get();
-    }
-    result->type = optional ? N_optarg : N_arg;
+       case S_when:
+           if (push_parsed_when_decl())
+               tac_exit(1);            /* no error return possibility */
+           parse(S_openbra);
+           result = parse_attrs();
+           parse(S_closebra);
+           if (pop_when_decl())
+               tac_exit(1);            /* no error return possibility */
+           break;
 
-    strcpy(buf, sym_buf);
-    parse(S_string);
-    strcat(buf, sym_buf);
-    parse(S_separator);
-    strcat(buf, sym_buf);
-    parse(S_string);
+       case S_optional:
+           optional = 1;
+           sym_get();
+           /* FALLTHRU */
+       default:
+           result = (NODE *) tac_malloc(sizeof(NODE));
 
-    result->value = tac_strdup(buf);
-    result->next = parse_attrs();
-    return (result);
+           bzero(result, sizeof(NODE));
+           result->line = sym_line;
+
+           result->type = optional ? N_optarg : N_arg;
+
+           strcpy(buf, sym_buf);
+           parse(S_string);
+           strcat(buf, sym_buf);
+           parse(S_separator);
+           strcat(buf, sym_buf);
+           parse(S_string);
+
+           result->value = tac_strdup(buf);
+
+           result->when = copy_current_when_decl();
+           expr_sink_register(result->when);
+
+           result->next = NULL;
+       }
+       *succp = result;
+       while (result->next)
+           result = result->next;      /* skip parsed chain from parse_attrs() */
+       succp = &result->next;
+    }
+    /* NOTREACHED */
 }
 
 
-static void
- getsym();
+static void sym_get TAC_ARGS((void));
 
 static void
 sym_get()
@@ -1163,9 +1766,11 @@ sym_get()
     }
 }
 
+static char *sym_buf_add TAC_ARGS((int c));
+
 static char *
 sym_buf_add(c)
-char c;
+int c;                         /* promoted "char" type */
 {
     if (sym_pos >= MAX_INPUT_LINE_LEN) {
        sym_buf[MAX_INPUT_LINE_LEN-1] = '\0';
@@ -1179,7 +1784,9 @@ char c;
     sym_buf[sym_pos++] = c;
     return(sym_buf);
 }
-    
+
+static void getsym TAC_ARGS((void));
+
 static void
 getsym()
 {
@@ -1220,6 +1827,18 @@ next:
        rch();
        return;
 
+    case '(':
+       strcpy(sym_buf, "(");
+       sym_code = S_openparen;
+       rch();
+       return;
+
+    case ')':
+       strcpy(sym_buf, ")");
+       sym_code = S_closeparen;
+       rch();
+       return;
+
     case '#':
        while ((sym_ch != '\n') && (sym_ch != EOF))
            rch();
@@ -1246,7 +1865,7 @@ next:
                        rch();
                        return;
                    }
-                   
+
                    /* fall through */
                case '"':
                    if (!sym_buf_add(sym_ch)) {
@@ -1304,6 +1923,8 @@ next:
     }
 }
 
+static void rch TAC_ARGS((void));
+
 static void
 rch()
 {
@@ -1318,96 +1939,105 @@ rch()
 }
 
 
-/* For a user or group, find the value of a field. Does not recurse. */
-VALUE
-get_value(user, field)
-USER *user;
+static VALUE get_value TAC_ARGS((ENTITY *entity, int field));
+
+/* Find the value of a field. Does not recurse. */
+static VALUE
+get_value(entity, field)
+ENTITY *entity;
 int field;
 {
     VALUE v;
 
+    v.pval = NULL;     /* do both just for sure... */
     v.intval = 0;
 
-    if (!user) {
-       parse_error("get_value: illegal user");
+    if (!entity) {
+       parse_error("get_value: illegal entity");
        return (v);
     }
     switch (field) {
 
     case S_name:
-       v.pval = user->name;
+       v.pval = entity->name;
        break;
 
     case S_login:
-       v.pval = user->login;
+       v.pval = entity->login;
        break;
 
     case S_global:
-       v.pval = user->global;
-       break;
-
-    case S_member:
-       v.pval = user->member;
+       v.pval = entity->global;
        break;
 
     case S_expires:
-       v.pval = user->expires;
+       v.pval = entity->expires;
        break;
 
     case S_arap:
-       v.pval = user->arap;
+       v.pval = entity->arap;
        break;
 
     case S_chap:
-       v.pval = user->chap;
+       v.pval = entity->chap;
        break;
 
 #ifdef MSCHAP
     case S_mschap:
-       v.pval = user->mschap;
+       v.pval = entity->mschap;
        break;
 #endif /* MSCHAP */
 
     case S_pap:
-       v.pval = user->pap;
+       v.pval = entity->pap;
        break;
 
     case S_opap:
-       v.pval = user->opap;
+       v.pval = entity->opap;
        break;
 
     case S_message:
-       v.pval = user->msg;
+       v.pval = entity->msg;
        break;
 
     case S_svc:
-       v.pval = user->svcs;
+       v.pval = entity->svcs;
        break;
 
     case S_before:
-       v.pval = user->before_author;
+       v.pval = entity->before_author;
        break;
 
     case S_after:
-       v.pval = user->after_author;
+       v.pval = entity->after_author;
        break;
 
     case S_svc_dflt:
-       v.intval = user->svc_dflt;
+       v.intval = entity->svc_dflt;
        break;
 
 #ifdef MAXSESS
     case S_maxsess:
-       v.intval = user->maxsess;
+       v.intval = entity->maxsess;
        break;
-#endif 
+#endif
 
     case S_nopasswd:
-       v.intval = user->nopasswd;
+       v.intval = entity->nopasswd;
        break;
-       
+
     case S_time:
-       v.pval = user->time;
+       v.pval = entity->time;
+       break;
+
+    case S_key:
+       if (entity->type == S_user) {
+           report(LOG_ERR, "get_value: S_key field not supported in %s %s",
+                       entity_type_to_string(entity->type), entity->name);
+           v.pval = NULL;
+           return(v);
+       }
+       v.pval = entity->key;
        break;
 
     default:
@@ -1417,102 +2047,100 @@ int field;
     return (v);
 }
 
-/* For host , find value of field. Doesn't recursive */
-VALUE
-get_hvalue(host, field)
-HOST *host;
-int field;
+
+/* Internal graph scanning routines */
+
+static enum value_scan_func_result value_scan TAC_ARGS((int type, const char *name, int recurse, value_scan_func_t func, void *func_data));
+
+static enum value_scan_func_result
+value_scan(type, name, recurse, func, func_data)
+int type;
+const char *name;
+int recurse;
+value_scan_func_t func;
+void *func_data;
 {
-    VALUE v;
-    v.intval = 0;
-    if(!host) {
-       parse_error("get_hvalue: illegal host");
-        return (v);
-    }
-    switch (field) {
-       case S_name:
-        v.pval = host->name;
-        break;
-       
-       case S_key:
-       v.pval = host->key;
-       break;
-       
-       default:
-        report(LOG_ERR, "get_value: unknown field %d", field);
-        break;
+    ENTITY *entity;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "value_scan: find %s %s, recurse=%d",
+               entity_type_to_string(type), name, recurse);
+
+    entity = entity_lookup(type, name);
+    if (!entity) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "value_scan: no %s named %s",
+                   entity_type_to_string(type), name);
+       return (VSFR_CONTINUE);
     }
-    return (v);
-}
 
+    return (value_scan_entity(entity, recurse, func, func_data));
+}
 
 /* For each user, check she doesn't circularly reference a
    group. Return 1 if it does */
-static int
-circularity_check()
+
+static int circularity_check_failed;
+
+static void circularity_check_fail TAC_ARGS((struct membership *membership));
+
+static void
+circularity_check_fail(membership)
+struct membership *membership;
 {
-    USER *user, *entry, *group;
-    USER **users = (USER **) hash_get_entries(usertable);
-    USER **groups = (USER **) hash_get_entries(grouptable);
-    USER **p, **q;
+    ENTITY *entity;
 
-    /* users */
-    for (p = users; *p; p++) {
-       user = *p;
+    circularity_check_failed = 1;
 
-       if (debug & DEBUG_PARSE_FLAG)
-           report(LOG_DEBUG, "circularity_check: user=%s", user->name);
+    report(LOG_ERR, "recursively defined groups:");
+    while (membership) {
+       entity = MEMBERSHIP_TO_CHILD_ENTITY(membership);
+       report(LOG_ERR, "%s %s",
+               entity_type_to_string(entity->type), entity->name);
+       membership = value_scan_backward(entity);
+    }
+}
 
-       /* Initialise all groups "seen" flags to zero */
-       for (q = groups; *q; q++) {
-           group = *q;
-           group->flags &= ~FLAG_SEEN;
-       }
+static enum value_scan_func_result circularity_check_func TAC_ARGS((ENTITY *entity, void *func_data));
 
-       entry = user;
+static enum value_scan_func_result
+circularity_check_func(entity, func_data /* unused */)
+ENTITY *entity;
+void *func_data;
+{
+    /* only useful to speedup case of failure */
+    if (circularity_check_failed)
+       return (VSFR_FOUND);
 
-       while (entry) {
-           /* check groups we are a member of */
-           char *groupname = entry->member;
+    return (VSFR_CONTINUE);
+}
 
-           if (debug & DEBUG_PARSE_FLAG)
-               report(LOG_DEBUG, "\tmember of group %s",
-                      groupname ? groupname : "<none>");
+static int circularity_check TAC_ARGS((void));
 
+static int
+circularity_check()
+{
+    ENTITY *entity;
+    ENTITY **users_base = (ENTITY **) hash_get_entries(usertable);
+    ENTITY **users;
 
-           /* if not a member of any groups, go on to next user */
-           if (!groupname)
-               break;
+    /* users */
+    for (users = users_base; *users; users++) {
+       entity = *users;
 
-           group = (USER *) hash_lookup(grouptable, groupname);
-           if (!group) {
-               report(LOG_ERR, "%s=%s, group %s does not exist",
-                      (entry->flags & FLAG_ISUSER) ? "user" : "group",
-                      entry->name, groupname);
-               free(users);
-               free(groups);
-               return (1);
-           }
-           if (group->flags & FLAG_SEEN) {
-               report(LOG_ERR, "recursively defined groups");
-
-               /* print all seen "groups" */
-               for (q = groups; *q; q++) {
-                   group = *q;
-                   if (group->flags & FLAG_SEEN)
-                       report(LOG_ERR, "%s", group->name);
-               }
-               free(users);
-               free(groups);
-               return (1);
-           }
-           group->flags |= FLAG_SEEN;  /* mark group as seen */
-           entry = group;
-       }
+       if (debug & DEBUG_PARSE_FLAG)
+           report(LOG_DEBUG, "circularity_check: user=%s", entity->name);
+
+       circularity_check_failed = 0;
+       value_scan_forward_seen_hook = circularity_check_fail;
+       value_scan_entity(entity, TAC_PLUS_RECURSE,
+               (value_scan_func_t) circularity_check_func, NULL /* func_data-unused */);
+       value_scan_forward_seen_hook = NULL;
+       if (circularity_check_failed)
+           break;
     }
-    free(users);
-    free(groups);
-    return (0);
+    free(users_base);
+    return (circularity_check_failed);
 }
 
 
@@ -1523,149 +2151,87 @@ circularity_check()
    member of, recursively.
 
    Returns void * because it can return a string or a node pointer
-   (should really return a union pointer).
-*/
-static VALUE
-cfg_get_value(name, isuser, attr, recurse)
-char *name;
-int isuser, attr, recurse;
-{
-    USER *user, *group;
-    VALUE value;
-
-    value.pval = NULL;
-
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_value: name=%s isuser=%d attr=%s rec=%d",
-              name, isuser, codestring(attr), recurse);
-
-    /* find the user/group entry */
+   (should really return a union pointer).
+*/
 
-    user = (USER *) hash_lookup(isuser ? usertable : grouptable, name);
+static VALUE cfg_get_value_VALUE;      /* private */
 
-    if (!user) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name);
-       return (value);
-    }
+static enum value_scan_func_result cfg_get_value_func TAC_ARGS((ENTITY *entity, int *attrp));
 
+static enum value_scan_func_result
+cfg_get_value_func(entity,attrp /* func_data */)
+ENTITY *entity;
+int *attrp;
+{
     /* found the entry. Lookup value from attr=value */
-    value = get_value(user, attr);
+    cfg_get_value_VALUE = get_value(entity, *attrp);
+    if (cfg_get_value_VALUE.pval)
+       return (VSFR_FOUND);
 
-    if (value.pval || !recurse) {
-       return (value);
-    }
-    /* no value. Check containing group */
-    if (user->member)
-       group = (USER *) hash_lookup(grouptable, user->member);
-    else
-       group = NULL;
+    return (VSFR_CONTINUE);
+}
 
-    while (group) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_value: recurse group = %s",
-                  group->name);
+static VALUE cfg_get_value TAC_ARGS((int type, const char *name, int attr, int recurse));
 
-       value = get_value(group, attr);
+static VALUE
+cfg_get_value(type, name, attr, recurse)
+int type;
+const char *name;
+int attr, recurse;
+{
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_value: type=%s name=%s attr=%s recurse=%d",
+              entity_type_to_string(type), name, codestring(attr), recurse);
 
-       if (value.pval) {
-           return (value);
-       }
-       /* still nothing. Check containing group and so on */
+    cfg_get_value_VALUE.pval = NULL;
+    value_scan(type, name, recurse,
+               (value_scan_func_t) cfg_get_value_func, &attr /* func_data */);
+    return (cfg_get_value_VALUE);
+}
 
-       if (group->member)
-           group = (USER *) hash_lookup(grouptable, group->member);
-       else
-           group = NULL;
-    }
 
-    /* no value for this user or her containing groups */
-    value.pval = NULL;
-    return (value);
-}
+/* Wrappers for cfg_get_value:
+ */
 
+int cfg_get_intvalue TAC_ARGS((int type, const char *name, int attr, int recurse));
 
-/* Wrappers for cfg_get_value */
 int
-cfg_get_intvalue(name, isuser, attr, recurse)
-char *name;
-int isuser, attr, recurse;
+cfg_get_intvalue(type, name, attr, recurse)
+int type;
+const char *name;
+int attr, recurse;
 {
-    int val = cfg_get_value(name, isuser, attr, recurse).intval;
+    int val = cfg_get_value(type, name, attr, recurse).intval;
 
     if (debug & DEBUG_CONFIG_FLAG)
        report(LOG_DEBUG, "cfg_get_intvalue: returns %d", val);
     return(val);
 }
 
-char *
-cfg_get_pvalue(name, isuser, attr, recurse)
-char *name;
-int isuser, attr, recurse;
-{
-    char *p = cfg_get_value(name, isuser, attr, recurse).pval;
-
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_pvalue: returns %s", 
-              p ? p : "NULL");
-    return(p);
-}
-
-/* For getting host values */
-static VALUE
-cfg_get_hvalue(name, attr)
-char *name;
-int attr;
-{
-    HOST *host;
-    VALUE value;
-
-    value.pval = NULL;
-    if (debug & DEBUG_CONFIG_FLAG)
-        report(LOG_DEBUG, "cfg_get_hvalue: name=%s attr=%s ",
-               name, codestring(attr));
-    
-    /* find the host entry in hash table */
-
-    host = (HOST *) hash_lookup( hosttable, name);
+const char *cfg_get_pvalue TAC_ARGS((int type, const char *name, int attr, int recurse));
 
-    if (!host) {
-        if (debug & DEBUG_CONFIG_FLAG)
-            report(LOG_DEBUG, "cfg_get_hvalue: no host named %s", name);
-        return (value);
-    }
-
-    /* found the entry. Lookup value from attr=value */
-    value = get_hvalue(host, attr);
-
-    if (value.pval) {
-        return (value);
-    }
-    /* No any value for this host */    
-    value.pval = NULL;
-    return (value);
-}
-
-/* Wrappers for cfg_get_hvalue */
-char *
-cfg_get_phvalue(name, attr)
-char *name;
-int attr;
+const char *
+cfg_get_pvalue(type, name, attr, recurse)
+int type;
+const char *name;
+int attr, recurse;
 {
-    char *p = cfg_get_hvalue(name, attr).pval;
+    char *p = cfg_get_value(type, name, attr, recurse).pval;
 
     if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_phvalue: returns %s", 
-              p ? p : "NULL");
+       report(LOG_DEBUG, "cfg_get_pvalue: returns %s",
+               p ? p : "NULL");
     return(p);
 }
 
-/*
-   Read the config file and do some basic sanity checking on
-   it. Return 1 if we find any errors. */
+/* Read the config file and do some basic sanity checking on
+ * it. Return 1 if we find any errors.
+ */
+int cfg_read_config TAC_ARGS((const char *cfile));
 
+int
 cfg_read_config(cfile)
-char *cfile;
+const char *cfile;
 {
     sym_line = 1;
 
@@ -1679,7 +2245,17 @@ char *cfile;
        return (1);
     }
 
-    if (circularity_check()) {
+    if (0
+     || enlist_entity_connect()
+     || expr_sink_commit()
+           /* circularity is allowed in the new fully-recursive algorithm */
+     || (!algorithm_recursive && circularity_check())
+        ) {
+       fclose(cf);
+       return (1);
+    }
+    if (!WHEN_EXPR_ROOT_EMPTY() || when_expr_dungeon) {
+       report(LOG_ERR, "Some 'when' expression found still pushed on stack");
        fclose(cf);
        return (1);
     }
@@ -1688,350 +2264,447 @@ char *cfile;
     return (0);
 }
 
-/* return 1 if user exists, 0 otherwise */
+/* return 1 if user exists, 0 otherwise
+ */
+int cfg_user_exists TAC_ARGS((const char *username));
+
 int
 cfg_user_exists(username)
-char *username;
+const char *username;
 {
-    USER *user = (USER *) hash_lookup(usertable, username);
-
-    return (user != NULL);
+    return (NULL != hash_lookup(usertable, username));
 }
 
 /* return expiry string of user. If none, try groups she is a member
-   on, and so on, recursively if recurse is non-zero */
-char *
-cfg_get_expires(username, recurse)
-char *username;
+ * on, and so on, recursively if recurse is non-zero
+ */
+const char *cfg_get_expires TAC_ARGS((const char *username, int recurse));
 
+const char *
+cfg_get_expires(username, recurse)
+const char *username;
+int recurse;
 {
-    return (cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse));
+    return (cfg_get_pvalue(S_user, username, S_expires, recurse));
 }
 
 /* return time string of user. If none, try groups she is a member
-   on, and so on, recursively if recurse is non-zero */
-char *
+ * on, and so on, recursively if recurse is non-zero
+ */
+const char *cfg_get_timestamp TAC_ARGS((const char *username, int recurse));
+
+const char *
 cfg_get_timestamp(username, recurse)
-char *username;
+const char *username;
+int recurse;
 {
-    return (cfg_get_pvalue(username, TAC_IS_USER, S_time, recurse));
+    return (cfg_get_pvalue(S_user, username, S_time, recurse));
 }
 
-
 /* return password string of user. If none, try groups she is a member
-   on, and so on, recursively if recurse is non-zero */
-char *
-cfg_get_login_secret(user, recurse)
-char *user;
+ * on, and so on, recursively if recurse is non-zero
+ */
+const char *cfg_get_login_secret TAC_ARGS((const char *user, int recurse));
 
+const char *
+cfg_get_login_secret(user, recurse)
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse));
+    return (cfg_get_pvalue(S_user, user, S_login, recurse));
 }
 
 /* return value of the nopasswd field. If none, try groups she is a member
-   on, and so on, recursively if recurse is non-zero */
+ * on, and so on, recursively if recurse is non-zero
+ */
+int cfg_get_user_nopasswd TAC_ARGS((const char *user, int recurse));
+
 int
 cfg_get_user_nopasswd(user, recurse)
-    char *user;
+const char *user;
+int recurse;
 {
-    return (cfg_get_intvalue(user, TAC_IS_USER, S_nopasswd, recurse));
+    return (cfg_get_intvalue(S_user, user, S_nopasswd, recurse));
 }
 
 /* return user's secret. If none, try groups she is a member
-   on, and so on, recursively if recurse is non-zero */
-char *
-cfg_get_arap_secret(user, recurse)
-char *user;
+ * on, and so on, recursively if recurse is non-zero
+ */
+const char *cfg_get_arap_secret TAC_ARGS((const char *user, int recurse));
 
+const char *
+cfg_get_arap_secret(user, recurse)
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse));
+    return (cfg_get_pvalue(S_user, user, S_arap, recurse));
 }
 
-char *
-cfg_get_chap_secret(user, recurse)
-char *user;
+const char *cfg_get_chap_secret TAC_ARGS((const char *user, int recurse));
 
+const char *
+cfg_get_chap_secret(user, recurse)
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse));
+    return (cfg_get_pvalue(S_user, user, S_chap, recurse));
 }
 
 #ifdef MSCHAP
-char *
-cfg_get_mschap_secret(user, recurse)
-char *user;
 
+const char *cfg_get_mschap_secret TAC_ARGS((const char *user, int recurse));
+
+const char *
+cfg_get_mschap_secret(user, recurse)
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse));
+    return (cfg_get_pvalue(S_user, user, S_mschap, recurse));
 }
+
 #endif /* MSCHAP */
 
-char *
+const char *cfg_get_pap_secret TAC_ARGS((const char *user, int recurse));
+
+const char *
 cfg_get_pap_secret(user, recurse)
-char *user;
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse));
+    return (cfg_get_pvalue(S_user, user, S_pap, recurse));
 }
 
-char *
+const char *cfg_get_opap_secret TAC_ARGS((const char *user, int recurse));
+
+const char *
 cfg_get_opap_secret(user, recurse)
-char *user;
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse));
+    return (cfg_get_pvalue(S_user, user, S_opap, recurse));
 }
 
 /* return the global password for the user (or the group, etc.) */
 
-char *
-cfg_get_global_secret(user, recurse)
-char *user;
+const char *cfg_get_global_secret TAC_ARGS((const char *user, int recurse));
 
+const char *
+cfg_get_global_secret(user, recurse)
+const char *user;
+int recurse;
 {
-    return (cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse));
+    return (cfg_get_pvalue(S_user, user, S_global, recurse));
 }
 
 #ifdef USE_PAM
+
 /* Return a pointer to a node representing a PAM Service name */
-char *
-cfg_get_pam_service(user,recurse)
-char *user;
 
+const char *cfg_get_pam_service TAC_ARGS((const char *user, int recurse));
+
+const char *
+cfg_get_pam_service(user, recurse)
+const char *user;
+int recurse;
 {
- char *cfg_passwd;
- char *p;   
   const char *cfg_passwd;
+    const char *p;
 
-cfg_passwd = cfg_get_pap_secret(user, recurse);
-if (!cfg_passwd) {
-               cfg_passwd = cfg_get_global_secret(user, recurse);
-}
-if (!cfg_passwd && !cfg_user_exists(user)) {
+    cfg_passwd = cfg_get_pap_secret(user, recurse);
+
+    if (!cfg_passwd)
+       cfg_passwd = cfg_get_global_secret(user, recurse);
+
+    if (!cfg_passwd && !cfg_user_exists(user)) {
         cfg_passwd = cfg_get_authen_default();
         switch (cfg_get_authen_default_method()) {
-               case (S_pam): 
-                       if (debug & DEBUG_AUTHOR_FLAG)
-                        report(LOG_DEBUG, "Get Default PAM Service :%s",cfg_passwd);
-                       return(cfg_passwd);
-                       break;
-               default:
-                       if (debug & DEBUG_AUTHOR_FLAG)
-                        report(LOG_DEBUG, "I havent find any PAM Service!!");
-                       return(NULL);/* Haven't any PAM Service!! */
+
+       case (S_pam):
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "Get Default PAM Service :%s",cfg_passwd);
+           return(cfg_passwd);
+           break;
+
+       default:
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "I havent find any PAM Service!!");
+           return(NULL);/* Haven't any PAM Service!! */
        }
-}
+    }
 
-p=tac_find_substring("pam ", cfg_passwd);
+    p = tac_find_substring("pam ", cfg_passwd);
 
-if(p) {  /* We find PAM services */
+    if(p) {  /* We find PAM services */
        if (debug & DEBUG_AUTHOR_FLAG)
                report(LOG_DEBUG, "I get PAM sevice:%s",p);
-return (p);
-}
+       return (p);
+    }
 
-if (debug & DEBUG_AUTHOR_FLAG)
+    if (debug & DEBUG_AUTHOR_FLAG)
        report(LOG_DEBUG, "No any PAM Sevice");
 
-return(NULL);
+    return(NULL);
 }
 
 #endif /* For PAM */
-       
 
 
 /* Return a pointer to a node representing a given service
    authorization, taking care of recursion issues correctly. Protocol
-   is only read if the type is N_svc_ppp. svcname is only read if type
+   is only read if the svctype is N_svc_ppp. svcname is only read if type
    is N_svc.
 */
 
-NODE *
-cfg_get_svc_node(username, type, protocol, svcname, recurse)
-char *username;
-int type;
-char *protocol, *svcname;
-int recurse;
-{
-    USER *user, *group;
-    NODE *svc;
-
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, 
-              "cfg_get_svc_node: username=%s %s proto=%s svcname=%s rec=%d",
-              username, 
-              cfg_nodestring(type), 
-              protocol ? protocol : "", 
-              svcname ? svcname : "", 
-              recurse);
-
-    /* find the user/group entry */
-    user = (USER *) hash_lookup(usertable, username);
+struct cfg_get_svc_node_param {
+    int svctype;
+    const char *protocol, *svcname;
+    NODE *node;
+    int retval;
+};
 
-    if (!user) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_svc_node: no user named %s", username);
-       return (NULL);
-    }
+static enum value_scan_func_result cfg_get_svc_node_func TAC_ARGS((ENTITY *entity, struct cfg_get_svc_node_param *param));
 
-    /* found the user entry. Find svc node */
-    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+static enum value_scan_func_result
+cfg_get_svc_node_func(entity, param /* func_data */)
+ENTITY *entity;
+struct cfg_get_svc_node_param *param;
+{
+    NODE *svc;
+    enum eval_result svc_default;
 
-       if (svc->type != type) 
+    for (svc = (NODE *) get_value(entity, S_svc).pval; svc; svc = svc->next) {
+       if (svc->type != param->svctype)
            continue;
-
-       if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
+       if (param->svctype == N_svc_ppp && param->protocol && !STREQ(svc->value1, param->protocol))
            continue;
-       }
-
-       if (type == N_svc && !STREQ(svc->value1, svcname)) {
+       if (param->svctype == N_svc     && param->svcname  && !STREQ(svc->value1, param->svcname ))
+           continue;
+       if (expr_eval(svc->when) != ER_TRUE)    /* expensive */
            continue;
-       }
 
        if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, 
+           report(LOG_DEBUG,
                   "cfg_get_svc_node: found %s proto=%s svcname=%s",
-                  cfg_nodestring(type), 
-                  protocol ? protocol : "", 
-                  svcname ? svcname : "");
+                  cfg_nodestring(param->svctype),
+                  param->protocol ? param->protocol : "",
+                  param->svcname ? param->svcname : "");
 
-       return(svc);
+       param->node = svc;
+       param->retval = 1;
+       return (VSFR_FOUND);
     }
 
-    if (!recurse) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
-       return (NULL);
-    }
-
-    /* no matching node. Check containing group */
-    if (user->member)
-       group = (USER *) hash_lookup(grouptable, user->member);
-    else
-       group = NULL;
-
-    while (group) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_svc_node: recurse group = %s",
-                  group->name);
-
-       for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {
-
-           if (svc->type != type) 
-               continue;
+    /* look at 'default service' settings */
+    svc_default = entity_svc_default(entity);
+    switch (svc_default) {
 
-           if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
-               continue;
-           }
+    case ER_TRUE:
+    case ER_FALSE:
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG,
+          "cfg_get_svc_node: svc=%s protocol=%s svcname=%s forced %s by default service",
+                  cfg_nodestring(param->svctype),
+                  param->protocol ? param->protocol : "",
+                  param->svcname ? param->svcname : "",
+                  (svc_default == ER_TRUE ? "permit" : "deny"));
+
+       param->retval = (svc_default == ER_TRUE);
+       return (VSFR_FOUND);
+
+    default:   /* shouldn't happen */
+    case ER_UNKNOWN:
+       return (VSFR_CONTINUE);
+    }
+    /* NOTREACHED */
+}
 
-           if (type == N_svc && !STREQ(svc->value1, svcname)) {
-               continue;
-           }
+int cfg_get_svc_node TAC_ARGS((const char *username, int svctype, const char *protocol, const char *svcname, int recurse, NODE **nodep));
 
-           if (debug & DEBUG_CONFIG_FLAG)
-               report(LOG_DEBUG, 
-                      "cfg_get_svc_node: found %s proto=%s svcname=%s",
-                      cfg_nodestring(type), 
-                      protocol ? protocol : "", 
-                      svcname ? svcname : "");
+int
+cfg_get_svc_node(username, svctype, protocol, svcname, recurse, nodep)
+const char *username;
+int svctype;
+const char *protocol;
+const char *svcname;
+int recurse;
+NODE **nodep;
+{
+    struct cfg_get_svc_node_param param;
+    enum value_scan_func_result vsfr;
 
-           return(svc);
-       }
+    param.svctype = svctype;
+    param.protocol = protocol;
+    param.svcname = svcname;
+    param.node = NULL;
+    param.retval = 0;
 
-       /* still nothing. Check containing group and so on */
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG,
+              "cfg_get_svc_node: username=%s %s proto=%s svcname=%s rec=%d",
+              username,
+              cfg_nodestring(svctype),
+              protocol ? protocol : "",
+              svcname ? svcname : "",
+              recurse);
 
-       if (group->member)
-           group = (USER *) hash_lookup(grouptable, group->member);
-       else
-           group = NULL;
-    }
+    vsfr = value_scan(S_user, username, recurse,
+               (value_scan_func_t) cfg_get_svc_node_func, &param /* func_data */);
+    if (nodep)
+       *nodep = param.node;
 
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
+    if (vsfr == VSFR_FOUND)
+       return (param.retval);
 
-    /* no matching svc node for this user or her containing groups */
-    return (NULL);
+    /* The service does not exist. Do the default */
+    return (cfg_no_user_permitted() ? 1 : 0);
 }
 
-/* Return a pointer to the node representing a set of command regexp
+/* Return a pointer to the node representing a set of command tac_regexp
    matches for a user and command, handling recursion issues correctly */
-NODE *
-cfg_get_cmd_node(name, cmdname, recurse)
-char *name, *cmdname;
-int recurse;
 
+struct cfg_authorize_cmd_param {
+    const char *cmd;
+    const char *args;
+    enum eval_result result;
+};
+
+static enum value_scan_func_result cfg_authorize_cmd_func TAC_ARGS((ENTITY *entity, struct cfg_authorize_cmd_param *param));
+
+static enum value_scan_func_result
+cfg_authorize_cmd_func(entity, param /* func_data */)
+ENTITY *entity;
+struct cfg_authorize_cmd_param *param;
 {
-    USER *user, *group;
     NODE *svc;
 
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_cmd_node: name=%s cmdname=%s rec=%d",
-              name, cmdname, recurse);
+    for (svc = (NODE *) get_value(entity, S_svc).pval; svc; svc = svc->next) {
+       NODE *node;
 
-    /* find the user/group entry */
-    user = (USER *) hash_lookup(usertable, name);
+       if (svc->type != N_svc_cmd)
+           continue;
+       if (!STREQ(svc->value, param->cmd))
+           continue;
+       if (expr_eval(svc->when) != ER_TRUE)    /* expensive */
+           continue;
 
-    if (!user) {
        if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_cmd_node: no user named %s", name);
-       return (NULL);
-    }
-    /* found the user entry. Find svc node */
-    svc = (NODE *) get_value(user, S_svc).pval;
-
-    while (svc) {
-       if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
-           if (debug & DEBUG_CONFIG_FLAG)
-               report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s %s node",
-                      cmdname, cfg_nodestring(svc->type));
-           return (svc);
-       }
-       svc = svc->next;
-    }
+           report(LOG_DEBUG, "cfg_authorize_cmd: found cmd %s %s node",
+                  param->cmd, cfg_nodestring(svc->type));
 
-    if (!recurse) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
-       return (NULL);
-    }
-    /* no matching node. Check containing group */
-    if (user->member)
-       group = (USER *) hash_lookup(grouptable, user->member);
-    else
-       group = NULL;
+       /* we have 'cmd <openbra>' point, now traverse through its 'permit'/'deny' pairs: */
 
-    while (group) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_get_cmd_node: recurse group = %s",
-                  group->name);
+       for (node = svc->value1; node; node = node->next) {
+           int match;
+
+           if (expr_eval(node->when) != ER_TRUE)               /* expensive */
+               continue;
+
+#ifdef WITH_INCLUDED_REGEX
+
+           match = tac_regexec((tac_regexp *) node->value1, param->args);
+
+#else /* WITH_INCLUDED_REGEX */
+
+           match = !regexec((const regex_t *) node->value1, param->args /* string */,
+                   0 /* nmatch */, NULL /* pmatch */, 0 /* eflags */);
 
-       svc = get_value(group, S_svc).pval;
+#endif /* WITH_INCLUDED_REGEX */
 
-       while (svc) {
-           if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
-               if (debug & DEBUG_CONFIG_FLAG)
-                   report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s node %s",
-                          cmdname, cfg_nodestring(svc->type));
-               return (svc);
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_INFO, "line %d compare %s %s '%s' & '%s' %smatch",
+                      node->line, param->cmd,
+                      node->type == N_permit ? "permit" : "deny",
+                      (const char *) node->value, param->args, (match ? "" : "no "));
+           }
+
+           if (!match)
+               continue;
+
+           switch (node->type) {
+           case N_permit:
+               if (debug & DEBUG_AUTHOR_FLAG) {
+                   report(LOG_DEBUG, "%s %s permitted by line %d",
+                          param->cmd, param->args, node->line);
+               }
+               param->result = ER_TRUE;
+               return (VSFR_FOUND);
+               break;
+           case N_deny:
+               if (debug & DEBUG_AUTHOR_FLAG) {
+                   report(LOG_DEBUG, "%s %s denied by line %d",
+                          param->cmd, param->args, node->line);
+               }
+               param->result = ER_FALSE;
+               return (VSFR_FOUND);
+               break;
+           default:
+               report(LOG_ERR, "INTERNAL: illegal configuration node: %s: %s %s",
+                      session.peer, param->cmd, param->args);
+               param->result = ER_UNKNOWN;     /* error */
+               return (VSFR_FOUND);
            }
-           svc = svc->next;
        }
+       if (!algorithm_recursive) {     /* compatibility mode: */
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "cmd %s exists, but no args match, denied (as no 'authorization = recursive' found)",
+                       param->cmd);
+           param->result = ER_FALSE;   /* emulate last "deny .*" */
+           return (VSFR_FOUND);
+       }
+    }
+
+    /* look at 'default service' settings */
+    param->result = entity_svc_default(entity);
+    switch (param->result) {
 
-       /* still nothing. Check containing group and so on */
+    case ER_TRUE:
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s does not exist, permitted by default", param->cmd);
+       return (VSFR_FOUND);
+
+    case ER_FALSE:
+
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s does not exist, denied by default", param->cmd);
+       return (VSFR_FOUND);
 
-       if (group->member)
-           group = (USER *) hash_lookup(grouptable, group->member);
-       else
-           group = NULL;
+    default:   /* shouldn't happen */
+    case ER_UNKNOWN:
+       return (VSFR_CONTINUE);
     }
+    /* NOTREACHED */
+}
+
+enum eval_result cfg_authorize_cmd TAC_ARGS((const char *username, const char *cmd, const char *args));
+
+enum eval_result
+cfg_authorize_cmd(username, cmd, args)
+const char *username;
+const char *cmd;
+const char *args;
+{
+    struct cfg_authorize_cmd_param param;
+
+    param.cmd = cmd;
+    param.args = args;
+    param.result = ER_UNKNOWN; /* error */
 
     if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
+       report(LOG_DEBUG, "cfg_authorize_cmd: name=%s cmdname=%s args=%s",
+              username, cmd, args);
 
-    /* no matching cmd node for this user or her containing groups */
-    return (NULL);
+    value_scan(S_user, username, TAC_PLUS_RECURSE,
+               (value_scan_func_t) cfg_authorize_cmd_func, &param /* func_data */);
+
+    if (param.result != ER_UNKNOWN)
+       return (param.result);
+
+    /* The command does not exist. Do the default */
+    return (cfg_no_user_permitted() ? ER_TRUE : ER_FALSE);
 }
 
 /* Return an array of character strings representing configured AV
- * pairs, given a username and a service node. 
+ * pairs, given a username and a service node.
  *
  * In the AV strings returned, manipulate the separator character to
  * indicate which args are optional and which are mandatory.
@@ -2039,6 +2712,8 @@ int recurse;
  * Lastly, indicate what default permission was configured by setting
  * denyp */
 
+char **cfg_get_svc_attrs TAC_ARGS((NODE *svcnode, int *denyp));
+
 char **
 cfg_get_svc_attrs(svcnode, denyp)
 NODE *svcnode;
@@ -2063,9 +2738,14 @@ int *denyp;
 
     i = 0;
     for (node = svcnode->value; node; node = node->next) {
-       char *arg = tac_strdup(node->value);
-       char *p = index(arg, '=');
+       char *arg;
+       char *p;
+
+       if (expr_eval(node->when) != ER_TRUE)   /* expensive */
+           continue;   /* ignore this node */
 
+       arg = tac_strdup(node->value);
+       p = index(arg, '=');
        if (p && node->type == N_optarg)
            *p = '*';
        args[i++] = arg;
@@ -2075,23 +2755,28 @@ int *denyp;
 }
 
 
-int
-cfg_user_svc_default_is_permit(user)
-char *user;
+static enum eval_result entity_svc_default TAC_ARGS((ENTITY *entity));
 
+static enum eval_result
+entity_svc_default(entity)
+ENTITY *entity;
 {
-    int permit = cfg_get_intvalue(user, TAC_IS_USER, S_svc_dflt,
-                              TAC_PLUS_RECURSE);
-
-    switch (permit) {
-    default:                   /* default is deny */
-    case S_deny:
-       return (0);
+    switch (entity->svc_dflt) {
     case S_permit:
-       return (1);
+       return (ER_TRUE);
+    case S_deny:
+       return (ER_FALSE);
+    case S_default:
+    case 0:    /* not specified */
+       return (ER_UNKNOWN);
+    default:
+       report(LOG_ERR, "INTERNAL: invalid entity svc_dflt (%d)", entity->svc_dflt);
+       return (ER_UNKNOWN);
     }
 }
 
+int cfg_no_user_permitted TAC_ARGS((void));
+
 int
 cfg_no_user_permitted()
 {
@@ -2101,106 +2786,141 @@ cfg_no_user_permitted()
 }
 
 
-char *
+const char *cfg_get_authen_default TAC_ARGS((void));
+
+const char *
 cfg_get_authen_default()
 {
     return (authen_default);
 }
 
+int cfg_get_authen_default_method TAC_ARGS((void));
+
 /* For describe authentication method(pam,file,db..etc) */
-int 
+int
 cfg_get_authen_default_method()
 {
    return (authen_default_method);
 }
 
 
-/* Return 1 if this user has any ppp services configured. Used for
-   authorizing ppp/lcp requests */
-int
-cfg_ppp_is_configured(username, recurse)
-    char *username;
-    int recurse;
-{
-    USER *user, *group;
-    NODE *svc;
+/* Host entity management:
+ */
 
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_ppp_is_configured: username=%s rec=%d",
-              username, recurse);
+const char *cfg_get_host_key TAC_ARGS((const char *host));
 
-    /* find the user/group entry */
-    user = (USER *) hash_lookup(usertable, username);
+/* For getting host key */
+const char *
+cfg_get_host_key(host)
+const char *host;
+{
+    return (cfg_get_pvalue(S_host, host, S_key, algorithm_recursive /* recurse */));
+}
 
-    if (!user) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", 
-                  username);
-       return (0);
-    }
+static ENTITY *force_belong_entity TAC_ARGS((int type, const char *name));
 
-    /* found the user entry. Find svc node */
-    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+static ENTITY *
+force_belong_entity(type, name)
+int type;
+const char *name;
+{
+    ENTITY *entity = entity_lookup(type, name);
 
-       if (svc->type != N_svc_ppp) 
-           continue;
+    if (entity)
+       eval_force_belong_entity(entity);
 
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
-                  svc->value1);
-       
-       return(1);
-    }
+    return (entity);
+}
 
-    if (!recurse) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
-       return (0);
-    }
+/* assumed existing initialized "session.peer*" */
 
-    /* no matching node. Check containing group */
-    if (user->member)
-       group = (USER *) hash_lookup(grouptable, user->member);
-    else
-       group = NULL;
+static ENTITY *request_peer_addr;
+static ENTITY *request_peer;
+static ENTITY *request_DEFAULT_group;
 
-    while (group) {
-       if (debug & DEBUG_CONFIG_FLAG)
-           report(LOG_DEBUG, "cfg_ppp_is_configured: recurse group = %s",
-                  group->name);
+static void enlist_request_peer TAC_ARGS((const char *hostname, ENTITY **entityp));
 
-       for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {
+static void
+enlist_request_peer(hostname, entityp)
+const char *hostname;
+ENTITY **entityp;
+{
+    *entityp = NULL;
+    if (!hostname)
+       return;
 
-           if (svc->type != N_svc_ppp)
-               continue;
+    *entityp = force_belong_entity(S_host, hostname);
+    if (*entityp && request_DEFAULT_group)
+       virtual_enlist_entity_direct(request_DEFAULT_group /* parent */, *entityp /* child */);
+}
 
-           if (debug & DEBUG_CONFIG_FLAG)
-               report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
-                      svc->value1);
-       
-           return(1);
-       }
+/* Try to build the following scenery:
+ * 
+ * host <session.peer_addr> [=ER_TRUE]
+ *  |
+ *  +-- group <DEFAULT_GROUPNAME>
+ *
+ * host <session.peer     > [=ER_TRUE]
+ *  |
+ *  +-- group <DEFAULT_GROUPNAME>
+ */
 
-       /* still nothing. Check containing group and so on */
+void cfg_request_scan_begin TAC_ARGS((void));
 
-       if (group->member)
-           group = (USER *) hash_lookup(grouptable, group->member);
-       else
-           group = NULL;
-    }
+void
+cfg_request_scan_begin()
+{
+    request_scan_begin();
 
-    if (debug & DEBUG_CONFIG_FLAG)
-       report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
+    request_DEFAULT_group = entity_lookup(S_group, DEFAULT_GROUPNAME);
 
-    /* no PPP svc nodes for this user or her containing groups */
-    return (0);
+    if (session.peer_addr != session.peer)
+       enlist_request_peer(session.peer_addr, &request_peer_addr);
+    enlist_request_peer(session.peer, &request_peer);
 }
 
-/* For getting host key */
-char *
-cfg_get_host_key(host)
-char *host;
+/* Try to build the following scenery:
+ *
+ * ( user <identity->username> |  user <DEFAULT_USERNAME> ) [=ER_TRUE]
+ *  |
+ *  +-- host <session.peer_addr> [=ER_TRUE]
+ *  |    |
+ *  |    +- group <DEFAULT_GROUPNAME>
+ *  |
+ *  +-- host <session.peer     > [=ER_TRUE]
+ *  |    |
+ *  |    +-- group <DEFAULT_GROUPNAME>
+ *  |
+ *  +-- group <DEFAULT_GROUPNAME>
+ */
+
+void cfg_request_identity TAC_ARGS((const struct identity *identity));
+
+void
+cfg_request_identity(identity)
+const struct identity *identity;
 {
-    return (cfg_get_phvalue(host, S_key));
-}
+    ENTITY *user_entity,*request_DEFAULT_group;
 
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_request_identity: username=%s, NAS_name=%s, NAS_port=%s, NAC_address=%s, priv_lvl=%d",
+           identity->username, identity->NAS_name, identity->NAS_port, identity->NAC_address, identity->priv_lvl);
+
+    user_entity = force_belong_entity(S_user, identity->username);
+    request_DEFAULT_group = entity_lookup(S_group, DEFAULT_GROUPNAME);
+
+    if (!user_entity)
+       user_entity = force_belong_entity(S_user, DEFAULT_USERNAME);
+
+    request_scan_user_known = 1;
+
+    if (!user_entity)
+       return;
+
+    if (request_peer_addr)
+       virtual_enlist_entity_direct(request_peer_addr     /* parent */, user_entity /* child */);
+    if (request_peer     )
+       virtual_enlist_entity_direct(request_peer          /* parent */, user_entity /* child */);
+    if (request_DEFAULT_group)
+       virtual_enlist_entity_direct(request_DEFAULT_group /* parent */, user_entity /* child */);
+}