In the tradition of programmers everywhere, there's no documentation.
But it works, honest
--- /dev/null
+TARGET = rlm_policy
+SRCS = rlm_policy.c parse.c evaluate.c
+HEADERS = rlm_policy.h
+RLM_CFLAGS =
+RLM_LIBS =
+
+include ../rules.mak
+
+$(STATIC_OBJS): $(HEADERS)
+
+$(DYNAMIC_OBJS): $(HEADERS)
--- /dev/null
+/*\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
--- /dev/null
+/*
+ * parse.c Parse 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright 2004 Alan DeKok <aland@ox.org>
+ */
+
+#include "rlm_policy.h"
+
+/*
+ * Explanations of what the lexical tokens are.
+ */
+static const LRAD_NAME_NUMBER policy_explanations[] = {
+ { "invalid input", POLICY_LEX_BAD },
+ { "end of file", POLICY_LEX_EOF },
+ { "end of line", POLICY_LEX_EOL },
+ { "whitespace", POLICY_LEX_WHITESPACE },
+ { "hash mark", POLICY_LEX_HASH },
+ { "left bracket", POLICY_LEX_L_BRACKET },
+ { "right bracket", POLICY_LEX_R_BRACKET },
+ { "{", POLICY_LEX_LC_BRACKET },
+ { "}", POLICY_LEX_RC_BRACKET },
+ { "comma", POLICY_LEX_COMMA },
+ { "logical AND", POLICY_LEX_L_AND },
+ { "logical OR", POLICY_LEX_L_OR },
+ { "AND", POLICY_LEX_AND },
+ { "OR", POLICY_LEX_OR },
+ { "logical NOT", POLICY_LEX_L_NOT },
+ { "assignment", POLICY_LEX_ASSIGN },
+ { "comparison", POLICY_LEX_CMP_EQUALS },
+ { "comparison", POLICY_LEX_CMP_NOT_EQUALS },
+ { "comparison", POLICY_LEX_LT },
+ { "comparison", POLICY_LEX_GT },
+ { "comparison", POLICY_LEX_LE },
+ { "comparison", POLICY_LEX_GT },
+ { "comparison", POLICY_LEX_RX_EQUALS },
+ { "comparison", POLICY_LEX_RX_NOT_EQUALS },
+ { "double quoted string", POLICY_LEX_DOUBLE_QUOTED_STRING },
+ { "single quoted string", POLICY_LEX_SINGLE_QUOTED_STRING },
+ { "back quoted string", POLICY_LEX_BACK_QUOTED_STRING },
+ { "bare word", POLICY_LEX_BARE_WORD },
+
+ { NULL, -1 }
+};
+
+
+const LRAD_NAME_NUMBER rlm_policy_tokens[] = {
+ { "EOF", POLICY_LEX_EOF },
+ { "#", POLICY_LEX_HASH },
+ { "(", POLICY_LEX_L_BRACKET },
+ { ")", POLICY_LEX_R_BRACKET },
+ { "{", POLICY_LEX_LC_BRACKET },
+ { "}", POLICY_LEX_RC_BRACKET },
+ { ",", POLICY_LEX_COMMA },
+ { "&&", POLICY_LEX_L_AND },
+ { "||", POLICY_LEX_L_OR },
+ { "&", POLICY_LEX_AND },
+ { "|", POLICY_LEX_OR },
+ { "!", POLICY_LEX_L_NOT },
+ { "=", POLICY_LEX_ASSIGN },
+ { "==", POLICY_LEX_CMP_EQUALS },
+ { "!=", POLICY_LEX_CMP_NOT_EQUALS },
+ { "<", POLICY_LEX_LT },
+ { ">", POLICY_LEX_GT },
+ { "<=", POLICY_LEX_LE },
+ { ">=", POLICY_LEX_GT },
+ { "=~", POLICY_LEX_RX_EQUALS },
+ { "!~", POLICY_LEX_RX_NOT_EQUALS },
+ { ".=", POLICY_LEX_CONCAT_EQUALS },
+ { ":=", POLICY_LEX_SET_EQUALS },
+ { "double quoted string", POLICY_LEX_DOUBLE_QUOTED_STRING },
+ { "single quoted string", POLICY_LEX_SINGLE_QUOTED_STRING },
+ { "back quoted string", POLICY_LEX_BACK_QUOTED_STRING },
+ { "bare word", POLICY_LEX_BARE_WORD },
+
+ { NULL, -1 }
+};
+
+
+/*
+ * Hand-coded lexical analysis of a string.
+ * Handed input string, updates token, possible a decoded
+ * string in buffer, and returns the pointer to the next token.
+ *
+ * Lexical tokens cannot cross a string boundary.
+ */
+static const char *policy_lex_string(const char *input,
+ policy_lex_t *token,
+ char *buffer, size_t buflen)
+{
+ rad_assert(input != NULL);
+
+ if (buffer) *buffer = '\0';
+
+ switch (*input) {
+ case '\0':
+ *token = POLICY_LEX_EOL;
+ return NULL; /* nothing more to do */
+
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ /*
+ * Skip over all of the whitespace in one swell foop.
+ */
+ *token = POLICY_LEX_WHITESPACE;
+ while ((*input == ' ') || (*input == '\t') ||
+ (*input == '\r') || (*input == '\n')) input++;
+ return input; /* point to next non-whitespace character */
+
+ case '#': /* ignore everything to the end of the line */
+ *token = POLICY_LEX_EOL;
+ return NULL;
+
+ case '(':
+ *token = POLICY_LEX_L_BRACKET;
+ return input + 1;
+
+ case ')':
+ *token = POLICY_LEX_R_BRACKET;
+ return input + 1;
+
+ case '{':
+ *token = POLICY_LEX_LC_BRACKET;
+ return input + 1;
+
+ case '}':
+ *token = POLICY_LEX_RC_BRACKET;
+ return input + 1;
+
+ case ',':
+ *token = POLICY_LEX_COMMA;
+ return input + 1;
+
+ case '+':
+ switch (input[1]) {
+ case '=':
+ *token = POLICY_LEX_PLUS_EQUALS;
+ input++;
+ break;
+
+ default:
+ *token = POLICY_LEX_PLUS;
+ break;
+ }
+ return input + 1;
+
+ case '-':
+ switch (input[1]) {
+ case '=':
+ *token = POLICY_LEX_MINUS_EQUALS;
+ input++;
+ break;
+
+ default:
+ *token = POLICY_LEX_MINUS;
+ break;
+ }
+ return input + 1;
+
+ case '.':
+ if (input[1] == '=') {
+ *token = POLICY_LEX_CONCAT_EQUALS;
+ return input + 2;
+ }
+ *token = POLICY_LEX_BAD;
+ return input + 1;
+
+ case ':':
+ if (input[1] == '=') {
+ *token = POLICY_LEX_SET_EQUALS;
+ return input + 2;
+ }
+ *token = POLICY_LEX_BAD;
+ return input + 1;
+
+ case '&':
+ switch (input[1]) {
+ case '&':
+ *token = POLICY_LEX_L_AND;
+ input++;
+ break;
+
+ case '=':
+ *token = POLICY_LEX_AND_EQUALS;
+ input++;
+ break;
+
+ default:
+ *token = POLICY_LEX_AND;
+ }
+ return input + 1;
+
+ case '|':
+ switch (input[1]) {
+ case '|':
+ *token = POLICY_LEX_L_OR;
+ input++;
+ break;
+
+ case '=':
+ *token = POLICY_LEX_OR_EQUALS;
+ input++;
+ break;
+
+ default:
+ *token = POLICY_LEX_OR;
+ }
+ return input + 1;
+
+ case '!':
+ switch (input[1]) {
+ case '~':
+ input++;
+ *token = POLICY_LEX_RX_NOT_EQUALS;
+ break;
+
+ case '*':
+ input++;
+ *token = POLICY_LEX_CMP_FALSE;
+ break;
+
+ default:
+ *token = POLICY_LEX_L_NOT;
+ }
+ return input + 1;
+
+ case '=':
+ switch (input[1]) {
+ case '=':
+ input++;
+ *token = POLICY_LEX_CMP_EQUALS;
+ break;
+
+ case '~':
+ input++;
+ *token = POLICY_LEX_RX_EQUALS;
+ break;
+
+ case '*':
+ input++;
+ *token = POLICY_LEX_CMP_TRUE;
+ break;
+
+ default:
+ *token = POLICY_LEX_ASSIGN;
+ }
+ return input + 1;
+
+ case '<':
+ if (input[1] == '=') {
+ input++;
+ *token = POLICY_LEX_LE;
+ } else {
+ *token = POLICY_LEX_LT;
+ }
+ return input + 1;
+
+ case '>':
+ if (input[1] == '=') {
+ input++;
+ *token = POLICY_LEX_GE;
+ } else {
+ *token = POLICY_LEX_GT;
+ }
+ return input + 1;
+
+ case '"':
+ if (buflen < 2) {
+ *token = POLICY_LEX_BAD;
+ return input + 1;
+ }
+
+ input++;
+ while (*input != '"') {
+ /*
+ * Strings can't pass EOL.
+ */
+ if (!*input) {
+ return POLICY_LEX_BAD;
+ }
+
+ *(buffer++) = *(input++);
+ buflen--;
+
+ /*
+ * FIXME: Print more warnings?
+ */
+ if (buflen == 1) {
+ break;
+ }
+ }
+ *buffer = '\0';
+
+ *token = POLICY_LEX_DOUBLE_QUOTED_STRING;
+ return input + 1; /* skip trailing '"' */
+
+ default: /* bare word */
+ break;
+ }
+
+ /*
+ * It's a bare word, with nowhere to put it. Die.
+ */
+ if (!buffer) {
+ *token = POLICY_LEX_BAD;
+ return input + 1;
+ }
+
+ /*
+ * Getting one character is stupid.
+ */
+ if (buflen < 2) {
+ *token = POLICY_LEX_BAD;
+ return input + 1;
+ }
+
+ /*
+ * Bare words are [-a-zA-Z0-9.]+
+ */
+ while (*input) {
+ if (!(((*input >= '0') && (*input <= '9')) ||
+ ((*input >= 'a') && (*input <= 'z')) ||
+ ((*input >= 'A') && (*input <= 'Z')) ||
+ (*input == '-') || (*input == '.') ||
+ (*input == ':') || (*input == '_'))) {
+ break;
+ }
+ *(buffer++) = *(input++);
+ buflen--;
+
+ /*
+ * FIXME: Print more warnings?
+ */
+ if (buflen == 1) {
+ break;
+ }
+ }
+ *buffer = '\0';
+
+ *token = POLICY_LEX_BARE_WORD;
+ return input;
+}
+
+
+/*
+ * We want to lexically analyze a file, so we need a wrapper
+ * around the lexical analysis of strings.
+ */
+typedef struct policy_lex_file_t {
+ FILE *fp;
+ const char *parse;
+ const char *filename;
+ int lineno;
+ int debug;
+ rbtree_t *policies;
+ policy_lex_t token;
+ char buffer[1024];
+} policy_lex_file_t;
+
+
+#define POLICY_LEX_FLAG_RETURN_EOL (1 << 0)
+#define POLICY_LEX_FLAG_PEEK (1 << 1)
+#define POLICY_LEX_FLAG_PRINT_TOKEN (1 << 2)
+
+#define debug_tokens if (lexer->debug & POLICY_DEBUG_PRINT_TOKENS) printf
+
+/*
+ * Function to return a token saying what it read, and possibly
+ * a buffer of the quoted string or bare word.
+ */
+static policy_lex_t policy_lex_file(policy_lex_file_t *lexer,
+ int flags,
+ char *mystring, size_t mystringlen)
+{
+ policy_lex_t token = POLICY_LEX_BARE_WORD; /* to prime it */
+
+ if (lexer->debug & POLICY_DEBUG_PRINT_TOKENS) {
+ flags |= POLICY_LEX_FLAG_PRINT_TOKEN;
+ }
+
+ if (!lexer->fp) {
+ return POLICY_LEX_EOF;
+ }
+
+ /*
+ * Starting off, the buffer needs to be primed.
+ */
+ if (!lexer->parse) {
+ lexer->parse = fgets(lexer->buffer,
+ sizeof(lexer->buffer),
+ lexer->fp);
+
+ if (!lexer->parse) {
+ return POLICY_LEX_EOF;
+ }
+
+ lexer->lineno = 1;
+ } /* buffer is primed, read stuff */
+
+ if (lexer->token != POLICY_LEX_BAD) {
+ token = lexer->token;
+ lexer->token = POLICY_LEX_BAD;
+ return token;
+ }
+
+ /*
+ * Ignore whitespace, and keep filling the buffer
+ */
+ while (lexer->parse) {
+ const char *next;
+
+ next = policy_lex_string(lexer->parse, &token,
+ mystring, mystringlen);
+ switch (token) {
+ case POLICY_LEX_WHITESPACE: /* skip whitespace */
+ lexer->parse = next;
+ continue;
+
+ case POLICY_LEX_EOL: /* read another line */
+ lexer->parse = fgets(lexer->buffer,
+ sizeof(lexer->buffer),
+ lexer->fp);
+ lexer->lineno++;
+ if (flags & POLICY_LEX_FLAG_RETURN_EOL) {
+ return POLICY_LEX_EOL;
+ }
+ break; /* read another token */
+
+ default: /* return the token */
+ if (!(flags & POLICY_LEX_FLAG_PEEK)) {
+ lexer->parse = next;
+ }
+ if (flags & POLICY_LEX_FLAG_PRINT_TOKEN) {
+ debug_tokens("[%s token %s] ",
+ (flags & POLICY_LEX_FLAG_PEEK) ? "peek " : "",
+ lrad_int2str(rlm_policy_tokens,
+ token, "?"));
+ }
+ return token;
+ break;
+ }
+ } /* loop until EOF */
+
+ /*
+ * Close it for the user.
+ */
+ fclose(lexer->fp);
+ lexer->fp = NULL;
+
+ return POLICY_LEX_EOF;
+}
+
+
+/*
+ * Push a token back onto the input.
+ *
+ * FIXME: Push words, too?
+ */
+static int policy_lex_push_token(policy_lex_file_t *lexer,
+ policy_lex_t token)
+{
+ if (lexer->token != POLICY_LEX_BAD) {
+ rad_assert(0 == 1);
+ return 0;
+ }
+
+ lexer->token = token;
+ return 1;
+}
+
+
+/*
+ * Forward declarations.
+ */
+static int parse_block(policy_lex_file_t *lexer, policy_item_t **tail);
+
+
+/*
+ * Map reserved words to tokens, and vice versa.
+ */
+const LRAD_NAME_NUMBER policy_reserved_words[] = {
+ { "if", POLICY_RESERVED_IF },
+ { "else", POLICY_RESERVED_ELSE },
+ { "debug", POLICY_RESERVED_DEBUG },
+ { "print", POLICY_RESERVED_PRINT },
+ { "policy", POLICY_RESERVED_POLICY },
+ { "call", POLICY_RESERVED_CALL },
+ { "control", POLICY_RESERVED_CONTROL },
+ { "request", POLICY_RESERVED_REQUEST },
+ { "reply", POLICY_RESERVED_REPLY },
+ { "proxy-request", POLICY_RESERVED_PROXY_REQUEST },
+ { "proxy-reply", POLICY_RESERVED_PROXY_REPLY },
+ { NULL, POLICY_RESERVED_UNKNOWN }
+};
+
+
+/*
+ * print foo
+ * print "foo"
+ */
+static int parse_print(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ policy_lex_t token;
+ char mystring[1024];
+ policy_print_t *this;
+
+ debug_tokens("[PRINT] ");
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_PRINT;
+ this->item.lineno = lexer->lineno;
+
+ token = policy_lex_file(lexer, 0, mystring, sizeof(mystring));
+ if ((token != POLICY_LEX_BARE_WORD) &&
+ (token != POLICY_LEX_DOUBLE_QUOTED_STRING)) {
+ fprintf(stderr, "%s[%d]: Bad print command\n",
+ lexer->filename, lexer->lineno);
+ return 0;
+ }
+
+ this->rhs_type = token;
+ this->rhs = strdup(mystring);
+
+ *tail = (policy_item_t *) this;
+
+ return 1;
+}
+
+
+/*
+ * Parse debugging statements
+ */
+static int parse_debug(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode = 0;
+ policy_lex_t token;
+ char buffer[32];
+
+ tail = tail; /* -Wunused */
+
+ token = policy_lex_file(lexer, 0, buffer, sizeof(buffer));
+ if (token != POLICY_LEX_BARE_WORD) {
+ fprintf(stderr, "%s[%d]: Bad debug command\n",
+ lexer->filename, lexer->lineno);
+ return 0;
+ }
+
+ if (strcasecmp(buffer, "none") == 0) {
+ lexer->debug = POLICY_DEBUG_NONE;
+ rcode = 1;
+
+ } else if (strcasecmp(buffer, "peek") == 0) {
+ lexer->debug |= POLICY_DEBUG_PEEK;
+ rcode = 1;
+
+ } else if (strcasecmp(buffer, "print_tokens") == 0) {
+ lexer->debug |= POLICY_DEBUG_PRINT_TOKENS;
+ rcode = 1;
+
+ } else if (strcasecmp(buffer, "print_policy") == 0) {
+ lexer->debug |= POLICY_DEBUG_PRINT_POLICY;
+ rcode = 1;
+
+ } else if (strcasecmp(buffer, "evaluate") == 0) {
+ lexer->debug |= POLICY_DEBUG_EVALUATE;
+ rcode = 1;
+ }
+
+ if (rcode) {
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_RETURN_EOL,
+ NULL, 0);
+ if (token != POLICY_LEX_EOL) {
+ fprintf(stderr, "%s[%d]: Expected EOL\n",
+ lexer->filename, lexer->lineno);
+ return 0;
+ }
+ } else {
+ fprintf(stderr, "%s[%d]: Bad debug command \"%s\"\n",
+ lexer->filename, lexer->lineno, buffer);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * (foo == bar), with nested conditionals.
+ */
+static int parse_condition(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode;
+ policy_lex_t token, compare;
+ char lhs[256], rhs[256];
+ policy_condition_t *this;
+
+ token = policy_lex_file(lexer, 0, lhs, sizeof(lhs));
+ if (token != POLICY_LEX_L_BRACKET) {
+ fprintf(stderr, "%s[%d]: Expected '(', got \"%s\"\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, token, lhs));
+ return 0;
+ }
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_CONDITIONAL;
+ this->item.lineno = lexer->lineno;
+
+ token = policy_lex_file(lexer, 0, lhs, sizeof(lhs));
+ switch (token) {
+ case POLICY_LEX_L_BRACKET:
+ if (!policy_lex_push_token(lexer, token)) {
+ rlm_policy_free_item((policy_item_t *) (policy_item_t *) this);
+ return 0;
+ }
+
+ this->compare = POLICY_LEX_L_BRACKET;
+ this->child_condition = POLICY_LEX_L_BRACKET;
+ rcode = parse_condition(lexer, &(this->child));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+ break;
+
+ case POLICY_LEX_L_NOT:
+ this->compare = POLICY_LEX_L_NOT;
+ this->child_condition = POLICY_LEX_L_NOT;
+ debug_tokens("[NOT] ");
+
+ /*
+ * FIXME: allow !foo, !foo=bar, etc.
+ *
+ * Maybe we should learn how to use lex && yacc?
+ */
+
+ rcode = parse_condition(lexer, &(this->child));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+ break;
+
+ case POLICY_LEX_BARE_WORD:
+ case POLICY_LEX_DOUBLE_QUOTED_STRING:
+ this->lhs_type = token;
+
+ /*
+ * Got word. May just be test for existence.
+ */
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0);
+ if (token == POLICY_LEX_R_BRACKET) {
+ debug_tokens("[TEST %s] ", lhs);
+ this->lhs = strdup(lhs);
+ this->compare = POLICY_LEX_CMP_TRUE;
+ break;
+ }
+
+ compare = policy_lex_file(lexer, 0, rhs, sizeof(rhs));
+ switch (compare) {
+ case POLICY_LEX_CMP_EQUALS:
+ case POLICY_LEX_CMP_NOT_EQUALS:
+ case POLICY_LEX_RX_EQUALS:
+ case POLICY_LEX_RX_NOT_EQUALS:
+ case POLICY_LEX_LT:
+ case POLICY_LEX_GT:
+ case POLICY_LEX_LE:
+ case POLICY_LEX_GE:
+ break;
+
+ default:
+ fprintf(stderr, "%s[%d]: Invalid operator \"%s\"\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, compare, rhs));
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ token = policy_lex_file(lexer, 0, rhs, sizeof(rhs));
+ if ((token != POLICY_LEX_BARE_WORD) &&
+ (token != POLICY_LEX_DOUBLE_QUOTED_STRING)) {
+ fprintf(stderr, "%s[%d]: Unexpected rhs token\n",
+ lexer->filename, lexer->lineno);
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+ debug_tokens("[COMPARE (%s %s %s)] ",
+ lhs, lrad_int2str(rlm_policy_tokens, compare, "?"), rhs);
+ this->lhs = strdup(lhs);
+ this->compare = compare;
+ this->rhs_type = token;
+ this->rhs = strdup(rhs);
+ break;
+
+ default:
+ fprintf(stderr, "%s[%d]: Unexpected lhs token\n",
+ lexer->filename, lexer->lineno);
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ token = policy_lex_file(lexer, 0, NULL, 0);
+ if (token != POLICY_LEX_R_BRACKET) {
+ fprintf(stderr, "%s[%d]: Expected ')', got \"%s\"\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, token, "?"));
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ /*
+ * After the end of condition, we MAY have && or ||
+ */
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK, NULL, 0);
+ if ((token == POLICY_LEX_L_AND) || (token == POLICY_LEX_L_OR)) {
+ token = policy_lex_file(lexer, 0, NULL, 0); /* skip over it */
+ debug_tokens("[%s] ",
+ lrad_int2str(rlm_policy_tokens, token, "?"));
+ this->child_condition = token;
+ rcode = parse_condition(lexer, &(this->child));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+ }
+
+ *tail = (policy_item_t *) this;
+
+ return 1;
+}
+
+
+/*
+ * if (...) {...}
+ * if (...) {...} else {...}
+ * if (...) {...} else if ...
+ */
+static int parse_if(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode;
+ policy_lex_t token;
+ char mystring[256];
+ policy_if_t *this;
+
+ debug_tokens("[IF] ");
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_IF;
+ this->item.lineno = lexer->lineno;
+
+ rcode = parse_condition(lexer, &(this->condition));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+
+ rcode = parse_block(lexer, &(this->if_true));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK,
+ mystring, sizeof(mystring));
+ if ((token == POLICY_LEX_BARE_WORD) &&
+ (lrad_str2int(policy_reserved_words, mystring,
+ POLICY_RESERVED_UNKNOWN) == POLICY_RESERVED_ELSE)) {
+ debug_tokens("[ELSE] ");
+ token = policy_lex_file(lexer, 0, mystring, sizeof(mystring));
+
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_PEEK,
+ mystring, sizeof(mystring));
+ if ((token == POLICY_LEX_BARE_WORD) &&
+ (lrad_str2int(policy_reserved_words, mystring,
+ POLICY_RESERVED_UNKNOWN) == POLICY_RESERVED_IF)) {
+ token = policy_lex_file(lexer, 0,
+ mystring, sizeof(mystring));
+ rcode = parse_if(lexer, &(this->if_false));
+ } else {
+ rcode = parse_block(lexer, &(this->if_false));
+ }
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+ }
+
+ debug_tokens("\n");
+
+ /*
+ * Empty "if" condition, don't even bother remembering
+ * it.
+ */
+ if (!this->if_true && !this->if_false) {
+ debug_tokens("Discarding empty \"if\" statement at line %d\n",
+ this->item.lineno);
+ rlm_policy_free_item((policy_item_t *) this);
+ return 1;
+ }
+
+ *tail = (policy_item_t *) this;
+
+ return 1;
+}
+
+
+/*
+ * Parse a named policy "policy foo {...}"
+ */
+static int parse_named_policy(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode;
+ policy_lex_t token;
+ char mystring[256];
+ policy_named_t *this;
+
+ tail = tail; /* -Wunused */
+
+ debug_tokens("[POLICY] ");
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_NAMED_POLICY;
+ this->item.lineno = lexer->lineno;
+
+ token = policy_lex_file(lexer, 0, mystring, sizeof(mystring));
+ if (token != POLICY_LEX_BARE_WORD) {
+ fprintf(stderr, "%s[%d]: Expected policy name, got \"%s\"\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, token, "?"));
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ rcode = parse_block(lexer, &(this->policy));
+ if (!rcode) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return rcode;
+ }
+ this->name = strdup(mystring);
+
+ /*
+ * And insert it into the tree of policies.
+ *
+ * For now, policy names aren't scoped, they're global.
+ */
+ if (!rlm_policy_insert(lexer->policies, this->name, this->policy)) {
+ fprintf(stderr, "Failed to insert %s\n", this->name);
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+ free(this); /* FIXME: shouldn't have allocated it... */
+
+ /*
+ * Do NOT add it into the list here! The above insertion
+ * will take care of freeing it if anything goes wrong...
+ */
+
+ return 1;
+}
+
+
+
+/*
+ * Parse a reference to a named policy "call foo"
+ */
+static int parse_call(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ policy_lex_t token;
+ char mystring[256];
+ policy_call_t *this;
+
+ debug_tokens("[CALL] ");
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_CALL;
+ this->item.lineno = lexer->lineno;
+
+ token = policy_lex_file(lexer, 0, mystring, sizeof(mystring));
+ if (token != POLICY_LEX_BARE_WORD) {
+ fprintf(stderr, "%s[%d]: Expected policy name, got \"%s\"\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, token, "?"));
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ this->name = strdup(mystring);
+
+ *tail = (policy_item_t *) this;
+
+ return 1;
+}
+
+
+
+/*
+ * Edit/update/replace an attribute list
+ */
+static int parse_attribute_block(policy_lex_file_t *lexer,
+ policy_item_t **tail,
+ policy_reserved_word_t where)
+{
+ policy_lex_t token;
+ policy_attributes_t *this;
+ char buffer[32];
+
+ token = policy_lex_file(lexer, 0, buffer, sizeof(buffer));
+ switch (token) {
+ case POLICY_LEX_ASSIGN:
+ case POLICY_LEX_SET_EQUALS:
+ case POLICY_LEX_CONCAT_EQUALS:
+ break;
+
+ default:
+ fprintf(stderr, "%s[%d]: Unexpected token %s\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(rlm_policy_tokens, token, "?"));
+ return 0; /* unknown */
+ }
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_ATTRIBUTE_LIST;
+ this->item.lineno = lexer->lineno;
+ this->where = where;
+ this->how = token;
+
+ if (!parse_block(lexer, &(this->attributes))) {
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+
+ *tail = (policy_item_t *) this;
+ return 1;
+}
+
+
+/*
+ * Parse one statement. 'foo = bar', or 'if (...) {...}', or '{...}',
+ * and so on.
+ */
+static int parse_statement(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode;
+ policy_reserved_word_t reserved;
+ policy_lex_t token, assign;
+ char lhs[256], rhs[256];
+ policy_assignment_t *this;
+
+ /*
+ * See what kind of token we have.
+ */
+ token = policy_lex_file(lexer, 0, lhs, sizeof(lhs));
+ switch (token) {
+ case POLICY_LEX_LC_BRACKET:
+ rcode = parse_block(lexer, tail);
+ if (!rcode) {
+ return 0;
+ }
+ break;
+
+ case POLICY_LEX_BARE_WORD:
+ reserved = lrad_str2int(policy_reserved_words,
+ lhs,
+ POLICY_RESERVED_UNKNOWN);
+ switch (reserved) {
+ case POLICY_RESERVED_IF:
+ if (parse_if(lexer, tail)) {
+ return 1;
+ }
+ return 0;
+ break;
+
+ case POLICY_RESERVED_CONTROL:
+ case POLICY_RESERVED_REQUEST:
+ case POLICY_RESERVED_REPLY:
+ case POLICY_RESERVED_PROXY_REQUEST:
+ case POLICY_RESERVED_PROXY_REPLY:
+ if (parse_attribute_block(lexer, tail,
+ reserved))
+ return 1;
+ return 0;
+ break;
+
+ case POLICY_RESERVED_DEBUG:
+ if (parse_debug(lexer, tail)) {
+ return 1;
+ }
+ return 0;
+ break;
+
+ case POLICY_RESERVED_PRINT:
+ if (parse_print(lexer, tail)) {
+ return 1;
+ }
+ return 0;
+ break;
+
+ case POLICY_RESERVED_POLICY:
+ if (parse_named_policy(lexer, tail)) {
+ return 1;
+ }
+ return 0;
+ break;
+
+ case POLICY_RESERVED_CALL:
+ if (parse_call(lexer, tail)) {
+ return 1;
+ }
+ return 0;
+ break;
+
+ case POLICY_RESERVED_UNKNOWN: /* wasn't a reserved word */
+ /*
+ * Maybe include another file.
+ */
+ if (strcasecmp(lhs, "include") == 0) {
+ /*
+ * FIXME: add stuff
+ */
+ } else {
+
+ const DICT_ATTR *dattr;
+
+ /*
+ * Bare words MUST be dictionary attributes
+ */
+
+ dattr = dict_attrbyname(lhs);
+ if (!dattr) {
+ fprintf(stderr, "%s[%d]: Expected attribute name, got \"%s\"\n",
+ lexer->filename, lexer->lineno, lhs);
+ return 0;
+ }
+ debug_tokens("%s[%d]: Got attribute %s\n",
+ lexer->filename, lexer->lineno,
+ lhs);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "%s[%d]: Unexpected reserved word \"%s\"\n",
+ lexer->filename, lexer->lineno, lhs);
+ return 0;
+ } /* switch over reserved words */
+ break;
+
+ /*
+ * Return from nested blocks.
+ */
+ case POLICY_LEX_RC_BRACKET:
+ policy_lex_push_token(lexer, token);
+ return 2; /* magic */
+
+ case POLICY_LEX_EOF: /* nothing more to do */
+ return 3;
+
+ default:
+ fprintf(stderr, "%s[%d]: Unexpected %s\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(policy_explanations,
+ token, "string"));
+ break;
+ }
+
+ /*
+ * Parse a bare statement.
+ */
+ assign = policy_lex_file(lexer, 0, rhs, sizeof(rhs));
+ switch (assign) {
+ case POLICY_LEX_ASSIGN:
+ case POLICY_LEX_SET_EQUALS:
+ case POLICY_LEX_AND_EQUALS:
+ case POLICY_LEX_OR_EQUALS:
+ case POLICY_LEX_PLUS_EQUALS:
+ break;
+
+ default:
+ fprintf(stderr, "%s[%d]: Unexpected assign %s\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(policy_explanations,
+ assign, "string"));
+ return 0;
+ }
+
+ this = rad_malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->item.type = POLICY_TYPE_ASSIGNMENT;
+ this->item.lineno = lexer->lineno;
+
+ token = policy_lex_file(lexer, 0, rhs, sizeof(rhs));
+ if ((token != POLICY_LEX_BARE_WORD) &&
+ (token != POLICY_LEX_DOUBLE_QUOTED_STRING)) {
+ fprintf(stderr, "%s[%d]: Unexpected rhs %s\n",
+ lexer->filename, lexer->lineno,
+ lrad_int2str(policy_explanations,
+ token, "string"));
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+ this->rhs_type = token;
+ this->rhs = strdup(rhs);
+
+ token = policy_lex_file(lexer, POLICY_LEX_FLAG_RETURN_EOL,
+ rhs, sizeof(rhs));
+ if (token != POLICY_LEX_EOL) {
+ fprintf(stderr, "%s[%d]: Expected EOL\n",
+ lexer->filename, lexer->lineno);
+ rlm_policy_free_item((policy_item_t *) this);
+ return 0;
+ }
+ debug_tokens("[ASSIGN %s %s %s]\n",
+ lhs, lrad_int2str(rlm_policy_tokens, assign, "?"), rhs);
+
+ /*
+ * Fill in the assignment struct
+ */
+ this->lhs = strdup(lhs);
+ this->assign = assign;
+
+ *tail = (policy_item_t *) this;
+
+ return 1;
+}
+
+
+/*
+ * Parse block of statements. The block has already been checked
+ * to begin with a '{'.
+ */
+static int parse_block(policy_lex_file_t *lexer, policy_item_t **tail)
+{
+ int rcode;
+ policy_lex_t token;
+
+ debug_tokens("[BLOCK] ");
+
+ token = policy_lex_file(lexer, 0, NULL, 0);
+ if (token != POLICY_LEX_LC_BRACKET) {
+ fprintf(stderr, "%s[%d]: Expected '{'\n",
+ lexer->filename, lexer->lineno);
+ return 0;
+ }
+
+ rcode = 0;
+ while ((rcode = parse_statement(lexer, tail)) != 0) {
+ if (rcode == 2) {
+ token = policy_lex_file(lexer, 0, NULL, 0);
+ if (token != POLICY_LEX_RC_BRACKET) {
+ fprintf(stderr, "%s[%d]: Expected '}'\n",
+ lexer->filename, lexer->lineno);
+ return 0;
+ }
+ return 1;
+ }
+ rad_assert(*tail != NULL);
+ /* parse_statement must fill this in */
+ if (*tail) tail = &((*tail)->next);
+ }
+ debug_tokens("\n");
+
+ /*
+ * Parse statement failed.
+ */
+ return 0;
+}
+
+
+/*
+ * Parse data from a file into a policy language.
+ */
+policy_item_t *rlm_policy_parse(rlm_policy_t *inst, const char *filename)
+{
+ int rcode;
+ FILE *fp;
+ policy_lex_file_t mylexer, *lexer;
+ policy_item_t *list = NULL, **tail;
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "WTF? %s: %s\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ lexer = &mylexer;
+ memset(lexer, 0, sizeof(*lexer));
+ lexer->filename = filename;
+ lexer->fp = fp;
+ lexer->token = POLICY_LEX_BAD;
+ lexer->parse = NULL; /* initial input */
+ lexer->policies = inst->policies;
+
+ tail = &list;
+ while ((rcode = parse_statement(lexer, tail)) != 0) {
+ if (rcode > 1) break;
+
+ if (*tail) tail = &((*tail)->next);
+ fflush(stdout);
+ } /* until EOF or error */
+
+ if (rcode == 2) {
+ fprintf(stderr, "%s[%d]: Unexpected '}'\n",
+ lexer->filename, lexer->lineno);
+ return NULL;
+ }
+
+ if (rcode == 0) {
+ fprintf(stderr, "Failed to parse %s\n", filename);
+ rlm_policy_free_item((policy_item_t *) &list);
+ return NULL;
+ }
+
+ debug_tokens("--------------------------------------------------\n");
+ if (lexer->debug & POLICY_DEBUG_PRINT_POLICY) rlm_policy_print(list);
+
+ return list;
+}
--- /dev/null
+/*
+ * rlm_policy.c Implements 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright 2004 Alan DeKok <aland@ox.org>
+ */
+
+#include "rlm_policy.h"
+
+#include "modules.h"
+#include "conffile.h"
+
+static const char rcsid[] = "$Id$";
+
+
+/*
+ * A mapping of configuration file names to internal variables.
+ *
+ * Note that the string is dynamically allocated, so it MUST
+ * be freed. When the configuration file parse re-reads the string,
+ * it free's the old one, and strdup's the new one, placing the pointer
+ * to the strdup'd string into 'config.string'. This gets around
+ * buffer over-flows.
+ */
+static CONF_PARSER module_config[] = {
+ { "filename", PW_TYPE_STRING_PTR,
+ offsetof(rlm_policy_t,filename), NULL, NULL},
+
+ { NULL, -1, 0, NULL, NULL } /* end the list */
+};
+
+
+/*
+ * Callbacks for red-black trees.
+ */
+static int policyname_cmp(const void *a, const void *b)
+{
+ return strcasecmp(((const rlm_policy_name_t *)a)->name,
+ ((const rlm_policy_name_t *)b)->name);
+}
+
+static void policyname_free(void *item)
+{
+ rlm_policy_name_t *this = (rlm_policy_name_t *) item;
+
+ rlm_policy_free_item(this->policy);
+ free(this);
+}
+
+
+/*
+ * Detach a policy.
+ */
+static int policy_detach(void *instance)
+{
+ rlm_policy_t *inst = instance;
+
+ if (inst->filename) free(inst->filename);
+ if (inst->policies) rbtree_free(inst->policies);
+ free(instance);
+ return 0;
+}
+
+/*
+ * Do any per-module initialization that is separate to each
+ * configured instance of the module. e.g. set up connections
+ * to external databases, read configuration files, set up
+ * dictionary entries, etc.
+ *
+ * If configuration information is given in the config section
+ * that must be referenced in later calls, store a handle to it
+ * in *instance otherwise put a null pointer there.
+ */
+static int policy_instantiate(CONF_SECTION *conf, void **instance)
+{
+ rlm_policy_t *inst;
+ policy_item_t *policy;
+
+ /*
+ * Set up a storage area for instance data
+ */
+ inst = rad_malloc(sizeof(*inst));
+ if (!inst) {
+ return -1;
+ }
+ memset(inst, 0, sizeof(*inst));
+
+ /*
+ * If the configuration parameters can't be parsed, then
+ * fail.
+ */
+ if (cf_section_parse(conf, inst, module_config) < 0) {
+ policy_detach(inst);
+ return -1;
+ }
+
+ inst->policies = rbtree_create(policyname_cmp, policyname_free, 0);
+ if (!inst->policies) {
+ policy_detach(inst);
+ return -1;
+ }
+
+ /*
+ * Parse the policy from the file.
+ */
+ if ((policy = rlm_policy_parse(inst, inst->filename)) == NULL) {
+ rlm_policy_free_item(policy);
+ policy_detach(inst);
+ return -1;
+ }
+
+ if (!rlm_policy_insert(inst->policies, "DEFAULT", policy)) {
+ policy_detach(inst);
+ return -1;
+ }
+
+ *instance = inst;
+
+ return 0;
+}
+
+
+/*
+ * Insert a named policy into a list.
+ */
+int rlm_policy_insert(rbtree_t *head, const char *name, policy_item_t *policy)
+{
+ rlm_policy_name_t *this;
+
+ /*
+ * Get the names from the policy
+ */
+ this = rad_malloc(sizeof(*this));
+ strNcpy(this->name, name, sizeof(this->name));
+ this->policy = policy;
+
+ if (!rbtree_insert(head, this)) {
+ policyname_free(this);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/*
+ * Find the named user in this modules database. Create the set
+ * of attribute-value pairs to check and reply with for this user
+ * from the database. The authentication code only needs to check
+ * the password, the rest is done here.
+ */
+static int policy_authorize(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "authorize");
+}
+
+
+static int policy_preacct(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "preacct");
+}
+
+static int policy_accounting(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "accounting");
+}
+
+static int policy_post_auth(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "post-auth");
+}
+
+static int policy_pre_proxy(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "pre-proxy");
+}
+
+static int policy_post_proxy(void *instance, REQUEST *request)
+{
+ return rlm_policy_evaluate((rlm_policy_t *) instance, request,
+ "post-proxy");
+}
+
+/*
+ * The "free" functions are here, for no particular reason.
+ */
+void rlm_policy_free_item(policy_item_t *item)
+{
+
+ while (item) {
+ policy_item_t *next = item->next;
+
+ switch (item->type) {
+ default:
+ case POLICY_TYPE_BAD:
+ break;
+
+ case POLICY_TYPE_ASSIGNMENT:
+ {
+ policy_assignment_t *this;
+
+ this = (policy_assignment_t *) item;
+ if (this->lhs) free(this->lhs);
+ if (this->rhs) free(this->rhs);
+ }
+ break;
+
+ case POLICY_TYPE_CONDITIONAL:
+ {
+ policy_condition_t *this;
+
+ this = (policy_condition_t *) item;
+ if (this->lhs) free(this->lhs);
+ if (this->rhs) free(this->rhs);
+
+ if (this->child) {
+ rlm_policy_free_item(this->child);
+ this->child = NULL;
+ }
+ }
+ break;
+
+ case POLICY_TYPE_IF:
+ {
+ policy_if_t *this;
+
+ this = (policy_if_t *) item;
+ if (this->condition) {
+ rlm_policy_free_item(this->condition);
+ this->condition = NULL;
+ }
+ if (this->if_true) {
+ rlm_policy_free_item(this->if_true);
+ this->if_true = NULL;
+ }
+ if (this->if_false) {
+ rlm_policy_free_item(this->if_false);
+ this->if_false = NULL;
+ }
+ }
+ break;
+
+ case POLICY_TYPE_ATTRIBUTE_LIST:
+ {
+ policy_attributes_t *this;
+
+ this = (policy_attributes_t *) item;
+ rlm_policy_free_item(this->attributes);
+ }
+ break;
+
+ case POLICY_TYPE_NAMED_POLICY:
+ {
+ policy_named_t *this;
+
+ this = (policy_named_t *) item;
+ rlm_policy_free_item(this->policy);
+ }
+ break;
+ } /* switch over type */
+ item->next = NULL; /* for debugging & sanity checks */
+ item->type = POLICY_TYPE_BAD;
+ free(item);
+
+ item = next;
+ }
+}
+
+
+/*
+ * The module name should be the only globally exported symbol.
+ * That is, everything else should be 'static'.
+ *
+ * If the module needs to temporarily modify it's instantiation
+ * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ * The server will then take care of ensuring that the module
+ * is single-threaded.
+ */
+module_t rlm_policy = {
+ "policy",
+ RLM_TYPE_THREAD_SAFE, /* type */
+ NULL, /* initialization */
+ policy_instantiate, /* instantiation */
+ {
+ NULL, /* authentication */
+ policy_authorize, /* authorization */
+ policy_preacct, /* preaccounting */
+ policy_accounting, /* accounting */
+ NULL, /* checksimul */
+ policy_pre_proxy, /* pre-proxy */
+ policy_post_proxy, /* post-proxy */
+ policy_post_auth /* post-auth */
+ },
+ policy_detach, /* detach */
+ NULL, /* destroy */
+};
--- /dev/null
+/*
+ * rlm_policy.h Header file for policy module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright 2004 Alan DeKok <aland@freeradius.org>
+ */
+#ifndef _RLM_POLICY_H
+#define _RLM_POLICY_H
+
+#include "autoconf.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "radiusd.h"
+
+#include "rad_assert.h"
+
+/*
+ * Internal lexer.
+ */
+typedef enum policy_lex_t {
+ POLICY_LEX_BAD = 0,
+ POLICY_LEX_EOF, /* end of the file/input */
+ POLICY_LEX_EOL, /* end of the line */
+ POLICY_LEX_WHITESPACE,
+ POLICY_LEX_HASH,
+ POLICY_LEX_L_BRACKET,
+ POLICY_LEX_R_BRACKET,
+ POLICY_LEX_LC_BRACKET, /* left curly bracket */
+ POLICY_LEX_RC_BRACKET, /* right curly bracket */
+ POLICY_LEX_COMMA,
+ POLICY_LEX_L_AND, /* logical AND */
+ POLICY_LEX_L_OR, /* logical OR */
+ POLICY_LEX_AND, /* bit-wise AND */
+ POLICY_LEX_OR, /* bit-wise OR */
+ POLICY_LEX_L_NOT,
+ POLICY_LEX_PLUS, /* + */
+ POLICY_LEX_MINUS, /* - */
+ POLICY_LEX_ASSIGN, /* = */
+ POLICY_LEX_CMP_EQUALS,
+ POLICY_LEX_CMP_NOT_EQUALS,
+ POLICY_LEX_CMP_TRUE,
+ POLICY_LEX_CMP_FALSE,
+ POLICY_LEX_LT,
+ POLICY_LEX_GT,
+ POLICY_LEX_LE,
+ POLICY_LEX_GE,
+ POLICY_LEX_RX_EQUALS,
+ POLICY_LEX_RX_NOT_EQUALS,
+ POLICY_LEX_SET_EQUALS, /* := */
+ POLICY_LEX_AND_EQUALS, /* &= */
+ POLICY_LEX_OR_EQUALS, /* |= */
+ POLICY_LEX_PLUS_EQUALS, /* += */
+ POLICY_LEX_MINUS_EQUALS, /* -= */
+ POLICY_LEX_CONCAT_EQUALS, /* .= */
+ POLICY_LEX_VARIABLE, /* %{foo} */
+ POLICY_LEX_DOUBLE_QUOTED_STRING,
+ POLICY_LEX_SINGLE_QUOTED_STRING,
+ POLICY_LEX_BACK_QUOTED_STRING,
+ POLICY_LEX_BARE_WORD
+} policy_lex_t;
+
+typedef enum policy_type_t {
+ POLICY_TYPE_BAD = 0,
+ POLICY_TYPE_IF,
+ POLICY_TYPE_CONDITIONAL,
+ POLICY_TYPE_ASSIGNMENT,
+ POLICY_TYPE_ATTRIBUTE_LIST,
+ POLICY_TYPE_PRINT,
+ POLICY_TYPE_NAMED_POLICY,
+ POLICY_TYPE_CALL,
+ POLICY_TYPE_NUM_TYPES
+} policy_type_t;
+
+
+/*
+ * For our policy language, we want to have some reserved words.
+ */
+typedef enum policy_reserved_word_t {
+ POLICY_RESERVED_UNKNOWN = 0,
+ POLICY_RESERVED_CONTROL,
+ POLICY_RESERVED_REQUEST,
+ POLICY_RESERVED_REPLY,
+ POLICY_RESERVED_PROXY_REQUEST,
+ POLICY_RESERVED_PROXY_REPLY,
+ POLICY_RESERVED_IF,
+ POLICY_RESERVED_ELSE,
+ POLICY_RESERVED_DEBUG,
+ POLICY_RESERVED_PRINT,
+ POLICY_RESERVED_POLICY,
+ POLICY_RESERVED_CALL,
+ POLICY_RESERVED_NUM_WORDS
+} policy_reserved_word_t;
+
+#define POLICY_DEBUG_NONE 0
+#define POLICY_DEBUG_PEEK (1 << 0)
+#define POLICY_DEBUG_PRINT_TOKENS (1 << 1)
+#define POLICY_DEBUG_PRINT_POLICY (1 << 2)
+#define POLICY_DEBUG_EVALUATE (1 << 3)
+
+/*
+ * A policy item
+ */
+typedef struct policy_item_t {
+ struct policy_item_t *next;
+ policy_type_t type;
+ int lineno;
+} policy_item_t;
+
+
+/*
+ * A list of attributes to add/replace/whatever in a packet.
+ */
+typedef struct policy_print_t {
+ policy_item_t item;
+ policy_lex_t rhs_type;
+ const char *rhs;
+} policy_print_t;
+
+
+/*
+ * A list of attributes to add/replace/whatever in a packet.
+ */
+typedef struct policy_attributes_t {
+ policy_item_t item;
+ policy_reserved_word_t where; /* where to do it */
+ policy_lex_t how; /* how to do */
+ policy_item_t *attributes; /* things to do */
+ /* FIXME: VALUE_PAIR *vps; */
+} policy_attributes_t;
+
+
+/*
+ * Holds a named policy
+ */
+typedef struct policy_named_t {
+ policy_item_t item;
+ char *name;
+ policy_item_t *policy;
+} policy_named_t;
+
+
+/*
+ * Reference to a named policy
+ */
+typedef struct policy_call_t {
+ policy_item_t item;
+ char *name;
+} policy_call_t;
+
+
+/*
+ * Holds an assignment.
+ */
+typedef struct policy_assignment_t {
+ policy_item_t item;
+ char *lhs;
+ policy_lex_t assign; /* operator for the assignment */
+ policy_lex_t rhs_type;
+ char *rhs;
+} policy_assignment_t;
+
+
+/*
+ * Condition
+ */
+typedef struct policy_condition_t {
+ policy_item_t item;
+
+ policy_lex_t lhs_type;
+ char *lhs;
+ policy_lex_t compare;
+ policy_lex_t rhs_type; /* bare word, quoted string, etc. */
+ char *rhs;
+
+ policy_lex_t child_condition;
+ policy_item_t *child;
+} policy_condition_t;
+
+
+/*
+ * Holds an "if" statement. The "else" may be a block, or another "if"
+ */
+typedef struct policy_if_t {
+ policy_item_t item;
+ policy_item_t *condition;
+ policy_item_t *if_true;
+ policy_item_t *if_false; /* assignment, or other 'if' */
+} policy_if_t;
+
+
+/*
+ * Define a structure for our module configuration.
+ *
+ * These variables do not need to be in a structure, but it's
+ * a lot cleaner to do so, and a pointer to the structure can
+ * be used as the instance handle.
+ */
+typedef struct rlm_policy_t {
+ char *filename;
+ rbtree_t *policies;
+} rlm_policy_t;
+
+
+/*
+ * Policies can be named.
+ */
+typedef struct rlm_policy_name_t {
+ char name[32];
+ policy_item_t *policy;
+} rlm_policy_name_t;
+
+
+/*
+ * Functions.
+ */
+extern const LRAD_NAME_NUMBER rlm_policy_tokens[];
+extern const LRAD_NAME_NUMBER policy_reserved_words[];
+
+extern int rlm_policy_insert(rbtree_t *head, const char *name,
+ policy_item_t *policy);
+
+extern policy_item_t *rlm_policy_parse(rlm_policy_t * inst,
+ const char *filename);
+extern void rlm_policy_free_item(policy_item_t *item);
+extern void rlm_policy_print(const policy_item_t *item);
+extern int rlm_policy_evaluate(rlm_policy_t *inst, REQUEST *request,
+ const char *name);
+
+#endif /* _RLM_POLICY_H */