new file mode 100644
@@ -0,0 +1,1506 @@
+From 441967ba1d1ec28aa9582ab0253ad01e14b42148 Mon Sep 17 00:00:00 2001
+From: Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+Date: Sun, 30 Jun 2024 14:03:17 -0600
+Subject: [PATCH] CVE-2024-3596: Backport fix for BlastRADIUS
+
+Upstream-Status: Backport from v3.0.x branch, commit range 3a00a6ecc188629b0441fd45ad61ca8986de156e..da643f1edc267ce95260dc36069e6f1a7a4d66f8
+CVE: CVE-2024-3596
+
+Signed-off-by: Rohini Sangam <rsangam@mvista.com>
+---
+ man/man1/radclient.1 | 10 ++-
+ man/man1/radtest.1 | 11 ++-
+ raddb/clients.conf | 47 ++++++++--
+ raddb/proxy.conf | 19 +++++
+ raddb/radiusd.conf.in | 185 ++++++++++++++++++++++++++++++++++++++++
+ src/include/clients.h | 6 +-
+ src/include/conffile.h | 1 +
+ src/include/libradius.h | 19 ++++-
+ src/include/radius.h | 1 +
+ src/include/radiusd.h | 6 ++
+ src/include/realms.h | 1 +
+ src/lib/radius.c | 87 +++++++++++++++++--
+ src/main/client.c | 45 ++++++++--
+ src/main/conffile.c | 4 +-
+ src/main/listen.c | 141 +++++++++++++++++++++++++++++-
+ src/main/mainconfig.c | 70 +++++++++++++++
+ src/main/process.c | 65 ++++++++++++++
+ src/main/radclient.c | 147 ++++++++++++++++++++++++++++++-
+ src/main/radtest.in | 6 +-
+ src/main/realms.c | 11 +++
+ src/main/tls_listen.c | 5 ++
+ 21 files changed, 855 insertions(+), 32 deletions(-)
+
+diff --git a/man/man1/radclient.1 b/man/man1/radclient.1
+index 229dcae0c7..b83bee931a 100644
+--- a/man/man1/radclient.1
++++ b/man/man1/radclient.1
+@@ -1,10 +1,11 @@
+-.TH RADCLIENT 1 "22 March 2019" "" "FreeRADIUS Daemon"
++.TH RADCLIENT 1 "21 May 2024" "" "FreeRADIUS Daemon"
+ .SH NAME
+ radclient - send packets to a RADIUS server, show reply
+ .SH SYNOPSIS
+ .B radclient
+ .RB [ \-4 ]
+ .RB [ \-6 ]
++.RB [ \-b ]
+ .RB [ \-c
+ .IR count ]
+ .RB [ \-d
+@@ -52,6 +53,13 @@ automatically encrypted before the packet is sent to the server.
+ Use IPv4 (default)
+ .IP \-6
+ Use IPv6
++.IP \-b
++Enforce the Blast RADIUS checks. All replies to an Access-Request packet
++must contain a Message-Authenticator as the first attribute.
++
++For compatibility with old servers, this flag is not set by default.
++However, radclient still checks for the Blast RADIUS signature, and
++discards packets which match the attack.
+ .IP \-c\ \fIcount\fP
+ Send each packet \fIcount\fP times.
+ .IP \-d\ \fIraddb_directory\fP
+diff --git a/man/man1/radtest.1 b/man/man1/radtest.1
+index b3184779c0..6bfab75944 100644
+--- a/man/man1/radtest.1
++++ b/man/man1/radtest.1
+@@ -1,4 +1,4 @@
+-.TH RADTEST 1 "5 April 2010" "" "FreeRADIUS Daemon"
++.TH RADTEST 1 "21 May 2024" "" "FreeRADIUS Daemon"
+ .SH NAME
+ radtest - send packets to a RADIUS server, show reply
+ .SH SYNOPSIS
+@@ -15,6 +15,8 @@ radtest - send packets to a RADIUS server, show reply
+ .IR ]
+ .RB [ \-6
+ .IR ]
++.RB [ \-b
++.IR
+ .I user password radius-server nas-port-number secret
+ .RB [ ppphint ]
+ .RB [ nasname ]
+@@ -26,6 +28,13 @@ way to test a radius server.
+
+ .SH OPTIONS
+
++.IP \-b
++Enforce the Blast RADIUS checks. All replies to an Access-Request packet
++must contain a Message-Authenticator as the first attribute.
++
++For compatibility with old servers, this flag is not set by default.
++However, radclient still checks for the Blast RADIUS signature, and
++discards packets which match the attack.
+ .IP "\-d \fIraddb_directory\fP"
+ The directory that contains the RADIUS dictionary files. Defaults to
+ \fI/etc/raddb\fP.
+diff --git a/raddb/clients.conf b/raddb/clients.conf
+index 76b300d3c5..d55414b7d2 100644
+--- a/raddb/clients.conf
++++ b/raddb/clients.conf
+@@ -100,15 +100,44 @@ client localhost {
+ secret = testing123
+
+ #
+- # Old-style clients do not send a Message-Authenticator
+- # in an Access-Request. RFC 5080 suggests that all clients
+- # SHOULD include it in an Access-Request. The configuration
+- # item below allows the server to require it. If a client
+- # is required to include a Message-Authenticator and it does
+- # not, then the packet will be silently discarded.
+- #
+- # allowed values: yes, no
+- require_message_authenticator = no
++ # The global configuration "security.require_message_authenticator"
++ # flag sets the default for all clients. That default can be
++ # over-ridden here, by setting it to a value. If no value is set,
++ # then the default from the "radiusd.conf" file is used.
++ #
++ # See that file for full documentation on the flag, along
++ # with allowed values and meanings.
++ #
++ # This flag exists solely for legacy clients which do not send
++ # Message-Authenticator in all Access-Request packets. We do not
++ # recommend setting it to "no".
++ #
++ # The number one way to protect yourself from the BlastRADIUS
++ # attack is to update all RADIUS servers, and then set this
++ # flag to "yes". If all RADIUS servers are updated, and if
++ # all of them have this flag set to "yes" for all clients,
++ # then your network is safe. You can then upgrade the
++ # clients when it is convenient, instead of rushing the
++ # upgrades.
++ #
++ # allowed values: yes, no, auto
++# require_message_authenticator = no
++
++ #
++ # The global configuration "security.limit_proxy_state"
++ # flag sets the default for all clients. That default can be
++ # over-ridden here, by setting it to "no".
++ #
++ # See that file for full documentation on the flag, along
++ # with allowed values,and meanings.
++ #
++ # This flag exists solely for legacy clients which do not send
++ # Message-Authenticator in all Access-Request packets. We do not
++ # recommend setting it to "no".
++ #
++ # allowed values: yes, no, auto
++ #
++# limit_proxy_state = yes
+
+ #
+ # The short name is used as an alias for the fully qualified
+diff --git a/raddb/proxy.conf b/raddb/proxy.conf
+index 91b4b37930..fa362b8a74 100644
+--- a/raddb/proxy.conf
++++ b/raddb/proxy.conf
+@@ -204,6 +204,25 @@ home_server localhost {
+ #
+ secret = testing123
+
++
++ #
++ # The global configuration "security.require_message_authenticator"
++ # flag sets the default for all home servers. That default can be
++ # over-ridden here, by setting it to a value. If no value is set,
++ # then the default from the "radiusd.conf" file is used.
++ #
++ # See that file for full documentation on the flag, along
++ # with allowed values and meanings.
++ #
++ # This flag exists solely for legacy home servers which do
++ # not send Message-Authenticator in all Access-Accept,
++ # Access-Reject, or Access-Challenge packets. We do not
++ # recommend setting it to "no".
++ #
++ # allowed values: yes, no, auto
++ #
++# require_message_authenticator = no
++
+ ############################################################
+ #
+ # The rest of the configuration items listed here are optional,
+diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in
+index e8aee3c001..5b8800bfc8 100644
+--- a/raddb/radiusd.conf.in
++++ b/raddb/radiusd.conf.in
+@@ -564,6 +564,191 @@ security {
+ #
+ status_server = yes
+
++ #
++ # Global configuration for requiring Message-Authenticator in
++ # all Access-* packets sent over UDP or TCP. This flag is
++ # ignored for TLS.
++ #
++ # The number one way to protect yourself from the BlastRADIUS
++ # attack is to update all RADIUS servers, and then set this
++ # flag to "yes". If all RADIUS servers are updated, and if
++ # all of them have this flag set to "yes" for all clients,
++ # then your network is safe. You can then upgrade the
++ # clients when it is convenient, instead of rushing the
++ # upgrades.
++ #
++ # This flag sets the global default for all clients and home
++ # servers. It can be over-ridden in an individual client or
++ # home_server definition by adding the same flag to that
++ # section with an appropriate value.
++ #
++ # All upgraded RADIUS implementations should send
++ # Message-Authenticator in all Access-Request, Access-Accept,
++ # Access-Reject, and Access-Challenge packets. Once all
++ # systems are upgraded, setting this flag to "yes" is the
++ # best protection from the attack.
++ #
++ # The possible values and meanings for
++ # "require_message_authenticator" are;
++ #
++ # * "no" - allow Access-* packet which do not contain
++ # Message-Authenticator
++ #
++ # For a client, if this flag is set to "no", then the
++ # "limit_proxy_state" flag, below, is also checked.
++ #
++ # For a home_server, if this flag is set to "no", then the
++ # Access-Accept, Access-Reject, and Access-Challenge
++ # packets do not need to contain Message-Authenticator.
++ #
++ # The only reason to set this flag to "no" is when the
++ # RADIUS client or home server has not been updated. It is
++ # always safer to set this flag "no" in the individual
++ # client or home_server definition. The global flag SHOULD
++ # still be set to a safe value: "yes".
++ #
++ # WARNING: Setting this flag and the "limit_proxy_state"
++ # flag to "no" will allow MITM attackers to create fake
++ # Access-Accept packets to the NAS! At least one of them
++ # MUST be set to "yes" for the system to have any
++ # protection against the attack.
++ #
++ # * "yes" - Require that all Access-* packets (client and
++ # home_server) contain Message-Authenticator. If a packet
++ # does not contain Message-Authenticator, then it is
++ # discarded.
++ #
++ # * "auto" - Automatically determine the value of the flag,
++ # based on the first packet received from that client or
++ # home_server.
++ #
++ # If the packet does not contain Message-Authenticator,
++ # then the value of the flag is automatically switched to
++ # "no".
++ #
++ # If the packet contains Message-Authenticator but not
++ # EAP-Message, then the value of the flag is automatically
++ # switched to "yes". The server has to check for
++ # EAP-Message, because the previous RFCs require that the
++ # packet contains Message-Authenticator when it also
++ # contains EAP-Message. So having a Message-Authenticator
++ # in those packets doesn't give the server enough
++ # information to determined if the client or home_server
++ # has been updated.
++ #
++ # If the packet contains Message-Authenticator and
++ # EAP-Message, then the flag is left at the "auto" value.
++ #
++ # WARNING: This switch is done for the first packet
++ # received from that client or home server. The change
++ # does NOT persist across server restarts. You MUST change
++ # the to "yes" manually, in order to make a permanent
++ # change to the configuration.
++ #
++ # WARNING: If there are multiple NASes with the same source
++ # IP and client definitions, BUT the NASes have different
++ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK.
++ #
++ # That is, when there are multiple different RADIUS clients
++ # behind one NATed IP address, then these security settings
++ # have to be set to allow the MOST INSECURE packets to be
++ # processed. This is a terrible idea, and will leave your
++ # network vulnerable to the attack. Please upgrade all
++ # clients immediately.
++ #
++ # The only solution to that rare configuration is to set
++ # this flag to "no", in which case the network will work,
++ # but will be vulnerable to the attack.
++ #
++ require_message_authenticator = auto
++
++ #
++ # Global configuration for limiting the combination of
++ # Proxy-State and Message-Authenticator. This flag only
++ # applies to packets sent over UDP or TCP. This flag is
++ # ignored for TLS.
++ #
++ # This flag sets the global default for all clients. It can
++ # be over-ridden in an individual client definition by adding
++ # the same flag to that section with an appropriate value.
++ #
++ # If "require_message_authenticator" is set to "yes", this
++ # configuration item is ignored.
++ #
++ # If "require_message_authenticator" is set to "no", this
++ # configuration item is checked.
++ #
++ # The possible values and meanings for "limit_proxy_state" are;
++ #
++ # * "no" - allow any packets from the client, even packets
++ # which contain the BlastRADIUS attack. Please be aware
++ # that in this configuration the server will complain for
++ # EVERY packet which it receives.
++ #
++ # The only reason to set this flag to "no" is when the
++ # client is a proxy, AND the proxy does not send
++ # Message-Authenticator in Access-Request packets. Even
++ # then, the best approach to fix the issue is to (1) update
++ # the proxy to send Message-Authenticator, and if that
++ # can't be done, then (2) set this flag to "no", but ONLY
++ # for that one client. The global flag SHOULD still be set
++ # to a safe value: "yes".
++ #
++ # WARNING: Setting both this flag and the
++ # "require_message_authenticator" flag to "no" will allow
++ # MITM attackers to create fake Access-Accept packets to the
++ # NAS! At least one of them MUST be set to "yes" for the
++ # system to have any protection against the attack.
++ #
++ # * "yes" - Allow packets without Message-Authenticator,
++ # but only when they do not contain Proxy-State.
++ # packets which contain Proxy-State MUST also contain
++ # Message-Authenticator, otherwise they are discarded.
++ #
++ # This setting is safe for most NASes, GGSNs, BRAS, etc.
++ # Most regular RADIUS clients do not send Proxy-State
++ # attributes for Access-Request packets that they originate.
++ # However some aggregators (e.g. Wireless LAN Controllers)
++ # may act as a RADIUS proxy for requests from their cohort
++ # of managed devices, and in such cases will provide a
++ # Proxy-State attribute. For those systems, you _must_ look
++ # at the actual packets to determine what to do. It may be
++ # that the only way to fix the vulnerability is to upgrade
++ # the WLC, and set "require_message_authenticator" to "yes".
++ #
++ # * "auto" - Automatically determine the value of the flag,
++ # based on the first packet received from that client.
++ #
++ # If the packet contains Proxy-State but no
++ # Message-Authenticator, then the value of the flag is
++ # automatically switched to "no".
++ #
++ # For all other situations, the value of the flag is
++ # automatically switched to "yes".
++ #
++ # WARNING: This switch is done for the first packet
++ # received from that client. The change does NOT persist
++ # across server restarts. You MUST change the to "yes"
++ # manually, in order to make a permanent change to the
++ # configuration.
++ #
++ # WARNING: If there are multiple NASes with the same source
++ # IP and client definitions, BUT the NASes have different
++ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK.
++ #
++ # That is, when there are multiple different RADIUS clients
++ # behind one NATed IP address, then these security settings
++ # have to be set to allow the MOST INSECURE packets to be
++ # processed. This is a terrible idea, and will leave your
++ # network vulnerable to the attack. Please upgrade all
++ # clients immediately.
++ #
++ # The only solution to that rare configuration is to set
++ # this flag to "no", in which case the network will work,
++ # but will be vulnerable to the attack.
++ #
++ limit_proxy_state = auto
++
+ @openssl_version_check_config@
+ }
+
+diff --git a/src/include/clients.h b/src/include/clients.h
+index 560211557f..0aeb1da8d9 100644
+--- a/src/include/clients.h
++++ b/src/include/clients.h
+@@ -39,7 +39,11 @@ typedef struct radclient {
+
+ char const *secret; //!< Secret PSK.
+
+- bool message_authenticator; //!< Require RADIUS message authenticator in requests.
++ fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests.
++
++ bool dynamic_require_ma; //!< for dynamic clients
++
++ fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests
+
+ char const *nas_type; //!< Type of client (arbitrary).
+
+diff --git a/src/include/conffile.h b/src/include/conffile.h
+index 8cb045c946..ddbcae4e4f 100644
+--- a/src/include/conffile.h
++++ b/src/include/conffile.h
+@@ -140,6 +140,7 @@ typedef struct timeval _timeval_t;
+ #define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies.
+ #define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value.
+ #define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist
++#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing
+ /* @} **/
+
+ #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\
+diff --git a/src/include/libradius.h b/src/include/libradius.h
+index ce2f713de1..2efef8b1d3 100644
+--- a/src/include/libradius.h
++++ b/src/include/libradius.h
+@@ -402,6 +402,10 @@ typedef struct radius_packet {
+ size_t partial;
+ int proto;
+ #endif
++ bool tls; //!< uses secure transport
++ bool message_authenticator;
++ bool proxy_state;
++ bool eap_message;
+ } RADIUS_PACKET;
+
+ typedef enum {
+@@ -507,6 +511,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor);
+ /* radius.c */
+ int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret);
+ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason);
++
++/*
++ * 1 == require_ma
++ * 2 == msg_peek
++ * 4 == limit_proxy_state
++ * 8 == require_ma for Access-* replies and Protocol-Error
++ */
+ RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags);
+ ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code);
+ void rad_recv_discard(int sockfd);
+@@ -694,7 +705,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */
+ extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */
+ extern int fr_debug_lvl; /* 0 = no debugging information */
+ extern uint32_t fr_max_attributes; /* per incoming packet */
+-#define FR_MAX_PACKET_CODE (52)
++#define FR_MAX_PACKET_CODE (53)
+ extern char const *fr_packet_codes[FR_MAX_PACKET_CODE];
+ #define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE))
+ extern FILE *fr_log_fp;
+@@ -932,6 +943,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout);
+ }
+ #endif
+
++typedef enum {
++ FR_BOOL_FALSE = 0,
++ FR_BOOL_TRUE,
++ FR_BOOL_AUTO,
++} fr_bool_auto_t;
++
+ #include <freeradius-devel/packet.h>
+
+ #ifdef WITH_TCP
+diff --git a/src/include/radius.h b/src/include/radius.h
+index 473528d65d..147d674eed 100644
+--- a/src/include/radius.h
++++ b/src/include/radius.h
+@@ -61,6 +61,7 @@ typedef enum {
+ PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request
+ PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive)
+ PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform)
++ PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue
+ PW_CODE_MAX = 255, //!< Maximum possible code
+ } PW_CODE;
+
+diff --git a/src/include/radiusd.h b/src/include/radiusd.h
+index b2a0a0f642..e429c5be7a 100644
+--- a/src/include/radiusd.h
++++ b/src/include/radiusd.h
+@@ -171,6 +171,10 @@ typedef struct main_config {
+
+ bool exiting; //!< are we exiting?
+
++ fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers
++
++ fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients
++
+
+ #ifdef ENABLE_OPENSSL_VERSION_CHECK
+ char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged.
+@@ -558,6 +562,8 @@ int main_config_free(void);
+ void main_config_hup(void);
+ void hup_logfile(void);
+
++int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str);
++
+ /* listen.c */
+ void listen_free(rad_listen_t **head);
+ int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag);
+diff --git a/src/include/realms.h b/src/include/realms.h
+index 6dae8b4f85..e643818e43 100644
+--- a/src/include/realms.h
++++ b/src/include/realms.h
+@@ -59,6 +59,7 @@ typedef struct home_server {
+ //!< stats or when specifying home servers for a pool.
+
+ bool dual; //!< One of a pair of homeservers on consecutive ports.
++ fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server
+ char const *server; //!< For internal proxying
+ char const *parent_server;
+
+diff --git a/src/lib/radius.c b/src/lib/radius.c
+index 3881111f7d..7b91a4bde2 100644
+--- a/src/lib/radius.c
++++ b/src/lib/radius.c
+@@ -142,8 +142,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = {
+ "47",
+ "48",
+ "49",
+- "IP-Address-Allocate",
+- "IP-Address-Release", //!< 50
++ "IP-Address-Allocate", //!< 50
++ "IP-Address-Release",
++ "Protocol-Error",
+ };
+
+
+@@ -1700,6 +1701,15 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original,
+ return rad_vp2vsa(packet, original, secret, pvp, start, room);
+ }
+
++static const bool code2ma[FR_MAX_PACKET_CODE] = {
++ [ PW_CODE_ACCESS_REQUEST ] = true,
++ [ PW_CODE_ACCESS_ACCEPT ] = true,
++ [ PW_CODE_ACCESS_REJECT ] = true,
++ [ PW_CODE_ACCESS_CHALLENGE ] = true,
++ [ PW_CODE_STATUS_SERVER ] = true,
++ [ PW_CODE_PROTOCOL_ERROR ] = true,
++};
++
+
+ /** Encode a packet
+ *
+@@ -1712,6 +1722,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
+ uint16_t total_length;
+ int len;
+ VALUE_PAIR const *reply;
++ bool seen_ma = false;
+
+ /*
+ * A 4K packet, aligned on 64-bits.
+@@ -1775,6 +1786,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
+ * memcpy.
+ */
+
++ /*
++ * Always add Message-Authenticator for replies to
++ * Access-Request packets, and for all Access-Accept,
++ * Access-Reject, Access-Challenge.
++ *
++ * It must be the FIRST attribute in the packet.
++ */
++ if (!packet->tls &&
++ ((code2ma[packet->code]) || (original && code2ma[original->code]))) {
++ seen_ma = true;
++
++ packet->offset = RADIUS_HDR_LEN;
++
++ ptr[0] = PW_MESSAGE_AUTHENTICATOR;
++ ptr[1] = 18;
++ memset(ptr + 2, 0, 16);
++
++ ptr += 18;
++ total_length += 18;
++ }
++
+ /*
+ * Loop over the reply attributes for the packet.
+ */
+@@ -1832,6 +1864,13 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
+ * length and initial value.
+ */
+ if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) {
++ /*
++ * We have already encoded the Message-Authenticator, don't do it again.
++ */
++ if (seen_ma) {
++ reply = reply->next;
++ continue;
++ }
+ if (room < 18) break;
+
+ /*
+@@ -2323,6 +2362,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ radius_packet_t *hdr;
+ char host_ipaddr[128];
+ bool require_ma = false;
++ bool limit_proxy_state = false;
++ bool seen_proxy_state = false;
+ bool seen_ma = false;
+ uint32_t num_attributes;
+ decode_fail_t failure = DECODE_FAIL_NONE;
+@@ -2371,15 +2412,26 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ }
+
+ /*
+- * Message-Authenticator is required in Status-Server
+- * packets, otherwise they can be trivially forged.
++ * If the caller requires Message-Authenticator, then set
++ * the flag.
+ */
+- if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true;
+
+ /*
+- * It's also required if the caller asks for it.
++ * We also require Message-Authenticator if the packet
++ * code is Status-Server.
++ *
++ * If we're receiving packets from a proxy socket, then
++ * require Message-Authenticator for Access-* replies,
++ * and for Protocol-Error.
+ */
+- if (flags) require_ma = true;
++ require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]);
++
++ /*
++ *
++ * We only limit Proxy-State if we're not requiring
++ * Message-Authenticator.
++ */
++ limit_proxy_state = ((flags & 0x04) != 0) && !require_ma;
+
+ /*
+ * Repeat the length checks. This time, instead of
+@@ -2534,6 +2586,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ case PW_EAP_MESSAGE:
+ require_ma = true;
+ eap = true;
++ packet->eap_message = true;
+ break;
+
+ case PW_USER_PASSWORD:
+@@ -2542,6 +2595,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ non_eap = true;
+ break;
+
++ case PW_PROXY_STATE:
++ seen_proxy_state = true;
++ packet->proxy_state = true;
++ break;
++
+ case PW_MESSAGE_AUTHENTICATOR:
+ if (attr[1] != 2 + AUTH_VECTOR_LEN) {
+ FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d",
+@@ -2553,6 +2611,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ goto finish;
+ }
+ seen_ma = true;
++ packet->message_authenticator = true;
+ break;
+ }
+
+@@ -2609,7 +2668,19 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
+ * Message-Authenticator attributes.
+ */
+ if (require_ma && !seen_ma) {
+- FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute",
++ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.",
++ inet_ntop(packet->src_ipaddr.af,
++ &packet->src_ipaddr.ipaddr,
++ host_ipaddr, sizeof(host_ipaddr)));
++ failure = DECODE_FAIL_MA_MISSING;
++ goto finish;
++ }
++
++ /*
++ * The client is a NAS which shouldn't send Proxy-State, but it did!
++ */
++ if (limit_proxy_state && seen_proxy_state && !seen_ma) {
++ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes",
+ inet_ntop(packet->src_ipaddr.af,
+ &packet->src_ipaddr.ipaddr,
+ host_ipaddr, sizeof(host_ipaddr)));
+diff --git a/src/main/client.c b/src/main/client.c
+index 6228438c47..875dc37d60 100644
+--- a/src/main/client.c
++++ b/src/main/client.c
+@@ -283,7 +283,8 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
+ (old->coa_server == client->coa_server) &&
+ (old->coa_pool == client->coa_pool) &&
+ #endif
+- (old->message_authenticator == client->message_authenticator)) {
++ (old->require_ma == client->require_ma) &&
++ (old->limit_proxy_state == client->limit_proxy_state)) {
+ WARN("Ignoring duplicate client %s", client->longname);
+ client_free(client);
+ return true;
+@@ -445,6 +446,8 @@ static fr_ipaddr_t cl_ipaddr;
+ static uint32_t cl_netmask;
+ static char const *cl_srcipaddr = NULL;
+ static char const *hs_proto = NULL;
++static char const *require_message_authenticator = NULL;
++static char const *limit_proxy_state = NULL;
+
+ #ifdef WITH_TCP
+ static CONF_PARSER limit_config[] = {
+@@ -467,7 +470,8 @@ static const CONF_PARSER client_config[] = {
+
+ { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL },
+
+- { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" },
++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL },
++ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL },
+
+ { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL },
+ { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL },
+@@ -663,7 +667,7 @@ static const CONF_PARSER dynamic_config[] = {
+ { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL },
+ { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL },
+
+- { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL },
++ { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL },
+
+ { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" },
+ { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" },
+@@ -845,8 +849,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo
+ c = talloc_zero(ctx, RADCLIENT);
+ c->cs = cs;
+
++ /*
++ * Set the "require message authenticator" and "limit
++ * proxy state" flags from the global default. If the
++ * configuration item exists, AND is set, it will
++ * over-ride the flag.
++ */
++ c->require_ma = main_config.require_ma;
++ c->limit_proxy_state = main_config.limit_proxy_state;
++
+ memset(&cl_ipaddr, 0, sizeof(cl_ipaddr));
+ cl_netmask = 255;
++ require_message_authenticator = NULL;
++ limit_proxy_state = NULL;
+
+ if (cf_section_parse(cs, c, client_config) < 0) {
+ cf_log_err_cs(cs, "Error parsing client section");
+@@ -857,6 +872,9 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo
+ cl_srcipaddr = NULL;
+ #endif
+
++ require_message_authenticator = NULL;
++ limit_proxy_state = NULL;
++
+ return NULL;
+ }
+
+@@ -1114,6 +1132,16 @@ done_coa:
+ }
+ #endif
+
++ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) {
++ goto error;
++ }
++
++ if (c->require_ma != FR_BOOL_TRUE) {
++ if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) {
++ goto error;
++ }
++ }
++
+ return c;
+ }
+
+@@ -1158,7 +1186,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons
+ if (shortname) c->shortname = talloc_typed_strdup(c, shortname);
+ if (type) c->nas_type = talloc_typed_strdup(c, type);
+ if (server) c->server = talloc_typed_strdup(c, server);
+- c->message_authenticator = require_ma;
++ c->require_ma = require_ma;
+
+ return c;
+ }
+@@ -1344,10 +1372,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request)
+ *pi = vp->vp_integer;
+
+ /*
+- * Same nastiness as above.
++ * Same nastiness as above, but hard-coded for require Message-Authenticator.
+ */
+ for (parse = client_config; parse->name; parse++) {
+- if (parse->offset == dynamic_config[i].offset) break;
++ if (parse->type == PW_TYPE_BOOLEAN) break;
+ }
+ if (!parse) break;
+
+@@ -1436,6 +1464,11 @@ validate:
+ goto error;
+ }
+
++ /*
++ * It can't be set to "auto". Too bad.
++ */
++ c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma;
++
+ if (!client_add_dynamic(clients, request->client, c)) {
+ return NULL;
+ }
+diff --git a/src/main/conffile.c b/src/main/conffile.c
+index a8c667bfb5..61754e991f 100644
+--- a/src/main/conffile.c
++++ b/src/main/conffile.c
+@@ -1418,6 +1418,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
+ {
+ int rcode;
+ bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists;
++ bool ignore_dflt;
+ char **q;
+ char const *value;
+ CONF_PAIR *cp = NULL;
+@@ -1441,6 +1442,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
+ cant_be_empty = (type & PW_TYPE_NOT_EMPTY);
+ tmpl = (type & PW_TYPE_TMPL);
+ multi = (type & PW_TYPE_MULTI);
++ ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT);
+
+ if (attribute) required = true;
+ if (required) cant_be_empty = true; /* May want to review this in the future... */
+@@ -1464,7 +1466,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
+ * section, use the default value.
+ */
+ if (!cp) {
+- if (deprecated) return 0; /* Don't set the default value */
++ if (deprecated || ignore_dflt) return 0; /* Don't set the default value */
+
+ rcode = 1;
+ value = dflt;
+diff --git a/src/main/listen.c b/src/main/listen.c
+index ebf7f5221c..c20fea243d 100644
+--- a/src/main/listen.c
++++ b/src/main/listen.c
+@@ -456,6 +456,122 @@ int rad_status_server(REQUEST *request)
+ return 0;
+ }
+
++static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client)
++{
++ if (client->require_ma == FR_BOOL_TRUE) return;
++
++ if (client->require_ma == FR_BOOL_AUTO) {
++ if (!packet->message_authenticator) {
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("BlastRADIUS check: Received packet without Message-Authenticator.");
++ ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ client->require_ma = FR_BOOL_FALSE;
++
++ /*
++ * And fall through to the
++ * limit_proxy_state checks, which might
++ * complain again. Oh well, maybe that
++ * will make people read the messages.
++ */
++
++ } else if (packet->eap_message) {
++ /*
++ * Don't set it to "true" for packets
++ * with EAP-Message. It's already
++ * required there, and we might get a
++ * non-EAP packet with (or without)
++ * Message-Authenticator
++ */
++ return;
++ } else {
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("BlastRADIUS check: Received packet with Message-Authenticator.");
++ ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack.");
++ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ client->require_ma = FR_BOOL_TRUE;
++ return;
++ }
++
++ }
++
++ /*
++ * If all of the checks are turned off, then complain for every packet we receive.
++ */
++ if (client->limit_proxy_state == FR_BOOL_FALSE) {
++ /*
++ * We have a Message-Authenticator, and it's valid. We don't need to compain.
++ */
++ if (!fr_debug_lvl) return; /* easier than checking for each line below */
++
++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ DEBUG("BlastRADIUS check: Received packet without Message-Authenticator.");
++ DEBUG("YOU MUST SET \"require_message_authenticator = true\", or");
++ DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname);
++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ DEBUG("The packet does not contain Message-Authenticator, which is a security issue");
++ DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ return;
++ }
++
++ /*
++ * Don't complain here. rad_packet_ok() will instead
++ * complain about every packet with Proxy-State but which
++ * is missing Message-Authenticator.
++ */
++ if (client->limit_proxy_state == FR_BOOL_TRUE) {
++ return;
++ }
++
++ if (packet->proxy_state && !packet->message_authenticator) {
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator.");
++ ERROR("This is either a BlastRADIUS attack, OR");
++ ERROR("the client is a proxy RADIUS server which has not been upgraded.");
++ ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ client->limit_proxy_state = FR_BOOL_FALSE;
++
++ } else {
++ client->limit_proxy_state = FR_BOOL_TRUE;
++
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ if (!packet->proxy_state) {
++ ERROR("BlastRADIUS check: Received packet without Proxy-State.");
++ } else {
++ ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator.");
++ }
++
++ ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname);
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ if (!packet->message_authenticator) {
++ ERROR("The packet does not contain Message-Authenticator, which is a security issue.");
++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
++ } else {
++ ERROR("The packet contains Message-Authenticator.");
++ if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack.");
++ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname);
++ }
++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ }
++}
++
++
+ #ifdef WITH_TCP
+ static int dual_tcp_recv(rad_listen_t *listener)
+ {
+@@ -532,6 +648,21 @@ static int dual_tcp_recv(rad_listen_t *listener)
+ switch (packet->code) {
+ case PW_CODE_ACCESS_REQUEST:
+ if (listener->type != RAD_LISTEN_AUTH) goto bad_packet;
++
++ /*
++ * Enforce BlastRADIUS checks on TCP, too.
++ */
++ if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) {
++ FR_STATS_INC(auth, total_malformed_requests);
++ rad_free(&sock->packet);
++ return 0;
++ }
++
++ /*
++ * Perform BlastRADIUS checks and warnings.
++ */
++ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client);
++
+ FR_STATS_INC(auth, total_requests);
+ fun = rad_authenticate;
+ break;
+@@ -1562,7 +1693,7 @@ static int auth_socket_recv(rad_listen_t *listener)
+ * Now that we've sanity checked everything, receive the
+ * packet.
+ */
+- packet = rad_recv(ctx, listener->fd, client->message_authenticator);
++ packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2));
+ if (!packet) {
+ FR_STATS_INC(auth, total_malformed_requests);
+ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
+@@ -1570,6 +1701,12 @@ static int auth_socket_recv(rad_listen_t *listener)
+ return 0;
+ }
+
++
++ /*
++ * Perform BlastRADIUS checks and warnings.
++ */
++ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client);
++
+ #ifdef __APPLE__
+ #ifdef WITH_UDPFROMTO
+ /*
+@@ -1955,7 +2092,7 @@ static int coa_socket_recv(rad_listen_t *listener)
+ * Now that we've sanity checked everything, receive the
+ * packet.
+ */
+- packet = rad_recv(ctx, listener->fd, client->message_authenticator);
++ packet = rad_recv(ctx, listener->fd, client->require_ma | (((int) client->limit_proxy_state) << 2));
+ if (!packet) {
+ FR_STATS_INC(coa, total_malformed_requests);
+ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
+diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c
+index e9dd412dee..520d7fa474 100644
+--- a/src/main/mainconfig.c
++++ b/src/main/mainconfig.c
+@@ -73,6 +73,8 @@ static char const *gid_name = NULL;
+ static char const *chroot_dir = NULL;
+ static bool allow_core_dumps = false;
+ static char const *radlog_dest = NULL;
++static char const *require_message_authenticator = NULL;
++static char const *limit_proxy_state = NULL;
+
+ /*
+ * These are not used anywhere else..
+@@ -87,6 +89,53 @@ static bool do_colourise = false;
+
+ static char const *radius_dir = NULL; //!< Path to raddb directory
+
++static const FR_NAME_NUMBER fr_bool_auto_names[] = {
++ { "false", FR_BOOL_FALSE },
++ { "no", FR_BOOL_FALSE },
++ { "0", FR_BOOL_FALSE },
++
++ { "true", FR_BOOL_TRUE },
++ { "yes", FR_BOOL_TRUE },
++ { "1", FR_BOOL_TRUE },
++
++ { "auto", FR_BOOL_AUTO },
++
++ { NULL, 0 }
++};
++
++/*
++ * Get decent values for false / true / auto
++ */
++int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str)
++{
++ int value;
++
++ /*
++ * Don't change anything.
++ */
++ if (!str) return 0;
++
++ value = fr_str2int(fr_bool_auto_names, str, -1);
++ if (value >= 0) {
++ *out = value;
++ return 0;
++ }
++
++ /*
++ * This should never happen, as the defaults are in the
++ * source code. If there's no CONF_PAIR, and there's a
++ * parse error, then the source code is wrong.
++ */
++ if (!cp) {
++ fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name);
++ return -1;
++ }
++
++ cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp));
++ return -1;
++}
++
++
+ /**********************************************************************
+ *
+ * We need to figure out where the logs go, before doing anything
+@@ -159,6 +208,8 @@ static const CONF_PARSER security_config[] = {
+ { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) },
+ { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) },
+ { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"},
++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"},
++ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"},
+ #ifdef ENABLE_OPENSSL_VERSION_CHECK
+ { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"},
+ #endif
+@@ -838,6 +889,8 @@ int main_config_init(void)
+ if (!main_config.dictionary_dir) {
+ main_config.dictionary_dir = DICTDIR;
+ }
++ main_config.require_ma = FR_BOOL_AUTO;
++ main_config.limit_proxy_state = FR_BOOL_AUTO;
+
+ /*
+ * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400
+@@ -1127,6 +1180,23 @@ do {\
+ main_config.init_delay.tv_sec = 0;
+ main_config.init_delay.tv_usec = 2* (1000000 / 3);
+
++ {
++ CONF_PAIR *cp = NULL;
++
++ subcs = cf_section_sub_find(cs, "security");
++ if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator");
++ if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) {
++ cf_file_free(cs);
++ return -1;
++ }
++
++ if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state");
++ if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) {
++ cf_file_free(cs);
++ return -1;
++ }
++ }
++
+ /*
+ * Free the old configuration items, and replace them
+ * with the new ones.
+diff --git a/src/main/process.c b/src/main/process.c
+index 1a48517d43..401033bdd6 100644
+--- a/src/main/process.c
++++ b/src/main/process.c
+@@ -2593,6 +2593,23 @@ int request_proxy_reply(RADIUS_PACKET *packet)
+
+ PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
++ if (!request->proxy_reply) {
++ decode_fail_t reason;
++
++ /*
++ * If the home server configuration requires a Message-Authenticator, then set the flag,
++ * but only if the proxied packet is Access-Request or Status-Sercer.
++ *
++ * The realms.c file already clears require_ma for TLS connections.
++ */
++ bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST);
++
++ if(!rad_packet_ok(packet, require_ma, &reason)) {
++ DEBUG("Ignoring invalid packet - %s", fr_strerror());
++ return 0;
++ }
++ }
++
+ /*
+ * No reply, BUT the current packet fails verification:
+ * ignore it. This does the MD5 calculations in the
+@@ -2618,6 +2635,54 @@ int request_proxy_reply(RADIUS_PACKET *packet)
+ return 0;
+ }
+
++
++ /*
++ * BlastRADIUS checks. We're running in the main
++ * listener thread, so there's no conflict
++ * checking or setting these fields.
++ */
++ if (!request->proxy_reply && (request->proxy->code == PW_CODE_ACCESS_REQUEST) &&
++#ifdef WITH_TLS
++ !request->home_server->tls &&
++#endif
++ !packet->eap_message) {
++ if (request->home_server->require_ma == FR_BOOL_AUTO) {
++ if (!packet->message_authenticator) {
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator.");
++ RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name);
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name);
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ request->home_server->require_ma = FR_BOOL_FALSE;
++ } else {
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator.");
++ RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name);
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack.");
++ RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name);
++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ request->home_server->require_ma = FR_BOOL_TRUE;
++ }
++
++ } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) {
++ /*
++ * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet.
++ */
++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name);
++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ RDEBUG("The packet does not contain Message-Authenticator, which is a security issue");
++ RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
++ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name);
++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ }
++ }
++
+ /*
+ * This shouldn't happen, but threads and race
+ * conditions.
+diff --git a/src/main/radclient.c b/src/main/radclient.c
+index 52d2872b13..47d5f07785 100644
+--- a/src/main/radclient.c
++++ b/src/main/radclient.c
+@@ -54,6 +54,7 @@ static fr_ipaddr_t server_ipaddr;
+ static int resend_count = 1;
+ static bool done = true;
+ static bool print_filename = false;
++static bool blast_radius = false;
+
+ static fr_ipaddr_t client_ipaddr;
+ static uint16_t client_port = 0;
+@@ -89,6 +90,7 @@ static void NEVER_RETURNS usage(void)
+ fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n");
+ fprintf(stderr, " -4 Use IPv4 address of server\n");
+ fprintf(stderr, " -6 Use IPv6 address of server.\n");
++ fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n");
+ fprintf(stderr, " -c <count> Send each packet 'count' times.\n");
+ fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
+ fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
+@@ -1000,6 +1002,130 @@ static int send_one_packet(rc_request_t *request)
+ return 0;
+ }
+
++/*
++ * Do Blast RADIUS checks.
++ *
++ * The request is an Access-Request, and does NOT contain Proxy-State.
++ *
++ * The reply is a raw packet, and is NOT yet decoded.
++ */
++static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply)
++{
++ uint8_t *attr, *end;
++ VALUE_PAIR *vp;
++ bool have_message_authenticator = false;
++
++ /*
++ * We've received a raw packet. Nothing has (as of yet) checked
++ * anything in it other than the length, and that it's a
++ * well-formed RADIUS packet.
++ */
++ switch (reply->data[0]) {
++ case PW_CODE_ACCESS_ACCEPT:
++ case PW_CODE_ACCESS_REJECT:
++ case PW_CODE_ACCESS_CHALLENGE:
++ if (reply->data[1] != request->packet->id) {
++ ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id);
++ return -1;
++ }
++ break;
++
++ default:
++ ERROR("Invalid reply code %d to Access-Request", reply->data[0]);
++ return -1;
++ }
++
++ /*
++ * If the reply has a Message-Authenticator, then it MIGHT be fine.
++ */
++ attr = reply->data + 20;
++ end = reply->data + reply->data_len;
++
++ /*
++ * It should be the first attribute, so we warn if it isn't there.
++ *
++ * But it's not a fatal error.
++ */
++ if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) {
++ RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.",
++ fr_packet_codes[reply->data[0]]);
++ }
++
++ /*
++ * Set up for Proxy-State checks.
++ *
++ * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack.
++ */
++ vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY);
++
++ while (attr < end) {
++ /*
++ * Blast RADIUS work-arounds require that
++ * Message-Authenticator is the first attribute in the
++ * reply. Note that we don't check for it being the
++ * first attribute, but simply that it exists.
++ *
++ * That check is a balance between securing the reply
++ * packet from attacks, and not violating the RFCs which
++ * say that there is no order to attributes in the
++ * packet.
++ *
++ * However, no matter the status of the '-b' flag we
++ * still can check for the signature of the attack, and
++ * discard packets which are suspicious. This behavior
++ * protects radclient from the attack, without mandating
++ * new behavior on the server side.
++ *
++ * Note that we don't set the '-b' flag by default.
++ * radclient is intended for testing / debugging, and is
++ * not intended to be used as part of a secure login /
++ * user checking system.
++ */
++ if (attr[0] == PW_MESSAGE_AUTHENTICATOR) {
++ have_message_authenticator = true;
++ goto next;
++ }
++
++ /*
++ * If there are Proxy-State attributes in the reply, they must
++ * match EXACTLY the Proxy-State attributes in the request.
++ *
++ * Note that we don't care if there are more Proxy-States
++ * in the request than in the reply. The Blast RADIUS
++ * issue requires _adding_ Proxy-State attributes, and
++ * cannot work when the server _deletes_ Proxy-State
++ * attributes.
++ */
++ if (attr[0] == PW_PROXY_STATE) {
++ if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) {
++ ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id);
++ ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent.");
++ return -1;
++ }
++
++ vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY);
++ }
++
++ next:
++ attr += attr[1];
++ }
++
++ /*
++ * If "-b" is set, then we require Message-Authenticator in the reply.
++ */
++ if (blast_radius && !have_message_authenticator) {
++ ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.",
++ fr_packet_codes[reply->data[0]]);
++ return -1;
++ }
++
++ /*
++ * The packet doesn't look like it's a Blast RADIUS attack. The
++ * caller will now verify the packet signature.
++ */
++ return 0;
++}
++
+ /*
+ * Receive one packet, maybe.
+ */
+@@ -1051,6 +1177,21 @@ static int recv_one_packet(int wait_time)
+ }
+ request = fr_packet2myptr(rc_request_t, packet, packet_p);
+
++
++ /*
++ * We want radclient to be able to send any packet, including
++ * imperfect ones. However, we do NOT want to be vulnerable to
++ * the "Blast RADIUS" issue. Instead of adding command-line
++ * flags to enable/disable similar flags to what the server
++ * sends, we just do a few more smart checks to double-check
++ * things.
++ */
++ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) &&
++ blast_radius_check(request, reply) < 0) {
++ rad_free(&reply);
++ return -1;
++ }
++
+ /*
+ * Fails the signature validation: not a real reply.
+ * FIXME: Silently drop it and listen for another packet.
+@@ -1183,7 +1324,7 @@ int main(int argc, char **argv)
+ exit(1);
+ }
+
+- while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx"
++ while ((c = getopt(argc, argv, "46bc:d:D:f:Fhn:p:qr:sS:t:vx"
+ #ifdef WITH_TCP
+ "P:"
+ #endif
+@@ -1192,6 +1333,10 @@ int main(int argc, char **argv)
+ force_af = AF_INET;
+ break;
+
++ case 'b':
++ blast_radius = true;
++ break;
++
+ case '6':
+ force_af = AF_INET6;
+ break;
+diff --git a/src/main/radtest.in b/src/main/radtest.in
+index 38b1ba9a0f..8a6741a26c 100644
+--- a/src/main/radtest.in
++++ b/src/main/radtest.in
+@@ -19,6 +19,7 @@ usage() {
+ echo " -x Enable debug output" >&2
+ echo " -4 Use IPv4 for the NAS address (default)" >&2
+ echo " -6 Use IPv6 for the NAS address" >&2
++ echo " -6 Mandate checks for Blast RADIUS (this is not set by default)." >&2
+ exit 1
+ }
+
+@@ -55,6 +56,10 @@ do
+ NAS_ADDR_ATTR="NAS-IPv6-Address"
+ shift
+ ;;
++ -b)
++ OPTIONS="$OPTIONS -b"
++ shift
++ ;;
+ -d)
+ OPTIONS="$OPTIONS -d $2"
+ shift;shift
+@@ -120,7 +125,6 @@ fi
+ echo "$PASSWORD = \"$2\""
+ echo "$NAS_ADDR_ATTR = $nas"
+ echo "NAS-Port = $4"
+- echo "Message-Authenticator = 0x00"
+ if [ "$radclient" = "$radeapclient" ]
+ then
+ echo "EAP-Code = Response"
+diff --git a/src/main/realms.c b/src/main/realms.c
+index eb42598116..5e1215c0bb 100644
+--- a/src/main/realms.c
++++ b/src/main/realms.c
+@@ -366,7 +366,10 @@ static CONF_PARSER home_server_coa[] = {
+ };
+ #endif
+
++static const char *require_message_authenticator = NULL;
++
+ static CONF_PARSER home_server_config[] = {
++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL },
+ { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL },
+ { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL },
+ { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL },
+@@ -640,6 +643,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
+ home->cs = cs;
+ home->state = HOME_STATE_UNKNOWN;
+ home->proto = IPPROTO_UDP;
++ home->require_ma = main_config.require_ma;
++
++ require_message_authenticator = false;
+
+ /*
+ * Parse the configuration into the home server
+@@ -647,6 +653,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
+ */
+ if (cf_section_parse(cs, home, home_server_config) < 0) goto error;
+
++ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) {
++ goto error;
++ }
++
+ /*
+ * It has an IP address, it must be a remote server.
+ */
+@@ -924,6 +934,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
+ * Parse the SSL client configuration.
+ */
+ if (tls) {
++ home->require_ma = false;
+ home->tls = tls_client_conf_parse(tls);
+ if (!home->tls) {
+ goto error;
+diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c
+index 0eed87b64f..4ae3c5b975 100644
+--- a/src/main/tls_listen.c
++++ b/src/main/tls_listen.c
+@@ -299,6 +299,8 @@ get_application_data:
+ packet->vps = NULL;
+ PTHREAD_MUTEX_UNLOCK(&sock->mutex);
+
++ packet->tls = true;
++
+ if (!rad_packet_ok(packet, 0, NULL)) {
+ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
+ DEBUG("Closing TLS socket from client");
+@@ -713,6 +715,8 @@ int proxy_tls_recv(rad_listen_t *listener)
+ memcpy(packet->data, data, packet->data_len);
+ memcpy(packet->vector, packet->data + 4, 16);
+
++ packet->tls = true;
++
+ /*
+ * FIXME: Client MIB updates?
+ */
+@@ -765,6 +769,7 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request)
+ * if there's no packet, encode it here.
+ */
+ if (!request->proxy->data) {
++ request->reply->tls = true;
+ request->proxy_listener->encode(request->proxy_listener,
+ request);
+ }
+--
+2.35.7
+
@@ -35,6 +35,7 @@ SRC_URI = "git://github.com/FreeRADIUS/freeradius-server.git;branch=v3.0.x;lfs=0
file://0001-version.c-don-t-print-build-flags.patch \
file://CVE-2022-41860.patch \
file://CVE-2022-41861.patch \
+ file://CVE-2024-3596.patch \
"
raddbdir="${sysconfdir}/${MLPREFIX}raddb"