regex matching can be convinced to be TRUE if you're insistive enough?

Brian Candler B.Candler at
Fri May 27 10:17:57 CEST 2011

On Thu, May 26, 2011 at 02:17:35PM +0200, Alan DeKok wrote:
>   A better fix would be to be able to do loop over attributes, doing
> checks for each one.  But that's getting into the area of a real
> programming language...

But it would be really useful. Here is a real-life example.  I have a
database with Cisco-AVPair attributes of the form

    Cisco-AVPair = "ip:vrf-id=XXX"
    Cisco-AVPair = "ip:ip-unnumbered=YYY"
    Cisco-AVPair = "ip:addr-pool=ZZZ"

But when replying to certain older devices I need to rewrite them to the
obsolete form:

    Cisco-AVPair = "lcp:interface-config=ip vrf forwarding XXX\nip unnumbered YYY\npeer default ip address pool ZZZ"

I do this in unlang today, but it's ugly.

        update reply {
            Tmp-String-9 !* ""
        if ("%{reply:Cisco-AVPair[*]}" =~ /(^|\n)(ip:vrf-id=([^\n]*))/) {
            update reply {
                Tmp-String-9 += "ip vrf forwarding %{3}"
                Cisco-AVPair -= "%{2}"
        if ("%{reply:Cisco-AVPair[*]}" =~ /(^|\n)(ip:ip-unnumbered=([^\n]*))/) {
            update reply {
                Tmp-String-9 += "ip unnumbered %{3}"
                Cisco-AVPair -= "%{2}"
        if ("%{reply:Cisco-AVPair[*]}" =~ /(^|\n)(ip:addr-pool=([^\n]*))/) {
            update reply {
                Tmp-String-9 += "peer default ip address pool %{3}"
                Cisco-AVPair -= "%{2}"
        if ("%{reply:Tmp-String-9}") {
            update reply {
                Cisco-AVPair += "lcp:interface-config=%{reply:Tmp-String-9[*]}"
                Tmp-String-9 !* ""

The payoff is avoiding having to link in perl/ruby (whose thread-safety
worries me), or use a custom C module for rewriting.

Now in the above case, if Foo =~ /pattern/ iterated over the values of Foo
and stopped on the first match, that would be good enough for me.  But
actually that's not very general. Users might require:

   Foo =~ /pattern/    # first match only
   Foo =~ /pattern/    # process every match
   Foo =~ /pattern/    # only true if all values match
   Foo !~ /pattern/    # first one which doesn't match
   Foo !~ /pattern/    # process all which don't match
   Foo !~ /pattern/    # only true if no values match

A simple approach might be:

    if (any Foo =~ /pattern/) { ... }
    if (all Foo =~ /pattern/) { ... }

(but it's not clear how that interacts with string expansions, especially if
an expression refers to multiple attributes)

Else you have an explicit loop: e.g.

    foreach Foo {
        if (Foo =~ /pattern/) {
            last Foo   # early termination (if required)

Within the foreach Foo construct, Foo would expand to the n'th instance
instead of the 0'th instance. This would work with string expansions too,
but you'd still need to be able to specify the list to iterate on, e.g.

    foreach reply:Foo { ... }

Aside: I would also like to see direct support for accessing control and
reply lists without string expansions.  Then you could write

    if (reply:Service-Type == Framed-User)

(I don't think that's possible today). It would also make the unwieldy
'update' block obsolete:

    if (reply:Service-Type == Framed-User) {
        reply:Framed-Protocol = PPP

radius 3.x perhaps? :-)



P.S. One other thing for multi-valued attributes: Foo[*] always uses \n as
separator and adds \n to the end.  It would be useful to be able to join
with another character, e.g.  Foo[*,] - and be able to suppress the trailing

More information about the Freeradius-Devel mailing list