and update the docs and tests to match.
# Create links for default modules
for mod in always attr_filter cache_eap chap client \
delay detail detail.log dhcpv4 digest eap \
- eap_inner echo exec expr files linelog logintime \
+ eap_inner echo exec files linelog logintime \
mschap ntlm_auth pap pam passwd radutmp \
soh sradutmp stats unix unpack utf8 ; do
if test ! -h /etc/freeradius/mods-enabled/$mod && \
-
-
-
-
= Expr Module
-This module performs mathematical calculations:
-
-NOTE: This module is not called directly in any section, it is
-invoked through the dynamic expansion of strings.
-
-e.g:
-
- Attribute-Name = "%{expr:2 + 3 + &NAS-Port}"
-
-It supports the following operators (in order of precedence).
-
-[options="header,autowidth"]
-|===
-| Operator | Description
-| & | binary AND
-| \| | binary OR
-| << | left shift
-| >> | right shift
-| + | addition
-| - | subtraction
-| * | multiply
-| / | divide
-| %% | remainder
-| ^ | exponentiation
-| (...) | sub-expression
-|===
-
-Operator precedence follows the normal rules.
-Division by zero means that the entire expression is invalid.
-
-All calculations are done on signed 63-bit integers.
-e.g. `int64_t`. This should be sufficient for all normal
-purposes.
-
- * Hex numbers are supported: 0xabcdef
- * It also allows unary negation: -1
- * And twos complement: ~1
- * Otherwise numbers are decimal.
-
-As with all string expansions, you can nest the expansions:
-
- %{expr: %{NAS-Port} + 1}
- %{expr: %{sql:SELECT ... } + 1}
-
-Attribute references are supported for integer attributes.
-e.g. `&NAS-Port`. The benefit of using attribute references
-is that the expression is calculated directly on the
-attribute.
-
-
-
-## Configuration Settings
-
-This module takes no configuration.
-
-
-
-== Default Configuration
+The `expr` module has been remove, and replaced with an equivalent
+`%{expr:...}` in the xref:xlat/builtin.adoc[built-in expansions]. The
+parameters to the `%{expr:...}` is an
+xref:reference:unlang/expression.adoc[Unlang expression].
-```
-expr {
-}
-```
+Most people should see no change in functionality. However, the new
+expressions offer significantly more operators, and work on more data
+types.
*** xref:unlang/condition/index.adoc[Conditional Expressions]
**** xref:unlang/condition/cmp.adoc[Comparisons]
+**** xref:unlang/expressions.adoc[expressions]
**** xref:unlang/condition/operands.adoc[Operands]
**** xref:unlang/condition/return_code.adoc[The Return Code Operator]
**** xref:unlang/condition/eq.adoc[The '==' Operator]
*** xref:type/string/backticks.adoc[Backtick-quoted string]
*** xref:type/string/unquoted.adoc[Unquoted Strings]
-** xref:xlat/index.adoc[String Expansion]
+** xref:xlat/index.adoc[Dynamic Expansion]
*** xref:xlat/alternation.adoc[Alternation Syntax]
*** xref:xlat/builtin.adoc[Built-in Expansions]
*** xref:xlat/character.adoc[Single Letter Expansions]
* xref:type/index.adoc[Data Types] in the server
* xref:unlang/index.adoc[Unlang] syntax
-* xref:xlat/index.adoc[String expansions] i.e. "xlat"s.
+* xref:xlat/index.adoc[Dynamic expansions] i.e. "xlat"s.
The solution is to resolve these ambiguities by allowing the values to
be cast to a particular type. Casting a value to a type tells the
interpreter how that value should be parsed. Casting is done by
-prefixing a value with the type name, surrounded by angle brackets;
-`<...>`.
+prefixing a value with the type name, surrounded by brackets;
+`(...)`.
.Syntax
----
-<...>value
+(...)value
----
We can add a cast to the above example, as follows:
[source,unlang]
----
-if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == <ipaddr>192.0.2.1) }
+if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == (ipaddr)192.0.2.1) }
....
}
----
-In this example, we prefix the IP address with the string `<ipaddr>`.
+In this example, we prefix the IP address with the string `(ipaddr)`.
The interpreter then knows that the value `192.0.2.` should be
interpreted as the data type `ipaddr`, and not as the literal string
`"192.0.2."`.
the xref:unlang/type/all_types.adoc[list of data types] page, and the
"Basic Type Types" section.
+In most cases, the server can automatically determine what data type
+to use. The cast syntax is used when either the data type is
+ambiguous, or when data should be normalized prior to comparison, or
+when a specific data type is required.
+
+=== Compatibility
+
+For compatibility with version 3, the `<cast>` syntax is also
+supported. We recommend, however, that people use the new syntax.
+
// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// Development of this documentation was sponsored by Network RADIUS SAS.
run-time expansion. The difference between the two methods is that the
`${...}` form is expanded when the server loads the configuration
files and is valid anywhere in the configuration files. The `%{...}`
-xref:xlat/index.adoc[string expansion] form is valid only in conditional
+xref:xlat/index.adoc[dynamic expansion] form is valid only in conditional
expressions and attribute assignments.
The output of the dynamic expansion can be interpreted as a string,
.Syntax
[source,unlang]
----
-<cast>lhs OP rhs
+(cast)lhs OP rhs
----
-The `cast` text can be any one of the standard RADIUS dictionary data
+The `cast` text can be any one of the supported data
types, as with the following example:
.Example
[source,unlang]
----
-<ipaddr>&Class == 127.0.0.1
+(ipaddr)&Class == 127.0.0.1
----
In this example, the `Class` attribute is treated as if it was an IPv4
.Example
[source,unlang]
----
-<integer>`/bin/echo 00` == 0
+(integer)`/bin/echo 00` == 0
----
In this example, the string output of the `echo` program is interpreted as an
string equality checks, then the comparison would fail, because the
strings `00` and `0` are different.
+=== Compatibility
+
+For compatibility with version 3, the `<cast>` syntax is also
+supported. We recommend, however, that people use the new syntax.
+
// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// Development of this documentation was sponsored by Network RADIUS SAS.
pre-compiled, and if support is available, JIT'd (converted to machine code)
to ensure fast execution.
-If a pattern contains a xref:xlat/index.adoc[string expansion], the pattern
+If a pattern contains a xref:xlat/index.adoc[dynamic expansion], the pattern
cannot be compiled on startup, and will be compiled at runtime each time the
expression is evaluated. The server will also turn off JITing for runtime
compiled expressions, as the overhead is greater than the time that would be
====
To ensure optimal performance you should limit the number of patterns
-containing xref:xlat/index.adoc[string expansions], and if using PCRE, combine
+containing xref:xlat/index.adoc[dynamic expansions], and if using PCRE, combine
multiple expressions operating on the same subject into a single expression
using the PCRE alternation '|' operator.
-.Using multiple string expansions and the PCRE alternation operator
+.Using multiple dynamic expansions and the PCRE alternation operator
====
[source,unlang]
----
any other operator is used._
If the _<rhs>_ is an "in-place" list, then all of the
-xref:xlat/index.adoc[string expansions] are valid, just as are
+xref:xlat/index.adoc[dynamic expansions] are valid, just as are
xref:reference:unlang/attr.adoc[attribute references].
As a special case, the _<rhs>_ can also be a string, or a
-xref:xlat/index.adoc[string expansion]. If so, the string is
+xref:xlat/index.adoc[dynamic expansion]. If so, the string is
interpreted as a set of attribute definitions, as if it was an
"in-place" list. For example, `"Filter-Id = foo"`
The _<rhs>_ can be a reference to another attribute
(e.g. `request.Filter-Id`). If the field is a double-quoted string,
-it undergoes xref:xlat/index.adoc[string expansion], and the resulting
+it undergoes xref:xlat/index.adoc[dynamic expansion], and the resulting
value is processed as described above.
In most cases, the edit operations "do the right thing". For example,
--- /dev/null
+= Expressions
+
+Expressions can be used inside of xref:xlat/index.adoc[dynamic expansions], or inside of xref:condition/index.adoc[conditions].
+
+The following operators are supported (in order of precedence).
+
+[options="header,autowidth"]
+|===
+| Operator | Description
+| & | binary AND
+| \| | binary OR
+| << | left shift
+| >> | right shift
+| + | addition
+| - | subtraction
+| * | multiplication
+| / | division
+| %% | remainder TODO
+| ^ | xor
+| (...) | sub-expression
+|===
+
+The following unary operators are also supported:
+
+[options="header,autowidth"]
+|===
+| Operator | Description
+| - | unary minus
+| ~ | unary complement
+| ! | unary not
+|===
+
+Operator precedence follows the normal rules.
+Division by zero means that the entire expression is invalid.
+
+== Conditions
+
+xref:condition/index.adoc[Conditions] in expressions are also
+supported. For example:
+
+[source,unlang]
+----
+&NAS-Port = 5 + (&User-Name == "bob")
+----
+
+This expression will return `6` if the users name is `bob`, and `5` otherwise.
+
+Similarly, expressions are also supported in conditions. There is no
+need to use `%{expr:...}` in conditions, as was needed in earlier
+versions of the server.
+
+== Data Types
+
+The new expression parser accepts significantly more data types than
+the old `rlm_expr` module. The `rlm_expr` module assumed that all
+inputs were signed 64-bit integers. Any attempt to use other data
+types resulted in an error.
+
+For example, the `+` operator can be applied to `string` and `octet`
+data types. (And incidentally, `-` is the inverse of `+`!)
+
+[source,unlang]
+----
+&Reply-Message := "foo" + "bar"
+----
+
+Will result in `&Reply-Message == "foobar"`.
+
+The suffix can then be "subtracted" off, with:
+
+[source,unlang]
+----
+&Reply-Message -= "bar"
+----
+
+Will result in `&Reply-Message == "foo"` !.
+
+Other data types will generally yield results which make sense. For
+example:
+
+* adding an integer to an `ipv4prefix` type will result in an `ipv4addr` data type,
+* `&` and `|` will work on `string` and `octets` data types,
+* Using `&` with `ipv4addr` data types an `uint32` will result in an `ipv4prefix` data type,
+* `ipv4addr`s can be subtracted, and will return a number,
+* `date`s can be subtracted, and will return a `time_delta`,
+* operations on integers are upgraded to the next largest integer size when necessary,
+* the logical operators `&&` and `||` return the value which caused them to succeed, e.g. `&Foo := (&User-Password || "help")` will return the contents of `&User-Name` if it exists, otherwise it will return the string `help`.
+
+In most cases, the data types are derived from the attribute
+dictionaries. However, it is sometimes necessary to force the fields
+(or output) of an expression to be parsed in a particular manner.
+
+== Casts
+
+xref:type/index.adoc[Type casting] is supported via the `(type)`
+syntax. The old-style syntax of `<type>` is accepted, but is
+deprecated.
+
+[source,unlang]
+----
+&NAS-Port-Id = (uint32) "%{sql: SELECT...}" + 4
+----
+
+== Errors
+
+Mathematical operations which cause overflow, underflow, or division
+by zero will return a `null` result. This result will propagate
+through any calculations, so that an expression which relies on `null`
+will also return `null`.
+
}
----
-== String Expansions
+== Dynamic Expansions
-xref:xlat/index.adoc[String expansion] Using `%{...}` to perform dynamic
-string expansions. (also known as xref:xlat/index.adoc[xlat])
+xref:xlat/index.adoc[Dynamic expansion] Using `%{...}` to perform dynamic
+expansions. (also known as xref:xlat/index.adoc[xlat])
-String expansions are usually performed in order to get additional
+Dynamic expansions are usually performed in order to get additional
information which is not immediately available to the policy. This
information can be taken from almost any source, including other
attributes, databases, and scripts.
....
====
+=== +%(eval:<string>)+
+
+Evaluates the string as an expansion, and returns the result. The main difference between using this expansion and just using `%{...}` is that the string being evaluated can be dynamically changed.
+
+.Return: _data_
+
+.Example:
+
+[source,unlang]
+----
+if (&User-Name == "bob") {
+ update request {
+ &Tmp-String-0 := "&User-Name"
+ }
+} else {
+ update request {
+ &Tmp-String-0 := "not bob!"
+ }
+}
+
+update reply {
+ &Reply-Message := "%{eval:&Tmp-String-0}"
+}
+
+----
+
+.Output when `&User-Name == bob`
+
+```
+bob
+```
+
+.Output when `&User-Name == not bob`
+
+```
+not bob!
+```
+
+
+=== +%(expr:<string>)+
+
+Evaluates the string as an xref:reference:unlang/expression.adoc[Unlang expression], and returns the result. Please see the
+xref:reference:unlang/expression.adoc[Unlang expression] page for full
+documentation on expressions.
+
+.Return: _data_
+
+.Example:
+
+[source,unlang]
+----
+update reply
+ &Tmp-String-0 := "%{expr: 1 + 2}"
+ }
+}
+
+----
+
+.Output
+
+```
+3
+```
+
=== +%(nexttime:<time>)+
Calculate number of seconds until next n hour(`s`), day(`s`), week(`s`), year(`s`).
-= String Expansion
+= Dynamic Expansion
-String expansion is a feature that allows strings to dynamically
-define their value at run time. For historical reasons, these string
-expansions are called "xlats".
+Dynamic expansion is a feature that allows values to be dynamically
+expanded at run time. For historical reasons, these string expansions
+are called "xlats".
-String expansion is performed via the following syntax:
+Dynamioc expansion is performed via the following syntax:
`%{...}`
signals the end of the dynamic expansion. The contents of the
expansion can be many things:
-.String Expansions
+.Types of Expansions
[options="header"]
|=====
| Keyword | Description
`%{User-Name}`
-This string expansion is done only for double-quoted strings and for
+This expansion is done only for double-quoted strings and for
the back-tick operator.
== Caveats
Unlike other languages, there is no way to define new variables. All
-of the string expansions must refer to attributes that already exist,
+of the expansions must refer to attributes that already exist,
or to modules that will return a string value.
== Character Escaping
*`man` page:* `radiusd.conf`
-*documentation page:* xref:reference:unlang/xlat/index.adoc[String expansions]
+*documentation page:* xref:reference:unlang/xlat/index.adoc[Dynamic expansions]
There are two kinds of variables within the server. The first is within
`radiusd.conf` and related files. These variables are referenced via the
DEFAULT_MODULES := always attr_filter cache_eap chap client \
delay detail detail.log digest eap \
- eap_inner echo escape exec expr files linelog logintime \
+ eap_inner echo escape exec files linelog logintime \
mschap ntlm_auth pap passwd radutmp \
soh sradutmp stats unix unpack utf8
+++ /dev/null
-# -*- text -*-
-#
-#
-# $Id$
-
-#######################################################################
-#
-# = Expr Module
-#
-# This module performs mathematical calculations:
-#
-# NOTE: This module is not called directly in any section, it is
-# invoked through the dynamic expansion of strings.
-#
-# e.g:
-#
-# Attribute-Name = "%{expr:2 + 3 + &NAS-Port}"
-#
-# It supports the following operators (in order of precedence).
-#
-# [options="header,autowidth"]
-# |===
-# | Operator | Description
-# | & | binary AND
-# | \| | binary OR
-# | << | left shift
-# | >> | right shift
-# | + | addition
-# | - | subtraction
-# | * | multiply
-# | / | divide
-# | %% | remainder
-# | ^ | exponentiation
-# | (...) | sub-expression
-# |===
-#
-# Operator precedence follows the normal rules.
-# Division by zero means that the entire expression is invalid.
-#
-# All calculations are done on signed 63-bit integers.
-# e.g. `int64_t`. This should be sufficient for all normal
-# purposes.
-#
-# * Hex numbers are supported: 0xabcdef
-# * It also allows unary negation: -1
-# * And twos complement: ~1
-# * Otherwise numbers are decimal.
-#
-# As with all string expansions, you can nest the expansions:
-#
-# %{expr: %{NAS-Port} + 1}
-# %{expr: %{sql:SELECT ... } + 1}
-#
-# Attribute references are supported for integer attributes.
-# e.g. `&NAS-Port`. The benefit of using attribute references
-# is that the expression is calculated directly on the
-# attribute.
-#
-
-#
-# ## Configuration Settings
-#
-# This module takes no configuration.
-#
-expr {
-
-}
* Parse the input as a literal expansion
*/
if (xlat_tokenize_ephemeral(rctx,
+ &rctx->ex, unlang_interpret_event_list(request),
+ &FR_SBUFF_IN(arg->vb_strvalue, arg->vb_length),
+ &(fr_sbuff_parse_rules_t){
+ .escapes = &escape_rules
+ },
+ &(tmpl_rules_t){
+ .attr = {
+ .allow_unknown = false,
+ .allow_unresolved = false,
+ .allow_foreign = false,
+ .dict_def = request->dict
+ },
+ .at_runtime = true
+ }) < 0) {
+ RPEDEBUG("Failed parsing expansion");
+ error:
+ talloc_free(rctx);
+ return XLAT_ACTION_FAIL;
+ }
+
+ /*
+ * Call the resolution function so we produce
+ * good errors about what function was
+ * unresolved.
+ */
+ if (rctx->ex->flags.needs_resolving &&
+ (xlat_resolve(rctx->ex, &(xlat_res_rules_t){ .allow_unresolved = false }) < 0)) {
+ RPEDEBUG("Unresolved expansion functions in expansion");
+ goto error;
+
+ }
+
+ if (unlang_xlat_yield(request, xlat_eval_resume, NULL, rctx) != XLAT_ACTION_YIELD) goto error;
+
+ if (unlang_xlat_push(ctx, &rctx->last_success, out->dlist,
+ request, rctx->ex, UNLANG_SUB_FRAME) < 0) goto error;
+
+ return XLAT_ACTION_PUSH_UNLANG;
+}
+
+/** Dynamically evaluate an expression string
+ *
+ * @ingroup xlat_functions
+ */
+static xlat_action_t xlat_func_expr(TALLOC_CTX *ctx, fr_dcursor_t *out,
+ UNUSED xlat_ctx_t const *xctx,
+ request_t *request, fr_value_box_list_t *in)
+{
+ xlat_eval_rctx_t *rctx;
+ fr_value_box_t *arg = fr_dlist_head(in);
+
+ /*
+ * These are escaping rules applied to the
+ * input string. They're mostly here to
+ * allow \% and \\ to work.
+ *
+ * Everything else should be passed in as
+ * unescaped data.
+ */
+ static fr_sbuff_unescape_rules_t const escape_rules = {
+ .name = "xlat",
+ .chr = '\\',
+ .subs = {
+ ['%'] = '%',
+ ['\\'] = '\\',
+ },
+ .do_hex = false,
+ .do_oct = false
+ };
+
+ MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), xlat_eval_rctx_t));
+
+ /*
+ * Parse the input as an expression.
+ */
+ if (xlat_tokenize_ephemeral_expression(rctx,
&rctx->ex, unlang_interpret_event_list(request),
&FR_SBUFF_IN(arg->vb_strvalue, arg->vb_length),
&(fr_sbuff_parse_rules_t){
XLAT_REGISTER_MONO("urlquote", xlat_func_urlquote, xlat_func_urlquote_arg);
XLAT_REGISTER_MONO("urlunquote", xlat_func_urlunquote, xlat_func_urlunquote_arg);
XLAT_REGISTER_MONO("eval", xlat_func_eval, xlat_func_eval_arg);
+ XLAT_REGISTER_MONO("expr", xlat_func_expr, xlat_func_eval_arg);
#undef XLAT_REGISTER_MONO
#define XLAT_REGISTER_MONO(_xlat, _func, _arg) \
+++ /dev/null
-# rlm_expr
-## Metadata
-<dl>
- <dt>category</dt><dd>policy</dd>
-</dl>
-
-## Summary
-Registers a string expansion "%{expr:}" that allows basic arithmetic and binary operations.
+++ /dev/null
-TARGETNAME := rlm_expr
-
-TARGET := $(TARGETNAME)$(L)
-SOURCES := $(TARGETNAME).c
-
-LOG_ID_LIB = 18
+++ /dev/null
-/*
- * This program is 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- * @file rlm_expr.c
- * @brief Register an xlat expansion to perform basic mathematical operations.
- *
- * @copyright 2001,2006 The FreeRADIUS server project
- * @copyright 2002 Alan DeKok (aland@freeradius.org)
- */
-RCSID("$Id$")
-USES_APPLE_DEPRECATED_API
-
-#include <freeradius-devel/server/base.h>
-#include <freeradius-devel/server/module_rlm.h>
-#include <freeradius-devel/server/tmpl_dcursor.h>
-#include <freeradius-devel/util/debug.h>
-
-#include <ctype.h>
-
-#include "rlm_expr.h"
-
-/** Calculate powers
- *
- * @author Orson Peters
- * @note Borrowed from the gist here: https://gist.github.com/nightcracker/3551590.
- *
- * @param base a 32bit signed integer.
- * @param exp amount to raise base by.
- * @return base ^ pow, or 0 on underflow/overflow.
- */
-static int64_t fr_pow(int64_t base, int64_t exp)
-{
- static const uint8_t highest_bit_set[] = {
- 0, 1, 2, 2, 3, 3, 3, 3,
- 4, 4, 4, 4, 4, 4, 4, 4,
- 5, 5, 5, 5, 5, 5, 5, 5,
- 5, 5, 5, 5, 5, 5, 5, 5,
- 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6 // anything past 63 is a guaranteed overflow with base > 1
- };
-
- int64_t result = 1;
-
- if (exp > 63) {
- if (base == 1) return 1;
- if (base == -1) return 1 - 2 * (exp & 1);
- return 0; /* overflow */
- }
-
- switch (highest_bit_set[exp]) {
- case 6:
- if (exp & 1) result *= base;
- exp >>= 1;
- base *= base;
- FALL_THROUGH;
- case 5:
- if (exp & 1) result *= base;
- exp >>= 1;
- base *= base;
- FALL_THROUGH;
- case 4:
- if (exp & 1) result *= base;
- exp >>= 1;
- base *= base;
- FALL_THROUGH;
- case 3:
- if (exp & 1) result *= base;
- exp >>= 1;
- base *= base;
- FALL_THROUGH;
- case 2:
- if (exp & 1) result *= base;
- exp >>= 1;
- base *= base;
- FALL_THROUGH;
- case 1:
- if (exp & 1) result *= base;
- FALL_THROUGH;
- default:
- return result;
- }
-}
-
-/*
- * Start of expression calculator.
- */
-typedef enum expr_token_t {
- TOKEN_NONE = 0,
- TOKEN_INTEGER,
-
- TOKEN_AND,
- TOKEN_OR,
-
- TOKEN_LSHIFT,
- TOKEN_RSHIFT,
-
- TOKEN_ADD,
- TOKEN_SUBTRACT,
-
- TOKEN_DIVIDE,
- TOKEN_REMAINDER,
- TOKEN_MULTIPLY,
-
- TOKEN_POWER,
- TOKEN_LAST
-} expr_token_t;
-
-static int precedence[TOKEN_LAST + 1] = {
- 0, 0, 1, 1, /* and or */
- 2, 2, 3, 3, /* shift add */
- 4, 4, 4, 5, /* mul, pow */
- 0
-};
-
-typedef struct {
- 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 },
- {'^', TOKEN_POWER },
- {0, TOKEN_LAST}
-};
-
-static bool get_expression(request_t *request, char const **string, int64_t *answer, expr_token_t prev);
-
-static bool get_number(request_t *request, char const **string, int64_t *answer)
-{
- fr_sbuff_term_t const bareword_terminals =
- FR_SBUFF_TERMS(
- L("\t"),
- L("\n"),
- L(" "),
- L("%"),
- L("&"),
- L(")"),
- L("+"),
- L("-"),
- L("/"),
- L("^"),
- L("|")
- );
- fr_sbuff_parse_rules_t const p_rules = { .terminals = &bareword_terminals };
- int64_t x;
- bool invert = false;
- bool negative = false;
- char const *p = *string;
- tmpl_t *vpt = NULL;
-
- /*
- * Look for a number.
- */
- fr_skip_whitespace(p);
-
- /*
- * ~1 == 0xff...ffe
- */
- if (*p == '~') {
- invert = true;
- p++;
- }
-
- /*
- * No algrebraic operator found, the next thing
- * MUST be a number.
- *
- * If it isn't, then we die.
- */
- if ((*p == '0') && (p[1] == 'x')) {
- char *end;
-
- x = strtoul(p, &end, 16);
- p = end;
- goto done;
- }
-
- if (*p == '-') {
- negative = true;
- p++;
- }
-
- /*
- * Look for an attribute.
- */
- if (*p == '&') {
- int i, max, err;
- ssize_t slen;
- fr_pair_t *vp;
- fr_dcursor_t cursor;
- tmpl_dcursor_ctx_t cc;
-
- slen = tmpl_afrom_attr_substr(request, NULL, &vpt,
- &FR_SBUFF_IN(p, strlen(p)),
- &p_rules,
- &(tmpl_rules_t){
- .attr = {
- .dict_def = request->dict
- }
- });
- if (slen <= 0) {
- RPEDEBUG("Failed parsing attribute name '%s'", p);
- return false;
- }
-
- p += slen;
-
- if (tmpl_num(vpt) == NUM_COUNT) {
- REDEBUG("Attribute count is not supported");
- return false;
- }
-
- if (tmpl_num(vpt) == NUM_ALL) {
- max = 65535;
- } else {
- max = 1;
- }
-
- x = 0;
- for (i = 0, vp = tmpl_dcursor_init(&err, NULL, &cc, &cursor, request, vpt);
- (i < max) && (vp != NULL);
- i++, vp = fr_dcursor_next(&cursor)) {
- int64_t y;
- fr_value_box_t value;
-
- switch (vp->vp_type) {
- case FR_TYPE_UINT64:
- if (vp->vp_uint64 > INT64_MAX) {
- /*
- * So we can print out the correct value
- * in the overflow error message.
- */
- fr_value_box_copy(NULL, &value, &vp->data);
- goto overflow;
- }
- y = (int64_t)vp->vp_uint64;
- break;
-
- case FR_TYPE_INT64:
- y = vp->vp_int64;
- break;
-
- /*
- * Scale the time_delta to whatever precision is defined by the
- * attribute. This is generally what the user expects. i.e. if
- * the attribute says it's in milliseconds, then print out an
- * integer number of milliseconds
- */
- case FR_TYPE_TIME_DELTA:
- y = fr_time_delta_to_integer(vp->vp_time_delta, vp->data.enumv ? vp->data.enumv->flags.flag_time_res : FR_TIME_RES_SEC);
- break;
-
- case FR_TYPE_DATE:
- /*
- * This is wrong... we should really do scaling, but internally
- * we have vp_date as fr_unix_time_t, and not as fr_time_t.
- *
- * Perhaps a better solution is to simply have the dictionaries
- * disallow any precision for the 'date' data type.
- */
- y = fr_unix_time_to_sec(vp->vp_date);
- break;
-
- case FR_TYPE_STRUCTURAL:
- REDEBUG("Cannot convert %s of type '%s' to integer",
- vp->da->name, fr_type_to_str(vp->vp_type));
- goto error;
-
- default:
- if (fr_value_box_cast(vp, &value, FR_TYPE_UINT64, NULL, &vp->data) < 0) {
- RPEDEBUG("Failed converting &%.*s to an integer value", (int) vpt->len,
- vpt->name);
- error:
- tmpl_dursor_clear(&cc);
- return false;
- }
- if (value.vb_uint64 > INT64_MAX) {
- overflow:
- talloc_free(vpt);
- REDEBUG("Value of &%.*s (%pV) would overflow a signed 64bit integer "
- "(our internal arithmetic type)", (int)vpt->len, vpt->name, &value);
- goto error;
- }
- y = (int64_t)value.vb_uint64;
-
- RINDENT();
- RDEBUG3("&%.*s --> %" PRIu64, (int)vpt->len, vpt->name, y);
- REXDENT();
- break;
- }
-
- /*
- * Check for overflow without actually overflowing.
- */
- if ((y > 0) && (x > (int64_t) INT64_MAX - y)) goto overflow;
-
- if ((y < 0) && (x < (int64_t) INT64_MIN - y)) goto overflow;
-
- x += y;
- } /* loop over all found VPs */
- tmpl_dursor_clear(&cc);
-
- if (err != 0) {
- RWDEBUG("Can't find %.*s. Using 0 as operand value", (int)vpt->len, vpt->name);
- goto done;
- }
-
- goto done;
- }
-
- /*
- * Do brackets recursively
- */
- if (*p == '(') {
- p++;
- if (!get_expression(request, &p, &x, TOKEN_NONE)) return false;
-
- if (*p != ')') {
- REDEBUG("No trailing ')'");
- return false;
- }
- p++;
- goto done;
- }
-
- if ((*p < '0') || (*p > '9')) {
- REDEBUG("Not a number at \"%s\"", p);
- return false;
- }
-
- /*
- * 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++;
- }
-
-done:
- if (vpt) talloc_free(vpt);
-
- if (invert) x = ~x;
-
- if (negative) x = -x;
-
- *string = p;
- *answer = x;
- return true;
-}
-
-static bool calc_result(request_t *request, int64_t lhs, expr_token_t op, int64_t rhs, int64_t *answer)
-{
- switch (op) {
- default:
- case TOKEN_SUBTRACT:
- rhs = -rhs;
- FALL_THROUGH;
-
- case TOKEN_ADD:
- if ((rhs > 0) && (lhs > (int64_t) INT64_MAX - rhs)) {
- overflow:
- REDEBUG("Numerical overflow in expression!");
- return false;
- }
-
- if ((rhs < 0) && (lhs < (int64_t) INT64_MIN - rhs)) goto overflow;
-
- *answer = lhs + rhs;
- break;
-
- case TOKEN_DIVIDE:
- if (rhs == 0) {
- REDEBUG("Division by zero in expression!");
- return false;
- }
-
- *answer = lhs / rhs;
- break;
-
- case TOKEN_REMAINDER:
- if (rhs == 0) {
- REDEBUG("Division by zero!");
- return false;
- }
-
- *answer = lhs % rhs;
- break;
-
- case TOKEN_MULTIPLY:
- *answer = lhs * rhs;
- break;
-
- case TOKEN_LSHIFT:
- if (rhs > 62) {
- REDEBUG("Shift must be less than 62 (was %lld)", (long long int) rhs);
- return false;
- }
-
- *answer = lhs << rhs;
- break;
-
- case TOKEN_RSHIFT:
- if (rhs > 62) {
- REDEBUG("Shift must be less than 62 (was %lld)", (long long int) rhs);
- return false;
- }
-
- *answer = lhs >> rhs;
- break;
-
- case TOKEN_AND:
- *answer = lhs & rhs;
- break;
-
- case TOKEN_OR:
- *answer = lhs | rhs;
- break;
-
- case TOKEN_POWER:
- if (rhs > 63) {
- REDEBUG("Exponent must be between 0-63 (was %lld)", (long long int) rhs);
- return false;
- }
-
- if (lhs > 65535) {
- REDEBUG("Base must be between 0-65535 (was %lld)", (long long int) lhs);
- return false;
- }
-
- *answer = fr_pow(lhs, rhs);
- break;
- }
-
- return true;
-}
-
-static bool get_operator(request_t *request, char const **string, expr_token_t *op)
-{
- int i;
- char const *p = *string;
-
- /*
- * All tokens are one character.
- */
- for (i = 0; map[i].token != TOKEN_LAST; i++) {
- if (*p == map[i].op) {
- *op = map[i].token;
- *string = p + 1;
- return true;
- }
- }
-
- if ((p[0] == '<') && (p[1] == '<')) {
- *op = TOKEN_LSHIFT;
- *string = p + 2;
- return true;
- }
-
- if ((p[0] == '>') && (p[1] == '>')) {
- *op = TOKEN_RSHIFT;
- *string = p + 2;
- return true;
- }
-
- REDEBUG("Expected operator at \"%s\"", p);
-
- return false;
-}
-
-
-static bool get_expression(request_t *request, char const **string, int64_t *answer, expr_token_t prev)
-{
- int64_t lhs, rhs;
- char const *p, *op_p;
- expr_token_t this;
-
- p = *string;
-
- if (!get_number(request, &p, &lhs)) return false;
-
-redo:
- fr_skip_whitespace(p);
-
- /*
- * A number by itself is OK.
- */
- if (!*p || (*p == ')')) {
- *answer = lhs;
- *string = p;
- return true;
- }
-
- /*
- * Peek at the operator.
- */
- op_p = p;
- if (!get_operator(request, &p, &this)) return false;
-
- /*
- * a + b + c ... = (a + b) + c ...
- * a * b + c ... = (a * b) + c ...
- *
- * Feed the current number to the caller, who will take
- * care of continuing.
- */
- if (precedence[this] <= precedence[prev]) {
- *answer = lhs;
- *string = op_p;
- return true;
- }
-
- /*
- * a + b * c ... = a + (b * c) ...
- */
- if (!get_expression(request, &p, &rhs, this)) return false;
-
- if (!calc_result(request, lhs, this, rhs, answer)) return false;
-
- /*
- * There may be more to calculate. The answer we
- * calculated here is now the LHS of the lower priority
- * operation which follows the current expression. e.g.
- *
- * a * b + c ... = (a * b) + c ...
- * = d + c ...
- */
- lhs = *answer;
- goto redo;
-}
-
-static xlat_arg_parser_t const expr_xlat_arg = {
- .required = true, .concat = true, .type = FR_TYPE_STRING
-};
-
-/** Xlat expressions
- *
- * Example (NAS-Port = 1):
-@verbatim
-"%{expr:2 + 3 + &NAS-Port}" == 6
-@endverbatim
- *
- * @ingroup xlat_functions
- */
-static xlat_action_t expr_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
- UNUSED xlat_ctx_t const *xctx,
- request_t *request, fr_value_box_list_t *in)
-{
- int64_t result;
- fr_value_box_t *arg = fr_dlist_head(in);
- char const *p = arg->vb_strvalue;
- fr_value_box_t *vb;
-
- if (!get_expression(request, &p, &result, TOKEN_NONE)) return XLAT_ACTION_FAIL;
-
- if (*p) {
- REDEBUG("Invalid text after expression: %s", p);
- return XLAT_ACTION_FAIL;
- }
-
- MEM(vb = fr_value_box_alloc_null(ctx));
- fr_value_box_int64(vb, NULL, result, false);
- fr_dcursor_append(out, vb);
- return XLAT_ACTION_DONE;
-}
-
-/*
- * 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 mod_bootstrap(module_inst_ctx_t const *mctx)
-{
- xlat_t *xlat;
-
- xlat = xlat_register_module(mctx->inst->data, mctx, mctx->inst->name, expr_xlat, NULL);
- xlat_func_mono(xlat, &expr_xlat_arg);
-
- return 0;
-}
-
-/*
- * 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 MODULE_TYPE_THREAD_UNSAFE.
- * The server will then take care of ensuring that the module
- * is single-threaded.
- */
-extern module_rlm_t rlm_expr;
-module_rlm_t rlm_expr = {
- .common = {
- .magic = MODULE_MAGIC_INIT,
- .name = "expr",
- .bootstrap = mod_bootstrap
- }
-};
+++ /dev/null
-#pragma once
-/*
- * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- *
- * @copyright 2007 The FreeRADIUS server project
- * @copyright 2007 Alan DeKok (aland@freeradius.org)
- */
-RCSIDH(rlm_expr_h, "$Id$")
-
-void pair_builtincompare_add(void *instance);
$INCLUDE ${raddb}/mods-enabled/chap
- $INCLUDE ${raddb}/mods-enabled/expr
-
$INCLUDE ${raddb}/mods-enabled/digest
}
modules {
$INCLUDE ${raddb}/mods-enabled/always
- $INCLUDE ${raddb}/mods-enabled/expr
-
$INCLUDE ${raddb}/mods-enabled/escape
delay reschedule {
modules {
$INCLUDE ${raddb}/mods-enabled/always
- $INCLUDE ${raddb}/mods-enabled/expr
-
$INCLUDE ${raddb}/mods-enabled/escape
delay reschedule {
$INCLUDE ${raddb}/mods-enabled/pap
- $INCLUDE ${raddb}/mods-enabled/expr
-
$INCLUDE ${raddb}/mods-enabled/escape
delay reschedule {
$INCLUDE ${raddb}/mods-enabled/pap
- $INCLUDE ${raddb}/mods-enabled/expr
-
$INCLUDE $ENV{MODULE_TEST_DIR}/module.conf
}
$INCLUDE ${raddb}/mods-enabled/always
$INCLUDE ${raddb}/mods-enabled/pap
-
- $INCLUDE ${raddb}/mods-enabled/expr
}
server default {