--- /dev/null
+ Expression Module Configuration
+
+0. INTRODUCTION
+
+ The expression module (rlm_expr) allows the server to perform
+limited mathematical calculations. It is enabled by default in
+radiusd.conf, so there's nothing special you have to do to use it.
+
+
+1. USAGE
+
+ The expression module is used via the dynamic translation of
+strings. (See 'variables.txt' in this directory for more
+information). For example, some NAS boxes send a NAS-Port attribute
+which is a 32-bit number composed of port, card, and interface, all in
+different bytes. To see these attributes split into pieces, you can
+do something like:
+
+DEFAULT
+ Vendor-Interface-Number = `%{expr: %{NAS-Port} / (256 * 256)}`,
+ Vendor-Card-Number += `%{expr: (%{NAS-Port} / 256) %% 256}`,
+ Vendor-Port-Number += `%{expr: %{NAS-Port} %% 256}`
+
+ where the attributes Vendor-Interface-Number, Vendor-Card-Number,
+and Vendor-Port-Number are attributes created by either you or the
+vendor-supplied dictionary.
+
+
+2. MATHEMATICAL OPERATORS
+
+ The methematical operators supported by the expression module are:
+
+ + addition
+ - subtraction
+ / division
+ %% modulo remainder
+ * multiplication
+ & boolean AND
+ | boolean OR
+ () grouping of sub-expressions.
+
+
+ Note that the modulo remainder operator is '%%', and not '%'. This
+is due to the '%' character being used as a special character for
+dynamic translation.
+
+ Note also that these operators do NOT have precedence. The parsing
+of the input string, and the calculation of the asnwer, is done
+strictly left to right. If you wish to order the expressions, you
+MUST group them into sub-expression, as shown in the previous
+example.
+
+ All of the calculations are performed as signed 32-bit integers.
+
+$Id$
# allowmultiplekeys = no
#}
- # Similar configuration, for the /etc/group file. Adds Group-Name
- # attribute for every group user is member of
+ # Similar configuration, for the /etc/group file. Adds a Group-Name
+ # attribute for every group that the user is member of.
+ #
#passwd etc_group {
# filename = /etc/group
# format = "Group-Name:::*,User-Name"
mpp = no
}
+ #
+ # The 'expression' module current has no configuration.
+ #
+ expr {
+ }
+
# ANSI X9.9 token support. Not included by default.
# $INCLUDE ${confdir}/x99.conf
# here, and ensure that the configuration will be OK.
#
instantiate {
-
+ #
+ # The expression module doesn't do authorization,
+ # authentication, or accounting. It only does dynamic
+ # translation, of the form:
+ #
+ # Session-Timeout = `%{expr:2 + 3}`
+ #
+ # So the module needs to be instantiated, but CANNOT be
+ # listed in any other section.
+ #
+ expr
}
# Authorization. First preprocess (hints and huntgroups files),
TOKEN_INTEGER,
TOKEN_ADD,
TOKEN_SUBTRACT,
- TOKEN_XLAT
+ TOKEN_DIVIDE,
+ TOKEN_REMAINDER,
+ TOKEN_MULTIPLY,
+ TOKEN_AND,
+ TOKEN_OR,
+ TOKEN_LAST
} expr_token_t;
-/*
- * Do xlat of strings!
- */
-static int expr_xlat(void *instance, REQUEST *request, char *fmt, char *out, int outlen,
- RADIUS_ESCAPE_STRING func)
+typedef struct expr_map_t {
+ char op;
+ expr_token_t token;
+} expr_map_t;
+
+static expr_map_t map[] =
{
+ {'+', TOKEN_ADD },
+ {'-', TOKEN_SUBTRACT },
+ {'/', TOKEN_DIVIDE },
+ {'*', TOKEN_MULTIPLY },
+ {'%', TOKEN_REMAINDER },
+ {'&', TOKEN_AND },
+ {'|', TOKEN_OR },
+ {0, TOKEN_LAST}
+};
+
+static int get_number(REQUEST *request, const char **string, int *answer)
+{
+ int i, found;
int result, x;
- const char *p;
+ const char *p;
expr_token_t this;
- rlm_expr_t *inst = instance;
- char buffer[256];
-
- /*
- * Do an xlat on the provided string (nice recursive operation).
- */
- if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
- radlog(L_ERR, "rlm_expr: xlat failed.");
- return 0;
- }
/*
* Loop over the input.
result = 0;
this = TOKEN_NONE;
- for (p = buffer; *p != '\0'; /* nothing */) {
+ for (p = *string; *p != '\0'; /* nothing */) {
if ((*p == ' ') ||
(*p == '\t')) {
p++;
continue;
}
- if (*p == '+') {
- if (this != TOKEN_NONE) {
- DEBUG2("rlm_expr: Invalid operator at \"%s\"", p);
- return 0;
+ /*
+ * Discover which token it is.
+ */
+ found = FALSE;
+ for (i = 0; map[i].token != TOKEN_LAST; i++) {
+ if (*p == map[i].op) {
+ if (this != TOKEN_NONE) {
+ DEBUG2("rlm_expr: Invalid operator at \"%s\"", p);
+ return -1;
+ }
+ this = map[i].token;
+ p++;
+ found = TRUE;
+ break;
}
- this = TOKEN_ADD;
- p++;
- continue;
}
- if (*p == '-') {
- if (this != TOKEN_NONE) {
- DEBUG2("rlm_expr: Invalid operator at \"%s\"", p);
- return 0;
- }
- this = TOKEN_SUBTRACT;
- p++;
+ /*
+ * Found the algebraic operator. Get the next number.
+ */
+ if (found) {
continue;
}
/*
- * NOT a number: die!
+ * End of a group. Stop.
*/
- if ((*p < '0') || (*p > '9')) {
- DEBUG2("rlm_expr: Not a number at \"%s\"", p);
- return 0;
+ if (*p == ')') {
+ if (this != TOKEN_NONE) {
+ DEBUG2("rlm_expr: Trailing operator before end sub-expression at \"%s\"", p);
+ return -1;
+ }
+ p++;
+ break;
}
-
+
/*
- * This is doing it the hard way, but it also allows
- * us to increment 'p'.
+ * Start of a group. Call ourselves recursively.
*/
- x = 0;
- while ((*p >= '0') && (*p <= '9')) {
- x *= 10;
- x += (*p - '0');
+ if (*p == '(') {
p++;
+
+ found = get_number(request, &p, &x);
+ if (found < 0) {
+ return -1;
+ }
+ } else {
+ /*
+ * No algrebraic operator found, the next thing
+ * MUST be a number.
+ *
+ * If it isn't, then we die.
+ */
+ if ((*p < '0') || (*p > '9')) {
+ DEBUG2("rlm_expr: Not a number at \"%s\"", p);
+ return -1;
+ }
+
+ /*
+ * This is doing it the hard way, but it also allows
+ * us to increment 'p'.
+ */
+ x = 0;
+ while ((*p >= '0') && (*p <= '9')) {
+ x *= 10;
+ x += (*p - '0');
+ p++;
+ }
}
-
- DEBUG2("rlm_expr: %d %d\n", result, x);
switch (this) {
default:
case TOKEN_SUBTRACT:
result -= x;
break;
+
+ case TOKEN_DIVIDE:
+ result /= x;
+ break;
+
+ case TOKEN_REMAINDER:
+ result %= x;
+ break;
+
+ case TOKEN_MULTIPLY:
+ result *= x;
+ break;
+
+ case TOKEN_AND:
+ result &= x;
+ break;
+
+ case TOKEN_OR:
+ result |= x;
+ break;
}
/*
this = TOKEN_NONE;
}
+ /*
+ * And return the answer to the caller.
+ */
+ *string = p;
+ *answer = result;
+ return 0;
+}
+
+/*
+ * Do xlat of strings!
+ */
+static int expr_xlat(void *instance, REQUEST *request, char *fmt, char *out, int outlen,
+ RADIUS_ESCAPE_STRING func)
+{
+ int rcode, result;
+ rlm_expr_t *inst = instance;
+ const char *p;
+ char buffer[256];
+
+ /*
+ * Do an xlat on the provided string (nice recursive operation).
+ */
+ if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
+ radlog(L_ERR, "rlm_expr: xlat failed.");
+ return 0;
+ }
+
+ p = buffer;
+ rcode = get_number(request, &p, &result);
+ if (rcode < 0) {
+ return 0;
+ }
+
+ /*
+ * We MUST have eaten the entire input string.
+ */
+ if (*p != '\0') {
+ DEBUG2("rlm_expr: Failed at %s", p);
+ return 0;
+ }
+
snprintf(out, outlen, "%d", result);
return strlen(out);
}