First pass at a "policy language"
authoraland <aland>
Sat, 4 Dec 2004 17:58:04 +0000 (17:58 +0000)
committeraland <aland>
Sat, 4 Dec 2004 17:58:04 +0000 (17:58 +0000)
In the tradition of programmers everywhere, there's no documentation.

But it works, honest

src/modules/rlm_policy/Makefile [new file with mode: 0644]
src/modules/rlm_policy/evaluate.c [new file with mode: 0644]
src/modules/rlm_policy/parse.c [new file with mode: 0644]
src/modules/rlm_policy/rlm_policy.c [new file with mode: 0644]
src/modules/rlm_policy/rlm_policy.h [new file with mode: 0644]

diff --git a/src/modules/rlm_policy/Makefile b/src/modules/rlm_policy/Makefile
new file mode 100644 (file)
index 0000000..163da0f
--- /dev/null
@@ -0,0 +1,11 @@
+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)
diff --git a/src/modules/rlm_policy/evaluate.c b/src/modules/rlm_policy/evaluate.c
new file mode 100644 (file)
index 0000000..1262bab
--- /dev/null
@@ -0,0 +1,990 @@
+/*\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(&reg, this->rhs,\r
+                                   REG_EXTENDED) != 0) {\r
+                               return FALSE;\r
+                       }\r
+                       rad_assert(data != NULL);\r
+                       rcode = regexec(&reg, data,\r
+                                       REQUEST_MAX_REGEX + 1,\r
+                                       rxmatch, 0);\r
+                       rcode = (rcode == 0);\r
+                       regfree(&reg);\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(&reg, this->rhs, REG_EXTENDED|REG_NOSUB);\r
+                       rad_assert(data != NULL);\r
+                       rcode = regexec(&reg, data,\r
+                                       0, NULL, 0);\r
+                       rcode = (rcode != 0);\r
+                       regfree(&reg);\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
diff --git a/src/modules/rlm_policy/parse.c b/src/modules/rlm_policy/parse.c
new file mode 100644 (file)
index 0000000..2b1b565
--- /dev/null
@@ -0,0 +1,1246 @@
+/*
+ * 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;
+}
diff --git a/src/modules/rlm_policy/rlm_policy.c b/src/modules/rlm_policy/rlm_policy.c
new file mode 100644 (file)
index 0000000..d4c6ece
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * 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 */
+};
diff --git a/src/modules/rlm_policy/rlm_policy.h b/src/modules/rlm_policy/rlm_policy.h
new file mode 100644 (file)
index 0000000..2d8aa97
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * 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 */