Added configuration and documentation for the expression module,
authoraland <aland>
Tue, 1 Oct 2002 15:27:02 +0000 (15:27 +0000)
committeraland <aland>
Tue, 1 Oct 2002 15:27:02 +0000 (15:27 +0000)
and added more math support.

doc/rlm_expr [new file with mode: 0644]
raddb/radiusd.conf.in
src/modules/rlm_expr/rlm_expr.c

diff --git a/doc/rlm_expr b/doc/rlm_expr
new file mode 100644 (file)
index 0000000..66199c5
--- /dev/null
@@ -0,0 +1,55 @@
+       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$
index e1b5f91..07e4e4c 100644 (file)
@@ -688,8 +688,9 @@ modules {
        #       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"
@@ -983,6 +984,12 @@ modules {
                mpp = no
        }
 
+       #
+       #  The 'expression' module current has no configuration.
+       #
+       expr {
+       }
+
        # ANSI X9.9 token support.  Not included by default.
        # $INCLUDE  ${confdir}/x99.conf
 
@@ -1005,7 +1012,17 @@ modules {
 # 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),
index 40fbebc..bb91eeb 100644 (file)
@@ -46,28 +46,37 @@ typedef enum expr_token_t {
   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.
@@ -75,53 +84,82 @@ static int expr_xlat(void *instance, REQUEST *request, char *fmt, char *out, int
        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:
@@ -136,6 +174,26 @@ static int expr_xlat(void *instance, REQUEST *request, char *fmt, char *out, int
                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;
                }
 
                /*
@@ -144,6 +202,47 @@ static int expr_xlat(void *instance, REQUEST *request, char *fmt, char *out, int
                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);
 }