Fix typos in previous commit
[freeradius.git] / src / main / evaluate.c
index 19d387a..10aeac4 100644 (file)
@@ -47,6 +47,7 @@ RCSID("$Id$")
 #endif
 #endif
 
+#ifdef WITH_UNLANG
 
 static int all_digits(const char *string)
 {
@@ -59,19 +60,11 @@ static int all_digits(const char *string)
        return (*p == '\0');
 }
 
-#ifndef NDEBUG
-#ifndef DEBUG4
-#define DEBUG4  if (debug_flag > 4)log_debug
-#endif
-#else
-#define DEBUG4 if (0) log_debug
-#endif
-
 static const char *filler = "????????????????????????????????????????????????????????????????";
 
 static const char *expand_string(char *buffer, size_t sizeof_buffer,
                                 REQUEST *request,
-                                LRAD_TOKEN value_type, const char *value)
+                                FR_TOKEN value_type, const char *value)
 {
        int result;
        char *p;
@@ -101,7 +94,6 @@ static const char *expand_string(char *buffer, size_t sizeof_buffer,
                }
                return buffer;
 
-
        case T_DOUBLE_QUOTED_STRING:
                if (!strchr(value, '%')) return value;
 
@@ -113,9 +105,10 @@ static const char *expand_string(char *buffer, size_t sizeof_buffer,
 }
 
 #ifdef HAVE_REGEX_H
-static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen, int *pcflags)
+static FR_TOKEN getregex(const char **ptr, char *buffer, size_t buflen,
+                        int *pcflags)
 {
-       char *p = *ptr;
+       const char *p = *ptr;
        char *q = buffer;
 
        if (*p != '/') return T_OP_INVALID;
@@ -169,7 +162,7 @@ static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen, int *pcflags
 
                        default:
                                if ((p[1] >= '0') && (p[1] <= '9') &&
-                                   (sscanf(p, "%3o", &x) == 1)) {
+                                   (sscanf(p + 1, "%3o", &x) == 1)) {
                                        *q++ = x;
                                        p += 2;
                                } else {
@@ -188,11 +181,11 @@ static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen, int *pcflags
        *q = '\0';
        *ptr = p;
 
-       return T_BARE_WORD;
+       return T_DOUBLE_QUOTED_STRING;
 }
 #endif
 
-static const LRAD_NAME_NUMBER modreturn_table[] = {
+static const FR_NAME_NUMBER modreturn_table[] = {
        { "reject",     RLM_MODULE_REJECT       },
        { "fail",       RLM_MODULE_FAIL         },
        { "ok",         RLM_MODULE_OK           },
@@ -205,6 +198,365 @@ static const LRAD_NAME_NUMBER modreturn_table[] = {
        { NULL, 0 }
 };
 
+
+int radius_get_vp(REQUEST *request, const char *name, VALUE_PAIR **vp_p)
+{
+       const char *vp_name = name;
+       REQUEST *myrequest = request;
+       DICT_ATTR *da;
+       VALUE_PAIR *vps = NULL;
+
+       *vp_p = NULL;
+
+       /*
+        *      Allow for tunneled sessions.
+        */
+       if (strncmp(vp_name, "outer.", 6) == 0) {
+               if (!myrequest->parent) return TRUE;
+               vp_name += 6;
+               myrequest = myrequest->parent;
+       }
+
+       if (strncmp(vp_name, "request:", 8) == 0) {
+               vp_name += 8;
+               vps = myrequest->packet->vps;
+
+       } else if (strncmp(vp_name, "reply:", 6) == 0) {
+               vp_name += 6;
+               vps = myrequest->reply->vps;
+
+#ifdef WITH_PROXY
+       } else if (strncmp(vp_name, "proxy-request:", 14) == 0) {
+               vp_name += 14;
+               if (request->proxy) vps = myrequest->proxy->vps;
+
+       } else if (strncmp(vp_name, "proxy-reply:", 12) == 0) {
+               vp_name += 12;
+               if (request->proxy_reply) vps = myrequest->proxy_reply->vps;
+#endif
+
+       } else if (strncmp(vp_name, "config:", 7) == 0) {
+               vp_name += 7;
+               vps = myrequest->config_items;
+
+       } else if (strncmp(vp_name, "control:", 8) == 0) {
+               vp_name += 8;
+               vps = myrequest->config_items;
+
+#ifdef WITH_COA
+       } else if (strncmp(vp_name, "coa:", 4) == 0) {
+               vp_name += 4;
+
+               if (myrequest->coa &&
+                   (myrequest->coa->proxy->code == PW_COA_REQUEST)) {
+                       vps = myrequest->coa->proxy->vps;
+               }
+
+       } else if (strncmp(vp_name, "coa-reply:", 10) == 0) {
+               vp_name += 10;
+
+               if (myrequest->coa && /* match reply with request */
+                   (myrequest->coa->proxy->code == PW_COA_REQUEST) &&
+                   (myrequest->coa->proxy_reply)) {
+                       vps = myrequest->coa->proxy_reply->vps;
+               }
+
+       } else if (strncmp(vp_name, "disconnect:", 11) == 0) {
+               vp_name += 11;
+
+               if (myrequest->coa &&
+                   (myrequest->coa->proxy->code == PW_DISCONNECT_REQUEST)) {
+                       vps = myrequest->coa->proxy->vps;
+               }
+
+       } else if (strncmp(vp_name, "disconnect-reply:", 17) == 0) {
+               vp_name += 17;
+
+               if (myrequest->coa && /* match reply with request */
+                   (myrequest->coa->proxy->code == PW_DISCONNECT_REQUEST) &&
+                   (myrequest->coa->proxy_reply)) {
+                       vps = myrequest->coa->proxy_reply->vps;
+               }
+#endif
+
+       } else {
+               vps = myrequest->packet->vps;
+       }
+
+       da = dict_attrbyname(vp_name);
+       if (!da) return FALSE;  /* not a dictionary name */
+
+       /*
+        *      May not may not be found, but it *is* a known name.
+        */
+       *vp_p = pairfind(vps, da->attr, da->vendor);
+       return TRUE;
+}
+
+
+/*
+ *     *presult is "did comparison match or not"
+ */
+static int radius_do_cmp(REQUEST *request, int *presult,
+                        FR_TOKEN lt, const char *pleft, FR_TOKEN token,
+                        FR_TOKEN rt, const char *pright,
+                        int cflags, int modreturn)
+{
+       int result;
+       uint32_t lint, rint;
+       VALUE_PAIR *vp = NULL;
+#ifdef HAVE_REGEX_H
+       char buffer[1024];
+#else
+       cflags = cflags;        /* -Wunused */
+#endif
+
+       rt = rt;                /* -Wunused */
+
+       if (lt == T_BARE_WORD) {
+               /*
+                *      Maybe check the last return code.
+                */
+               if (token == T_OP_CMP_TRUE) {
+                       int isreturn;
+
+                       /*
+                        *      Looks like a return code, treat is as such.
+                        */
+                       isreturn = fr_str2int(modreturn_table, pleft, -1);
+                       if (isreturn != -1) {
+                               *presult = (modreturn == isreturn);
+                               return TRUE;
+                       }
+               }
+
+               /*
+                *      Bare words on the left can be attribute names.
+                */
+               if (radius_get_vp(request, pleft, &vp)) {
+                       VALUE_PAIR myvp;
+
+                       /*
+                        *      VP exists, and that's all we're looking for.
+                        */
+                       if (token == T_OP_CMP_TRUE) {
+                               *presult = (vp != NULL);
+                               return TRUE;
+                       }
+
+                       if (!vp) {
+                               DICT_ATTR *da;
+                               
+                               /*
+                                *      The attribute on the LHS may
+                                *      have been a dynamically
+                                *      registered callback.  i.e. it
+                                *      doesn't exist as a VALUE_PAIR.
+                                *      If so, try looking for it.
+                                */
+                               da = dict_attrbyname(pleft);
+                               if (da && (da->vendor == 0) && radius_find_compare(da->attr)) {
+                                       VALUE_PAIR *check = pairmake(pleft, pright, token);
+                                       *presult = (radius_callback_compare(request, NULL, check, NULL, NULL) == 0);
+                                       RDEBUG3("  Callback returns %d",
+                                               *presult);
+                                       pairfree(&check);
+                                       return TRUE;
+                               }
+                               
+                               RDEBUG2("    (Attribute %s was not found)",
+                                      pleft);
+                               *presult = 0;
+                               return TRUE;
+                       }
+
+#ifdef HAVE_REGEX_H
+                       /*
+                        *      Regex comparisons treat everything as
+                        *      strings.
+                        */
+                       if ((token == T_OP_REG_EQ) ||
+                           (token == T_OP_REG_NE)) {
+                               vp_prints_value(buffer, sizeof(buffer), vp, 0);
+                               pleft = buffer;
+                               goto do_checks;
+                       }
+#endif
+
+                       memcpy(&myvp, vp, sizeof(myvp));
+                       if (!pairparsevalue(&myvp, pright)) {
+                               RDEBUG2("Failed parsing \"%s\": %s",
+                                      pright, fr_strerror());
+                               return FALSE;
+                       }
+
+                       myvp.operator = token;
+                       *presult = paircmp(&myvp, vp);
+                       RDEBUG3("  paircmp -> %d", *presult);
+                       return TRUE;
+               } /* else it's not a VP in a list */
+       }
+
+#ifdef HAVE_REGEX_H
+       do_checks:
+#endif
+       switch (token) {
+       case T_OP_GE:
+       case T_OP_GT:
+       case T_OP_LE:
+       case T_OP_LT:
+               if (!all_digits(pright)) {
+                       RDEBUG2("    (Right field is not a number at: %s)", pright);
+                       return FALSE;
+               }
+               rint = strtoul(pright, NULL, 0);
+               if (!all_digits(pleft)) {
+                       RDEBUG2("    (Left field is not a number at: %s)", pleft);
+                       return FALSE;
+               }
+               lint = strtoul(pleft, NULL, 0);
+               break;
+               
+       default:
+               lint = rint = 0;  /* quiet the compiler */
+               break;
+       }
+       
+       switch (token) {
+       case T_OP_CMP_TRUE:
+               /*
+                *      Check for truth or falsehood.
+                */
+               if (all_digits(pleft)) {
+                       lint = strtoul(pleft, NULL, 0);
+                       result = (lint != 0);
+                       
+               } else {
+                       result = (*pleft != '\0');
+               }
+               break;
+               
+
+       case T_OP_CMP_EQ:
+               result = (strcmp(pleft, pright) == 0);
+               break;
+               
+       case T_OP_NE:
+               result = (strcmp(pleft, pright) != 0);
+               break;
+               
+       case T_OP_GE:
+               result = (lint >= rint);
+               break;
+               
+       case T_OP_GT:
+               result = (lint > rint);
+               break;
+               
+       case T_OP_LE:
+               result = (lint <= rint);
+               break;
+               
+       case T_OP_LT:
+               result = (lint < rint);
+               break;
+
+#ifdef HAVE_REGEX_H
+       case T_OP_REG_EQ: {
+               int i, compare;
+               regex_t reg;
+               regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
+               
+               /*
+                *      Include substring matches.
+                */
+               compare = regcomp(&reg, pright, cflags);
+               if (compare != 0) {
+                       if (debug_flag) {
+                               char errbuf[128];
+
+                               regerror(compare, &reg, errbuf, sizeof(errbuf));
+                               DEBUG("ERROR: Failed compiling regular expression: %s", errbuf);
+                       }
+                       return FALSE;
+               }
+
+               compare = regexec(&reg, pleft,
+                                 REQUEST_MAX_REGEX + 1,
+                                 rxmatch, 0);
+               regfree(&reg);
+               
+               /*
+                *      Add new %{0}, %{1}, etc.
+                */
+               if (compare == 0) for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
+                       char *r;
+
+                       free(request_data_get(request, request,
+                                             REQUEST_DATA_REGEX | i));
+
+                       /*
+                        *      No %{i}, skip it.
+                        *      We MAY have %{2} without %{1}.
+                        */
+                       if (rxmatch[i].rm_so == -1) continue;
+                       
+                       /*
+                        *      Copy substring into allocated buffer
+                        */
+                       r = rad_malloc(rxmatch[i].rm_eo -rxmatch[i].rm_so + 1);
+                       memcpy(r, pleft + rxmatch[i].rm_so,
+                              rxmatch[i].rm_eo - rxmatch[i].rm_so);
+                       r[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
+
+                       request_data_add(request, request,
+                                        REQUEST_DATA_REGEX | i,
+                                        r, free);
+               }
+               result = (compare == 0);
+       }
+               break;
+               
+       case T_OP_REG_NE: {
+               int compare;
+               regex_t reg;
+               regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
+               
+               /*
+                *      Include substring matches.
+                */
+               compare = regcomp(&reg, pright, cflags);
+               if (compare != 0) {
+                       if (debug_flag) {
+                               char errbuf[128];
+
+                               regerror(compare, &reg, errbuf, sizeof(errbuf));
+                               DEBUG("ERROR: Failed compiling regular expression: %s", errbuf);
+                       }
+                       return FALSE;
+               }
+
+               compare = regexec(&reg, pleft,
+                                 REQUEST_MAX_REGEX + 1,
+                                 rxmatch, 0);
+               regfree(&reg);
+               
+               result = (compare != 0);
+       }
+               break;
+#endif
+               
+       default:
+               RDEBUG4(">>> NOT IMPLEMENTED %d", token);
+               result = FALSE;
+               break;
+       }
+       
+       *presult = result;
+       return TRUE;
+}
+
+
 int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                              const char **ptr, int evaluate_it, int *presult)
 {
@@ -212,42 +564,54 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
        int result = TRUE;
        int invert = FALSE;
        int evaluate_next_condition = evaluate_it;
-       const char *p = *ptr;
+       const char *p;
        const char *q, *start;
-       LRAD_TOKEN token, lt, rt;
+       FR_TOKEN token, lt, rt;
        char left[1024], right[1024], comp[4];
        const char *pleft, *pright;
        char  xleft[1024], xright[1024];
-       int lint, rint;
-#ifdef HAVE_REGEX_H
        int cflags = 0;
-#endif
-
+       
        if (!ptr || !*ptr || (depth >= 64)) {
                radlog(L_ERR, "Internal sanity check failed in evaluate condition");
                return FALSE;
        }
 
+       /*
+        *      Horrible parser.
+        */
+       p =  *ptr;
        while (*p) {
                while ((*p == ' ') || (*p == '\t')) p++;
 
-               if (*p == '!') {
-                       DEBUG4(">>> INVERT");
-                       invert = TRUE;
+               /*
+                *      ! EXPR
+                */
+               if (!found_condition && (*p == '!')) {
+                       /*
+                        *      Don't change the results if we're not
+                        *      evaluating the condition.
+                        */
+                       if (evaluate_next_condition) {
+                               RDEBUG4(">>> INVERT");
+                               invert = TRUE;
+                       }
                        p++;
+
+                       while ((*p == ' ') || (*p == '\t')) p++;
                }
 
                /*
-                *      It's a subcondition.
+                *      ( EXPR ) 
                 */
-               if (*p == '(') {
+               if (!found_condition && (*p == '(')) {
                        const char *end = p + 1;
 
                        /*
                         *      Evaluate the condition, bailing out on
                         *      parse error.
                         */
-                       DEBUG4(">>> CALLING EVALUATE %s", end);
+                       RDEBUG4(">>> RECURSING WITH ... %s", end);
                        if (!radius_evaluate_condition(request, modreturn,
                                                       depth + 1, &end,
                                                       evaluate_next_condition,
@@ -255,14 +619,14 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                                return FALSE;
                        }
 
-                       if (invert && evaluate_next_condition) {
-                               DEBUG2("%.*s Converting !%s -> %s",
-                                      depth, filler,
-                                      (result != FALSE) ? "TRUE" : "FALSE",
-                                      (result == FALSE) ? "TRUE" : "FALSE");
-
-                                      
-                               result = (result == FALSE);
+                       if (invert) {
+                               if (evaluate_next_condition) {
+                                       RDEBUG2("%.*s Converting !%s -> %s",
+                                               depth, filler,
+                                               (result != FALSE) ? "TRUE" : "FALSE",
+                                               (result == FALSE) ? "TRUE" : "FALSE");
+                                       result = (result == FALSE);
+                               }
                                invert = FALSE;
                        }
 
@@ -271,76 +635,71 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                         *      condition
                         */
                        p = end;
-                       DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
-                       
-                       if (!((*p == ')') || (*p == '!') ||
-                             ((p[0] == '&') && (p[1] == '&')) ||
-                             ((p[0] == '|') && (p[1] == '|')))) {
+                       RDEBUG4(">>> AFTER RECURSION ... %s", end);
 
-                               radlog(L_ERR, "Parse error in condition at: %s", p);
+                       while ((*p == ' ') || (*p == '\t')) p++;
+
+                       if (!*p) {
+                               radlog(L_ERR, "No closing brace");
                                return FALSE;
                        }
-                       if (*p == ')') p++;     /* skip it */
+
+                       if (*p == ')') p++; /* eat closing brace */
                        found_condition = TRUE;
-                       
+
                        while ((*p == ' ') || (*p == '\t')) p++;
+               }
 
+               /*
+                *      At EOL or closing brace, update && return.
+                */
+               if (found_condition && (!*p || (*p == ')'))) break;
+
+               /*
+                *      Now it's either:
+                *
+                *      WORD
+                *      WORD1 op WORD2
+                *      && EXPR
+                *      || EXPR
+                */
+               if (found_condition) {
                        /*
-                        *      EOL.  It's OK.
+                        *      (A && B) means "evaluate B
+                        *      only if A was true"
                         */
-                       if (!*p) {
-                               DEBUG4(">>> AT EOL");
-                               *ptr = p;
-                               *presult = result;
-                               return TRUE;
-                               
-                               /*
-                                *      (A && B) means "evaluate B
-                                *      only if A was true"
-                                */
-                       } else if ((p[0] == '&') && (p[1] == '&')) {
-                               if (result == TRUE) {
-                                       evaluate_next_condition = evaluate_it;
-                               } else {
-                                       evaluate_next_condition = FALSE;
-                               }
-                               p += 2;
-                               
-                               /*
-                                *      (A || B) means "evaluate B
-                                *      only if A was false"
-                                */
-                       } else if ((p[0] == '|') && (p[1] == '|')) {
-                               if (result == FALSE) {
-                                       evaluate_next_condition = evaluate_it;
-                               } else {
-                                       evaluate_next_condition = FALSE;
-                               }
+                       if ((p[0] == '&') && (p[1] == '&')) {
+                               if (!result) evaluate_next_condition = FALSE;
                                p += 2;
+                               found_condition = FALSE;
+                               continue; /* go back to the start */
+                       }
 
-                       } else if (*p == ')') {
-                               DEBUG4(">>> CLOSING BRACE");
-                               *ptr = p;
-                               *presult = result;
-                               return TRUE;
-
-                       } else {
-                               /*
-                                *      Parse error
-                                */
-                               radlog(L_ERR, "Unexpected trailing text at: %s", p);
-                               return FALSE;
+                       /*
+                        *      (A || B) means "evaluate B
+                        *      only if A was false"
+                        */
+                       if ((p[0] == '|') && (p[1] == '|')) {
+                               if (result) evaluate_next_condition = FALSE;
+                               p += 2;
+                               found_condition = FALSE;
+                               continue;
                        }
-               } /* else it wasn't an opening brace */
 
-               while ((*p == ' ') || (*p == '\t')) p++;
+                       /*
+                        *      It must be:
+                        *
+                        *      WORD
+                        *      WORD1 op WORD2
+                        */
+               }
 
-               /*
-                *      More conditions, keep going.
-                */
-               if ((*p == '(') || (p[0] == '!')) continue;
+               if (found_condition) {
+                       radlog(L_ERR, "Consecutive conditions at %s", p);
+                       return FALSE;
+               }
 
-               DEBUG4(">>> LOOKING AT %s", p);
+               RDEBUG4(">>> LOOKING AT %s", p);
                start = p;
 
                /*
@@ -352,7 +711,7 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                }
 
                /*
-                *      Look for word == value
+                *      Look for WORD1 op WORD2
                 */
                lt = gettoken(&p, left, sizeof(left));
                if ((lt != T_BARE_WORD) &&
@@ -375,63 +734,48 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                }
 
                /*
-                *      Peek ahead.  Maybe it's just a check for
-                *      existence.  If so, there shouldn't be anything
-                *      else.
+                *      Peek ahead, to see if it's:
+                *
+                *      WORD
+                *
+                *      or something else, such as
+                *
+                *      WORD1 op WORD2
+                *      WORD )
+                *      WORD && EXPR
+                *      WORD || EXPR
                 */
                q = p;
                while ((*q == ' ') || (*q == '\t')) q++;
 
                /*
-                *      End of condition, 
+                *      If the next thing is:
+                *
+                *      EOL
+                *      end of condition
+                *      &&
+                *      ||
+                *
+                *      Then WORD is just a test for existence.
+                *      Remember that and skip ahead.
                 */
                if (!*q || (*q == ')') ||
-                   ((*q == '!') && (q[1] != '=') && (q[1] != '~')) ||
                    ((q[0] == '&') && (q[1] == '&')) ||
                    ((q[0] == '|') && (q[1] == '|'))) {
-                       /*
-                        *      Check for truth or falsehood.
-                        */
-                       if (all_digits(pleft)) {
-                               lint = atoi(pleft);
-                               result = (lint != 0);
-
-                       } else if (lt == T_BARE_WORD) {
-                               result = (modreturn == lrad_str2int(modreturn_table, pleft, -1));
-                       } else {
-                               result = (*pleft != '\0');
-                       }
-
-                       if (invert) {
-                               DEBUG4(">>> INVERTING result");
-                               result = (result == FALSE);
-                               invert = FALSE;
-                       }
-
-                       if (evaluate_next_condition) {
-                               DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
-                                      depth, filler,
-                                      invert ? "!" : "", pleft,
-                                      (result != FALSE) ? "TRUE" : "FALSE");
-
-                       } else if (request) {
-                               DEBUG2("%.*s Skipping %s\"%s\"",
-                                      depth, filler,
-                                      invert ? "!" : "", pleft);
-                       }
-
-                       DEBUG4(">>> I%d %d:%s", invert,
-                              lt, left);
-                       goto end_of_condition;
+                       token = T_OP_CMP_TRUE;
+                       rt = T_OP_INVALID;
+                       pright = NULL;
+                       goto do_cmp;
                }
 
                /*
-                *      Else it's a full "foo == bar" thingy.
+                *      Otherwise, it's:
+                *
+                *      WORD1 op WORD2
                 */
                token = gettoken(&p, comp, sizeof(comp));
                if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
-                   (token == T_OP_CMP_TRUE) ||
-                   (token == T_OP_CMP_FALSE)) {
+                   (token == T_OP_CMP_TRUE)) {
                        radlog(L_ERR, "Expected comparison at: %s", comp);
                        return FALSE;
                }
@@ -451,6 +795,10 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                if ((token == T_OP_REG_EQ) ||
                    (token == T_OP_REG_NE)) {
                        rt = getregex(&p, right, sizeof(right), &cflags);
+                       if (rt != T_DOUBLE_QUOTED_STRING) {
+                               radlog(L_ERR, "Expected regular expression at: %s", p);
+                               return FALSE;
+                       }
                } else
 #endif
                        rt = gettoken(&p, right, sizeof(right));
@@ -474,198 +822,84 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
                        }
                }
                
-               DEBUG4(">>> %d:%s %d %d:%s",
+               RDEBUG4(">>> %d:%s %d %d:%s",
                       lt, pleft, token, rt, pright);
                
+       do_cmp:
                if (evaluate_next_condition) {
-                       switch (token) {
-                       case T_OP_GT:
-                       case T_OP_LE:
-                       case T_OP_LT:
-                               if (!all_digits(pleft)) {
-                                       radlog(L_ERR, "Left field is not a number at: %s", pleft);
-                                       return FALSE;
-                               }
-                               if (!all_digits(pright)) {
-                                       radlog(L_ERR, "Right field is not a number at: %s", pright);
-                                       return FALSE;
-                               }
-                               lint = atoi(pleft);
-                               rint = atoi(pright);
-                               break;
-                               
-                       default:
-                               break;
-                       }
-
-                       switch (token) {
-                       case T_OP_CMP_EQ:
-                               result = (strcmp(pleft, pright) == 0);
-                               break;
-
-                       case T_OP_NE:
-                               result = (strcmp(pleft, pright) != 0);
-                               break;
-
-                       case T_OP_GE:
-                               result = (lint >= rint);
-                               break;
-
-                       case T_OP_GT:
-                               result = (lint > rint);
-                               break;
-
-                       case T_OP_LE:
-                               result = (lint <= rint);
-                               break;
-
-                       case T_OP_LT:
-                               result = (lint < rint);
-                               break;
-
-#ifdef HAVE_REGEX_H
-                       case T_OP_REG_EQ: {
-                               int i, compare;
-                               regex_t reg;
-                               regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
-                               
-                               /*
-                                *      Include substring matches.
-                                */
-                               regcomp(&reg, pright, cflags);
-                               compare = regexec(&reg, pleft,
-                                                 REQUEST_MAX_REGEX + 1,
-                                                 rxmatch, 0);
-                               regfree(&reg);
-                               
-                               /*
-                                *      Add %{0}, %{1}, etc.
-                                */
-                               for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
-                                       char *r;
-                                       char buffer[1024];
-                                       
-                                       /*
-                                        *      Didn't match: delete old
-                                        *      match, if it existed.
-                                        */
-                                       if ((compare != 0) ||
-                                           (rxmatch[i].rm_so == -1)) {
-                                               r = request_data_get(request,
-                                                                    request,
-                                                                    REQUEST_DATA_REGEX | i);
-                                               if (r) {
-                                                       free(r);
-                                                       continue;
-                                               }
-                                               
-                                               /*
-                                                *      No previous match
-                                                *      to delete, stop.
-                                                */
-                                               break;
-                                       }
-                                       
-                                       /*
-                                        *      Copy substring into buffer.
-                                        */
-                                       memcpy(buffer, pleft + rxmatch[i].rm_so,
-                                              rxmatch[i].rm_eo - rxmatch[i].rm_so);
-                                       buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
-                                       
-                                       /*
-                                        *      Copy substring, and add it to
-                                        *      the request.
-                                        *
-                                        *      Note that we don't check
-                                        *      for out of memory, which is
-                                        *      the only error we can get...
-                                        */
-                                       r = strdup(buffer);
-                                       request_data_add(request, request,
-                                                        REQUEST_DATA_REGEX | i,
-                                                        r, free);
-                               }
-                               result = (compare == 0);
-                       }
-                               break;
-
-                       case T_OP_REG_NE: {
-                               int compare;
-                               regex_t reg;
-                               regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
-                               
-                               /*
-                                *      Include substring matches.
-                                */
-                               regcomp(&reg, pright, cflags);
-                               compare = regexec(&reg, pleft,
-                                                 REQUEST_MAX_REGEX + 1,
-                                                 rxmatch, 0);
-                               regfree(&reg);
-                               
-                               result = (compare != 0);
+                       /*
+                        *      More parse errors.
+                        */
+                       if (!radius_do_cmp(request, &result, lt, pleft, token,
+                                          rt, pright, cflags, modreturn)) {
+                               return FALSE;
                        }
-                               break;
-#endif
+                       RDEBUG4(">>> Comparison returned %d", result);
 
-                       default:
-                               DEBUG4(">>> NOT IMPLEMENTED %d", token);
-                               break;
+                       if (invert) {
+                               RDEBUG4(">>> INVERTING result");
+                               result = (result == FALSE);
                        }
 
-                       DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
+                       RDEBUG2("%.*s Evaluating %s(%.*s) -> %s",
                               depth, filler,
                               invert ? "!" : "", p - start, start,
                               (result != FALSE) ? "TRUE" : "FALSE");
 
-                       DEBUG4(">>> GOT result %d", result);
+                       invert = FALSE;
+                       RDEBUG4(">>> GOT result %d", result);
 
                        /*
                         *      Not evaluating it.  We may be just
                         *      parsing it.
                         */
                } else if (request) {
-                       DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
+                       RDEBUG2("%.*s Skipping %s(%.*s)",
                               depth, filler,
-                              invert ? "!" : "", pleft, comp, pright);
+                              invert ? "!" : "", p - start, start);
                }
 
-               end_of_condition:
-               if (invert) {
-                       DEBUG4(">>> INVERTING result");
-                       result = (result == FALSE);
-                       invert = FALSE;
-               }
+               found_condition = TRUE;
+       } /* loop over the input condition */
 
-               /*
-                *      Don't evaluate it.
-                */
-               DEBUG4(">>> EVALUATE %d ::%s::",
-                       evaluate_next_condition, p);
+       if (!found_condition) {
+               radlog(L_ERR, "Syntax error.  Expected condition at %s", p);
+               return FALSE;
+       }
 
-               while ((*p == ' ') || (*p == '\t')) p++;
+       RDEBUG4(">>> AT EOL -> %d", result);
+       *ptr = p;
+       if (evaluate_it) *presult = result;
+       return TRUE;
+}
+#endif
 
-               /*
-                *      Closing brace or EOL, return.
-                */
-               if (!*p || (*p == ')') ||
-                   ((*p == '!') && (p[1] != '=') && (p[1] != '~')) ||
-                   ((p[0] == '&') && (p[1] == '&')) ||
-                   ((p[0] == '|') && (p[1] == '|'))) {
-                       DEBUG4(">>> AT EOL2a");
-                       *ptr = p;
-                       if (evaluate_next_condition) *presult = result;
-                       return TRUE;
+static void fix_up(REQUEST *request)
+{
+       VALUE_PAIR *vp;
+
+       request->username = NULL;
+       request->password = NULL;
+       
+       for (vp = request->packet->vps; vp != NULL; vp = vp->next) {
+               if (vp->vendor != 0) continue;
+
+               if ((vp->attribute == PW_USER_NAME) &&
+                   !request->username) {
+                       request->username = vp;
+                       
+               } else if (vp->attribute == PW_STRIPPED_USER_NAME) {
+                       request->username = vp;
+                       
+               } else if (vp->attribute == PW_USER_PASSWORD) {
+                       request->password = vp;
                }
-       } /* loop over the input condition */
 
-       DEBUG4(">>> AT EOL2b");
-       *ptr = p;
-       if (evaluate_next_condition) *presult = result;
-       return TRUE;
+               if (request->username && request->password) break;
+       }
 }
 
+
 /*
  *     The pairmove() function in src/lib/valuepair.c does all sorts of
  *     extra magic that we don't want here.
@@ -674,12 +908,12 @@ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
  *     only paircopy() those attributes that we're really going to
  *     use.
  */
-static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
+void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
 {
        int i, j, count, from_count, to_count, tailto;
        VALUE_PAIR *vp, *next, **last;
        VALUE_PAIR **from_list, **to_list;
-       int *edited;
+       int *edited = NULL;
 
        /*
         *      Set up arrays for editing, to remove some of the
@@ -730,7 +964,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
        edited = rad_malloc(sizeof(*edited) * to_count);
        memset(edited, 0, sizeof(*edited) * to_count);
 
-       DEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count);
+       RDEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count);
 
        /*
         *      Now that we have the lists initialized, start working
@@ -739,7 +973,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
        for (i = 0; i < from_count; i++) {
                int found;
 
-               DEBUG4("::: Examining %s", from_list[i]->name);
+               RDEBUG4("::: Examining %s", from_list[i]->name);
 
                /*
                 *      Attribute should be appended, OR the "to" list
@@ -750,12 +984,13 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
 
                found = FALSE;
                for (j = 0; j < to_count; j++) {
-                       if (edited[j]) continue;
+                       if (edited[j] || !to_list[j] || !from_list[i]) continue;
 
                        /*
                         *      Attributes aren't the same, skip them.
                         */
-                       if (from_list[i]->attribute != to_list[j]->attribute) {
+                       if ((from_list[i]->attribute != to_list[j]->attribute) ||
+                           (from_list[i]->vendor != to_list[j]->vendor)) {
                                continue;
                        }
 
@@ -772,7 +1007,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
                         *      the one in the "from" list.
                         */
                        if (from_list[i]->operator == T_OP_SET) {
-                               DEBUG4("::: OVERWRITING %s FROM %d TO %d",
+                               RDEBUG4("::: OVERWRITING %s FROM %d TO %d",
                                       to_list[j]->name, i, j);
                                pairfree(&to_list[j]);
                                to_list[j] = from_list[i];
@@ -792,10 +1027,19 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
                        }
 
                        /*
+                        *      Delete every attribute, independent
+                        *      of its value.
+                        */
+                       if (from_list[i]->operator == T_OP_CMP_FALSE) {
+                               goto delete;
+                       }
+
+                       /*
                         *      Delete all matching attributes from
                         *      "to"
                         */
                        if ((from_list[i]->operator == T_OP_SUB) ||
+                           (from_list[i]->operator == T_OP_CMP_EQ) ||
                            (from_list[i]->operator == T_OP_LE) ||
                            (from_list[i]->operator == T_OP_GE)) {
                                int rcode;
@@ -821,9 +1065,14 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
                                from_list[i]->operator = old_op;
 
                                switch (old_op) {
+                               case T_OP_CMP_EQ:
+                                       if (rcode != 0) goto delete;
+                                       break;
+
                                case T_OP_SUB:
                                        if (rcode == 0) {
-                                               DEBUG4("::: DELETING %s FROM %d TO %d",
+                                       delete:
+                                               RDEBUG4("::: DELETING %s FROM %d TO %d",
                                                       from_list[i]->name, i, j);
                                                pairfree(&to_list[j]);
                                                to_list[j] = NULL;
@@ -836,7 +1085,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
                                         */
                                case T_OP_LE:
                                        if (rcode > 0) {
-                                               DEBUG4("::: REPLACING %s FROM %d TO %d",
+                                               RDEBUG4("::: REPLACING %s FROM %d TO %d",
                                                       from_list[i]->name, i, j);
                                                pairfree(&to_list[j]);
                                                to_list[j] = from_list[i];
@@ -847,7 +1096,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
 
                                case T_OP_GE:
                                        if (rcode < 0) {
-                                               DEBUG4("::: REPLACING %s FROM %d TO %d",
+                                               RDEBUG4("::: REPLACING %s FROM %d TO %d",
                                                       from_list[i]->name, i, j);
                                                pairfree(&to_list[j]);
                                                to_list[j] = from_list[i];
@@ -875,7 +1124,7 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
                            (from_list[i]->operator == T_OP_GE) ||
                            (from_list[i]->operator == T_OP_SET)) {
                        append:
-                               DEBUG4("::: APPENDING %s FROM %d TO %d",
+                               RDEBUG4("::: APPENDING %s FROM %d TO %d",
                                       from_list[i]->name, i, tailto);
                                to_list[tailto++] = from_list[i];
                                from_list[i] = NULL;
@@ -893,37 +1142,50 @@ static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
        }
        free(from_list);
 
-       DEBUG4("::: TO in %d out %d", to_count, tailto);
+       RDEBUG4("::: TO in %d out %d", to_count, tailto);
 
        /*
         *      Re-chain the "to" list.
         */
        *to = NULL;
        last = to;
+
        for (i = 0; i < tailto; i++) {
                if (!to_list[i]) continue;
                
-               DEBUG4("::: to[%d] = %s", i, to_list[i]->name);
+               RDEBUG4("::: to[%d] = %s", i, to_list[i]->name);
+
+               /*
+                *      Mash the operator to a simple '='.  The
+                *      operators in the "to" list aren't used for
+                *      anything.  BUT they're used in the "detail"
+                *      file and debug output, where we don't want to
+                *      see the operators.
+                */
+               to_list[i]->operator = T_OP_EQ;
 
                *last = to_list[i];
                last = &(*last)->next;
+       }
 
-               /*
-                *      Fix dumb cache issues
-                */
-               if ((i >= to_count) || edited[i]) {
-                       if (to_list[i]->attribute == PW_USER_NAME) {
-                               request->username = to_list[i];
-                               
-                       } else if (to_list[i]->attribute == PW_USER_PASSWORD) {
-                               request->password = to_list[i];
-                       }
-               }
+       rad_assert(request != NULL);
+       rad_assert(request->packet != NULL);
+
+       /*
+        *      Fix dumb cache issues
+        */
+       if (to == &request->packet->vps) {
+               fix_up(request);
+       } else if (request->parent && (to == &request->parent->packet->vps)) {
+               fix_up(request->parent);
        }
+
        free(to_list);
+       free(edited);
 }
 
 
+#ifdef WITH_UNLANG
 /*
  *     Copied shamelessly from conffile.c, to simplify the API for
  *     now...
@@ -946,8 +1208,8 @@ struct conf_pair {
        CONF_ITEM item;
        char *attr;
        char *value;
-       LRAD_TOKEN operator;
-       LRAD_TOKEN value_type;
+       FR_TOKEN operator;
+       FR_TOKEN value_type;
 };
 
 /*
@@ -959,26 +1221,78 @@ int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
        CONF_ITEM *ci;
        VALUE_PAIR *newlist, *vp;
        VALUE_PAIR **output_vps = NULL;
+       REQUEST *myrequest = request;
 
        if (!request || !cs) return RLM_MODULE_INVALID;
 
+       /*
+        *      If we are an inner tunnel session, permit the
+        *      policy to update the outer lists directly.
+        */
+       if (strncmp(name, "outer.", 6) == 0) {
+               if (!request->parent) return RLM_MODULE_NOOP;
+
+               myrequest = request->parent;
+               name += 6;
+       }
+
        if (strcmp(name, "request") == 0) {
-               output_vps = &request->packet->vps;
+               output_vps = &myrequest->packet->vps;
 
        } else if (strcmp(name, "reply") == 0) {
-               output_vps = &request->reply->vps;
+               output_vps = &myrequest->reply->vps;
 
+#ifdef WITH_PROXY
        } else if (strcmp(name, "proxy-request") == 0) {
-               if (request->proxy) output_vps = &request->proxy->vps;
+               if (request->proxy) output_vps = &myrequest->proxy->vps;
 
        } else if (strcmp(name, "proxy-reply") == 0) {
                if (request->proxy_reply) output_vps = &request->proxy_reply->vps;
+#endif
 
        } else if (strcmp(name, "config") == 0) {
-               output_vps = &request->config_items;
+               output_vps = &myrequest->config_items;
 
        } else if (strcmp(name, "control") == 0) {
-               output_vps = &request->config_items;
+               output_vps = &myrequest->config_items;
+
+#ifdef WITH_COA
+       } else if (strcmp(name, "coa") == 0) {
+               if (!myrequest->coa) {
+                       request_alloc_coa(myrequest);
+                       myrequest->coa->proxy->code = PW_COA_REQUEST;
+               }
+                 
+               if (myrequest->coa &&
+                   (myrequest->coa->proxy->code == PW_COA_REQUEST)) {
+                       output_vps = &myrequest->coa->proxy->vps;
+               }
+
+       } else if (strcmp(name, "coa-reply") == 0) {
+               if (myrequest->coa && /* match reply with request */
+                   (myrequest->coa->proxy->code == PW_COA_REQUEST) &&
+                    (myrequest->coa->proxy_reply)) {
+                     output_vps = &myrequest->coa->proxy_reply->vps;
+               }
+
+       } else if (strcmp(name, "disconnect") == 0) {
+               if (!myrequest->coa) {
+                       request_alloc_coa(myrequest);
+                       if (myrequest->coa) myrequest->coa->proxy->code = PW_DISCONNECT_REQUEST;
+               }
+
+               if (myrequest->coa &&
+                   (myrequest->coa->proxy->code == PW_DISCONNECT_REQUEST)) {
+                       output_vps = &myrequest->coa->proxy->vps;
+               }
+
+       } else if (strcmp(name, "disconnect-reply") == 0) {
+               if (myrequest->coa && /* match reply with request */
+                   (myrequest->coa->proxy->code == PW_DISCONNECT_REQUEST) &&
+                   (myrequest->coa->proxy_reply)) {
+                       output_vps = &myrequest->coa->proxy_reply->vps;
+               }
+#endif
 
        } else {
                return RLM_MODULE_INVALID;
@@ -988,7 +1302,7 @@ int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
 
        newlist = paircopy(input_vps);
        if (!newlist) {
-               DEBUG2("Out of memory");
+               RDEBUG2("Out of memory");
                return RLM_MODULE_FAIL;
        }
 
@@ -1008,6 +1322,13 @@ int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
 
                cp = cf_itemtopair(ci);
 
+#ifndef NDEBUG
+               if (debug_flag && (vp->vendor == 0) &&
+                   radius_find_compare(vp->attribute)) {
+                       DEBUG("WARNING: You are modifying the value of virtual attribute %s.  This is not supported.", vp->name);
+               }
+#endif
+
                /*
                 *      The VP && CF lists should be in sync.  If they're
                 *      not, panic.
@@ -1024,8 +1345,8 @@ int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
                        }
 
                        if (!pairparsevalue(vp, value)) {
-                               DEBUG2("ERROR: Failed parsing value \"%s\" for attribute %s: %s",
-                                      value, vp->name, librad_errstr);
+                               RDEBUG2("ERROR: Failed parsing value \"%s\" for attribute %s: %s",
+                                      value, vp->name, fr_strerror());
                                pairfree(&newlist);
                                return RLM_MODULE_FAIL;
                        }
@@ -1034,7 +1355,8 @@ int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
                vp = vp->next;
        }
 
-       my_pairmove(request, output_vps, newlist);
+       radius_pairmove(request, output_vps, newlist);
 
        return RLM_MODULE_UPDATED;
 }
+#endif