From 1dd179f3366c6229b1dfe2420f9b3cf4aace4404 Mon Sep 17 00:00:00 2001 From: Antonio Torres Date: Wed, 10 Jul 2024 11:46:17 +0200 Subject: [PATCH] Backport fix for BlastRADIUS CVE Resolves: RHEL-46567 Signed-off-by: Antonio Torres --- freeradius-Backport-OpenSSL3-fixes.patch | 7332 +++++++++++++++++++++- freeradius.spec | 6 +- 2 files changed, 7208 insertions(+), 130 deletions(-) diff --git a/freeradius-Backport-OpenSSL3-fixes.patch b/freeradius-Backport-OpenSSL3-fixes.patch index d16aa28..92d22c1 100644 --- a/freeradius-Backport-OpenSSL3-fixes.patch +++ b/freeradius-Backport-OpenSSL3-fixes.patch @@ -5,6 +5,7 @@ Backport TLS and OpenSSL3 fixes from the 3.0.x branch as of May 24th, 2022. Related: rhbz#1978216 Related: rhbz#2083699 +Related: rhbz#2263240 Signed-off-by: Antonio Torres [antorres@redhat.com]: these changes include the macro WITH_FIPS, which allows FreeRADIUS @@ -12,32 +13,61 @@ to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this ma [antorres@redhat.com]: backported tls.c, tls-h changes from 3.2.x branch. [antorres@redhat.com]: the sites-available/tls file has been modified to add the fix_cert_order option. [antorres@redhat.com]: mods-available/eap has been modified to comment out 'disable_tlsv1' and 'dh_file' options. +[antorres@redhat.com]: add fix for BlastRADIUS CVE, commit range backported: 3a00a6ecc188629b0441fd45ad61ca8986de156e^..da643f1edc267ce95260dc36069e6f1a7a4d66f8, +this backport includes changes from other files not included in the commit range, to ensure correct compilation. --- + man/man1/radclient.1 | 10 +- + man/man1/radtest.1 | 13 +- + raddb/clients.conf | 98 +- raddb/mods-available/eap | 6 +- + raddb/proxy.conf | 83 +- + raddb/radiusd.conf.in | 280 ++- raddb/sites-available/tls | 8 + + share/dictionary.freeradius | 5 + share/dictionary.freeradius.internal | 54 +- - src/include/build.h | 25 +- - src/include/libradius.h | 23 +- - src/include/listen.h | 26 +- + src/include/build.h | 29 +- + src/include/clients.h | 16 +- + src/include/conffile.h | 2 + + src/include/event.h | 3 + + src/include/libradius.h | 43 +- + src/include/listen.h | 30 +- src/include/md4.h | 50 +- src/include/md5.h | 33 +- + src/include/missing-h | 4 + src/include/openssl3.h | 109 ++ + src/include/process.h | 9 + + src/include/radius.h | 1 + + src/include/radiusd.h | 27 +- + src/include/realms.h | 23 +- + src/include/socket.h | 53 + src/include/tls-h | 45 +- src/include/token.h | 7 +- src/lib/dict.c | 150 +- + src/lib/event.c | 153 +- src/lib/hmacmd5.c | 6 +- src/lib/hmacsha1.c | 5 +- src/lib/md4.c | 1 + src/lib/md5.c | 1 + src/lib/pair.c | 97 +- src/lib/print.c | 19 +- - src/lib/radius.c | 300 ++- + src/lib/radius.c | 428 ++++- src/lib/token.c | 24 +- src/main/cb.c | 121 +- + src/main/client.c | 255 ++- + src/main/command.c | 220 ++- + src/main/conffile.c | 71 +- + src/main/listen.c | 582 +++++- + src/main/mainconfig.c | 130 +- src/main/map.c | 54 +- - src/main/radclient.c | 77 +- + src/main/process.c | 640 +++++-- + src/main/radclient.c | 231 ++- + src/main/radiusd.c | 1 + + src/main/radtest.in | 8 +- + src/main/realms.c | 354 +++- + src/main/session.c | 33 +- + src/main/stats.c | 177 +- src/main/tls.c | 2012 ++++++++++++++++---- - src/main/tls_listen.c | 177 +- + src/main/tls_listen.c | 509 ++++- src/modules/proto_dhcp/rlm_dhcp.c | 2 +- src/modules/rlm_eap/libeap/eap_tls.c | 178 +- src/modules/rlm_eap/libeap/eap_tls.h | 10 +- @@ -70,8 +100,208 @@ to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this ma src/modules/rlm_wimax/milenage.h | 128 ++ src/modules/rlm_wimax/rlm_wimax.c | 429 ++++- src/tests/keywords/md4 | 58 + - 57 files changed, 6032 insertions(+), 1199 deletions(-) + 84 files changed, 9222 insertions(+), 1902 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..d90651aba8 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. +@@ -35,7 +44,7 @@ Use \fIproto\fP transport protocol ("tcp" or "udp"). + Only available if FreeRADIUS is compiled with TCP transport support. + + .IP "\-t \fIpap/chap/mschap/eap-md5\fP" +-Choose the authentiction method to use. e.g. "-t pap", "-t chap", "-t ++Choose the authentication method to use. e.g. "-t pap", "-t chap", "-t + mschap", or "-t eap-md5",. Defaults to "pap". Using EAP-MD5 requires + that the "radeapclient" program is installed. + +diff --git a/raddb/clients.conf b/raddb/clients.conf +index 76b300d3c5..28bd6863b5 100644 +--- a/raddb/clients.conf ++++ b/raddb/clients.conf +@@ -82,17 +82,33 @@ client localhost { + # Quotation marks can be entered by escaping them, + # e.g. "foo\"bar" + # +- # A note on security: The security of the RADIUS protocol ++ # A note on security: The security of the RADIUS protocol + # depends COMPLETELY on this secret! We recommend using a +- # shared secret that is composed of: ++ # shared secret that at LEAST 16 characters long. It should ++ # preferably be 32 characters in length. The secret MUST be ++ # random, and should not be words, phrase, or anything else ++ # that is recognisable. + # +- # upper case letters +- # lower case letters +- # numbers ++ # Computing power has increased enormously since RADIUS was ++ # first defined. A hobbyist with a high-end GPU can try ALL ++ # of the 8-character shared secrets in about a day. The ++ # security of shared secrets increases MUCH more with the ++ # length of the shared secret, than with number of different ++ # characters used in it. So don't bother trying to use ++ # "special characters" or anything else in an attempt to get ++ # un-guessable secrets. Instead, just get data from a secure ++ # random number generator, and use that. + # +- # And is at LEAST 8 characters long, preferably 16 characters in +- # length. The secret MUST be random, and should not be words, +- # phrase, or anything else that is recognisable. ++ # You should create shared secrets using a method like this: ++ # ++ # dd if=/dev/random bs=1 count=24 | base64 ++ # ++ # This process will give output which takes 24 random bytes, ++ # and converts them to 32 characters of ASCII. The output ++ # should be accepted by all RADIUS clients. ++ # ++ # You should NOT create shared secrets by hand. They will ++ # not be random. They will will be trivial to crack. + # + # The default secret below is only for testing, and should + # not be used in any real environment. +@@ -100,15 +116,45 @@ 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. ++ # 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". + # +- # allowed values: yes, no +- require_message_authenticator = 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 +@@ -260,6 +306,26 @@ client localhost_ipv6 { + # "clients = per_socket_clients". That IP address/port combination + # will then accept ONLY the clients listed in this section. + # ++# There are additional considerations when using clients from SQL. ++# ++# A client can be link to a virtual server via modules such as SQL. ++# This link is done via the following process: ++# ++# If there is no listener in a virtual server, SQL clients are added ++# to the global list for that virtual server. ++# ++# If there is a listener, and the first listener does not have a ++# "clients=..." configuration item, SQL clients are added to the ++# global list. ++# ++# If there is a listener, and the first one does have a "clients=..." ++# configuration item, SQL clients are added to that list. The client ++# { ...} ` configured in that list are also added for that listener. ++# ++# The only issue is if you have multiple listeners in a virtual ++# server, each with a different client list, then the SQL clients are ++# added only to the first listener. ++# + #clients per_socket_clients { + # client socket_client { + # ipaddr = 192.0.2.4 diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap index a89a783663..bf73485e3c 100644 --- a/raddb/mods-available/eap @@ -96,6 +326,512 @@ index a89a783663..bf73485e3c 100644 # Set min / max TLS version. Mainly for Debian # "trusty", which disables older versions of TLS, and +diff --git a/raddb/proxy.conf b/raddb/proxy.conf +index 91b4b37930..7295538d5e 100644 +--- a/raddb/proxy.conf ++++ b/raddb/proxy.conf +@@ -66,6 +66,46 @@ proxy server { + # + default_fallback = no + ++ # ++ # Whether or not we allow dynamic home servers. ++ # ++ # This setting should be "no" by default. If set to "yes", ++ # it can slow the server down, due to mutex locking across ++ # multiple threads. ++ # ++ # Dynamic servers will work ONLY with the "directory" ++ # configuration below. ++ # ++# dynamic = yes ++ ++ # ++ # The directory which contains dynamic home servers. Each ++ # file in the directory should be a normal "home_server" ++ # definitions. This directory does not exist by default. ++ # ++ # e.g: The content of home_servers/example.com should be ++ # a home server definition. ++ # ++ # The name of the home server MUST be the same as the ++ # filename. ++ # ++ # Each home server must be set to only one type. e.g. ++ # "type = auth", and not "type = auth+acct" ++ # ++ # For example: ++ # ++ # home_server example.com { ++ # type = auth ++ # ipaddr = ... ++ # ... ++ # } ++ # ++ # For complete documentation, please see ++ # ++ # doc/configuration/dynamic_home_servers.md ++ # ++# directory = ${confdir}/home_servers ++ + } + + ####################################################################### +@@ -111,6 +151,12 @@ proxy server { + # which was awkward. In 2.0, they have been made independent + # from realms, which is better for a number of reasons. + # ++# You can proxy to a specific home server by doing: ++# ++# update control { ++# Home-Server-Name = "name of home server" ++# } ++# + home_server localhost { + # + # Home servers can be sent Access-Request packets +@@ -204,6 +250,24 @@ 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, +@@ -510,6 +574,12 @@ home_server localhost { + # 10 'realm" sections, and one "home_server_pool" section to tie the + # two together. + # ++# You can proxy to a specific home server pool by doing: ++# ++# update control { ++# Home-Server-Pool = "name of pool" ++# } ++# + home_server_pool my_auth_failover { + # + # The type of this pool controls how home servers are chosen. +@@ -664,10 +734,9 @@ realm example.com { + auth_pool = my_auth_failover + # acct_pool = acct + +- # As of Version 3.0, the server can proxy CoA packets +- # based on the Operator-Name attribute. This requires +- # that the "suffix" module be listed in the "recv-coa" +- # section. ++ # The server can proxy CoA packets based on the Operator-Name ++ # attribute. This requires that the "suffix" module be ++ # listed in the "recv-coa" section. + # + # See raddb/sites-available/coa + # +@@ -689,6 +758,9 @@ realm example.com { + # + # If you do not want this to happen, uncomment "nostrip" below. + # ++ # Note that if the system is doing EAP, you MUST set the "nostrip" ++ # option for realms used in EAP. Otherwise EAP will fail. ++ # + # nostrip + + # There are no more configuration entries for a realm. +@@ -790,9 +862,6 @@ realm LOCAL { + # Yes, this rule is different than the normal "unlang" rules for + # regular expressions. That may be fixed in a future release. + # +-# - for version 3.0.4 and following, with "correct_escapes = true", +-# use normal regex backslash rules. Just one. Not two. +-# + # - If you are matching domain names, put a '$' at the end of the regex + # that matches the domain name. This tells the regex matching code + # that the realm ENDS with the domain name, so it does not match +diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in +index e8aee3c001..4909c1b901 100644 +--- a/raddb/radiusd.conf.in ++++ b/raddb/radiusd.conf.in +@@ -25,7 +25,7 @@ + # The "sites-available" directory contains many + # worked examples of common configurations. + # +-# raddb/certs/README ++# raddb/certs/README.md + # How to create certificates for EAP or RadSec. + # + # Every configuration item in the server is documented +@@ -151,13 +151,21 @@ pidfile = ${run_dir}/${name}.pid + # + # correct_escapes: use correct backslash escaping + # ++# This setting is for compatibility with 3.0.4 and earlier. If ++# you're running a copy of the configuration from 3.0.4, you can ++# change this setting to "no" in order to run a new binary using the ++# old configuration files. ++# ++# If you've created the configuration after 2014, this should be set ++# to "true", and you can ignore it. ++# + # Prior to version 3.0.5, the handling of backslashes was a little + # awkward, i.e. "wrong". In some cases, to get one backslash into + # a regex, you had to put 4 in the config files. + # +-# Version 3.0.5 fixes that. However, for backwards compatibility, ++# Version 3.0.5 fixed that. However, for backwards compatibility, + # the new method of escaping is DISABLED BY DEFAULT. This means +-# that upgrading to 3.0.5 won't break your configuration. ++# that upgrading from a 3.0.4 or below won't break your configuration. + # + # If you don't have double backslashes (i.e. \\) in your configuration, + # this won't matter to you. If you do have them, fix that to use only +@@ -207,8 +215,8 @@ correct_escapes = true + + # max_request_time: The maximum time (in seconds) to handle a request. + # +-# Requests which take more time than this to process may be killed, and +-# a REJECT message is returned. ++# Requests which take more time than this to process are discarded, ++# and no reply is returned. + # + # WARNING: If you notice that requests take a long time to be handled, + # then this MAY INDICATE a bug in the server, in one of the modules +@@ -219,6 +227,12 @@ correct_escapes = true + # then it probably means that you haven't indexed the database. See your + # SQL server documentation for more information. + # ++# In general, values larger than 30 or so are useless. If the server ++# replies to the NAS after 60 seconds, the NAS will almost always ++# have given up on the request, and gone to another one. You can see ++# this happening when the logs have messages like "received conflicting ++# packet", and "discarding old request". ++# + # Useful range of values: 5 to 120 + # + max_request_time = 30 +@@ -279,6 +293,17 @@ max_requests = 16384 + # + hostname_lookups = no + ++# ++# Run a "Post-Auth-Type Client-Lost" section. This ONLY happens when ++# the server sends an Access-Challenge, and then client does not ++# respond to it. The goal is to allow administrators to log ++# something when the client does not respond. ++# ++# See sites-available/default, "Post-Auth-Type Client-Lost" for more ++# information. ++# ++#postauth_client_lost = no ++ + # + # Logging section. The various "log_*" configuration items + # will eventually be moved here. +@@ -377,6 +402,25 @@ log { + # The message when the user exceeds the Simultaneous-Use limit. + # + msg_denied = "You are already logged in - access denied" ++ ++ # Suppress "secret" attributes when printing them in debug mode. ++ # ++ # Secrets are NOT tracked across xlat expansions. If your ++ # configuration puts secrets into other strings, they will ++ # still get printed. ++ # ++ # Setting this to "yes" means that the server prints ++ # ++ # <<< secret >>> ++ # ++ # instead of the value, for attriburtes which contain secret ++ # information. e.g. User-Name, Tunnel-Password, etc. ++ # ++ # This configuration is disabled by default. It is extremely ++ # important for administrators to be able to debug user logins ++ # by seeing what is actually being sent. ++ # ++# suppress_secrets = no + } + + # The program to execute to do concurrency checks. +@@ -436,6 +480,24 @@ ENV { + # LD_PRELOAD = /path/to/library2.so + } + ++# ++# TEMPLATES ++# ++# Template files hold common definitions that can be used in other ++# server sections. When a template is referenced, the configuration ++# items within the referenced template are copied to the referencing ++# section. ++# ++# Using templates reduces repetition of common configuration items, ++# which in turn makes the server configuration easier to maintain. ++# ++# See templates.conf for examples of using templates, and the ++# referencing syntax. ++# ++ ++# $INCLUDE templates.conf ++ ++ + # SECURITY CONFIGURATION + # + # There may be multiple methods of attacking on the server. This +@@ -462,7 +524,7 @@ security { + # + # If you are worried about security issues related to this + # use of chdir, then simply ensure that the "raddb" directory +- # is inside of the chroot, end be sure to do "cd raddb" ++ # is inside of the chroot, and be sure to do "cd raddb" + # BEFORE starting the server. + # + # If the server is statically linked, then the only files +@@ -538,8 +600,7 @@ security { + # rejects will be sent at 'cleanup_delay' time, when the request + # is deleted from the internal cache of requests. + # +- # As of Version 3.0.5, "reject_delay" has sub-second resolution. +- # e.g. "reject_delay = 1.4" seconds is possible. ++ # This number can be a decimal, e.g. 3.4 + # + # Useful ranges: 1 to 5 + reject_delay = 1 +@@ -564,6 +625,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@ + } + +@@ -754,19 +1000,19 @@ modules { + # directory from start to finish. Which means that the + # modules are read off of disk randomly. + # +- # As of 3.0.18, you can list individual modules *before* the +- # directory inclusion. Those modules will be loaded first. +- # Then, when the directory is read, those modules will be +- # skipped and not read twice. ++ # You can list individual modules *before* the directory ++ # inclusion. Those modules will be loaded first. Then, when ++ # the directory is read, those modules will be skipped and ++ # not read twice. + # + # $INCLUDE mods-enabled/sql + + # +- # As of 3.0, modules are in mods-enabled/. Files matching +- # the regex /[a-zA-Z0-9_.]+/ are loaded. The modules are +- # initialized ONLY if they are referenced in a processing +- # section, such as authorize, authenticate, accounting, +- # pre/post-proxy, etc. ++ # All modules are in ther mods-enabled/ directory. Files ++ # matching the regex /[a-zA-Z0-9_.]+/ are read. The ++ # modules are initialized ONLY if they are referenced in a ++ # processing section, such as authorize, authenticate, ++ # accounting, pre/post-proxy, etc. + # + $INCLUDE mods-enabled/ + } diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls index e2a3b080ca..25a10b6364 100644 --- a/raddb/sites-available/tls @@ -115,6 +851,27 @@ index e2a3b080ca..25a10b6364 100644 # # For TLS-PSK, the key should be specified # dynamically, instead of using a hard-coded +diff --git a/share/dictionary.freeradius b/share/dictionary.freeradius +index c92c5214ad..bfab9296c4 100644 +--- a/share/dictionary.freeradius ++++ b/share/dictionary.freeradius +@@ -107,6 +107,8 @@ VALUE FreeRADIUS-Stats-Server-State Alive 0 + VALUE FreeRADIUS-Stats-Server-State Zombie 1 + VALUE FreeRADIUS-Stats-Server-State Dead 2 + VALUE FreeRADIUS-Stats-Server-State Idle 3 ++VALUE FreeRADIUS-Stats-Server-State Admin-Down 4 ++VALUE FreeRADIUS-Stats-Server-State Connection-Fail 5 + + # + # When a home server is marked "dead" or "alive" +@@ -182,4 +184,7 @@ ATTRIBUTE FreeRADIUS-EAP-FAST-PKCS 186.20 octets + + ATTRIBUTE FreeRADIUS-Stats-Error 187 string + ++ATTRIBUTE FreeRADIUS-Stats-Client-IPv6-Address 188 ipv6addr ++ATTRIBUTE FreeRADIUS-Stats-Server-IPv6-Address 189 ipv6addr ++ + END-VENDOR FreeRADIUS diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal index 724e1f7ff6..347e3e59f3 100644 --- a/share/dictionary.freeradius.internal @@ -248,7 +1005,7 @@ index 724e1f7ff6..347e3e59f3 100644 # # And Post-Proxy diff --git a/src/include/build.h b/src/include/build.h -index 5da940c2b7..e1c2a1c79b 100644 +index 5da940c2b7..c5eaa45457 100644 --- a/src/include/build.h +++ b/src/include/build.h @@ -46,9 +46,13 @@ extern "C" { @@ -286,20 +1043,134 @@ index 5da940c2b7..e1c2a1c79b 100644 /* * Macros to add pragmas */ -@@ -137,6 +153,11 @@ extern "C" { +@@ -66,6 +82,7 @@ extern "C" { + * Macros for controlling warnings in GCC >= 4.2 and clang >= 2.8 + */ + #if defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 ++# define DIAG_UNKNOWN_PRAGMAS pragmas + # define DIAG_PRAGMA(_x) PRAGMA(GCC diagnostic _x) + # if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 + # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) +@@ -75,10 +92,12 @@ extern "C" { + # define DIAG_ON(_x) DIAG_PRAGMA(warning JOINSTR(-W,_x)) + # endif + #elif defined(__clang__) && ((__clang_major__ * 100) + __clang_minor__ >= 208) ++# define DIAG_UNKNOWN_PRAGMAS unknown-pragmas + # define DIAG_PRAGMA(_x) PRAGMA(clang diagnostic _x) + # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) + # define DIAG_ON(_x) DIAG_PRAGMA(pop) + #else ++# define DIAG_UNKNOWN_PRAGMAS + # define DIAG_OFF(_x) + # define DIAG_ON(_x) + #endif +@@ -137,6 +156,12 @@ extern "C" { # endif #endif +#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) +#define NEVER_RETURNS CC_HINT(noreturn) ++#define HIDDEN CC_HINT(visibility("hidden")) +#define UNUSED CC_HINT(unused) +#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ + #ifdef __cplusplus } #endif +diff --git a/src/include/clients.h b/src/include/clients.h +index 560211557f..7d3288a489 100644 +--- a/src/include/clients.h ++++ b/src/include/clients.h +@@ -26,10 +26,14 @@ + * @copyright 2015 The FreeRADIUS server project + */ + ++typedef struct radclient_list RADCLIENT_LIST; ++ ++ + /** Describes a host allowed to send packets to the server + * + */ + typedef struct radclient { ++ RADCLIENT_LIST *list; //!< parent list + fr_ipaddr_t ipaddr; //!< IPv4/IPv6 address of the host. + fr_ipaddr_t src_ipaddr; //!< IPv4/IPv6 address to send responses + //!< from (family must match ipaddr). +@@ -39,7 +43,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). + +@@ -91,16 +99,14 @@ typedef struct radclient { + + #ifdef WITH_COA + char const *coa_name; //!< Name of the CoA home server or pool. +- home_server_t *coa_server; //!< The CoA home_server_t the client is associated with. ++ home_server_t *coa_home_server; //!< The CoA home_server_t the client is associated with. + //!< Must be used exclusively from coa_pool. +- home_pool_t *coa_pool; //!< The CoA home_pool_t the client is associated with. ++ home_pool_t *coa_home_pool; //!< The CoA home_pool_t the client is associated with. + //!< Must be used exclusively from coa_server. + bool defines_coa_server; //!< Client also defines a home_server. + #endif + } RADCLIENT; + +-typedef struct radclient_list RADCLIENT_LIST; +- + /** Callback for retrieving values when building client sections + * + * Example: +diff --git a/src/include/conffile.h b/src/include/conffile.h +index 8cb045c946..237469c880 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)\ +@@ -273,6 +274,7 @@ int cf_pair_count(CONF_SECTION const *cs); + CONF_SECTION *cf_item_parent(CONF_ITEM const *ci); + bool cf_item_is_section(CONF_ITEM const *item); + bool cf_item_is_pair(CONF_ITEM const *item); ++bool cf_item_is_data(CONF_ITEM const *item); + CONF_PAIR *cf_item_to_pair(CONF_ITEM const *item); + CONF_SECTION *cf_item_to_section(CONF_ITEM const *item); + CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp); +diff --git a/src/include/event.h b/src/include/event.h +index a29c9562a9..884edec526 100644 +--- a/src/include/event.h ++++ b/src/include/event.h +@@ -40,6 +40,7 @@ typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); + fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status); + + int fr_event_list_num_fds(fr_event_list_t *el); ++int fr_event_list_full(fr_event_list_t *el); + int fr_event_list_num_elements(fr_event_list_t *el); + + int fr_event_insert(fr_event_list_t *el, +@@ -53,6 +54,8 @@ int fr_event_now(fr_event_list_t *el, struct timeval *when); + + int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t handler, void *ctx); ++int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, ++ fr_event_fd_handler_t write_handler, void *ctx); + int fr_event_fd_delete(fr_event_list_t *el, int type, int fd); + int fr_event_loop(fr_event_list_t *el); + void fr_event_loop_exit(fr_event_list_t *el, int code); diff --git a/src/include/libradius.h b/src/include/libradius.h -index ce2f713de1..757828f070 100644 +index ce2f713de1..1b975517b5 100644 --- a/src/include/libradius.h +++ b/src/include/libradius.h @@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$") @@ -339,7 +1210,19 @@ index ce2f713de1..757828f070 100644 uint8_t encrypt; //!< Ecryption method. uint8_t length; } ATTR_FLAGS; -@@ -443,7 +448,7 @@ size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q +@@ -402,6 +407,11 @@ 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 { +@@ -443,7 +453,7 @@ size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote); @@ -348,7 +1231,7 @@ index ce2f713de1..757828f070 100644 size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp); void vp_print(FILE *, VALUE_PAIR const *); void vp_printlist(FILE *, VALUE_PAIR const *); -@@ -472,6 +477,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value); +@@ -472,6 +482,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value); int dict_init(char const *dir, char const *fn); void dict_free(void); int dict_read(char const *dir, char const *filename); @@ -357,7 +1240,21 @@ index ce2f713de1..757828f070 100644 void dict_attr_free(DICT_ATTR const **da); int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor); -@@ -586,6 +593,7 @@ int rad_vp2attr(RADIUS_PACKET const *packet, +@@ -507,6 +519,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); +@@ -586,6 +605,7 @@ int rad_vp2attr(RADIUS_PACKET const *packet, VALUE_PAIR const **pvp, uint8_t *ptr, size_t room); /* pair.c */ @@ -365,7 +1262,7 @@ index ce2f713de1..757828f070 100644 VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da); VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor); int fr_pair_to_unknown(VALUE_PAIR *vp); -@@ -611,6 +619,7 @@ VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor); +@@ -611,6 +631,7 @@ VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor); VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new); void fr_pair_delete_by_num(VALUE_PAIR **, unsigned int attr, unsigned int vendor, int8_t tag); void fr_pair_add(VALUE_PAIR **, VALUE_PAIR *); @@ -373,7 +1270,7 @@ index ce2f713de1..757828f070 100644 void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *add); int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b); int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b); -@@ -632,7 +641,7 @@ void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src); +@@ -632,7 +653,7 @@ void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src); void fr_pair_value_strcpy(VALUE_PAIR *vp, char const * src); void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const * src, size_t len); void fr_pair_value_sprintf(VALUE_PAIR *vp, char const * fmt, ...) CC_HINT(format (printf, 2, 3)); @@ -382,8 +1279,30 @@ index ce2f713de1..757828f070 100644 void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, unsigned int attr, unsigned int vendor, int8_t tag); void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, +@@ -694,7 +715,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 +953,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 + + #ifdef WITH_TCP diff --git a/src/include/listen.h b/src/include/listen.h -index 4f50bbf808..e8222a3f02 100644 +index 4f50bbf808..2b4e02bd54 100644 --- a/src/include/listen.h +++ b/src/include/listen.h @@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE { @@ -395,7 +1314,7 @@ index 4f50bbf808..e8222a3f02 100644 RAD_LISTEN_STATUS_FROZEN, RAD_LISTEN_STATUS_EOL, RAD_LISTEN_STATUS_REMOVE_NOW -@@ -68,11 +70,12 @@ struct rad_listen { +@@ -68,24 +70,41 @@ struct rad_listen { int fd; char const *server; int status; @@ -410,11 +1329,14 @@ index 4f50bbf808..e8222a3f02 100644 #endif bool nodup; bool synchronous; -@@ -80,12 +83,25 @@ struct rad_listen { ++ bool dead; + uint32_t workers; #ifdef WITH_TLS fr_tls_server_conf_t *tls; + bool check_client_connections; ++ bool nonblock; ++ bool blocked; #endif rad_listen_recv_t recv; @@ -436,7 +1358,11 @@ index 4f50bbf808..e8222a3f02 100644 rad_listen_print_t print; CONF_SECTION const *cs; -@@ -146,6 +162,12 @@ typedef struct listen_socket_t { +@@ -143,9 +162,16 @@ typedef struct listen_socket_t { + tls_session_t *ssn; + REQUEST *request; /* horrible hacks */ + VALUE_PAIR *certs; ++ uint32_t connect_timeout; pthread_mutex_t mutex; uint8_t *data; size_t partial; @@ -582,6 +1508,21 @@ index a44584564f..b7d571ac7b 100644 /* hmac.c */ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, +diff --git a/src/include/missing-h b/src/include/missing-h +index bb74e060b9..5698797b0e 100644 +--- a/src/include/missing-h ++++ b/src/include/missing-h +@@ -516,6 +516,10 @@ size_t SSL_SESSION_get_master_key(const SSL_SESSION *s, + #define O_DIRECTORY 0 + #endif + ++#ifndef O_NOFOLLOW ++#define O_NOFOLLOW 0 ++#endif ++ + /* + * Not really missing, but may be submitted as patches + * to the talloc project at some point in the future. diff --git a/src/include/openssl3.h b/src/include/openssl3.h new file mode 100644 index 0000000000..4423ee538a @@ -697,6 +1638,275 @@ index 0000000000..4423ee538a +#endif /* OPENSSL_VERSION_NUMBER */ +#endif +#endif /* FR_OPENSSL3_H */ +diff --git a/src/include/process.h b/src/include/process.h +index 8c3c7275e0..35a91bfa55 100644 +--- a/src/include/process.h ++++ b/src/include/process.h +@@ -47,6 +47,11 @@ typedef enum fr_state_action_t { /* server action */ + FR_ACTION_PROXY_REPLY, + #endif + FR_ACTION_CANCELLED, ++ FR_ACTION_CONFLICT, ++ FR_ACTION_MAX_TIME, ++ FR_ACTION_INTERNAL_FAILURE, ++ FR_ACTION_CLEANUP_DELAY, ++ FR_ACTION_COA_CANCELLED, + } fr_state_action_t; + + /* +@@ -66,9 +71,13 @@ int request_enqueue(REQUEST *request); + + int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *packet, + RADCLIENT *client, RAD_REQUEST_FUNP fun); ++void request_inject(REQUEST *request); + + #ifdef WITH_PROXY + int request_proxy_reply(RADIUS_PACKET *packet); ++ ++void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler); ++void proxy_listener_thaw(rad_listen_t *listener); + #endif + + #ifdef __cplusplus +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..2b9bc19f15 100644 +--- a/src/include/radiusd.h ++++ b/src/include/radiusd.h +@@ -114,6 +114,7 @@ typedef struct main_config { + fr_ipaddr_t myip; //!< IP to bind to. Set on command line. + uint16_t port; //!< Port to bind to. Set on command line. + ++ bool suppress_secrets; //!< for debug levels < 3 + bool log_auth; //!< Log all authentication attempts. + bool log_accept; //!< Log Access-Accept + bool log_reject; //!< Log Access-Reject +@@ -140,6 +141,8 @@ typedef struct main_config { + uint32_t cleanup_delay; //!< How long before cleaning up cached responses. + uint32_t max_requests; + ++ bool postauth_client_lost; //!< Whether to run Post-Auth-Type Client-Lost section ++ + uint32_t debug_level; + char const *log_file; + int syslog_facility; +@@ -171,6 +174,9 @@ 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. +@@ -191,9 +197,8 @@ typedef struct main_config { + typedef enum { + REQUEST_ACTIVE = 1, + REQUEST_STOP_PROCESSING, +- REQUEST_COUNTED + } rad_master_state_t; +-#define REQUEST_MASTER_NUM_STATES (REQUEST_COUNTED + 1) ++#define REQUEST_MASTER_NUM_STATES (REQUEST_STOP_PROCESSING + 1) + + typedef enum { + REQUEST_QUEUED = 1, +@@ -312,8 +317,10 @@ struct rad_request { + #define RAD_REQUEST_LVL_DEBUG3 (3) + #define RAD_REQUEST_LVL_DEBUG4 (4) + +-#define RAD_REQUEST_OPTION_COA (1 << 0) +-#define RAD_REQUEST_OPTION_CTX (1 << 1) ++#define RAD_REQUEST_OPTION_COA (1 << 0) ++#define RAD_REQUEST_OPTION_CTX (1 << 1) ++#define RAD_REQUEST_OPTION_CANCELLED (1 << 2) ++#define RAD_REQUEST_OPTION_STATS (1 << 3) + + #define SECONDS_PER_DAY 86400 + #define MAX_REQUEST_TIME 30 +@@ -351,8 +358,8 @@ extern char const *radacct_dir; + extern char const *radlog_dir; + extern char const *radlib_dir; + extern bool log_stripped_names; +-extern char const *radiusd_version; +-extern char const *radiusd_version_short; ++extern HIDDEN char const *radiusd_version; ++extern HIDDEN char const *radiusd_version_short; + void radius_signal_self(int flag); + + typedef enum { +@@ -374,8 +381,8 @@ int rad_accounting(REQUEST *); + int rad_coa_recv(REQUEST *request); + + /* session.c */ +-int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, char const *sessionid); +-int session_zap(REQUEST *request, uint32_t nasaddr, ++int rad_check_ts(fr_ipaddr_t const *nas_addr, uint32_t nas_port, char const *user, char const *sessionid); ++int session_zap(REQUEST *request, fr_ipaddr_t const *nas_addr, + uint32_t nas_port, char const *user, + char const *sessionid, uint32_t cliaddr, + char proto, int session_time); +@@ -558,6 +565,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); +@@ -583,7 +592,7 @@ void radius_event_free(void); + int radius_event_process(void); + void radius_update_listener(rad_listen_t *listener); + void revive_home_server(void *ctx); +-void mark_home_server_dead(home_server_t *home, struct timeval *when); ++void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down); + + /* evaluate.c */ + typedef struct fr_cond_t fr_cond_t; +diff --git a/src/include/realms.h b/src/include/realms.h +index 6dae8b4f85..171a449112 100644 +--- a/src/include/realms.h ++++ b/src/include/realms.h +@@ -21,10 +21,10 @@ typedef enum { + HOME_TYPE_INVALID = 0, + HOME_TYPE_AUTH, //!< Authentication server + HOME_TYPE_ACCT, //!< Accounting server +- HOME_TYPE_AUTH_ACCT //!< Authentication and accounting server ++ HOME_TYPE_AUTH_ACCT, //!< Authentication and accounting server + + #ifdef WITH_COA +- ,HOME_TYPE_COA //!< CoA destination (NAS or Proxy) ++ HOME_TYPE_COA, //!< CoA destination (NAS or Proxy) + #endif + } home_type_t; + +@@ -36,13 +36,16 @@ typedef enum { + } home_ping_check_t; + + typedef enum { +- HOME_STATE_UNKNOWN = 0, +- HOME_STATE_ALIVE, ++ HOME_STATE_ALIVE = 0, + HOME_STATE_ZOMBIE, + HOME_STATE_IS_DEAD, ++ HOME_STATE_UNKNOWN, ++ HOME_STATE_ADMIN_DOWN, + HOME_STATE_CONNECTION_FAIL, + } home_state_t; + ++#define HOME_SERVER_IS_DEAD(_x) (((_x)->state == HOME_STATE_IS_DEAD) || ((_x)->state == HOME_STATE_ADMIN_DOWN) || ((_x)->state == HOME_STATE_CONNECTION_FAIL)) ++ + typedef struct fr_socket_limit_t { + uint32_t max_connections; + uint32_t num_connections; +@@ -59,7 +62,11 @@ typedef struct home_server { + //!< stats or when specifying home servers for a pool. + + bool dual; //!< One of a pair of homeservers on consecutive ports. +- char const *server; //!< For internal proxying ++ bool dynamic; //!< is this a dynamically added home server? ++ bool nonblock; //!< Enable a socket non-blocking to the home server. ++ fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server ++ ++ char const *virtual_server; //!< For internal proxying + char const *parent_server; + + fr_ipaddr_t ipaddr; //!< IP address of home server. +@@ -122,6 +129,8 @@ typedef struct home_server { + #endif + #ifdef WITH_TLS + fr_tls_server_conf_t *tls; ++ uint32_t connect_timeout; ++ rbtree_t *listeners; + #endif + + #ifdef WITH_STATS +@@ -202,10 +211,14 @@ CONF_SECTION *home_server_cs_afrom_client(CONF_SECTION *client); + home_server_t *home_server_byname(char const *name, int type); + #endif + #ifdef WITH_STATS ++extern int home_server_max_number; + home_server_t *home_server_bynumber(int number); + #endif + home_pool_t *home_pool_byname(char const *name, int type); + ++int home_server_afrom_file(char const *filename); ++int home_server_delete(char const *name, char const *type); ++ + #ifdef __cplusplus + } + #endif +diff --git a/src/include/socket.h b/src/include/socket.h +new file mode 100644 +index 0000000000..821c2a0b7a +--- /dev/null ++++ b/src/include/socket.h +@@ -0,0 +1,53 @@ ++#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 ++ */ ++ ++/** Functions for establishing and managing low level sockets ++ * ++ * @file src/include/socket.h ++ * ++ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) ++ * @author Alan DeKok (aland@freeradius.org) ++ * ++ * @copyright 2015 The FreeRADIUS project ++ */ ++RCSIDH(socket_h, "$Id$") ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#include ++#include ++ ++#ifdef HAVE_SYS_UN_H ++# include ++/* ++ * The linux headers define the macro as: ++ * ++ * # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ ++ * + strlen ((ptr)->sun_path)) ++ * ++ * Which trips UBSAN, because it sees an operation on a NULL pointer. ++ */ ++# undef SUN_LEN ++# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) ++#endif ++ ++#ifdef __cplusplus ++} ++#endif diff --git a/src/include/tls-h b/src/include/tls-h index 62f57c4715..4bf1665483 100644 --- a/src/include/tls-h @@ -1138,6 +2348,292 @@ index 96e06b5287..479bf1104e 100644 +{ + return fr_hash_table_walk(attributes_byname, callback, context); +} +diff --git a/src/lib/event.c b/src/lib/event.c +index 0c2976b3c9..d912955fe8 100644 +--- a/src/lib/event.c ++++ b/src/lib/event.c +@@ -28,6 +28,8 @@ RCSID("$Id$") + #include + #include + ++#include ++ + #ifdef HAVE_KQUEUE + #ifndef HAVE_SYS_EVENT_H + #error kqueue requires +@@ -39,7 +41,9 @@ RCSID("$Id$") + + typedef struct fr_event_fd_t { + int fd; ++ + fr_event_fd_handler_t handler; ++ fr_event_fd_handler_t write_handler; + void *ctx; + } fr_event_fd_t; + +@@ -61,13 +65,15 @@ struct fr_event_list_t { + int num_readers; + #ifndef HAVE_KQUEUE + int max_readers; ++ int max_fd; + +- bool changed; +- ++ fd_set read_fds; ++ fd_set write_fds; + #else + int kq; + struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ + #endif ++ + fr_event_fd_t readers[FR_EV_MAX_FDS]; + }; + +@@ -139,8 +145,9 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) + } + + #ifndef HAVE_KQUEUE +- el->changed = true; /* force re-set of fds's */ +- ++ el->max_fd = 0; ++ FD_ZERO(&el->read_fds); ++ FD_ZERO(&el->write_fds); + #else + el->kq = kqueue(); + if (el->kq < 0) { +@@ -161,6 +168,11 @@ int fr_event_list_num_fds(fr_event_list_t *el) + return el->num_readers; + } + ++int fr_event_list_full(fr_event_list_t *el) ++{ ++ return (el->num_readers >= FR_EV_MAX_FDS); ++} ++ + int fr_event_list_num_elements(fr_event_list_t *el) + { + if (!el) return 0; +@@ -432,6 +444,9 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + el->num_readers++; + + if (i == el->max_readers) el->max_readers = i + 1; ++ ++ FD_SET(fd, &el->read_fds); ++ if (el->max_fd <= fd) el->max_fd = fd; + break; + } + } +@@ -446,13 +461,69 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + ef->handler = handler; + ef->ctx = ctx; + +-#ifndef HAVE_KQUEUE +- el->changed = true; +-#endif +- + return 1; + } + ++int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, ++ fr_event_fd_handler_t write_handler, void *ctx) ++{ ++ int i; ++ ++ if (!el || (fd < 0)) return 0; ++ ++ if (type != 0) return 0; ++ ++#ifdef HAVE_KQUEUE ++ for (i = 0; i < FR_EV_MAX_FDS; i++) { ++ int j; ++ struct kevent evset; ++ ++ j = (i + fd) & (FR_EV_MAX_FDS - 1); ++ ++ if (el->readers[j].fd != fd) continue; ++ ++ fr_assert(ctx = el->readers[j].ctx); ++ ++ /* ++ * Tell us when the socket is ready for writing ++ */ ++ if (write_handler) { ++ fr_assert(!el->readers[j].write_handler); ++ ++ el->readers[j].write_handler = write_handler; ++ ++ EV_SET(&evset, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); ++ } else { ++ fr_assert(el->readers[j].write_handler); ++ ++ el->readers[j].write_handler = NULL; ++ ++ EV_SET(&evset, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); ++ } ++ if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { ++ fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); ++ return 0; ++ } ++ ++ return 1; ++ } ++ ++#else ++ ++ for (i = 0; i < el->max_readers; i++) { ++ if (el->readers[i].fd != fd) continue; ++ ++ fr_assert(ctx = el->readers[i].ctx); ++ el->readers[i].write_handler = write_handler; ++ ++ FD_SET(fd, &el->write_fds); /* fd MUST already be in the set of readers! */ ++ return 1; ++ } ++#endif /* HAVE_KQUEUE */ ++ ++ return 0; ++} ++ + int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + { + int i; +@@ -480,6 +551,14 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + ++ /* ++ * Delete the write handler if it exits. ++ */ ++ if (el->readers[j].write_handler) { ++ EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); ++ (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); ++ } ++ + el->readers[j].fd = -1; + el->num_readers--; + +@@ -487,14 +566,18 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + } + + #else +- + for (i = 0; i < el->max_readers; i++) { + if (el->readers[i].fd == fd) { + el->readers[i].fd = -1; + el->num_readers--; + + if ((i + 1) == el->max_readers) el->max_readers = i; +- el->changed = true; ++ FD_CLR(fd, &el->read_fds); ++ FD_CLR(fd, &el->write_fds); ++ ++ /* ++ * @todo - update el->max_fd, too. ++ */ + return 1; + } + } +@@ -523,39 +606,13 @@ int fr_event_loop(fr_event_list_t *el) + #ifdef HAVE_KQUEUE + struct timespec ts_when, *ts_wake; + #else +- int maxfd = 0; +- fd_set read_fds, master_fds; +- +- el->changed = true; ++ fd_set read_fds, write_fds; + #endif + + el->exit = 0; + el->dispatch = true; + + while (!el->exit) { +-#ifndef HAVE_KQUEUE +- /* +- * Cache the list of FD's to watch. +- */ +- if (el->changed) { +-#ifdef __clang_analyzer__ +- memset(&master_fds, 0, sizeof(master_fds)); +-#else +- FD_ZERO(&master_fds); +-#endif +- for (i = 0; i < el->max_readers; i++) { +- if (el->readers[i].fd < 0) continue; +- +- if (el->readers[i].fd > maxfd) { +- maxfd = el->readers[i].fd; +- } +- FD_SET(el->readers[i].fd, &master_fds); +- } +- +- el->changed = false; +- } +-#endif /* HAVE_KQUEUE */ +- + /* + * Find the first event. If there's none, we wait + * on the socket forever. +@@ -604,8 +661,9 @@ int fr_event_loop(fr_event_list_t *el) + if (el->status) el->status(wake); + + #ifndef HAVE_KQUEUE +- read_fds = master_fds; +- rcode = select(maxfd + 1, &read_fds, NULL, NULL, wake); ++ read_fds = el->read_fds; ++ write_fds = el->write_fds; ++ rcode = select(el->max_fd + 1, &read_fds, &write_fds, NULL, wake); + if ((rcode < 0) && (errno != EINTR)) { + fr_strerror_printf("Failed in select: %s", fr_syserror(errno)); + el->dispatch = false; +@@ -618,6 +676,7 @@ int fr_event_loop(fr_event_list_t *el) + ts_wake = &ts_when; + ts_when.tv_sec = when.tv_sec; + ts_when.tv_nsec = when.tv_usec * 1000; ++ + } else { + ts_wake = NULL; + } +@@ -644,11 +703,16 @@ int fr_event_loop(fr_event_list_t *el) + + if (ef->fd < 0) continue; + ++ /* ++ * Check if the socket is available for writing. ++ */ ++ if (ef->write_handler && FD_ISSET(ef->fd, &write_fds)) { ++ ef->write_handler(el, ef->fd, ef->ctx); ++ } ++ + if (!FD_ISSET(ef->fd, &read_fds)) continue; + + ef->handler(el, ef->fd, ef->ctx); +- +- if (el->changed) break; + } + + #else /* HAVE_KQUEUE */ +@@ -673,6 +737,11 @@ int fr_event_loop(fr_event_list_t *el) + continue; + } + ++ if (el->events[i].filter == EVFILT_WRITE) { ++ ef->write_handler(el, ef->fd, ef->ctx); ++ continue; ++ } ++ + /* + * Else it's our event. We only set + * EVFILT_READ, so it must be a read +@@ -719,7 +788,7 @@ static uint32_t event_rand(void) + { + uint32_t num; + +- num = rand_pool.randrsl[rand_pool.randcnt++]; ++ num = rand_pool.randrsl[rand_pool.randcnt++ & 0xff]; + if (rand_pool.randcnt == 256) { + fr_isaac(&rand_pool); + rand_pool.randcnt = 0; diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c index 1cca00fa2a..2aad490e30 100644 --- a/src/lib/hmacmd5.c @@ -1434,7 +2930,7 @@ index 9ac927358b..57455b6f30 100644 str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); } else { diff --git a/src/lib/radius.c b/src/lib/radius.c -index 3881111f7d..524e68088e 100644 +index 3881111f7d..6dcee141e2 100644 --- a/src/lib/radius.c +++ b/src/lib/radius.c @@ -28,6 +28,7 @@ RCSID("$Id$") @@ -1445,7 +2941,19 @@ index 3881111f7d..524e68088e 100644 #include #include -@@ -528,6 +529,8 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, +@@ -142,8 +143,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", + }; + + +@@ -528,6 +530,8 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, for ( i = 0; i < length; i++ ) { digest[i] ^= value[i]; } @@ -1454,7 +2962,7 @@ index 3881111f7d..524e68088e 100644 } #define MAX_PASS_LEN (128) -@@ -562,8 +565,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, +@@ -562,8 +566,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, *outlen = len; fr_md5_init(&context); @@ -1465,7 +2973,7 @@ index 3881111f7d..524e68088e 100644 /* * Do first pass. -@@ -572,7 +576,7 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, +@@ -572,7 +577,7 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, for (n = 0; n < len; n += AUTH_PASS_LEN) { if (n > 0) { @@ -1474,7 +2982,7 @@ index 3881111f7d..524e68088e 100644 fr_md5_update(&context, passwd + n - AUTH_PASS_LEN, AUTH_PASS_LEN); -@@ -585,6 +589,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, +@@ -585,6 +590,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, } memcpy(output, passwd, len); @@ -1484,7 +2992,7 @@ index 3881111f7d..524e68088e 100644 } -@@ -653,8 +660,9 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, +@@ -653,8 +661,9 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, output[2] = inlen; /* length of the password string */ fr_md5_init(&context); @@ -1495,7 +3003,7 @@ index 3881111f7d..524e68088e 100644 fr_md5_update(&context, vector, AUTH_VECTOR_LEN); fr_md5_update(&context, &output[0], 2); -@@ -663,7 +671,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, +@@ -663,7 +672,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, size_t block_len; if (n > 0) { @@ -1504,7 +3012,7 @@ index 3881111f7d..524e68088e 100644 fr_md5_update(&context, output + 2 + n - AUTH_PASS_LEN, AUTH_PASS_LEN); -@@ -681,6 +689,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, +@@ -681,6 +690,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, output[i + 2 + n] ^= digest[i]; } } @@ -1513,7 +3021,7 @@ index 3881111f7d..524e68088e 100644 } static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest) -@@ -1552,6 +1562,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, +@@ -1552,6 +1563,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, VERIFY_VP(vp); @@ -1522,7 +3030,7 @@ index 3881111f7d..524e68088e 100644 if (vp->da->vendor != 0) { fr_strerror_printf("rad_vp2rfc called with VSA"); return -1; -@@ -1594,6 +1606,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, +@@ -1594,6 +1607,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, return 18; } @@ -1611,7 +3119,96 @@ index 3881111f7d..524e68088e 100644 /* * EAP-Message is special. */ -@@ -2036,6 +2130,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, +@@ -1700,6 +1795,14 @@ 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 +1815,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 +1879,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 +1957,14 @@ 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; + + /* +@@ -1973,11 +2106,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: +- case PW_CODE_DISCONNECT_ACK: +- case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_REQUEST: +- case PW_CODE_COA_ACK: +- case PW_CODE_COA_NAK: + memset(hdr->vector, 0, AUTH_VECTOR_LEN); + break; + +@@ -1985,6 +2114,10 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: ++ case PW_CODE_DISCONNECT_ACK: ++ case PW_CODE_DISCONNECT_NAK: ++ case PW_CODE_COA_ACK: ++ case PW_CODE_COA_NAK: + memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); + break; + +@@ -2036,6 +2169,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); fr_md5_final(digest, &context); @@ -1619,7 +3216,7 @@ index 3881111f7d..524e68088e 100644 memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); memcpy(packet->vector, digest, AUTH_VECTOR_LEN); -@@ -2159,6 +2254,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) +@@ -2159,6 +2293,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) fr_md5_update(&context, packet->data, packet->data_len); fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); fr_md5_final(digest, &context); @@ -1627,7 +3224,7 @@ index 3881111f7d..524e68088e 100644 /* * Return 0 if OK, 2 if not OK. -@@ -2198,6 +2294,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, +@@ -2198,6 +2333,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, fr_md5_update(&context, packet->data, packet->data_len); fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); fr_md5_final(calc_digest, &context); @@ -1635,7 +3232,94 @@ index 3881111f7d..524e68088e 100644 /* * Copy the packet's vector back to the packet. -@@ -2896,6 +2993,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre +@@ -2323,6 +2459,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 +2509,23 @@ 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. ++ * ++ * 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 (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; ++ require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]); + + /* +- * It's also required if the caller asks for it. ++ * We only limit Proxy-State if we're not requiring ++ * Message-Authenticator. + */ +- if (flags) require_ma = true; ++ limit_proxy_state = ((flags & 0x04) != 0) && !require_ma; + + /* + * Repeat the length checks. This time, instead of +@@ -2534,6 +2680,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 +2689,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 +2705,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 +2762,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))); +@@ -2896,6 +3061,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre } @@ -1751,7 +3435,59 @@ index 3881111f7d..524e68088e 100644 /** Convert a "concatenated" attribute to one long VP * */ -@@ -3503,7 +3709,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, +@@ -3014,6 +3288,7 @@ ssize_t rad_data2vp_tlvs(TALLOC_CTX *ctx, + tlv_len = data2vp(ctx, packet, original, secret, child, + data + 2, data[1] - 2, data[1] - 2, tail); + if (tlv_len < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ + fr_pair_list_free(&head); + return -1; + } +@@ -3101,7 +3376,10 @@ static ssize_t data2vp_vsa(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + attrlen - (dv->type + dv->length), + attrlen - (dv->type + dv->length), + pvp); +- if (my_len < 0) return my_len; ++ if (my_len < 0) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return my_len; ++ } + + return attrlen; + } +@@ -3154,7 +3432,10 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; ++ } + return attrlen; + } + +@@ -3165,7 +3446,6 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, attrlen - 2, attrlen - 2, + pvp); +- + if ((rcode < 0) || (((size_t) rcode + 2) != attrlen)) goto raw; /* didn't decode all of the data */ + return attrlen; + } +@@ -3307,7 +3587,10 @@ static ssize_t data2vp_wimax(TALLOC_CTX *ctx, + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; ++ } + return attrlen; + } + +@@ -3503,7 +3786,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, /* * WiMAX craziness */ @@ -1760,7 +3496,7 @@ index 3881111f7d..524e68088e 100644 rcode = data2vp_wimax(ctx, packet, original, secret, vendor, data, attrlen, packetlen, pvp); return rcode; -@@ -3610,19 +3816,12 @@ ssize_t data2vp(TALLOC_CTX *ctx, +@@ -3610,19 +3893,12 @@ ssize_t data2vp(TALLOC_CTX *ctx, return 0; } @@ -1784,7 +3520,20 @@ index 3881111f7d..524e68088e 100644 } /* -@@ -3926,44 +4125,44 @@ ssize_t data2vp(TALLOC_CTX *ctx, +@@ -3874,8 +4150,10 @@ ssize_t data2vp(TALLOC_CTX *ctx, + /* + * This requires a whole lot more work. + */ +- return data2vp_extended(ctx, packet, original, secret, child, +- start, attrlen, packetlen, pvp); ++ rcode = data2vp_extended(ctx, packet, original, secret, child, ++ start, attrlen, packetlen, pvp); ++ if (rcode < 0) dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; + + case PW_TYPE_EVS: + if (datalen < 6) goto raw; /* vid, vtype, value */ +@@ -3926,44 +4204,47 @@ ssize_t data2vp(TALLOC_CTX *ctx, default: raw: @@ -1832,7 +3581,11 @@ index 3881111f7d..524e68088e 100644 */ - alloc_cui: vp = fr_pair_afrom_da(ctx, da); - if (!vp) return -1; +- if (!vp) return -1; ++ if (!vp) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return -1; ++ } +alloc_raw: vp->vp_length = datalen; @@ -1845,7 +3598,12 @@ index 3881111f7d..524e68088e 100644 switch (da->type) { case PW_TYPE_STRING: p = talloc_array(vp, char, vp->vp_length + 1); -@@ -4072,6 +4271,8 @@ ssize_t data2vp(TALLOC_CTX *ctx, +@@ -4068,10 +4349,13 @@ ssize_t data2vp(TALLOC_CTX *ctx, + fail: + #endif + default: ++ dict_attr_free(&da); /* only frees unknowns */ + fr_pair_list_free(&vp); fr_strerror_printf("Internal sanity check %d", __LINE__); return -1; } @@ -1854,7 +3612,7 @@ index 3881111f7d..524e68088e 100644 vp->type = VT_DATA; *pvp = vp; -@@ -4112,6 +4313,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, +@@ -4112,6 +4396,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, return data2vp_concat(ctx, da, data, length, pvp); } @@ -1866,7 +3624,19 @@ index 3881111f7d..524e68088e 100644 /* * Note that we pass the entire length, not just the * length of this attribute. The Extended or WiMAX -@@ -4261,7 +4467,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, +@@ -4120,7 +4409,10 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, + */ + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, data[1] - 2, length - 2, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return rcode; ++ } + + return 2 + rcode; + } +@@ -4261,7 +4553,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, uint32_t num_attributes; uint8_t *ptr; radius_packet_t *hdr; @@ -1875,7 +3645,7 @@ index 3881111f7d..524e68088e 100644 /* * Extract attribute-value pairs -@@ -4385,8 +4591,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, +@@ -4385,8 +4677,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, secretlen = strlen(secret); fr_md5_init(&context); @@ -1886,7 +3656,7 @@ index 3881111f7d..524e68088e 100644 /* * Encrypt it in place. Don't bother checking -@@ -4397,7 +4604,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, +@@ -4397,7 +4690,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, fr_md5_update(&context, vector, AUTH_PASS_LEN); fr_md5_final(digest, &context); } else { @@ -1895,7 +3665,7 @@ index 3881111f7d..524e68088e 100644 fr_md5_update(&context, (uint8_t *) passwd + n - AUTH_PASS_LEN, AUTH_PASS_LEN); -@@ -4409,6 +4616,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, +@@ -4409,6 +4702,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, } } @@ -1905,7 +3675,7 @@ index 3881111f7d..524e68088e 100644 return 0; } -@@ -4441,8 +4651,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, +@@ -4441,8 +4737,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, secretlen = strlen(secret); fr_md5_init(&context); @@ -1916,7 +3686,7 @@ index 3881111f7d..524e68088e 100644 /* * The inverse of the code above. -@@ -4452,7 +4663,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, +@@ -4452,7 +4749,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, fr_md5_update(&context, vector, AUTH_VECTOR_LEN); fr_md5_final(digest, &context); @@ -1925,7 +3695,7 @@ index 3881111f7d..524e68088e 100644 if (pwlen > AUTH_PASS_LEN) { fr_md5_update(&context, (uint8_t *) passwd, AUTH_PASS_LEN); -@@ -4460,7 +4671,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, +@@ -4460,7 +4757,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, } else { fr_md5_final(digest, &context); @@ -1934,7 +3704,7 @@ index 3881111f7d..524e68088e 100644 if (pwlen > (n + AUTH_PASS_LEN)) { fr_md5_update(&context, (uint8_t *) passwd + n, AUTH_PASS_LEN); -@@ -4473,6 +4684,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, +@@ -4473,6 +4770,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, } done: @@ -1944,7 +3714,7 @@ index 3881111f7d..524e68088e 100644 passwd[pwlen] = '\0'; return strlen(passwd); } -@@ -4609,8 +4823,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, +@@ -4609,8 +4909,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, secretlen = strlen(secret); fr_md5_init(&context); @@ -1955,7 +3725,7 @@ index 3881111f7d..524e68088e 100644 /* * Set up the initial key: -@@ -4637,7 +4852,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, +@@ -4637,7 +4938,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, fr_md5_final(digest, &context); @@ -1964,7 +3734,16 @@ index 3881111f7d..524e68088e 100644 /* * A quick check: decrypt the first octet -@@ -4657,7 +4872,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, +@@ -4647,6 +4948,8 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + reallen = passwd[2] ^ digest[0]; + if (reallen > encrypted_len) { + fr_strerror_printf("tunnel password is too long for the attribute"); ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + return -1; + } + +@@ -4657,7 +4960,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, fr_md5_final(digest, &context); @@ -1973,7 +3752,7 @@ index 3881111f7d..524e68088e 100644 fr_md5_update(&context, passwd + n + 2, block_len); } -@@ -4669,6 +4884,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, +@@ -4669,6 +4972,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, *pwlen = reallen; passwd[reallen] = 0; @@ -2251,6 +4030,2253 @@ index 4ae14e575b..f8b2edbecc 100644 } #endif +diff --git a/src/main/client.c b/src/main/client.c +index 6228438c47..4d67a27688 100644 +--- a/src/main/client.c ++++ b/src/main/client.c +@@ -41,11 +41,16 @@ RCSID("$Id$") + #endif + + struct radclient_list { ++ char const *name; /* name of this list */ ++ char const *server; /* virtual server associated with this client list */ ++ + /* + * FIXME: One set of trees for IPv4, and another for IPv6? + */ + rbtree_t *trees[129]; /* for 0..128, inclusive. */ + uint32_t min_prefix; ++ ++ bool parsed; + }; + + +@@ -145,6 +150,15 @@ RADCLIENT_LIST *client_list_init(CONF_SECTION *cs) + + clients->min_prefix = 128; + ++ /* ++ * Associate the "clients" list with the virtual server. ++ */ ++ if (cs && (cf_data_add(cs, "clients", clients, NULL) < 0)) { ++ ERROR("Failed to associate client list with section %s\n", cf_section_name1(cs)); ++ client_list_free(clients); ++ return false; ++ } ++ + return clients; + } + +@@ -161,6 +175,18 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + + if (!client) return false; + ++ /* ++ * Initialize the global list, if not done already. ++ */ ++ if (!root_clients) { ++ root_clients = cf_data_find(main_config.config, "clients"); ++ if (!root_clients) root_clients = client_list_init(main_config.config); ++ if (!root_clients) { ++ ERROR("Cannot add client - failed creating client list"); ++ return false; ++ } ++ } ++ + /* + * Hack to fixup wildcard clients + * +@@ -188,66 +214,85 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + /* + * If the client also defines a server, do that now. + */ +- if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; ++ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_home_server)) return false; + + /* +- * If "clients" is NULL, it means add to the global list, +- * unless we're trying to add it to a virtual server... ++ * If there's no client list, BUT there's a virtual ++ * server, try to add the client to the appropriate ++ * "clients" section for that virtual server. + */ +- if (!clients) { +- if (client->server != NULL) { +- CONF_SECTION *cs; +- CONF_SECTION *subcs; +- +- cs = cf_section_sub_find_name2(main_config.config, "server", client->server); +- if (!cs) { +- ERROR("Failed to find virtual server %s", client->server); +- return false; +- } ++ if (!clients && client->server) { ++ CONF_SECTION *cs; ++ CONF_SECTION *subcs; ++ CONF_PAIR *cp; ++ char const *section_name; + +- /* +- * If this server has no "listen" section, add the clients +- * to the global client list. +- */ +- subcs = cf_section_sub_find(cs, "listen"); +- if (!subcs) { +- DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", +- client->server); +- goto global_clients; +- } ++ cs = cf_section_sub_find_name2(main_config.config, "server", client->server); ++ if (!cs) { ++ ERROR("Cannot add client - virtual server %s does not exist", client->server); ++ return false; ++ } + +- /* +- * If the client list already exists, use that. +- * Otherwise, create a new client list. +- */ +- clients = cf_data_find(cs, "clients"); +- if (!clients) { +- clients = client_list_init(cs); +- if (!clients) { +- ERROR("Out of memory"); +- return false; +- } ++ /* ++ * If this server has no "listen" section, add the clients ++ * to the global client list. ++ */ ++ subcs = cf_section_sub_find(cs, "listen"); ++ if (!subcs) { ++ DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", ++ client->server); ++ goto check_list; ++ } + +- if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { +- ERROR("Failed to associate clients with virtual server %s", client->server); +- client_list_free(clients); +- return false; +- } +- } ++ cp = cf_pair_find(subcs, "clients"); ++ if (!cp) { ++ DEBUG("No 'clients' configuration item in first listener of virtual server %s. Adding client to global client list", ++ client->server); ++ goto check_list; ++ } + +- } else { +- global_clients: +- /* +- * Initialize the global list, if not done already. +- */ +- if (!root_clients) { +- root_clients = client_list_init(NULL); +- if (!root_clients) return false; +- } +- clients = root_clients; ++ /* ++ * Duplicate the lookup logic in common_socket_parse() ++ * ++ * Explicit list given: use it. ++ */ ++ section_name = cf_pair_value(cp); ++ if (!section_name) goto check_list; ++ ++ subcs = cf_section_sub_find_name2(main_config.config, "clients", section_name); ++ if (!subcs) { ++ subcs = cf_section_find(section_name); ++ } ++ if (!subcs) { ++ cf_log_err_cs(cs, ++ "Failed to find clients %s {...}", ++ section_name); ++ return false; ++ } ++ ++ DEBUG("Adding client to client list %s", section_name); ++ ++ /* ++ * If the client list already exists, use that. ++ * Otherwise, create a new client list. ++ * ++ * @todo - add the client to _all_ listeners? ++ */ ++ clients = cf_data_find(subcs, "clients"); ++ if (clients) goto check_list; ++ ++ clients = client_list_init(subcs); ++ if (!clients) { ++ ERROR("Cannot add client - failed creating client list %s for server %s", section_name, ++ client->server); ++ return false; + } + } + ++check_list: ++ if (!clients) clients = root_clients; ++ client->list = clients; ++ + /* + * Create a tree for it. + */ +@@ -280,10 +325,11 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + #endif + #ifdef WITH_COA + namecmp(coa_name) && +- (old->coa_server == client->coa_server) && +- (old->coa_pool == client->coa_pool) && ++ (old->coa_home_server == client->coa_home_server) && ++ (old->coa_home_pool == client->coa_home_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 +491,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 +515,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 }, +@@ -512,14 +561,34 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + * it. Otherwise create a new one. + */ + clients = cf_data_find(section, "clients"); +- if (clients) return clients; +- +- clients = client_list_init(section); +- if (!clients) return NULL; ++ if (clients) { ++ /* ++ * Modules are initialized before the listeners. ++ * Which means that we MIGHT have read clients ++ * from SQL before parsing this "clients" ++ * section. So there may already be a clients ++ * list. ++ * ++ * But the list isn't _our_ list that we parsed, ++ * so we still need to parse the clients here. ++ */ ++ if (clients->parsed) return clients; ++ } else { ++ clients = client_list_init(section); ++ if (!clients) return NULL; ++ } + +- if (cf_top_section(section) == section) global = true; ++ if (cf_top_section(section) == section) { ++ global = true; ++ clients->name = "global"; ++ clients->server = NULL; ++ } + +- if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; ++ if (strcmp("server", cf_section_name1(section)) == 0) { ++ clients->name = NULL; ++ clients->server = cf_section_name2(section); ++ in_server = true; ++ } + + for (cs = cf_subsection_find_next(section, NULL, "client"); + cs; +@@ -592,8 +661,8 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { +- if (isalpha((int)*p) || +- isdigit((int)*p) || ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || + (*p == ':') || + (*p == '.')) continue; + break; +@@ -632,15 +701,6 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + + } + +- /* +- * Associate the clients structure with the section. +- */ +- if (cf_data_add(section, "clients", clients, NULL) < 0) { +- cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); +- client_list_free(clients); +- return NULL; +- } +- + /* + * Replace the global list of clients with the new one. + * The old one is still referenced from the original +@@ -648,6 +708,7 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + */ + if (global) root_clients = clients; + ++ clients->parsed = true; + return clients; + } + +@@ -663,7 +724,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), "" }, +@@ -824,7 +885,7 @@ int client_map_section(CONF_SECTION *out, CONF_SECTION const *map, client_value_ + * @param ctx to allocate new clients in. + * @param cs to process as a client. + * @param in_server Whether the client should belong to a specific virtual server. +- * @param with_coa If true and coa_server or coa_pool aren't specified automatically, ++ * @param with_coa If true and coa_home_server or coa_home_pool aren't specified automatically, + * create a coa home_server section and add it to the client CONF_SECTION. + * @return new RADCLIENT struct. + */ +@@ -845,8 +906,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"); +@@ -856,6 +928,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + hs_proto = NULL; + cl_srcipaddr = NULL; + #endif ++ require_message_authenticator = NULL; ++ limit_proxy_state = NULL; + + return NULL; + } +@@ -1054,11 +1128,11 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + cp = cf_pair_find(cs, "coa_server"); + if (cp) { + c->coa_name = cf_pair_value(cp); +- c->coa_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); +- if (!c->coa_pool) { +- c->coa_server = home_server_byname(c->coa_name, HOME_TYPE_COA); ++ c->coa_home_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); ++ if (!c->coa_home_pool) { ++ c->coa_home_server = home_server_byname(c->coa_name, HOME_TYPE_COA); + } +- if (!c->coa_pool && !c->coa_server) { ++ if (!c->coa_home_pool && !c->coa_home_server) { + cf_log_err_cs(cs, "No such home_server or home_server_pool \"%s\"", c->coa_name); + goto error; + } +@@ -1096,7 +1170,7 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + + rad_assert(home->type == HOME_TYPE_COA); + +- c->coa_server = home; ++ c->coa_home_server = home; + c->defines_coa_server = true; + } + } +@@ -1114,6 +1188,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 +1242,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; + } +@@ -1175,7 +1259,6 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + int i, *pi; + char **p; + RADCLIENT *c; +- CONF_PAIR *cp = NULL; + char buffer[128]; + + vp_cursor_t cursor; +@@ -1206,6 +1289,7 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + for (i = 0; dynamic_config[i].name != NULL; i++) { + DICT_ATTR const *da; + char *strvalue = NULL; ++ CONF_PAIR *cp = NULL; + + da = dict_attrbyname(dynamic_config[i].name); + if (!da) { +@@ -1344,10 +1428,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; + +@@ -1377,6 +1461,8 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + fr_cursor_first(&cursor); + vp = fr_cursor_remove(&cursor); + if (vp) { ++ CONF_PAIR *cp; ++ + do { + char *value; + +@@ -1436,6 +1522,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/command.c b/src/main/command.c +index 844898eed1..706260b150 100644 +--- a/src/main/command.c ++++ b/src/main/command.c +@@ -27,19 +27,14 @@ + #include + #include + #include ++#include ++#include + + #include + #ifdef HAVE_INTTYPES_H + #include + #endif + +-#ifdef HAVE_SYS_UN_H +-#include +-#ifndef SUN_LEN +-#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +-#endif +-#endif +- + #ifdef HAVE_SYS_STAT_H + #include + #endif +@@ -1009,6 +1004,11 @@ static char const *method_names[MOD_COUNT] = { + "pre-proxy", + "post-proxy", + "post-auth" ++#ifdef WITH_COA ++ , ++ "recv-coa", ++ "send-coa" ++#endif + }; + + +@@ -1138,7 +1138,7 @@ static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED + } + + #ifdef WITH_PROXY +-static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) ++static int command_show_home_servers(rad_listen_t *listener, int argc, char *argv[]) + { + int i; + home_server_t *home; +@@ -1146,9 +1146,10 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + + char buffer[256]; + +- for (i = 0; i < 256; i++) { +- home = home_server_bynumber(i); +- if (!home) break; ++ for (i = 0; i < home_server_max_number; i++) { ++ ++ if ((home = home_server_bynumber(i)) == NULL) ++ continue; + + /* + * Internal "virtual" home server. +@@ -1190,6 +1191,9 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + } else if (home->state == HOME_STATE_IS_DEAD) { + state = "dead"; + ++ } else if (home->state == HOME_STATE_ADMIN_DOWN) { ++ state = "down"; ++ + } else if (home->state == HOME_STATE_UNKNOWN) { + time_t now = time(NULL); + +@@ -1212,10 +1216,19 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + + } else continue; + +- cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", +- ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), +- home->port, proto, type, state, +- home->currently_outstanding); ++ if (argc > 0 && !strcmp(argv[0], "all")) { ++ char const *dynamic = home->dynamic ? "yes" : "no"; ++ ++ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\t(name=%s, dynamic=%s)\n", ++ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), ++ home->port, proto, type, state, ++ home->currently_outstanding, home->name, dynamic); ++ } else { ++ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", ++ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), ++ home->port, proto, type, state, ++ home->currently_outstanding); ++ } + } + + return CMD_OK; +@@ -1239,27 +1252,107 @@ static int command_show_client_config(rad_listen_t *listener, int argc, char *ar + return 1; + } + ++/* ++ * @todo - copied from clients.c. Better to re-use, but whatever. ++ */ ++struct radclient_list { ++ char const *name; /* name of this list */ ++ char const *server; /* virtual server associated with this client list */ ++ ++ /* ++ * FIXME: One set of trees for IPv4, and another for IPv6? ++ */ ++ rbtree_t *trees[129]; /* for 0..128, inclusive. */ ++ uint32_t min_prefix; ++}; ++ + +-static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) ++static int command_show_clients(rad_listen_t *listener, int argc, char *argv[]) + { + int i; + RADCLIENT *client; + char buffer[256]; + +- for (i = 0; i < 256; i++) { +- client = client_findbynumber(NULL, i); +- if (!client) break; ++ if (argc == 0) { ++ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { ++ ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); ++ ++ if (((client->ipaddr.af == AF_INET) && ++ (client->ipaddr.prefix != 32)) || ++ ((client->ipaddr.af == AF_INET6) && ++ (client->ipaddr.prefix != 128))) { ++ cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); ++ } else { ++ cprintf(listener, "%s\n", buffer); ++ } ++ } + +- ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); ++ return CMD_OK; ++ } + +- if (((client->ipaddr.af == AF_INET) && +- (client->ipaddr.prefix != 32)) || +- ((client->ipaddr.af == AF_INET6) && +- (client->ipaddr.prefix != 128))) { +- cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); ++ if (argc != 1) { ++ cprintf_error(listener, "Unknown command %s %s ...\n", argv[0], argv[1]); ++ return -1; ++ } ++ ++ if (strcmp(argv[0], "verbose") != 0) { ++ cprintf_error(listener, "Unknown command %s\n", argv[0]); ++ return -1; ++ } ++ ++ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { ++ if (client->cs) { ++ cprintf(listener, "client %s {\n", cf_section_name2(client->cs)); + } else { +- cprintf(listener, "%s\n", buffer); ++ cprintf(listener, "client {\n"); ++ } ++ ++ fr_ntop(buffer, sizeof(buffer), &client->ipaddr); ++ cprintf(listener, "\tipaddr = %s\n", buffer); ++ ++ if (client->src_ipaddr.af != AF_UNSPEC) { ++ fr_ntop(buffer, sizeof(buffer), &client->src_ipaddr); ++ cprintf(listener, "\tsrc_ipaddr = %s\n", buffer); ++ } ++ ++ if (client->proto == IPPROTO_UDP) { ++ cprintf(listener, "\tproto = udp\n"); ++ } else if (client->proto == IPPROTO_TCP) { ++ cprintf(listener, "\tproto = tcp\n"); ++ } else { ++ cprintf(listener, "\tproto = *\n"); ++ } ++ ++ cprintf(listener, "\tsecret = %s\n", client->secret); ++ cprintf(listener, "\tlongname = %s\n", client->longname); ++ cprintf(listener, "\tshortname = %s\n", client->shortname); ++ if (client->nas_type) cprintf(listener, "\tnas_type = %s\n", client->nas_type); ++ cprintf(listener, "\tnumber = %d\n", client->number); ++ ++ if (client->server) { ++ cprintf(listener, "\tvirtual_server = %s\n", client->server); ++ } ++ ++#ifdef WITH_DYNAMIC_CLIENTS ++ if (client->dynamic) { ++ cprintf(listener, "\tdynamic = yes\n"); ++ cprintf(listener, "\tlifetime = %u\n", client->lifetime); ++ } ++#endif ++ ++#ifdef WITH_TLS ++ if (client->tls_required) { ++ cprintf(listener, "\ttls = yes\n"); + } ++#endif ++ ++ if (client->list && client->list->server) { ++ cprintf(listener, "\tparent_virtual_server = %s\n", client->list->server); ++ } else { ++ cprintf(listener, "\tglobal = yes\n"); ++ } ++ ++ cprintf(listener, "}\n"); + } + + return CMD_OK; +@@ -1301,7 +1394,7 @@ static int command_debug_file(rad_listen_t *listener, int argc, char *argv[]) + return -1; + } + +- if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) { ++ if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) == argv[0])) { + cprintf_error(listener, "Cannot direct debug logs to absolute path.\n"); + } + +@@ -1646,8 +1739,14 @@ static int command_set_home_server_state(rad_listen_t *listener, int argc, char + } else if (strcmp(argv[last], "dead") == 0) { + struct timeval now; + +- gettimeofday(&now, NULL); /* we do this WAY too ofetn */ +- mark_home_server_dead(home, &now); ++ gettimeofday(&now, NULL); /* we do this WAY too often */ ++ mark_home_server_dead(home, &now, false); ++ ++ } else if (strcmp(argv[last], "down") == 0) { ++ struct timeval now; ++ ++ gettimeofday(&now, NULL); /* we do this WAY too often */ ++ mark_home_server_dead(home, &now, true); + + } else { + cprintf_error(listener, "Unknown state \"%s\"\n", argv[last]); +@@ -1677,6 +1776,10 @@ static int command_show_home_server_state(rad_listen_t *listener, int argc, char + cprintf(listener, "zombie\n"); + break; + ++ case HOME_STATE_ADMIN_DOWN: ++ cprintf(listener, "down\n"); ++ break; ++ + case HOME_STATE_UNKNOWN: + cprintf(listener, "unknown\n"); + break; +@@ -2047,7 +2150,7 @@ static fr_command_table_t command_table_show_client[] = { + "- show configuration for given client", + command_show_client_config, NULL }, + { "list", FR_READ, +- "show client list - shows list of global clients", ++ "show client list [verbose] - shows list of global clients", + command_show_clients, NULL }, + + { NULL, 0, NULL, NULL, NULL } +@@ -2056,7 +2159,7 @@ static fr_command_table_t command_table_show_client[] = { + #ifdef WITH_PROXY + static fr_command_table_t command_table_show_home[] = { + { "list", FR_READ, +- "show home_server list - shows list of home servers", ++ "show home_server list [all] - shows list of home servers", + command_show_home_servers, NULL }, + { "state", FR_READ, + "show home_server state [udp|tcp] [src ] - shows state of given home server", +@@ -2597,6 +2700,36 @@ static int command_del_client(rad_listen_t *listener, int argc, char *argv[]) + } + + ++static int command_del_home_server(rad_listen_t *listener, int argc, char *argv[]) ++{ ++ if (argc < 2) { ++ cprintf_error(listener, " and are required\n"); ++ return 0; ++ } ++ ++ if (home_server_delete(argv[0], argv[1]) < 0) { ++ cprintf_error(listener, "Failed deleted home_server %s - %s\n", argv[1], fr_strerror()); ++ return 0; ++ } ++ ++ return CMD_OK; ++} ++ ++static int command_add_home_server_file(rad_listen_t *listener, int argc, char *argv[]) ++{ ++ if (argc < 1) { ++ cprintf_error(listener, " is required\n"); ++ return 0; ++ } ++ ++ if (home_server_afrom_file(argv[0]) < 0) { ++ cprintf_error(listener, "Unable to add home server - %s\n", fr_strerror()); ++ return 0; ++ } ++ ++ return CMD_OK; ++} ++ + static fr_command_table_t command_table_del_client[] = { + { "ipaddr", FR_WRITE, + "del client ipaddr [udp|tcp] [listen ] - Delete a dynamically created client", +@@ -2605,16 +2738,26 @@ static fr_command_table_t command_table_del_client[] = { + { NULL, 0, NULL, NULL, NULL } + }; + ++static fr_command_table_t command_table_del_home_server[] = { ++ { "file", FR_WRITE, ++ "del home_server file [auth|acct|coa] - Delete a dynamically created home_server", ++ command_del_home_server, NULL }, ++ ++ { NULL, 0, NULL, NULL, NULL } ++}; + + static fr_command_table_t command_table_del[] = { + { "client", FR_WRITE, + "del client - Delete client configuration commands", + NULL, command_table_del_client }, + ++ { "home_server", FR_WRITE, ++ "del home_server - Delete home_server configuration commands", ++ NULL, command_table_del_home_server }, ++ + { NULL, 0, NULL, NULL, NULL } + }; + +- + static fr_command_table_t command_table_add_client[] = { + { "file", FR_WRITE, + "add client file - Add new client definition from ", +@@ -2623,12 +2766,23 @@ static fr_command_table_t command_table_add_client[] = { + { NULL, 0, NULL, NULL, NULL } + }; + ++static fr_command_table_t command_table_add_home_server[] = { ++ { "file", FR_WRITE, ++ "add home_server file - Add new home serverdefinition from ", ++ command_add_home_server_file, NULL }, ++ ++ { NULL, 0, NULL, NULL, NULL } ++}; + + static fr_command_table_t command_table_add[] = { + { "client", FR_WRITE, + "add client - Add client configuration commands", + NULL, command_table_add_client }, + ++ { "home_server", FR_WRITE, ++ "add home_server - Add home server configuration commands", ++ NULL, command_table_add_home_server }, ++ + { NULL, 0, NULL, NULL, NULL } + }; + #endif +@@ -2636,7 +2790,7 @@ static fr_command_table_t command_table_add[] = { + #ifdef WITH_PROXY + static fr_command_table_t command_table_set_home[] = { + { "state", FR_WRITE, +- "set home_server state [udp|tcp] [src ] [alive|dead] - set state for given home server", ++ "set home_server state [udp|tcp] [src ] [alive|dead|down] - set state for given home server", + command_set_home_server_state, NULL }, + + { NULL, 0, NULL, NULL, NULL } +diff --git a/src/main/conffile.c b/src/main/conffile.c +index a8c667bfb5..d458792cd5 100644 +--- a/src/main/conffile.c ++++ b/src/main/conffile.c +@@ -353,6 +353,7 @@ error: + + file->filename = filename; + file->cs = cs; ++ file->from_dir = from_dir; + + if (fstat(fd, &file->buf) == 0) { + #ifdef S_IWOTH +@@ -1174,10 +1175,14 @@ static char const *cf_expand_variables(char const *cf, int *lineno, + return NULL; + } + ++ /* ++ * Might as well make ++ * non-existent string be the ++ * empty string. ++ */ + if (!cp->value) { +- ERROR("%s[%d]: Reference \"%s\" has no value", +- cf, *lineno, input); +- return NULL; ++ *p = '\0'; ++ goto skip_value; + } + + if (p + strlen(cp->value) >= output + outsize) { +@@ -1188,6 +1193,7 @@ static char const *cf_expand_variables(char const *cf, int *lineno, + + strcpy(p, cp->value); + p += strlen(p); ++ skip_value: + ptr = end + 1; + + } else if (ci->type == CONF_ITEM_SECTION) { +@@ -1418,6 +1424,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 +1448,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 +1472,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; +@@ -1798,6 +1806,7 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, + CONF_PARSER const *variables) + { + int i; ++ void *data; + + for (i = 0; variables[i].name != NULL; i++) { + if (variables[i].type == PW_TYPE_SUBSECTION) { +@@ -1822,9 +1831,13 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, + subcs->item.lineno = cs->item.lineno; + cf_item_add(cs, &(subcs->item)); + } ++ if (base) { ++ data = ((uint8_t *)base) + variables[i].offset; ++ } else { ++ data = NULL; ++ } + +- cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset, +- (CONF_PARSER const *) variables[i].dflt); ++ cf_section_parse_init(subcs, data, (CONF_PARSER const *) variables[i].dflt); + continue; + } + +@@ -1922,8 +1935,13 @@ int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) + goto finish; + } + +- ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset, +- (CONF_PARSER const *) variables[i].dflt); ++ if (base) { ++ data = ((uint8_t *)base) + variables[i].offset; ++ } else { ++ data = NULL; ++ } ++ ++ ret = cf_section_parse(subcs, data, (CONF_PARSER const *) variables[i].dflt); + if (ret < 0) goto finish; + continue; + } /* else it's a CONF_PAIR */ +@@ -2313,7 +2331,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + if (has_spaces) { + ptr = cbuf; +- while (isspace((int) *ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (ptr > cbuf) { + memmove(cbuf, ptr, len - (ptr - cbuf)); +@@ -2329,7 +2347,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + if (at_eof) break; + + ptr = buf; +- while (*ptr && isspace((int) *ptr)) ptr++; ++ while (*ptr && isspace((uint8_t) *ptr)) ptr++; + + if (!*ptr || (*ptr == '#')) continue; + +@@ -2511,6 +2529,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + */ + while ((dp = readdir(dir)) != NULL) { + char const *p; ++ int slen; + + if (dp->d_name[0] == '.') continue; + +@@ -2518,8 +2537,8 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { +- if (isalpha((int)*p) || +- isdigit((int)*p) || ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || + (*p == '-') || + (*p == '_') || + (*p == '.')) continue; +@@ -2527,8 +2546,12 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + } + if (*p != '\0') continue; + +- snprintf(buf2, sizeof(buf2), "%s%s", +- value, dp->d_name); ++ slen = snprintf(buf2, sizeof(buf2), "%s%s", ++ value, dp->d_name); ++ if (slen >= (int) sizeof(buf2) || slen < 0) { ++ ERROR("%s: Full file path is too long.", dp->d_name); ++ return -1; ++ } + if ((stat(buf2, &stat_buf) != 0) || + S_ISDIR(stat_buf.st_mode)) continue; + +@@ -2794,7 +2817,15 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + case T_OP_EQ: + case T_OP_SET: +- while (isspace((int) *ptr)) ptr++; ++ case T_OP_PREPEND: ++ while (isspace((uint8_t) *ptr)) ptr++; ++ ++ /* ++ * Be a little more forgiving. ++ */ ++ if (*ptr == '#') { ++ t3 = T_HASH; ++ } else + + /* + * New parser: non-quoted strings are +@@ -2809,7 +2840,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + t3 = T_BARE_WORD; + while (*q && (*q >= ' ') && (*q != ',') && +- !isspace(*q)) q++; ++ !isspace((uint8_t) *q)) q++; + + if ((size_t) (q - ptr) >= sizeof(buf3)) { + ERROR("%s[%d]: Parse error: value too long", +@@ -2891,7 +2922,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + /* + * Require a comma, unless there's a comment. + */ +- while (isspace(*ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (*ptr == ',') { + ptr++; +@@ -2964,7 +2995,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + /* + * Done parsing one thing. Skip to EOL if possible. + */ +- while (isspace(*ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (*ptr == '#') continue; + +@@ -3561,6 +3592,10 @@ bool cf_item_is_pair(CONF_ITEM const *item) + return item->type == CONF_ITEM_PAIR; + } + ++bool cf_item_is_data(CONF_ITEM const *item) ++{ ++ return item->type == CONF_ITEM_DATA; ++} + + static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name, + void *data, void (*data_free)(void *)) +diff --git a/src/main/listen.c b/src/main/listen.c +index ebf7f5221c..6cda245ef0 100644 +--- a/src/main/listen.c ++++ b/src/main/listen.c +@@ -52,6 +52,17 @@ RCSID("$Id$") + #include + #endif + ++#ifdef WITH_TLS ++#include ++ ++# if defined(__APPLE__) || defined(__FreeBSD__) ++# if !defined(SOL_TCP) && defined(IPPROTO_TCP) ++# define SOL_TCP IPPROTO_TCP ++# endif ++# endif ++ ++#endif ++ + #ifdef DEBUG_PRINT_PACKET + static void print_packet(RADIUS_PACKET *packet) + { +@@ -217,7 +228,7 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, + client_delete(clients, client); + + /* +- * Add a timer to free the client in 120s ++ * Add a timer to free the client 20s after it's already timed out. + */ + el = radius_event_list_corral(EVENT_CORRAL_MAIN); + +@@ -349,7 +360,6 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, + + static int listen_bind(rad_listen_t *this); + +- + /* + * Process and reply to a server-status request. + * Like rad_authenticate and rad_accounting this should +@@ -360,6 +370,52 @@ int rad_status_server(REQUEST *request) + int rcode = RLM_MODULE_OK; + DICT_VALUE *dval; + ++#ifdef WITH_TLS ++ if (request->listener->tls) { ++ listen_socket_t *sock = request->listener->data; ++ ++ if (sock->state == LISTEN_TLS_CHECKING) { ++ int autz_type = PW_AUTZ_TYPE; ++ char const *name = "Autz-Type"; ++ ++ if (request->listener->type == RAD_LISTEN_ACCT) { ++ autz_type = PW_ACCT_TYPE; ++ name = "Acct-Type"; ++ } ++ ++ RDEBUG("(TLS) Checking connection to see if it is authorized."); ++ ++ dval = dict_valbyname(autz_type, 0, "New-TLS-Connection"); ++ if (dval) { ++ rcode = process_authorize(dval->value, request); ++ } else { ++ rcode = RLM_MODULE_OK; ++ RWDEBUG("(TLS) Did not find '%s New-TLS-Connection' - defaulting to accept", name); ++ } ++ ++ if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { ++ RDEBUG("(TLS) Connection is authorized"); ++ request->reply->code = PW_CODE_ACCESS_ACCEPT; ++ } else { ++ RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); ++ request->reply->code = PW_CODE_ACCESS_REJECT; ++ } ++ ++ return 0; ++ } ++ } ++#endif ++ ++#ifdef WITH_STATS ++ /* ++ * Full statistics are available only on a statistics ++ * socket. ++ */ ++ if (request->listener->type == RAD_LISTEN_NONE) { ++ request_stats_reply(request); ++ } ++#endif ++ + switch (request->listener->type) { + #ifdef WITH_STATS + case RAD_LISTEN_NONE: +@@ -443,17 +499,124 @@ int rad_status_server(REQUEST *request) + return 0; + } + +-#ifdef WITH_STATS ++ 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; ++ } ++ ++ } ++ + /* +- * Full statistics are available only on a statistics +- * socket. ++ * If all of the checks are turned off, then complain for every packet we receive. + */ +- if (request->listener->type == RAD_LISTEN_NONE) { +- request_stats_reply(request); ++ if (client->limit_proxy_state == FR_BOOL_FALSE) { ++ /* ++ * We have a Message-Authenticator, and it's valid. We don't need to compain. ++ */ ++ if (packet->message_authenticator) return; ++ ++ 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; + } +-#endif + +- return 0; ++ /* ++ * 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."); ++ ERROR("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."); ++ ERROR("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 +@@ -532,13 +695,37 @@ 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; + + #ifdef WITH_ACCOUNTING + case PW_CODE_ACCOUNTING_REQUEST: +- if (listener->type != RAD_LISTEN_ACCT) goto bad_packet; ++ if (listener->type != RAD_LISTEN_ACCT) { ++ /* ++ * Allow auth + dual. Disallow ++ * everything else. ++ */ ++ if (!((listener->type == RAD_LISTEN_AUTH) && ++ (listener->dual))) { ++ goto bad_packet; ++ } ++ } + FR_STATS_INC(acct, total_requests); + fun = rad_accounting; + break; +@@ -574,6 +761,79 @@ static int dual_tcp_recv(rad_listen_t *listener) + return 1; + } + ++#ifdef WITH_TLS ++typedef struct { ++ char const *name; ++ SSL_CTX *ctx; ++} fr_realm_ctx_t; /* hack from tls. */ ++ ++static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) ++{ ++ fr_tls_server_conf_t *conf = arg; ++ char const *name, *p; ++ int type; ++ fr_realm_ctx_t my_r, *r; ++ REQUEST *request; ++ char buffer[PATH_MAX]; ++ ++ /* ++ * No SNI, that's fine. ++ */ ++ type = SSL_get_servername_type(ssl); ++ if (type < 0) return SSL_TLSEXT_ERR_OK; ++ ++ /* ++ * No realms configured, just use the default context. ++ */ ++ if (!conf->realms) return SSL_TLSEXT_ERR_OK; ++ ++ name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); ++ if (!name) return SSL_TLSEXT_ERR_OK; ++ ++ /* ++ * RFC Section 6066 Section 3 says that the names are ++ * ASCII, without a trailing dot. i.e. punycode. ++ */ ++ for (p = name; *p != '\0'; p++) { ++ if (*p == '-') continue; ++ if (*p == '.') continue; ++ if ((*p >= 'A') && (*p <= 'Z')) continue; ++ if ((*p >= 'a') && (*p <= 'z')) continue; ++ if ((*p >= '0') && (*p <= '9')) continue; ++ ++ /* ++ * Anything else, fail. ++ */ ++ return SSL_TLSEXT_ERR_ALERT_FATAL; ++ } ++ ++ /* ++ * Too long, fail. ++ */ ++ if ((p - name) > 255) return SSL_TLSEXT_ERR_ALERT_FATAL; ++ ++ snprintf(buffer, sizeof(buffer), "%s/%s.pem", conf->realm_dir, name); ++ ++ my_r.name = buffer; ++ r = fr_hash_table_finddata(conf->realms, &my_r); ++ ++ /* ++ * If found, switch certs. Otherwise use the default ++ * one. ++ */ ++ if (r) (void) SSL_set_SSL_CTX(ssl, r->ctx); ++ ++ /* ++ * Set an attribute saying which server has been selected. ++ */ ++ request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ if (request) { ++ (void) pair_make_config("TLS-Server-Name-Indication", name, T_OP_SET); ++ } ++ ++ return SSL_TLSEXT_ERR_OK; ++} ++#endif + + static int dual_tcp_accept(rad_listen_t *listener) + { +@@ -731,6 +991,14 @@ static int dual_tcp_accept(rad_listen_t *listener) + if (this->tls) { + this->recv = dual_tls_recv; + this->send = dual_tls_send; ++ ++ /* ++ * Set up SNI callback. We don't do it ++ * in the main TLS code, because EAP ++ * doesn't need or use SNI. ++ */ ++ SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); ++ SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); + } + #endif + } +@@ -932,7 +1200,6 @@ static CONF_PARSER limit_config[] = { + CONF_PARSER_TERMINATOR + }; + +- + #ifdef WITH_TCP + /* + * TLS requires child threads to handle the listeners. Which +@@ -1018,6 +1285,7 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + + } else if (strcmp(proto, "tcp") == 0) { + sock->proto = IPPROTO_TCP; ++ + } else { + cf_log_err_cs(cs, + "Unknown proto name \"%s\"", proto); +@@ -1050,6 +1318,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + return -1; + } + ++ /* ++ * Allow non-blocking for TLS sockets ++ */ ++ rcode = cf_item_parse(cs, "nonblock", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->nonblock), NULL); ++ if (rcode < 0) return -1; ++ + /* + * If unset, set to default. + */ +@@ -1068,6 +1342,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + } + #endif + ++ rcode = cf_item_parse(cs, "check_client_connections", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->check_client_connections), "no"); ++ if (rcode < 0) return -1; + } + #else /* WITH_TLS */ + /* +@@ -1312,6 +1588,11 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + */ + this->recv = dual_tcp_accept; + ++ /* ++ * @todo - add a free function? Though this only ++ * matters when we're tearing down the server, so ++ * perhaps it's less relevant. ++ */ + this->children = rbtree_create(this, listener_cmp, NULL, 0); + if (!this->children) { + cf_log_err_cs(cs, "Failed to create child list for TCP socket."); +@@ -1324,12 +1605,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + } + + /* +- * Send an authentication response packet ++ * Send a response packet + */ +-static int auth_socket_send(rad_listen_t *listener, REQUEST *request) ++static int common_socket_send(rad_listen_t *listener, REQUEST *request) + { + rad_assert(request->listener == listener); +- rad_assert(listener->send == auth_socket_send); ++ rad_assert(listener->send == common_socket_send); + + if (request->reply->code == 0) return 0; + +@@ -1355,46 +1636,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) + } + + +-#ifdef WITH_ACCOUNTING +-/* +- * Send an accounting response packet (or not) +- */ +-static int acct_socket_send(rad_listen_t *listener, REQUEST *request) +-{ +- rad_assert(request->listener == listener); +- rad_assert(listener->send == acct_socket_send); +- +- /* +- * Accounting reject's are silently dropped. +- * +- * We do it here to avoid polluting the rest of the +- * code with this knowledge +- */ +- if (request->reply->code == 0) return 0; +- +-#ifdef WITH_UDPFROMTO +- /* +- * Overwrite the src ip address on the outbound packet +- * with the one specified by the client. +- * This is useful to work around broken DSR implementations +- * and other routing issues. +- */ +- if (request->client->src_ipaddr.af != AF_UNSPEC) { +- request->reply->src_ipaddr = request->client->src_ipaddr; +- } +-#endif +- +- if (rad_send(request->reply, request->packet, +- request->client->secret) < 0) { +- RERROR("Failed sending reply: %s", +- fr_strerror()); +- return -1; +- } +- +- return 0; +-} +-#endif +- + #ifdef WITH_PROXY + /* + * Send a packet to a home server. +@@ -1404,7 +1645,7 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) + static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) + { + rad_assert(request->proxy_listener == listener); +- rad_assert(listener->send == proxy_socket_send); ++ rad_assert(listener->proxy_send == proxy_socket_send); + + if (rad_send(request->proxy, NULL, + request->home_server->secret) < 0) { +@@ -1436,8 +1677,6 @@ static int stats_socket_recv(rad_listen_t *listener) + rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); + if (rcode < 0) return 0; + +- FR_STATS_INC(auth, total_requests); +- + if (rcode < 20) { /* RADIUS_HDR_LEN */ + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); + FR_STATS_INC(auth, total_malformed_requests); +@@ -1485,7 +1724,6 @@ static int stats_socket_recv(rad_listen_t *listener) + } + #endif + +- + /* + * Check if an incoming request is "ok" + * +@@ -1521,13 +1759,12 @@ static int auth_socket_recv(rad_listen_t *listener) + return 0; + } + +- FR_STATS_TYPE_INC(client->auth.total_requests); +- + /* + * Some sanity checks, based on the packet code. + */ + switch (code) { + case PW_CODE_ACCESS_REQUEST: ++ FR_STATS_TYPE_INC(client->auth.total_requests); + fun = rad_authenticate; + break; + +@@ -1562,7 +1799,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 +1807,11 @@ 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 + /* +@@ -1637,13 +1879,12 @@ static int acct_socket_recv(rad_listen_t *listener) + return 0; + } + +- FR_STATS_TYPE_INC(client->acct.total_requests); +- + /* + * Some sanity checks, based on the packet code. + */ + switch (code) { + case PW_CODE_ACCOUNTING_REQUEST: ++ FR_STATS_TYPE_INC(client->acct.total_requests); + fun = rad_accounting; + break; + +@@ -1729,7 +1970,6 @@ static int do_proxy(REQUEST *request) + */ + vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY); + if (!vp) vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IPV6_ADDRESS, 0, TAG_ANY); +- + if (!vp) return 0; + + return 1; +@@ -1955,7 +2195,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); + if (!packet) { + FR_STATS_INC(coa, total_malformed_requests); + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); +@@ -2049,19 +2289,67 @@ static int proxy_socket_recv(rad_listen_t *listener) + */ + static int proxy_socket_tcp_recv(rad_listen_t *listener) + { ++ int rcode; + RADIUS_PACKET *packet; + listen_socket_t *sock = listener->data; +- char buffer[128]; ++ char buffer[256]; + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + +- packet = fr_tcp_recv(listener->fd, 0); +- if (!packet) { ++ if (!sock->packet) { ++ sock->packet = rad_alloc(sock, false); ++ if (!sock->packet) return 0; ++ ++ sock->packet->sockfd = listener->fd; ++ sock->packet->src_ipaddr = sock->other_ipaddr; ++ sock->packet->src_port = sock->other_port; ++ sock->packet->dst_ipaddr = sock->my_ipaddr; ++ sock->packet->dst_port = sock->my_port; ++ sock->packet->proto = sock->proto; ++ } ++ ++ packet = sock->packet; ++ ++ rcode = fr_tcp_read_packet(packet, 0); ++ ++ /* ++ * Still only a partial packet. Put it back, and return, ++ * so that we'll read more data when it's ready. ++ */ ++ if (rcode == 0) { ++ return 0; ++ } ++ ++ if (rcode == -1) { /* error reading packet */ ++ ERROR("Invalid packet from %s port %d, closing socket: %s", ++ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), ++ packet->src_port, fr_strerror()); ++ } ++ ++ if (rcode < 0) { /* error or connection reset */ + listener->status = RAD_LISTEN_STATUS_EOL; ++ ++ /* ++ * Tell the event handler that an FD has disappeared. ++ */ ++ DEBUG("Home server %s port %d has closed connection", ++ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), ++ packet->src_port); ++ + radius_update_listener(listener); ++ ++ /* ++ * Do NOT free the listener here. It's in use by ++ * a request, and will need to hang around until ++ * all of the requests are done. ++ * ++ * It is instead free'd in remove_from_request_hash() ++ */ + return 0; + } + ++ sock->packet = NULL; /* we have no need for more partial reads */ ++ + /* + * FIXME: Client MIB updates? + */ +@@ -2089,10 +2377,6 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) + return 0; + } + +- packet->src_ipaddr = sock->other_ipaddr; +- packet->src_port = sock->other_port; +- packet->dst_ipaddr = sock->my_ipaddr; +- packet->dst_port = sock->my_port; + + /* + * FIXME: Have it return an indication of packets that +@@ -2113,11 +2397,26 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) + #endif + #endif + ++#ifdef WITH_TLS ++#define TLS_UNUSED ++#else ++#define TLS_UNUSED UNUSED ++#endif + +-static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) ++static int client_socket_encode(TLS_UNUSED rad_listen_t *listener, REQUEST *request) + { ++#ifdef WITH_TLS ++ /* ++ * Don't encode fake packets. ++ */ ++ listen_socket_t *sock = listener->data; ++ if (sock->state == LISTEN_TLS_CHECKING) return 0; ++#endif ++ + if (!request->reply->code) return 0; + ++ if (request->reply->data) return 0; /* already encoded */ ++ + if (rad_encode(request->reply, request->packet, request->client->secret) < 0) { + RERROR("Failed encoding packet: %s", fr_strerror()); + +@@ -2142,7 +2441,7 @@ static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) + static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) + { + #ifdef WITH_TLS +- listen_socket_t *sock; ++ listen_socket_t *sock = request->listener->data; + #endif + + if (rad_verify(request->packet, NULL, +@@ -2151,9 +2450,6 @@ static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) + } + + #ifdef WITH_TLS +- sock = request->listener->data; +- rad_assert(sock != NULL); +- + /* + * FIXME: Add the rest of the TLS parameters, too? But + * how do we separate EAP-TLS parameters from RADIUS/TLS +@@ -2218,7 +2514,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + #ifdef WITH_STATS + { RLM_MODULE_INIT, "status", sizeof(listen_socket_t), NULL, + common_socket_parse, NULL, +- stats_socket_recv, auth_socket_send, ++ stats_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + #else + /* +@@ -2241,14 +2537,14 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + /* authentication */ + { RLM_MODULE_INIT, "auth", sizeof(listen_socket_t), NULL, + common_socket_parse, common_socket_free, +- auth_socket_recv, auth_socket_send, ++ auth_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + + #ifdef WITH_ACCOUNTING + /* accounting */ + { RLM_MODULE_INIT, "acct", sizeof(listen_socket_t), NULL, + common_socket_parse, common_socket_free, +- acct_socket_recv, acct_socket_send, ++ acct_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode}, + #else + { 0, "acct", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +@@ -2282,7 +2578,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + /* Change of Authorization */ + { RLM_MODULE_INIT, "coa", sizeof(listen_socket_t), NULL, + common_socket_parse, NULL, +- coa_socket_recv, auth_socket_send, /* CoA packets are same as auth */ ++ coa_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + #else + { 0, "coa", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +@@ -2755,6 +3051,9 @@ static int _listener_free(rad_listen_t *this) + + rad_assert(!sock->ssn || (talloc_parent(sock->ssn) == sock)); + rad_assert(!sock->request || (talloc_parent(sock->request) == sock)); ++ ++ if (sock->home && sock->home->listeners) (void) rbtree_deletebydata(sock->home->listeners, this); ++ + #ifdef HAVE_PTHREAD_H + pthread_mutex_destroy(&(sock->mutex)); + #endif +@@ -2780,8 +3079,16 @@ static rad_listen_t *listen_alloc(TALLOC_CTX *ctx, RAD_LISTEN_TYPE type) + this->recv = master_listen[this->type].recv; + this->send = master_listen[this->type].send; + this->print = master_listen[this->type].print; +- this->encode = master_listen[this->type].encode; +- this->decode = master_listen[this->type].decode; ++ ++ if (type != RAD_LISTEN_PROXY) { ++ this->encode = master_listen[this->type].encode; ++ this->decode = master_listen[this->type].decode; ++ } else { ++ this->send = NULL; /* proxy packets shouldn't call this! */ ++ this->proxy_send = master_listen[this->type].send; ++ this->proxy_encode = master_listen[this->type].encode; ++ this->proxy_decode = master_listen[this->type].decode; ++ } + + talloc_set_destructor(this, _listener_free); + +@@ -2806,7 +3113,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + + if (!home) return NULL; + +- rad_assert(home->server == NULL); /* we only open real sockets */ ++ rad_assert(home->virtual_server == NULL); /* we only open real sockets */ + + if ((home->limit.max_connections > 0) && + (home->limit.num_connections >= home->limit.max_connections)) { +@@ -2847,11 +3154,20 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + * FIXME: connect() is blocking! + * We do this with the proxy mutex locked, which may + * cause large delays! +- * +- * http://www.developerweb.net/forum/showthread.php?p=13486 + */ + this->fd = fr_socket_client_tcp(&home->src_ipaddr, +- &home->ipaddr, home->port, false); ++ &home->ipaddr, home->port, ++#ifdef WITH_TLS ++ !this->nonblock ++#else ++ false ++#endif ++ ); ++ ++ /* ++ * Set max_requests, lifetime, and idle_timeout from the home server. ++ */ ++ sock->limit = home->limit; + } else + #endif + this->fd = fr_socket(&home->src_ipaddr, src_port); +@@ -2869,17 +3185,74 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + #ifdef WITH_TCP + #ifdef WITH_TLS + if ((home->proto == IPPROTO_TCP) && home->tls) { +- DEBUG("Trying SSL to port %d\n", home->port); ++ DEBUG("(TLS) Trying new outgoing proxy connection to %s", buffer); ++ ++ /* ++ * Set SNI, if configured. ++ * ++ * The OpenSSL API says the filename is "char ++ * const *", but some versions have it as "void ++ * *", without the "const". So we un-const it ++ * here through various C magic. ++ */ ++ if (home->tls->client_hostname) { ++ (void) SSL_set_tlsext_host_name(sock->ssn->ssl, (void *) (uintptr_t) home->tls->client_hostname); ++ } ++ ++ this->nonblock |= home->nonblock; ++ ++ /* ++ * Set non-blocking if it's configured. ++ */ ++ if (this->nonblock) { ++ if (fr_nonblock(this->fd) < 0) { ++ ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); ++ goto error; ++ } ++ ++ rad_assert(home->listeners != NULL); ++ ++ if (!rbtree_insert(home->listeners, this)) { ++ ERROR("(TLS) Failed adding tracking informtion for proxy socket '%s'", buffer); ++ goto error; ++ } ++ ++#ifdef TCP_NODELAY ++ /* ++ * Also set TCP_NODELAY, to force the data to be written quickly. ++ */ ++ if (sock->proto == IPPROTO_TCP) { ++ int on = 1; ++ ++ if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { ++ ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); ++ goto error; ++ } ++ } ++#endif ++ } ++ ++ /* ++ * This is blocking. :( ++ */ + sock->ssn = tls_new_client_session(sock, home->tls, this->fd, &sock->certs); + if (!sock->ssn) { +- ERROR("Failed starting SSL to new proxy socket '%s'", buffer); +- home->last_failed_open = now; +- listen_free(&this); +- return NULL; ++ ERROR("(TLS) Failed opening connection on proxy socket '%s'", buffer); ++ goto error; + } + ++ sock->connect_timeout = home->connect_timeout; ++ + this->recv = proxy_tls_recv; +- this->send = proxy_tls_send; ++ this->proxy_send = proxy_tls_send; ++ ++#ifdef HAVE_PTHREAD_H ++ if (pthread_mutex_init(&sock->mutex, NULL) < 0) { ++ rad_assert(0 == 1); ++ listen_free(&this); ++ return 0; ++ } ++#endif + } + #endif + #endif +@@ -2895,6 +3268,8 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + &sizeof_src) < 0) { + ERROR("Failed getting socket name for '%s': %s", + buffer, fr_syserror(errno)); ++ error: ++ close(this->fd); + home->last_failed_open = now; + listen_free(&this); + return NULL; +@@ -2903,9 +3278,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + if (!fr_sockaddr2ipaddr(&src, sizeof_src, + &sock->my_ipaddr, &sock->my_port)) { + ERROR("Socket has unsupported address family for '%s'", buffer); +- home->last_failed_open = now; +- listen_free(&this); +- return NULL; ++ goto error; + } + + this->print(this, buffer, sizeof(buffer)); +@@ -2966,6 +3339,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) + char const *value; + fr_dlhandle handle; + CONF_SECTION *server_cs; ++#ifdef WITH_TCP ++ char const *p; ++#endif + char buffer[32]; + + cp = cf_pair_find(cs, "type"); +@@ -3076,10 +3452,13 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) + + #ifdef WITH_TCP + /* +- * Special-case '+' for "auth+acct". ++ * Add special flags '+' for "auth+acct". + */ +- if (strchr(listen_type, '+') != NULL) { +- this->dual = true; ++ p = strchr(listen_type, '+'); ++ if (p) { ++ if (strncmp(p + 1, "acct", 4) == 0) { ++ this->dual = true; ++ } + } + #endif + +@@ -3115,7 +3494,6 @@ static void *recv_thread(void *arg) + + while (1) { + this->recv(this); +- DEBUG("%p", &this); + } + + return NULL; +@@ -3207,7 +3585,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) + if (override) { + cs = cf_section_sub_find_name2(config, "server", + main_config.name); +- if (cs) this->server = main_config.name; ++ if (!cs) cs = cf_section_sub_find_name2(config, "server", ++ "default"); ++ if (cs) this->server = cf_section_name2(cs); + } + + *last = this; +diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c +index e9dd412dee..957bff933f 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,52 @@ 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 +@@ -148,6 +196,7 @@ static const CONF_PARSER log_config[] = { + { "colourise",FR_CONF_POINTER(PW_TYPE_BOOLEAN, &do_colourise), NULL }, + { "use_utc", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &log_dates_utc), NULL }, + { "msg_denied", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.denied_msg), "You are already logged in - access denied" }, ++ { "suppress_secrets", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.suppress_secrets), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -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 +@@ -196,6 +247,7 @@ static const CONF_PARSER server_config[] = { + { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, + { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, + { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, ++ { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, + { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, + { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, + +@@ -499,14 +551,31 @@ static ssize_t xlat_listen_common(REQUEST *request, rad_listen_t *listen, + VALUE_PAIR *vp; + listen_socket_t *sock = listen->data; + ++ if (!listen->tls) { ++ RDEBUG("Listener is not using TLS. TLS attributes are not available"); ++ *out = '\0'; ++ return 0; ++ } ++ + for (vp = sock->certs; vp != NULL; vp = vp->next) { + if (strcmp(fmt, vp->da->name) == 0) { + return vp_prints_value(out, outlen, vp, 0); + } + } ++ ++ RDEBUG("Unknown TLS attribute \"%s\"", fmt); ++ *out = '\0'; ++ return 0; ++ } ++#else ++ if (strncmp(fmt, "TLS-", 4) == 0) { ++ RDEBUG("Server is not built with TLS support"); ++ *out = '\0'; ++ return 0; + } + #endif + ++ + cp = cf_pair_find(listen->cs, fmt); + if (!cp || !(value = cf_pair_value(cp))) { + RDEBUG("Listener does not contain config item \"%s\"", fmt); +@@ -560,7 +629,6 @@ static int switch_users(CONF_SECTION *cs) + * initialized. + */ + if (fr_set_dumpable_init() < 0) { +- fr_perror("%s", main_config.name); + return 0; + } + +@@ -571,8 +639,7 @@ static int switch_users(CONF_SECTION *cs) + if (rad_debug_lvl && (getuid() != 0)) return 1; + + if (cf_section_parse(cs, NULL, bootstrap_config) < 0) { +- fr_strerror_printf("%s: Error: Failed to parse user/group information.\n", +- main_config.name); ++ fr_strerror_printf("Failed to parse user/group information."); + return 0; + } + +@@ -587,8 +654,8 @@ static int switch_users(CONF_SECTION *cs) + + gr = getgrnam(gid_name); + if (!gr) { +- fr_strerror_printf("%s: Cannot get ID for group %s: %s\n", +- main_config.name, gid_name, fr_syserror(errno)); ++ fr_strerror_printf("Cannot get ID for group %s: %s", ++ gid_name, fr_syserror(errno)); + return 0; + } + +@@ -608,8 +675,8 @@ static int switch_users(CONF_SECTION *cs) + struct passwd *user; + + if (rad_getpwnam(cs, &user, uid_name) < 0) { +- fr_strerror_printf("%s: Cannot get passwd entry for user %s: %s\n", +- main_config.name, uid_name, fr_strerror()); ++ fr_strerror_printf("Cannot get passwd entry for user %s: %s", ++ uid_name, fr_strerror()); + return 0; + } + +@@ -621,8 +688,8 @@ static int switch_users(CONF_SECTION *cs) + do_suid = true; + #ifdef HAVE_INITGROUPS + if (initgroups(uid_name, server_gid) < 0) { +- fr_strerror_printf("%s: Cannot initialize supplementary group list for user %s: %s\n", +- main_config.name, uid_name, fr_syserror(errno)); ++ fr_strerror_printf("Cannot initialize supplementary group list for user %s: %s", ++ uid_name, fr_syserror(errno)); + talloc_free(user); + return 0; + } +@@ -637,8 +704,8 @@ static int switch_users(CONF_SECTION *cs) + */ + if (chroot_dir) { + if (chroot(chroot_dir) < 0) { +- fr_strerror_printf("%s: Failed to perform chroot %s: %s", +- main_config.name, chroot_dir, fr_syserror(errno)); ++ fr_strerror_printf("Failed to perform chroot to %s: %s", ++ chroot_dir, fr_syserror(errno)); + return 0; + } + +@@ -665,8 +732,8 @@ static int switch_users(CONF_SECTION *cs) + */ + if (do_sgid) { + if (setgid(server_gid) < 0){ +- fr_strerror_printf("%s: Failed setting group to %s: %s", +- main_config.name, gid_name, fr_syserror(errno)); ++ fr_strerror_printf("Failed setting group to %s: %s", ++ gid_name, fr_syserror(errno)); + return 0; + } + } +@@ -713,8 +780,8 @@ static int switch_users(CONF_SECTION *cs) + default_log.fd = open(main_config.log_file, + O_WRONLY | O_APPEND | O_CREAT, 0640); + if (default_log.fd < 0) { +- fr_strerror_printf("%s: Failed to open log file %s: %s\n", +- main_config.name, main_config.log_file, fr_syserror(errno)); ++ fr_strerror_printf("Failed to open log file %s: %s\n", ++ main_config.log_file, fr_syserror(errno)); + return 0; + } + } +@@ -730,8 +797,8 @@ static int switch_users(CONF_SECTION *cs) + if ((do_suid || do_sgid) && + (default_log.dst == L_DST_FILES)) { + if (fchown(default_log.fd, server_uid, server_gid) < 0) { +- fr_strerror_printf("%s: Cannot change ownership of log file %s: %s\n", +- main_config.name, main_config.log_file, fr_syserror(errno)); ++ fr_strerror_printf("Cannot change ownership of log file %s: %s\n", ++ main_config.log_file, fr_syserror(errno)); + return 0; + } + } +@@ -750,7 +817,7 @@ static int switch_users(CONF_SECTION *cs) + * aren't allowed. + */ + if (fr_set_dumpable(allow_core_dumps) < 0) { +- ERROR("%s", fr_strerror()); ++ WARN("Failed to allow core dumps - %s", fr_strerror()); + } + + if (allow_core_dumps) { +@@ -838,6 +905,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 +@@ -941,6 +1010,9 @@ do {\ + for (ci = cf_item_find_next(subcs, NULL); + ci != NULL; + ci = cf_item_find_next(subcs, ci)) { ++ ++ if (cf_item_is_data(ci)) continue; ++ + if (!cf_item_is_pair(ci)) { + cf_log_err(ci, "Unexpected item in ENV section"); + goto failure; +@@ -1066,7 +1138,10 @@ do {\ + /* + * Switch users as early as possible. + */ +- if (!switch_users(cs)) fr_exit(1); ++ if (!switch_users(cs)) { ++ fprintf(stderr, "%s: ERROR - %s\n", main_config.name, fr_strerror()); ++ fr_exit(1); ++ } + #endif + + /* +@@ -1127,6 +1202,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/map.c b/src/main/map.c index 6275ba124d..17988d27f9 100644 --- a/src/main/map.c @@ -2347,8 +6373,1326 @@ index 6275ba124d..17988d27f9 100644 /* * += - Add all src_list attributes to the destination */ +diff --git a/src/main/process.c b/src/main/process.c +index 1a48517d43..2d20ebf552 100644 +--- a/src/main/process.c ++++ b/src/main/process.c +@@ -74,8 +74,14 @@ static char const *action_codes[] = { + "dup", + "timer", + #ifdef WITH_PROXY +- "proxy-reply" +-#endif ++ "proxy-reply", ++#endif ++ "request was cancelled", ++ "conflicting packet was received", ++ "max_time was reached", ++ "internal failure", ++ "cleanup_delay was reached", ++ "CoA packet was cancelled, and not sent", + }; + + #ifdef DEBUG_STATE_MACHINE +@@ -604,6 +610,46 @@ static void request_free(REQUEST *request) + + + #ifdef WITH_PROXY ++#ifdef WITH_TLS ++void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler) ++{ ++ PTHREAD_MUTEX_LOCK(&proxy_mutex); ++ if (!fr_packet_list_socket_freeze(proxy_list, ++ listener->fd)) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ listener->blocked = true; ++ ++ if (fr_event_fd_write_handler(el, 0, listener->fd, write_handler, listener) < 0) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++} ++ ++void proxy_listener_thaw(rad_listen_t *listener) ++{ ++ PTHREAD_MUTEX_LOCK(&proxy_mutex); ++ if (!fr_packet_list_socket_thaw(proxy_list, ++ listener->fd)) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ listener->blocked = false; ++ ++ if (fr_event_fd_write_handler(el, 0, listener->fd, NULL, listener) < 0) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++} ++#endif /* WITH_TLS */ ++ + static void proxy_reply_too_late(REQUEST *request) + { + char buffer[128]; +@@ -632,9 +678,10 @@ static void proxy_reply_too_late(REQUEST *request) + * } + * \enddot + */ +-static void request_done(REQUEST *request, int action) ++static void request_done(REQUEST *request, int original) + { + struct timeval now, when; ++ int action = original; + + VERIFY_REQUEST(request); + +@@ -690,7 +737,7 @@ static void request_done(REQUEST *request, int action) + home_server_t *home = request->home_pool->servers[i]; + + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + } + } +@@ -700,7 +747,7 @@ static void request_done(REQUEST *request, int action) + /* + * If it was administratively canceled, then it's done. + */ +- if (action == FR_ACTION_CANCELLED) { ++ if (action >= FR_ACTION_CANCELLED) { + action = FR_ACTION_DONE; + + #ifdef WITH_COA +@@ -887,9 +934,10 @@ static void request_done(REQUEST *request, int action) + #endif + + if (request->packet) { +- RDEBUG2("Cleaning up request packet ID %u with timestamp +%d", ++ RDEBUG2("Cleaning up request packet ID %u with timestamp +%d due to %s", + request->packet->id, +- (unsigned int) (request->timestamp - fr_start_time)); ++ (unsigned int) (request->timestamp - fr_start_time), ++ action_codes[original]); + } /* else don't print anything */ + + ASSERT_MASTER; +@@ -958,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) + #ifdef HAVE_PTHREAD_H + rad_assert(request->child_pid == NO_SUCH_CHILD_PID); + #endif ++ ++ /* ++ * Set the statistics immediately if we can. ++ */ ++ request_stats_final(request); ++ + STATE_MACHINE_TIMER(FR_ACTION_TIMER); + return; + } +@@ -994,8 +1048,7 @@ static bool request_max_time(REQUEST *request) + * stop" macro already took care of it. + */ + if (request->child_state == REQUEST_DONE) { +- done: +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_DONE); + return true; + } + +@@ -1027,7 +1080,8 @@ static bool request_max_time(REQUEST *request) + /* + * Tell the request that it's done. + */ +- goto done; ++ request_done(request, FR_ACTION_MAX_TIME); ++ return true; + } + + /* +@@ -1096,7 +1150,7 @@ static void request_queue_or_run(REQUEST *request, + * Otherwise we're not going to do anything with + * it... + */ +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_INTERNAL_FAILURE); + return; + } + #endif +@@ -1115,6 +1169,11 @@ static void request_queue_or_run(REQUEST *request, + #endif + } + ++void request_inject(REQUEST *request) ++{ ++ request_queue_or_run(request, request_running); ++} ++ + + static void request_dup(REQUEST *request) + { +@@ -1203,11 +1262,14 @@ static void request_cleanup_delay(REQUEST *request, int action) + #ifdef DEBUG_STATE_MACHINE + if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); + #endif ++ ++ request_stats_final(request); ++ + STATE_MACHINE_TIMER(FR_ACTION_TIMER); + return; + } /* else it's time to clean up */ + +- request_done(request, FR_ACTION_DONE); ++ request_done(request, FR_ACTION_CLEANUP_DELAY); + break; + + default: +@@ -1282,6 +1344,7 @@ static void request_response_delay(REQUEST *request, int action) + } /* else it's time to send the reject */ + + RDEBUG2("Sending delayed response"); ++ request->listener->encode(request->listener, request); + debug_packet(request, request->reply, false); + request->listener->send(request->listener, request); + +@@ -1327,7 +1390,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) + /* + * Ignore parse errors. + */ +- if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition)) { ++ if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition) == 1) { + request->log.lvl = L_DBG_LVL_2; + request->log.func = vradlog_request; + } +@@ -1340,7 +1403,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) + } + + if (rcode < 0) { +- RATE_LIMIT(INFO("Dropping packet without response because of error: %s", fr_strerror())); ++ RATE_LIMIT(INFO("Dropping packet without response because of error: %s (from client %s)", fr_strerror(), request->client->shortname)); + request->reply->offset = -2; /* bad authenticator */ + return 0; + } +@@ -1506,7 +1569,8 @@ static void request_finish(REQUEST *request, int action) + /* + * See if we need to delay an Access-Reject packet. + */ +- if ((request->reply->code == PW_CODE_ACCESS_REJECT) && ++ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && ++ (request->reply->code == PW_CODE_ACCESS_REJECT) && + (request->root->reject_delay.tv_sec > 0)) { + request->response_delay = request->root->reject_delay; + +@@ -1534,9 +1598,14 @@ static void request_finish(REQUEST *request, int action) + #ifdef WITH_PROXY + /* + * If we timed out a proxy packet, don't delay +- * the reject any more. ++ * the reject any more. Or, if we proxied it to ++ * a real home server, then don't delay it. ++ * ++ * We don't want to have each proxy in a chain ++ * adding their own reject delay, which would ++ * result in N*reject_delays being applied. + */ +- if (request->proxy && !request->proxy_reply) { ++ if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { + request->response_delay.tv_sec = 0; + request->response_delay.tv_usec = 0; + } +@@ -1560,6 +1629,7 @@ static void request_finish(REQUEST *request, int action) + } + } + ++ request->listener->encode(request->listener, request); + debug_packet(request, request->reply, false); + request->listener->send(request->listener, request); + } +@@ -1598,6 +1668,8 @@ static void request_finish(REQUEST *request, int action) + */ + static void request_running(REQUEST *request, int action) + { ++ int rcode; ++ + VERIFY_REQUEST(request); + + TRACE_STATE_MACHINE; +@@ -1631,7 +1703,8 @@ static void request_running(REQUEST *request, int action) + /* + * We may need to send a proxied request. + */ +- if (request_will_proxy(request)) { ++ rcode = request_will_proxy(request); ++ if (rcode == 1) { + #ifdef DEBUG_STATE_MACHINE + if (rad_debug_lvl) printf("(%u) ********\tWill Proxy\t********\n", request->number); + #endif +@@ -1641,13 +1714,42 @@ static void request_running(REQUEST *request, int action) + * up the post proxy fail + * handler. + */ ++ retry_proxy: + if (request_proxy(request) < 0) { +- if (request->home_server && request->home_server->server) goto req_finished; ++ if (request->home_server && request->home_server->virtual_server) goto req_finished; ++ ++ if (request->home_pool && request->home_server && ++ HOME_SERVER_IS_DEAD(request->home_server)) { ++ VALUE_PAIR *vp; ++ REALM *realm = NULL; ++ home_server_t *home = NULL; ++ ++ vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY); ++ if (vp) realm = realm_find2(vp->vp_strvalue); ++ ++ /* ++ * Since request->home_server is dead, ++ * this function won't pick the same home server as before. ++ */ ++ if (realm) home = home_server_ldb(vp->vp_strvalue, request->home_pool, request); ++ if (home) { ++ home_server_update_request(home, request); ++ goto retry_proxy; ++ } ++ } + + (void) setup_post_proxy_fail(request); + process_proxy_reply(request, NULL); + goto req_finished; + } ++ ++ } else if (rcode < 0) { ++ /* ++ * No live home servers, run Post-Proxy-Type Fail. ++ */ ++ (void) setup_post_proxy_fail(request); ++ process_proxy_reply(request, NULL); ++ goto req_finished; + } else + #endif + { +@@ -1762,7 +1864,7 @@ int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *pack + * the request just as we're logging the + * complaint. + */ +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_CONFLICT); + request = NULL; + + /* +@@ -1851,7 +1953,7 @@ skip_dup: + if (!listener->nodup) { + if (!rbtree_insert(pl, &request->packet)) { + RERROR("Failed to insert request in the list of live requests: discarding it"); +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_INTERNAL_FAILURE); + return 1; + } + +@@ -2016,23 +2118,7 @@ static void tcp_socket_timer(void *ctx) + + fr_event_now(el, &now); + +- switch (listener->type) { +-#ifdef WITH_PROXY +- case RAD_LISTEN_PROXY: +- limit = &sock->home->limit; +- break; +-#endif +- +- case RAD_LISTEN_AUTH: +-#ifdef WITH_ACCOUNTING +- case RAD_LISTEN_ACCT: +-#endif +- limit = &sock->limit; +- break; +- +- default: +- return; +- } ++ limit = &sock->limit; + + /* + * If we enforce a lifetime, do it now. +@@ -2069,6 +2155,16 @@ static void tcp_socket_timer(void *ctx) + * Mark the socket as "don't use if at all possible". + */ + listener->status = RAD_LISTEN_STATUS_FROZEN; ++ ++ /* ++ * If it's blocked, then push all of the requests to other sockets. ++ */ ++#ifdef WITH_TLS ++ if (listener->blocked) { ++ listener->status = RAD_LISTEN_STATUS_REMOVE_NOW; ++ } ++#endif ++ + event_new_fd(listener); + return; + } +@@ -2222,11 +2318,9 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) + } + } + +-#ifdef WITH_TCP + if (request->proxy_listener) { + request->proxy_listener->count--; + } +-#endif + request->proxy_listener = NULL; + + /* +@@ -2278,7 +2372,7 @@ static int insert_into_proxy_hash(REQUEST *request) + + + PTHREAD_MUTEX_LOCK(&proxy_mutex); +- proxy_listener = NULL; ++ proxy_listener = request->proxy_listener; /* may or may not be NULL */ + request->num_proxied_requests = 1; + request->num_proxied_responses = 0; + +@@ -2288,8 +2382,8 @@ static int insert_into_proxy_hash(REQUEST *request) + + RDEBUG3("proxy: Trying to allocate ID (%d/2)", tries); + success = fr_packet_list_id_alloc(proxy_list, +- request->home_server->proto, +- &request->proxy, &proxy_listener); ++ request->home_server->proto, ++ &request->proxy, &proxy_listener); + if (success) break; + + if (tries > 0) continue; /* try opening new socket only once */ +@@ -2298,6 +2392,16 @@ static int insert_into_proxy_hash(REQUEST *request) + if (proxy_no_new_sockets) break; + #endif + ++ /* ++ * Don't try to add a new listener if it's not possible. ++ */ ++ if (fr_event_list_full(el)) { ++ RATE_LIMIT(ERROR("Cannot open a new proxy socket - too many sockets are already open")); ++ request->home_server->state = HOME_STATE_CONNECTION_FAIL; ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++ goto fail; ++ } ++ + RDEBUG3("proxy: Trying to open a new listener to the home server"); + this = proxy_new_listener(proxy_ctx, request->home_server, 0); + if (!this) { +@@ -2362,9 +2466,7 @@ static int insert_into_proxy_hash(REQUEST *request) + */ + request->home_server->currently_outstanding++; + +-#ifdef WITH_TCP + request->proxy_listener->count++; +-#endif + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + +@@ -2389,7 +2491,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + /* + * There may be a proxy reply, but it may be too late. + */ +- if ((request->home_server && !request->home_server->server) && !request->proxy_listener) return 0; ++ if ((request->home_server && !request->home_server->virtual_server) && !request->proxy_listener) return 0; + + /* + * Delete any reply we had accumulated until now. +@@ -2453,7 +2555,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + * Decode the packet if required. + */ + if (request->proxy_listener) { +- rcode = request->proxy_listener->decode(request->proxy_listener, request); ++ rcode = request->proxy_listener->proxy_decode(request->proxy_listener, request); + debug_packet(request, reply, true); + + /* +@@ -2473,19 +2575,18 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + remove_from_proxy_hash(request); + } + +- old_server = request->server; + + /* +- * If the home server is virtual, just run pre_proxy from +- * that section. ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. + */ +- if (request->home_server && request->home_server->server) { +- request->server = request->home_server->server; ++ old_server = request->server; ++ if (request->home_server && request->home_server->virtual_server) { ++ request->server = request->home_server->virtual_server; + +- } else { +- if (request->home_pool && request->home_pool->virtual_server) { ++ } else if (request->home_pool && request->home_pool->virtual_server) { + request->server = request->home_pool->virtual_server; +- } + } + + /* +@@ -2598,11 +2699,79 @@ int request_proxy_reply(RADIUS_PACKET *packet) + * ignore it. This does the MD5 calculations in the + * server core, but I guess we can fix that later. + */ +- if (!request->proxy_reply && +- (rad_verify(packet, request->proxy, +- request->home_server->secret) != 0)) { +- DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); +- return 0; ++ 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 (!request->home_server) { ++ proxy_reply_too_late(request); ++ return 0; ++ } ++ ++ if (!rad_packet_ok(packet, require_ma, &reason)) { ++ DEBUG("Ignoring invalid packet - %s", fr_strerror()); ++ return 0; ++ } ++ ++ if (rad_verify(packet, request->proxy, ++ request->home_server->secret) != 0) { ++ DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); ++ 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->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."); ++ RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); ++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++ } + } + + /* +@@ -2887,8 +3056,9 @@ static void proxy_running(REQUEST *request, int action) + * The key attributes are: + * - PW_PROXY_TO_REALM - Specifies a realm the request should be proxied to. + * - PW_HOME_SERVER_POOL - Specifies a specific home server pool to proxy to. +- * - PW_PACKET_DST_IP_ADDRESS - Specifies a specific IPv4 home server to proxy to. +- * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a specific IPv6 home server to proxy to. ++ * - PW_HOME_SERVER_NAME - Specifies a home server by name ++ * - PW_PACKET_DST_IP_ADDRESS - Specifies a home server by IPv4 address ++ * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a home server by IPv5 address + * + * Certain packet types such as #PW_CODE_STATUS_SERVER will never be proxied. + * +@@ -2918,7 +3088,9 @@ static int request_will_proxy(REQUEST *request) + + VERIFY_REQUEST(request); + +- if (!request->root->proxy_requests) return 0; ++ if (!request->root->proxy_requests) { ++ return 0; ++ } + if (request->packet->dst_port == 0) return 0; + if (request->packet->code == PW_CODE_STATUS_SERVER) return 0; + if (request->in_proxy_hash) return 0; +@@ -3052,7 +3224,55 @@ static int request_will_proxy(REQUEST *request) + * The home server is alive (or may be alive). + * Send the packet to the IP. + */ +- if (home->state < HOME_STATE_IS_DEAD) goto do_home; ++ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; ++ ++ /* ++ * The home server is dead. If you wanted ++ * fail-over, you should have proxied to a pool. ++ * Sucks to be you. ++ */ ++ ++ return 0; ++ ++ } else if ((vp = fr_pair_find_by_num(request->config, PW_HOME_SERVER_NAME, 0, TAG_ANY)) != NULL) { ++ int type; ++ ++ switch (request->packet->code) { ++ case PW_CODE_ACCESS_REQUEST: ++ type = HOME_TYPE_AUTH; ++ break; ++ ++#ifdef WITH_ACCOUNTING ++ case PW_CODE_ACCOUNTING_REQUEST: ++ type = HOME_TYPE_ACCT; ++ break; ++#endif ++ ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ case PW_CODE_DISCONNECT_REQUEST: ++ type = HOME_TYPE_COA; ++ break; ++#endif ++ ++ default: ++ return 0; ++ } ++ ++ /* ++ * Find the home server by name. ++ */ ++ home = home_server_byname(vp->vp_strvalue, type); ++ if (!home) { ++ RWDEBUG("No such home server %s", vp->vp_strvalue); ++ return 0; ++ } ++ ++ /* ++ * The home server is alive (or may be alive). ++ * Send the packet to the IP. ++ */ ++ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; + + /* + * The home server is dead. If you wanted +@@ -3063,6 +3283,7 @@ static int request_will_proxy(REQUEST *request) + return 0; + + } else { ++ + return 0; + } + +@@ -3081,8 +3302,8 @@ static int request_will_proxy(REQUEST *request) + home = home_server_ldb(realmname, pool, request); + + if (!home) { +- REDEBUG2("Failed to find live home server: Cancelling proxy"); +- return 1; ++ REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); ++ return -1; + } + + do_home: +@@ -3093,7 +3314,11 @@ do_home: + * Once we've decided to proxy a request, we cannot send + * a CoA packet. So we free up any CoA packet here. + */ +- if (request->coa) request_done(request->coa, FR_ACTION_CANCELLED); ++ if (request->coa) { ++ RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); ++ request_done(request->coa, FR_ACTION_COA_CANCELLED); ++ request->coa = NULL; ++ } + #endif + + /* +@@ -3179,14 +3404,14 @@ do_home: + pre_proxy_type = vp->vp_integer; + } + +- old_server = request->server; +- + /* +- * If the home server is virtual, just run pre_proxy from +- * that section. ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. + */ +- if (request->home_server && request->home_server->server) { +- request->server = request->home_server->server; ++ old_server = request->server; ++ if (request->home_server && request->home_server->virtual_server) { ++ request->server = request->home_server->virtual_server; + + } else { + char buffer[128]; +@@ -3245,7 +3470,7 @@ static int proxy_to_virtual_server(REQUEST *request) + } + + DEBUG("Proxying to virtual server %s", +- request->home_server->server); ++ request->home_server->virtual_server); + + /* + * Packets to virtual servers don't get +@@ -3260,7 +3485,7 @@ static int proxy_to_virtual_server(REQUEST *request) + fake->packet->vps = fr_pair_list_copy(fake->packet, request->packet->vps); + talloc_free(request->proxy); + +- fake->server = request->home_server->server; ++ fake->server = request->home_server->virtual_server; + fake->handle = request->handle; + fake->process = NULL; /* should never be run for anything */ + +@@ -3309,7 +3534,8 @@ static int request_proxy(REQUEST *request) + #ifdef WITH_COA + if (request->coa) { + RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); +- request_done(request->coa, FR_ACTION_CANCELLED); ++ request_done(request->coa, FR_ACTION_COA_CANCELLED); ++ request->coa = NULL; + } + #endif + +@@ -3329,7 +3555,7 @@ static int request_proxy(REQUEST *request) + * + * So, we have some horrible hacks to get around that. + */ +- if (request->home_server->server) return proxy_to_virtual_server(request); ++ if (request->home_server->virtual_server) return proxy_to_virtual_server(request); + + /* + * We're actually sending a proxied packet. Do that now. +@@ -3372,7 +3598,7 @@ static int request_proxy(REQUEST *request) + /* + * Encode the packet before we do anything else. + */ +- request->proxy_listener->encode(request->proxy_listener, request); ++ request->proxy_listener->proxy_encode(request->proxy_listener, request); + debug_packet(request, request->proxy, false); + + /* +@@ -3392,7 +3618,7 @@ static int request_proxy(REQUEST *request) + /* + * And send the packet. + */ +- request->proxy_listener->send(request->proxy_listener, request); ++ request->proxy_listener->proxy_send(request->proxy_listener, request); + return 1; + } + +@@ -3458,7 +3684,7 @@ static int request_proxy_anew(REQUEST *request) + * If so, run that instead of doing proxying to a real + * server. + */ +- if (home->server) { ++ if (home->virtual_server) { + request->home_server = home; + TALLOC_FREE(request->proxy); + +@@ -3538,7 +3764,7 @@ static void request_ping(REQUEST *request, int action) + * + * If it's zombie, we mark it alive immediately. + */ +- if ((home->state >= HOME_STATE_IS_DEAD) && ++ if (HOME_SERVER_IS_DEAD(home) && + (home->num_received_pings < home->num_pings_to_alive)) { + return; + } +@@ -3610,7 +3836,7 @@ static void ping_home_server(void *ctx) + + if (timercmp(&when, &now, <)) { + DEBUG("PING: Zombie period is over for home server %s", home->log_name); +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + } + +@@ -3639,6 +3865,7 @@ static void ping_home_server(void *ctx) + NO_CHILD_THREAD; + + request->proxy = rad_alloc(request, true); ++ request->root = &main_config; + rad_assert(request->proxy != NULL); + + if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) { +@@ -3735,9 +3962,10 @@ static void ping_home_server(void *ctx) + home->num_sent_pings++; + + rad_assert(request->proxy_listener != NULL); ++ request->proxy_listener->proxy_encode(request->proxy_listener, request); + debug_packet(request, request->proxy, false); +- request->proxy_listener->send(request->proxy_listener, +- request); ++ request->proxy_listener->proxy_send(request->proxy_listener, ++ request); + + /* + * Add +/- 2s of jitter, as suggested in RFC 3539 +@@ -3840,7 +4068,34 @@ void revive_home_server(void *ctx) + home->port); + } + +-void mark_home_server_dead(home_server_t *home, struct timeval *when) ++#ifdef WITH_TLS ++static int eol_home_listener(UNUSED void *ctx, void *data) ++{ ++ rad_listen_t *this = talloc_get_type_abort(data, rad_listen_t); ++ ++ /* ++ * The socket isn't blocked, we can still use it. ++ * ++ * i.e. the home server is dead for a reason OTHER than ++ * "all available sockets are blocked". ++ * ++ * We can still ping the home server via sockets which ++ * are writable. ++ */ ++ if (!this->blocked) return 0; ++ ++ this->status = RAD_LISTEN_STATUS_EOL; ++ ++ FD_MUTEX_LOCK(&fd_mutex); ++ this->next = new_listeners; ++ new_listeners = this; ++ FD_MUTEX_UNLOCK(&fd_mutex); ++ ++ return 1; /* alway delete from this tree */ ++} ++#endif ++ ++void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down) + { + int previous_state = home->state; + char buffer[128]; +@@ -3853,6 +4108,34 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when) + home->state = HOME_STATE_IS_DEAD; + home_trigger(home, "home_server.dead"); + ++#ifdef WITH_TLS ++ /* ++ * If the home server is dead, then close all of the sockets associated with it. ++ * ++ * Note that the "EOL listener" code expects to _also_ ++ * delete the listeners. At which point we end up with a ++ * mutex locked twice, and bad things happen. The ++ * solution is to move the listeners to the global ++ * "waiting for update" list, and then notify ourselves ++ * that there are listeners waiting to be updated. ++ */ ++ if (home->listeners) { ++ ASSERT_MASTER; ++ ++ rbtree_walk(home->listeners, RBTREE_DELETE_ORDER, eol_home_listener, NULL); ++ radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD); ++ } ++#endif ++ ++ /* ++ * Administratively down - don't do anything to bring it ++ * up. ++ */ ++ if (down) { ++ home->state = HOME_STATE_ADMIN_DOWN; ++ return; ++ } ++ + /* + * Ping it if configured, AND we can ping it. + */ +@@ -3925,14 +4208,14 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * The request was proxied to a virtual server. + * Ignore the retransmit. + */ +- if (request->home_server->server) return; ++ if (request->home_server->virtual_server) return; + + /* + * Failed connections get the home server marked + * as dead. + */ + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + + /* +@@ -3948,7 +4231,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * If the listener is known or frozen, use it for + * retransmits. + */ +- if ((home->state >= HOME_STATE_IS_DEAD) || ++ if (HOME_SERVER_IS_DEAD(home) || + !request->proxy_listener || + (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { + request_proxy_anew(request); +@@ -3980,7 +4263,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + when.tv_sec++; + + if (timercmp(&now, &when, <)) { +- DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", ++ DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), +@@ -4014,7 +4297,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + home->last_packet_sent = now.tv_sec; + request->proxy->timestamp = now; + debug_packet(request, request->proxy, false); +- request->proxy_listener->send(request->proxy_listener, request); ++ request->proxy_listener->proxy_send(request->proxy_listener, request); + break; + + case FR_ACTION_TIMER: +@@ -4023,7 +4306,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * as dead. + */ + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + + response_window = request_response_window(request); +@@ -4175,6 +4458,7 @@ static void request_coa_originate(REQUEST *request) + VALUE_PAIR *vp; + REQUEST *coa; + fr_ipaddr_t ipaddr; ++ char const *old_server; + char buffer[256]; + + VERIFY_REQUEST(request); +@@ -4202,7 +4486,7 @@ static void request_coa_originate(REQUEST *request) + + if (!main_config.proxy_requests) { + RWDEBUG("Cannot originate CoA packets unless 'proxy_requests = yes'"); +- TALLOC_FREE(request->coa); ++ TALLOC_FREE(request->coa); + return; + } + +@@ -4234,11 +4518,11 @@ static void request_coa_originate(REQUEST *request) + /* + * Prefer the pool to one server + */ +- } else if (request->client->coa_pool) { +- coa->home_pool = request->client->coa_pool; ++ } else if (request->client->coa_home_pool) { ++ coa->home_pool = request->client->coa_home_pool; + +- } else if (request->client->coa_server) { +- coa->home_server = request->client->coa_server; ++ } else if (request->client->coa_home_server) { ++ coa->home_server = request->client->coa_home_server; + + } else { + /* +@@ -4318,19 +4602,26 @@ static void request_coa_originate(REQUEST *request) + pre_proxy_type = vp->vp_integer; + } + +- if (coa->home_pool && coa->home_pool->virtual_server) { +- char const *old_server = coa->server; ++ /* ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. ++ */ ++ old_server = request->server; ++ if (coa->home_server && coa->home_server->virtual_server) { ++ coa->server = coa->home_server->virtual_server; + ++ } else if (coa->home_pool && coa->home_pool->virtual_server) { + coa->server = coa->home_pool->virtual_server; +- RDEBUG2("server %s {", coa->server); +- RINDENT(); +- rcode = process_pre_proxy(pre_proxy_type, coa); +- REXDENT(); +- RDEBUG2("}"); +- coa->server = old_server; +- } else { +- rcode = process_pre_proxy(pre_proxy_type, coa); + } ++ ++ RDEBUG2("server %s {", coa->server); ++ RINDENT(); ++ rcode = process_pre_proxy(pre_proxy_type, coa); ++ REXDENT(); ++ RDEBUG2("}"); ++ coa->server = old_server; ++ + switch (rcode) { + default: + goto fail; +@@ -4379,7 +4670,7 @@ static void request_coa_originate(REQUEST *request) + /* + * Encode the packet before we do anything else. + */ +- coa->proxy_listener->encode(coa->proxy_listener, coa); ++ coa->proxy_listener->proxy_encode(coa->proxy_listener, coa); + debug_packet(coa, coa->proxy, false); + + #ifdef DEBUG_STATE_MACHINE +@@ -4404,7 +4695,7 @@ static void request_coa_originate(REQUEST *request) + /* + * And send the packet. + */ +- coa->proxy_listener->send(coa->proxy_listener, coa); ++ coa->proxy_listener->proxy_send(coa->proxy_listener, coa); + } + + +@@ -4420,11 +4711,11 @@ static void coa_retransmit(REQUEST *request) + * Don't do fail-over. This is a 3.1 feature. + */ + if (!request->home_server || +- (request->home_server->state >= HOME_STATE_IS_DEAD) || ++ HOME_SERVER_IS_DEAD(request->home_server) || + request->proxy_reply || + !request->proxy_listener || + (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_COA_CANCELLED); + return; + } + +@@ -4548,8 +4839,8 @@ static void coa_retransmit(REQUEST *request) + request->proxy->dst_port, + request->proxy->id); + +- request->proxy_listener->send(request->proxy_listener, +- request); ++ request->proxy_listener->proxy_send(request->proxy_listener, ++ request); + } + + +@@ -4579,7 +4870,7 @@ static bool coa_max_time(REQUEST *request) + */ + if (request->child_state == REQUEST_DONE) { + done: +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_MAX_TIME); + return true; + } + +@@ -4615,7 +4906,8 @@ static bool coa_max_time(REQUEST *request) + buffer, sizeof(buffer)), + request->proxy->dst_port, + mrd); +- goto done; ++ request_done(request, FR_ACTION_DONE); ++ return true; + } + + #ifdef HAVE_PTHREAD_H +@@ -4948,15 +5240,14 @@ static void event_status(struct timeval *wake) + } + } + +-#ifdef WITH_TCP + static void listener_free_cb(void *ctx) + { + rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t); ++ listen_socket_t *sock = this->data; + char buffer[1024]; + + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + fr_event_now(el, &when); + when.tv_sec += 3; +@@ -4977,9 +5268,13 @@ static void listener_free_cb(void *ctx) + this->print(this, buffer, sizeof(buffer)); + DEBUG("... cleaning up socket %s", buffer); + rad_assert(this->next == NULL); ++#ifdef WITH_TCP ++ fr_event_delete(el, &sock->ev); ++#endif + talloc_free(this); + } + ++#ifdef WITH_TCP + #ifdef WITH_PROXY + static int proxy_eol_cb(void *ctx, void *data) + { +@@ -5024,6 +5319,7 @@ static int proxy_eol_cb(void *ctx, void *data) + static void event_new_fd(rad_listen_t *this) + { + char buffer[1024]; ++ listen_socket_t *sock = NULL; + + ASSERT_MASTER; + +@@ -5031,29 +5327,28 @@ static void event_new_fd(rad_listen_t *this) + + this->print(this, buffer, sizeof(buffer)); + +- if (this->status == RAD_LISTEN_STATUS_INIT) { +- listen_socket_t *sock = this->data; +- ++ if (this->type != RAD_LISTEN_DETAIL) { ++ sock = this->data; + rad_assert(sock != NULL); ++ } ++ ++ if (this->status == RAD_LISTEN_STATUS_INIT) { + if (just_started) { + DEBUG("Listening on %s", buffer); +- } else { +- INFO(" ... adding new socket %s", buffer); +- } + + #ifdef WITH_PROXY +- if (!just_started && (this->type == RAD_LISTEN_PROXY)) { +- home_server_t *home; +- +- home = sock->home; +- if (!home || !home->limit.max_connections) { +- INFO(" ... adding new socket %s", buffer); +- } else { ++ } else if (this->type == RAD_LISTEN_PROXY) { ++ home_server_t *home = sock->home; ++ ++ if (home && home->limit.max_connections) { + INFO(" ... adding new socket %s (%u of %u)", buffer, + home->limit.num_connections, home->limit.max_connections); ++ } else { ++ INFO(" ... adding new socket %s", buffer); + } +- + #endif ++ } else { ++ INFO(" ... adding new socket %s", buffer); + } + + switch (this->type) { +@@ -5084,6 +5379,7 @@ static void event_new_fd(rad_listen_t *this) + */ + case RAD_LISTEN_PROXY: + #ifdef WITH_TCP ++ rad_assert(sock != NULL); + rad_assert((sock->proto == IPPROTO_UDP) || (sock->home != NULL)); + + /* +@@ -5102,6 +5398,19 @@ static void event_new_fd(rad_listen_t *this) + rad_panic("Failed to insert event"); + } + } ++ ++ /* ++ * Run a callback to do any specific ++ * signalling on "connection up". ++ * ++ * For TLS sockets and WITH_COA_TUNNEL, ++ * this function should be similar to ++ * ping_home_server(), except that it ++ * should send a Status-Server packet, ++ * with Originating-Realm-Key as a VSA. ++ */ ++// process_listener_up(this); ++ + #endif /* WITH_TCP */ + break; + #endif /* WITH_PROXY */ +@@ -5136,16 +5445,35 @@ static void event_new_fd(rad_listen_t *this) + /* + * All sockets: add the FD to the event handler. + */ ++ insert_fd: + if (fr_event_fd_insert(el, 0, this->fd, + event_socket_handler, this)) { + this->status = RAD_LISTEN_STATUS_KNOWN; + return; + } + +- ERROR("Failed adding event handler for socket: %s", fr_strerror()); +- this->status = RAD_LISTEN_STATUS_REMOVE_NOW; ++ /* ++ * Print out which socket failed. ++ * ++ * If we're trying to add the socket, then ++ * forcibly remove it immediately, without any ++ * additional cleanups. There cannot, and MUST ++ * NOT be any packets associated with the socket. ++ */ ++ this->print(this, buffer, sizeof(buffer)); ++ ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); ++ ++ this->status = RAD_LISTEN_STATUS_EOL; ++ goto listener_is_eol; + } /* end of INIT */ + ++ if (this->status == RAD_LISTEN_STATUS_PAUSE) { ++ fr_event_fd_delete(el, 0, this->fd); ++ return; ++ } ++ ++ if (this->status == RAD_LISTEN_STATUS_RESUME) goto insert_fd; ++ + #ifdef WITH_TCP + /* + * The socket has reached a timeout. Try to close it. +@@ -5157,7 +5485,6 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + /* + * Try again to clean up the socket in 30 +@@ -5179,6 +5506,7 @@ static void event_new_fd(rad_listen_t *this) + fr_event_fd_delete(el, 0, this->fd); + this->status = RAD_LISTEN_STATUS_REMOVE_NOW; + } ++#endif /* WITH_TCP */ + + /* + * The socket has had a catastrophic error. Close it. +@@ -5189,6 +5517,7 @@ static void event_new_fd(rad_listen_t *this) + */ + fr_event_fd_delete(el, 0, this->fd); + ++ listener_is_eol: + #ifdef WITH_PROXY + /* + * Tell all requests using this socket that the socket is dead. +@@ -5214,7 +5543,6 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + /* + * Try again to clean up the socket in 30 +@@ -5238,17 +5566,16 @@ static void event_new_fd(rad_listen_t *this) + */ + this->status = RAD_LISTEN_STATUS_REMOVE_NOW; + } /* socket is at EOL */ +-#endif /* WITH_TCP */ ++ ++ if (this->dead) goto wait_some_more; + + /* + * Nuke the socket. + */ + if (this->status == RAD_LISTEN_STATUS_REMOVE_NOW) { + int devnull; +-#ifdef WITH_TCP +- listen_socket_t *sock = this->data; +- struct timeval when; +-#endif ++ ++ this->dead = true; + + /* + * Re-open the socket, pointing it to /dev/null. +@@ -5290,6 +5617,7 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->type == RAD_LISTEN_PROXY) { + home_server_t *home; ++ sock = this->data; + + home = sock->home; + if (!home || !home->limit.max_connections) { +@@ -5307,6 +5635,20 @@ static void event_new_fd(rad_listen_t *this) + buffer, fr_strerror()); + fr_exit(1); + } ++ ++#ifdef WITH_TLS ++ /* ++ * Remove this socket from the list of sockets assocated with this home server. ++ * ++ * This MUST be done with the proxy mutex locked! ++ */ ++ if (home && home->tls) { ++ fr_assert(home->listeners); ++ ++ (void) rbtree_deletebydata(home->listeners, this); ++ } ++#endif ++ + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + } else + #endif /* WITH_PROXY */ +@@ -5324,7 +5666,10 @@ static void event_new_fd(rad_listen_t *this) + */ + if (!spawn_flag) { + ASSERT_MASTER; +- if (sock->ev) fr_event_delete(el, &sock->ev); ++ ++ if (this->type != RAD_LISTEN_DETAIL && sock && sock->ev) { ++ fr_event_delete(el, &sock->ev); ++ } + listen_free(&this); + return; + } +@@ -5332,14 +5677,8 @@ static void event_new_fd(rad_listen_t *this) + /* + * Wait until all requests using this socket are done. + */ +- gettimeofday(&when, NULL); +- when.tv_sec += 3; +- +- ASSERT_MASTER; +- if (!fr_event_insert(el, listener_free_cb, this, &when, +- &(sock->ev))) { +- rad_panic("Failed to insert event"); +- } ++ wait_some_more: ++ listener_free_cb(this); + #endif /* WITH_TCP */ + } + +@@ -5603,7 +5942,10 @@ static void check_proxy(rad_listen_t *head) + rad_listen_t *this; + + if (check_config) return; +- if (!main_config.proxy_requests) return; ++ if (!main_config.proxy_requests) { ++ DEBUG3("Cannot proxy packets unless 'proxy_requests = yes'"); ++ return; ++ } + if (!head) return; + #ifdef WITH_TCP + if (!home_servers_udp) return; diff --git a/src/main/radclient.c b/src/main/radclient.c -index 52d2872b13..09d27c8711 100644 +index 52d2872b13..b48d97235e 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -28,6 +28,10 @@ RCSID("$Id$") @@ -2371,7 +7715,23 @@ index 52d2872b13..09d27c8711 100644 typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */ #include "smbdes.h" -@@ -155,9 +161,60 @@ static int _rc_request_free(rc_request_t *request) +@@ -54,6 +60,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 +96,7 @@ static void NEVER_RETURNS usage(void) + fprintf(stderr, " 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 Send each packet 'count' times.\n"); + fprintf(stderr, " -d Set user dictionary directory (defaults to " RADDBDIR ").\n"); + fprintf(stderr, " -D Set main dictionary directory (defaults to " DICTDIR ").\n"); +@@ -155,9 +163,60 @@ static int _rc_request_free(rc_request_t *request) return 0; } @@ -2432,7 +7792,7 @@ index 52d2872b13..09d27c8711 100644 unsigned int i; uint8_t *p; VALUE_PAIR *challenge, *reply; -@@ -190,9 +247,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, +@@ -190,9 +249,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, p[1] = 0x01; /* NT hash */ @@ -2444,7 +7804,7 @@ index 52d2872b13..09d27c8711 100644 smbdes_mschap(nthash, challenge->vp_octets, p + 26); return 1; -@@ -960,8 +1016,8 @@ static int send_one_packet(rc_request_t *request) +@@ -960,8 +1018,8 @@ static int send_one_packet(rc_request_t *request) */ fr_packet_list_yank(pl, request->packet); @@ -2455,13 +7815,187 @@ index 52d2872b13..09d27c8711 100644 deallocate_id(request); /* -@@ -1197,9 +1253,11 @@ int main(int argc, char **argv) +@@ -1000,6 +1058,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 +1233,20 @@ 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. +@@ -1146,6 +1342,7 @@ packet_done: + return 0; + } + ++DIAG_OFF(deprecated-declarations) + int main(int argc, char **argv) + { + int c; +@@ -1183,7 +1380,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 +@@ -1196,10 +1393,16 @@ int main(int argc, char **argv) + force_af = AF_INET6; break; ++ case 'b': ++ blast_radius = true; ++ break; ++ case 'c': - if (!isdigit((int) *optarg)) - usage(); -+ if (!isdigit((int) *optarg)) usage(); ++ if (!isdigit((uint8_t) *optarg)) usage(); + resend_count = atoi(optarg); + @@ -2469,7 +8003,34 @@ index 52d2872b13..09d27c8711 100644 break; case 'D': -@@ -1421,6 +1479,8 @@ int main(int argc, char **argv) +@@ -1274,7 +1477,7 @@ int main(int argc, char **argv) + break; + + case 'r': +- if (!isdigit((int) *optarg)) usage(); ++ if (!isdigit((uint8_t) *optarg)) usage(); + retries = atoi(optarg); + if ((retries == 0) || (retries > 1000)) usage(); + break; +@@ -1314,7 +1517,7 @@ int main(int argc, char **argv) + break; + + case 't': +- if (!isdigit((int) *optarg)) ++ if (!isdigit((uint8_t) *optarg)) + usage(); + timeout = atof(optarg); + break; +@@ -1361,7 +1564,7 @@ int main(int argc, char **argv) + /* + * Get the request type + */ +- if (!isdigit((int) argv[2][0])) { ++ if (!isdigit((uint8_t) argv[2][0])) { + packet_code = fr_str2int(request_types, argv[2], -2); + if (packet_code == -2) { + ERROR("Unrecognised request type \"%s\"", argv[2]); +@@ -1421,6 +1624,8 @@ int main(int argc, char **argv) exit(1); } @@ -2478,7 +8039,7 @@ index 52d2872b13..09d27c8711 100644 /* * Bind to the first specified IP address and port. * This means we ignore later ones. -@@ -1637,5 +1697,8 @@ int main(int argc, char **argv) +@@ -1637,5 +1842,9 @@ int main(int argc, char **argv) if ((stats.lost > 0) || (stats.failed > 0)) { exit(1); } @@ -2487,6 +8048,1065 @@ index 52d2872b13..09d27c8711 100644 + exit(0); } ++DIAG_ON(deprecated-declarations) +diff --git a/src/main/radiusd.c b/src/main/radiusd.c +index 9739514509..6cece08893 100644 +--- a/src/main/radiusd.c ++++ b/src/main/radiusd.c +@@ -710,6 +710,7 @@ cleanup: + if (main_config.memory_report) { + INFO("Allocated memory at time of report:"); + fr_log_talloc_report(NULL); ++ talloc_disable_null_tracking(); + } + + return rcode; +diff --git a/src/main/radtest.in b/src/main/radtest.in +index 38b1ba9a0f..af9df80eec 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 +@@ -112,7 +117,7 @@ if [ "$7" ] + then + nas=$7 + else +- nas=`hostname` ++ nas=`(hostname || uname -n) 2>/dev/null | sed 1q` + fi + + ( +@@ -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..3f2c37270c 100644 +--- a/src/main/realms.c ++++ b/src/main/realms.c +@@ -37,6 +37,10 @@ static rbtree_t *realms_byname = NULL; + bool home_servers_udp = false; + #endif + ++#ifdef HAVE_DIRENT_H ++#include ++#endif ++ + #ifdef HAVE_REGEX + typedef struct realm_regex realm_regex_t; + +@@ -53,6 +57,9 @@ static realm_regex_t *realms_regex = NULL; + + struct realm_config { + CONF_SECTION *cs; ++#ifdef HAVE_DIRENT_H ++ char const *directory; ++#endif + uint32_t dead_time; + uint32_t retry_count; + uint32_t retry_delay; +@@ -89,7 +96,7 @@ static realm_config_t *realm_config = NULL; + static rbtree_t *home_servers_byaddr = NULL; + static rbtree_t *home_servers_byname = NULL; + #ifdef WITH_STATS +-static int home_server_max_number = 0; ++int home_server_max_number = 0; + static rbtree_t *home_servers_bynumber = NULL; + #endif + +@@ -107,6 +114,10 @@ static const CONF_PARSER proxy_config[] = { + + { "dynamic", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, dynamic), NULL }, + ++#ifdef HAVE_DIRENT_H ++ { "directory", FR_CONF_OFFSET(PW_TYPE_STRING, realm_config_t, directory), NULL }, ++#endif ++ + { "dead_time", FR_CONF_OFFSET(PW_TYPE_INTEGER, realm_config_t, dead_time), STRINGIFY(DEAD_TIME) }, + + { "wake_all_if_all_dead", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, wake_all_if_all_dead), "no" }, +@@ -148,12 +159,12 @@ static int home_server_addr_cmp(void const *one, void const *two) + home_server_t const *a = one; + home_server_t const *b = two; + +- if (a->server && !b->server) return -1; +- if (!a->server && b->server) return +1; +- if (a->server && b->server) { ++ if (a->virtual_server && !b->virtual_server) return -1; ++ if (!a->virtual_server && b->virtual_server) return +1; ++ if (a->virtual_server && b->virtual_server) { + rcode = a->type - b->type; + if (rcode != 0) return rcode; +- return strcmp(a->server, b->server); ++ return strcmp(a->virtual_server, b->virtual_server); + } + + if (a->port < b->port) return -1; +@@ -268,6 +279,10 @@ static ssize_t xlat_home_server(UNUSED void *instance, REQUEST *request, + state = "fail"; + break; + ++ case HOME_STATE_ADMIN_DOWN: ++ state = "down"; ++ break; ++ + default: + state = "unknown"; + break; +@@ -317,6 +332,60 @@ static ssize_t xlat_server_pool(UNUSED void *instance, REQUEST *request, + + return xlat_cs(request->home_pool->cs, fmt, out, outlen); + } ++ ++ ++/* ++ * Xlat for %{home_server_dynamic:foo} ++ */ ++static ssize_t xlat_home_server_dynamic(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ int type; ++ char const *p; ++ home_server_t *home; ++ ++ if (outlen < 2) return 0; ++ ++ switch (request->packet->code) { ++ case PW_CODE_ACCESS_REQUEST: ++ type = HOME_TYPE_AUTH; ++ break; ++ ++#ifdef WITH_ACCOUNTING ++ case PW_CODE_ACCOUNTING_REQUEST: ++ type = HOME_TYPE_ACCT; ++ break; ++#endif ++ ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ case PW_CODE_DISCONNECT_REQUEST: ++ type = HOME_TYPE_COA; ++ break; ++#endif ++ ++ default: ++ *out = '\0'; ++ return 0; ++ } ++ ++ p = fmt; ++ while (isspace((uint8_t) *p)) p++; ++ ++ home = home_server_byname(p, type); ++ if (!home) { ++ *out = '\0'; ++ return 0; ++ } ++ ++ /* ++ * 1 for dynamic, 0 for static ++ */ ++ out[0] = '0' + home->dynamic; ++ out[1] = '\0'; ++ ++ return 1; ++} + #endif + + void realms_free(void) +@@ -366,11 +435,15 @@ static CONF_PARSER home_server_coa[] = { + }; + #endif + ++static const char *require_message_authenticator = NULL; ++ + static CONF_PARSER home_server_config[] = { ++ { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, ++ { "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 }, +- { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, server), NULL }, ++ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, virtual_server), NULL }, + + { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, home_server_t, port), "0" }, + +@@ -519,7 +592,7 @@ static bool home_server_insert(home_server_t *home, CONF_SECTION *cs) + return false; + } + +- if (!home->server && !rbtree_insert(home_servers_byaddr, home)) { ++ if (!home->virtual_server && !rbtree_insert(home_servers_byaddr, home)) { + rbtree_deletebydata(home_servers_byname, home); + cf_log_err_cs(cs, "Internal error %d adding home server %s", __LINE__, home->log_name); + return false; +@@ -561,7 +634,7 @@ bool realm_home_server_add(home_server_t *home) + return false; + } + +- if (!home->server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { ++ if (!home->virtual_server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { + char buffer[INET6_ADDRSTRLEN + 3]; + + inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr, buffer, sizeof(buffer)); +@@ -620,6 +693,19 @@ bool realm_home_server_add(home_server_t *home) + return true; + } + ++#ifdef WITH_TLS ++/* ++ * The listeners are always different. And we always look them up by *known* listener. And not "find me some random thing". ++ */ ++static int listener_cmp(void const *one, void const *two) ++{ ++ if (one < two) return -1; ++ if (one > two) return +1; ++ ++ return 0; ++} ++#endif ++ + /** Alloc a new home server defined by a CONF_SECTION + * + * @param ctx to allocate home_server_t in. +@@ -640,6 +726,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 +736,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. + */ +@@ -670,23 +763,25 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + } else if (cf_pair_find(cs, "virtual_server") != NULL) { + home->ipaddr.af = AF_UNSPEC; /* mark ipaddr as unused */ + +- if (!home->server) { ++ if (!home->virtual_server) { + cf_log_err_cs(cs, "Invalid value for virtual_server"); + goto error; + } + + /* +- * Try and find a 'server' section off the root of ++ * Try and find a "server" section off the root of + * the config with a name that matches the + * virtual_server. + */ +- if (!cf_section_sub_find_name2(rc->cs, "server", home->server)) { +- cf_log_err_cs(cs, "No such server %s", home->server); ++ if (!rc) goto error; ++ ++ if (!cf_section_sub_find_name2(rc->cs, "server", home->virtual_server)) { ++ cf_log_err_cs(cs, "No such server %s", home->virtual_server); + goto error; + } + + home->secret = ""; +- home->log_name = talloc_typed_strdup(home, home->server); ++ home->log_name = talloc_typed_strdup(home, home->virtual_server); + /* + * Otherwise it's an invalid config section and we + * raise an error. +@@ -717,7 +812,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + + #ifdef WITH_COA + case HOME_TYPE_COA: +- if (home->server != NULL) { ++ if (home->virtual_server != NULL) { + cf_log_err_cs(cs, "Home servers of type \"coa\" cannot point to a virtual server"); + goto error; + } +@@ -781,8 +876,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + cf_log_err_cs(cs, "Server not built with support for RADIUS over TCP"); + goto error; + #endif +- if (home->ping_check != HOME_PING_CHECK_NONE) { +- cf_log_err_cs(cs, "Only 'status_check = none' is allowed for home " ++ if ((home->ping_check != HOME_PING_CHECK_NONE) && ++ (home->ping_check != HOME_PING_CHECK_STATUS_SERVER)) { ++ cf_log_err_cs(cs, "Only 'status_check = status-server' is allowed for home " + "servers with 'proto = tcp'"); + goto error; + } +@@ -796,7 +892,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + home->proto = proto; + } + +- if (!home->server && rbtree_finddata(home_servers_byaddr, home)) { ++ if (!home->virtual_server && rbtree_finddata(home_servers_byaddr, home)) { + cf_log_err_cs(cs, "Duplicate home server"); + goto error; + } +@@ -831,7 +927,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + /* + * Virtual servers have some TLS restrictions. + */ +- if (home->server) { ++ if (home->virtual_server) { + if (tls) { + cf_log_err_cs(cs, "Virtual home_servers cannot have a \"tls\" subsection"); + goto error; +@@ -924,10 +1020,29 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + * Parse the SSL client configuration. + */ + if (tls) { ++ int rcode; ++ ++ /* ++ * We don't require this for TLS connections. ++ */ ++ home->require_ma = false; ++ + home->tls = tls_client_conf_parse(tls); + if (!home->tls) { + goto error; + } ++ ++ /* ++ * Connection timeouts for outgoing TLS connections. ++ */ ++ ++ rcode = cf_item_parse(tls, "connect_timeout", FR_ITEM_POINTER(PW_TYPE_INTEGER, &home->connect_timeout), NULL); ++ if (rcode < 0) goto error; ++ ++ if (!home->connect_timeout || (home->connect_timeout > 30)) home->connect_timeout = 30; ++ ++ home->listeners = rbtree_create(home, listener_cmp, NULL, RBTREE_FLAG_LOCK); ++ if (!home->listeners) goto error; + } + #endif + } /* end of parse home server */ +@@ -1173,8 +1288,17 @@ void realm_pool_free(home_pool_t *pool) + } + #endif /* HAVE_PTHREAD_H */ + +-int realm_pool_add(home_pool_t *pool, UNUSED CONF_SECTION *cs) ++int realm_pool_add(home_pool_t *pool, CONF_SECTION *cs) + { ++ home_pool_t *old; ++ ++ old = rbtree_finddata(home_pools_byname, pool); ++ if (old) { ++ cf_log_err_cs(cs, "Cannot add duplicate home server %s, original is at %s[%d]", pool->name, ++ cf_section_filename(old->cs), cf_section_lineno(old->cs)); ++ return 0; ++ } ++ + /* + * The structs aren't mutex protected. Refuse to destroy + * the server. +@@ -1265,7 +1389,7 @@ static int server_pool_add(realm_config_t *rc, + goto error; + } + +- if (!pool->fallback->server) { ++ if (!pool->fallback->virtual_server) { + cf_log_err_cs(cs, "Fallback home_server %s does NOT contain a virtual_server directive", + pool->fallback->log_name); + goto error; +@@ -1541,7 +1665,7 @@ static int old_server_add(realm_config_t *rc, CONF_SECTION *cs, + home->src_ipaddr.af = home->ipaddr.af; + } else { + home->ipaddr.af = AF_UNSPEC; +- home->server = server; ++ home->virtual_server = server; + } + talloc_free(q); + +@@ -2255,9 +2379,68 @@ int realms_init(CONF_SECTION *config) + #ifdef WITH_PROXY + xlat_register("home_server", xlat_home_server, NULL, NULL); + xlat_register("home_server_pool", xlat_server_pool, NULL, NULL); ++ xlat_register("home_server_dynamic", xlat_home_server_dynamic, NULL, NULL); + #endif + + realm_config = rc; ++ ++#ifdef HAVE_DIRENT_H ++ if (!rc->dynamic) { ++ if (rc->directory) { ++ WARN("Ignoring 'directory' as dynamic home servers were not configured."); ++ } ++ } else { ++ DIR *dir; ++ struct dirent *dp; ++ ++ if (!rc->directory) { ++ WARN("Ignoring \"dynamic = true\" due to not set \"directory\" in proxy.conf"); ++ return 1; ++ } ++ ++ DEBUG2("including files in directory %s", rc->directory); ++ ++ dir = opendir(rc->directory); ++ if (!dir) { ++ cf_log_err_cs(config, "Error reading directory %s: %s", ++ rc->directory, fr_syserror(errno)); ++ goto error; ++ } ++ ++ /* ++ * Read the directory, ignoring "." files. ++ */ ++ while ((dp = readdir(dir)) != NULL) { ++ char const *p; ++ char conf_file[PATH_MAX]; ++ ++ if (dp->d_name[0] == '.') continue; ++ ++ /* ++ * Check for valid characters ++ */ ++ for (p = dp->d_name; *p != '\0'; p++) { ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || ++ (*p == '-') || ++ (*p == '_') || ++ (*p == '.')) continue; ++ break; ++ } ++ if (*p != '\0') continue; ++ ++ snprintf(conf_file, sizeof(conf_file), "%s/%s", rc->directory, dp->d_name); ++ if (home_server_afrom_file(conf_file) < 0) { ++ ERROR("Failed reading home_server from %s - %s", ++ conf_file, fr_strerror()); ++ closedir(dir); ++ goto error; ++ } ++ } ++ closedir(dir); ++ } ++#endif ++ + return 1; + } + +@@ -2388,7 +2571,7 @@ void home_server_update_request(home_server_t *home, REQUEST *request) + * the 'hints' file. + */ + request->proxy->vps = fr_pair_list_copy(request->proxy, +- request->packet->vps); ++ request->packet->vps); + } + + /* +@@ -2516,7 +2699,7 @@ home_server_t *home_server_ldb(char const *realmname, + * Home servers that are unknown, alive, or zombie + * are used for proxying. + */ +- if (home->state >= HOME_STATE_IS_DEAD) { ++ if (HOME_SERVER_IS_DEAD(home)) { + continue; + } + +@@ -2533,7 +2716,8 @@ home_server_t *home_server_ldb(char const *realmname, + * came from this server. Don't re-proxy it + * there. + */ +- if ((request->listener->type == RAD_LISTEN_DETAIL) && ++ if (request->listener && ++ (request->listener->type == RAD_LISTEN_DETAIL) && + (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) && + (fr_ipaddr_cmp(&home->ipaddr, &request->packet->src_ipaddr) == 0)) { + continue; +@@ -2641,7 +2825,7 @@ home_server_t *home_server_ldb(char const *realmname, + found = pool->fallback; + + WARN("Home server pool %s failing over to fallback %s", +- pool->name, found->server); ++ pool->name, found->virtual_server); + if (pool->in_fallback) goto update_and_return; + + pool->in_fallback = true; +@@ -2680,7 +2864,7 @@ home_server_t *home_server_ldb(char const *realmname, + + if (!home) continue; + +- if ((home->state >= HOME_STATE_IS_DEAD) && ++ if (HOME_SERVER_IS_DEAD(home) && + (home->ping_check == HOME_PING_CHECK_NONE)) { + home->state = HOME_STATE_ALIVE; + home->response_timeouts = 0; +@@ -2740,7 +2924,7 @@ home_server_t *home_server_find(fr_ipaddr_t *ipaddr, uint16_t port, + #else + myhome.proto = IPPROTO_UDP; + #endif +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_byaddr, &myhome); + } +@@ -2764,7 +2948,7 @@ home_server_t *home_server_find_bysrc(fr_ipaddr_t *ipaddr, uint16_t port, + #else + myhome.proto = IPPROTO_UDP; + #endif +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_byaddr, &myhome); + } +@@ -2789,7 +2973,7 @@ home_server_t *home_server_bynumber(int number) + + memset(&myhome, 0, sizeof(myhome)); + myhome.number = number; +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_bynumber, &myhome); + } +@@ -2805,4 +2989,116 @@ home_pool_t *home_pool_byname(char const *name, int type) + return rbtree_finddata(home_pools_byname, &mypool); + } + ++int home_server_afrom_file(char const *filename) ++{ ++ CONF_SECTION *cs, *subcs; ++ char const *p; ++ home_server_t *home; ++ ++ if (!realm_config->dynamic) { ++ fr_strerror_printf("Must set \"dynamic = true\" in proxy.conf for dynamic home servers to work"); ++ return -1; ++ } ++ ++ cs = cf_section_alloc(NULL, "home", filename); ++ if (!cs) { ++ fr_strerror_printf("Failed allocating memory"); ++ return -1; ++ } ++ ++ if (cf_file_read(cs, filename) < 0) { ++ fr_strerror_printf("Failed reading file %s", filename); ++ error: ++ talloc_free(cs); ++ return -1; ++ } ++ ++ p = strrchr(filename, '/'); ++ if (p) { ++ p++; ++ } else { ++ p = filename; ++ } ++ ++ subcs = cf_section_sub_find_name2(cs, "home_server", p); ++ if (!subcs) { ++ fr_strerror_printf("No 'home_server %s' definition in the file.", p); ++ goto error; ++ } ++ ++ home = home_server_afrom_cs(realm_config, realm_config, subcs); ++ if (!home) { ++ fr_strerror_printf("Failed parsing configuration to a home_server structure"); ++ goto error; ++ } ++ ++ home->dynamic = true; ++ ++ if (home->virtual_server) { ++ fr_strerror_printf("Dynamic home_server '%s' cannot have 'server = %s' configuration item", p, home->virtual_server); ++ talloc_free(home); ++ goto error; ++ } ++ ++ if (home->dual) { ++ fr_strerror_printf("Dynamic home_server '%s' is missing 'type', or it is set to 'auth+acct'. Please specify 'type = auth' or 'type = acct', etc.", p); ++ talloc_free(home); ++ goto error; ++ } ++ ++ if (!realm_home_server_add(home)) { ++ fr_strerror_printf("Failed adding home_server to the internal data structures"); ++ talloc_free(home); ++ goto error; ++ } ++ ++ return 0; ++} ++ ++int home_server_delete(char const *name, char const *type_name) ++{ ++ home_server_t *home; ++ int type; ++ char const *p; ++ ++ if (!realm_config->dynamic) { ++ fr_strerror_printf("Must set 'dynamic' in proxy.conf for dynamic home servers to work"); ++ return -1; ++ } ++ ++ type = fr_str2int(home_server_types, type_name, HOME_TYPE_INVALID); ++ if (type == HOME_TYPE_INVALID) { ++ fr_strerror_printf("Unknown home_server type '%s'", type_name); ++ return -1; ++ } ++ ++ p = strrchr(name, '/'); ++ if (p) { ++ p++; ++ } else { ++ p = name; ++ } ++ ++ home = home_server_byname(p, type); ++ if (!home) { ++ fr_strerror_printf("Failed to find home_server %s", p); ++ return -1; ++ } ++ ++ if (!home->dynamic) { ++ fr_strerror_printf("Cannot delete static home_server %s", p); ++ return -1; ++ } ++ ++ (void) rbtree_deletebydata(home_servers_byname, home); ++ (void) rbtree_deletebydata(home_servers_byaddr, home); ++#ifdef WITH_STATS ++ (void) rbtree_deletebydata(home_servers_bynumber, home); ++#endif ++ ++ /* ++ * Leak home, and home->cs. Oh well. ++ */ ++ return 0; ++} + #endif +diff --git a/src/main/session.c b/src/main/session.c +index e359010a1b..8dbf5a6f14 100644 +--- a/src/main/session.c ++++ b/src/main/session.c +@@ -34,7 +34,7 @@ RCSID("$Id$") + /* + * End a session by faking a Stop packet to all accounting modules. + */ +-int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, ++int session_zap(REQUEST *request, fr_ipaddr_t const *nasaddr, uint32_t nas_port, + char const *user, + char const *sessionid, uint32_t cliaddr, char proto, + int session_time) +@@ -64,6 +64,8 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + + #define IPPAIR(n,v) PAIR(n,v,vp_ipaddr) + ++#define IPV6PAIR(n,v) PAIR(n,v,vp_ipv6addr) ++ + #define STRINGPAIR(n,v) do { \ + if(!(vp = fr_pair_afrom_num(stopreq->packet,n, 0))) { \ + talloc_free(stopreq); \ +@@ -75,7 +77,12 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + } while(0) + + INTPAIR(PW_ACCT_STATUS_TYPE, PW_STATUS_STOP); +- IPPAIR(PW_NAS_IP_ADDRESS, nasaddr); ++ ++ if (nasaddr->af == AF_INET) { ++ IPPAIR(PW_NAS_IP_ADDRESS, nasaddr->ipaddr.ip4addr.s_addr); ++ } else { ++ IPV6PAIR(PW_NAS_IPV6_ADDRESS, nasaddr->ipaddr.ip6addr); ++ } + + INTPAIR(PW_EVENT_TIMESTAMP, 0); + vp->vp_date = time(NULL); +@@ -127,29 +134,25 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + * 1 The user is logged in. + * 2 Some error occured. + */ +-int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, uint32_t nas_port, char const *user, + char const *session_id) + { + pid_t pid, child_pid; + int status; +- char address[16]; ++ char address[64]; + char port[11]; + RADCLIENT *cl; +- fr_ipaddr_t ipaddr; +- +- ipaddr.af = AF_INET; +- ipaddr.ipaddr.ip4addr.s_addr = nasaddr; + + /* + * Find NAS type. + */ +- cl = client_find_old(&ipaddr); ++ cl = client_find_old(nasaddr); + if (!cl) { + /* + * Unknown NAS, so trusting radutmp. + */ + DEBUG2("checkrad: Unknown NAS %s, not checking", +- ip_ntoa(address, nasaddr)); ++ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address))); + return 1; + } + +@@ -202,8 +205,8 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, + */ + closefrom(3); + +- ip_ntoa(address, nasaddr); +- snprintf(port, 11, "%u", nas_port); ++ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address)); ++ snprintf(port, sizeof(port), "%u", nas_port); + + #ifdef __EMX__ + /* OS/2 can't directly execute scripts then we call the command +@@ -223,7 +226,7 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, + exit(2); + } + #else +-int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, + UNUSED char const *user, UNUSED char const *session_id) + { + ERROR("Simultaneous-Use is not supported"); +@@ -234,7 +237,7 @@ int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, + #else + /* WITH_SESSION_MGMT */ + +-int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_t nas_port, ++int session_zap(UNUSED REQUEST *request, fr_ipaddr_t const *nasaddr, UNUSED uint32_t nas_port, + UNUSED char const *user, + UNUSED char const *sessionid, UNUSED uint32_t cliaddr, UNUSED char proto, + UNUSED int session_time) +@@ -242,7 +245,7 @@ int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_ + return RLM_MODULE_FAIL; + } + +-int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, + UNUSED char const *user, UNUSED char const *session_id) + { + ERROR("Simultaneous-Use is not supported"); +diff --git a/src/main/stats.c b/src/main/stats.c +index 33b5fd238a..6aa908bfea 100644 +--- a/src/main/stats.c ++++ b/src/main/stats.c +@@ -90,44 +90,58 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, + + void request_stats_final(REQUEST *request) + { +- if (request->master_state == REQUEST_COUNTED) return; ++ rad_listen_t *listener; ++ RADCLIENT *client; + +- if (!request->listener) return; +- if (!request->client) return; ++ if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; + +- if ((request->listener->type != RAD_LISTEN_NONE) && ++ /* don't count statistic requests */ ++ if (request->packet->code == PW_CODE_STATUS_SERVER) { ++ return; ++ } ++ ++ listener = request->listener; ++ if (listener) switch (listener->type) { ++ case RAD_LISTEN_NONE: + #ifdef WITH_ACCOUNTING +- (request->listener->type != RAD_LISTEN_ACCT) && ++ case RAD_LISTEN_ACCT: + #endif + #ifdef WITH_COA +- (request->listener->type != RAD_LISTEN_COA) && ++ case RAD_LISTEN_COA: + #endif +- (request->listener->type != RAD_LISTEN_AUTH)) return; ++ case RAD_LISTEN_AUTH: ++ break; + +- /* don't count statistic requests */ +- if (request->packet->code == PW_CODE_STATUS_SERVER) +- return; ++ default: ++ return; ++ } ++ ++ /* ++ * Deal with TCP / TLS issues. The statistics are kept in the parent socket. ++ */ ++ if (listener && listener->parent) listener = listener->parent; ++ client = request->client; + + #undef INC_AUTH +-#define INC_AUTH(_x) radius_auth_stats._x++;request->listener->stats._x++;request->client->auth._x++; ++#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; + + #undef INC_ACCT + #ifdef WITH_ACCOUNTING +-#define INC_ACCT(_x) radius_acct_stats._x++;request->listener->stats._x++;request->client->acct._x++ ++#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ + #else + #define INC_ACCT(_x) + #endif + + #undef INC_COA + #ifdef WITH_COA +-#define INC_COA(_x) radius_coa_stats._x++;request->listener->stats._x++;request->client->coa._x++ ++#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ + #else + #define INC_COA(_x) + #endif + + #undef INC_DSC + #ifdef WITH_DSC +-#define INC_DSC(_x) radius_dsc_stats._x++;request->listener->stats._x++;request->client->dsc._x++ ++#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ + #else + #define INC_DSC(_x) + #endif +@@ -140,7 +154,7 @@ void request_stats_final(REQUEST *request) + * deleted, because only the main server thread calls + * this function, which makes it thread-safe. + */ +- if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { ++ if (request->reply) switch (request->reply->code) { + case PW_CODE_ACCESS_ACCEPT: + INC_AUTH(total_access_accepts); + +@@ -268,7 +282,7 @@ void request_stats_final(REQUEST *request) + if (!request->proxy_reply) goto done; /* simplifies formatting */ + + #undef INC +-#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; ++#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; + + switch (request->proxy_reply->code) { + case PW_CODE_ACCESS_ACCEPT: +@@ -339,7 +353,7 @@ void request_stats_final(REQUEST *request) + done: + #endif /* WITH_PROXY */ + +- request->master_state = REQUEST_COUNTED; ++ request->options |= RAD_REQUEST_OPTION_STATS; + } + + typedef struct fr_stats2vp { +@@ -582,6 +596,23 @@ void request_stats_reply(REQUEST *request) + */ + if (!cl) return; + } ++#ifdef AF_INET6 ++ } else { ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_port) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++ cl = listener_find_client_list(&ipaddr, server_port->vp_integer, IPPROTO_UDP); ++ ++ /* ++ * Not found: don't do anything ++ */ ++ if (!cl) return; ++ } ++ } ++#endif /* AF_INET6 */ + } + + +@@ -597,6 +628,19 @@ void request_stats_reply(REQUEST *request) + } + #endif + ++#ifdef AF_INET6 ++ } else if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ memset(&ipaddr, 0, sizeof(ipaddr)); ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; ++ client = client_find(cl, &ipaddr, IPPROTO_UDP); ++#ifdef WITH_TCP ++ if (!client) { ++ client = client_find(cl, &ipaddr, IPPROTO_TCP); ++ } ++#endif ++#endif /* AF_INET6 */ ++ + /* + * Else look it up by number. + */ +@@ -615,23 +659,44 @@ void request_stats_reply(REQUEST *request) + * When retrieving client by number, also + * echo back it's IP address. + */ +- if ((vp->da->type == PW_TYPE_INTEGER) && +- (client->ipaddr.af == AF_INET)) { +- vp = radius_pair_create(request->reply, +- &request->reply->vps, +- PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); +- if (vp) { +- vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; ++ if (vp->da->type == PW_TYPE_INTEGER) { ++ if (client->ipaddr.af == AF_INET) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; ++ } ++ ++ if (client->ipaddr.prefix != 32) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_integer = client->ipaddr.prefix; ++ } ++ } + } + +- if (client->ipaddr.prefix != 32) { ++#ifdef AF_INET6 ++ if (client->ipaddr.af == AF_INET6) { + vp = radius_pair_create(request->reply, +- &request->reply->vps, +- PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS); + if (vp) { +- vp->vp_integer = client->ipaddr.prefix; ++ vp->vp_ipv6addr = client->ipaddr.ipaddr.ip6addr; ++ } ++ ++ if (client->ipaddr.prefix != 128) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_integer = client->ipaddr.prefix; ++ } + } + } ++#endif /* AF_INET6 */ + } + + if (server_ip) { +@@ -674,21 +739,26 @@ void request_stats_reply(REQUEST *request) + * See if we need to look up the server by socket + * socket. + */ +- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); +- if (!server_ip) return; +- + server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); + if (!server_port) return; + +- ipaddr.af = AF_INET; +- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +- this = listener_find_byipaddr(&ipaddr, +- server_port->vp_integer, +- IPPROTO_UDP); ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ ipaddr.af = AF_INET; ++ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; ++#ifdef AF_INET6 ++ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++#endif /* AF_INET6 */ ++ } else { ++ stats_error(request, "No listener IP address supplied"); ++ } + + /* + * Not found: don't do anything + */ ++ this = listener_find_byipaddr(&ipaddr, server_port->vp_integer, IPPROTO_UDP); + if (!this) { + stats_error(request, "No such listener"); + return; +@@ -730,16 +800,6 @@ void request_stats_reply(REQUEST *request) + VALUE_PAIR *server_ip, *server_port; + fr_ipaddr_t ipaddr; + +- /* +- * See if we need to look up the server by socket +- * socket. +- */ +- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); +- if (!server_ip) { +- stats_error(request, "No home server IP supplied"); +- return; +- } +- + server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); + if (!server_port) { + stats_error(request, "No home server port supplied"); +@@ -749,15 +809,30 @@ void request_stats_reply(REQUEST *request) + #ifndef NDEBUG + memset(&ipaddr, 0, sizeof(ipaddr)); + #endif +- ipaddr.af = AF_INET; +- ipaddr.prefix = 32; +- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +- home = home_server_find(&ipaddr, server_port->vp_integer, +- IPPROTO_UDP); ++ ++ /* ++ * See if we need to look up the server by socket ++ * socket. ++ */ ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ ipaddr.af = AF_INET; ++ ipaddr.prefix = 32; ++ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; ++#ifdef AF_INET6 ++ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++#endif /* AF_INET6 */ ++ } else { ++ stats_error(request, "No home server IP supplied"); ++ return; ++ } + + /* + * Not found: don't do anything + */ ++ home = home_server_find(&ipaddr, server_port->vp_integer, IPPROTO_UDP); + if (!home) { + stats_error(request, "Failed to find home server IP"); + return; diff --git a/src/main/tls.c b/src/main/tls.c index 78c7370a63..338ccd6446 100644 --- a/src/main/tls.c @@ -5670,43 +12290,126 @@ index 78c7370a63..338ccd6446 100644 } } diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c -index 0eed87b64f..c3c40d17ea 100644 +index 0eed87b64f..6d954d269f 100644 --- a/src/main/tls_listen.c +++ b/src/main/tls_listen.c -@@ -81,7 +81,7 @@ static void tls_socket_close(rad_listen_t *listener) +@@ -81,54 +81,96 @@ static void tls_socket_close(rad_listen_t *listener) /* * Tell the event handler that an FD has disappeared. */ - DEBUG("Client has closed connection"); -+ DEBUG("(TLS) Client has closed connection"); ++ DEBUG("(TLS) Closing connection"); radius_update_listener(listener); /* -@@ -102,11 +102,11 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re - p = sock->ssn->dirty_out.data; +- * Do NOT free the listener here. It's in use by ++ * Do NOT free the listener here. It may be in use by + * a request, and will need to hang around until + * all of the requests are done. + * +- * It is instead free'd in remove_from_request_hash() ++ * It is instead free'd when all of the requests using it ++ * are done. + */ + } - while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { +-static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *request) ++static void tls_write_available(fr_event_list_t *el, int sock, void *ctx); ++ ++static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener) + { +- uint8_t *p; + ssize_t rcode; + listen_socket_t *sock = listener->data; + +- p = sock->ssn->dirty_out.data; +- +- while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { - RDEBUG3("Writing to socket %d", request->packet->sockfd); - rcode = write(request->packet->sockfd, p, -+ RDEBUG3("(TLS) Writing to socket %d", listener->fd); -+ rcode = write(listener->fd, p, - (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); - if (rcode <= 0) { +- (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); +- if (rcode <= 0) { - RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno)); -+ RDEBUG("(TLS) Error writing to socket: %s", fr_syserror(errno)); ++ /* ++ * It's not writable, so we don't bother writing to it. ++ */ ++ if (listener->blocked) return 0; - tls_socket_close(listener); +- tls_socket_close(listener); ++ /* ++ * Write as much as possible. ++ */ ++ rcode = write(listener->fd, sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); ++ if (rcode <= 0) { ++#ifdef EWOULDBLOCK ++ /* ++ * Writing to the socket would cause it to block. ++ * As a result, we just mark it as "don't use" ++ * until such time as it becomes writable. ++ */ ++ if (errno == EWOULDBLOCK) { ++ proxy_listener_freeze(listener, tls_write_available); return 0; -@@ -118,8 +118,6 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re + } +- p += rcode; ++#endif ++ ++ ++ ERROR("(TLS) Error writing to socket: %s", fr_syserror(errno)); ++ ++ tls_socket_close(listener); ++ return -1; + } - return 1; +- sock->ssn->dirty_out.used = 0; ++ /* ++ * All of the data was written. It's fine. ++ */ ++ if ((size_t) rcode == sock->ssn->dirty_out.used) { ++ sock->ssn->dirty_out.used = 0; ++ return 0; ++ } + +- return 1; ++ /* ++ * Move the data to the start of the buffer. ++ * ++ * Yes, this is horrible. But doing this means that we ++ * don't have to modify the rest of the code which mangles dirty_out, and assumes that the write offset is always &data[used]. ++ */ ++ memmove(&sock->ssn->dirty_out.data[0], &sock->ssn->dirty_out.data[rcode], sock->ssn->dirty_out.used - rcode); ++ sock->ssn->dirty_out.used -= rcode; ++ ++ return 0; ++} ++ ++static void tls_write_available(UNUSED fr_event_list_t *el, UNUSED int fd, void *ctx) ++{ ++ rad_listen_t *listener = ctx; ++ listen_socket_t *sock = listener->data; ++ ++ proxy_listener_thaw(listener); ++ ++ PTHREAD_MUTEX_LOCK(&sock->mutex); ++ (void) tls_socket_write(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); } -- -- + + static int tls_socket_recv(rad_listen_t *listener) { - bool doing_init = false; -@@ -165,7 +163,7 @@ static int tls_socket_recv(rad_listen_t *listener) +- bool doing_init = false; ++ bool doing_init = false, already_read = false; + ssize_t rcode; + RADIUS_PACKET *packet; + REQUEST *request; + listen_socket_t *sock = listener->data; + fr_tls_status_t status; +- RADCLIENT *client = sock->client; + + if (!sock->packet) { + sock->packet = rad_alloc(sock, false); +@@ -165,7 +207,7 @@ static int tls_socket_recv(rad_listen_t *listener) rad_assert(sock->ssn == NULL); sock->ssn = tls_new_session(sock, listener->tls, sock->request, @@ -5715,15 +12418,16 @@ index 0eed87b64f..c3c40d17ea 100644 if (!sock->ssn) { TALLOC_FREE(sock->request); sock->packet = NULL; -@@ -175,6 +173,7 @@ static int tls_socket_recv(rad_listen_t *listener) - SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); +@@ -176,6 +218,8 @@ static int tls_socket_recv(rad_listen_t *listener) SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs); SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock); -+ sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */ ++ sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */ ++ doing_init = true; } -@@ -186,7 +185,18 @@ static int tls_socket_recv(rad_listen_t *listener) + +@@ -186,7 +230,18 @@ static int tls_socket_recv(rad_listen_t *listener) request = sock->request; @@ -5743,7 +12447,7 @@ index 0eed87b64f..c3c40d17ea 100644 PTHREAD_MUTEX_LOCK(&sock->mutex); /* -@@ -195,9 +205,9 @@ static int tls_socket_recv(rad_listen_t *listener) +@@ -195,33 +250,38 @@ static int tls_socket_recv(rad_listen_t *listener) * the socket. */ if (SSL_pending(sock->ssn->ssl)) { @@ -5754,32 +12458,56 @@ index 0eed87b64f..c3c40d17ea 100644 + goto check_for_setup; } - rcode = read(request->packet->sockfd, -@@ -205,14 +215,14 @@ static int tls_socket_recv(rad_listen_t *listener) - sizeof(sock->ssn->dirty_in.data)); - if ((rcode < 0) && (errno == ECONNRESET)) { - do_close: +- rcode = read(request->packet->sockfd, +- sock->ssn->dirty_in.data, +- sizeof(sock->ssn->dirty_in.data)); +- if ((rcode < 0) && (errno == ECONNRESET)) { +- do_close: - DEBUG("Closing TLS socket from client port %u", sock->other_port); -+ DEBUG("(TLS) Closing socket from client port %u", sock->other_port); - tls_socket_close(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - return 0; - } +- tls_socket_close(listener); +- PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- return 0; +- } ++ if (!already_read) { ++ rcode = read(request->packet->sockfd, ++ sock->ssn->dirty_in.data, ++ sizeof(sock->ssn->dirty_in.data)); ++ if ((rcode < 0) && (errno == ECONNRESET)) { ++ do_close: ++ DEBUG("(TLS) Closing socket from client port %u", sock->other_port); ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } - if (rcode < 0) { +- if (rcode < 0) { - RDEBUG("Error reading TLS socket: %s", fr_syserror(errno)); -+ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); - goto do_close; - } +- goto do_close; +- } ++ if (rcode < 0) { ++ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); ++ goto do_close; ++ } -@@ -222,23 +232,23 @@ static int tls_socket_recv(rad_listen_t *listener) - if (rcode == 0) goto do_close; +- /* +- * Normal socket close. +- */ +- if (rcode == 0) goto do_close; ++ /* ++ * Normal socket close. ++ */ ++ if (rcode == 0) { ++ RDEBUG("(TLS) Client has closed the TCP connection"); ++ goto do_close; ++ } + +- sock->ssn->dirty_in.used = rcode; ++ sock->ssn->dirty_in.used = rcode; ++ } - sock->ssn->dirty_in.used = rcode; -- dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); - /* +@@ -229,16 +289,17 @@ static int tls_socket_recv(rad_listen_t *listener) * Catch attempts to use non-SSL. */ if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) { @@ -5799,21 +12527,26 @@ index 0eed87b64f..c3c40d17ea 100644 goto do_close; } -@@ -252,10 +262,55 @@ static int tls_socket_recv(rad_listen_t *listener) - } - - /* -- * FIXME: Run the request through a virtual -- * server in order to see if we like the -- * certificate presented by the client. +@@ -246,24 +307,93 @@ static int tls_socket_recv(rad_listen_t *listener) + * More ACK data to send. Do so. + */ + if (sock->ssn->dirty_out.used > 0) { +- tls_socket_write(listener, request); ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ tls_socket_write(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } ++ ++ /* + * If SSL handshake still isn't finished, then there + * is more data to read. Release the mutex and + * return so this function will be called again + */ + if (!SSL_is_init_finished(sock->ssn->ssl)) { -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 0; -+ } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } + } + + /* @@ -5839,18 +12572,22 @@ index 0eed87b64f..c3c40d17ea 100644 + * Fake out a Status-Server packet, which + * does NOT have a Message-Authenticator, + * or any other contents. - */ ++ */ + request->packet->code = PW_CODE_STATUS_SERVER; + request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); + request->packet->data[0] = PW_CODE_STATUS_SERVER; + request->packet->data[3] = 20; ++ request->listener = listener; + sock->state = LISTEN_TLS_CHECKING; + PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ -+ /* + + /* +- * FIXME: Run the request through a virtual +- * server in order to see if we like the +- * certificate presented by the client. + * Don't read from the socket until the request + * returns. -+ */ + */ + listener->status = RAD_LISTEN_STATUS_PAUSE; + radius_update_listener(listener); + @@ -5858,16 +12595,38 @@ index 0eed87b64f..c3c40d17ea 100644 } /* -@@ -263,7 +318,7 @@ static int tls_socket_recv(rad_listen_t *listener) + * Try to get application data. */ get_application_data: ++ /* ++ * More data to send. Do so. ++ */ ++ if (sock->ssn->dirty_out.used > 0) { ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ rcode = tls_socket_write(listener); ++ if (rcode < 0) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return rcode; ++ } ++ } ++ status = tls_application_data(sock->ssn, request); - RDEBUG("Application data status %d", status); + RDEBUG3("(TLS) Application data status %d", status); ++ ++ /* ++ * Some kind of failure. Close the socket. ++ */ ++ if (status == FR_TLS_FAIL) { ++ DEBUG("(TLS) Unable to recover from TLS error, closing socket from client port %u", sock->other_port); ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } if (status == FR_TLS_MORE_FRAGMENTS) { PTHREAD_MUTEX_UNLOCK(&sock->mutex); -@@ -275,6 +330,16 @@ get_application_data: +@@ -275,6 +405,16 @@ get_application_data: return 0; } @@ -5884,7 +12643,7 @@ index 0eed87b64f..c3c40d17ea 100644 /* * We now have a bunch of application data. */ -@@ -286,7 +351,7 @@ get_application_data: +@@ -286,7 +426,7 @@ get_application_data: */ if ((sock->ssn->clean_out.used < 20) || (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) { @@ -5893,8 +12652,12 @@ index 0eed87b64f..c3c40d17ea 100644 sock->ssn->clean_out.used, (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]); goto do_close; -@@ -301,7 +366,7 @@ get_application_data: +@@ -299,9 +439,11 @@ 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"); @@ -5902,7 +12665,7 @@ index 0eed87b64f..c3c40d17ea 100644 PTHREAD_MUTEX_LOCK(&sock->mutex); tls_socket_close(listener); PTHREAD_MUTEX_UNLOCK(&sock->mutex); -@@ -315,7 +380,7 @@ get_application_data: +@@ -315,7 +457,7 @@ get_application_data: char host_ipaddr[128]; if (is_radius_code(packet->code)) { @@ -5911,7 +12674,7 @@ index 0eed87b64f..c3c40d17ea 100644 fr_packet_codes[packet->code], inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, -@@ -323,7 +388,7 @@ get_application_data: +@@ -323,7 +465,7 @@ get_application_data: packet->src_port, packet->id, (int) packet->data_len); } else { @@ -5920,7 +12683,16 @@ index 0eed87b64f..c3c40d17ea 100644 inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, host_ipaddr, sizeof(host_ipaddr)), -@@ -359,6 +424,7 @@ redo: +@@ -333,8 +475,6 @@ get_application_data: + } + } + +- FR_STATS_INC(auth, total_requests); +- + return 1; + } + +@@ -359,6 +499,7 @@ redo: rad_assert(client != NULL); packet = talloc_steal(NULL, sock->packet); @@ -5928,7 +12700,7 @@ index 0eed87b64f..c3c40d17ea 100644 sock->packet = NULL; /* -@@ -391,8 +457,26 @@ redo: +@@ -391,8 +532,26 @@ redo: break; #endif @@ -5956,7 +12728,7 @@ index 0eed87b64f..c3c40d17ea 100644 FR_STATS_INC(auth, total_unknown_types); WARN("Ignoring Status-Server request due to security configuration"); rad_free(&packet); -@@ -405,7 +489,7 @@ redo: +@@ -405,7 +564,7 @@ redo: bad_packet: FR_STATS_INC(auth, total_unknown_types); @@ -5965,7 +12737,7 @@ index 0eed87b64f..c3c40d17ea 100644 packet->code, client->shortname, packet->src_port); rad_free(&packet); return 0; -@@ -432,7 +516,7 @@ redo: +@@ -432,7 +591,7 @@ redo: int peek = SSL_peek(sock->ssn->ssl, buf, 1); if (peek > 0) { @@ -5974,7 +12746,7 @@ index 0eed87b64f..c3c40d17ea 100644 goto redo; } } -@@ -455,6 +539,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) +@@ -455,6 +614,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; @@ -6009,7 +12781,195 @@ index 0eed87b64f..c3c40d17ea 100644 /* * Accounting reject's are silently dropped. * -@@ -727,6 +839,15 @@ int proxy_tls_recv(rad_listen_t *listener) +@@ -507,38 +694,43 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) + if (sock->ssn->dirty_out.used > 0) { + dump_hex("WRITE TO SSL", sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); + +- tls_socket_write(listener, request); ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ tls_socket_write(listener); + } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + + return 0; + } + +-static int try_connect(tls_session_t *ssn) ++static int try_connect(listen_socket_t *sock) + { + int ret; +- ret = SSL_connect(ssn->ssl); +- if (ret < 0) { +- switch (SSL_get_error(ssn->ssl, ret)) { +- default: +- break; ++ time_t now; + ++ now = time(NULL); ++ if ((sock->opened + sock->connect_timeout) < now) { ++ tls_error_io_log(NULL, sock->ssn, 0, "Timeout in SSL_connect"); ++ return -1; ++ } + ++ ret = SSL_connect(sock->ssn->ssl); ++ if (ret <= 0) { ++ switch (SSL_get_error(sock->ssn->ssl, ret)) { ++ default: ++ tls_error_io_log(NULL, sock->ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); ++ return -1; + + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) SSL_connect() returned WANT_READ"); ++ return 2; ++ + case SSL_ERROR_WANT_WRITE: +- ssn->connected = false; +- return 0; ++ DEBUG3("(TLS) SSL_connect() returned WANT_WRITE"); ++ return 2; + } + } + +- if (ret <= 0) { +- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); +- talloc_free(ssn); +- +- return -1; +- } +- ++ sock->ssn->connected = true; + return 1; + } + +@@ -564,15 +756,27 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + listen_socket_t *sock = listener->data; + + if (!sock->ssn->connected) { +- rcode = try_connect(sock->ssn); +- if (rcode == 0) return 0; ++ rcode = try_connect(sock); ++ if (rcode <= 0) return rcode; + +- if (rcode < 0) { +- SSL_shutdown(sock->ssn->ssl); +- return -1; +- } ++ if (rcode == 2) return 0; /* more negotiation needed */ ++ } + +- sock->ssn->connected = true; ++ if (sock->ssn->clean_out.used) { ++ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); ++ /* ++ * Write to SSL. ++ */ ++ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); ++ if (rcode > 0) { ++ if ((size_t) rcode < sock->ssn->clean_out.used) { ++ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, ++ sock->ssn->clean_out.used - rcode); ++ sock->ssn->clean_out.used -= rcode; ++ } else { ++ sock->ssn->clean_out.used = 0; ++ } ++ } + } + + /* +@@ -589,9 +793,14 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + if (rcode <= 0) { + int err = SSL_get_error(sock->ssn->ssl, rcode); + switch (err) { ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ return 0; ++ + case SSL_ERROR_WANT_WRITE: +- return 0; /* do some more work later */ ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); ++ return 0; + + case SSL_ERROR_ZERO_RETURN: + /* remote end sent close_notify, send one back */ +@@ -602,9 +811,12 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + do_close: + return -1; + +- default: +- tls_error_log(NULL, "Failed in proxy receive"); ++ case SSL_ERROR_SSL: ++ DEBUG("(TLS) Home server has closed the connection"); ++ goto do_close; + ++ default: ++ tls_error_log(NULL, "Failed in proxy receive with OpenSSL error %d", err); + goto do_close; + } + } +@@ -641,16 +853,28 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + rcode = SSL_read(sock->ssn->ssl, data + sock->partial, + length - sock->partial); + if (rcode <= 0) { +- switch (SSL_get_error(sock->ssn->ssl, rcode)) { ++ int err = SSL_get_error(sock->ssn->ssl, rcode); ++ switch (err) { ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ return 0; ++ + case SSL_ERROR_WANT_WRITE: ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); + return 0; + + case SSL_ERROR_ZERO_RETURN: + /* remote end sent close_notify, send one back */ + SSL_shutdown(sock->ssn->ssl); + goto do_close; ++ ++ case SSL_ERROR_SSL: ++ DEBUG("(TLS) Home server has closed the connection"); ++ goto do_close; ++ + default: ++ DEBUG("(TLS) Unexpected OpenSSL error %d", err); + goto do_close; + } + } +@@ -683,18 +907,18 @@ int proxy_tls_recv(rad_listen_t *listener) + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + ++ rad_assert(sock->ssn != NULL); ++ + DEBUG3("Proxy SSL socket has data to read"); + PTHREAD_MUTEX_LOCK(&sock->mutex); + data_len = proxy_tls_read(listener); +- PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- + if (data_len < 0) { +- DEBUG("Closing TLS socket to home server"); +- PTHREAD_MUTEX_LOCK(&sock->mutex); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ DEBUG("Closing TLS socket to home server"); + return 0; + } ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); + + if (data_len == 0) return 0; /* not done yet */ + +@@ -713,6 +937,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? + */ +@@ -727,6 +953,14 @@ int proxy_tls_recv(rad_listen_t *listener) break; #endif @@ -6019,23 +12979,137 @@ index 0eed87b64f..c3c40d17ea 100644 + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + break; -+ +#endif + default: /* * FIXME: Update MIB for packet types? -@@ -765,8 +886,8 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) +@@ -765,42 +999,111 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) * if there's no packet, encode it here. */ if (!request->proxy->data) { - request->proxy_listener->encode(request->proxy_listener, - request); ++ request->reply->tls = true; + request->proxy_listener->proxy_encode(request->proxy_listener, + request); } ++ rad_assert(sock->ssn != NULL); ++ if (!sock->ssn->connected) { + PTHREAD_MUTEX_LOCK(&sock->mutex); +- rcode = try_connect(sock->ssn); ++ rcode = try_connect(sock); ++ if (rcode <= 0) { ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return rcode; ++ } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- if (rcode == 0) return 0; + +- if (rcode < 0) { +- SSL_shutdown(sock->ssn->ssl); +- return -1; +- } ++ /* ++ * More negotiation is needed, but remember to ++ * save this packet to an intermediate buffer. ++ * Once the SSL connection is established, the ++ * later code writes the packet to the ++ * connection. ++ */ ++ if (rcode == 2) { ++ PTHREAD_MUTEX_LOCK(&sock->mutex); ++ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ RERROR("(TLS) Too much data buffered during SSL_connect()"); ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ radius_update_listener(listener); ++ return -1; ++ } + +- sock->ssn->connected = true; ++ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); ++ sock->ssn->clean_out.used += request->proxy->data_len; ++ RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); ++ ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } + } + + DEBUG3("Proxy is writing %u bytes to SSL", + (unsigned int) request->proxy->data_len); + PTHREAD_MUTEX_LOCK(&sock->mutex); +- rcode = SSL_write(sock->ssn->ssl, request->proxy->data, +- request->proxy->data_len); ++ ++ /* ++ * We may have previously cached data on SSL_connect(), which now needs to be written to the home server. ++ */ ++ if (sock->ssn->clean_out.used > 0) { ++ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ RERROR("(TLS) Too much data buffered after SSL_connect()"); ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ radius_update_listener(listener); ++ return -1; ++ } ++ ++ /* ++ * Add in our packet. ++ */ ++ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); ++ sock->ssn->clean_out.used += request->proxy->data_len; ++ ++ /* ++ * Write to SSL. ++ */ ++ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); ++ ++ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); ++ if (rcode > 0) { ++ if ((size_t) rcode < sock->ssn->clean_out.used) { ++ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, ++ sock->ssn->clean_out.used - rcode); ++ sock->ssn->clean_out.used -= rcode; ++ } else { ++ sock->ssn->clean_out.used = 0; ++ } ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 1; ++ } ++ } else { ++ rcode = SSL_write(sock->ssn->ssl, request->proxy->data, ++ request->proxy->data_len); ++ } + if (rcode < 0) { + int err; + + err = ERR_get_error(); + switch (err) { + case SSL_ERROR_NONE: ++ break; ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ break; ++ + case SSL_ERROR_WANT_WRITE: +- break; /* let someone else retry */ ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); ++ break; + + default: +- tls_error_log(NULL, "Failed in proxy send"); +- DEBUG("Closing TLS socket to home server"); ++ tls_error_log(NULL, "Failed in proxy send with OpenSSL error %d", err); ++ DEBUG("(TLS) Closing socket to home server"); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c index 9fed166e62..1cd73ff246 100644 --- a/src/modules/proto_dhcp/rlm_dhcp.c diff --git a/freeradius.spec b/freeradius.spec index c305964..e88d7f2 100644 --- a/freeradius.spec +++ b/freeradius.spec @@ -1,7 +1,7 @@ Summary: High-performance and highly configurable free RADIUS server Name: freeradius Version: 3.0.21 -Release: 41%{?dist} +Release: 42%{?dist} License: GPLv2+ and LGPLv2+ URL: http://www.freeradius.org/ @@ -864,6 +864,10 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest %changelog +* Wed Jul 10 2024 Antonio Torres - 3.0.21-42 +- Backport fixes for BlastRADIUS CVE + Resolves: RHEL-46567 + * Wed Apr 24 2024 Antonio Torres - 3.0.21-41 - Rebuild for OpenSSL rebase to 3.2.1 Resolves: RHEL-33857