+ return T_DOUBLE_QUOTED_STRING;
+}
+#endif
+
+static const FR_NAME_NUMBER modreturn_table[] = {
+ { "reject", RLM_MODULE_REJECT },
+ { "fail", RLM_MODULE_FAIL },
+ { "ok", RLM_MODULE_OK },
+ { "handled", RLM_MODULE_HANDLED },
+ { "invalid", RLM_MODULE_INVALID },
+ { "userlock", RLM_MODULE_USERLOCK },
+ { "notfound", RLM_MODULE_NOTFOUND },
+ { "noop", RLM_MODULE_NOOP },
+ { "updated", RLM_MODULE_UPDATED },
+ { 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(®, pright, cflags);
+ if (compare != 0) {
+ if (debug_flag) {
+ char errbuf[128];
+
+ regerror(compare, ®, errbuf, sizeof(errbuf));
+ DEBUG("ERROR: Failed compiling regular expression: %s", errbuf);
+ }
+ return FALSE;
+ }
+
+ compare = regexec(®, pleft,
+ REQUEST_MAX_REGEX + 1,
+ rxmatch, 0);
+ regfree(®);
+
+ /*
+ * 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(®, pright, cflags);
+ if (compare != 0) {
+ if (debug_flag) {
+ char errbuf[128];
+
+ regerror(compare, ®, errbuf, sizeof(errbuf));
+ DEBUG("ERROR: Failed compiling regular expression: %s", errbuf);
+ }
+ return FALSE;
+ }
+
+ compare = regexec(®, pleft,
+ REQUEST_MAX_REGEX + 1,
+ rxmatch, 0);
+ regfree(®);
+
+ result = (compare != 0);
+ }
+ break;
+#endif
+
+ default:
+ RDEBUG4(">>> NOT IMPLEMENTED %d", token);
+ result = FALSE;
+ break;
+ }
+
+ *presult = result;
+ return TRUE;