Import of tac_plus.v8.tar.gz: 173206 bytes, md5:
[tac_plus.git] / cfgfile.c
diff --git a/cfgfile.c b/cfgfile.c
new file mode 100644 (file)
index 0000000..6eaef53
--- /dev/null
+++ b/cfgfile.c
@@ -0,0 +1,2206 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include <stdio.h>
+#include <errno.h>
+#include "regexp.h"
+
+/*
+   <config>         := <decl>*
+
+   <decl>           := <top_level_decl> | <user_decl>
+
+   <top_level_decl> := <authen_default> |
+                       accounting file = <string>
+                       default authorization = permit |
+                       key = <string>
+
+   <authen_default> := default authentication = file <filename> 
+#if defined(DB)
+                   | db <string> )
+#endif
+   
+<permission>     := permit | deny
+
+   <filename>       := <string>
+
+   <password>       := <string>
+
+   <user_decl>      := user = <string> {
+                        [ default service = [ permit | deny ] ]
+                        <user_attr>*
+                        <svc>*
+                   }
+
+   <password_spec>  := file <filename> | 
+                      skey | 
+                      cleartext <password> |
+                      des <password> |
+
+#ifdef USE_PAM         
+                      pam <pam_service> |
+#endif                 
+#if defined(DB)                
+                       db <string>
+#endif
+                      nopassword
+
+   <user_attr>      :=   name     = <string> |
+                         login    = <password_spec> |
+                        member   = <string> |
+                        expires  = <string> |
+                        arap     = cleartext <string> |
+                        chap     = cleartext <string> |
+#ifdef MSCHAP
+                        ms-chap  = cleartext <string> |
+#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>
+
+   <svc>            := <svc_auth> | <cmd_auth>
+
+   <cmd_auth>       := cmd = <string> {
+                        <cmd-match>*
+                    }
+
+   <cmd-match>      := <permission> <string>
+
+   <svc_auth>       := service = ( exec | arap | slip | ppp protocol = <string> {
+                        [ default attribute = permit ]
+                        <attr_value_pair>*
+                    }
+
+   <attr_value_pair> := [ optional ] <string> = <string>
+
+*/
+
+static char sym_buf[MAX_INPUT_LINE_LEN];       /* parse buffer */
+static int sym_pos=0;           /* current place in sym_buf */
+static int sym_ch;             /* current parse character */
+static int sym_code;           /* parser output */
+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 */
+static char *authen_default = NULL;    /* top level authentication default */
+static int authen_default_method = 0; /*For method check */
+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;
+
+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 */
+
+
+static void
+ sym_get();
+
+
+#ifdef __STDC__
+#include <stdarg.h>            /* ANSI C, variable length args */
+static void
+parse_error(char *fmt,...)
+#else
+#include <varargs.h>           /* has 'vararg' definitions */
+/* VARARGS2 */
+static void
+parse_error(fmt, va_alist)
+char *fmt;
+
+va_dcl                         /* no terminating semi-colon */
+#endif
+{
+    char msg[256];             /* temporary string */
+    va_list ap;
+
+#ifdef __STDC__
+    va_start(ap, fmt);
+#else
+    va_start(ap);
+#endif
+    vsprintf(msg, fmt, ap);
+    va_end(ap);
+
+    report(LOG_ERR, "%s", msg);
+    fprintf(stderr, "Error: %s\n", msg);
+    tac_exit(1);
+}
+
+char *
+cfg_nodestring(type)
+    int type;
+{
+    switch (type) {
+    default:
+       return ("unknown node type");
+    case N_arg:
+       return ("N_arg");
+    case N_optarg:
+       return ("N_optarg");
+    case N_svc:
+       return ("N_svc");
+    case N_svc_exec:
+       return ("N_svc_exec");
+    case N_svc_slip:
+       return ("N_svc_slip");
+    case N_svc_ppp:
+       return ("N_svc_ppp");
+    case N_svc_arap:
+       return ("N_svc_arap");
+    case N_svc_cmd:
+       return ("N_svc_cmd");
+    case N_permit:
+       return ("N_permit");
+    case N_deny:
+       return ("N_deny");
+    }
+}
+
+static void
+free_attrs(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+       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);
+           break;
+       default:
+           report(LOG_ERR, "Illegal node type %s for free_attrs", 
+                  cfg_nodestring(node->type));
+           return;
+       }
+
+       free(node->value);
+       next = node->next;
+       free(node);
+       node = next;
+    }
+}
+
+static void
+free_cmd_matches(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+       if (debug & DEBUG_CLEAN_FLAG)
+           report(LOG_DEBUG, "free_cmd_match %s %s",
+                  cfg_nodestring(node->type),
+                  node->value);
+
+       free(node->value);      /* text */
+       free(node->value1);     /* regexp compiled text */
+       next = node->next;
+       free(node);
+       node = next;
+    }
+}
+
+static void
+free_svcs(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+
+       switch (node->type) {
+       case N_svc_cmd:
+           if (debug & DEBUG_CLEAN_FLAG)
+               report(LOG_DEBUG, "free %s %s",
+                      cfg_nodestring(node->type), node->value);
+           free(node->value);  /* cmd name */
+           free_cmd_matches(node->value1);
+           next = node->next;
+           free(node);
+           node = next;
+           continue;
+
+       case N_svc:
+       case N_svc_ppp:
+           free(node->value1);
+           /* FALL-THROUGH */
+       case N_svc_exec:
+       case N_svc_arap:
+       case N_svc_slip:
+           if (debug & DEBUG_CLEAN_FLAG)
+               report(LOG_DEBUG, "free %s", cfg_nodestring(node->type));
+           free_attrs(node->value);
+           next = node->next;
+           free(node);
+           node = next;
+           continue;
+
+       default:
+           report(LOG_ERR, "Illegal node type %d for free_svcs", node->type);
+           return;
+       }
+    }
+}
+
+static void
+free_userstruct(user)
+USER *user;
+{
+    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);
+#ifdef MSCHAP
+    if (user->mschap)
+       free(user->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);
+}
+
+static void
+free_hoststruct(host)
+HOST *host;
+{
+    if (debug & DEBUG_CLEAN_FLAG)
+       report(LOG_DEBUG, "free %s",
+               host->name);
+
+    if (host->name)
+       free(host->name);
+    
+    if (host->key)
+       free(host->key);
+    
+    if (host->type)
+       free(host->type);
+}
+
+/*
+ * Exported routines
+ */
+
+/* Free all allocated structures preparatory to re-reading the config file */
+void
+cfg_clean_config()
+{
+    int i;
+    USER *entry, *next;
+    HOST *host_entry,*hn;
+
+    if (authen_default) {
+       free(authen_default);
+       authen_default = NULL;
+    }
+   
+   if (authen_default_method) {
+       authen_default_method = 0;
+    }
+
+    if (session.key) {
+       free(session.key);
+       session.key = NULL;
+    }
+
+    if (session.acctfile) {
+       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;
+    }
+
+    /* 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;
+    }
+}
+
+static int
+parse_permission()
+{
+    int symbol = sym_code;
+
+    if (sym_code != S_permit && sym_code != S_deny) {
+       parse_error("expecting permit or deny but found '%s' on line %d",
+                   sym_buf, sym_line);
+       return (0);
+    }
+    sym_get();
+
+    return (symbol);
+}
+
+static int
+parse(symbol)
+int symbol;
+
+{
+    if (sym_code != symbol) {
+       parse_error("expecting '%s' but found '%s' on line %d",
+                   (symbol == S_string ? "string" : codestring(symbol)),
+                   sym_buf, sym_line);
+       return (1);
+    }
+    sym_get();
+    return (0);
+}
+
+static int
+parse_opt_svc_default()
+{
+    if (sym_code != S_default) {
+       return (0);
+    }
+
+    parse(S_default);
+    parse(S_svc);
+    parse(S_separator);
+    if (sym_code == S_permit) {
+       parse(S_permit);
+       return (S_permit);
+    }
+    parse(S_deny);
+    return (S_deny);
+}
+
+static int
+parse_opt_attr_default()
+{
+    if (sym_code != S_default)
+       return (S_deny);
+
+    parse(S_default);
+    parse(S_attr);
+    parse(S_separator);
+    parse(S_permit);
+    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()
+{
+    no_user_dflt = 0; /* default if user doesn't exist */
+
+    sym_code = 0;
+    rch();
+
+    bzero(grouptable, sizeof(grouptable));
+    bzero(usertable, sizeof(usertable));
+    bzero(hosttable, sizeof(hosttable)); 
+
+    sym_get();
+
+    /* Top level of parser */
+    while (1) {
+
+       switch (sym_code) {
+       case S_eof:
+           return (0);
+
+       case S_accounting:
+           sym_get();
+           parse(S_file);
+           parse(S_separator);
+           if (session.acctfile) 
+               free(session.acctfile);
+           session.acctfile = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+#ifdef DB      
+       case S_db_accounting:
+           sym_get();
+           parse(S_separator);
+           if (session.db_acct) 
+               free(session.db_acct);
+           session.db_acct = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+#endif
+
+       case S_default:
+           sym_get();
+           switch (sym_code) {
+           default:
+               parse_error(
+               "Expecting default authorization/authentication on lines %d",
+                           sym_line);
+               return (1);
+
+           case S_authentication:
+               if (authen_default) {
+                   parse_error(
+                   "Multiply defined authentication default on line %d",
+                               sym_line);
+                   return (1);
+               }
+               parse(S_authentication);
+               parse(S_separator);
+
+               switch(sym_code) {
+                
+               case S_file:
+#ifdef DB
+               case S_db:
+#endif
+#ifdef USE_LDAP
+               case S_ldap;
+#endif
+#ifdef USE_PAM
+               case S_pam:
+#endif
+                authen_default_method = sym_code;
+               break;
+
+               default:
+                parse_error("expecting default_method keyword after 'default authentication = ' on line %d",sym_line);
+               return (1);
+                }
+                sym_get();
+
+               authen_default = tac_strdup(sym_buf);
+               sym_get();
+               continue;
+
+           case S_authorization:
+               parse(S_authorization);
+               parse(S_separator);
+               parse(S_permit);
+               no_user_dflt = S_permit;
+               report(LOG_INFO, 
+                      "default authorization = permit is now deprecated. Please use user = DEFAULT instead");
+               continue;
+           }
+
+       case S_key:
+           /* Process a key declaration. */
+           sym_get();
+           parse(S_separator);
+           if (session.key) {
+               parse_error("multiply defined key on lines %d and %d",
+                           session.keyline, sym_line);
+               return (1);
+           }
+           session.key = tac_strdup(sym_buf);
+           session.keyline = sym_line;
+           sym_get();
+           continue;
+       
+       case S_host:
+           parse_host();
+           continue;
+       
+       case S_user:
+       case S_group:
+           parse_user();
+           continue;
+
+           /* case S_host: parse_host(); continue; */
+
+       default:
+           parse_error("Unrecognised token %s on line %d", sym_buf, sym_line);
+           return (1);
+       }
+    }
+}
+
+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 */
+
+#define ASSIGN(field) \
+sym_get(); parse(S_separator); if (field) { \
+       parse_error("Duplicate value for %s %s and %s on line %d", \
+                   codestring(sym_code), field, sym_buf, sym_line); \
+        tac_exit(1); \
+    } \
+    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];
+
+    bzero(host, sizeof(HOST));
+
+    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);
+    }
+
+    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);
+
+       default:
+           parse_error("Unrecognised keyword %s for host %s on line %d",
+                        sym_buf, host->name,sym_line);
+
+            return (0);
+        }
+    } /* while */
+} /* finish parse_host */
+
+
+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];
+
+    bzero(user, sizeof(USER));
+
+    isuser = (sym_code == S_user);
+
+    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);
+    }
+
+    if (n) {
+       parse_error("multiply defined %s %s on lines %d and %d",
+                   isuser ? "user" : "group",
+                   user->name, n->line, sym_line);
+       return (1);
+    }
+    sym_get();
+    parse(S_openbra);
+
+    /* Is the default deny for svcs or cmds to be overridden? */
+    user->svc_dflt = parse_opt_svc_default();
+
+    while (1) {
+       switch (sym_code) {
+       case S_eof:
+           return (0);
+       
+       case S_time:
+          ASSIGN(user->time);
+          sym_get(); 
+          continue;
+
+       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;
+
+       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;
+
+       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;
+
+       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) {
+
+           case S_skey:
+               user->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;
+               break;
+               
+           case S_file:
+           case S_cleartext:
+           case S_des:
+#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);
+               break;
+       
+           default:
+#ifdef USE_PAM
+               parse_error(
+ "expecting 'file', 'cleartext', 'pam'.'nopassword', 'skey', or 'des' keyword after 'login =' on line %d",
+                           sym_line);
+#else  
+               parse_error(
+ "expecting 'file', 'cleartext', 'nopassword', 'skey', or 'des' keyword after 'login =' on line %d", 
+                           sym_line);
+#endif /* USE_PAM */                   
+           }
+           sym_get();
+           continue;
+
+       case S_pap:
+           if (user->pap) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(sym_code), user->pap,
+                           sym_buf, sym_line);
+               tac_exit(1);
+           }
+           sym_get();
+           parse(S_separator);
+           switch(sym_code) {
+
+           case S_cleartext:
+           case S_des:
+#ifdef USE_PAM 
+           case S_pam:
+#endif /*USE_PAM */                    
+               sprintf(buf, "%s ", sym_buf);
+               sym_get();
+               strcat(buf, sym_buf);
+               user->pap = tac_strdup(buf);
+               break;  
+
+               sprintf(buf, "%s ", sym_buf);
+               user->pap = tac_strdup(buf);
+               break;
+
+           default:
+#ifdef USE_PAM
+               parse_error(
+ "expecting 'cleartext', 'pam', or 'des' keyword after 'pap =' on line %d",
+ sym_line);
+#else
+               parse_error(
+ "expecting 'cleartext', or 'des' keyword after 'pap =' on line %d", 
+ sym_line);
+#endif /*USE_PAM */
+           }
+           sym_get();
+           continue;
+
+       case S_name:
+           ASSIGN(user->full_name);
+           sym_get();
+           continue;
+
+       case S_member:
+           ASSIGN(user->member);
+           sym_get();
+           continue;
+       
+
+       case S_expires:
+           ASSIGN(user->expires);
+           sym_get();
+           continue;
+       
+       case S_message:
+           ASSIGN(user->msg);
+           sym_get();
+           continue;
+
+       case S_arap:
+       case S_chap:
+#ifdef MSCHAP
+       case S_mschap:
+#endif /* MSCHAP */
+       case S_opap:
+       case S_global:
+           save_sym = sym_code;
+           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;
+#ifdef MSCHAP
+           if (save_sym == S_mschap)
+               fieldp = &user->mschap;
+#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;
+
+           if (*fieldp) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(save_sym), *fieldp, sym_buf, sym_line);
+               tac_exit(1);
+           }
+           *fieldp = tac_strdup(buf);
+           sym_get();
+           continue;
+
+       case S_closebra:
+           parse(S_closebra);
+           return (0);
+
+#ifdef MAXSESS
+       case S_maxsess:
+           sym_get(); 
+           parse(S_separator);
+           if (sscanf(sym_buf, "%d", &user->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);
+       }
+    }
+}
+
+static NODE *parse_attrs();
+static NODE *parse_cmd_matches();
+
+static NODE *
+parse_svcs()
+{
+    NODE *result;
+
+    switch (sym_code) {
+    default:
+       return (NULL);
+    case S_svc:
+    case S_cmd:
+       break;
+    }
+
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    /* cmd declaration */
+    if (sym_code == S_cmd) {
+       parse(S_cmd);
+       parse(S_separator);
+       result->value = tac_strdup(sym_buf);
+
+       sym_get();
+       parse(S_openbra);
+
+       result->value1 = parse_cmd_matches();
+       result->type = N_svc_cmd;
+
+       parse(S_closebra);
+       result->next = parse_svcs();
+       return (result);
+    }
+
+    /* svc declaration */
+    parse(S_svc);
+    parse(S_separator);
+    switch (sym_code) {
+    default:
+       parse_error("expecting service type but found %s on line %d",
+                   sym_buf, sym_line);
+       return (NULL);
+
+    case S_string:
+       result->type = N_svc;
+       /* should perhaps check that this is an allowable service name */
+       result->value1 = tac_strdup(sym_buf);
+       break;
+    case S_exec:
+       result->type = N_svc_exec;
+       break;
+    case S_arap:
+       result->type = N_svc_arap;
+       break;
+    case S_slip:
+       result->type = N_svc_slip;
+       break;
+    case S_ppp:
+       result->type = N_svc_ppp;
+       parse(S_ppp);
+       parse(S_protocol);
+       parse(S_separator);
+       /* Should perhaps check that this is a known PPP protocol name */
+       result->value1 = tac_strdup(sym_buf);
+       break;
+    }
+    sym_get();
+    parse(S_openbra);
+    result->dflt = parse_opt_attr_default();
+    result->value = parse_attrs();
+    parse(S_closebra);
+    result->next = parse_svcs();
+    return (result);
+}
+
+/*  <cmd-match>         := <permission> <string> */
+
+static NODE *
+parse_cmd_matches()
+{
+    NODE *result;
+
+    if (sym_code != S_permit && sym_code != S_deny) {
+       return (NULL);
+    }
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    result->type = (parse_permission() == S_permit) ? N_permit : N_deny;
+    result->value = tac_strdup(sym_buf);
+
+    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->next = parse_cmd_matches();
+
+    return (result);
+}
+
+static NODE *
+parse_attrs()
+{
+    NODE *result;
+    char buf[MAX_INPUT_LINE_LEN];
+    int optional = 0;
+
+    if (sym_code == S_closebra) {
+       return (NULL);
+    }
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    if (sym_code == S_optional) {
+       optional++;
+       sym_get();
+    }
+    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->next = parse_attrs();
+    return (result);
+}
+
+
+static void
+ getsym();
+
+static void
+sym_get()
+{
+    getsym();
+
+    if (debug & DEBUG_PARSE_FLAG) {
+       report(LOG_DEBUG, "line=%d sym=%s code=%d buf='%s'",
+              sym_line, codestring(sym_code), sym_code, sym_buf);
+    }
+}
+
+static char *
+sym_buf_add(c)
+char c;
+{
+    if (sym_pos >= MAX_INPUT_LINE_LEN) {
+       sym_buf[MAX_INPUT_LINE_LEN-1] = '\0';
+       if (debug & DEBUG_PARSE_FLAG) {
+           report(LOG_DEBUG, "line too long: line=%d sym=%s code=%d buf='%s'",
+                  sym_line, codestring(sym_code), sym_code, sym_buf);
+       }
+       return(NULL);
+    }
+
+    sym_buf[sym_pos++] = c;
+    return(sym_buf);
+}
+    
+static void
+getsym()
+{
+
+next:
+    switch (sym_ch) {
+
+    case EOF:
+       sym_code = S_eof;
+       return;
+
+    case '\n':
+       sym_line++;
+       rch();
+       goto next;
+
+    case '\t':
+    case ' ':
+       while (sym_ch == ' ' || sym_ch == '\t')
+           rch();
+       goto next;
+
+    case '=':
+       strcpy(sym_buf, "=");
+       sym_code = S_separator;
+       rch();
+       return;
+
+    case '{':
+       strcpy(sym_buf, "{");
+       sym_code = S_openbra;
+       rch();
+       return;
+
+    case '}':
+       strcpy(sym_buf, "}");
+       sym_code = S_closebra;
+       rch();
+       return;
+
+    case '#':
+       while ((sym_ch != '\n') && (sym_ch != EOF))
+           rch();
+       goto next;
+
+    case '"':
+       rch();
+       sym_pos = 0;
+       while (1) {
+
+           if (sym_ch == '"') {
+               break;
+           }
+
+           /* backslash-double-quote is supported inside strings */
+           /* also allow \n */
+           if (sym_ch == '\\') {
+               rch();
+               switch (sym_ch) {
+               case 'n':
+                   /* preserve the slash for \n */
+                   if (!sym_buf_add('\\')) {
+                       sym_code = S_unknown;
+                       rch();
+                       return;
+                   }
+                   
+                   /* fall through */
+               case '"':
+                   if (!sym_buf_add(sym_ch)) {
+                       sym_code = S_unknown;
+                       rch();
+                       return;
+                   }
+                   rch();
+                   continue;
+               default:
+                   sym_code = S_unknown;
+                   rch();
+                   return;
+               }
+           }
+           if (!sym_buf_add(sym_ch)) {
+               sym_code = S_unknown;
+               rch();
+               return;
+           }
+           rch();
+       }
+       rch();
+
+       if (!sym_buf_add('\0')) {
+           sym_code = S_unknown;
+           rch();
+           return;
+       }
+       sym_code = S_string;
+       return;
+
+    default:
+       sym_pos = 0;
+       while (sym_ch != '\t' && sym_ch != ' ' && sym_ch != '='
+              && sym_ch != '\n') {
+
+           if (!sym_buf_add(sym_ch)) {
+               sym_code = S_unknown;
+               rch();
+               return;
+           }
+           rch();
+       }
+
+       if (!sym_buf_add('\0')) {
+           sym_code = S_unknown;
+           rch();
+           return;
+       }
+       sym_code = keycode(sym_buf);
+       if (sym_code == S_unknown)
+           sym_code = S_string;
+       return;
+    }
+}
+
+static void
+rch()
+{
+    if (sym_error) {
+       sym_ch = EOF;
+       return;
+    }
+    sym_ch = getc(cf);
+
+    if (parse_only && sym_ch != EOF)
+       fprintf(stderr, "%c", sym_ch);
+}
+
+
+/* For a user or group, find the value of a field. Does not recurse. */
+VALUE
+get_value(user, field)
+USER *user;
+int field;
+{
+    VALUE v;
+
+    v.intval = 0;
+
+    if (!user) {
+       parse_error("get_value: illegal user");
+       return (v);
+    }
+    switch (field) {
+
+    case S_name:
+       v.pval = user->name;
+       break;
+
+    case S_login:
+       v.pval = user->login;
+       break;
+
+    case S_global:
+       v.pval = user->global;
+       break;
+
+    case S_member:
+       v.pval = user->member;
+       break;
+
+    case S_expires:
+       v.pval = user->expires;
+       break;
+
+    case S_arap:
+       v.pval = user->arap;
+       break;
+
+    case S_chap:
+       v.pval = user->chap;
+       break;
+
+#ifdef MSCHAP
+    case S_mschap:
+       v.pval = user->mschap;
+       break;
+#endif /* MSCHAP */
+
+    case S_pap:
+       v.pval = user->pap;
+       break;
+
+    case S_opap:
+       v.pval = user->opap;
+       break;
+
+    case S_message:
+       v.pval = user->msg;
+       break;
+
+    case S_svc:
+       v.pval = user->svcs;
+       break;
+
+    case S_before:
+       v.pval = user->before_author;
+       break;
+
+    case S_after:
+       v.pval = user->after_author;
+       break;
+
+    case S_svc_dflt:
+       v.intval = user->svc_dflt;
+       break;
+
+#ifdef MAXSESS
+    case S_maxsess:
+       v.intval = user->maxsess;
+       break;
+#endif 
+
+    case S_nopasswd:
+       v.intval = user->nopasswd;
+       break;
+       
+    case S_time:
+       v.pval = user->time;
+       break;
+
+    default:
+       report(LOG_ERR, "get_value: unknown field %d", field);
+       break;
+    }
+    return (v);
+}
+
+/* For host , find value of field. Doesn't recursive */
+VALUE
+get_hvalue(host, field)
+HOST *host;
+int field;
+{
+    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;
+    }
+    return (v);
+}
+
+
+/* For each user, check she doesn't circularly reference a
+   group. Return 1 if it does */
+static int
+circularity_check()
+{
+    USER *user, *entry, *group;
+    USER **users = (USER **) hash_get_entries(usertable);
+    USER **groups = (USER **) hash_get_entries(grouptable);
+    USER **p, **q;
+
+    /* users */
+    for (p = users; *p; p++) {
+       user = *p;
+
+       if (debug & DEBUG_PARSE_FLAG)
+           report(LOG_DEBUG, "circularity_check: user=%s", user->name);
+
+       /* Initialise all groups "seen" flags to zero */
+       for (q = groups; *q; q++) {
+           group = *q;
+           group->flags &= ~FLAG_SEEN;
+       }
+
+       entry = user;
+
+       while (entry) {
+           /* check groups we are a member of */
+           char *groupname = entry->member;
+
+           if (debug & DEBUG_PARSE_FLAG)
+               report(LOG_DEBUG, "\tmember of group %s",
+                      groupname ? groupname : "<none>");
+
+
+           /* if not a member of any groups, go on to next user */
+           if (!groupname)
+               break;
+
+           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;
+       }
+    }
+    free(users);
+    free(groups);
+    return (0);
+}
+
+
+/* Return a value for a group or user (isuser says if
+   this name is a group or a user name).
+
+   If no value exists, and recurse is true, also check groups we are a
+   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 */
+
+    user = (USER *) hash_lookup(isuser ? usertable : grouptable, name);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name);
+       return (value);
+    }
+
+    /* found the entry. Lookup value from attr=value */
+    value = get_value(user, attr);
+
+    if (value.pval || !recurse) {
+       return (value);
+    }
+    /* no value. 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_value: recurse group = %s",
+                  group->name);
+
+       value = get_value(group, attr);
+
+       if (value.pval) {
+           return (value);
+       }
+       /* still nothing. Check containing group and so on */
+
+       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(name, isuser, attr, recurse)
+char *name;
+int isuser, attr, recurse;
+{
+    int val = cfg_get_value(name, isuser, 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);
+
+    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;
+{
+    char *p = cfg_get_hvalue(name, attr).pval;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_phvalue: 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. */
+
+cfg_read_config(cfile)
+char *cfile;
+{
+    sym_line = 1;
+
+    if ((cf = fopen(cfile, "r")) == NULL) {
+       report(LOG_ERR, "read_config: fopen() error for file %s %s, exiting",
+              cfile, sys_errlist[errno]);
+       return (1);
+    }
+    if (parse_decls() || sym_error) {
+       fclose(cf);
+       return (1);
+    }
+
+    if (circularity_check()) {
+       fclose(cf);
+       return (1);
+    }
+
+    fclose(cf);
+    return (0);
+}
+
+/* return 1 if user exists, 0 otherwise */
+int
+cfg_user_exists(username)
+char *username;
+{
+    USER *user = (USER *) hash_lookup(usertable, username);
+
+    return (user != NULL);
+}
+
+/* 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;
+
+{
+    return (cfg_get_pvalue(username, TAC_IS_USER, 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 *
+cfg_get_timestamp(username, recurse)
+char *username;
+{
+    return (cfg_get_pvalue(username, TAC_IS_USER, 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;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_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 */
+int
+cfg_get_user_nopasswd(user, recurse)
+    char *user;
+{
+    return (cfg_get_intvalue(user, TAC_IS_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;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse));
+}
+
+char *
+cfg_get_chap_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse));
+}
+
+#ifdef MSCHAP
+char *
+cfg_get_mschap_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse));
+}
+#endif /* MSCHAP */
+
+char *
+cfg_get_pap_secret(user, recurse)
+char *user;
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse));
+}
+
+char *
+cfg_get_opap_secret(user, recurse)
+char *user;
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse));
+}
+
+/* return the global password for the user (or the group, etc.) */
+
+char *
+cfg_get_global_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_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;
+
+{
+ char *cfg_passwd;
+ 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_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!! */
+       }
+}
+
+p=tac_find_substring("pam ", cfg_passwd);
+
+if(p) {  /* We find PAM services */
+       if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "I get PAM sevice:%s",p);
+return (p);
+}
+
+if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "No any PAM Sevice");
+
+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 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);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_svc_node: no user named %s", username);
+       return (NULL);
+    }
+
+    /* found the user entry. Find svc node */
+    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+
+       if (svc->type != type) 
+           continue;
+
+       if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
+           continue;
+       }
+
+       if (type == N_svc && !STREQ(svc->value1, svcname)) {
+           continue;
+       }
+
+       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 : "");
+
+       return(svc);
+    }
+
+    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;
+
+           if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
+               continue;
+           }
+
+           if (type == N_svc && !STREQ(svc->value1, svcname)) {
+               continue;
+           }
+
+           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 : "");
+
+           return(svc);
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
+
+    /* no matching svc node for this user or her containing groups */
+    return (NULL);
+}
+
+/* Return a pointer to the node representing a set of command regexp
+   matches for a user and command, handling recursion issues correctly */
+NODE *
+cfg_get_cmd_node(name, cmdname, recurse)
+char *name, *cmdname;
+int recurse;
+
+{
+    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);
+
+    /* find the user/group entry */
+    user = (USER *) hash_lookup(usertable, name);
+
+    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;
+    }
+
+    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;
+
+    while (group) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_cmd_node: recurse group = %s",
+                  group->name);
+
+       svc = get_value(group, 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 node %s",
+                          cmdname, cfg_nodestring(svc->type));
+               return (svc);
+           }
+           svc = svc->next;
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
+
+    /* no matching cmd node for this user or her containing groups */
+    return (NULL);
+}
+
+/* Return an array of character strings representing configured AV
+ * 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.
+ *
+ * Lastly, indicate what default permission was configured by setting
+ * denyp */
+
+char **
+cfg_get_svc_attrs(svcnode, denyp)
+NODE *svcnode;
+int *denyp;
+{
+    int i;
+    NODE *node;
+    char **args;
+
+    *denyp = 1;
+
+    if (!svcnode)
+       return (NULL);
+
+    *denyp = (svcnode->dflt == S_deny);
+
+    i = 0;
+    for (node = svcnode->value; node; node = node->next)
+       i++;
+
+    args = (char **) tac_malloc(sizeof(char *) * (i + 1));
+
+    i = 0;
+    for (node = svcnode->value; node; node = node->next) {
+       char *arg = tac_strdup(node->value);
+       char *p = index(arg, '=');
+
+       if (p && node->type == N_optarg)
+           *p = '*';
+       args[i++] = arg;
+    }
+    args[i] = NULL;
+    return (args);
+}
+
+
+int
+cfg_user_svc_default_is_permit(user)
+char *user;
+
+{
+    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);
+    case S_permit:
+       return (1);
+    }
+}
+
+int
+cfg_no_user_permitted()
+{
+    if (no_user_dflt == S_permit)
+       return (1);
+    return (0);
+}
+
+
+char *
+cfg_get_authen_default()
+{
+    return (authen_default);
+}
+
+/* For describe authentication method(pam,file,db..etc) */
+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;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_ppp_is_configured: username=%s rec=%d",
+              username, recurse);
+
+    /* find the user/group entry */
+    user = (USER *) hash_lookup(usertable, username);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", 
+                  username);
+       return (0);
+    }
+
+    /* found the user entry. Find svc node */
+    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+
+       if (svc->type != N_svc_ppp) 
+           continue;
+
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
+                  svc->value1);
+       
+       return(1);
+    }
+
+    if (!recurse) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
+       return (0);
+    }
+
+    /* 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_ppp_is_configured: recurse group = %s",
+                  group->name);
+
+       for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {
+
+           if (svc->type != N_svc_ppp)
+               continue;
+
+           if (debug & DEBUG_CONFIG_FLAG)
+               report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
+                      svc->value1);
+       
+           return(1);
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
+
+    /* no PPP svc nodes for this user or her containing groups */
+    return (0);
+}
+
+/* For getting host key */
+char *
+cfg_get_host_key(host)
+char *host;
+{
+    return (cfg_get_phvalue(host, S_key));
+}
+