#
# = Radius Module
#
-# The `radius` module in v4, can be used to implement proxying and request
-# fan-out, as well as synchronous and asynchronous `CoA` and `DM`.
+# The `radius` module in v4 implements RADIUS proxying and
+# replication. In v3, RADIUS proxying is a special kind of
+# configuration, with it's own load-balancing, fail-over etc. That
+# configuration is now simpler in v4. The outbound RADIUS proxying
+# is done by just another module: the `radius` module.
+# Load-balancing and redundant fail-over are handled by the
+# `load-balance` and `redundant` keywords.
#
-# ## Configuration Settings
+# In v4, the `radius` module most clearly maps to a `home_server` in
+# v4. The `radius` module typically makes a connection from one
+# source IP address to a server at one destination IP/port. It may
+# open multiple source ports, depending on how many packets are being
+# proxied.
+#
+# ## Behavior
+#
+# The module adds a Proxy-State attribute to all proxied packets.
+# This `Proxy-State` contains a 32-bit random number, which is unique
+# to this module. This unique number helps to detect proxy loops.
+#
+# The reply from home server is appended to the reply list for the
+# current packet.
+#
+# WARNING: For security reasons, the module ensures that all proxied
+# `Access-Request` packets contain a `Message-Authenticator`
+# attribute. This behavior is *NOT* configurable, and *CANNOT* be
+# changed. This behavior is part of the BlastRADIUS mitigations.
#
# Any proxied packet (including `Accounting-Request`) can
# receive a `Protocol-Error` response packet. This packet
# is an explicit `NAK` that something went wrong processing
# the request.
#
-# WARNING: For security reasons, the module ensures that all proxied
-# `Access-Request` packets contain a `Message-Authenticator` attribute.
-# This behavior is *NOT* configurable, and *CANNOT* be changed.
+# Unlike v3, the server does not support any "pre-proxy" or
+# "post-proxy" processing sections. Similarly, this module does not
+# support any "proxy" or "proxy-reply" list. Instead, the current
+# request is proxied as-is, and the proxied reply is added to the
+# current reply list. If you want to modify the proxied request
+# and/or proxied reply, then you should use a `subrequest` block to
+# create a child request. That child request can then be modified
+# independently of the parent. Any reply attributes will have to be
+# copied back manually to the parent request.
#
-# The module adds a Proxy-State attribute to all proxied packets.
-# This `Proxy-State` contains a 32-bit random number, which is unique
-# to this module. This unique number helps to detect proxy loops.
+# ## Configuration Settings
+#
+# The configuration settings of this module are similar to the
+# `home_server` settings in v3.
#
# The module has the following return codes:
#
#
radius {
#
- # transport:: Only UDP transport is allowed.
+ # mode:: What kind of client behavior is being used.
+ #
+ # proxy - forward packets which are received from a NAS
+ # - each packet has a Proxy-State attribute added.
+ # - it looks for, and stops proxy loops
+ # - retransmissions are sent only when the NAS retransmits
+ # - the module fails if it does not receive a reply
+ #
+ # client - originate packet, and do retransmissions ourselves
+ # - no Proxy-State is added.
+ # - the module retransmits as needed
+ # - the module fails if it does not receive a reply
+ #
+ # replicate - send packets without waiting for a reply.
+ # - no Proxy-State is added
+ # - the module does not expect any reply
+ # - all replies are discarded
+ # - the module continues with "ok" immediately after
+ # sending the packet.
+ #
+ # unconnected-replicate - replicate packets to dynamic destinations
+ # - For unconnected UDP sockets only.
+ # - It MUST have `transport = udp`
+ # - It MUST have `src_ipaddr = *` and no `src_port`
+ # - You CANNOT use the module "in place" as with normal proxying.
+ # - It is only supported via the function %replicate.sendto.ipaddr(ipaddr, port, secret)
+ #
+ # unconnected-proxy - proxy packets to dynamic destinations
+ # - For unconnected UDP sockets only.
+ # - It MUST have `transport = udp`
+ # - It MUST have `src_ipaddr = *` and no `src_port`
+ # - You CANNOT use the module "in place" as with normal proxying.
+ # - It is only supported via the function %proxy.sendto.ipaddr(ipaddr, port, secret)
+ #
+ # The server can still be used to create (i.e. originate)
+ # packets via this module when `mode = proxy` is set. The
+ # module can automatically detect the difference between
+ # proxied packets and client packets it originates.
+ # Originated packets are taken from the detail file, or
+ # result when changing packet type (e.g. Accounting-Request
+ # to Disconnect-Request), or when the current request is a
+ # subrequest, and the parent request is from a different
+ # protocol.
+ #
+ # Note that there is no `mode = unconnected`, where the
+ # module then both proxies packets, and replicates them. The
+ # need to track replies when proxying means that it's
+ # difficult to both proxy and replicate at the same time. As
+ # a result, there are two "unconnected" modes, one for each
+ # of "proxy" and "replicate".
+ #
+ mode = proxy
+
+ #
+ # transport:: Transport protocol. Can be `udp` or `tcp`.
#
transport = udp
#
# type:: List of allowed packet types.
#
- # There is currently no way to change the packet type in the
- # request. See `unlang` fork for that functionality.
+ # The module will only send packets types which are listed
+ # here. Other types of packets will be ignored. The main
+ # purpose of the `type` configuration is to ensure that the
+ # correct packets are being sent to the home server. This
+ # entry serves as a double-check against misconfigurations.
+ #
+ # In v3, the `home_server` configuration of `auth`, `acct`,
+ # or `auth+acct` is used to _find_ a home server. That is,
+ # when FreeRADIUS has an `Access-Request` packet in v3, it
+ # proxies it by looking up a matching `home_server`.
+ #
+ # In v4, proxying is done by listing the `radius` module in a
+ # processing section, such as `authenticate radius { ... }`,
+ # or `recv Accounting-Request { ... }`. So unlike v3, the
+ # module doesn't have to _find_ a proxy destination for a
+ # particular kind of packet. Instead, the administrator
+ # _configures_ the module to send packets to a destination.
+ #
+ # As a result, the module doesn't really care about what kind
+ # of packets it sends. It has a packet, a destination where
+ # that packet should be sent, and it sends the packet.
#
- # NOTE: `Status-Server` is reserved for `inter-server` signaling,
+ # In order to change packet types, see the `subrequest` keyword.
+ #
+ # NOTE: `Status-Server` is reserved for connection signaling,
# and cannot be proxied.
#
+ # Unlike v3, all packet types are allocated from the same
+ # 8-bit ID space. This change does not affect the majority
+ # of RADIUS proxying, which only sends one type of packet.
+ # This change does not affect the home server which receives
+ # these packets, as the home server does not track IDs except
+ # to correlate requests to replies.
+ #
+ # The only visible difference, then, between v3 and v4 is
+ # that in some cases, the new `radius` module will use more
+ # source ports when proxying.
+ #
+ # This change simplifies the implementation of the RADIUS
+ # client.
+ #
type = Access-Request
type = Accounting-Request
#
- # replicate:: Whether or not we are replicating packets.
- #
- # Replication is "send proxied request, and continue,
- # without waiting for a response". Any response received
- # is discarded, and does not affect packet processing.
+ # require_message_authenticator::Require Message-Authenticator
+ # in responses.
#
-# replicate = no
-
+ # A server should include Message-Authenticator attribute as
+ # the first attribute in responses to Access-Request packets.
+ # This behavior mitigates against the BlastRADIUS attack.
#
- # synchronous::
+ # However, not all servers follow this security practice. As
+ # a result, this module can be configured to either not
+ # require, or require, Message-Authenticator.
#
- # In many cases, the module should do retransmissions of
- # proxied request on its own. See the various
- # configurations for `initial_rtx_time`,
- # etc. below. This means setting `synchronous = no`.
+ # If value is `auto`, then the module will automatically
+ # detect the existence of Message-Authenticator in response
+ # packets. Once the module sees a Message-Authenticator, it
+ # will automatically change the configuration internally to
+ # `yes`. This change prevents security "down-bidding"
+ # attacks.
#
- # However, in some cases, it is useful to retransmit only
- # when the server receives a retransmission from the NAS.
- # This is done by setting `synchronous = yes`
+ # Allowed values: yes, no, auto
#
- # In general, if the server is receiving packets directly
- # from a NAS, you should set `synchronous = no`. This is
- # because the NAS retransmission behavior is horrible,
- # inconsistent, and hard to configure.
+ # The default is `auto`.
#
- # If the server is receiving packets from another proxy
- # server, you should set `synchronous = yes`. This allows
- # the other proxy server to do retransmissions correctly.
+ require_message_authenticator = auto
+
#
- # NOTE: The behavior in v3 is the same as `synchronous = yes`
+ # response_window: If we do not receive any replies within
+ # this time period, then start `zombie_period`
#
-# synchronous = no
+ response_window = 15
#
- # originate:: Whether or not we are creating the packet.
+ # zombie_period:: If the home server does not reply to
+ # packets within `response_window`, then `zombie_period`
+ # starts.
#
- # Sometimes we are creating a request that is not for the purpose of
- # proxying another request, in which case we do not want to add a
- # Proxy-State attribute.
+ # When `zombie_period` starts, a connection is marked
+ # `zombie`, and then is not used to send new packets. If
+ # there are no responses on this connection within
+ # `zombie_period`, the module either closes the connection
+ # (no `status_check` subsection), or starts pinging the home
+ # server (`status_check.type = Status-Server`).
#
- # In some cases, such as originating a CoA or Disconnect request,
- # including Proxy-State may confuse the receiving NAS.
-# originate = no
+ zombie_period = 10
#
- # require_message_authenticator::Require Message-Authenticator
- # in responses.
+ # revive_interval:: If there are no status checks, mark the
+ # home server alive after `revive_interval` timeout.
#
- # Including a Message-Authenticator attribute first in response
- # packet, mitigates against the blastradius prefix attack.
+ # Some home servers do not support status checks via the
+ # `Status-Server` packet. Others may not have a "test" user
+ # configured that can be used to query the server, to see if
+ # it is alive. For those servers, we have NO WAY of knowing
+ # when it becomes alive again. Therefore, after the server
+ # has been marked dead, we wait a period of time, and mark
+ # it alive again, in the hope that it has come back to
+ # life.
#
- # If value is auto, then if any packet received from the client
- # contains a valid Message-Authenticator attribute, then the server
- # will require it from all future packets from that client.
+ # If it has NOT come back to life, then the module will wait
+ # for `zombie_period` before marking it dead again. During
+ # the `zombie_period`, ALL AUTHENTICATIONS WILL FAIL, because
+ # the home server is still dead. There is NOTHING that can
+ # be done about this, other than to enable the status checks,
+ # as documented above.
#
- # Allowed values: yes, no, auto
+ # e.g. if `zombie_period` is 40 seconds, and `revive_interval`
+ # is 300 seconds, the for 40 seconds out of every 340, or about
+ # 10% of the time, all authentications will fail.
#
- # The default is "no".
+ # If the `zombie_period` and `revive_interval` configurations
+ # are set smaller, than it is possible for up to 50% of
+ # authentications to fail.
#
- require_message_authenticator = auto
+ # As a result, we recommend enabling status checks, and
+ # we do NOT recommend using `revive_interval`.
+ #
+ # The `revive_interval` configuration is used ONLY if the
+ # `status_check` subsection is not used. Otherwise,
+ # `revive_interval` is not necessary, and should be deleted.
+ #
+ # Useful range of values: 10 to 3600
+ #
+ revive_interval = 3600
+ #
+ # ### Check the status of a connection
#
# status_check { ... }:: For "are you alive?" queries.
#
# e.g. 'Access-Request', 'Accounting-Request' or
# 'Status-Server'.
#
- # Status-Server is recommended as other packet types may
- # be interpreted incorrectly, or proxied to a remote
- # server defeting the purpose of the status check
+ # Status-Server is recommended as other packet types
+ # may be interpreted incorrectly, or may ve proxied
+ # to a remote server, which defeats the purpose of
+ # the status checks.
#
# If you specify another type of packet, it MUST be listed
- # as an allowed `type`, above.
+ # as an allowed `type` above.
#
type = Status-Server
# be edited.
#
# For other packet types, you can set the contents
- # here. The section MUST be set over "&request.<attribute> = value", and
- # anything else will cause a parse error.
+ # here. The section MUST be set over
+ # "&request.<attribute> = value", and anything else
+ # will cause a parse error.
#
# We RECOMMEND that you use packet contents which
# lets the other end easily tell that they are not
}
#
- # response_window: If we do not receive a reply within this time period, then
- # start `zombie_period`
+ # ## Transport Protocols
#
- response_window = 15
-
- #
- # zombie_period:: If the home server does not reply to a packet, the
- # `zombie_period` starts.
- #
- # The connection is marked `zombie`, and isn't used to send new packets.
- # If there are no responses within `zombie_period`, the server either
- # closes the connection (no `status_check` subsection), or starts pinging the
- # home server (`status_check.type = Status-Server`).
+ # The module supports multiple transport protocols.
#
- zombie_period = 10
#
- # revive_interval:: If there are no status checks, mark the
- # home server alive after `revive_interval` timeout.
+ # ### File Output
#
- # Some home servers do not support status checks via the
- # `Status-Server` packet. Others may not have a "test" user
- # configured that can be used to query the server, to see if
- # it is alive. For those servers, we have NO WAY of knowing
- # when it becomes alive again. Therefore, after the server
- # has been marked dead, we wait a period of time, and mark
- # it alive again, in the hope that it has come back to
- # life.
+ # Write raw RADIUS packets (no IP or UDP header) to a file.
+ # This transport can only be used for `mode = replicate`
#
- # If it has NOT come back to life, then the module will wait
- # for `zombie_period` before marking it dead again. During
- # the `zombie_period`, ALL AUTHENTICATIONS WILL FAIL, because
- # the home server is still dead. There is NOTHING that can
- # be done about this, other than to enable the status checks,
- # as documented above.
+ file {
+ filename = ${logdir}/packets.bin
+ }
+
#
- # e.g. if `zombie_period` is 40 seconds, and `revive_interval`
- # is 300 seconds, the for 40 seconds out of every 340, or about
- # 10% of the time, all authentications will fail.
+ # ### UDP Transport
#
- # If the `zombie_period` and `revive_interval` configurations
- # are set smaller, than it is possible for up to 50% of
- # authentications to fail.
+ # Much like the v3 `home_server` configuration.
#
- # As a result, we recommend enabling status checks, and
- # we do NOT recommend using `revive_interval`.
+ udp {
+ #
+ # src_ipaddr:: IP we open our socket on.
+ #
+# src_ipaddr = *
+
+ #
+ # Destination IP address, port, and secret.
+ #
+ # Use `ipv4addr = ...` to force IPv4 addresses.
+ # Use `ipv6addr = ...` to force IPv6 addresses.
+ #
+ ipaddr = 127.0.0.1
+ port = 1812
+ secret = testing123
+
+ #
+ # interface:: Interface to bind to.
+ #
+# interface = eth0
+
+ #
+ # max_packet_size:: Our max packet size. may be different from the parent.
+ #
+# max_packet_size = 4096
+
+ #
+ # recv_buff:: How big the kernel's receive buffer should be.
+ #
+# recv_buff = 1048576
+
+ #
+ # send_buff:: How big the kernel's send buffer should be.
+ #
+# send_buff = 1048576
+ }
+
#
- # The `revive_interval` configuration is used ONLY if the
- # `status_check` subsection is not used. Otherwise,
- # `revive_interval` is not necessary, and should be deleted.
+ # ### TCP
#
- # Useful range of values: 10 to 3600
+ # The TCP configuration is identical to the `udp` configuration.
#
- revive_interval = 3600
+# tcp {
+# ...
+# }
#
# ## Connection trunking
}
#
- # ## Protocols
- #
- # For now, only UDP is supported.
- #
- # udp { ... }:: UDP is configured here.
- #
- udp {
- ipaddr = 127.0.0.1
- port = 1812
- secret = testing123
-
- #
- # NOTE: Don't change anything if you are not sure.
- #
-
- #
- # interface:: Interface to bind to.
- #
-# interface = eth0
-
- #
- # max_packet_size:: Our max packet size. may be different from the parent.
- #
-# max_packet_size = 4096
-
- #
- # recv_buff:: How big the kernel's receive buffer should be.
- #
-# recv_buff = 1048576
-
- #
- # send_buff:: How big the kernel's send buffer should be.
- #
-# send_buff = 1048576
-
- #
- # src_ipaddr:: IP we open our socket on.
- #
-# src_ipaddr = ""
- }
-
- #
- # ## Packets
+ # ## Retransmission timers.
#
# Each packet can have its own retransmission timers.
#
# The sections are named for each packet type. The contents
- # are the same for all packet types. Only the relevant ones
- # are parsed (see `type` above).
+ # are the same for all packet types.
#
#
max_rtx_duration = 30
}
}
+
+#
+# ## Replication of Packets
+#
+# The module supports replication of packets to new destinations at
+# run time. In this context, replication means "send the packet, and
+# do not wait for the response". This functionality is most useful
+# when copying large amounts of accounting data to multiple
+# destinations.
+#
+# The module can then only be used as a dynamic expansion. That is,
+# you cannot specify the `replicate` module directly in a processing
+# section.
+#
+# ### Usage
+#
+# This module can only be used as a dynamic expansion. Since the
+# module does not wait for any response, the expansion does not
+# return any value.
+#
+# The module can be called from any processing section as follows:
+#
+# %replicate.sendto.ipaddr(127.0.0.1, 1813, 'testing123')
+#
+# The function takes three arguments:
+#
+# * IP address where the packet is sent. It MUST be the same
+# address family as `src_ipaddr` below.
+# * port where the packet is sent.
+# * secret for this packet.
+#
+# This function allows the module to send packets to _any_
+# destination, where the destination is chosen dynamically at run
+# time. The arguments to the function can be take from other
+# attributes, database queries, etc.
+#
+radius replicate {
+ #
+ # Generally you only want to replicate accounting packets.
+ #
+ type = Accounting-Request
+
+ #
+ # We are not opening a socket from our server to their
+ # server. We are replicating packets.
+ #
+ mode = unconnected-replicate
+
+ #
+ # For replicated packets, only UDP is supported.
+ #
+ transport = udp
+
+ #
+ # ### UDP Transport
+ #
+ # For unconnected modes, only UDP is supported.
+ #
+ udp {
+ #
+ # src_ipaddr:: The source IP address used by the module.
+ #
+ src_ipaddr = *
+
+ #
+ # `src_port` cannot be used. If it is used here, the
+ # module will refuse to start. Instead, the module
+ # will open a unique source port per thread.
+ #
+ # `secret` cannot be used. If it is used, the value
+ # will be ignored.
+ #
+ }
+
+ #
+ # ### Other Configuration
+ #
+ # No other configuration items are supported when using
+ # `mode = unconnected-replicate`.
+ #
+ # The `pool` configuration is ignored, as is `status-check`,
+ # along with all per-packet timeouts.
+ #
+}
+++ /dev/null
-# rlm_radius
-
-## 2017-10-11
-
-After refactoring...
-
-* on read(), don't put connection into active state, as it may not be writable?
- * or, just do it, and hope for the best... with the event loop handling it
-
-
-## RADIUS fixes
-
-* idle out old connections
-
-* limit # of bad / reconnected connections
-
-* move status_u allocation and init to connection alloc
-
-## connection state
-
-* maybe move Status-Server to fixed-time pings, as recommended in RFC 3539?
- * low priority, and probably not useful
-
-## Limits
-
-We limit the number of connections, but not the number of proxied
-packets. This is because (for now), each connection can only proxy 256 packets...
-
-## Status Checks
-
-* connection negotiation in Status-Server in proto_radius
- * some is there (Response-Length)
- * add more? Extended ID, etc.
-
-## Core Issues
-
-things to do in the server core. Tracked here because it's related to
-the work in rlm_radius.
-
-## Cleanup_delay
-
-* need to double-check cleanup_delay
- * it works, but it's likely set too small?
- * especially if the client retransmits are 10s?
- * or maybe it was the dup detection bug (timestamp) where it didn't detect dups...
-
-## sequence / ACK in network / worker
-
-* double-check ENABLE_SKIPS in src/lib/io/channel.c. It's disabled
- for now, as it caused problems. So it ALWAYS signals the other side. :(
-
-We should move to a "must_signal" approach, as with the network side
-The worker should suppress signals if it sees that the ACKs from the
-other end haven't caught up to it's sent packets. Otherwise, it must
-signal.
-
-this whole thing is wrong... we end up signaling on every damned packet in real life...
-
-OK... fix the damned channel to use queue depth instead of ACKs
-which makes them less general, but better. The worker can NAK a packet, send a reply, or mark it ask discarded
-
-
- DATA N -> W: (packet + queue 1, active)
-
- DATA N <- W (packet + queue is now 0, inactive)
-
- DISCARD N <- W (no packet, queue is now 0, inactive)
-
- SLEEPING N <- W (no packet, queue is 1, inactive)
-
-
-We also need an "must_signal" flag, for if the other end is
-sleeping... the network always sets it, I guess..
-
-### Fork
-
-* fix fork
-
- fork server.packet-type {
- &foo += &parent:bar
- }
-
-* fork is an 'update' section that also runs a new virtual server. A
- little weird, but it should work.
-
-* needs helper functions in virtual_server.c to do it.. and to create
- child request_t async stuff with listen, protocol handler, etc.
-
-* fork also needs to do this sanity check on compile, so that it knows
- it can dereference sections which exist...
-
-### clean up request_t structure
-
-many fields are essentially unused. request->proxy is no longer used,
-but is referenced all over the place.
-
-grunt work, but very useful.
-
-### Network and worker fixups
-
-* switch worker selection from recursing / heap to O(N) lookups and "power of 2"
- * see comments in src/lib/io/network.c
-
-* associate packets with a particular worker across multiple packets
- * once this is done, we can move to per-thread SSL contexts, and drop contention massively
- * with the caveat that *all SSL work* has to be done in one thread
- * hopefully this doesn't affect things like SQL drivers? need to check...
-
-* do NUMA for high-end systems
- * associate N network threads with W worker threads
-ifeq "${WITH_RADIUS2}" ""
-SUBMAKEFILES := rlm_radius.mk rlm_radius_udp.mk
-endif
+TARGETNAME := rlm_radius
+TARGET := $(TARGETNAME)$(L)
+
+SOURCES := rlm_radius.c track.c
+
+TGT_PREREQS := libfreeradius-radius$(L) libfreeradius-bio-config$(L) libfreeradius-bio$(L)
+LOG_ID_LIB = 39
#include <freeradius-devel/io/application.h>
#include <freeradius-devel/server/modpriv.h>
+#include <freeradius-devel/unlang/xlat_func.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/dlist.h>
#include "rlm_radius.h"
+static int mode_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
static int status_check_type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
static int status_check_update_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
CONF_PARSER_TERMINATOR
};
+static conf_parser_t const transport_config[] = {
+ { FR_CONF_OFFSET_FLAGS("secret", CONF_FLAG_REQUIRED, rlm_radius_t, secret) },
+
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * We only parse the pool options if we're connected.
+ */
+static conf_parser_t const connected_config[] = {
+ { FR_CONF_POINTER("status_check", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_check_config },
+
+ { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
+
+ { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },
+
+ { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },
+
+ CONF_PARSER_TERMINATOR
+};
/*
* A mapping of configuration file names to internal variables.
*/
static conf_parser_t const module_config[] = {
- { FR_CONF_OFFSET_TYPE_FLAGS("transport", FR_TYPE_VOID, 0, rlm_radius_t, io_submodule),
- .func = module_rlm_submodule_parse },
+ { FR_CONF_OFFSET_FLAGS("mode", CONF_FLAG_REQUIRED, rlm_radius_t, mode), .func = mode_parse, .dflt = "proxy" },
+
+ { FR_CONF_OFFSET_REF(rlm_radius_t, fd_config, fr_bio_fd_client_config) },
{ FR_CONF_OFFSET_FLAGS("type", CONF_FLAG_NOT_EMPTY | CONF_FLAG_MULTI | CONF_FLAG_REQUIRED, rlm_radius_t, types),
.func = type_parse },
- { FR_CONF_OFFSET("replicate", rlm_radius_t, replicate) },
+ { FR_CONF_OFFSET_FLAGS("replicate", CONF_FLAG_DEPRECATED, rlm_radius_t, replicate) },
- { FR_CONF_OFFSET("synchronous", rlm_radius_t, synchronous) },
+ { FR_CONF_OFFSET_FLAGS("synchronous", CONF_FLAG_DEPRECATED, rlm_radius_t, synchronous) },
- { FR_CONF_OFFSET("originate", rlm_radius_t, originate) },
+ { FR_CONF_OFFSET_FLAGS("originate", CONF_FLAG_DEPRECATED, rlm_radius_t, originate) },
- { FR_CONF_POINTER("status_check", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_check_config },
+ { FR_CONF_OFFSET("max_packet_size", rlm_radius_t, max_packet_size), .dflt = "4096" },
+ { FR_CONF_OFFSET("max_send_coalesce", rlm_radius_t, max_send_coalesce), .dflt = "1024" },
{ FR_CONF_OFFSET("max_attributes", rlm_radius_t, max_attributes), .dflt = STRINGIFY(RADIUS_MAX_ATTRIBUTES) },
{ FR_CONF_OFFSET("revive_interval", rlm_radius_t, revive_interval) },
- { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
-
CONF_PARSER_TERMINATOR
};
static fr_dict_attr_t const *attr_packet_type;
static fr_dict_attr_t const *attr_proxy_state;
+static fr_dict_attr_t const *attr_error_cause;
+static fr_dict_attr_t const *attr_event_timestamp;
+static fr_dict_attr_t const *attr_extended_attribute_1;
+static fr_dict_attr_t const *attr_message_authenticator;
+static fr_dict_attr_t const *attr_eap_message;
+static fr_dict_attr_t const *attr_nas_identifier;
+static fr_dict_attr_t const *attr_original_packet_code;
+static fr_dict_attr_t const *attr_response_length;
+static fr_dict_attr_t const *attr_user_password;
+
extern fr_dict_attr_autoload_t rlm_radius_dict_attr[];
fr_dict_attr_autoload_t rlm_radius_dict_attr[] = {
{ .out = &attr_chap_challenge, .name = "CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
{ .out = &attr_chap_password, .name = "CHAP-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
{ .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
{ .out = &attr_proxy_state, .name = "Proxy-State", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
+
+ { .out = &attr_error_cause, .name = "Error-Cause", .type = FR_TYPE_UINT32, .dict = &dict_radius },
+ { .out = &attr_event_timestamp, .name = "Event-Timestamp", .type = FR_TYPE_DATE, .dict = &dict_radius},
+ { .out = &attr_extended_attribute_1, .name = "Extended-Attribute-1", .type = FR_TYPE_TLV, .dict = &dict_radius},
+ { .out = &attr_message_authenticator, .name = "Message-Authenticator", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
+ { .out = &attr_eap_message, .name = "EAP-Message", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
+ { .out = &attr_nas_identifier, .name = "NAS-Identifier", .type = FR_TYPE_STRING, .dict = &dict_radius},
+ { .out = &attr_original_packet_code, .name = "Extended-Attribute-1.Original-Packet-Code", .type = FR_TYPE_UINT32, .dict = &dict_radius},
+ { .out = &attr_response_length, .name = "Extended-Attribute-1.Response-Length", .type = FR_TYPE_UINT32, .dict = &dict_radius },
+ { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius},
+
{ NULL }
};
+#include "bio.c"
+
+static fr_table_num_sorted_t mode_names[] = {
+ { L("client"), RLM_RADIUS_MODE_CLIENT },
+ { L("proxy"), RLM_RADIUS_MODE_PROXY },
+ { L("replicate"), RLM_RADIUS_MODE_REPLICATE },
+ { L("unconnected-replicate"), RLM_RADIUS_MODE_UNCONNECTED_REPLICATE },
+// { L("unconnected-proxy"), RLM_RADIUS_MODE_UNCONNECTED_PROXY },
+};
+static size_t mode_names_len = NUM_ELEMENTS(mode_names);
+
+
+/** Set the mode of operation
+ *
+ * @param[in] ctx to allocate data in (instance of rlm_radius).
+ * @param[out] out Where to write the parsed data.
+ * @param[in] parent Base structure address.
+ * @param[in] ci #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mode_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
+ CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
+{
+ char const *name = cf_pair_value(cf_item_to_pair(ci));
+ rlm_radius_mode_t mode;
+ rlm_radius_t *inst = talloc_get_type_abort(parent, rlm_radius_t);
+
+ mode = fr_table_value_by_str(mode_names, name, RLM_RADIUS_MODE_INVALID);
+
+ /*
+ * Commented out until we upgrade the old configurations.
+ */
+ if (mode == RLM_RADIUS_MODE_INVALID) {
+ cf_log_err(ci, "Invalid mode name \"%s\"", name);
+ return -1;
+ }
+
+ *(rlm_radius_mode_t *) out = mode;
+
+ /*
+ * Normally we want connected sockets, in which case we push additional configuration for connected sockets.
+ */
+ if ((mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
+ (mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
+
+ inst->fd_config.type = FR_BIO_FD_CONNECTED;
+
+ if (cf_section_rules_push(cs, connected_config) < 0) return -1;
+
+ } else {
+ inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
+ }
+
+ return 0;
+}
+
+
/** Set which types of packets we can parse
*
* @param[in] ctx to allocate data in (instance of rlm_radius).
*/
cf_section_rule_push(cs, &type_interval_config[code]);
- memcpy(out, &code, sizeof(code));
+ *(uint32_t *) out = code;
return 0;
}
/*
* Check for proxy loops.
*/
- if (!inst->originate && RDEBUG_ENABLED) {
+ if ((inst->mode == RLM_RADIUS_MODE_PROXY) && RDEBUG_ENABLED) {
fr_dcursor_t cursor;
for (vp = fr_pair_dcursor_by_da_init(&cursor, &request->request_pairs, attr_proxy_state);
vp = fr_dcursor_next(&cursor)) {
if (vp->vp_length != 4) continue;
- if (memcmp(&inst->proxy_state, vp->vp_octets, 4) == 0) {
+ if (memcmp(&inst->common_ctx.proxy_state, vp->vp_octets, 4) == 0) {
RWARN("Possible proxy loop - please check server configuration.");
break;
}
}
}
+ /*
+ * @todo - check for proxy loops for client && replicate, too.
+ *
+ * This only catches "self loops", but it may be worth doing.
+ */
+
if (request->packet->code != FR_RADIUS_CODE_ACCESS_REQUEST) return;
if (fr_pair_find_by_da(&request->request_pairs, NULL, attr_chap_password) &&
if ((request->packet->code >= FR_RADIUS_CODE_MAX) ||
!fr_time_delta_ispos(inst->retry[request->packet->code].irt)) { /* can't be zero */
- REDEBUG("Invalid packet code %d", request->packet->code);
+ REDEBUG("Invalid packet code %u", request->packet->code);
+ RETURN_MODULE_FAIL;
+ }
+
+ /*
+ * Unconnected sockets use %radius.replicate(ip, port, secret),
+ * or %radius.sendto(ip, port, secret)
+ */
+ if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
+ (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ REDEBUG("When using 'mode = unconnected-*', this module cannot be used in-place. Instead, it must be called via a function call");
RETURN_MODULE_FAIL;
}
* return another code which indicates what happened to
* the request...
*/
- return inst->io->enqueue(&rcode, inst->io_submodule->data,
- module_thread(inst->io_submodule)->data, request);
+ return mod_enqueue(&rcode, inst,
+ module_thread(mctx->mi)->data, request);
}
+
static int mod_instantiate(module_inst_ctx_t const *mctx)
{
size_t i, num_types;
rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
CONF_SECTION *conf = mctx->mi->conf;
- inst->io = (rlm_radius_io_t const *)inst->io_submodule->exported; /* Public symbol exported by the module */
inst->name = mctx->mi->name;
inst->received_message_authenticator = talloc_zero(NULL, bool); /* Allocated outside of inst to default protection */
/*
- * These limits are specific to RADIUS, and cannot be over-ridden
+ * Allow explicit setting of mode.
*/
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
+ if (inst->mode != RLM_RADIUS_MODE_INVALID) goto check_others;
+
+ /*
+ * If not set, try to insinuate it from context.
+ */
+ if (inst->replicate) {
+ if (inst->originate) {
+ cf_log_err(conf, "Cannot set 'replicate=true' and 'originate=true' at the same time.");
+ return -1;
+ }
+
+ if (inst->synchronous) {
+ cf_log_warn(conf, "Ignoring 'synchronous=true' due to 'replicate=true'");
+ }
+
+ inst->mode = RLM_RADIUS_MODE_REPLICATE;
+ goto check_others;
+ }
+
+ /*
+ * Argubly we should be allowed to do synchronous proxying _and_ originating client packets.
+ *
+ * However, the previous code didn't really do that consistently.
+ */
+ if (inst->synchronous && inst->originate) {
+ cf_log_err(conf, "Cannot set 'synchronous=true' and 'originate=true'");
+ return -1;
+ }
+
+ if (inst->synchronous) {
+ inst->mode = RLM_RADIUS_MODE_PROXY;
+ } else {
+ inst->mode = RLM_RADIUS_MODE_CLIENT;
+ }
+
+check_others:
+ /*
+ * Replication is write-only, and append by default.
+ */
+ if (inst->mode == RLM_RADIUS_MODE_REPLICATE) {
+ if (inst->fd_config.filename && (inst->fd_config.flags != O_WRONLY)) {
+ cf_log_info(conf, "Setting 'flags = write-only' for writing to a file");
+ }
+ inst->fd_config.flags = O_WRONLY | O_APPEND;
+
+ } else if (inst->fd_config.filename) {
+ cf_log_err(conf, "When using an output 'filename', you MUST set 'mode = replicate'");
+ return -1;
+
+ } else {
+ /*
+ * All other IO is read+write.
+ */
+ inst->fd_config.flags = O_RDWR;
+ }
+
+ if (fr_bio_fd_check_config(&inst->fd_config) < 0) {
+ cf_log_perr(conf, "Invalid configuration");
+ return -1;
+ }
+
+ /*
+ * Clamp max_packet_size first before checking recv_buff and send_buff
+ */
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
+
+ if ((inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
+ (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ /*
+ * These limits are specific to RADIUS, and cannot be over-ridden
+ */
+ FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
+ FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
+ FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
+ } else {
+ if (inst->fd_config.src_port != 0) {
+ cf_log_err(conf, "Cannot set 'src_port' when using 'mode = unconnected'");
+ return -1;
+ }
+ }
FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
num_types = talloc_array_length(inst->types);
fr_assert(num_types > 0);
+ inst->timeout_retry = (fr_retry_config_t) {
+ .mrc = 1,
+ .mrd = inst->response_window,
+ };
+
+ inst->common_ctx = (fr_radius_ctx_t) {
+ .secret = inst->secret,
+ .secret_length = inst->secret ? talloc_array_length(inst->secret) - 1 : 0,
+ .proxy_state = fr_rand(),
+ };
+
/*
* Allow for O(1) lookup later...
*/
* If we're replicating, we don't care if the other end
* is alive.
*/
- if (inst->replicate && inst->status_check) {
- cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'replicate = true'",
- fr_radius_packet_name[inst->status_check]);
- inst->status_check = 0;
+ if (inst->status_check) {
+ if (inst->mode == RLM_RADIUS_MODE_REPLICATE) {
+ cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = replicate'",
+ fr_radius_packet_name[inst->status_check]);
+ inst->status_check = false;
+
+ } else if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
+ (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = unconnected-*'",
+ fr_radius_packet_name[inst->status_check]);
+ inst->status_check = false;
+ }
}
-
/*
* If we have status checks, then do some sanity checks.
* Status-Server is always allowed. Otherwise, the
* section, to be sure that (e.g.) Access-Request
* contains User-Name, etc.
*/
+
+ if (inst->fd_config.filename) {
+ cf_log_info(conf, "Disabling status checks for output file %s", inst->fd_config.filename);
+ inst->status_check = 0;
+ }
+ }
+
+ /*
+ * Files and unix sockets can just have us call write().
+ */
+ if (inst->fd_config.filename || inst->fd_config.path) {
+ inst->max_send_coalesce = 1;
}
+ inst->trunk_conf.req_pool_headers = 4; /* One for the request, one for the buffer, one for the tracking binding, one for Proxy-State VP */
+ inst->trunk_conf.req_pool_size = 1024 + sizeof(fr_pair_t) + 20;
+
/*
- * Don't sanity check the async timers if we're doing
- * synchronous proxying.
+ * Only check the async timers when we're acting as a client.
*/
- if (inst->synchronous) goto setup_io_submodule;
+ if (inst->mode != RLM_RADIUS_MODE_CLIENT) {
+ return 0;
+ }
/*
* Set limits on retransmission timers
FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrd, <=, fr_time_delta_from_sec(30));
}
-setup_io_submodule:
- /*
- * Get random Proxy-State identifier for this module.
- */
- inst->proxy_state = fr_rand();
+ return 0;
+}
+
+static int mod_bootstrap(module_inst_ctx_t const *mctx)
+{
+ xlat_t *xlat;
+ rlm_radius_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
+
+ switch (inst->mode) {
+ case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
+ xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "sendto.ipaddr", xlat_radius_replicate, FR_TYPE_VOID);
+ xlat_func_args_set(xlat, xlat_radius_send_args);
+ break;
+
+ case RLM_RADIUS_MODE_UNCONNECTED_PROXY:
+ fr_assert(0); /* not implemented */
+ break;
+
+ default:
+ break;
+ }
return 0;
}
+
static int mod_detach(module_detach_ctx_t const *mctx)
{
rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
.onload = mod_load,
.unload = mod_unload,
+ .bootstrap = mod_bootstrap,
.instantiate = mod_instantiate,
- .detach = mod_detach
+ .detach = mod_detach,
+
+ .thread_inst_size = sizeof(bio_thread_t),
+ .thread_inst_type = "bio_thread_t",
+ .thread_instantiate = mod_thread_instantiate,
},
.method_group = {
.bindings = (module_method_binding_t[]){
#include <freeradius-devel/util/retry.h>
#include <freeradius-devel/unlang/module.h>
#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/radius/bio.h>
+
+#include <freeradius-devel/bio/fd.h>
/*
* $Id$
*/
typedef struct rlm_radius_s rlm_radius_t;
-typedef struct rlm_radius_io_s rlm_radius_io_t;
+
+typedef enum {
+ RLM_RADIUS_MODE_INVALID = 0,
+ RLM_RADIUS_MODE_PROXY,
+ RLM_RADIUS_MODE_CLIENT,
+ RLM_RADIUS_MODE_REPLICATE,
+ RLM_RADIUS_MODE_UNCONNECTED_REPLICATE,
+ RLM_RADIUS_MODE_UNCONNECTED_PROXY,
+} rlm_radius_mode_t;
/*
* Define a structure for our module configuration.
*/
struct rlm_radius_s {
+ fr_bio_fd_config_t fd_config; //!< for now MUST be at the start!
+
char const *name;
- module_instance_t *io_submodule;
- rlm_radius_io_t const *io; //!< Public symbol exported by the submodule.
fr_time_delta_t response_window;
fr_time_delta_t zombie_period;
fr_time_delta_t revive_interval;
+ char const *secret; //!< Shared secret.
+
+ uint32_t max_packet_size; //!< Maximum packet size.
+ uint16_t max_send_coalesce; //!< Maximum number of packets to coalesce into one mmsg call.
+
+ fr_radius_ctx_t common_ctx;
+
bool replicate; //!< Ignore responses.
bool synchronous; //!< Retransmit when receiving a duplicate request.
- bool originate; //!< Originating packets, instead of proxying existing ones.
+ bool originate; //!< Originating packets, instead of proxying existing ones.
///< Controls whether Proxy-State is added to the outbound
- ///< request.
+ ///< request
+ rlm_radius_mode_t mode; //!< proxy, client, etc.
uint32_t max_attributes; //!< Maximum number of attributes to decode in response.
fr_radius_require_ma_t require_message_authenticator; //!< Require Message-Authenticator in responses.
bool *received_message_authenticator; //!< Received Message-Authenticator in responses.
- uint32_t proxy_state; //!< Unique ID (mostly) of this module.
uint32_t *types; //!< array of allowed packet types
uint32_t status_check; //!< code of status-check type
map_list_t status_check_map; //!< attributes for the status-server checks
///< mark the connection as alive.
bool allowed[FR_RADIUS_CODE_MAX];
+
+ fr_retry_config_t timeout_retry;
fr_retry_config_t retry[FR_RADIUS_CODE_MAX];
trunk_conf_t trunk_conf; //!< trunk configuration
};
-
-/** Enqueue a request_t to an IO submodule
- *
- */
-typedef unlang_action_t (*rlm_radius_io_enqueue_t)(rlm_rcode_t *p_result, void *instance, void *thread, request_t *request);
-
-/** Public structure describing an I/O path for an outgoing socket.
- *
- * This structure is exported by client I/O modules e.g. rlm_radius_udp.
- */
-struct rlm_radius_io_s {
- module_t common; //!< Common fields to all loadable modules.
- rlm_radius_io_enqueue_t enqueue; //!< Enqueue a request_t with an IO submodule.
-};
#include <freeradius-devel/util/debug.h>
#include "track.h"
-#include "rlm_radius.h"
/** Create an radius_track_t
*
fr_dlist_insert_tail(&tt->free_list, &tt->id[i]);
}
- tt->next_id = fr_rand() & 0xff;
-
return tt;
}
-/** Compare two radius_track_entry_t
- *
- */
-static int8_t te_cmp(void const *one, void const *two)
-{
- radius_track_entry_t const *a = one;
- radius_track_entry_t const *b = two;
- int ret;
-
- ret = memcmp(a->vector, b->vector, sizeof(a->vector));
- return CMP(ret, 0);
-}
-
/** Ensures the entry is released when the ctx passed to radius_track_entry_reserve is freed
*
* @param[in] te_p Entry to release.
* don't use it". Ensure that we only return IDs
* which are in the static array.
*/
- if (!tt->use_authenticator && (te != &tt->id[te->id])) {
+ if (te != &tt->id[te->id]) {
talloc_free(te);
goto retry;
}
* There are no free entries, and we can't use the
* Request Authenticator. Oh well...
*/
- if (!tt->use_authenticator) {
- fr_strerror_const("No free entries");
- return -1;
- }
-
- /*
- * Get a new ID. It's value doesn't matter at this
- * point.
- */
- tt->next_id++;
- tt->next_id &= 0xff;
-
- /*
- * If needed, allocate a subtree.
- */
- if (!tt->subtree[tt->next_id]) {
- MEM(tt->subtree[tt->next_id] = fr_rb_inline_talloc_alloc(tt, radius_track_entry_t, node,
- te_cmp, NULL));
- }
-
- /*
- * Allocate a new one, and insert it into the appropriate subtree.
- */
- te = talloc_zero(tt, radius_track_entry_t);
- te->id = tt->next_id;
+ fr_strerror_const("No free entries");
+ return -1;
done:
te->tt = tt;
/*
* We're freeing a static ID, just go do that...
*/
- if (te == &tt->id[te->id]) {
- /*
- * This entry MAY be in a subtree. If so, delete
- * it.
- */
- if (tt->subtree[te->id]) (void) fr_rb_delete(tt->subtree[te->id], te);
+ fr_assert(te == &tt->id[te->id]);
- goto done;
- }
-
- /*
- * At this point, it MUST be talloc'd.
- */
- (void) talloc_get_type_abort(te, radius_track_entry_t);
-
- /*
- * Delete it from the tracking subtree.
- */
- fr_assert(tt->subtree[te->id] != NULL);
- (void) fr_rb_delete(tt->subtree[te->id], te);
-
- /*
- * Try to free memory if the system gets idle. If the
- * system is busy, we will try to keep entries in the
- * free list. If the system becomes completely idle, we
- * will clear the free list.
- */
- if (fr_dlist_num_elements(&tt->free_list) > tt->num_requests) {
- talloc_free(te);
- *te_to_free = NULL;
- return 0;
- }
-
- /*
- * Otherwise put it back on the free list.
- */
-done:
fr_dlist_insert_tail(&tt->free_list, te);
*te_to_free = NULL;
*/
int radius_track_entry_update(radius_track_entry_t *te, uint8_t const *vector)
{
+#ifndef NDEBUG
radius_track_t *tt = te->tt;
+#endif
fr_assert(tt);
- /*
- * The authentication vector may have changed.
- */
- if (tt->subtree[te->id]) (void) fr_rb_delete(tt->subtree[te->id], te);
-
memcpy(te->vector, vector, sizeof(te->vector));
/*
*
* @todo - gracefully handle fallback if the server screws up.
*/
- if (!tt->use_authenticator) {
- fr_assert(te == &tt->id[te->id]);
- return 0;
- }
-
- /*
- * Insert it into the tree of authenticators
- *
- * We do this even if it was allocated from the static
- * array. That way if the server responds with
- * Original-Request-Authenticator, we can easily find it.
- */
- if (!fr_rb_insert(tt->subtree[te->id], te)) return -1;
-
+ fr_assert(te == &tt->id[te->id]);
return 0;
}
*/
radius_track_entry_t *radius_track_entry_find(radius_track_t *tt, uint8_t packet_id, uint8_t const *vector)
{
- radius_track_entry_t my_te, *te;
+ radius_track_entry_t *te;
(void) talloc_get_type_abort(tt, radius_track_t);
/*
* Just use the static array.
*/
- if (!tt->use_authenticator || !vector) {
- te = &tt->id[packet_id];
-
- /*
- * Not in use, die.
- */
- if (!te->request) return NULL;
-
- /*
- * Ignore the Request Authenticator, as the
- * caller doesn't have it.
- */
- return te;
- }
+ te = &tt->id[packet_id];
/*
- * The entry MAY be in the subtree!
+ * Not in use, die.
*/
- memcpy(&my_te.vector, vector, sizeof(my_te.vector));
+ if (!te->request) return NULL;
- te = fr_rb_find(tt->subtree[packet_id], &my_te);
+ if (!vector) return te;
/*
- * Not found, the packet MAY have been allocated in the
- * old-style method prior to negotiation of
- * Original-Request-Identifier.
+ * Protocol-Error and Original-Packet-Vector <sigh>
+ *
+ * This should arguably have been Original-Packet-Code, but we are stupid.
+ *
+ * @todo - Allow for multiple ID arrays, one for each packet code. Or, just switch to using
+ * src/protocols/radius/id.[ch].
*/
- if (!te) {
- te = &tt->id[packet_id];
-
- /*
- * Not in use, die.
- */
- if (!te->request) return NULL;
-
- // @todo - add a "generation" count for packets, so we can skip this after all outstanding packets
- // are using the new method. Hmm... probably just a timer "last sent packet with old-style"
- // and then compare it to te->start
-
- /*
- * We have the vector, so we need to check it.
- */
- if (memcmp(te->vector, vector, sizeof(te->vector)) != 0) {
- return NULL;
- }
-
- return te;
- }
-
- (void) talloc_get_type_abort(te, radius_track_entry_t);
- fr_assert(te->request != NULL);
+ if (memcmp(te->vector, vector, sizeof(te->vector)) != 0) return NULL;
+ /*
+ * Ignore the Request Authenticator, as the
+ * caller doesn't have it.
+ */
return te;
}
-/** Use Request Authenticator (or not) as an Identifier
- *
- * @param tt The radius_track_t tracking table
- * @param flag Whether or not to use it.
- */
-void radius_track_use_authenticator(radius_track_t *tt, bool flag)
-{
- (void) talloc_get_type_abort(tt, radius_track_t);
-
- tt->use_authenticator = flag;
-}
-
#ifndef NDEBUG
/** Print out the state of every tracking entry
*
///< when its parent is freed. We also zero
///< out the tracking entry field in the parent.
- request_t *request; //!< as always...
+ request_t *request; //!< as always...
void *uctx; //!< Result/resumption context.
fr_dlist_head_t free_list; //!< so we allocate by least recently used
- bool use_authenticator; //!< whether to use the request authenticator as an ID
- int next_id; //!< next ID to allocate
-
radius_track_entry_t id[UINT8_MAX + 1]; //!< which ID was used
- fr_rb_tree_t *subtree[UINT8_MAX + 1]; //!< for Original-Request-Authenticator
-
#ifndef NDEBUG
uint64_t operation; //!< Incremented each alloc and de-alloc
#endif
radius_track_entry_t *radius_track_entry_find(radius_track_t *tt, uint8_t packet_id,
uint8_t const *vector) CC_HINT(nonnull(1));
-
-void radius_track_use_authenticator(radius_track_t *te, bool flag) CC_HINT(nonnull);
+++ /dev/null
-# rlm_radius
-## Metadata
-<dl>
- <dt>category</dt><dd>io</dd>
-</dl>
-
-## Summary
-Allows Access-Requests, Accounting-Requests, CoA-Requests and Disconnect-Messages to be sent during request processing.
-
-This module can be used to implement proxying and request fan-out, as well as synchronous and asynchronous CoA and DM.
+++ /dev/null
-#
-# For now this is tracked in Git, but isn't part of the
-# normal build.
-#
-ifneq "${WITH_RADIUS2}" ""
-TARGETNAME := rlm_radius
-TARGET := $(TARGETNAME)$(L)
-
-SOURCES := rlm_radius.c track.c
-
-TGT_PREREQS := libfreeradius-radius$(L) libfreeradius-bio-config$(L) libfreeradius-bio$(L)
-LOG_ID_LIB = 39
-
-endif
+++ /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_radius.c
- * @brief A RADIUS client library.
- *
- * @copyright 2016 The FreeRADIUS server project
- * @copyright 2016 Network RADIUS SAS
- */
-RCSID("$Id$")
-
-#include <freeradius-devel/io/application.h>
-#include <freeradius-devel/server/modpriv.h>
-#include <freeradius-devel/unlang/xlat_func.h>
-#include <freeradius-devel/util/debug.h>
-#include <freeradius-devel/util/dlist.h>
-
-#include "rlm_radius.h"
-
-static int mode_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
-static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
-static int status_check_type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
-static int status_check_update_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
-
-static conf_parser_t const status_check_config[] = {
- { FR_CONF_OFFSET_TYPE_FLAGS("type", FR_TYPE_VOID, 0, rlm_radius_t, status_check),
- .func = status_check_type_parse },
-
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t const status_check_update_config[] = {
- { FR_CONF_OFFSET_TYPE_FLAGS("update", FR_TYPE_VOID, CONF_FLAG_SUBSECTION | CONF_FLAG_REQUIRED, rlm_radius_t, status_check_map),
- .name2 = CF_IDENT_ANY,
- .func = status_check_update_parse },
- { FR_CONF_OFFSET("num_answers_to_alive", rlm_radius_t, num_answers_to_alive), .dflt = STRINGIFY(3) },
-
- CONF_PARSER_TERMINATOR
-};
-
-/*
- * Retransmission intervals for the packets we support.
- */
-static conf_parser_t auth_config[] = {
- { FR_CONF_OFFSET("initial_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_ACCESS_REQUEST].irt), .dflt = STRINGIFY(2) },
- { FR_CONF_OFFSET("max_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrt), .dflt = STRINGIFY(16) },
- { FR_CONF_OFFSET("max_rtx_count", rlm_radius_t, retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrc), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_duration", rlm_radius_t, retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrd), .dflt = STRINGIFY(30) },
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t acct_config[] = {
- { FR_CONF_OFFSET("initial_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].irt), .dflt = STRINGIFY(2) },
- { FR_CONF_OFFSET("max_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrt), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_count", rlm_radius_t, retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrc), .dflt = STRINGIFY(1) },
- { FR_CONF_OFFSET("max_rtx_duration", rlm_radius_t, retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrd), .dflt = STRINGIFY(30) },
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t status_config[] = {
- { FR_CONF_OFFSET("initial_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_STATUS_SERVER].irt), .dflt = STRINGIFY(2) },
- { FR_CONF_OFFSET("max_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_STATUS_SERVER].mrt), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_count", rlm_radius_t, retry[FR_RADIUS_CODE_STATUS_SERVER].mrc), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_duration", rlm_radius_t, retry[FR_RADIUS_CODE_STATUS_SERVER].mrd), .dflt = STRINGIFY(30) },
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t coa_config[] = {
- { FR_CONF_OFFSET("initial_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_COA_REQUEST].irt), .dflt = STRINGIFY(2) },
- { FR_CONF_OFFSET("max_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_COA_REQUEST].mrt), .dflt = STRINGIFY(16) },
- { FR_CONF_OFFSET("max_rtx_count", rlm_radius_t, retry[FR_RADIUS_CODE_COA_REQUEST].mrc), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_duration", rlm_radius_t, retry[FR_RADIUS_CODE_COA_REQUEST].mrd), .dflt = STRINGIFY(30) },
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t disconnect_config[] = {
- { FR_CONF_OFFSET("initial_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].irt), .dflt = STRINGIFY(2) },
- { FR_CONF_OFFSET("max_rtx_time", rlm_radius_t, retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrt), .dflt = STRINGIFY(16) },
- { FR_CONF_OFFSET("max_rtx_count", rlm_radius_t, retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrc), .dflt = STRINGIFY(5) },
- { FR_CONF_OFFSET("max_rtx_duration", rlm_radius_t, retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrd), .dflt = STRINGIFY(30) },
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t const transport_config[] = {
- { FR_CONF_OFFSET_FLAGS("secret", CONF_FLAG_REQUIRED, rlm_radius_t, secret) },
-
- CONF_PARSER_TERMINATOR
-};
-
-/*
- * We only parse the pool options if we're connected.
- */
-static conf_parser_t const connected_config[] = {
- { FR_CONF_POINTER("status_check", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_check_config },
-
- { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
-
- { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },
-
- { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },
-
- CONF_PARSER_TERMINATOR
-};
-
-/*
- * A mapping of configuration file names to internal variables.
- */
-static conf_parser_t const module_config[] = {
- { FR_CONF_OFFSET_FLAGS("mode", CONF_FLAG_REQUIRED, rlm_radius_t, mode), .func = mode_parse, .dflt = "proxy" },
-
- { FR_CONF_OFFSET_REF(rlm_radius_t, fd_config, fr_bio_fd_client_config) },
-
- { FR_CONF_OFFSET_FLAGS("type", CONF_FLAG_NOT_EMPTY | CONF_FLAG_MULTI | CONF_FLAG_REQUIRED, rlm_radius_t, types),
- .func = type_parse },
-
- { FR_CONF_OFFSET_FLAGS("replicate", CONF_FLAG_DEPRECATED, rlm_radius_t, replicate) },
-
- { FR_CONF_OFFSET_FLAGS("synchronous", CONF_FLAG_DEPRECATED, rlm_radius_t, synchronous) },
-
- { FR_CONF_OFFSET_FLAGS("originate", CONF_FLAG_DEPRECATED, rlm_radius_t, originate) },
-
- { FR_CONF_OFFSET("max_packet_size", rlm_radius_t, max_packet_size), .dflt = "4096" },
- { FR_CONF_OFFSET("max_send_coalesce", rlm_radius_t, max_send_coalesce), .dflt = "1024" },
-
- { FR_CONF_OFFSET("max_attributes", rlm_radius_t, max_attributes), .dflt = STRINGIFY(RADIUS_MAX_ATTRIBUTES) },
-
- { FR_CONF_OFFSET("require_message_authenticator", rlm_radius_t, require_message_authenticator),
- .func = cf_table_parse_int,
- .uctx = &(cf_table_parse_ctx_t){ .table = fr_radius_require_ma_table, .len = &fr_radius_require_ma_table_len },
- .dflt = "no" },
-
- { FR_CONF_OFFSET("response_window", rlm_radius_t, response_window), .dflt = STRINGIFY(20) },
-
- { FR_CONF_OFFSET("zombie_period", rlm_radius_t, zombie_period), .dflt = STRINGIFY(40) },
-
- { FR_CONF_OFFSET("revive_interval", rlm_radius_t, revive_interval) },
-
- CONF_PARSER_TERMINATOR
-};
-
-static conf_parser_t const type_interval_config[FR_RADIUS_CODE_MAX] = {
- [FR_RADIUS_CODE_ACCESS_REQUEST] = { FR_CONF_POINTER("Access-Request", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) auth_config },
-
- [FR_RADIUS_CODE_ACCOUNTING_REQUEST] = { FR_CONF_POINTER("Accounting-Request", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) acct_config },
- [FR_RADIUS_CODE_STATUS_SERVER] = { FR_CONF_POINTER("Status-Server", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_config },
- [FR_RADIUS_CODE_COA_REQUEST] = { FR_CONF_POINTER("CoA-Request", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) coa_config },
- [FR_RADIUS_CODE_DISCONNECT_REQUEST] = { FR_CONF_POINTER("Disconnect-Request", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) disconnect_config },
-};
-
-static fr_dict_t const *dict_radius;
-
-extern fr_dict_autoload_t rlm_radius_dict[];
-fr_dict_autoload_t rlm_radius_dict[] = {
- { .out = &dict_radius, .proto = "radius" },
- { NULL }
-};
-
-static fr_dict_attr_t const *attr_chap_challenge;
-static fr_dict_attr_t const *attr_chap_password;
-static fr_dict_attr_t const *attr_packet_type;
-static fr_dict_attr_t const *attr_proxy_state;
-
-static fr_dict_attr_t const *attr_error_cause;
-static fr_dict_attr_t const *attr_event_timestamp;
-static fr_dict_attr_t const *attr_extended_attribute_1;
-static fr_dict_attr_t const *attr_message_authenticator;
-static fr_dict_attr_t const *attr_eap_message;
-static fr_dict_attr_t const *attr_nas_identifier;
-static fr_dict_attr_t const *attr_original_packet_code;
-static fr_dict_attr_t const *attr_response_length;
-static fr_dict_attr_t const *attr_user_password;
-
-extern fr_dict_attr_autoload_t rlm_radius_dict_attr[];
-fr_dict_attr_autoload_t rlm_radius_dict_attr[] = {
- { .out = &attr_chap_challenge, .name = "CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
- { .out = &attr_chap_password, .name = "CHAP-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
- { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
- { .out = &attr_proxy_state, .name = "Proxy-State", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
-
- { .out = &attr_error_cause, .name = "Error-Cause", .type = FR_TYPE_UINT32, .dict = &dict_radius },
- { .out = &attr_event_timestamp, .name = "Event-Timestamp", .type = FR_TYPE_DATE, .dict = &dict_radius},
- { .out = &attr_extended_attribute_1, .name = "Extended-Attribute-1", .type = FR_TYPE_TLV, .dict = &dict_radius},
- { .out = &attr_message_authenticator, .name = "Message-Authenticator", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
- { .out = &attr_eap_message, .name = "EAP-Message", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
- { .out = &attr_nas_identifier, .name = "NAS-Identifier", .type = FR_TYPE_STRING, .dict = &dict_radius},
- { .out = &attr_original_packet_code, .name = "Extended-Attribute-1.Original-Packet-Code", .type = FR_TYPE_UINT32, .dict = &dict_radius},
- { .out = &attr_response_length, .name = "Extended-Attribute-1.Response-Length", .type = FR_TYPE_UINT32, .dict = &dict_radius },
- { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius},
-
- { NULL }
-};
-
-#include "bio.c"
-
-static fr_table_num_sorted_t mode_names[] = {
- { L("client"), RLM_RADIUS_MODE_CLIENT },
- { L("proxy"), RLM_RADIUS_MODE_PROXY },
- { L("replicate"), RLM_RADIUS_MODE_REPLICATE },
- { L("unconnected-replicate"), RLM_RADIUS_MODE_UNCONNECTED_REPLICATE },
-// { L("unconnected-proxy"), RLM_RADIUS_MODE_UNCONNECTED_PROXY },
-};
-static size_t mode_names_len = NUM_ELEMENTS(mode_names);
-
-
-/** Set the mode of operation
- *
- * @param[in] ctx to allocate data in (instance of rlm_radius).
- * @param[out] out Where to write the parsed data.
- * @param[in] parent Base structure address.
- * @param[in] ci #CONF_PAIR specifying the name of the type module.
- * @param[in] rule unused.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int mode_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
- CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
-{
- char const *name = cf_pair_value(cf_item_to_pair(ci));
- rlm_radius_mode_t mode;
- rlm_radius_t *inst = talloc_get_type_abort(parent, rlm_radius_t);
-
- mode = fr_table_value_by_str(mode_names, name, RLM_RADIUS_MODE_INVALID);
-
-#if 0
- /*
- * Commented out until we upgrade the old configurations.
- */
- if (mode == RLM_RADIUS_MODE_INVALID) {
- cf_log_err(ci, "Invalid mode name \"%s\"", name);
- return -1;
- }
-#endif
-
- *(rlm_radius_mode_t *) out = mode;
-
- /*
- * Normally we want connected sockets, in which case we push additional configuration for connected sockets.
- */
- if ((mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
- (mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
-
- inst->fd_config.type = FR_BIO_FD_CONNECTED;
-
- if (cf_section_rules_push(cs, connected_config) < 0) return -1;
-
- } else {
- inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
- }
-
- return 0;
-}
-
-
-/** Set which types of packets we can parse
- *
- * @param[in] ctx to allocate data in (instance of rlm_radius).
- * @param[out] out Where to write the parsed data.
- * @param[in] parent Base structure address.
- * @param[in] ci #CONF_PAIR specifying the name of the type module.
- * @param[in] rule unused.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
- CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
-{
- char const *type_str = cf_pair_value(cf_item_to_pair(ci));
- CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
- fr_dict_enum_value_t const *type_enum;
- uint32_t code;
-
- /*
- * Must be the RADIUS module
- */
- fr_assert(cs && (strcmp(cf_section_name1(cs), "radius") == 0));
-
- /*
- * Allow the process module to be specified by
- * packet type.
- */
- type_enum = fr_dict_enum_by_name(attr_packet_type, type_str, -1);
- if (!type_enum) {
- invalid_code:
- cf_log_err(ci, "Unknown or invalid RADIUS packet type '%s'", type_str);
- return -1;
- }
-
- code = type_enum->value->vb_uint32;
-
- /*
- * Status-Server packets cannot be proxied.
- */
- if (code == FR_RADIUS_CODE_STATUS_SERVER) {
- cf_log_err(ci, "Invalid setting of 'type = Status-Server'. Status-Server packets cannot be proxied.");
- return -1;
- }
-
- if (!code ||
- (code >= FR_RADIUS_CODE_MAX) ||
- (!type_interval_config[code].name1)) goto invalid_code;
-
- /*
- * If we're doing async proxying, push the timers for the
- * various packet types.
- */
- cf_section_rule_push(cs, &type_interval_config[code]);
-
- *(uint32_t *) out = code;
-
- return 0;
-}
-
-/** Allow for Status-Server ping checks
- *
- * @param[in] ctx to allocate data in (instance of proto_radius).
- * @param[out] out Where to write our parsed data.
- * @param[in] parent Base structure address.
- * @param[in] ci #CONF_PAIR specifying the name of the type module.
- * @param[in] rule unused.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int status_check_type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
- CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
-{
- char const *type_str = cf_pair_value(cf_item_to_pair(ci));
- CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
- fr_dict_enum_value_t const *type_enum;
- uint32_t code;
-
- /*
- * Allow the process module to be specified by
- * packet type.
- */
- type_enum = fr_dict_enum_by_name(attr_packet_type, type_str, -1);
- if (!type_enum) {
- invalid_code:
- cf_log_err(ci, "Unknown or invalid RADIUS packet type '%s'", type_str);
- return -1;
- }
-
- code = type_enum->value->vb_uint32;
-
- /*
- * Cheat, and reuse the "type" array for allowed packet
- * types.
- */
- if (!code ||
- (code >= FR_RADIUS_CODE_MAX) ||
- (!type_interval_config[code].name1)) goto invalid_code;
-
- /*
- * Add irt / mrt / mrd / mrc parsing, in the parent
- * configuration section.
- */
- cf_section_rule_push(cf_item_to_section(cf_parent(cs)), &type_interval_config[code]);
-
- memcpy(out, &code, sizeof(code));
-
- /*
- * Nothing more to do here, so we stop.
- */
- if (code == FR_RADIUS_CODE_STATUS_SERVER) return 0;
-
- cf_section_rule_push(cs, status_check_update_config);
-
- return 0;
-}
-
-/** Allow the admin to set packet contents for Status-Server ping checks
- *
- * @param[in] ctx to allocate data in (instance of proto_radius).
- * @param[out] out Where to write our parsed data
- * @param[in] parent Base structure address.
- * @param[in] ci #CONF_SECTION specifying the things to update
- * @param[in] rule unused.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int status_check_update_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
- CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
-{
- int rcode;
- CONF_SECTION *cs;
- char const *name2;
- map_list_t *head = (map_list_t *)out;
-
- fr_assert(cf_item_is_section(ci));
- map_list_init(head);
-
- cs = cf_item_to_section(ci);
- name2 = cf_section_name2(cs);
- if (!name2 || (strcmp(name2, "request") != 0)) {
- cf_log_err(cs, "You must specify 'request' as the destination list");
- return -1;
- }
-
- /*
- * Compile the "update" section.
- */
- {
- tmpl_rules_t parse_rules = {
- .attr = {
- .dict_def = dict_radius,
- }
- };
-
- rcode = map_afrom_cs(ctx, head, cs, &parse_rules, &parse_rules, unlang_fixup_update, NULL, 128);
- if (rcode < 0) return -1; /* message already printed */
- if (map_list_empty(head)) {
- cf_log_err(cs, "'update' sections cannot be empty");
- return -1;
- }
- }
-
- /*
- * Rely on "bootstrap" to do sanity checks between 'type
- * = Access-Request', and 'update' containing passwords.
- */
- return 0;
-}
-
-
-/** Do any RADIUS-layer fixups for proxying.
- *
- */
-static void radius_fixups(rlm_radius_t const *inst, request_t *request)
-{
- fr_pair_t *vp;
-
- /*
- * Check for proxy loops.
- */
- if ((inst->mode == RLM_RADIUS_MODE_PROXY) && RDEBUG_ENABLED) {
- fr_dcursor_t cursor;
-
- for (vp = fr_pair_dcursor_by_da_init(&cursor, &request->request_pairs, attr_proxy_state);
- vp;
- vp = fr_dcursor_next(&cursor)) {
- if (vp->vp_length != 4) continue;
-
- if (memcmp(&inst->common_ctx.proxy_state, vp->vp_octets, 4) == 0) {
- RWARN("Possible proxy loop - please check server configuration.");
- break;
- }
- }
- }
-
- /*
- * @todo - check for proxy loops for client && replicate, too.
- *
- * This only catches "self loops", but it may be worth doing.
- */
-
- if (request->packet->code != FR_RADIUS_CODE_ACCESS_REQUEST) return;
-
- if (fr_pair_find_by_da(&request->request_pairs, NULL, attr_chap_password) &&
- !fr_pair_find_by_da(&request->request_pairs, NULL, attr_chap_challenge)) {
- MEM(pair_append_request(&vp, attr_chap_challenge) >= 0);
- fr_pair_value_memdup(vp, request->packet->vector, sizeof(request->packet->vector), true);
- }
-}
-
-
-/** Send packets outbound.
- *
- */
-static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
- rlm_radius_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_radius_t);
- rlm_rcode_t rcode;
- fr_client_t *client;
-
- if (!request->packet->code) {
- REDEBUG("You MUST specify a packet code");
- RETURN_MODULE_FAIL;
- }
-
- /*
- * Reserve Status-Server for ourselves, for link-specific
- * signaling.
- */
- if (request->packet->code == FR_RADIUS_CODE_STATUS_SERVER) {
- REDEBUG("Cannot proxy Status-Server packets");
- RETURN_MODULE_FAIL;
- }
-
- if ((request->packet->code >= FR_RADIUS_CODE_MAX) ||
- !fr_time_delta_ispos(inst->retry[request->packet->code].irt)) { /* can't be zero */
- REDEBUG("Invalid packet code %u", request->packet->code);
- RETURN_MODULE_FAIL;
- }
-
- /*
- * Unconnected sockets use %radius.replicate(ip, port, secret),
- * or %radius.sendto(ip, port, secret)
- */
- if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
- (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- REDEBUG("When using 'mode = unconnected-*', this module cannot be used in-place. Instead, it must be called via a function call");
- RETURN_MODULE_FAIL;
- }
-
- if (!inst->allowed[request->packet->code]) {
- REDEBUG("Packet code %s is disallowed by the configuration",
- fr_radius_packet_name[request->packet->code]);
- RETURN_MODULE_FAIL;
- }
-
- client = client_from_request(request);
- if (client && client->dynamic && !client->active) {
- REDEBUG("Cannot proxy packets which define dynamic clients");
- RETURN_MODULE_FAIL;
- }
-
- /*
- * Do any necessary RADIUS level fixups
- * - check Proxy-State
- * - do CHAP-Challenge fixups
- */
- radius_fixups(inst, request);
-
- /*
- * Push the request and it's data to the IO submodule.
- *
- * This may return YIELD, for "please yield", or it may
- * return another code which indicates what happened to
- * the request...
- */
- return mod_enqueue(&rcode, inst,
- module_thread(mctx->mi)->data, request);
-}
-
-
-static int mod_instantiate(module_inst_ctx_t const *mctx)
-{
- size_t i, num_types;
- rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
- CONF_SECTION *conf = mctx->mi->conf;
-
- inst->name = mctx->mi->name;
- inst->received_message_authenticator = talloc_zero(NULL, bool); /* Allocated outside of inst to default protection */
-
- /*
- * Allow explicit setting of mode.
- */
- if (inst->mode != RLM_RADIUS_MODE_INVALID) goto check_others;
-
- /*
- * If not set, try to insinuate it from context.
- */
- if (inst->replicate) {
- if (inst->originate) {
- cf_log_err(conf, "Cannot set 'replicate=true' and 'originate=true' at the same time.");
- return -1;
- }
-
- if (inst->synchronous) {
- cf_log_warn(conf, "Ignoring 'synchronous=true' due to 'replicate=true'");
- }
-
- inst->mode = RLM_RADIUS_MODE_REPLICATE;
- goto check_others;
- }
-
- /*
- * Argubly we should be allowed to do synchronous proxying _and_ originating client packets.
- *
- * However, the previous code didn't really do that consistently.
- */
- if (inst->synchronous && inst->originate) {
- cf_log_err(conf, "Cannot set 'synchronous=true' and 'originate=true'");
- return -1;
- }
-
- if (inst->synchronous) {
- inst->mode = RLM_RADIUS_MODE_PROXY;
- } else {
- inst->mode = RLM_RADIUS_MODE_CLIENT;
- }
-
-check_others:
- /*
- * Replication is write-only, and append by default.
- */
- if (inst->mode == RLM_RADIUS_MODE_REPLICATE) {
- if (inst->fd_config.filename && (inst->fd_config.flags != O_WRONLY)) {
- cf_log_info(conf, "Setting 'flags = write-only' for writing to a file");
- }
- inst->fd_config.flags = O_WRONLY | O_APPEND;
-
- } else if (inst->fd_config.filename) {
- cf_log_err(conf, "When using an output 'filename', you MUST set 'mode = replicate'");
- return -1;
-
- } else {
- /*
- * All other IO is read+write.
- */
- inst->fd_config.flags = O_RDWR;
- }
-
- if (fr_bio_fd_check_config(&inst->fd_config) < 0) {
- cf_log_perr(conf, "Invalid configuration");
- return -1;
- }
-
- /*
- * Clamp max_packet_size first before checking recv_buff and send_buff
- */
- FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
- FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
-
- if ((inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
- (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- /*
- * These limits are specific to RADIUS, and cannot be over-ridden
- */
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
- FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
- } else {
- if (inst->fd_config.src_port != 0) {
- cf_log_err(conf, "Cannot set 'src_port' when using 'mode = unconnected'");
- return -1;
- }
- }
-
- FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
-
- FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, <=, fr_time_delta_from_sec(120));
-
- if (!inst->status_check) {
- FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, >=, fr_time_delta_from_sec(10));
- FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, <=, fr_time_delta_from_sec(3600));
- }
-
- num_types = talloc_array_length(inst->types);
- fr_assert(num_types > 0);
-
- inst->timeout_retry = (fr_retry_config_t) {
- .mrc = 1,
- .mrd = inst->response_window,
- };
-
- inst->common_ctx = (fr_radius_ctx_t) {
- .secret = inst->secret,
- .secret_length = inst->secret ? talloc_array_length(inst->secret) - 1 : 0,
- .proxy_state = fr_rand(),
- };
-
- /*
- * Allow for O(1) lookup later...
- */
- for (i = 0; i < num_types; i++) {
- uint32_t code;
-
- code = inst->types[i];
- fr_assert(code > 0);
- fr_assert(code < FR_RADIUS_CODE_MAX);
-
- inst->allowed[code] = true;
- }
-
- fr_assert(inst->status_check < FR_RADIUS_CODE_MAX);
-
- /*
- * If we're replicating, we don't care if the other end
- * is alive.
- */
- if (inst->status_check) {
- if (inst->mode == RLM_RADIUS_MODE_REPLICATE) {
- cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = replicate'",
- fr_radius_packet_name[inst->status_check]);
- inst->status_check = false;
-
- } else if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
- (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = unconnected-*'",
- fr_radius_packet_name[inst->status_check]);
- inst->status_check = false;
- }
- }
-
- /*
- * If we have status checks, then do some sanity checks.
- * Status-Server is always allowed. Otherwise, the
- * status checks have to match one of the allowed
- * packets.
- */
- if (inst->status_check) {
- if (inst->status_check == FR_RADIUS_CODE_STATUS_SERVER) {
- inst->allowed[inst->status_check] = true;
-
- } else if (!inst->allowed[inst->status_check]) {
- cf_log_err(conf, "Using 'status_check = %s' requires also 'type = %s'",
- fr_radius_packet_name[inst->status_check], fr_radius_packet_name[inst->status_check]);
- return -1;
- }
-
- /*
- * @todo - check the contents of the "update"
- * section, to be sure that (e.g.) Access-Request
- * contains User-Name, etc.
- */
-
- if (inst->fd_config.filename) {
- cf_log_info(conf, "Disabling status checks for output file %s", inst->fd_config.filename);
- inst->status_check = 0;
- }
- }
-
- /*
- * Files and unix sockets can just have us call write().
- */
- if (inst->fd_config.filename || inst->fd_config.path) {
- inst->max_send_coalesce = 1;
- }
-
- inst->trunk_conf.req_pool_headers = 4; /* One for the request, one for the buffer, one for the tracking binding, one for Proxy-State VP */
- inst->trunk_conf.req_pool_size = 1024 + sizeof(fr_pair_t) + 20;
-
- /*
- * Only check the async timers when we're acting as a client.
- */
- if (inst->mode != RLM_RADIUS_MODE_CLIENT) {
- return 0;
- }
-
- /*
- * Set limits on retransmission timers
- */
- if (inst->allowed[FR_RADIUS_CODE_ACCESS_REQUEST]) {
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].irt, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrt, >=, fr_time_delta_from_sec(5));
- FR_INTEGER_BOUND_CHECK("Access-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrc, >=, 1);
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrd, >=, fr_time_delta_from_sec(5));
-
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].irt, <=, fr_time_delta_from_sec(3));
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrt, <=, fr_time_delta_from_sec(30));
- FR_INTEGER_BOUND_CHECK("Access-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrc, <=, 10);
- FR_TIME_DELTA_BOUND_CHECK("Access-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_ACCESS_REQUEST].mrd, <=, fr_time_delta_from_sec(30));
- }
-
- /*
- * Note that RFC 5080 allows for Accounting-Request to
- * have mrt=mrc=mrd = 0, which means "retransmit
- * forever". We allow that, with the restriction that
- * the server core will automatically free the request at
- * max_request_time.
- */
- if (inst->allowed[FR_RADIUS_CODE_ACCOUNTING_REQUEST]) {
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].irt, >=, fr_time_delta_from_sec(1));
-#if 0
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrt, >=, fr_time_delta_from_sec(5));
- FR_INTEGER_BOUND_CHECK("Accounting-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrc, >=, 0);
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrd, >=, fr_time_delta_from_sec(0));
-#endif
-
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].irt, <=, fr_time_delta_from_sec(3));
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrt, <=, fr_time_delta_from_sec(30));
- FR_INTEGER_BOUND_CHECK("Accounting-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrc, <=, 10);
- FR_TIME_DELTA_BOUND_CHECK("Accounting-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_ACCOUNTING_REQUEST].mrd, <=, fr_time_delta_from_sec(30));
- }
-
- /*
- * Status-Server
- */
- if (inst->allowed[FR_RADIUS_CODE_STATUS_SERVER]) {
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.initial_rtx_time", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].irt, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.max_rtx_time", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrt, >=, fr_time_delta_from_sec(5));
- FR_INTEGER_BOUND_CHECK("Status-Server.max_rtx_count", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrc, >=, 1);
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.max_rtx_duration", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrd, >=, fr_time_delta_from_sec(5));
-
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.initial_rtx_time", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].irt, <=, fr_time_delta_from_sec(3));
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.max_rtx_time", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrt, <=, fr_time_delta_from_sec(30));
- FR_INTEGER_BOUND_CHECK("Status-Server.max_rtx_count", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrc, <=, 10);
- FR_TIME_DELTA_BOUND_CHECK("Status-Server.max_rtx_duration", inst->retry[FR_RADIUS_CODE_STATUS_SERVER].mrd, <=, fr_time_delta_from_sec(30));
- }
-
- /*
- * CoA
- */
- if (inst->allowed[FR_RADIUS_CODE_COA_REQUEST]) {
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_COA_REQUEST].irt, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrt, >=, fr_time_delta_from_sec(5));
- FR_INTEGER_BOUND_CHECK("CoA-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrc, >=, 1);
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrd, >=, fr_time_delta_from_sec(5));
-
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_COA_REQUEST].irt, <=, fr_time_delta_from_sec(3));
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrt, <=, fr_time_delta_from_sec(60));
- FR_INTEGER_BOUND_CHECK("CoA-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrc, <=, 10);
- FR_TIME_DELTA_BOUND_CHECK("CoA-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_COA_REQUEST].mrd, <=, fr_time_delta_from_sec(30));
- }
-
- /*
- * Disconnect
- */
- if (inst->allowed[FR_RADIUS_CODE_DISCONNECT_REQUEST]) {
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].irt, >=, fr_time_delta_from_sec(1));
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrt, >=, fr_time_delta_from_sec(5));
- FR_INTEGER_BOUND_CHECK("Disconnect-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrc, >=, 1);
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrd, >=, fr_time_delta_from_sec(5));
-
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.initial_rtx_time", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].irt, <=, fr_time_delta_from_sec(3));
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.max_rtx_time", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrt, <=, fr_time_delta_from_sec(30));
- FR_INTEGER_BOUND_CHECK("Disconnect-Request.max_rtx_count", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrc, <=, 10);
- FR_TIME_DELTA_BOUND_CHECK("Disconnect-Request.max_rtx_duration", inst->retry[FR_RADIUS_CODE_DISCONNECT_REQUEST].mrd, <=, fr_time_delta_from_sec(30));
- }
-
- return 0;
-}
-
-static int mod_bootstrap(module_inst_ctx_t const *mctx)
-{
- xlat_t *xlat;
- rlm_radius_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
-
- switch (inst->mode) {
- case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
- xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "sendto.ipaddr", xlat_radius_replicate, FR_TYPE_VOID);
- xlat_func_args_set(xlat, xlat_radius_send_args);
- break;
-
- case RLM_RADIUS_MODE_UNCONNECTED_PROXY:
- fr_assert(0); /* not implemented */
- break;
-
- default:
- break;
- }
-
- return 0;
-}
-
-
-static int mod_detach(module_detach_ctx_t const *mctx)
-{
- rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
-
- talloc_free(inst->received_message_authenticator);
- return 0;
-}
-
-static int mod_load(void)
-{
- if (fr_radius_global_init() < 0) {
- PERROR("Failed initialising protocol library");
- return -1;
- }
- return 0;
-}
-
-static void mod_unload(void)
-{
- fr_radius_global_free();
-}
-
-/*
- * 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_radius;
-module_rlm_t rlm_radius = {
- .common = {
- .magic = MODULE_MAGIC_INIT,
- .name = "radius",
- .inst_size = sizeof(rlm_radius_t),
- .config = module_config,
-
- .onload = mod_load,
- .unload = mod_unload,
-
- .bootstrap = mod_bootstrap,
- .instantiate = mod_instantiate,
- .detach = mod_detach,
-
- .thread_inst_size = sizeof(bio_thread_t),
- .thread_inst_type = "bio_thread_t",
- .thread_instantiate = mod_thread_instantiate,
- },
- .method_group = {
- .bindings = (module_method_binding_t[]){
- { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = mod_process },
- MODULE_BINDING_TERMINATOR
- },
- }
-};
+++ /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
- */
-#include <freeradius-devel/io/atomic_queue.h>
-#include <freeradius-devel/server/base.h>
-#include <freeradius-devel/server/map.h>
-#include <freeradius-devel/server/module_rlm.h>
-#include <freeradius-devel/server/trunk.h>
-#include <freeradius-devel/util/dlist.h>
-#include <freeradius-devel/util/retry.h>
-#include <freeradius-devel/unlang/module.h>
-#include <freeradius-devel/radius/radius.h>
-#include <freeradius-devel/radius/bio.h>
-
-#include <freeradius-devel/bio/fd.h>
-
-/*
- * $Id$
- *
- * @file rlm_radius.h
- * @brief Structures for the RADIUS client packets
- *
- * @copyright 2017 Alan DeKok (aland@freeradius.org)
- */
-
-typedef struct rlm_radius_s rlm_radius_t;
-
-typedef enum {
- RLM_RADIUS_MODE_INVALID = 0,
- RLM_RADIUS_MODE_PROXY,
- RLM_RADIUS_MODE_CLIENT,
- RLM_RADIUS_MODE_REPLICATE,
- RLM_RADIUS_MODE_UNCONNECTED_REPLICATE,
- RLM_RADIUS_MODE_UNCONNECTED_PROXY,
-} rlm_radius_mode_t;
-
-/*
- * Define a structure for our module configuration.
- */
-struct rlm_radius_s {
- fr_bio_fd_config_t fd_config; //!< for now MUST be at the start!
-
- char const *name;
-
- fr_time_delta_t response_window;
- fr_time_delta_t zombie_period;
- fr_time_delta_t revive_interval;
-
- char const *secret; //!< Shared secret.
-
- uint32_t max_packet_size; //!< Maximum packet size.
- uint16_t max_send_coalesce; //!< Maximum number of packets to coalesce into one mmsg call.
-
- fr_radius_ctx_t common_ctx;
-
- bool replicate; //!< Ignore responses.
- bool synchronous; //!< Retransmit when receiving a duplicate request.
- bool originate; //!< Originating packets, instead of proxying existing ones.
- ///< Controls whether Proxy-State is added to the outbound
- ///< request
- rlm_radius_mode_t mode; //!< proxy, client, etc.
-
- uint32_t max_attributes; //!< Maximum number of attributes to decode in response.
-
- fr_radius_require_ma_t require_message_authenticator; //!< Require Message-Authenticator in responses.
- bool *received_message_authenticator; //!< Received Message-Authenticator in responses.
-
- uint32_t *types; //!< array of allowed packet types
- uint32_t status_check; //!< code of status-check type
- map_list_t status_check_map; //!< attributes for the status-server checks
- uint32_t num_answers_to_alive; //!< How many status check responses we need to
- ///< mark the connection as alive.
-
- bool allowed[FR_RADIUS_CODE_MAX];
-
- fr_retry_config_t timeout_retry;
- fr_retry_config_t retry[FR_RADIUS_CODE_MAX];
-
- trunk_conf_t trunk_conf; //!< trunk configuration
-};
+++ /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_radius/track.c
- * @brief Tracking RADUS client packets
- *
- * @copyright 2017 Network RADIUS SAS
- */
-RCSID("$Id$")
-
-#include <freeradius-devel/server/base.h>
-#include <freeradius-devel/util/rb.h>
-#include <freeradius-devel/io/application.h>
-#include <freeradius-devel/util/dlist.h>
-#include <freeradius-devel/util/debug.h>
-
-#include "track.h"
-
-/** Create an radius_track_t
- *
- * @param ctx the talloc ctx
- * @return
- * - NULL on error
- * - radius_track_t on success
- */
-radius_track_t *radius_track_alloc(TALLOC_CTX *ctx)
-{
- int i;
- radius_track_t *tt;
-
- MEM(tt = talloc_zero(ctx, radius_track_t));
-
- fr_dlist_init(&tt->free_list, radius_track_entry_t, entry);
-
- for (i = 0; i < 256; i++) {
- tt->id[i].id = i;
-#ifndef NDEBUG
- tt->id[i].file = __FILE__;
- tt->id[i].line = __LINE__;
-#endif
- fr_dlist_insert_tail(&tt->free_list, &tt->id[i]);
- }
-
- return tt;
-}
-
-
-/** Ensures the entry is released when the ctx passed to radius_track_entry_reserve is freed
- *
- * @param[in] te_p Entry to release.
- * @return 0
- */
-static int _radius_track_entry_release_on_free(radius_track_entry_t ***te_p)
-{
- radius_track_entry_release(*te_p);
-
- return 0;
-}
-
-/** Allocate a tracking entry.
- *
- * @param[in] file The allocation was made in.
- * @param[in] line The allocation was made on.
- * @param[out] te_out Where the tracking entry should be written.
- * If ctx is not-null, then this pointer must
- * remain valid for the lifetime of the ctx.
- * @param[in] ctx If not-null, the tracking entry release will
- * be bound to the lifetime of the talloc chunk.
- * @param[in] tt The radius_track_t tracking table.
- * @param[in] request The request which will send the proxied packet.
- * @param[in] code Of the outbound request.
- * @param[in] uctx The context to associate with the request
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-#ifndef NDEBUG
-int _radius_track_entry_reserve(char const *file, int line,
-#else
-int radius_track_entry_reserve(
-#endif
- radius_track_entry_t **te_out,
- TALLOC_CTX *ctx, radius_track_t *tt, request_t *request, uint8_t code, void *uctx)
-{
- radius_track_entry_t *te;
-
- if (!fr_cond_assert_msg(!*te_out, "Expected tracking entry to be NULL")) return -1;
-
-retry:
- te = fr_dlist_head(&tt->free_list);
- if (te) {
- fr_assert(te->request == NULL);
-
- /*
- * Mark it as used, and remove it from the free list.
- */
- fr_dlist_remove(&tt->free_list, te);
-
- /*
- * We've transitioned from "use it", to "oops,
- * don't use it". Ensure that we only return IDs
- * which are in the static array.
- */
- if (te != &tt->id[te->id]) {
- talloc_free(te);
- goto retry;
- }
-
- goto done;
- }
-
- /*
- * There are no free entries, and we can't use the
- * Request Authenticator. Oh well...
- */
- fr_strerror_const("No free entries");
- return -1;
-
-done:
- te->tt = tt;
- te->request = request;
- te->uctx = uctx;
- te->code = code;
-#ifndef NDEBUG
- te->operation = te->tt->operation++;
- te->file = file;
- te->line = line;
-#endif
- if (ctx) {
- te->binding = talloc_zero(ctx, radius_track_entry_t **);
- talloc_set_destructor(te->binding, _radius_track_entry_release_on_free);
- *(te->binding) = te_out;
- }
-
- /*
- * te->id is already allocated
- */
- tt->num_requests++;
-
- *te_out = te;
-
- return 0;
-}
-
-/** Release a tracking entry
- *
- * @param[in] file Allocation was released in.
- * @param[in] line Allocation was released on.
- * @param[in,out] te_to_free The #radius_track_entry_t allocated via #radius_track_entry_reserve.
- * @return
- * - <0 on error
- * - 0 on success
- */
-#ifndef NDEBUG
-int _radius_track_entry_release(char const *file, int line,
-#else
-int radius_track_entry_release(
-#endif
- radius_track_entry_t **te_to_free)
-{
- radius_track_entry_t *te = *te_to_free;
- radius_track_t *tt;
-
- if (!te) return 0;
-
- tt = talloc_get_type_abort(te->tt, radius_track_t); /* Make sure table is still valid */
-
- if (te->binding) {
- talloc_set_destructor(te->binding, NULL); /* Disarm the destructor */
- talloc_free(te->binding);
- }
-
-#ifndef NDEBUG
- te->operation = te->tt->operation++;
- te->file = file;
- te->line = line;
-#endif
-
- te->request = NULL;
-
- fr_assert(tt->num_requests > 0);
- tt->num_requests--;
-
- /*
- * We're freeing a static ID, just go do that...
- */
- fr_assert(te == &tt->id[te->id]);
-
- fr_dlist_insert_tail(&tt->free_list, te);
-
- *te_to_free = NULL;
-
- return 0;
-}
-
-/** Update a tracking entry with the authentication vector
- *
- * @param te The radius_track_entry_t, via radius_track_entry_reserve()
- * @param vector The authentication vector for the packet we're sending
- * @return
- * - <0 on error
- * - 0 on success
- */
-int radius_track_entry_update(radius_track_entry_t *te, uint8_t const *vector)
-{
- radius_track_t *tt = te->tt;
-
- fr_assert(tt);
-
- memcpy(te->vector, vector, sizeof(te->vector));
-
- /*
- * If we're not using the Request Authenticator, the
- * tracking entry must be in the static array.
- *
- * @todo - gracefully handle fallback if the server screws up.
- */
- fr_assert(te == &tt->id[te->id]);
- return 0;
-}
-
-/** Find a tracking entry from a request authenticator
- *
- * @param tt The radius_track_t tracking table
- * @param packet_id The ID from the RADIUS header
- * @param vector The Request Authenticator (may be NULL)
- * @return
- * - NULL on "not found"
- * - radius_track_entry_t on success
- */
-radius_track_entry_t *radius_track_entry_find(radius_track_t *tt, uint8_t packet_id, uint8_t const *vector)
-{
- radius_track_entry_t *te;
-
- (void) talloc_get_type_abort(tt, radius_track_t);
-
- /*
- * Just use the static array.
- */
- te = &tt->id[packet_id];
-
- /*
- * Not in use, die.
- */
- if (!te->request) return NULL;
-
- if (!vector) return te;
-
- /*
- * Protocol-Error and Original-Packet-Vector <sigh>
- *
- * This should arguably have been Original-Packet-Code, but we are stupid.
- *
- * @todo - Allow for multiple ID arrays, one for each packet code. Or, just switch to using
- * src/protocols/radius/id.[ch].
- */
- if (memcmp(te->vector, vector, sizeof(te->vector)) != 0) return NULL;
-
- /*
- * Ignore the Request Authenticator, as the
- * caller doesn't have it.
- */
- return te;
-}
-
-
-#ifndef NDEBUG
-/** Print out the state of every tracking entry
- *
- * @param[in] log destination.
- * @param[in] log_type Type of log message.
- * @param[in] file this function was called in.
- * @param[in] line this function was called on.
- * @param[in] tt Table to print.
- * @param[in] extra Callback function for printing extra detail.
- */
-void radius_track_state_log(fr_log_t const *log, fr_log_type_t log_type, char const *file, int line,
- radius_track_t *tt, radius_track_log_extra_t extra)
-{
- size_t i;
-
- for (i = 0; i < NUM_ELEMENTS(tt->id); i++) {
- radius_track_entry_t *entry;
-
- entry = &tt->id[i];
-
- if (entry->request) {
- fr_log(log, log_type, file, line,
- "[%zu] %"PRIu64 " - Allocated at %s:%u to request %p (%s), uctx %p",
- i, entry->operation,
- entry->file, entry->line, entry->request, entry->request->name, entry->uctx);
- } else {
- fr_log(log, log_type, file, line,
- "[%zu] %"PRIu64 " - Freed at %s:%u",
- i, entry->operation, entry->file, entry->line);
- }
-
- if (extra) extra(log, log_type, file, line, entry);
- }
-}
-#endif
+++ /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
- */
-
-/*
- * $Id$
- *
- * @file track.h
- * @brief RADIUS client packet tracking
- *
- * @copyright 2017 Alan DeKok (aland@freeradius.org)
- */
-
-#include "rlm_radius.h"
-#include <freeradius-devel/util/dlist.h>
-
-typedef struct radius_track_entry_s radius_track_entry_t;
-typedef struct radius_track_s radius_track_t;
-
-/** Track one request to a response
- *
- */
-struct radius_track_entry_s {
- fr_rb_node_t node; //!< Entry in the tracking tree.
-
- radius_track_t *tt;
-
- radius_track_entry_t ***binding; //!< Binding chunk we use to release the entry
- ///< when its parent is freed. We also zero
- ///< out the tracking entry field in the parent.
-
- request_t *request; //!< as always...
-
- void *uctx; //!< Result/resumption context.
-
- uint8_t code; //!< packet code (sigh)
- uint8_t id; //!< our ID
-
- union {
- fr_dlist_t entry; //!< For free list.
- uint8_t vector[RADIUS_AUTH_VECTOR_LENGTH]; //!< copy of the request authenticator.
- };
-
-#ifndef NDEBUG
- uint64_t operation; //!< Used to give an idea of the alloc/free timeline.
- char const *file; //!< Where the entry was allocated.
- int line; //!< Where the entry was freed.
-#endif
-};
-
-struct radius_track_s {
- unsigned int num_requests; //!< number of requests in the allocation
-
- fr_dlist_head_t free_list; //!< so we allocate by least recently used
-
- radius_track_entry_t id[UINT8_MAX + 1]; //!< which ID was used
-
-#ifndef NDEBUG
- uint64_t operation; //!< Incremented each alloc and de-alloc
-#endif
-};
-
-radius_track_t *radius_track_alloc(TALLOC_CTX *ctx);
-
-/*
- * Debug functions which track allocations and frees
- */
-#ifndef NDEBUG
-# define radius_track_entry_reserve(_te_out, _ctx, _tt, _request, _code, _uctx) \
- _radius_track_entry_reserve( __FILE__, __LINE__, _te_out, _ctx, _tt, _request, _code, _uctx)
-int _radius_track_entry_reserve(char const *file, int line,
- radius_track_entry_t **te_out,
- TALLOC_CTX *ctx, radius_track_t *tt, request_t *request,
- uint8_t code, void *uctx)
- CC_HINT(nonnull(3,5,6));
-
-# define radius_track_entry_release(_te) \
- _radius_track_entry_release( __FILE__, __LINE__, _te)
-int _radius_track_entry_release(char const *file, int line, radius_track_entry_t **te)
- CC_HINT(nonnull);
-
-typedef void (*radius_track_log_extra_t)(fr_log_t const *log, fr_log_type_t log_type, char const *file, int line,
- radius_track_entry_t *te);
-
-void radius_track_state_log(fr_log_t const *log, fr_log_type_t log_type, char const *file, int line,
- radius_track_t *tt, radius_track_log_extra_t extra);
-/*
- * Non-debug functions
- */
-#else
-int radius_track_entry_reserve(radius_track_entry_t **te_out,
- TALLOC_CTX *ctx, radius_track_t *tt, request_t *request,
- uint8_t code, void *uctx)
- CC_HINT(nonnull(1,3,4));
-
-int radius_track_entry_release(radius_track_entry_t **te) CC_HINT(nonnull);
-#endif
-
-int radius_track_entry_update(radius_track_entry_t *te,
- uint8_t const *vector) CC_HINT(nonnull);
-
-radius_track_entry_t *radius_track_entry_find(radius_track_t *tt, uint8_t packet_id,
- uint8_t const *vector) CC_HINT(nonnull(1));
}
radius {
+ mode = proxy
+
type = Access-Request
type = Accounting-Request