-/*\r
- * evaluate.c Evaluate a policy language\r
- *\r
- * Version: $Id$\r
- *\r
- * This program is free software; you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation; either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
- *\r
- * Copyright 2004 Alan DeKok <aland@ox.org>\r
- */\r
-\r
-#include "rlm_policy.h"\r
-\r
-#include "modules.h"\r
-\r
-#ifdef HAVE_REGEX_H\r
-#include <regex.h>\r
-#endif\r
-\r
-#define debug_evaluate if (0) printf\r
-\r
-/*\r
- * Print stuff we've parsed\r
- */\r
-static void policy_print(const policy_item_t *item, int indent)\r
-{\r
- if (!item) {\r
- if (indent) printf("%*s", indent, " ");\r
- printf("[NULL]\n");\r
- return;\r
- }\r
- \r
- while (item) {\r
- switch (item->type) {\r
- case POLICY_TYPE_BAD:\r
- if (indent) printf("%*s", indent, " ");\r
- printf("[BAD STATEMENT]");\r
- break;\r
- \r
- case POLICY_TYPE_PRINT:\r
- if (indent) printf("%*s", indent, " ");\r
- {\r
- const policy_print_t *this;\r
-\r
- this = (const policy_print_t *) item;\r
- \r
- if (this->rhs_type == POLICY_LEX_BARE_WORD) {\r
- printf("print %s\n", this->rhs);\r
- } else {\r
- printf("print \"%s\"\n", this->rhs);\r
- }\r
- }\r
- break;\r
- \r
- case POLICY_TYPE_ASSIGNMENT:\r
- {\r
- const policy_assignment_t *assign;\r
- \r
- assign = (const policy_assignment_t *) item;\r
- if (indent) printf("%*s", indent, " ");\r
-\r
- printf("\t%s %s ", assign->lhs,\r
- lrad_int2str(rlm_policy_tokens,\r
- assign->assign, "?"));\r
- if (assign->rhs_type == POLICY_LEX_BARE_WORD) {\r
- printf("%s\n", assign->rhs);\r
- } else {\r
- /*\r
- * FIXME: escape "\r
- */\r
- printf("\"%s\"\n", assign->rhs);\r
- }\r
- }\r
- break;\r
-\r
- case POLICY_TYPE_CONDITIONAL: /* no indentation here */\r
- {\r
- const policy_condition_t *condition;\r
-\r
- condition = (const policy_condition_t *) item;\r
-\r
- printf("(");\r
-\r
- /*\r
- * Nested conditions.\r
- */\r
- if (condition->compare == POLICY_LEX_L_BRACKET) {\r
- policy_print(condition->child, indent);\r
- printf(")");\r
- break;\r
- }\r
-\r
- if (condition->compare == POLICY_LEX_L_NOT) {\r
- printf("!");\r
- policy_print(condition->child, indent);\r
- printf(")");\r
- break;\r
- }\r
-\r
- if (condition->compare == POLICY_LEX_CMP_TRUE) {\r
- printf("%s)", condition->lhs);\r
- break;\r
- }\r
-\r
- if (condition->lhs_type == POLICY_LEX_BARE_WORD) {\r
- printf("%s", condition->lhs);\r
- } else {\r
- /*\r
- * FIXME: escape ",\r
- * and move all of this logic\r
- * to a function.\r
- */\r
- printf("\"%s\"", condition->lhs);\r
- }\r
-\r
- /*\r
- * We always print this condition.\r
- */\r
- printf(" %s ", lrad_int2str(rlm_policy_tokens,\r
- condition->compare,\r
- "?"));\r
- if (condition->rhs_type == POLICY_LEX_BARE_WORD) {\r
- printf("%s", condition->rhs);\r
- } else {\r
- /*\r
- * FIXME: escape ",\r
- * and move all of this logic\r
- * to a function.\r
- */\r
- printf("\"%s\"", condition->rhs);\r
- }\r
- printf(")");\r
- \r
- if (condition->child_condition != POLICY_LEX_BAD) {\r
- printf(" %s ", lrad_int2str(rlm_policy_tokens, condition->child_condition, "?"));\r
- policy_print(condition->child, indent);\r
- }\r
- }\r
- break;\r
-\r
- case POLICY_TYPE_IF:\r
- {\r
- const policy_if_t *statement;\r
-\r
- statement = (const policy_if_t *) item;\r
-\r
- if (indent) printf("%*s", indent, " ");\r
- printf("if ");\r
- policy_print(statement->condition, indent);\r
- printf(" {\n");\r
- policy_print(statement->if_true, indent + 1);\r
- if (indent) printf("%*s", indent, " ");\r
- if (statement->if_false) {\r
- printf("} else ");\r
- if (statement->if_false->type == POLICY_TYPE_ASSIGNMENT) {\r
- printf(" { ");\r
- policy_print(statement->if_false, indent + 1);\r
- if (indent) printf("%*s", indent, " ");\r
- printf(" }");\r
- } else {\r
- policy_print(statement->if_false, indent + 1);\r
- }\r
- } else {\r
- printf("}\n");\r
- }\r
- }\r
- break;\r
-\r
- case POLICY_TYPE_ATTRIBUTE_LIST:\r
- {\r
- const policy_attributes_t *this;\r
-\r
- this = (const policy_attributes_t *) item;\r
-\r
- if (indent) printf("%*s", indent, " ");\r
- printf("%s %s {\n",\r
- lrad_int2str(policy_reserved_words,\r
- this->where, "?"),\r
- lrad_int2str(rlm_policy_tokens,\r
- this->how, "?"));\r
- policy_print(this->attributes, indent + 1);\r
- if (indent) printf("%*s", indent, " ");\r
- printf("}\n");\r
- }\r
- break;\r
-\r
- case POLICY_TYPE_NAMED_POLICY:\r
- {\r
- const policy_named_t *this;\r
-\r
- this = (const policy_named_t *) item;\r
- if (indent) printf("%*s", indent, " ");\r
- printf("policy %s {\n", this->name);\r
- policy_print(this->policy, indent + 1);\r
- if (indent) printf("%*s", indent, " ");\r
- printf("}\n");\r
- }\r
- break;\r
-\r
- case POLICY_TYPE_CALL:\r
- {\r
- const policy_call_t *this;\r
-\r
- this = (const policy_call_t *) item;\r
- if (indent) printf("%*s", indent, " ");\r
- printf("call %s\n", this->name);\r
- }\r
- break;\r
-\r
- default:\r
- if (indent) printf("%*s", indent, " ");\r
- printf("[HUH?]\n");\r
- break;\r
- \r
- }\r
-\r
- item = item->next;\r
- }\r
-}\r
-\r
-\r
-void rlm_policy_print(const policy_item_t *item)\r
-{\r
- printf("----------------------------------------------------------\n");\r
- policy_print(item, 0);\r
- printf("----------------------------------------------------------\n");\r
-}\r
-\r
-/*\r
- * Internal stack of things to do.\r
- *\r
- * When a function is about to be pushed onto the stack, we walk\r
- * backwards through the stack, and ensure that the function is\r
- * not already there. This prevents infinite recursion.\r
- *\r
- * This means that we NEVER pop functions. Rather, we push the\r
- * function, and then immediately push it's first element.\r
- *\r
- * When we've finished popping all of the elements, we pop the\r
- * function, realize it's a function, ignore it, and pop one more\r
- * entry.\r
- */\r
-#define POLICY_MAX_STACK 16\r
-typedef struct policy_state_t {\r
- rlm_policy_t *inst;\r
- int depth;\r
- REQUEST *request; /* so it's not passed on the C stack */\r
- const policy_item_t *stack[POLICY_MAX_STACK];\r
-} policy_state_t;\r
-\r
-\r
-/*\r
- * Push an item onto the state.\r
- */\r
-static int policy_stack_push(policy_state_t *state, const policy_item_t *item)\r
-{\r
- rad_assert(state->depth >= 0);\r
-\r
- /*\r
- * Asked to push nothing. Don't push it.\r
- */\r
- if (!item) return 1;\r
-\r
- /*\r
- * State is full. Die.\r
- */\r
- if (state->depth >= POLICY_MAX_STACK) {\r
- return 0;\r
- }\r
-\r
- /*\r
- * Walk back up the stack, looking for previous ocurrances\r
- * of this name. If found, we have infinite recursion,\r
- * which we stop dead in the water!\r
- */\r
- if (item->type == POLICY_TYPE_NAMED_POLICY) {\r
- int i;\r
-\r
- for (i = 0; i < state->depth; i++) {\r
- if (state->stack[i]->type != POLICY_TYPE_NAMED_POLICY) {\r
- continue;\r
- }\r
-\r
- /*\r
- * FIXME: check for more stuff.\r
- */\r
- }\r
- }\r
-\r
- debug_evaluate("push %d %p\n", state->depth, item);\r
-\r
- state->stack[state->depth] = item;\r
- state->depth++; /* points to unused entry */\r
-\r
- return 1;\r
-}\r
-\r
-\r
-/*\r
- * Pop an item from the state.\r
- */\r
-static int policy_stack_pop(policy_state_t *state, const policy_item_t **pitem)\r
-{\r
- rad_assert(pitem != NULL);\r
- rad_assert(state->depth >= 0);\r
-\r
- if (state->depth == 0) {\r
- *pitem = NULL;\r
- return 0;\r
- }\r
-\r
- *pitem = state->stack[state->depth - 1];\r
-\r
- /*\r
- * Process the whole item list.\r
- */\r
- if ((*pitem)->next) {\r
- state->stack[state->depth - 1] = (*pitem)->next;\r
- debug_evaluate("pop/push %d %p\n", state->depth - 1, *pitem);\r
- } else {\r
- state->depth--; /* points to unused entry */\r
- debug_evaluate("pop %d %p\n", state->depth, *pitem);\r
- }\r
-\r
- return 1;\r
-}\r
-\r
-\r
-/*\r
- * Evaluate a print statement\r
- */\r
-static int evaluate_print(policy_state_t *state, const policy_item_t *item)\r
-{\r
- const policy_print_t *this;\r
-\r
- this = (const policy_print_t *) item;\r
-\r
- if (this->rhs_type == POLICY_LEX_BARE_WORD) {\r
- printf("%s\n", this->rhs);\r
- } else {\r
- char buffer[1024];\r
-\r
- radius_xlat(buffer, sizeof(buffer), this->rhs,\r
- state->request, NULL);\r
- printf("%s", buffer);\r
- }\r
-\r
- return 1;\r
-}\r
-\r
-/*\r
- * Return a VALUE_PAIR, given an attribute name.\r
- *\r
- * FIXME: Have it return the N'th one, too, like\r
- * doc/variables.txt?\r
- *\r
- * The amount of duplicated code is getting annoying...\r
- */\r
-static VALUE_PAIR *find_vp(REQUEST *request, const char *name)\r
-{\r
- const char *p;\r
- const DICT_ATTR *dattr;\r
- VALUE_PAIR *vps;\r
-\r
- p = name;\r
- vps = request->packet->vps;;\r
-\r
- /*\r
- * FIXME: use names from reserved word list?\r
- */\r
- if (strncasecmp(name, "request:", 8) == 0) {\r
- p += 8;\r
- } else if (strncasecmp(name, "reply:", 6) == 0) {\r
- p += 6;\r
- vps = request->reply->vps;\r
- } else if (strncasecmp(name, "proxy-request:", 14) == 0) {\r
- p += 14;\r
- if (request->proxy) {\r
- vps = request->proxy->vps;\r
- }\r
- } else if (strncasecmp(name, "proxy-reply:", 12) == 0) {\r
- p += 12;\r
- if (request->proxy_reply) {\r
- vps = request->proxy_reply->vps;\r
- }\r
- } else if (strncasecmp(name, "control:", 8) == 0) {\r
- p += 8;\r
- vps = request->config_items;\r
- } /* else it must be a bare attribute name */\r
-\r
- if (!vps) {\r
- return NULL;\r
- }\r
-\r
- dattr = dict_attrbyname(p);\r
- if (!dattr) {\r
- fprintf(stderr, "No such attribute %s\n", p);\r
- return NULL; /* no such attribute */\r
- }\r
-\r
- return pairfind(vps, dattr->attr);\r
-}\r
-\r
-\r
-/*\r
- * Evaluate an assignment\r
- */\r
-static int evaluate_assignment(policy_state_t *state, const policy_item_t *item)\r
-{\r
- const policy_assignment_t *this;\r
- const DICT_ATTR *dattr;\r
-\r
- this = (const policy_assignment_t *) item;\r
-\r
- rad_assert(this->lhs != NULL);\r
- rad_assert(this->rhs != NULL);\r
-\r
-#if 0\r
- dattr = dict_attrbyname(this->lhs);\r
- if (!dattr) {\r
- fprintf(stderr, "HUH?\n");\r
- return 0;\r
- }\r
-#endif\r
-\r
- return 1;\r
-}\r
-\r
-\r
-/*\r
- * Evaluate a condition\r
- */\r
-static int evaluate_condition(policy_state_t *state, const policy_item_t *item)\r
-{\r
- int rcode;\r
- const policy_condition_t *this;\r
- VALUE_PAIR *vp;\r
- char *data = NULL;\r
- int compare;\r
-#ifdef HAVE_REGEX_H\r
- regex_t reg;\r
-#endif\r
- char buffer[256];\r
- char lhs_buffer[2048];\r
-\r
- this = (const policy_condition_t *) item;\r
-\r
- redo:\r
- /*\r
- * FIXME: Don't always do this...\r
- */\r
- if ((this->compare != POLICY_LEX_L_BRACKET) &&\r
- (this->lhs_type == POLICY_LEX_DOUBLE_QUOTED_STRING)) {\r
- if (radius_xlat(lhs_buffer, sizeof(lhs_buffer), this->lhs,\r
- state->request, NULL) > 0) {\r
- data = lhs_buffer;\r
- }\r
- }\r
- \r
- switch (this->compare) {\r
- case POLICY_LEX_L_BRACKET: /* nested brackets are a special case */\r
- rcode = evaluate_condition(state, this->child);\r
- break;\r
-\r
- case POLICY_LEX_L_NOT:\r
- rcode = evaluate_condition(state, this->child);\r
- rcode = (rcode == FALSE); /* reverse sense of test */\r
- break;\r
-\r
- case POLICY_LEX_CMP_TRUE: /* existence */\r
- if (this->lhs_type == POLICY_LEX_BARE_WORD) {\r
- vp = find_vp(state->request, this->lhs);\r
- rcode = (vp != NULL);\r
- } else {\r
- rcode = (data != NULL);\r
- }\r
- break;\r
-\r
- default: /* process other comparisons */\r
- if ((this->compare != POLICY_LEX_CMP_EQUALS) &&\r
-#ifdef HAVE_REGEX_H\r
- (this->compare != POLICY_LEX_RX_EQUALS) &&\r
- (this->compare != POLICY_LEX_RX_NOT_EQUALS) &&\r
-#endif\r
- (this->compare != POLICY_LEX_LT) &&\r
- (this->compare != POLICY_LEX_GT) &&\r
- (this->compare != POLICY_LEX_LE) &&\r
- (this->compare != POLICY_LEX_GE) &&\r
- (this->compare != POLICY_LEX_CMP_NOT_EQUALS)) {\r
- fprintf(stderr, "%d: bad comparison\n",\r
- this->item.lineno);\r
- return FALSE;\r
- }\r
-\r
- if (this->lhs_type == POLICY_LEX_BARE_WORD) {\r
- VALUE_PAIR *myvp;\r
-\r
-\r
- vp = find_vp(state->request, this->lhs);\r
- /*\r
- * FIXME: Move sanity checks to\r
- * post-parse code, so we don't do\r
- * it on every packet.\r
- */\r
- if (vp) {\r
- vp_prints_value(buffer, sizeof(buffer), vp, 0);\r
- myvp = pairmake(vp->name, this->rhs, T_OP_EQ);\r
- } else {\r
- buffer[0] = '\0';\r
- myvp = pairmake(this->lhs, this->rhs, T_OP_EQ);\r
- }\r
- data = buffer;\r
- if (!myvp) {\r
- return FALSE;\r
- }\r
-\r
- /*\r
- * FIXME: What to do about comparisons\r
- * where vp doesn't exist? Right now,\r
- * "simplepaircmp" returns -1, which is\r
- * probably a bad idea. it should\r
- * instead take an operator, a pointer to\r
- * the comparison result, and return\r
- * "true/false" for "comparions\r
- * succeeded/failed", which are different\r
- * error codes than "comparison is less\r
- * than, equal to, or greater than zero".\r
- */\r
- compare = simplepaircmp(state->request,\r
- vp, myvp);\r
- pairfree(&myvp);\r
- \r
- } else {\r
- /*\r
- * FIXME: Do something for RHS type?\r
- */\r
- printf("CMP %s %s\n", lhs_buffer, this->rhs);\r
- compare = strcmp(lhs_buffer, this->rhs);\r
- }\r
-\r
- debug_evaluate("CONDITION COMPARE %d\n", compare);\r
- \r
- switch (this->compare) {\r
- case POLICY_LEX_CMP_EQUALS:\r
- rcode = (compare == 0);\r
- break;\r
- \r
- case POLICY_LEX_CMP_NOT_EQUALS:\r
- rcode = (compare != 0);\r
- break;\r
- \r
- case POLICY_LEX_LT:\r
- rcode = (compare < 0);\r
- break;\r
- \r
- case POLICY_LEX_GT:\r
- rcode = (compare > 0);\r
- break;\r
- \r
- case POLICY_LEX_LE:\r
- rcode =(compare <= 0);\r
- break;\r
- \r
- case POLICY_LEX_GE:\r
- rcode = (compare >= 0);\r
- break;\r
- \r
-#ifdef HAVE_REGEX_H\r
- case POLICY_LEX_RX_EQUALS:\r
- { /* FIXME: copied from src/main/valuepair.c */\r
- int i;\r
- regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];\r
- \r
- /*\r
- * Include substring matches.\r
- */\r
- if (regcomp(®, this->rhs,\r
- REG_EXTENDED) != 0) {\r
- return FALSE;\r
- }\r
- rad_assert(data != NULL);\r
- rcode = regexec(®, data,\r
- REQUEST_MAX_REGEX + 1,\r
- rxmatch, 0);\r
- rcode = (rcode == 0);\r
- regfree(®);\r
- \r
- /*\r
- * Add %{0}, %{1}, etc.\r
- */\r
- for (i = 0; i <= REQUEST_MAX_REGEX; i++) {\r
- char *p;\r
- char rxbuffer[256];\r
- \r
- /*\r
- * Didn't match: delete old\r
- * match, if it existed.\r
- */\r
- if (!rcode ||\r
- (rxmatch[i].rm_so == -1)) {\r
- p = request_data_get(state->request, state->request,\r
- REQUEST_DATA_REGEX | i);\r
- if (p) {\r
- free(p);\r
- continue;\r
- }\r
- \r
- /*\r
- * No previous match\r
- * to delete, stop.\r
- */\r
- break;\r
- }\r
- \r
- /*\r
- * Copy substring into buffer.\r
- */\r
- memcpy(rxbuffer,\r
- data + rxmatch[i].rm_so,\r
- rxmatch[i].rm_eo - rxmatch[i].rm_so);\r
- rxbuffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';\r
- \r
- /*\r
- * Copy substring, and add it to\r
- * the request.\r
- *\r
- * Note that we don't check\r
- * for out of memory, which is\r
- * the only error we can get...\r
- */\r
- p = strdup(rxbuffer);\r
- request_data_add(state->request,\r
- state->request,\r
- REQUEST_DATA_REGEX | i,\r
- p, free);\r
- }\r
- \r
- }\r
- break;\r
- \r
- case POLICY_LEX_RX_NOT_EQUALS:\r
- regcomp(®, this->rhs, REG_EXTENDED|REG_NOSUB);\r
- rad_assert(data != NULL);\r
- rcode = regexec(®, data,\r
- 0, NULL, 0);\r
- rcode = (rcode != 0);\r
- regfree(®);\r
- break;\r
-#endif /* HAVE_REGEX_H */\r
- default:\r
- rcode = FALSE;\r
- break;\r
- } /* switch over comparison operators */\r
- break; /* default from first switch over compare */\r
- }\r
-\r
- /*\r
- * No trailing &&, ||\r
- */\r
- switch (this->child_condition) {\r
- default:\r
- return rcode;\r
-\r
- case POLICY_LEX_L_AND:\r
- if (!rcode) return rcode; /* FALSE && x == FALSE */\r
- break;\r
-\r
- case POLICY_LEX_L_OR:\r
- if (rcode) return rcode; /* TRUE && x == TRUE */\r
- break;\r
- }\r
-\r
- /*\r
- * Tail recursion.\r
- */\r
- this = (const policy_condition_t *) this->child;\r
- goto redo;\r
-\r
- return 1; /* should never reach here */\r
-}\r
-\r
-\r
-/*\r
- * Evaluate an 'if' statement\r
- */\r
-static int evaluate_if(policy_state_t *state, const policy_item_t *item)\r
-{\r
- int rcode;\r
- const policy_if_t *this;\r
-\r
- this = (const policy_if_t *) item;\r
-\r
- /*\r
- * evaluate_condition calls itself recursively.\r
- * We should probably allocate a new state, instead.\r
- */\r
- rcode = evaluate_condition(state, this->condition);\r
- debug_evaluate("IF condition returned %s\n",\r
- rcode ? "true" : "false");\r
- if (rcode) {\r
- rcode = policy_stack_push(state, this->if_true);\r
- if (!rcode) return rcode;\r
- } else if (this->if_false) {\r
- rcode = policy_stack_push(state, this->if_false);\r
- if (!rcode) return rcode;\r
- }\r
-\r
- /*\r
- * 'if' can fail, if the block it's processing fails.\r
- */\r
- return 1;;\r
-}\r
-\r
-\r
-/*\r
- * Make a VALUE_PAIR from a policy_assignment_t*\r
- *\r
- * The assignment operator has to be '='.\r
- */\r
-static VALUE_PAIR *assign2vp(REQUEST *request,\r
- const policy_assignment_t *assign)\r
-{\r
- VALUE_PAIR *vp;\r
- LRAD_TOKEN operator = T_OP_EQ;\r
- const char *value = assign->rhs;\r
- char buffer[2048];\r
-\r
- if ((assign->rhs_type == POLICY_LEX_DOUBLE_QUOTED_STRING) &&\r
- (strchr(assign->rhs, '%') != NULL)) {\r
- radius_xlat(buffer, sizeof(buffer), assign->rhs,\r
- request, NULL);\r
- value = buffer;\r
- }\r
-\r
- /*\r
- * This is crappy.. fix it.\r
- */\r
- switch (assign->assign) {\r
- case POLICY_LEX_ASSIGN:\r
- operator = T_OP_EQ;\r
- break;\r
-\r
- case POLICY_LEX_SET_EQUALS:\r
- operator = T_OP_SET;\r
- break;\r
- \r
- case POLICY_LEX_PLUS_EQUALS:\r
- operator = T_OP_ADD;\r
- break;\r
- \r
- default:\r
- fprintf(stderr, "Expected '=' for operator, not '%s' at line %d\n",\r
- lrad_int2str(rlm_policy_tokens,\r
- assign->assign, "?"),\r
- assign->item.lineno);\r
- return NULL;\r
- }\r
- \r
- vp = pairmake(assign->lhs, value, operator);\r
- if (!vp) {\r
- fprintf(stderr, "SHIT: %s %s\n", value, librad_errstr);\r
- }\r
-\r
- return vp;\r
-}\r
-\r
-\r
-/*\r
- * Evaluate a 'packet .= {attrs}' statement\r
- */\r
-static int evaluate_attr_list(policy_state_t *state, const policy_item_t *item)\r
-{\r
- const policy_attributes_t *this;\r
- VALUE_PAIR **vps = NULL;\r
- VALUE_PAIR *vp, *head, **tail;\r
- const policy_item_t *attr;\r
-\r
- this = (const policy_attributes_t *) item;\r
-\r
- switch (this->where) {\r
- case POLICY_RESERVED_CONTROL:\r
- vps = &(state->request->config_items);\r
- break;\r
-\r
- case POLICY_RESERVED_REQUEST:\r
- vps = &(state->request->packet->vps);\r
- break;\r
-\r
- case POLICY_RESERVED_REPLY:\r
- vps = &(state->request->reply->vps);\r
- break;\r
-\r
- case POLICY_RESERVED_PROXY_REQUEST:\r
- if (!state->request->proxy) return 0; /* FIXME: print error */\r
- vps = &(state->request->proxy->vps);\r
- break;\r
-\r
- case POLICY_RESERVED_PROXY_REPLY:\r
- if (!state->request->proxy_reply) return 0; /* FIXME: print error */\r
- vps = &(state->request->proxy_reply->vps);\r
- break;\r
-\r
- default:\r
- return 0;\r
- }\r
-\r
- head = NULL;\r
- tail = &head;\r
-\r
- for (attr = this->attributes; attr != NULL; attr = attr->next) {\r
- if (attr->type != POLICY_TYPE_ASSIGNMENT) {\r
- fprintf(stderr, "bad assignment in attribute list at line %d\n", attr->lineno);\r
- pairfree(&head);\r
- return 0;\r
- }\r
-\r
- vp = assign2vp(state->request, (const policy_assignment_t *) attr);\r
- if (!vp) {\r
- fprintf(stderr, "Failed to allocate VP\n");\r
- pairfree(&head);\r
- return 0;\r
- }\r
- *tail = vp;\r
- tail = &(vp->next);\r
- }\r
-\r
- switch (this->how) {\r
- case POLICY_LEX_SET_EQUALS: /* dangerous: removes all previous things! */\r
- pairfree(vps);\r
- *vps = head;\r
- break;\r
-\r
- case POLICY_LEX_ASSIGN: /* 'union' */\r
- pairmove(vps, &head);\r
- pairfree(&head);\r
- break;\r
-\r
- case POLICY_LEX_CONCAT_EQUALS:\r
- pairadd(vps, head);\r
- break;\r
-\r
- default:\r
- fprintf(stderr, "HUH?\n");\r
- pairfree(&head);\r
- return 0;\r
- }\r
-\r
- return 1;\r
-}\r
-\r
-\r
-/*\r
- * Evaluate an 'call foo' statement\r
- */\r
-static int evaluate_call(policy_state_t *state, const policy_item_t *item)\r
-{\r
- int rcode;\r
- const policy_call_t *this;\r
- rlm_policy_name_t mypolicy;\r
- const rlm_policy_name_t *policy;\r
-\r
- this = (const policy_call_t *) item;\r
-\r
- strNcpy(mypolicy.name, this->name, sizeof(mypolicy.name));\r
- policy = rbtree_finddata(state->inst->policies, &mypolicy);\r
- if (!policy) return 0; /* not found... */\r
- \r
- DEBUG2("rlm_policy: Evaluating policy %s", this->name);\r
- \r
- rad_assert(policy->policy->type != POLICY_TYPE_BAD);\r
- rad_assert(policy->policy->type < POLICY_TYPE_NUM_TYPES);\r
- \r
- /*\r
- * Push it onto the stack. Other code will take care of\r
- * calling it.\r
- */\r
- rcode = policy_stack_push(state, policy->policy);\r
- if (!rcode) {\r
- return rcode;\r
- }\r
-\r
- /*\r
- * Function calls always succeed?\r
- *\r
- * FIXME: Push the function name, etc. onto the stack,\r
- * so we can check for infinite recursion above, and\r
- * so we can also check for return codes from functions\r
- * we call...\r
- */\r
- return 1;\r
-}\r
-\r
-\r
-/*\r
- * State machine stuff.\r
- */\r
-typedef int (*policy_evaluate_type_t)(policy_state_t *, const policy_item_t *);\r
-\r
-\r
-/*\r
- * MUST be kept in sync with policy_type_t\r
- */\r
-static policy_evaluate_type_t evaluate_functions[POLICY_TYPE_NUM_TYPES] = {\r
- NULL, /* POLICY_TYPE_BAD */\r
- evaluate_if,\r
- evaluate_condition,\r
- evaluate_assignment,\r
- evaluate_attr_list,\r
- evaluate_print,\r
- NULL, /* define a named policy.. */\r
- evaluate_call\r
-};\r
-\r
-\r
-/*\r
- * Evaluate a policy, keyed by name.\r
- */\r
-static int policy_evaluate_name(policy_state_t *state, const char *name)\r
-{\r
- int rcode;\r
- const policy_item_t *this;\r
- rlm_policy_name_t mypolicy, *policy;\r
- \r
- strNcpy(mypolicy.name, name, sizeof(mypolicy.name));\r
- policy = rbtree_finddata(state->inst->policies, &mypolicy);\r
- if (!policy) return RLM_MODULE_FAIL;\r
- \r
- DEBUG2("rlm_policy: Evaluating policy %s", name);\r
- \r
- rad_assert(policy->policy->type != POLICY_TYPE_BAD);\r
- rad_assert(policy->policy->type < POLICY_TYPE_NUM_TYPES);\r
- \r
- rcode = policy_stack_push(state, policy->policy);\r
- if (!rcode) {\r
- return RLM_MODULE_FAIL;\r
- }\r
-\r
- /*\r
- * FIXME: Look for magic keywords like "return",\r
- * where the packet gets accepted/rejected/whatever\r
- */\r
- while (policy_stack_pop(state, &this)) {\r
- rad_assert(this != NULL);\r
- rad_assert(this->type != POLICY_TYPE_BAD);\r
- rad_assert(this->type < POLICY_TYPE_NUM_TYPES);\r
- \r
- debug_evaluate("Evaluating at line %d\n",\r
- this->lineno);\r
- rcode = (*evaluate_functions[this->type])(state,\r
- this);\r
- if (!rcode) {\r
- return RLM_MODULE_FAIL;\r
- }\r
- } /* loop until the stack is empty */\r
-\r
- return RLM_MODULE_OK;\r
-}\r
-\r
-\r
-/*\r
- * Evaluate, which is pretty close to print, but we look at what\r
- * we're printing.\r
- */\r
-int rlm_policy_evaluate(rlm_policy_t *inst, REQUEST *request, const char *name)\r
-{\r
- int rcode;\r
- policy_state_t *state;\r
-\r
- state = rad_malloc(sizeof(*state));\r
- memset(state, 0, sizeof(*state));\r
- state->request = request;\r
- state->inst = inst;\r
-\r
- rcode = policy_evaluate_name(state, name);\r
-\r
- free(state);\r
-\r
- return rcode; /* evaluated OK. */\r
-}\r
+/*
+ * evaluate.c Evaluate a policy language
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2004 Alan DeKok <aland@ox.org>
+ * Copyright 2006 The FreeRADIUS server project
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include "rlm_policy.h"
+
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+
+#define debug_evaluate if (0) printf
+
+/*
+ * Print stuff we've parsed
+ */
+static void policy_print(const policy_item_t *item, int indent)
+{
+ if (!item) {
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "[NULL]\n");
+ return;
+ }
+
+ while (item) {
+ switch (item->type) {
+ case POLICY_TYPE_BAD:
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "[BAD STATEMENT]");
+ break;
+
+ case POLICY_TYPE_PRINT:
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ {
+ const policy_print_t *this;
+
+ this = (const policy_print_t *) item;
+
+ if (this->rhs_type == POLICY_LEX_BARE_WORD) {
+ fprintf(fr_log_fp, "print %s\n", this->rhs);
+ } else {
+ fprintf(fr_log_fp, "print \"%s\"\n", this->rhs);
+ }
+ }
+ break;
+
+ case POLICY_TYPE_ASSIGNMENT:
+ {
+ const policy_assignment_t *assign;
+
+ assign = (const policy_assignment_t *) item;
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+
+ fprintf(fr_log_fp, "\t%s %s ", assign->lhs,
+ fr_int2str(rlm_policy_tokens,
+ assign->assign, "?"));
+ if (assign->rhs_type == POLICY_LEX_BARE_WORD) {
+ fprintf(fr_log_fp, "%s\n", assign->rhs);
+ } else {
+ /*
+ * FIXME: escape "
+ */
+ fprintf(fr_log_fp, "\"%s\"\n", assign->rhs);
+ }
+ }
+ break;
+
+ case POLICY_TYPE_CONDITIONAL: /* no indentation here */
+ {
+ const policy_condition_t *condition;
+
+ condition = (const policy_condition_t *) item;
+
+ fprintf(fr_log_fp, "(");
+
+ if (condition->sense) {
+ fprintf(fr_log_fp, "!");
+ }
+
+ /*
+ * Nested conditions.
+ */
+ if (condition->compare == POLICY_LEX_L_BRACKET) {
+ policy_print(condition->child, indent);
+ fprintf(fr_log_fp, ")");
+ break;
+ }
+
+ if (condition->compare == POLICY_LEX_L_NOT) {
+ fprintf(fr_log_fp, "!");
+ policy_print(condition->child, indent);
+ fprintf(fr_log_fp, ")");
+ break;
+ }
+
+ if (condition->compare == POLICY_LEX_CMP_TRUE) {
+ fprintf(fr_log_fp, "%s)", condition->lhs);
+ break;
+ }
+
+ if (condition->lhs_type == POLICY_LEX_FUNCTION) {
+ fprintf(fr_log_fp, "%s()", condition->lhs);
+ } else {
+ /*
+ * FIXME: escape ",
+ * and move all of this logic
+ * to a function.
+ */
+ fprintf(fr_log_fp, "\"%s\"", condition->lhs);
+ }
+
+ /*
+ * We always print this condition.
+ */
+ fprintf(fr_log_fp, " %s ", fr_int2str(rlm_policy_tokens,
+ condition->compare,
+ "?"));
+ if (condition->rhs_type == POLICY_LEX_BARE_WORD) {
+ fprintf(fr_log_fp, "%s", condition->rhs);
+ } else {
+ /*
+ * FIXME: escape ",
+ * and move all of this logic
+ * to a function.
+ */
+ fprintf(fr_log_fp, "\"%s\"", condition->rhs);
+ }
+ fprintf(fr_log_fp, ")");
+
+ if ((condition->child_condition != POLICY_LEX_BAD) &&
+ (condition->child_condition != POLICY_LEX_BARE_WORD)) {
+ fprintf(fr_log_fp, " %s ", fr_int2str(rlm_policy_tokens, condition->child_condition, "?"));
+ policy_print(condition->child, indent);
+ }
+ }
+ break;
+
+ case POLICY_TYPE_IF:
+ {
+ const policy_if_t *statement;
+
+ statement = (const policy_if_t *) item;
+
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "if ");
+ policy_print(statement->condition, indent);
+ fprintf(fr_log_fp, " {\n");
+ policy_print(statement->if_true, indent + 1);
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ if (statement->if_false) {
+ fprintf(fr_log_fp, "} else ");
+ if (statement->if_false->type == POLICY_TYPE_ASSIGNMENT) {
+ fprintf(fr_log_fp, " { ");
+ policy_print(statement->if_false, indent + 1);
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, " }");
+ } else {
+ policy_print(statement->if_false, indent + 1);
+ }
+ } else {
+ fprintf(fr_log_fp, "}\n");
+ }
+ }
+ break;
+
+ case POLICY_TYPE_ATTRIBUTE_LIST:
+ {
+ const policy_attributes_t *this;
+
+ this = (const policy_attributes_t *) item;
+
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "%s %s {\n",
+ fr_int2str(policy_reserved_words,
+ this->where, "?"),
+ fr_int2str(rlm_policy_tokens,
+ this->how, "?"));
+ policy_print(this->attributes, indent + 1);
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "}\n");
+ }
+ break;
+
+ case POLICY_TYPE_NAMED_POLICY:
+ {
+ const policy_named_t *this;
+
+ this = (const policy_named_t *) item;
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "policy %s {\n", this->name);
+ policy_print(this->policy, indent + 1);
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "}\n");
+ }
+ break;
+
+ case POLICY_TYPE_CALL:
+ {
+ const policy_call_t *this;
+
+ this = (const policy_call_t *) item;
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "call %s\n", this->name);
+ }
+ break;
+
+ case POLICY_TYPE_RETURN:
+ {
+ const policy_return_t *this;
+
+ this = (const policy_return_t *) item;
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "return %s\n",
+ fr_int2str(policy_return_codes,
+ this->rcode, "???"));
+ }
+ break;
+
+ case POLICY_TYPE_MODULE:
+ {
+ const policy_module_t *this;
+
+ this = (const policy_module_t *) item;
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "module %s <stuff>\n",
+ fr_int2str(policy_component_names,
+ this->component, "???"));
+ }
+ break;
+
+ default:
+ if (indent) fprintf(fr_log_fp, "%*s", indent, " ");
+ fprintf(fr_log_fp, "[HUH?]\n");
+ break;
+
+ }
+
+ item = item->next;
+ }
+}
+
+
+void rlm_policy_print(const policy_item_t *item)
+{
+ if (!fr_log_fp) return;
+
+ fprintf(fr_log_fp, "# rlm_policy \n");
+ policy_print(item, 0);
+}
+
+/*
+ * Internal stack of things to do. This lets us have function
+ * calls...
+ *
+ * Yes, we should learn lex, yacc, etc.
+ */
+#define POLICY_MAX_STACK 16
+typedef struct policy_state_t {
+ rlm_policy_t *inst;
+ REQUEST *request; /* so it's not passed on the C stack */
+ int rcode; /* for functions, etc. */
+ int component; /* for calling other modules */
+ int depth;
+ const policy_item_t *stack[POLICY_MAX_STACK];
+} policy_state_t;
+
+
+static int policy_evaluate_name(policy_state_t *state, const char *name);
+
+/*
+ * Push an item onto the state.
+ */
+static int policy_stack_push(policy_state_t *state, const policy_item_t *item)
+{
+ rad_assert(state->depth >= 0);
+
+ /*
+ * Asked to push nothing. Don't push it.
+ */
+ if (!item) return 1;
+
+ /*
+ * State is full. Die.
+ */
+ if (state->depth >= POLICY_MAX_STACK) {
+ return 0;
+ }
+
+ /*
+ * Walk back up the stack, looking for previous ocurrances
+ * of this name. If found, we have infinite recursion,
+ * which we stop dead in the water!
+ *
+ * This isn't strictly necessary right now, as we look up
+ * policies by name when they're first referenced. This
+ * means that ALL references are backwards (to the start
+ * of the file), which means that there are no circular
+ * references.
+ */
+ if (item->type == POLICY_TYPE_NAMED_POLICY) {
+ int i;
+
+ for (i = 0; i < state->depth; i++) {
+ /*
+ * Check for circular references, by seeing
+ * if the function is already on the stack.
+ *
+ * Hmmm... do we want to do this for any type?
+ */
+ if (state->stack[i] == item) {
+ debug_evaluate("Circular call to policy %s\n",
+ ((const policy_named_t *) item)->name);
+ return 0;
+ }
+ }
+ }
+
+ debug_evaluate("push %d %p\n", state->depth, item);
+
+ state->stack[state->depth] = item;
+ state->depth++; /* points to unused entry */
+
+ return 1;
+}
+
+
+/*
+ * Pop an item from the state.
+ */
+static int policy_stack_pop(policy_state_t *state, const policy_item_t **pitem)
+{
+ rad_assert(pitem != NULL);
+ rad_assert(state->depth >= 0);
+
+ redo:
+ if (state->depth == 0) {
+ *pitem = NULL;
+ return 0;
+ }
+
+ *pitem = state->stack[state->depth - 1];
+
+ /*
+ * Named policies are on the stack for catching recursion.
+ */
+ if ((*pitem)->type == POLICY_TYPE_NAMED_POLICY) {
+ state->depth--;
+ goto redo;
+ }
+
+ /*
+ * Process the whole item list.
+ */
+ if ((*pitem)->next) {
+ state->stack[state->depth - 1] = (*pitem)->next;
+ debug_evaluate("pop/push %d %p\n", state->depth - 1, *pitem);
+ } else {
+ state->depth--; /* points to unused entry */
+ debug_evaluate("pop %d %p\n", state->depth, *pitem);
+ }
+
+ return 1;
+}
+
+
+/*
+ * Evaluate a print statement
+ */
+static int evaluate_print(policy_state_t *state, const policy_item_t *item)
+{
+ const policy_print_t *this;
+
+ if (!fr_log_fp) return 1;
+
+ this = (const policy_print_t *) item;
+
+ if (this->rhs_type == POLICY_LEX_BARE_WORD) {
+ fprintf(fr_log_fp, "%s\n", this->rhs);
+ } else {
+ char buffer[1024];
+
+ radius_xlat(buffer, sizeof(buffer), this->rhs,
+ state->request, NULL);
+ fprintf(fr_log_fp, "%s", buffer);
+ if (!strchr(buffer, '\n')) fprintf(fr_log_fp, "\n");
+ }
+
+ /*
+ * Doesn't change state->rcode
+ */
+
+ return 1;
+}
+
+/*
+ * Return a VALUE_PAIR, given an attribute name.
+ *
+ * FIXME: Have it return the N'th one, too, like
+ * doc/variables.txt?
+ *
+ * The amount of duplicated code is getting annoying...
+ */
+static VALUE_PAIR *find_vp(REQUEST *request, const char *name)
+{
+ const char *p;
+ const DICT_ATTR *dattr;
+ VALUE_PAIR *vps;
+
+ p = name;
+ vps = request->packet->vps;;
+
+ /*
+ * FIXME: use names from reserved word list?
+ */
+ if (strncasecmp(name, "request:", 8) == 0) {
+ p += 8;
+ } else if (strncasecmp(name, "reply:", 6) == 0) {
+ p += 6;
+ vps = request->reply->vps;
+#ifdef WITH_PROXY
+ } else if (strncasecmp(name, "proxy-request:", 14) == 0) {
+ p += 14;
+ if (request->proxy) {
+ vps = request->proxy->vps;
+ }
+ } else if (strncasecmp(name, "proxy-reply:", 12) == 0) {
+ p += 12;
+ if (request->proxy_reply) {
+ vps = request->proxy_reply->vps;
+ }
+#endif
+ } else if (strncasecmp(name, "control:", 8) == 0) {
+ p += 8;
+ vps = request->config_items;
+ } /* else it must be a bare attribute name */
+
+ if (!vps) {
+ return NULL;
+ }
+
+ dattr = dict_attrbyname(p);
+ if (!dattr) {
+ fprintf(stderr, "No such attribute %s\n", p);
+ return NULL; /* no such attribute */
+ }
+
+ return pairfind(vps, dattr->attr, dattr->vendor);
+}
+
+
+/*
+ * Evaluate an assignment
+ *
+ * Not really used much...
+ */
+static int evaluate_assignment(UNUSED policy_state_t *state, const policy_item_t *item)
+{
+ const policy_assignment_t *this;
+#if 0
+ const DICT_ATTR *dattr;
+#endif
+
+ this = (const policy_assignment_t *) item;
+
+ rad_assert(this->lhs != NULL);
+ rad_assert(this->rhs != NULL);
+
+#if 0
+ dattr = dict_attrbyname(this->lhs);
+ if (!dattr) {
+ fprintf(stderr, "HUH?\n");
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+
+/*
+ * Evaluate a condition
+ */
+static int evaluate_condition(policy_state_t *state, const policy_item_t *item)
+{
+ int rcode;
+ const policy_condition_t *this;
+ VALUE_PAIR *vp = NULL;
+ const char *data = NULL;
+ int compare;
+#ifdef HAVE_REGEX_H
+ regex_t reg;
+#endif
+ char buffer[256];
+ char lhs_buffer[2048];
+
+ this = (const policy_condition_t *) item;
+
+ redo:
+ /*
+ * FIXME: Don't always do this...
+ */
+ if (this->compare != POLICY_LEX_L_BRACKET) {
+ if (this->lhs_type == POLICY_LEX_FUNCTION) {
+ /*
+ * We can't call evaluate_call here,
+ * because that just pushes stuff onto
+ * the stack, and we want to actually
+ * evaluate all of it...
+ */
+ rcode = policy_evaluate_name(state, this->lhs);
+ data = fr_int2str(policy_return_codes, rcode, "???");
+ strlcpy(lhs_buffer, data, sizeof(lhs_buffer)); /* FIXME: yuck */
+ } else if (this->lhs_type == POLICY_LEX_DOUBLE_QUOTED_STRING) {
+ if (radius_xlat(lhs_buffer, sizeof(lhs_buffer), this->lhs,
+ state->request, NULL) > 0) {
+ data = lhs_buffer;
+ }
+ }
+ }
+
+ switch (this->compare) {
+ case POLICY_LEX_L_BRACKET: /* nested brackets are a special case */
+ rcode = evaluate_condition(state, this->child);
+ break;
+
+ case POLICY_LEX_L_NOT:
+ rcode = evaluate_condition(state, this->child);
+ rcode = (rcode == FALSE); /* reverse sense of test */
+ break;
+
+ case POLICY_LEX_CMP_FALSE: /* non-existence */
+ if (this->lhs_type == POLICY_LEX_BARE_WORD) {
+ vp = find_vp(state->request, this->lhs);
+ rcode = (vp == NULL);
+ } else {
+ rcode = (data == NULL);
+ }
+ break;
+
+ case POLICY_LEX_CMP_TRUE: /* existence */
+ if (this->lhs_type == POLICY_LEX_BARE_WORD) {
+ vp = find_vp(state->request, this->lhs);
+ rcode = (vp != NULL);
+ } else {
+ rcode = (data != NULL);
+ }
+ break;
+
+ default: /* process other comparisons */
+ if ((this->compare != POLICY_LEX_CMP_EQUALS) &&
+#ifdef HAVE_REGEX_H
+ (this->compare != POLICY_LEX_RX_EQUALS) &&
+ (this->compare != POLICY_LEX_RX_NOT_EQUALS) &&
+#endif
+ (this->compare != POLICY_LEX_LT) &&
+ (this->compare != POLICY_LEX_GT) &&
+ (this->compare != POLICY_LEX_LE) &&
+ (this->compare != POLICY_LEX_GE) &&
+ (this->compare != POLICY_LEX_CMP_NOT_EQUALS)) {
+ fprintf(stderr, "%d: bad comparison\n",
+ this->item.lineno);
+ return FALSE;
+ }
+
+ if (this->lhs_type == POLICY_LEX_BARE_WORD) {
+ VALUE_PAIR *myvp;
+
+ vp = find_vp(state->request, this->lhs);
+
+ /*
+ * A op B is FALSE if A doesn't
+ * exist.
+ */
+ if (!vp) {
+ rcode = FALSE;
+ break;
+ }
+
+ /*
+ * FIXME: Move sanity checks to
+ * post-parse code, so we don't do
+ * it on every packet.
+ */
+ vp_prints_value(buffer, sizeof(buffer), vp, 0);
+ myvp = pairmake(vp->name, this->rhs, T_OP_EQ);
+ if (!myvp) return FALSE; /* memory failure */
+ data = buffer;
+
+ /*
+ * FIXME: What to do about comparisons
+ * where vp doesn't exist? Right now,
+ * "simplepaircmp" returns -1, which is
+ * probably a bad idea. it should
+ * instead take an operator, a pointer to
+ * the comparison result, and return
+ * "true/false" for "comparions
+ * succeeded/failed", which are different
+ * error codes than "comparison is less
+ * than, equal to, or greater than zero".
+ */
+ compare = radius_callback_compare(state->request,
+ vp, myvp, NULL, NULL);
+ pairfree(&myvp);
+
+ } else {
+ /*
+ * FIXME: Do something for RHS type?
+ */
+ fr_printf_log("CMP %s %s\n", lhs_buffer, this->rhs);
+ compare = strcmp(lhs_buffer, this->rhs);
+ }
+
+ debug_evaluate("CONDITION COMPARE %d\n", compare);
+
+ switch (this->compare) {
+ case POLICY_LEX_CMP_EQUALS:
+ rcode = (compare == 0);
+ break;
+
+ case POLICY_LEX_CMP_NOT_EQUALS:
+ rcode = (compare != 0);
+ break;
+
+ case POLICY_LEX_LT:
+ rcode = (compare < 0);
+ break;
+
+ case POLICY_LEX_GT:
+ rcode = (compare > 0);
+ break;
+
+ case POLICY_LEX_LE:
+ rcode =(compare <= 0);
+ break;
+
+ case POLICY_LEX_GE:
+ rcode = (compare >= 0);
+ break;
+
+#ifdef HAVE_REGEX_H
+ case POLICY_LEX_RX_EQUALS:
+ { /* FIXME: copied from src/main/valuepair.c */
+ int i;
+ regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
+
+ /*
+ * Include substring matches.
+ */
+ if (regcomp(®, this->rhs,
+ REG_EXTENDED) != 0) {
+ /* FIXME: print error */
+ return FALSE;
+ }
+ rad_assert(data != NULL);
+ rcode = regexec(®, data,
+ REQUEST_MAX_REGEX + 1,
+ rxmatch, 0);
+ rcode = (rcode == 0);
+ regfree(®);
+
+ /*
+ * Add %{0}, %{1}, etc.
+ */
+ for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
+ char *p;
+ char rxbuffer[256];
+
+ /*
+ * Didn't match: delete old
+ * match, if it existed.
+ */
+ if (!rcode ||
+ (rxmatch[i].rm_so == -1)) {
+ p = request_data_get(state->request, state->request,
+ REQUEST_DATA_REGEX | i);
+ if (p) {
+ free(p);
+ continue;
+ }
+
+ /*
+ * No previous match
+ * to delete, stop.
+ */
+ break;
+ }
+
+ /*
+ * Copy substring into buffer.
+ */
+ memcpy(rxbuffer,
+ data + rxmatch[i].rm_so,
+ rxmatch[i].rm_eo - rxmatch[i].rm_so);
+ rxbuffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
+
+ /*
+ * Copy substring, and add it to
+ * the request.
+ *
+ * Note that we don't check
+ * for out of memory, which is
+ * the only error we can get...
+ */
+ p = strdup(rxbuffer);
+ request_data_add(state->request,
+ state->request,
+ REQUEST_DATA_REGEX | i,
+ p, free);
+ }
+
+ }
+ break;
+
+ case POLICY_LEX_RX_NOT_EQUALS:
+ regcomp(®, this->rhs, REG_EXTENDED|REG_NOSUB);
+ rad_assert(data != NULL);
+ rcode = regexec(®, data,
+ 0, NULL, 0);
+ rcode = (rcode != 0);
+ regfree(®);
+ break;
+#endif /* HAVE_REGEX_H */
+ default:
+ rcode = FALSE;
+ break;
+ } /* switch over comparison operators */
+ break; /* default from first switch over compare */
+ }
+
+ if (this->sense) rcode = (rcode == FALSE); /* reverse sense of test */
+
+ /*
+ * No trailing &&, ||
+ */
+ switch (this->child_condition) {
+ default:
+ return rcode;
+
+ case POLICY_LEX_L_AND:
+ if (!rcode) return rcode; /* FALSE && x == FALSE */
+ break;
+
+ case POLICY_LEX_L_OR:
+ if (rcode) return rcode; /* TRUE && x == TRUE */
+ break;
+ }
+
+ /*
+ * Tail recursion.
+ */
+ this = (const policy_condition_t *) this->child;
+ goto redo;
+
+ return 1; /* should never reach here */
+}
+
+
+/*
+ * Evaluate an 'if' statement
+ */
+static int evaluate_if(policy_state_t *state, const policy_item_t *item)
+{
+ int rcode;
+ const policy_if_t *this;
+
+ this = (const policy_if_t *) item;
+
+ /*
+ * evaluate_condition calls itself recursively.
+ * We should probably allocate a new state, instead.
+ */
+ rcode = evaluate_condition(state, this->condition);
+ debug_evaluate("IF condition returned %s\n",
+ rcode ? "true" : "false");
+ if (rcode) {
+ rcode = policy_stack_push(state, this->if_true);
+ if (!rcode) return rcode;
+ } else if (this->if_false) {
+ rcode = policy_stack_push(state, this->if_false);
+ if (!rcode) return rcode;
+ }
+
+ /*
+ * 'if' can fail, if the block it's processing fails.
+ */
+ return 1;;
+}
+
+
+/*
+ * Make a VALUE_PAIR from a policy_assignment_t*
+ *
+ * The assignment operator has to be '='.
+ */
+static VALUE_PAIR *assign2vp(REQUEST *request,
+ const policy_assignment_t *assign)
+{
+ VALUE_PAIR *vp;
+ FR_TOKEN operator = T_OP_EQ;
+ const char *value = assign->rhs;
+ char buffer[2048];
+
+ if ((assign->rhs_type == POLICY_LEX_DOUBLE_QUOTED_STRING) &&
+ (strchr(assign->rhs, '%') != NULL)) {
+ radius_xlat(buffer, sizeof(buffer), assign->rhs,
+ request, NULL);
+ value = buffer;
+ }
+
+ /*
+ * This is crappy.. fix it.
+ */
+ switch (assign->assign) {
+ case POLICY_LEX_ASSIGN:
+ operator = T_OP_EQ;
+ break;
+
+ case POLICY_LEX_SET_EQUALS:
+ operator = T_OP_SET;
+ break;
+
+ case POLICY_LEX_PLUS_EQUALS:
+ operator = T_OP_ADD;
+ break;
+
+ default:
+ fprintf(stderr, "Expected '=' for operator, not '%s' at line %d\n",
+ fr_int2str(rlm_policy_tokens,
+ assign->assign, "?"),
+ assign->item.lineno);
+ return NULL;
+ }
+
+ vp = pairmake(assign->lhs, value, operator);
+ if (!vp) {
+ fprintf(stderr, "Failed creating pair: %s %s\n", value, fr_strerror());
+ }
+
+ return vp;
+}
+
+
+/*
+ * Evaluate a 'packet .= {attrs}' statement
+ */
+static int evaluate_attr_list(policy_state_t *state, const policy_item_t *item)
+{
+ const policy_attributes_t *this;
+ VALUE_PAIR **vps = NULL;
+ VALUE_PAIR *vp, *head, **tail;
+ const policy_item_t *attr;
+ policy_lex_t this_how;
+
+ this = (const policy_attributes_t *) item;
+
+ switch (this->where) {
+ case POLICY_RESERVED_CONTROL:
+ vps = &(state->request->config_items);
+ break;
+
+ case POLICY_RESERVED_REQUEST:
+ vps = &(state->request->packet->vps);
+ break;
+
+ case POLICY_RESERVED_REPLY:
+ vps = &(state->request->reply->vps);
+ break;
+
+#ifdef WITH_PROXY
+ case POLICY_RESERVED_PROXY_REQUEST:
+ if (!state->request->proxy) return 0; /* FIXME: print error */
+ vps = &(state->request->proxy->vps);
+ break;
+
+ case POLICY_RESERVED_PROXY_REPLY:
+ if (!state->request->proxy_reply) return 0; /* FIXME: print error */
+ vps = &(state->request->proxy_reply->vps);
+ break;
+#endif
+
+ default:
+ return 0;
+ }
+
+ head = NULL;
+ tail = &head;
+
+ for (attr = this->attributes; attr != NULL; attr = attr->next) {
+ if (attr->type != POLICY_TYPE_ASSIGNMENT) {
+ fprintf(stderr, "bad assignment in attribute list at line %d\n", attr->lineno);
+ pairfree(&head);
+ return 0;
+ }
+
+ vp = assign2vp(state->request, (const policy_assignment_t *) attr);
+ if (!vp) {
+ fprintf(stderr, "Failed to allocate VP\n");
+ pairfree(&head);
+ return 0;
+ }
+ *tail = vp;
+ tail = &(vp->next);
+ }
+
+ this_how = this->how;
+ retry_how:
+ switch (this_how) {
+ case POLICY_LEX_SET_EQUALS: /* dangerous: removes all previous things! */
+ pairfree(vps);
+ *vps = head;
+ break;
+
+ case POLICY_LEX_AFTER_TAIL_ASSIGN:
+ pairmove(vps, &head);
+ pairfree(&head);
+ break;
+
+ case POLICY_LEX_ASSIGN: /* 'union' */
+ pairmove(vps, &head);
+ pairfree(&head);
+ break;
+
+ case POLICY_LEX_BEFORE_HEAD_ASSIGN:
+ pairmove(&head, vps);
+ pairfree(vps);
+ *vps = head;
+ break;
+
+ case POLICY_LEX_AFTER_TAIL_EQUALS:
+ case POLICY_LEX_CONCAT_EQUALS:
+ pairadd(vps, head);
+ break;
+
+ case POLICY_LEX_BEFORE_HEAD_EQUALS:
+ pairadd(&head, *vps);
+ *vps = head;
+ break;
+
+ case POLICY_LEX_BEFORE_WHERE_EQUALS:
+ case POLICY_LEX_AFTER_WHERE_EQUALS:
+ case POLICY_LEX_BEFORE_WHERE_ASSIGN:
+ case POLICY_LEX_AFTER_WHERE_ASSIGN:
+ /* find location*/
+ {
+ VALUE_PAIR *vpprev = NULL, *vpnext = NULL, *lvp;
+
+ for(lvp = *vps; lvp; vpprev = lvp, lvp = lvp->next) {
+ vpnext = lvp->next;
+ lvp->next = NULL;
+ if (evaluate_condition(state, this->where_loc))
+ break;
+ lvp->next = vpnext;
+ }
+
+ if (lvp) {
+ switch(this_how) {
+ case POLICY_LEX_BEFORE_WHERE_EQUALS:
+ case POLICY_LEX_BEFORE_WHERE_ASSIGN:
+ if (vpprev) {
+ lvp->next = vpnext;
+ vpnext = lvp;
+ vpprev->next = NULL;
+ lvp = vpprev;
+ }
+ default: /* always reached */
+ break;
+ }
+
+ switch(this_how) {
+ case POLICY_LEX_BEFORE_WHERE_EQUALS:
+ if (vpprev)
+ pairadd(&lvp, head);
+ else
+ *vps = lvp = head;
+ break;
+ case POLICY_LEX_AFTER_WHERE_EQUALS:
+ pairadd(&lvp, head);
+ break;
+ case POLICY_LEX_BEFORE_WHERE_ASSIGN:
+ if (vpprev) {
+ pairmove(&lvp, &head);
+ pairfree(&head);
+ }
+ else
+ *vps = lvp = head;
+ break;
+ case POLICY_LEX_AFTER_WHERE_ASSIGN:
+ pairmove(&lvp, &head);
+ pairfree(&head);
+ break;
+ default:/*never reached*/
+ break;
+ }
+ for( ; lvp && lvp->next; lvp = lvp->next);
+ if (lvp)
+ lvp->next = vpnext;
+ break;
+ }
+
+ switch(this_how) {
+ case POLICY_LEX_BEFORE_WHERE_EQUALS:
+ this_how = POLICY_LEX_BEFORE_HEAD_EQUALS;
+ break;
+ case POLICY_LEX_AFTER_WHERE_EQUALS:
+ this_how = POLICY_LEX_AFTER_TAIL_EQUALS;
+ break;
+ case POLICY_LEX_BEFORE_WHERE_ASSIGN:
+ this_how = POLICY_LEX_BEFORE_HEAD_ASSIGN;
+ break;
+ case POLICY_LEX_AFTER_WHERE_ASSIGN:
+ this_how = POLICY_LEX_AFTER_TAIL_ASSIGN;
+ break;
+ default: /*never reached*/
+ break;
+ }
+ goto retry_how;
+ }
+ /* FALL-THROUGH */
+
+ default:
+ fprintf(stderr, "HUH?\n");
+ pairfree(&head);
+ return 0;
+ }
+
+ state->rcode = RLM_MODULE_UPDATED; /* we did stuff */
+
+ return 1;
+}
+
+
+/*
+ * Evaluate a reference call to a module.
+ */
+static int evaluate_call(policy_state_t *state, const policy_item_t *item)
+{
+ int rcode;
+ const policy_call_t *this;
+ const policy_named_t *policy;
+
+ this = (const policy_call_t *) item;
+
+ policy = rlm_policy_find(state->inst->policies, this->name);
+ if (!policy) return 0; /* not found... */
+
+ DEBUG2("rlm_policy: Evaluating policy %s", this->name);
+
+ rad_assert(policy->policy->type != POLICY_TYPE_BAD);
+ rad_assert(policy->policy->type < POLICY_TYPE_NUM_TYPES);
+
+ /*
+ * Push the name of the function onto the stack,
+ * so that we can catch recursive calls.
+ *
+ * The "pop" function will skip over it when it sees it.
+ */
+ rcode = policy_stack_push(state, (const policy_item_t *) policy);
+ if (!rcode) {
+ return rcode;
+ }
+
+ /*
+ * Push it onto the stack. Other code will take care of
+ * calling it.
+ */
+ rcode = policy_stack_push(state, policy->policy);
+ if (!rcode) {
+ return rcode;
+ }
+
+ return 1;
+}
+
+
+/*
+ * Evaluate a return statement
+ */
+static int evaluate_return(policy_state_t *state, const policy_item_t *item)
+{
+ const policy_return_t *this;
+
+ this = (const policy_return_t *) item;
+ state->rcode = this->rcode;
+
+ return 1; /* we succeeded */
+}
+
+
+/*
+ * Evaluate a module statement
+ */
+static int evaluate_module(policy_state_t *state, const policy_item_t *item)
+{
+ const policy_module_t *this;
+
+ this = (const policy_module_t *) item;
+
+ /*
+ * Just to be paranoid. Maybe we want to loosen this
+ * restriction in the future?
+ */
+ if (this->component != state->component) {
+ DEBUG2("rlm_policy: Cannot mix & match components");
+ return 0;
+ }
+
+ DEBUG2("rlm_policy: begin nested call");
+ state->rcode = modcall(this->component, this->mc, state->request);
+ DEBUG2("rlm_policy: end nested call");
+
+ return 1; /* we succeeded */
+}
+
+
+/*
+ * State machine stuff.
+ */
+typedef int (*policy_evaluate_type_t)(policy_state_t *, const policy_item_t *);
+
+
+/*
+ * MUST be kept in sync with policy_type_t
+ */
+static policy_evaluate_type_t evaluate_functions[POLICY_TYPE_NUM_TYPES] = {
+ NULL, /* POLICY_TYPE_BAD */
+ evaluate_if,
+ evaluate_condition,
+ evaluate_assignment,
+ evaluate_attr_list,
+ evaluate_print,
+ NULL, /* define a named policy.. */
+ evaluate_call,
+ evaluate_return,
+ evaluate_module
+};
+
+
+/*
+ * Evaluate a policy, keyed by name.
+ */
+static int policy_evaluate_name(policy_state_t *state, const char *name)
+{
+ int rcode;
+ const policy_item_t *this;
+ policy_named_t mypolicy, *policy;
+
+ mypolicy.name = name;
+ policy = rbtree_finddata(state->inst->policies, &mypolicy);
+ if (!policy) return RLM_MODULE_FAIL;
+
+ DEBUG2("rlm_policy: Evaluating policy %s", name);
+
+ rad_assert(policy->item.type != POLICY_TYPE_BAD);
+ rad_assert(policy->item.type < POLICY_TYPE_NUM_TYPES);
+
+ rcode = policy_stack_push(state, policy->policy);
+ if (!rcode) {
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * FIXME: Look for magic keywords like "return",
+ * where the packet gets accepted/rejected/whatever
+ */
+ while (policy_stack_pop(state, &this)) {
+ rad_assert(this != NULL);
+ rad_assert(this->type != POLICY_TYPE_BAD);
+ rad_assert(this->type < POLICY_TYPE_NUM_TYPES);
+
+ debug_evaluate("Evaluating at line %d\n",
+ this->lineno);
+ rcode = (*evaluate_functions[this->type])(state,
+ this);
+ if (!rcode) {
+ return RLM_MODULE_FAIL;
+ }
+ } /* loop until the stack is empty */
+
+ return state->rcode;
+}
+
+
+/*
+ * Evaluate, which is pretty close to print, but we look at what
+ * we're printing.
+ */
+int rlm_policy_evaluate(rlm_policy_t *inst, REQUEST *request, const char *name)
+{
+ int rcode;
+ policy_state_t *state;
+
+ state = rad_malloc(sizeof(*state));
+ memset(state, 0, sizeof(*state));
+ state->request = request;
+ state->inst = inst;
+ state->rcode = RLM_MODULE_OK;
+ state->component = fr_str2int(policy_component_names, name,
+ RLM_COMPONENT_COUNT);
+
+ rcode = policy_evaluate_name(state, name);
+
+ free(state);
+
+ return rcode; /* evaluated OK. */
+}