Upgrading from v3 to v4
The configuration for v4 is somewhat compatible with the v3 configuration. It should be possible to reuse most of a v3 reconfiguration with minor tweaks.
This file describes the differences between v3 and v4. It does not contain a step-by-step process for upgrading the server. |
In general, we have the following changes:
-
most module configuration is very close to v3.
-
most of the
unlang
processing is very close to v3. -
update
is now replaced just by editing the attribute in place. -
each
server
section need anamespace
parameter. -
Packet processing sections are now
recv Access-Request
, etc. Notauthorize
, etc. -
each
listen
section needs to be converted to the v4 format.
Upgrading from older versions
Upgrading from v3
When upgrading, please start with the default configuration of v4. Then,
move your v3 configuration over, one module at a time. Check this file
for differences in module configuration, and update the module to use
the new configuration. Start the server after every change via
radiusd -XC
to see if the configuration is OK. Then, convert the
listen
sections, followed by the server
sections.
Take your time. It is better to make small incremental progress, than
to make massive changes, and then to spend weeks debugging it. Use a
revision control system such as git
to save and track your changes.
If the changes work as you expect, do git commit
, and continue with
the next change. If the changes do not work, either keep at it, or
move to a different portion of the configuration
All of the attribute names used in v3 have been changed in v4. Please see the attribute names document for more information. This change was necessary in order to support the new "grouped" attributes, which are required for DHCPv6 and other protocols.
Attribute Names
Much of the information in this section is also in the
raddb/dictionary
file
All of the attributes have been renamed from v3. This change was
necessary in order to support new funtionality in v4. The
unfortunate side effect of this change is that all of the names in
SQL, LDAP, and the files
module are incompatible with v4.
We recognize that is is difficult to change every entry in a database, especially when there’s no clear mapping between the "old" and "new" names. This renaming is made more complex because the "new" names need to be grouped and arranged in ways that the old ones were not.
The "old" names were all in flat lists, so that User-Name
appeared
next to Cisco-AVPAir
. This organization was simple enough to work
for 20 years, but its time has come. The new names are hierarchical,
and are nested by definition.
For v4, the Cisco-AVPair
attribute is called AVPair
, and it lives
inside of the Cisco
namespace, which in turn lives inside of the
Vendor-Specific
namespace. So the old Cisco-AVPair
attribute is
now named Vendor-Specific.Cisco.AVPair
.
This renaming process continues for many thousands of vendor-specific attributes.
Happily, it is possible to (mostly) use the old names with v4. There are limitations, but it will mostly work. The main reason for enabling the old names is to try out v4 with a database which is also used by v3. This lets you test that v4 works, without going through a complex "upgrade everything" process.
The old v3 names are in "alias" dictionaries, in the
${dictdir}/alias/
directory. To find out where this directory is on
your local system, run "radiusd -h" or "radclient -h". Then look for
the "-D" command-line option, and it will tell you where the
dictionary files are located.
The v3 names are in a file named ${dictdir}/radius/alias/VENDOR.txt
where
VENDOR
is the name of the vendor, which is taken from the VENDOR
definition in the v3 dictionaries.
You will need to add a $INCLUDE
line for each vendor-specific
dictionary which is used by your local system. The default v4
dictionaries do not enable all of v3 compatibilty names. The reason
is simple: the alias names mostly work, in most situations. But
there are situations where the aliases do not behave correctly.
We recognize that this process is a bit of work. However, we wish to encourage everyone using v4 to upgrade to using the new v4 features. Our experience shows that if we automatically enable "compatibility features", then those compatiblity features will be used for a decade, and no one will upgrade to use the new features. So we need to find a balance between upgrades and ongoing support. Easy upgrades will mean complex ongoing support. Complex upgrades make ongoing support easier, but also make it less likely that people will upgrade.
radiusd.conf
The following configurations have been removed. See the new listen
sections for their replacements.
v3 | v4 |
---|---|
|
replaced with |
|
see |
|
see |
The log
section has been updated to remove many configuration items
which are specific to RADIUS, and to Access-Request packets. Please see
sites-available/default
, and look for the Access-Request
subsection there. See also templates.conf
for a way to regain one global configuration for Access-Request
packets.
Instantiate Section
The instantiate
section has been removed. It originally started out
as a way to ensure that modules were instantiated in a particular
order. A later use of the instantiate
section was to define
"virtual modules" for dynamic expansion. That functionality has been
moved to the mods-available/
and mods-enabled/
directories.
i.e. in version 4, just list the virtual module in a file, as if it
was a real module.
See the redundant_sql module
for more information. In short, a virtual module can be put into
the mods-enabled
directory as with any other module, as with the
following text:
redundant redundant_sql {
sql1
sql2
}
In this case, this definition creates a redundant_sql
virtual module.
Virtual Servers
There are some changes to the virtual servers in v4
. First, every
virtual server has to begin with an entry:
namespace = ...
For RADIUS, use:
namespace = radius
This tells the server what protocol is being used in that virtual server. This configuration was not necessary in v3, because each protocol was pretending to be RADIUS. That was simple to program at the time and worked for some things, but it was not the best approach.
In v4, each protocol is completely independent, and RADIUS is no longer built into the server core. i.e. The server core does modules, configuration files, policies, etc. RADIUS has been relegated to just another plug-in protocol, with the same status as DHCPv4 and DHCPv6.
Every example virtual server in the sites-enabled/
directory
contains a namespace
parameter. Please look at those files for
examples of configuring and running each supported protocol.
Listen Sections
The listen
sections have changed. There is now a type
entry, which
lists the packet type by their correct name (e.g._`Access-Request`
instead of auth
). To accept multiple kinds of packets, just list
type
multiple times:
type = Access-Request
type = Accounting-Request
Each listen
section also has a transport
entry. This
configuration can be left out for headless
servers, such as
inner-tunnel
. For example, setting UDP transport is done via:
transport = udp
Each type of transport has its configuration stored in a subsection named for that transport:
transport = udp
udp {
... udp transport configuration ...
}
For udp
, the configuration entries are largely the same as for v3.
e.g. ipaddr
, port
, etc.
The listen
section then compiles each
Processing Section based on the named packet
types. It has a recv
section for receiving packets, and a send
section for sending packets, as seen in the following example:
recv Access-Request {
... unlang ...
}
send Access-Accept {
... unlang ...
}
This configuration is different from v3. The benefit of the change is
that it is much easier to understand. Instead of using confusing names
such as Post-Auth-Type Reject
, the server now just uses send
Access-Reject
.
See also Processing Section for how the
unlang
statements are parsed.
Clients
The server supports global clients in the clients.conf
file, as with
v3.
Client can also be defined in a client
subsection of a virtual
server. Unlike v3
, there is no need to have a clients
section
which "wraps" one or more client
definitions. See
sites-available/default
for examples.
The server also supports dynamic clients. See
sites-available/dynamic_clients
for a worked example. There are many
changes from v3. First, there is no need to have a client
definition
which contains a network. Instead, there is a network
section which
has a number of allow
and deny
rules. Second, dynamic clients
can be defined on a per-connection basis. Finally, the
sites-available/dynamic_clients
virtual server has full access to
the entire RADIUS packet.
The result of these changes is that it is now possible to have multiple
clients behind a NAT gateway, and to have different shared secrets for
each client. e.g._by keying off of the NAS-Identifier
attribute.
The dynamic client functionality behaves the same for all protocols supported by the server. e.g. RADIUS, DHCP, VMPS, TACACS+, etc.
Processing Sections
All of the processing sections have been renamed. Sorry, but this was required for the new features in v4.
Old Name | New Name |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i.e. instead of the section names being (mostly) randomly named, the
names are now consistent. The recv
sections receive packets from the
network. The send
sections send packets back to the network. The
second name of the section is the type of the packet that is being
received or sent.
For accounting, packets are also processed through an
accounting section named after Acct-Status-Type. This process is
similar to authenticate for Access-Request packets. The goal
here is to allow a common pre-processing of accounting packets in the
recv Accounting-Request packet, followed by type-specific processing
in accounting %{Acct-Status-Type} . See sites-available/default for
examples and more information.
|
update sections
A major difference between v3 and v4 is that update
sections are no
longer necessary. It is now possible to just edit attributes "in
place", as with:
See the update documentation for a description of what has changed, and how to use the attribute new edit functionality.
For example, instead of doing this:
if (&User-Name == "bob") {
update reply {
&Reply-Message := "Hello, %{User-Name}"
}
}
You can now do this:
if (&User-Name == "bob") {
&reply.Reply-Message := "Hello, %{User-Name}"
}
Proxying
Proxying has undergone massive changes. The proxy.conf
file no
longer exists, and everything in it has been removed. e.g. realm
,
home_server
, home_server_pool
no longer exist. The old proxying
functionality was welded into the server core, which made many useful
features impossible to configure.
The radius
module now handles basic proxying to home servers. We
recommend creating one instance of the radius
module per home
server. e.g.
radius home_server_1 {
... configuration for home server 1 ...
}
You can then use home_server_1
in any processing section, and the
request will be proxied when processing reaches the module.
For ease of management, we recommend naming the modules for the host name of the home server.
It is often simplest to do proxying via an authenticate proxy { … }
section, though that section can have any name. e.g. setting
Auth-Type := proxy
will call the authenticate proxy
section, and
is similar to the previous setting Proxy-To-Realm
.
authenticate proxy {
home_server_1
}
For more detailed examples, see the Wiki page: https://wiki.freeradius.org/upgrading/version4/proxy. That page also describes how to upgrade a v3 configuration to the new v4 style.
The benefit of this approach is that the "RADIUS proxy" functionality is just another module. It is now possible to not just fail over from one home server to another, but also to proxy the same packet to multiple destinations.
home_server
The home_server
configuration has been replaced with the radius
module. See raddb/mods-available/radius
for examples and
documentation.
home_server_pool
The home_server_pool
configuration has been replaced with standard
unlang configurations. The various load-balancing options can be
re-created using in-place unlang
configuration.
The mappings for type
are as follows:
-
type = fail-over
- replaced withunlang
redundant {
home_server_1
home_server_2
home_server_3
}
Of course, you will have to use the names of the radius
modules in your configuration, and not home_server_1 , etc.
|
-
type = load-balance
- replaced withunlang
load-balance {
home_server_1
home_server_2
home_server_3
}
-
type = client-balance
- replaced withunlang
load-balance "%{Net.Src.IP}" {
home_server_1
home_server_2
home_server_3
}
-
type = client-port-balance
- replaced withunlang
load-balance "%%{Net.Src.IP}-%{Net.Src.Port}" {
home_server_1
home_server_2
home_server_3
}
-
type = keyed-balance
- replaced withunlang
load-balance "%{Load-Balance-Key}" {
home_server_1
home_server_2
home_server_3
}
While the Load-Balance-Key
was a special attribute in v3, it has no
special meaning in v4. You can use any attribute or string expansion as
part of the load-balance
key.
Things which were impossible in v3
In v3, it was impossible to proxy the same request to multiple destinations. This is now trivial. In any processing section, do:
...
home_server_1
home_server_2
...
When processing reaches that point, it will proxy the request to
home_server_1
, followed by home_server_2
.
This functionality can be used to send Accounting-Request
packets to
multiple destinations.
You can also catch failed proxying, and do something else. In the
example below, try to proxy to home_server_1
, if that fails, just
accept
the request.
...
home_server_1
if (fail) {
accept
}
...
Dictionaries
The struct
data type is now supported. See man dictionary
.
Bit fields are now support via a data type such as bit[3]
. Not that
bit fields are only supported inside of a struct
definition.
The dictionary parser includes many more sanity checks and helpful messages for people who create new dictionaries.
Dictionaries are now split up by protocol. e.g._`share/freeradius/radius/dictionary*`. All protocol-specific data types have been removed, and replaced with per-attribute flags.
The old abinary
data type has been removed. Attributes needing this
functionality should instead be marked with a flag, e.g._`string abinary`.
The old extended
data type has been removed. Attributes needing this
functionality should instead be marked with a flag, e.g._`tlv
extended`.
"Tagged" RADIUS attributes
The old-style "tagged" RADIUS format has been removed. Instead of using
Tunnel-Type:1 = PPTP
you should use
Tag-1.Tunnel-Type = PPTP
It is also possible to "group" tagged attributes together, as in the following example:
Tag-1 = { Tunnel-Type = PPTP, Tunnel-Medium-Type = IPv4 }
There are 31 such attributes, Tag-1
through Tag-31
. There is no
Tag-0
attribute, as it is not needed.
After much investigation, it was unfortunately impossible to continue
supporting the Attribute-Name:tag
syntax for tagged attributes.
This change requires modifications to all configuration files and
databases which use tags. This change means also that detail
files
from v3 are not readable by v4.
Attribute references
In previous versions of the user attributes could be referred to by
their name only e.g. if (User-Name == 'foo')
.
To allow for more thorough error checking, it is now required to
prefix attribute references with &
. Using bare names will result in
an error, and a suggestion to use &
.
Common places which will need to be checked and corrected are the left
and right hand side of update {}
sections, along with if
conditions.
The v3 server has warned about using non prefixed attribute references for some time. If users have addressed those warnings, few modifications will be required.
Use of attributes in xlats e.g. %{User-Name}
remains unchanged.
There is no plan to require prefixes here.
As of v3, the preferred format for unknown
attributes is
&Attr-oid.oid.oid
, e.g. &Attr-26.11344.255
. However, v3 would
still parse (but not generate) attributes of the form
Vendor-FreeRADIUS-Attr-255
. The Vendor-
syntax has been removed in
version 4. The server would never produce such names, and allowing
them as input made attribute parsing significantly more complex.
List references
The old-style request:
and reply:
syntax for lists has been
deprecated. Please use request.
and reply.
instead.
Many lists have been removed. e.g._`proxy`, proxy-reply
, coa
,
coa-reply
, disconnect
, and disconnect-reply
. The underlying
functionality still exists, but it has been moved to different
keywords, such as subrequest
.
Update sections
The update
sections are deprecated. See the new way to
edit attributes.
The server has limited support for "auto-conversion" of update
sections to the new syntax.
Recommendations
We recommend manually converting the update
sections to the new
method. The biggest change that confuses people is the old +=
operator does not work the same way as before.
Instead of doing:
&Reply-Message += "foo"
you should edit the reply
list, using the +=
operator:
&reply += {
&Reply-Message = "foo"
}
We also recommend removing double-quotes from xlat expansions where possible. The temptation in v3 is to just add double quotes to everything, and hope it all works out. This is no longer necessary in v4.
For example, in v3 you would do:
update reply {
&Framed-IP-Address := "%{sql:SELECT ...}"
}
In v4, you can remove the update
, and rewrite the SQL call to:
&reply.Framed-IP-Address := %sql("SELECT ...")
Using double quotes everywhere means that every bit of data gets
converted to printable strings, and then back to it’s real data type
(ipaddr
in the above example). Removing the double quotes means
that there is less work going on, which means higher performance.
load-balance and redundant-load-balance sections
Before v4, the load-balance
sections implemented load balancing by
picking a child at random. This meant that load balancing was
probabilistically fair, but not perfectly fair.
In v4, load-balance
sections track how many requests are in each
sub-section, and pick the subsection which is used the least. This is
like the v3 proxy behavior of load balancing across home server pools.
The load-balance
and redundant-load-balance
sections now allow
for a load-balance key:
load-balance "%{Calling-Station-Id}" {
module1
module2
module3
...
}
If the key exists, it is hashed, and used to pick one of the
subsections. This behavior allows for deterministic load-balancing of
modules, similar to the v3 proxy keyed-balance
configuration.
Connection timeouts
In v3 and earlier, the configuration items for configuring connection timeouts were either confusingly named, or completely absent in the case of many contributed modules.
In v4, connection timeouts can be configured universally for all modules
with the connect_timeout
config item of the module’s pool {}
section.
The following modules will apply connect_timeout
:
-
rlm_rest
-
rlm_linelog (network connections only)
-
rlm_ldap
-
rlm_couchbase
-
rlm_cache_memcached
-
rlm_redis_* (all the redis modules)
-
rlm_sql_cassandra
-
rlm_sql_db2
-
rlm_sql_freetds
-
rlm_sql_mysql
-
rlm_sql_unixodbc
Some modules such as rlm_sql_postgresql can have their timeout set via
an alternative configuration item (e.g. radius_db
in the case of
postgresql).
Unlang Syntax
Many new unlang keywords have been added.
Data type casts have changed from <ipaddr> …
to (ipaddr) …
Xlat expansions
xlat expansions have been changed from syntax like %{md5:…}
to %md5(…)
.
New Modules
The following modules are new in v4.
rlm_client
This module handles the %{client:..}
xlat expansions.
The Client-Shortname
attribute has been removed. You should use %client(shortname)
instead.
rlm_radius
The radius
module has taken over much of the functionality of
proxy.conf
. See raddb/mods-available/radius
for documentation
and configuration examples.
The radius
module connects to one home server, just like the
home_server
configuration in v3. Some of the configuration items are
similar to the home_server
configuration, but not all.
The module can send multiple packet types to one home server. e.g. Access-Request and Accounting-Request.
This module also replaces the old coa
and originate-coa
configuration. See also subrequest
for creating child requests that are
different from the parent requests.
Unlike v3, the module can do asynchronous proxying. That is, proxying where the server controls the retransmission behavior. In v3, the server retransmitted proxied packets only when it received a retransmission from the NAS. That behavior is good, but there are times where retransmitting packets at the proxy is better.
Changed Modules
The following modules exhibit changed behaviour.
rlm_cache
&control.Cache-Merge
has been renamed to
&control.Cache-Merge-New
and controls whether new entries are merged
into the current request. It defaults to no
. The primary use case,
is if you’re using xlat expansions in the cache module itself to
retrieve information for caching, and need the result of those
expensions to be available immediately.
Two new control attributes &control.Cache-Allow-Merge
and
&control.Cache-Allow-Insert
have been added. These control whether
existing entries are to be merged, and new entries created on the next
call to a cache module instance. Both default to yes
.
rlm_eap
All certificate attributes are available in the &session-state.
list, immediately after they are parsed from their ASN1 form.
The certificates are no longer added to the &request.
list. Instead,
they are added to the session-state
list. You are advised to update
any references during the upgrade to 4.0:
s/TLS-Cert-/session-state.TLS-Cert-/
The rlm_eap_ikev2
module was removed. It does not follow RFC 5106,
and no one was maintaining it.
The rlm_eap_tnc
module was removed. No one was using or maintaining
it.
The in-memory SSL cache was removed. Changes in OpenSSL and FreeRADIUS
made it difficult to continue using the OpenSSL implementation of a
cache. See raddb/sites-available/tls-cache
for a better replacement.
The OpenSSL cache can now be placed on disk, in memory, in memcache,
or in a redis cache. The result is both higher performance, and more
configurable.
The use_tunneled_reply
and copy_request_to_tunnel
configuration
items have been removed. Their functionality has been replaced with the
use_tunneled_reply
and copy_request_to_tunnel
policies. See
raddb/sites-available/inner-tunnel
and raddb/policy.d/eap
for
more information.
These configuration items were removed because they caused issues for a
number of users, and they made the code substantially more complicated.
Experience shows that having configurable policies in unlang
is
preferable to having them hard-coded in C.
rlm_eap_pwd
The virtual_server
configuration has been removed from EAP-PWD. The
module now looks for &request.control.Password.Cleartext.
rlm_eap_leap
The LEAP protocol has been removed from the server. It is insecure, non-standard, and should not be used.
rlm_exec
Exec-Program and Exec-Program-Wait have been removed.
The packet_type
configuration has been removed. Use unlang
checks to see if you want to execute the module.
rlm_expr
The expr
module is no longer necessary and has been removed.
The xlat expansions just support math natively. For example:
&Reply-Message := "1 + 2 = %{1 + 2}"
will return the string 1 + 2 = 3
. The contents of the expansion can
be any math or condition. Attribute assignments in expansions are not
supported.
rlm_expiration
The expiration
module has been replaced with an unlang
policy.
The policy is located in raddb/policy.d/time
. The Expiration
attribute should continue to work the same as with v3.
rlm_ldap
The ldap
module provides an expansion %ldap.memberof(<name>)
instead of
LDAP-Group
for dynamically testing group membership. The old method of
if (LDAP-Group == "foo") { ...
will no longer work. Instead, use
if (%ldap.memberof(foo)) { ...
The cacheing of group membership into attributes in the control
list is
still available, so
&control.LDAP-Group[*] == "foo"
can also be used to test membership after having called the ldap
module,
if cacheable_name
or cacheable_dn
are enabled.
rlm_mschap
The winbind_*
configuration options are now in a winbind
subsection. See mods-available/mschap
for details.
rlm_perl
Attributes of type octets
are now passed directly to Perl as binary
data, instead of as hex strings.
All data received from the network is marked tainted
by default.
rlm_radutmp
The case_sensitive
option has been removed. Administrators should
not be permitting users to log in with multiple different user names.
If your system needs to be case insensitive, we suggest changing all
names to lowercase:
recv Access-Request {
&Stripped-User-Name := %tolower(%{User-Name})
...
}
rlm_rest
REST-HTTP-Code
is now inserted into the &request.
list instead
of the &reply.
list, to be compliant with the
list usage guidelines.
rlm_sql
Driver-specific options have moved from mods-available/sql
to
mods-config/sql/driver/<drivername>
.
If caching is enabled, the SQL-Group
attribute is cached in the
control
list after the module has run. This means it is possible to
do regular expression comparison on group names.
It also means that any comparison of &SQL-Group == "foo"
has to be
updated to use &control.SQL-Group == "foo"
instead.
This caching also means that the group comparison will be done internally, and will not result in a database lookup. This also means that it is now possible to do group comparisons based on regular expressions.
It is possible to force a dynamic group lookup via the expansion
%sql.group(foo)
. This expansion returns true
if the user is a
member of that SQL group, and false
otherwise.
if (%sql.group(sales)) {
...
}
will return true
.
rlm_sqlcounter
Attribute references:
- The following config items must now be defined as attribute references
counter_name
check_name
reply_name
- For example where in v3 you would specify the attribute names as
counter_name = Daily-Session-Time
check_name = Max-Daily-Session
reply_name = Session-Timeout
key = User-Name
- In v4 they must now be specified as
counter_name = &control.Daily-Session-Time
check_name = &control.Max-Daily-Session
reply_name = &reply.Session-Timeout
key = "%{&Stripped-User-Name || &User-Name}"
Just adding the &
prefix to the attribute name is not sufficient.
Attributes must be qualified with the list to search in, or add to.
This allows significantly greater flexibility, and better integration
with newer features in the server such as CoA, where reply_name can now
be &coa:Session-Timeout
. That allows the server to send a CoA packet
which updates the Session-Timeout
for the user.
In v4, when the key
field was set to User-Name
, the module would
also look for Stripped-User-Name
as the key. In v4, this
functionality has been moved to the configuration. To get the same
functionality, the key should now be specified as a dynamic expansion:
key = "%{&Stripped-User-Name || &User-Name}"
The count_attribute
has been removed, as it is no longer necessary.
The old expansions %%b
and %%e
have been removed. This should
only affect people who are editing the queries manually. See the file
mods-availble/sqlcounter
for more information.
The attribute comparison has been removed. It is no longer possible
to check Daily-Session-Time > 4
everywhere. Instead, the attribute
exists only after the sqlcounter
module has been run.
rlm_sqlippool
The ipv6 configuration item has been deleted. It was
deprecated in 3.0.16.
|
Instead, use attribute-name
. See mods-available/sqlippool
for
more information.
Deleted Modules
rlm_logintime
This module was poorly documented, and it appears that no one was using it.
The attributes Time-Of-Day
, Login-Time
, and Current-Time
have
also been removed. Any configuration which tries to use them will
result in an error.
Deleted Functionality
Many "virtual" or "fake" attributes have been removed or renamed.
&Module-Return-Code
should be replaced by %interpreter(rcode)
.
&Response-Packet-Type
should be replaced by &reply.Packet-Type
.
&Virtual-Server
should be replaced by %interpreter(server)
.
&Packet-Authentication-Vector
should be replaced by %radius.packet.vector()
.
&Packet-Dst-IP-Address
and &Packet-Dst-IPv6-Address
should be replaced by &Net.Dst.IP
.
&Packet-Dst-Port
should be replaced by &Net.Dst.Port
.
&Packet-Src-IP-Address
and &Packet-Src-IPv6-Address
should be replaced by &Net.Src.IP
.
&Packet-Src-Port
should be replaced by &Net.Src.Port
.
Recommended Changes
In v3, many people had a habit of just adding "…"
around everything. For example:
update reply {
&Reply-Message := "%{User-Name}"
}
...
if ("%{User-Name}" == "bob") {
...
}
This practice is not recommended. It was never necessary, and it’s not clear why it became a (bad) habit.
In v4, it is clearer, simpler, and faster to just use unlang
syntax correctly.
&reply.Reply-Message := &User-Name
...
if (&User-Name == "bob") {
...
}