diff -ruN tacacs+-F4.0.4.27a.orig/CHANGES tacacs+-F4.0.4.27a/CHANGES --- tacacs+-F4.0.4.27a.orig/CHANGES 2013-08-05 00:00:24.000000000 +0800 +++ tacacs+-F4.0.4.27a/CHANGES 2014-08-12 21:52:38.374437290 +0800 @@ -459,3 +459,15 @@ name given to PAM - change socket binding to allow an IPv6 address with the -B argument - bind v4 and v6 sockets if system claims its has addresses for the AFs + +F4.0.4.27a-k1 + Set of changes by Gabor Kiss borrowed from patch + http://bakacsin.ki.iif.hu/~kissg/pd/tac_plus/tacacs+-F4.0.4.14-k5.diff. + - Allow multiple group membership + - Fix a config tree traversal bug + - Add new keyword 'return' to ACL-s. If NAS address matches + such a line check of current ACL is terminated and search for + another ACL is continued in higher level groups. + - Add new keyword 'include'. Configuration files can be nested + in any depth. Syntax: is: include = /absolute/path/file + - Fix stderr/stdin confusion with -P option. diff -ruN tacacs+-F4.0.4.27a.orig/config.c tacacs+-F4.0.4.27a/config.c --- tacacs+-F4.0.4.27a.orig/config.c 2012-06-29 06:37:06.000000000 +0800 +++ tacacs+-F4.0.4.27a/config.c 2014-08-12 23:16:27.070344803 +0800 @@ -128,8 +128,8 @@ static int sym_ch; /* current parse character */ static int sym_code; /* parser output */ static int sym_line = 1; /* current line number */ -static FILE *cf = NULL; /* config file pointer */ -static int sym_error = 0; /* a parsing error occurred */ +static FILE *cf = NULL; /* current cfg file pointer */ +static NODE *pending = NULL; /* chain of pending cfg files */ static char *authen_default = NULL; /* top level authentication default */ static char *nopasswd_str = "nopassword"; @@ -178,7 +178,7 @@ # endif #endif char *global; /* password to use if none set */ - char *member; /* group we are a member of */ + NODE *member; /* groups we are a member of */ char *expires; /* expiration date */ char *arap; /* our arap secret */ char *pap; /* our pap secret */ @@ -235,7 +235,7 @@ void *hosttable[HASH_TAB_SIZE]; /* Table of host declarations */ static VALUE cfg_get_hvalue(char *, int); -static VALUE cfg_get_value(char *, int, int, int); +static VALUE cfg_get_value(char *, int, int, int, ITER_CB *); static int circularity_check(void); static void free_aclstruct(ACL *); static void free_attrs(NODE *); @@ -260,6 +260,15 @@ static int parse_user(void); static void rch(void); static void sym_get(void); +static FILE * open_config(char *); +static int restore_config(void); +static void free_pending(void); + +typedef NODE ITERATOR; +static ITERATOR *new_iterator(char *, int); +static void free_iterator(ITERATOR *); +static NODE *next_iteration(ITERATOR *); +static int depth(ITERATOR *); #ifdef __STDC__ #include /* ANSI C, variable length args */ @@ -691,7 +700,7 @@ { ACL *n; ACL *acl = (ACL *)tac_malloc(sizeof(ACL)); - int isdeny = S_permit; + int perm; memset(acl, 0, sizeof(ACL)); @@ -716,13 +725,13 @@ return(0); case S_deny: - isdeny = S_deny; case S_permit: + case S_return: + perm = sym_code; sym_get(); parse(S_separator); - insert_acl_entry(acl, isdeny); + insert_acl_entry(acl, perm); parse(S_string); - isdeny = S_permit; continue; case S_closebra: @@ -841,6 +850,17 @@ parse_logging(); continue; + case S_include: + /* Process an include file */ + sym_get(); + parse(S_separator); + if (open_config(sym_buf)==NULL) { + parse_error("Cannot include file %s", sym_buf); + return 1; + } + sym_get(); + continue; + default: parse_error("Unrecognised token %s on line %d", sym_buf, sym_line); return(1); @@ -1010,6 +1030,7 @@ int save_sym; char **fieldp; char buf[MAX_INPUT_LINE_LEN]; + NODE *newnode; fieldp = NULL; memset(user, 0, sizeof(USER)); @@ -1248,7 +1269,23 @@ continue; case S_member: - ASSIGN(user->member); + sym_get(); + parse(S_separator); + newnode = (NODE *)tac_malloc(sizeof(NODE)); + newnode->type = N_group; + newnode->value = NULL; + newnode->value1 = tac_strdup(sym_buf); + newnode->next=NULL; + newnode->line = sym_line; + if (user->member) { + /* append chain */ + NODE *p; + for (p=user->member; p->next; p=p->next) /* EMPTY */; + p->next = newnode; + } + else { + user->member = newnode; + } sym_get(); continue; @@ -1637,14 +1674,12 @@ static void rch(void) { - if (sym_error) { - sym_ch = EOF; - return; + while ((sym_ch = getc(cf)) == EOF) { + if (restore_config()) break; } - sym_ch = getc(cf); if (parse_only && sym_ch != EOF) - fprintf(stderr, "%c", sym_ch); + fputc(sym_ch, stdout); } /* For a user or group, find the value of a field. Does not recurse. */ @@ -1810,63 +1845,53 @@ static int circularity_check(void) { - USER *user, *entry, *group; + USER *entry; 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; + ITERATOR *iterator; + NODE *node; if (debug & DEBUG_PARSE_FLAG) - report(LOG_DEBUG, "circularity_check: user=%s", user->name); + report(LOG_DEBUG, "circularity_check: user=%s", (*p)->name); /* Initialise all groups "seen" flags to zero */ for (q = groups; *q; q++) { - group = *q; - group->flags &= ~FLAG_SEEN; + (*q)->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 : ""); - - - /* if not a member of any groups, go on to next user */ - if (!groupname) - break; + iterator = new_iterator((*p)->name, 1); + node = next_iteration(iterator); - 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); + while ((node = next_iteration(iterator))) { + entry = node->value; + if (entry == NULL) { free(users); free(groups); return(1); } - if (group->flags & FLAG_SEEN) { + /* check groups we are a member of */ + + if (debug & DEBUG_PARSE_FLAG) + report(LOG_DEBUG, "\tmember of group %s", + entry->name ? entry->name : ""); + + if (entry->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); + if ((*q)->flags & FLAG_SEEN) + report(LOG_ERR, "%s", (*q)->name); } free(users); free(groups); return(1); } - group->flags |= FLAG_SEEN; /* mark group as seen */ - entry = group; + entry->flags |= FLAG_SEEN; /* mark group as seen */ } } free(users); @@ -1885,9 +1910,11 @@ * really return a union pointer). */ static VALUE -cfg_get_value(char *name, int isuser, int attr, int recurse) +cfg_get_value(char *name, int isuser, int attr, int recurse, ITER_CB *cb) { USER *user, *group; + ITERATOR *iterator; + NODE *node; VALUE value; memset(&value, 0, sizeof(VALUE)); @@ -1897,44 +1924,42 @@ name, isuser, codestring(attr), recurse); /* find the user/group entry */ - user = (USER *)hash_lookup(isuser ? usertable : grouptable, name); + iterator = new_iterator(name, isuser); - if (!user) { + if (!iterator) { if (debug & DEBUG_CONFIG_FLAG) - report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name); + report(LOG_DEBUG, "cfg_get_value: no %s named %s", + isuser ? "user" : "group", name); return(value); } /* found the entry. Lookup value from attr=value */ + node = next_iteration(iterator); + user = node->value; value = get_value(user, attr); - if (value.pval || !recurse) { + if ((value.pval && (!cb || cb->fn(value, cb) == CFG_GET_TERMINATE)) || + !recurse) { + free_iterator(iterator); return(value); } - /* no value. Check containing group */ - if (user->member) - group = (USER *)hash_lookup(grouptable, user->member); - else - group = NULL; - - while (group) { + /* no value. Traverse tree of containing groups */ + while ((node = next_iteration(iterator))) { + group = node->value; if (debug & DEBUG_CONFIG_FLAG) - report(LOG_DEBUG, "cfg_get_value: recurse group = %s", group->name); + report(LOG_DEBUG, "cfg_get_value: recurse group=%s depth=%d", + group->name, depth(iterator)); value = get_value(group, attr); - if (value.pval) { + if (value.pval && (!cb || cb->fn(value, cb) == CFG_GET_TERMINATE)) { + free_iterator(iterator); 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 */ memset(&value, 0, sizeof(VALUE)); + free_iterator(iterator); return(value); } @@ -1977,7 +2002,7 @@ { int val; - val = cfg_get_value(name, isuser, attr, recurse).intval; + val = cfg_get_value(name, isuser, attr, recurse, NULL).intval; if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_get_intvalue: returns %d", val); @@ -1999,11 +2024,11 @@ } char * -cfg_get_pvalue(char *name, int isuser, int attr, int recurse) +cfg_get_pvalue(char *name, int isuser, int attr, int recurse, ITER_CB *cb) { char *p; - p = cfg_get_value(name, isuser, attr, recurse).pval; + p = cfg_get_value(name, isuser, attr, recurse, cb).pval; if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_get_pvalue: returns %s", @@ -2021,12 +2046,10 @@ { sym_line = 1; - if ((cf = fopen(cfile, "r")) == NULL) { - report(LOG_ERR, "read_config: fopen() error for file %s %s, exiting", - cfile, strerror(errno)); + if (open_config(cfile) == NULL) { return(1); } - if (parse_decls() || sym_error) { + if (parse_decls()) { fclose(cf); return(1); } @@ -2037,9 +2060,83 @@ } fclose(cf); + cf = NULL; return(0); } +static FILE * +open_config(char *filename) +{ + if (cf != NULL) { + struct stat newstat; + NODE *newnode, *node; + + newnode = (NODE *) tac_malloc(sizeof(NODE)); + newnode->type = N_file; + newnode->next = pending; + newnode->value = cf; + newnode->line = sym_line + 1; + cf = NULL; + pending = newnode; + + /* check circularity */ + if (stat(filename, &newstat) == -1) { + report(LOG_ERR, "push_config: cannot fstat file %s: %s", + filename, strerror(errno)); + free_pending(); + return NULL; + } + for (node=pending; node; node=node->next) { + struct stat oldstat; + if (fstat(fileno(((FILE*)(node->value))), &oldstat) == -1) { + report(LOG_ERR, "save_config: cannot fstat file: %s", + strerror(errno)); + free_pending(); + return NULL; + } + if (newstat.st_dev == oldstat.st_dev && + newstat.st_ino == oldstat.st_ino) { + report(LOG_ERR, "save_config: endless loop of nested include files detected"); + free_pending(); + return NULL; + } + } + sym_line = 0; + } + else { + sym_line = 1; + } + if ((cf = fopen(filename, "r")) == NULL) { + report(LOG_ERR, "read_config: fopen() error for file %s %s, exiting", + filename, strerror(errno)); + free_pending(); + return NULL; + } + return cf; +} + +static int +restore_config(void) +{ + NODE *node = pending; + + if (node == NULL) return 1; /* no more pending files */ + + if (cf) fclose(cf); + pending = node->next; + sym_line = node->line; /* restore line number */ + cf = node->value; /* restore file handle */ + + free(node); + + return 0; +} + +static void +free_pending(void) { + while (!restore_config()) /* EMPTY */; +} + /* return 1 if user exists, 0 otherwise */ int cfg_user_exists(char *username) @@ -2058,14 +2155,14 @@ char * cfg_get_expires(char *username, int recurse) { - return(cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse)); + return(cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse, NULL)); } #ifdef ACLS /* * check the acl against the provided ip. return S_permit (succeed) if the - * ip matches a permit, else S_deny (fail) if it matches a deny or does not - * match any of the entries. + * ip matches a 'permit', S_return if if matches a 'return', else S_deny (fail) + * if it matches a 'deny' or does not match any of the entries. */ int cfg_acl_check(char *aclname, char *ip) @@ -2087,8 +2184,10 @@ while (next) { if (regexec((regex_t *)next->value1, ip, 0, NULL, 0) == REG_OK) { if (debug & DEBUG_AUTHEN_FLAG) - report(LOG_DEBUG, "ip %s matched %s regex %s of acl filter %s", - ip, next->type == S_deny ? "deny" : "permit", + report(LOG_DEBUG, "ip %s matched '%s' regex '%s' of acl filter %s", + ip, + next->type == S_deny ? "deny" : + next->type == S_permit ? "permit" : "return", next->value, aclname); return(next->type); } @@ -2110,7 +2209,7 @@ char * cfg_get_enable_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_enable, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_enable, recurse, NULL)); } #endif @@ -2153,7 +2252,7 @@ char * cfg_get_login_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse, NULL)); } #ifdef UENABLE @@ -2185,40 +2284,40 @@ char * cfg_get_arap_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse, NULL)); } char * cfg_get_chap_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse, NULL)); } #ifdef MSCHAP char * cfg_get_mschap_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse, NULL)); } #endif /* MSCHAP */ char * cfg_get_pap_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse, NULL)); } char * cfg_get_opap_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse, NULL)); } /* return the global password for the user (or the group, etc.) */ char * cfg_get_global_secret(char *user, int recurse) { - return(cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse)); + return(cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse, NULL)); } /* @@ -2231,6 +2330,8 @@ int recurse) { USER *user, *group; + ITERATOR *iterator; + NODE *node; NODE *svc; if (debug & DEBUG_CONFIG_FLAG) @@ -2240,15 +2341,17 @@ svcname ? svcname : "", recurse); /* find the user/group entry */ - user = (USER *)hash_lookup(usertable, username); + iterator = new_iterator(username, TAC_IS_USER); - if (!user) { + if (!iterator) { 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 */ + node = next_iteration(iterator); + user = node->value; for (svc = (NODE *)get_value(user, S_svc).pval; svc; svc = svc->next) { if (svc->type != type) @@ -2275,19 +2378,16 @@ if (!recurse) { if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_get_svc_node: returns NULL"); + free_iterator(iterator); return(NULL); } - /* no matching node. Check containing group */ - if (user->member) - group = (USER *)hash_lookup(grouptable, user->member); - else - group = NULL; - - while (group) { + /* no matching node. Traverse tree of containing groups */ + while ((node = next_iteration(iterator))) { + group = node->value; if (debug & DEBUG_CONFIG_FLAG) - report(LOG_DEBUG, "cfg_get_svc_node: recurse group = %s", - group->name); + report(LOG_DEBUG, "cfg_get_svc_node: recurse group=%s depth=%d", + group->name, depth(iterator)); for (svc = (NODE *)get_value(group, S_svc).pval; svc; svc = svc->next) { @@ -2308,21 +2408,17 @@ cfg_nodestring(type), protocol ? protocol : "", svcname ? svcname : ""); + free_iterator(iterator); 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 */ + free_iterator(iterator); return(NULL); } @@ -2334,6 +2430,8 @@ cfg_get_cmd_node(char *name, char *cmdname, int recurse) { USER *user, *group; + ITERATOR *iterator; + NODE *node; NODE *svc; if (debug & DEBUG_CONFIG_FLAG) @@ -2341,14 +2439,16 @@ name, cmdname, recurse); /* find the user/group entry */ - user = (USER *)hash_lookup(usertable, name); + iterator = new_iterator(name, TAC_IS_USER); - if (!user) { + if (!iterator) { 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 */ + node = next_iteration(iterator); + user = node->value; svc = (NODE *)get_value(user, S_svc).pval; while (svc) { @@ -2364,43 +2464,32 @@ if (!recurse) { if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL"); + free_iterator(iterator); return(NULL); } - /* no matching node. Check containing group */ - if (user->member) - group = (USER *)hash_lookup(grouptable, user->member); - else - group = NULL; - - while (group) { + /* no matching node. Traverse tree of containing groups */ + while ((node = next_iteration(iterator))) { + group = node->value; if (debug & DEBUG_CONFIG_FLAG) - report(LOG_DEBUG, "cfg_get_cmd_node: recurse group = %s", - group->name); + report(LOG_DEBUG, "cfg_get_cmd_node: recurse group=%s depth=%d", + group->name, depth(iterator)); - svc = get_value(group, S_svc).pval; + for (svc = get_value(group, S_svc).pval; svc; svc = svc->next) { + if (svc->type != N_svc_cmd || !STREQ(svc->value, cmdname)) continue; - 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)); + free_iterator(iterator); 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 */ + free_iterator(iterator); return(NULL); } @@ -2476,6 +2565,8 @@ cfg_ppp_is_configured(char *username, int recurse) { USER *user, *group; + ITERATOR *iterator; + NODE *node; NODE *svc; if (debug & DEBUG_CONFIG_FLAG) @@ -2483,9 +2574,9 @@ username, recurse); /* find the user/group entry */ - user = (USER *)hash_lookup(usertable, username); + iterator = new_iterator(username, TAC_IS_USER); - if (!user) { + if (!iterator) { if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", username); @@ -2493,6 +2584,8 @@ } /* found the user entry. Find svc node */ + node = next_iteration(iterator); + user = node->value; for (svc = (NODE *)get_value(user, S_svc).pval; svc; svc = svc->next) { if (svc->type != N_svc_ppp) @@ -2502,25 +2595,23 @@ report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node", svc->value1); + free_iterator(iterator); return(1); } if (!recurse) { if (debug & DEBUG_CONFIG_FLAG) report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0"); + free_iterator(iterator); return(0); } - /* no matching node. Check containing group */ - if (user->member) - group = (USER *)hash_lookup(grouptable, user->member); - else - group = NULL; - - while (group) { + /* no matching node. Traverse tree of containing groups */ + while ((node = next_iteration(iterator))) { + group = node->value; if (debug & DEBUG_CONFIG_FLAG) - report(LOG_DEBUG, "cfg_ppp_is_configured: recurse group = %s", - group->name); + report(LOG_DEBUG,"cfg_ppp_is_configured: recurse group=%s depth=%d", + group->name, depth(iterator)); for (svc = (NODE *)get_value(group, S_svc).pval; svc; svc = svc->next) { @@ -2531,20 +2622,153 @@ report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s " "node", svc->value1); + free_iterator(iterator); 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 */ + free_iterator(iterator); return(0); } + +/*********************************************************************/ + +/* + * Iterator handling routines. + */ + +static NODE * +node_lookup(NODE *node, int isuser) +{ + if (node==NULL) return NULL; + if (node->value) return node; + node->value = hash_lookup(isuser ? usertable:grouptable, node->value1); + if (node->value) return node; + report(LOG_ERR, "%s %s does not exist", + isuser ? "user" : "group", node->value1); + return NULL; +} + +static void +free_iterator(ITERATOR *iterator) +{ + if (iterator->value && ((NODE*)(iterator->value))->line==-1) { + if (debug & DEBUG_CLEAN_FLAG) + report(LOG_DEBUG, "free guard node"); + free(iterator->value); /* free guard */ + } + if (iterator->next) + free_iterator(iterator->next); + if (debug & DEBUG_CLEAN_FLAG) + report(LOG_DEBUG, "free iterator"); + free(iterator); +} + +/* + * iterator fields: + * type: S_iterator + * next: deeper iterator + * value: chain of S_group nodes + * value1: temporary pointer to S_group nodes + * line: depth + * + * group node fields: + * type: S_group + * next: next group node at the same level + * value: pointer to user/group structure + * value1: string pointer to user/group name (guard: NULL) + * line: line no. (guard: -1) + */ + +static ITERATOR * +new_iterator(char *name, int isuser) +{ + ITERATOR *iterator; + NODE *guard; + USER *user; + + user = hash_lookup(isuser ? usertable : grouptable, name); + if (user == NULL) return NULL; + + guard = (NODE *) tac_malloc(sizeof(NODE)); + guard->type = N_group; + guard->next = NULL; + guard->value = user; + guard->value1 = NULL; + guard->line = -1; /* guard marker */ + + iterator = (ITERATOR *) tac_malloc(sizeof(NODE)); + iterator->type = N_iterator; + iterator->next = NULL; + iterator->value = NULL; /* marks unprocessed root */ + iterator->value1 = guard; /* temporary pointer to root */ + iterator->line = 0; /* tree depth */ + + return iterator; +} + +/* + * Find next node. + */ +static NODE * +next_iteration(ITERATOR *iterator) +{ + NODE *current, *parent; + int subtree_done = 0; + + if (iterator->next) { + /* recursively descent till the last iterator */ + current = next_iteration(iterator->next); + if (current) return current; + + /* subtree exhausted. cut down iterator chain. */ + free_iterator(iterator->next); + iterator->next = NULL; + subtree_done = 1; + } + + current = iterator->value; /* the last processed node */ + if (current == NULL) { + /* process root of tree */ + return iterator->value = iterator->value1; /* restore saved ptr */ + } + + /* check if it has unprocessed parents */ + parent = ((USER*)(current->value))->member; + if (parent && !subtree_done) { + ITERATOR *new_iterator; + /* go deeper */ + if (node_lookup(parent, !TAC_IS_USER)==NULL) return NULL; + + new_iterator = (ITERATOR *) tac_malloc(sizeof(NODE)); + new_iterator->type = N_iterator; + new_iterator->next = NULL; + new_iterator->value = parent; + new_iterator->value1 = NULL; + new_iterator->line = iterator->line + 1; /* tree depth */ + iterator->next = new_iterator; + return parent; + } + else { + /* step right */ + iterator->value = current = current->next; + if (node_lookup(current, !TAC_IS_USER)==NULL) return NULL; + return current; + } +} + +/* + * Return depth of current node. + * Just for debugging purposes. + */ +static int +depth(ITERATOR *iterator) +{ + while (iterator->next) iterator = iterator->next; + return iterator->line; +} diff -ruN tacacs+-F4.0.4.27a.orig/default_fn.c tacacs+-F4.0.4.27a/default_fn.c --- tacacs+-F4.0.4.27a.orig/default_fn.c 2012-06-29 06:37:06.000000000 +0800 +++ tacacs+-F4.0.4.27a/default_fn.c 2014-08-12 22:29:13.858869546 +0800 @@ -824,15 +824,43 @@ #endif /* MSCHAP */ #ifdef ACLS +struct acl_iter_cb { + int (*fn)(VALUE, ITER_CB *); +/* So far is like ITER_CB */ +/* Function specific fields follow */ + char *NAS_ip; /* input from verify_host() */ + int result; /* return from cfg_acl_check: S_(permit|deny|return) */ + char *aclname; /* just for debugging purposes */ +}; +typedef struct acl_iter_cb ACL_ITER_CB; + +/* + * This callback function is used by verify_host(). + * It will be executed for each ACL-s found during config tree traversal + * while returns CFG_GET_CONTINUE. + * The result of last run of cfg_acl_check() is retrieved by verify_host() + * from 'result' field of callback structure. + */ + +static int +acl_check_cb(VALUE value, ITER_CB *cb) { + ACL_ITER_CB *acl_cb = (ACL_ITER_CB *)cb; + + acl_cb->aclname = (char*)value.pval; + acl_cb->result = cfg_acl_check(acl_cb->aclname, acl_cb->NAS_ip); + return acl_cb->result == S_return ? CFG_GET_CONTINUE : CFG_GET_TERMINATE; +} + /* * Verify that the NAS's peerip matches the host acl filter. * Return S_deny if session.peerip is invalid, else S_permit */ + int verify_host(char *name, struct authen_data *data, int type, int recurse) { - char *realname, *val; - int status; + char *realname; + ACL_ITER_CB aclcb; /* lookup host acl for user */ if (!cfg_user_exists(name) && cfg_user_exists(DEFAULT_USERNAME)) { @@ -843,19 +871,21 @@ realname = DEFAULT_USERNAME; } else realname = name; - val = cfg_get_pvalue(realname, 1, type, recurse); - /* no host acl for user */ - if (val == NULL) - return(S_permit); - - if ((status = cfg_acl_check(val, data->NAS_id->NAS_ip)) != S_permit) { - if (debug & DEBUG_AUTHEN_FLAG) - report(LOG_DEBUG, "host ACLs for user '%s' deny", realname); - } else - if (debug & DEBUG_AUTHEN_FLAG) - report(LOG_DEBUG, "host ACLs for user '%s' permit", realname); + aclcb.fn = acl_check_cb; + aclcb.aclname = NULL; + aclcb.NAS_ip = data->NAS_id->NAS_ip; + aclcb.result = S_permit; /* default if no host acl for user */ - return(status); + cfg_get_pvalue(realname, TAC_IS_USER, type, recurse, (ITER_CB*)(void*)&aclcb); + + if (aclcb.result == S_return) { + /* unterminated chain */ + if (debug & DEBUG_AUTHEN_FLAG) { + report(LOG_DEBUG, "ACL chain for user '%s' is unterminated. Last ACL was '%s'.", name, aclcb.aclname); + } + return S_deny; + } + return aclcb.result; } #endif diff -ruN tacacs+-F4.0.4.27a.orig/do_author.c tacacs+-F4.0.4.27a/do_author.c --- tacacs+-F4.0.4.27a.orig/do_author.c 2012-03-28 02:40:57.000000000 +0800 +++ tacacs+-F4.0.4.27a/do_author.c 2014-08-12 22:12:58.702386252 +0800 @@ -174,7 +174,7 @@ * proceed */ cmd = cfg_get_pvalue(username, TAC_IS_USER, - S_before, TAC_PLUS_RECURSE); + S_before, TAC_PLUS_RECURSE, NULL); if (!cmd) return(0); @@ -283,7 +283,7 @@ int out_cnt, i; int status; char *after = cfg_get_pvalue(username, TAC_IS_USER, - S_after, TAC_PLUS_RECURSE); + S_after, TAC_PLUS_RECURSE, NULL); if (!after) return; diff -ruN tacacs+-F4.0.4.27a.orig/parse.c tacacs+-F4.0.4.27a/parse.c --- tacacs+-F4.0.4.27a.orig/parse.c 2012-06-29 06:37:06.000000000 +0800 +++ tacacs+-F4.0.4.27a/parse.c 2014-08-12 22:08:02.362178358 +0800 @@ -121,6 +121,8 @@ declare("PAM", S_pam); #endif declare("syslog", S_syslog); + declare("return", S_return); + declare("include", S_include); } /* Return a keyword code if a keyword is recognized. 0 otherwise */ @@ -266,5 +268,9 @@ #endif case S_syslog: return("syslog"); + case S_return: + return("return"); + case S_include: + return("include"); } } diff -ruN tacacs+-F4.0.4.27a.orig/parse.h tacacs+-F4.0.4.27a/parse.h --- tacacs+-F4.0.4.27a.orig/parse.h 2012-06-29 06:37:06.000000000 +0800 +++ tacacs+-F4.0.4.27a/parse.h 2014-08-12 22:00:31.436448466 +0800 @@ -28,6 +28,7 @@ #define S_openbra 107 #define S_closebra 108 #define S_svc_dflt 109 +#define S_include 110 #define S_key 1 #define S_user 2 @@ -91,3 +92,4 @@ #endif #define S_syslog 50 #define S_aceclnt 51 +#define S_return 52 diff -ruN tacacs+-F4.0.4.27a.orig/tac_plus.h tacacs+-F4.0.4.27a/tac_plus.h --- tacacs+-F4.0.4.27a.orig/tac_plus.h 2012-06-29 06:37:06.000000000 +0800 +++ tacacs+-F4.0.4.27a/tac_plus.h 2014-08-12 21:57:43.634316213 +0800 @@ -297,6 +297,9 @@ #define N_permit 57 #define N_deny 58 #define N_svc 59 +#define N_group 60 +#define N_iterator 61 +#define N_file 62 /* A parse tree node */ typedef struct node { @@ -313,6 +316,15 @@ void *pval; } VALUE; +struct iter_cb { + int (*fn)(VALUE, struct iter_cb *); + /* more members follow ... */ +}; +typedef struct iter_cb ITER_CB; + +#define CFG_GET_TERMINATE 0 +#define CFG_GET_CONTINUE 1 + /* acct.c */ void accounting(u_char *); @@ -380,7 +392,7 @@ char *cfg_get_expires(char *, int); int cfg_get_intvalue(char *, int, int, int); char *cfg_get_phvalue(char *, int); -char *cfg_get_pvalue(char *, int, int, int); +char *cfg_get_pvalue(char *, int, int, int, ITER_CB *); char *cfg_get_global_secret(char *, int); char *cfg_get_host_enable(char *); char *cfg_get_host_key(char *); diff -ruN tacacs+-F4.0.4.27a.orig/version.h.in tacacs+-F4.0.4.27a/version.h.in --- tacacs+-F4.0.4.27a.orig/version.h.in 2013-08-05 00:00:10.000000000 +0800 +++ tacacs+-F4.0.4.27a/version.h.in 2014-08-12 21:53:42.167247903 +0800 @@ -3,6 +3,6 @@ #define VERSION_H char package[] = "tacacs+"; -char version[] = "F4.0.4.27a"; +char version[] = "F4.0.4.27a-k1"; #endif