summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2024-08-01 14:06:58 +0000
committerCoprDistGit <infra@openeuler.org>2024-08-01 14:06:58 +0000
commitfa0594f4021dbf53966e167cf44c1bb84df5bb23 (patch)
tree3c991fcabf18a0e314a10edf337db266e504af11
parent72d830c7e64b038eb96c0f36e0c1a0ab225238e3 (diff)
automatic import of freeradiusopeneuler24.03_LTS
-rw-r--r--.gitignore1
-rw-r--r--freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch60
-rw-r--r--freeradius-Backport-OpenSSL3-fixes.patch18790
-rw-r--r--freeradius-Fix-resource-hard-limit-error.patch32
-rw-r--r--freeradius-Use-system-crypto-policy-by-default.patch86
-rw-r--r--freeradius-bootstrap-create-only.patch89
-rw-r--r--freeradius-bootstrap-make-permissions.patch29
-rw-r--r--freeradius-bootstrap-pass-noenc-to-certificate-generation.patch136
-rw-r--r--freeradius-fix-crash-on-invalid-abinary-data.patch47
-rw-r--r--freeradius-fix-crash-unknown-eap-sim.patch115
-rw-r--r--freeradius-fix-python3-library-suffix.patch635
-rw-r--r--freeradius-ldap-infinite-timeout-on-starttls.patch31
-rw-r--r--freeradius-logrotate56
-rw-r--r--freeradius-no-buildtime-cert-gen.patch104
-rw-r--r--freeradius-pam-conf6
-rw-r--r--freeradius-tmpfiles.conf2
-rw-r--r--freeradius.spec2617
-rw-r--r--freeradius.sysusers3
-rw-r--r--radiusd.service15
-rw-r--r--sources1
20 files changed, 22855 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..5e87c5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/freeradius-server-3.0.21.tar.bz2
diff --git a/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch b/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch
new file mode 100644
index 0000000..6b2329b
--- /dev/null
+++ b/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch
@@ -0,0 +1,60 @@
+From 958f470cda2ba8943f02f13d1b46f357f92d9639 Mon Sep 17 00:00:00 2001
+From: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+Date: Mon, 8 Sep 2014 12:32:13 +0300
+Subject: [PATCH] Adjust configuration to fit Red Hat specifics
+
+---
+ raddb/mods-available/eap | 4 ++--
+ raddb/radiusd.conf.in | 7 +++----
+ 2 files changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap
+index 2621e183c..94494b2c6 100644
+--- a/raddb/mods-available/eap
++++ b/raddb/mods-available/eap
+@@ -533,7 +533,7 @@
+ # You should also delete all of the files
+ # in the directory when the server starts.
+ #
+- # tmpdir = /tmp/radiusd
++ # tmpdir = /var/run/radiusd/tmp
+
+ # The command used to verify the client cert.
+ # We recommend using the OpenSSL command-line
+@@ -548,7 +548,7 @@
+ # deleted by the server when the command
+ # returns.
+ #
+- # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}"
++ # client = "/usr/bin/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}"
+ }
+
+ # OCSP Configuration
+diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in
+index a83c1f687..e500cf97b 100644
+--- a/raddb/radiusd.conf.in
++++ b/raddb/radiusd.conf.in
+@@ -70,8 +70,7 @@ certdir = ${confdir}/certs
+ cadir = ${confdir}/certs
+ run_dir = ${localstatedir}/run/${name}
+
+-# Should likely be ${localstatedir}/lib/radiusd
+-db_dir = ${raddbdir}
++db_dir = ${localstatedir}/lib/radiusd
+
+ #
+ # libdir: Where to find the rlm_* modules.
+@@ -398,8 +397,8 @@ security {
+ # member. This can allow for some finer-grained access
+ # controls.
+ #
+-# user = radius
+-# group = radius
++ user = radiusd
++ group = radiusd
+
+ # Core dumps are a bad thing. This should only be set to
+ # 'yes' if you're debugging a problem with the server.
+--
+2.13.2
+
diff --git a/freeradius-Backport-OpenSSL3-fixes.patch b/freeradius-Backport-OpenSSL3-fixes.patch
new file mode 100644
index 0000000..92d22c1
--- /dev/null
+++ b/freeradius-Backport-OpenSSL3-fixes.patch
@@ -0,0 +1,18790 @@
+Date: Tue, 11 Jan 2022
+Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.x
+
+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>
+
+[antorres@redhat.com]: these changes include the macro WITH_FIPS, which allows FreeRADIUS
+to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this macro on the specfile.
+[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 | 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 | 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/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 | 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 +-
+ src/modules/rlm_eap/libeap/mppe_keys.c | 211 +-
+ src/modules/rlm_eap/radeapclient.c | 8 +
+ src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c | 51 +-
+ .../rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c | 64 +-
+ src/modules/rlm_eap/types/rlm_eap_peap/peap.c | 22 +-
+ .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c | 21 +-
+ src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h | 190 ++
+ src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c | 779 +++++---
+ src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h | 16 +-
+ .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c | 508 ++++-
+ .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h | 2 +
+ .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c | 52 +-
+ .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h | 5 +
+ .../rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c | 20 +-
+ src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c | 25 +-
+ src/modules/rlm_exec/rlm_exec.c | 4 +-
+ src/modules/rlm_expr/rlm_expr.c | 115 ++
+ src/modules/rlm_files/rlm_files.c | 2 +-
+ src/modules/rlm_ldap/ldap.h | 4 +
+ src/modules/rlm_mschap/rlm_mschap.c | 99 +-
+ src/modules/rlm_otp/otp_mppe.c | 16 +-
+ src/modules/rlm_otp/otp_radstate.c | 3 +-
+ src/modules/rlm_rest/rest.c | 107 +-
+ src/modules/rlm_rest/rest.h | 18 +
+ src/modules/rlm_rest/rlm_rest.c | 12 +
+ src/modules/rlm_wimax/milenage.c | 642 +++++++
+ src/modules/rlm_wimax/milenage.h | 128 ++
+ src/modules/rlm_wimax/rlm_wimax.c | 429 ++++-
+ src/tests/keywords/md4 | 58 +
+ 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
++++ b/raddb/mods-available/eap
+@@ -281,7 +281,7 @@ eap {
+ #
+ # openssl dhparam -out certs/dh 2048
+ #
+- dh_file = ${certdir}/dh
++ # dh_file = ${certdir}/dh
+
+ # If your system doesn't have /dev/urandom,
+ # you will need to create this file, and
+@@ -392,8 +392,8 @@ eap {
+ # tls_max_version.
+ #
+ # disable_tlsv1_2 = no
+- disable_tlsv1_1 = yes
+- disable_tlsv1 = yes
++ # disable_tlsv1_1 = yes
++ # disable_tlsv1 = yes
+
+ # 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
++++ b/raddb/sites-available/tls
+@@ -468,6 +468,14 @@ home_server tls {
+ # this configuration item.
+ ca_file = ${cadir}/ca.pem
+
++ # In previous versions, outbound RadSec connections
++ # would put the home server certificate into the
++ # TLS-Client-Cert* attributes. Set this configuration
++ # item to "yes" in order to have the home server
++ # certificates placed into the "TLS-Cert-*" attributes.
++ #
++# fix_cert_order = yes
++
+ #
+ # 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
++++ b/share/dictionary.freeradius.internal
+@@ -148,7 +148,7 @@ VALUE EAP-IKEv2-IDType DER_ASN1_GN 10
+ VALUE EAP-IKEv2-IDType KEY_ID 11
+
+ ATTRIBUTE EAP-IKEv2-ID 1104 string
+-ATTRIBUTE EAP-IKEv2-Secret 1105 string
++ATTRIBUTE EAP-IKEv2-Secret 1105 string secret
+ ATTRIBUTE EAP-IKEv2-AuthType 1106 integer
+
+ VALUE EAP-IKEv2-AuthType none 0
+@@ -196,7 +196,7 @@ ATTRIBUTE FreeRADIUS-Client-Require-MA 1122 integer
+ VALUE FreeRADIUS-Client-Require-MA no 0
+ VALUE FreeRADIUS-Client-Require-MA yes 1
+
+-ATTRIBUTE FreeRADIUS-Client-Secret 1123 string
++ATTRIBUTE FreeRADIUS-Client-Secret 1123 string secret
+ ATTRIBUTE FreeRADIUS-Client-Shortname 1124 string
+ ATTRIBUTE FreeRADIUS-Client-NAS-Type 1125 string
+ ATTRIBUTE FreeRADIUS-Client-Virtual-Server 1126 string
+@@ -254,6 +254,7 @@ ATTRIBUTE FreeRADIUS-Response-Delay-USec 1155 integer
+
+ ATTRIBUTE REST-HTTP-Header 1160 string
+ ATTRIBUTE REST-HTTP-Body 1161 string
++ATTRIBUTE REST-HTTP-Status-Code 1162 integer
+
+ ATTRIBUTE Cache-Expires 1170 date
+ ATTRIBUTE Cache-Created 1171 date
+@@ -277,7 +278,24 @@ ATTRIBUTE SSHA2-256-Password 1178 octets
+ ATTRIBUTE SSHA2-384-Password 1179 octets
+ ATTRIBUTE SSHA2-512-Password 1180 octets
+
++ATTRIBUTE PBKDF2-Password 1181 octets
++ATTRIBUTE SSHA3-224-Password 1182 octets
++ATTRIBUTE SSHA3-256-Password 1183 octets
++ATTRIBUTE SSHA3-384-Password 1184 octets
++ATTRIBUTE SSHA3-512-Password 1185 octets
++
+ ATTRIBUTE MS-CHAP-Peer-Challenge 1192 octets
++ATTRIBUTE Home-Server-Name 1193 string
++ATTRIBUTE Originating-Realm-Key 1194 string
++ATTRIBUTE Proxy-To-Originating-Realm 1195 string
++
++ATTRIBUTE TOTP-Secret 1194 string # base32 encoded
++ATTRIBUTE TOTP-Key 1195 octets # raw key
++ATTRIBUTE TOTP-Password 1196 string
++
++ATTRIBUTE Proxy-Tunneled-Request-As-EAP 1197 integer
++VALUE Proxy-Tunneled-Request-As-EAP No 0
++VALUE Proxy-Tunneled-Request-As-EAP Yes 1
+
+ #
+ # Range: 1200-1279
+@@ -318,6 +336,10 @@ ATTRIBUTE EAP-Sim-Algo-Version 1216 integer
+ ATTRIBUTE Outer-Realm-Name 1218 string
+ ATTRIBUTE Inner-Realm-Name 1219 string
+
++ATTRIBUTE EAP-Pwd-Password-Hash 1220 octets
++ATTRIBUTE EAP-Pwd-Password-Salt 1221 octets
++ATTRIBUTE EAP-Pwd-Password-Prep 1222 byte
++
+ #
+ # Range: 1280 - 1535
+ # EAP-type specific attributes
+@@ -516,6 +538,11 @@ ATTRIBUTE Tmp-Cast-IPv4Prefix 1870 ipv4prefix
+ # these attributes.
+ #
+ ATTRIBUTE WiMAX-MN-NAI 1900 string
++ATTRIBUTE WiMAX-SIM-Ki 1901 octets
++ATTRIBUTE WiMAX-SIM-OPc 1902 octets
++ATTRIBUTE WiMAX-SIM-AMF 1903 octets
++ATTRIBUTE WiMAX-SIM-SQN 1904 octets
++ATTRIBUTE WiMAX-SIM-RAND 1905 octets
+
+ ATTRIBUTE TLS-Cert-Serial 1910 string
+ ATTRIBUTE TLS-Cert-Expiration 1911 string
+@@ -526,7 +553,7 @@ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Email 1915 string
+ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Dns 1916 string
+ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Upn 1917 string
+ ATTRIBUTE TLS-Cert-Valid-Since 1918 string
+-# 1919: reserved for future cert attribute
++ATTRIBUTE TLS-Session-Information 1919 string
+ ATTRIBUTE TLS-Client-Cert-Serial 1920 string
+ ATTRIBUTE TLS-Client-Cert-Expiration 1921 string
+ ATTRIBUTE TLS-Client-Cert-Issuer 1922 string
+@@ -543,10 +570,19 @@ ATTRIBUTE TLS-Client-Cert-Subject-Alt-Name-Upn 1932 string
+ ATTRIBUTE TLS-PSK-Identity 1933 string
+ ATTRIBUTE TLS-Client-Cert-X509v3-Extended-Key-Usage-OID 1936 string
+ ATTRIBUTE TLS-Client-Cert-Valid-Since 1937 string
++ATTRIBUTE TLS-Cache-Method 1938 integer
++VALUE TLS-Cache-Method save 1
++VALUE TLS-Cache-Method load 2
++VALUE TLS-Cache-Method clear 3
++VALUE TLS-Cache-Method refresh 4
+
+-# 1938 - 1939: reserved for future cert attributes
+
+-# 1940 - 1949: reserved for TLS session caching, mostly in 4.0
++# 1939: reserved for future cert attributes
++
++# 1940 - 1959: reserved for TLS session caching, mostly in 4.0
++
++ATTRIBUTE TLS-Session-ID 1940 octets
++ATTRIBUTE TLS-Session-Data 1942 octets
+
+ # Set by EAP-TLS code
+ ATTRIBUTE TLS-OCSP-Cert-Valid 1943 integer
+@@ -560,8 +596,13 @@ ATTRIBUTE TLS-Cache-Filename 1946 string
+ ATTRIBUTE TLS-Session-Version 1947 string
+ ATTRIBUTE TLS-Session-Cipher-Suite 1948 string
+
++ATTRIBUTE TLS-Session-Cert-File 1949 string
++ATTRIBUTE TLS-Session-Cert-Private-Key-File 1950 string
++
++ATTRIBUTE TLS-Server-Name-Indication 1951 string
++
+ #
+-# Range: 1950-2099
++# Range: 1960-2099
+ # Free
+ #
+ # Range: 2100-2199
+@@ -650,6 +691,7 @@ VALUE Session-Type Local 1
+ VALUE Post-Auth-Type Local 1
+ VALUE Post-Auth-Type Reject 2
+ VALUE Post-Auth-Type Challenge 3
++VALUE Post-Auth-Type Client-Lost 4
+
+ #
+ # And Post-Proxy
+diff --git a/src/include/build.h b/src/include/build.h
+index 5da940c2b7..c5eaa45457 100644
+--- a/src/include/build.h
++++ b/src/include/build.h
+@@ -46,9 +46,13 @@ extern "C" {
+ * compiler.
+ */
+ #ifdef __GNUC__
+-# define CC_HINT(_x) __attribute__ ((_x))
++# define CC_HINT(...) __attribute__ ((__VA_ARGS__))
++# define likely(_x) __builtin_expect((_x), 1)
++# define unlikely(_x) __builtin_expect((_x), 0)
+ #else
+-# define CC_HINT(_x)
++# define CC_HINT(...)
++# define likely(_x) _x
++# define unlikely(_x) _x
+ #endif
+
+ #ifdef HAVE_ATTRIBUTE_BOUNDED
+@@ -57,6 +61,18 @@ extern "C" {
+ # define CC_BOUNDED(...)
+ #endif
+
++/*
++ * GCC uses __SANITIZE_ADDRESS__, clang uses __has_feature, which
++ * GCC complains about.
++ */
++#ifndef __SANITIZE_ADDRESS__
++#ifdef __has_feature
++#if __has_feature(address_sanitizer)
++#define __SANITIZE_ADDRESS__ (1)
++#endif
++#endif
++#endif
++
+ /*
+ * Macros to add pragmas
+ */
+@@ -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..1b975517b5 100644
+--- a/src/include/libradius.h
++++ b/src/include/libradius.h
+@@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$")
+ * Talloc memory allocation is used in preference to malloc throughout
+ * the libraries and server.
+ */
++#ifdef HAVE_WDOCUMENTATION
++DIAG_OFF(documentation)
++#endif
+ #include <talloc.h>
++#ifdef HAVE_WDOCUMENTATION
++DIAG_ON(documentation)
++#endif
+
+ /*
+ * Defines signatures for any missing functions.
+@@ -162,11 +168,6 @@ typedef void (*sig_t)(int);
+
+ #define PAD(_x, _y) (_y - ((_x) % _y))
+
+-#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1))
+-#define NEVER_RETURNS CC_HINT(noreturn)
+-#define UNUSED CC_HINT(unused)
+-#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */
+-
+ typedef struct attr_flags {
+ unsigned int is_unknown : 1; //!< Attribute number or vendor is unknown.
+ unsigned int is_tlv : 1; //!< Is a sub attribute.
+@@ -189,6 +190,10 @@ typedef struct attr_flags {
+
+ unsigned int compare : 1; //!< has a paircompare registered
+
++ unsigned int is_dup : 1; //!< is a duplicate of another attribute
++
++ unsigned int secret : 1; //!< is a secret thingy
++
+ uint8_t encrypt; //!< Ecryption method.
+ uint8_t length;
+ } ATTR_FLAGS;
+@@ -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);
+
+-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp);
++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value);
+ 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 +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);
++size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da);
++int dict_walk(fr_hash_table_walk_t callback, void *context);
+
+ void dict_attr_free(DICT_ATTR const **da);
+ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor);
+@@ -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 */
++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx);
+ 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 +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 *);
++void fr_pair_prepend(VALUE_PAIR **, VALUE_PAIR *);
+ 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 +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));
+-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from);
++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op);
+ 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 <freeradius-devel/packet.h>
+
+ #ifdef WITH_TCP
+diff --git a/src/include/listen.h b/src/include/listen.h
+index 4f50bbf808..2b4e02bd54 100644
+--- a/src/include/listen.h
++++ b/src/include/listen.h
+@@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE {
+ typedef enum RAD_LISTEN_STATUS {
+ RAD_LISTEN_STATUS_INIT = 0,
+ RAD_LISTEN_STATUS_KNOWN,
++ RAD_LISTEN_STATUS_PAUSE,
++ RAD_LISTEN_STATUS_RESUME,
+ RAD_LISTEN_STATUS_FROZEN,
+ RAD_LISTEN_STATUS_EOL,
+ RAD_LISTEN_STATUS_REMOVE_NOW
+@@ -68,24 +70,41 @@ struct rad_listen {
+ int fd;
+ char const *server;
+ int status;
+-#ifdef WITH_TCP
+ int count;
+- bool dual;
++#ifdef WITH_TCP
+ rbtree_t *children;
+ rad_listen_t *parent;
++
++ bool dual;
+ #endif
+ bool nodup;
+ bool synchronous;
++ 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;
+ rad_listen_send_t send;
++
++ /*
++ * We don't need a proxy_recv, because the main loop in
++ * process.c calls listener->recv(), and we don't know
++ * what kind of packet we're receiving until we receive
++ * it.
++ */
++ rad_listen_send_t proxy_send;
++
++
+ rad_listen_encode_t encode;
+ rad_listen_decode_t decode;
++ rad_listen_encode_t proxy_encode;
++ rad_listen_decode_t proxy_decode;
+ rad_listen_print_t print;
+
+ CONF_SECTION const *cs;
+@@ -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;
++ enum {
++ LISTEN_TLS_INIT = 0,
++ LISTEN_TLS_CHECKING,
++ LISTEN_TLS_SETUP,
++ LISTEN_TLS_RUNNING,
++ } state;
+ #endif
+
+ RADCLIENT_LIST *clients;
+diff --git a/src/include/md4.h b/src/include/md4.h
+index b7bdd6a15e..1492bd4a5c 100644
+--- a/src/include/md4.h
++++ b/src/include/md4.h
+@@ -26,6 +26,10 @@ RCSIDH(md4_h, "$Id$")
+
+ #include <string.h>
+
++#ifdef WITH_FIPS
++#undef HAVE_OPENSSL_MD4_H
++#endif
++
+ #ifdef HAVE_OPENSSL_MD4_H
+ # include <openssl/md4.h>
+ #endif
+@@ -71,14 +75,58 @@ void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx)
+ void fr_md4_transform(uint32_t buf[4], uint8_t const inc[MD4_BLOCK_LENGTH])
+ CC_BOUNDED(__size__, 1, 4, 4)
+ CC_BOUNDED(__minbytes__, 2, MD4_BLOCK_LENGTH);
++# define fr_md4_destroy(_x)
+ #else /* HAVE_OPENSSL_MD4_H */
++#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ USES_APPLE_DEPRECATED_API
+ # define FR_MD4_CTX MD4_CTX
+ # define fr_md4_init MD4_Init
+ # define fr_md4_update MD4_Update
+ # define fr_md4_final MD4_Final
+ # define fr_md4_transform MD4_Transform
+-#endif
++# define fr_md4_destroy(_x)
++#else
++#include <openssl/evp.h>
++
++/*
++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of
++ * the code too much.
++ */
++typedef struct FR_MD4_CTX {
++ EVP_MD_CTX *ctx;
++ EVP_MD const *md;
++ unsigned int len;
++} FR_MD4_CTX;
++
++static inline void fr_md4_init(FR_MD4_CTX *ctx)
++{
++ ctx->ctx = EVP_MD_CTX_new();
++// ctx->md = EVP_MD_fetch(NULL, "MD4", "provider=legacy");
++ ctx->md = EVP_md4();
++ ctx->len = MD4_DIGEST_LENGTH;
++
++ EVP_MD_CTX_set_flags(ctx->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
++ EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL);
++}
++
++static inline void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen)
++{
++ EVP_DigestUpdate(ctx->ctx, in, inlen);
++}
++
++static inline void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx)
++{
++ EVP_DigestFinal_ex(ctx->ctx, out, &(ctx->len));
++}
++
++static inline void fr_md4_destroy(FR_MD4_CTX *ctx)
++{
++ EVP_MD_CTX_destroy(ctx->ctx);
++// EVP_MD_free(ctx->md);
++}
++
++#endif /* OPENSSL3 */
++#endif /* HAVE_OPENSSL_MD4_H */
+
+ /* md4.c */
+ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen);
+diff --git a/src/include/md5.h b/src/include/md5.h
+index a44584564f..b7d571ac7b 100644
+--- a/src/include/md5.h
++++ b/src/include/md5.h
+@@ -26,6 +26,10 @@ RCSIDH(md5_h, "$Id$")
+
+ # include <string.h>
+
++#ifdef WITH_FIPS
++#undef HAVE_OPENSSL_MD5_H
++#endif
++
+ #ifdef HAVE_OPENSSL_MD5_H
+ # include <openssl/md5.h>
+ #endif
+@@ -68,14 +72,41 @@ void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx)
+ void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH])
+ CC_BOUNDED(__size__, 1, 4, 4)
+ CC_BOUNDED(__minbytes__, 2, MD5_BLOCK_LENGTH);
++# define fr_md5_destroy(_x)
++# define fr_md5_copy(_dst, _src) _dst = _src
+ #else /* HAVE_OPENSSL_MD5_H */
++#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ USES_APPLE_DEPRECATED_API
+ # define FR_MD5_CTX MD5_CTX
+ # define fr_md5_init MD5_Init
+ # define fr_md5_update MD5_Update
+ # define fr_md5_final MD5_Final
+ # define fr_md5_transform MD5_Transform
+-#endif
++# define fr_md5_copy(_dst, _src) _dst = _src
++# define fr_md5_destroy(_x)
++#else
++#include <openssl/evp.h>
++
++/*
++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of
++ * the code too much.
++ */
++typedef EVP_MD_CTX* FR_MD5_CTX;
++
++# define fr_md5_init(_ctx) \
++ do { \
++ *_ctx = EVP_MD_CTX_new(); \
++ EVP_MD_CTX_set_flags(*_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); \
++ EVP_DigestInit_ex(*_ctx, EVP_md5(), NULL); \
++ } while (0)
++# define fr_md5_update(_ctx, _str, _len) \
++ EVP_DigestUpdate(*_ctx, _str, _len)
++# define fr_md5_final(_out, _ctx) \
++ EVP_DigestFinal_ex(*_ctx, _out, NULL)
++# define fr_md5_destroy(_ctx) EVP_MD_CTX_destroy(*_ctx)
++# define fr_md5_copy(_dst, _src) EVP_MD_CTX_copy_ex(_dst, _src)
++#endif /* OPENSSL3 */
++#endif /* HAVE_OPENSSL_MD5_H */
+
+ /* 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
+--- /dev/null
++++ b/src/include/openssl3.h
+@@ -0,0 +1,109 @@
++/*
++ * 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
++ */
++#ifndef FR_OPENSSL3_H
++#define FR_OPENSSL3_H
++/**
++ * $Id$
++ *
++ * @file openssl3.h
++ * @brief Wrappers to shut up OpenSSL3
++ *
++ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
++ */
++
++RCSIDH(openssl3_h, "$Id$")
++
++/*
++ * The HMAC APIs are deprecated in OpenSSL3. We don't want to
++ * fill the code with ifdef's, so we define some horrific
++ * wrappers here.
++ *
++ * This file should be included AFTER all OpenSSL header files.
++ */
++#ifdef HAVE_OPENSSL_SSL_H
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++#include <string.h>
++#include <openssl/evp.h>
++#include <openssl/core_names.h>
++
++typedef struct {
++ EVP_MAC *mac;
++ EVP_MAC_CTX *ctx;
++} HMAC3_CTX;
++#define HMAC_CTX HMAC3_CTX
++
++#define HMAC_CTX_new HMAC3_CTX_new
++static inline HMAC3_CTX *HMAC3_CTX_new(void)
++{
++ HMAC3_CTX *h = calloc(1, sizeof(*h));
++
++ return h;
++}
++
++#define HMAC_Init_ex(_ctx, _key, _keylen, _md, _engine) HMAC3_Init_ex(_ctx, _key, _keylen, _md, _engine)
++static inline int HMAC3_Init_ex(HMAC3_CTX *ctx, const unsigned char *key, unsigned int keylen, const EVP_MD *md, UNUSED void *engine)
++{
++ OSSL_PARAM params[2], *p = params;
++ char const *name;
++ char *unconst;
++
++ ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
++ if (!ctx->mac) return 0;
++
++ ctx->ctx = EVP_MAC_CTX_new(ctx->mac);
++ if (!ctx->ctx) return 0;
++
++ name = EVP_MD_get0_name(md);
++ memcpy(&unconst, &name, sizeof(name)); /* const issues */
++
++ p[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, unconst, 0);
++ p[1] = OSSL_PARAM_construct_end();
++
++ return EVP_MAC_init(ctx->ctx, key, keylen, params);
++}
++
++#define HMAC_Update HMAC3_Update
++static inline int HMAC3_Update(HMAC3_CTX *ctx, const unsigned char *data, unsigned int datalen)
++{
++ return EVP_MAC_update(ctx->ctx, data, datalen);
++}
++
++#define HMAC_Final HMAC3_Final
++static inline int HMAC3_Final(HMAC3_CTX *ctx, unsigned char *out, unsigned int *len)
++{
++ size_t mylen = *len;
++
++ if (!EVP_MAC_final(ctx->ctx, out, &mylen, mylen)) return 0;
++
++ *len = mylen;
++ return 1;
++}
++
++#define HMAC_CTX_free HMAC3_CTX_free
++static inline void HMAC3_CTX_free(HMAC3_CTX *ctx)
++{
++ if (!ctx) return;
++
++ EVP_MAC_free(ctx->mac);
++ EVP_MAC_CTX_free(ctx->ctx);
++ free(ctx);
++}
++
++#define HMAC_CTX_set_flags(_ctx, _flags)
++
++#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 <freeradius-devel/build.h>
++#include <freeradius-devel/missing.h>
++
++#ifdef HAVE_SYS_UN_H
++# include <sys/un.h>
++/*
++ * 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
++++ b/src/include/tls-h
+@@ -67,7 +67,7 @@ typedef enum {
+ } fr_tls_status_t;
+ extern FR_NAME_NUMBER const fr_tls_status_table[];
+
+-#define MAX_RECORD_SIZE 16384
++#define MAX_RECORD_SIZE 65536
+
+ /*
+ * A single TLS record may be up to 16384 octets in length, but a
+@@ -89,12 +89,12 @@ extern FR_NAME_NUMBER const fr_tls_status_table[];
+ * or configure TLS not to exceed MAX_RECORD_SIZE.
+ */
+ typedef struct _record_t {
+- uint8_t data[MAX_RECORD_SIZE];
+ size_t used;
++ uint8_t data[MAX_RECORD_SIZE];
+ } record_t;
+
+ typedef struct _tls_info_t {
+- int origin;
++ int origin; // 0 - received (from peer), 1 - sending (to peer)
+ int content_type;
+ uint8_t handshake_type;
+ uint8_t alert_level;
+@@ -103,7 +103,6 @@ typedef struct _tls_info_t {
+
+ char info_description[256];
+ size_t record_len;
+- int version;
+ } tls_info_t;
+
+ #if OPENSSL_VERSION_NUMBER < 0x10001000L
+@@ -139,6 +138,9 @@ typedef struct _tls_session_t {
+ bool invalid_hb_used; //!< Whether heartbleed attack was detected.
+ bool connected; //!< whether the outgoing socket is connected
+ bool is_init_finished; //!< whether or not init is finished
++ bool client_cert_ok; //!< whether or not we validated the client certificate
++ bool authentication_success; //!< whether or not the user was authenticated (cert or PW)
++ bool quick_session_tickets; //!< for EAP-TLS.
+
+ /*
+ * Framed-MTU attribute in RADIUS, if present, can also be used to set this
+@@ -160,8 +162,11 @@ typedef struct _tls_session_t {
+ void *opaque;
+ void (*free_opaque)(void *opaque);
+
+- char const *prf_label;
++ char const *label;
+ bool allow_session_resumption; //!< Whether session resumption is allowed.
++ bool session_not_resumed; //!< Whether our session was not resumed.
++
++ fr_tls_server_conf_t const *conf; //! for better complaints
+ } tls_session_t;
+
+ /*
+@@ -309,16 +314,17 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char co
+ CC_HINT(format (printf, 4, 5));
+
+ void tls_global_cleanup(void);
+-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert);
++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, bool allow_tls13);
+ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs);
+ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs);
+ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs);
+ fr_tls_server_conf_t *tls_server_conf_alloc(TALLOC_CTX *ctx);
+-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client);
++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file);
+ int tls_handshake_recv(REQUEST *, tls_session_t *ssn);
+ int tls_handshake_send(REQUEST *, tls_session_t *ssn);
+ void tls_session_information(tls_session_t *ssn);
+ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize);
++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf);
+
+ /*
+ * Low-level TLS stuff
+@@ -335,6 +341,7 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request);
+ #define FR_TLS_EX_INDEX_STORE (14)
+ #define FR_TLS_EX_INDEX_SSN (15)
+ #define FR_TLS_EX_INDEX_TALLOC (16)
++#define FR_TLS_EX_INDEX_FIX_CERT_ORDER (17)
+
+ extern int fr_tls_ex_index_certs;
+ extern int fr_tls_ex_index_vps;
+@@ -360,6 +367,10 @@ struct fr_tls_server_conf_t {
+ bool disable_tlsv1;
+ bool disable_tlsv1_1;
+ bool disable_tlsv1_2;
++ bool disallow_untrusted; //!< allow untrusted CAs to issue client certificates
++
++ int min_version;
++ int max_version;
+
+ char const *tls_min_version;
+ char const *tls_max_version;
+@@ -371,16 +382,21 @@ struct fr_tls_server_conf_t {
+ bool check_crl;
+ bool check_all_crl;
+ bool allow_expired_crl;
++ uint32_t ca_path_reload_interval;
++ uint32_t ca_path_last_reload;
++ X509_STORE *old_x509_store;
+ char const *check_cert_cn;
+ char const *cipher_list;
+ bool cipher_server_preference;
+ char const *check_cert_issuer;
++ char const *sigalgs_list;
+
+ bool session_cache_enable;
+- uint32_t session_timeout;
++ uint32_t session_lifetime;
+ uint32_t session_cache_size;
+ char const *session_id_name;
+ char const *session_cache_path;
++ char const *session_cache_server;
+ fr_hash_table_t *cache_ht;
+ char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH];
+
+@@ -389,6 +405,10 @@ struct fr_tls_server_conf_t {
+ char const *verify_client_cert_cmd;
+ bool require_client_cert;
+
++ bool fix_cert_order;
++
++ pthread_mutex_t mutex;
++
+ #ifdef HAVE_OPENSSL_OCSP_H
+ /*
+ * OCSP Configuration
+@@ -414,6 +434,15 @@ struct fr_tls_server_conf_t {
+ char const *psk_query;
+ #endif
+
++ char const *realm_dir;
++ fr_hash_table_t *realms;
++
++ char const *client_hostname;
++
++#ifdef WITH_RADIUSV11
++ char const *radiusv11_name;
++ fr_radiusv11_t radiusv11;
++#endif
+ };
+
+ #ifdef __cplusplus
+diff --git a/src/include/token.h b/src/include/token.h
+index 6cbd05217a..c8bb748702 100644
+--- a/src/include/token.h
++++ b/src/include/token.h
+@@ -56,16 +56,17 @@ typedef enum fr_token_t {
+ T_OP_CMP_TRUE, /* =* 20 */
+ T_OP_CMP_FALSE, /* !* */
+ T_OP_CMP_EQ, /* == */
++ T_OP_PREPEND, /* ^= */
+ T_HASH, /* # */
+- T_BARE_WORD, /* bare word */
+- T_DOUBLE_QUOTED_STRING, /* "foo" 25 */
++ T_BARE_WORD, /* bare word 25 */
++ T_DOUBLE_QUOTED_STRING, /* "foo" */
+ T_SINGLE_QUOTED_STRING, /* 'foo' */
+ T_BACK_QUOTED_STRING, /* `foo` */
+ T_TOKEN_LAST
+ } FR_TOKEN;
+
+ #define T_EQSTART T_OP_ADD
+-#define T_EQEND (T_OP_CMP_EQ + 1)
++#define T_EQEND (T_OP_PREPEND + 1)
+
+ typedef struct FR_NAME_NUMBER {
+ char const *name;
+diff --git a/src/lib/dict.c b/src/lib/dict.c
+index 96e06b5287..479bf1104e 100644
+--- a/src/lib/dict.c
++++ b/src/lib/dict.c
+@@ -725,7 +725,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type,
+ {
+ size_t namelen;
+ DICT_ATTR const *parent;
+- DICT_ATTR *n;
++ DICT_ATTR *n;
++ DICT_ATTR const *old;
+ static int max_attr = 0;
+
+ namelen = strlen(name);
+@@ -882,6 +883,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type,
+ return -1;
+ }
+
++ if (flags.encrypt) flags.secret = 1;
++
+ if (flags.length && (type != PW_TYPE_OCTETS)) {
+ fr_strerror_printf("The \"length\" flag can only be set for attributes of type \"octets\"");
+ return -1;
+@@ -1080,6 +1083,18 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type,
+ n->type = type;
+ n->flags = flags;
+
++ /*
++ * Allow old-style names, but they always end up as
++ * new-style names.
++ */
++ old = dict_attrbyvalue(n->attr, n->vendor);
++ if (old && (old->type == n->type)) {
++ DICT_ATTR *mutable;
++
++ memcpy(&mutable, &old, sizeof(old)); /* const issues */
++ mutable->flags.is_dup = true;
++ }
++
+ /*
+ * Insert the attribute, only if it's not a duplicate.
+ */
+@@ -1258,6 +1273,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value)
+ fr_int2str(dict_attr_types, da->type, "?Unknown?"));
+ return -1;
+ }
++ /* in v4 this is done with the UNCONST #define */
++ ((DICT_ATTR *)((uintptr_t)(da)))->flags.has_value = 1;
+ } else {
+ value_fixup_t *fixup;
+
+@@ -1742,6 +1759,10 @@ static int process_attribute(char const* fn, int const line,
+ "\"encrypt=3\" flag set", fn, line);
+ return -1;
+ }
++ flags.secret = 1;
++
++ } else if (strncmp(key, "secret", 6) == 0) {
++ flags.secret = 1;
+
+ } else if (strncmp(key, "array", 6) == 0) {
+ flags.array = 1;
+@@ -2022,7 +2043,7 @@ static int process_value_alias(char const* fn, int const line, char **argv,
+ }
+
+
+-static int parse_format(char const *fn, int line, char const *format, int *pvalue, int *ptype, int *plength, bool *pcontinuation)
++static int parse_format(char const *fn, int line, char const *format, int *ptype, int *plength, bool *pcontinuation)
+ {
+ char const *p;
+ int type, length;
+@@ -2075,9 +2096,8 @@ static int parse_format(char const *fn, int line, char const *format, int *pvalu
+ }
+ continuation = true;
+
+- if ((*pvalue != VENDORPEC_WIMAX) ||
+- (type != 1) || (length != 1)) {
+- fr_strerror_printf("dict_init: %s[%d]: Only WiMAX VSAs can have continuations",
++ if ((type != 1) || (length != 1)) {
++ fr_strerror_printf("dict_init: %s[%d]: Only 'format=1,1' VSAs can have continuations",
+ fn, line);
+ return -1;
+ }
+@@ -2132,7 +2152,7 @@ static int process_vendor(char const* fn, int const line, char **argv,
+ * Look for a format statement. Allow it to over-ride the hard-coded formats below.
+ */
+ if (argc == 3) {
+- if (parse_format(fn, line, argv[2], &value, &type, &length, &continuation) < 0) {
++ if (parse_format(fn, line, argv[2], &type, &length, &continuation) < 0) {
+ return -1;
+ }
+
+@@ -2409,7 +2429,8 @@ static int my_dict_init(char const *parent, char const *filename,
+ /*
+ * Optionally include a dictionary
+ */
+- if (strcasecmp(argv[0], "$INCLUDE-") == 0) {
++ if ((strcasecmp(argv[0], "$INCLUDE-") == 0) ||
++ (strcasecmp(argv[0], "$-INCLUDE") == 0)) {
+ int rcode = my_dict_init(dir, argv[1], fn, line);
+
+ if (rcode == -2) continue;
+@@ -2783,45 +2804,72 @@ int dict_init(char const *dir, char const *fn)
+ return 0;
+ }
+
+-static size_t print_attr_oid(char *buffer, size_t size, unsigned int attr,
+- int dv_type)
++static size_t print_attr_oid(char *buffer, size_t bufsize, unsigned int attr, unsigned int vendor)
+ {
+- int nest;
+- size_t outlen;
++ int nest, dv_type = 1;
+ size_t len;
++ char *p = buffer;
++
++ if (vendor > FR_MAX_VENDOR) {
++ len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR);
++ p += len;
++ bufsize -= len;
++ vendor &= (FR_MAX_VENDOR) - 1;
++ }
++
++ if (vendor) {
++ DICT_VENDOR *dv;
++
++ /*
++ * dv_type is the length of the vendor's type field
++ * RFC 2865 never defined a mandatory length, so
++ * different vendors have different length type fields.
++ */
++ dv = dict_vendorbyvalue(vendor);
++ if (dv) dv_type = dv->type;
++
++ len = snprintf(p, bufsize, "26.%u.", vendor);
++
++ p += len;
++ bufsize -= len;
++ }
++
+
+ switch (dv_type) {
+ default:
+ case 1:
+- len = snprintf(buffer, size, "%u", attr & 0xff);
++ len = snprintf(p, bufsize, "%u", attr & 0xff);
++ p += len;
++ bufsize -= len;
++ if ((attr >> 8) == 0) return p - buffer;
+ break;
+
+- case 4:
+- return snprintf(buffer, size, "%u", attr);
+-
+ case 2:
+- return snprintf(buffer, size, "%u", attr & 0xffff);
+-
+- }
++ len = snprintf(p, bufsize, "%u", attr & 0xffff);
++ p += len;
++ return p - buffer;
+
+- if ((attr >> 8) == 0) return len;
++ case 4:
++ len = snprintf(p, bufsize, "%u", attr);
++ p += len;
++ return p - buffer;
+
+- outlen = len;
+- buffer += len;
+- size -= len;
++ }
+
++ /*
++ * "attr" is a sequence of packed numbers. Unpack them.
++ */
+ for (nest = 1; nest <= fr_attr_max_tlv; nest++) {
+ if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break;
+
+- len = snprintf(buffer, size, ".%u",
++ len = snprintf(p, bufsize, ".%u",
+ (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]);
+
+- outlen = len;
+- buffer += len;
+- size -= len;
++ p += len;
++ bufsize -= len;
+ }
+
+- return outlen;
++ return p - buffer;
+ }
+
+ /** Free dynamically allocated (unknown attributes)
+@@ -2862,7 +2910,6 @@ void dict_attr_free(DICT_ATTR const **da)
+ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor)
+ {
+ char *p;
+- int dv_type = 1;
+ size_t len = 0;
+ size_t bufsize = DICT_ATTR_MAX_NAME_LEN;
+
+@@ -2888,32 +2935,7 @@ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vend
+ p += len;
+ bufsize -= len;
+
+- if (vendor > FR_MAX_VENDOR) {
+- len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR);
+- p += len;
+- bufsize -= len;
+- vendor &= (FR_MAX_VENDOR) - 1;
+- }
+-
+- if (vendor) {
+- DICT_VENDOR *dv;
+-
+- /*
+- * dv_type is the length of the vendor's type field
+- * RFC 2865 never defined a mandatory length, so
+- * different vendors have different length type fields.
+- */
+- dv = dict_vendorbyvalue(vendor);
+- if (dv) {
+- dv_type = dv->type;
+- }
+- len = snprintf(p, bufsize, "26.%u.", vendor);
+-
+- p += len;
+- bufsize -= len;
+- }
+-
+- print_attr_oid(p, bufsize , attr, dv_type);
++ print_attr_oid(p, bufsize , attr, vendor);
+
+ return 0;
+ }
+@@ -3270,7 +3292,15 @@ DICT_ATTR const *dict_attrbyname(char const *name)
+ da = (DICT_ATTR *) buffer;
+ strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1);
+
+- return fr_hash_table_finddata(attributes_byname, da);
++ da = fr_hash_table_finddata(attributes_byname, da);
++ if (!da) return NULL;
++
++ if (!da->flags.is_dup) return da;
++
++ /*
++ * This MUST exist if the dup flag is set.
++ */
++ return dict_attrbyvalue(da->attr, da->vendor);
+ }
+
+ /** Look up a dictionary attribute by name embedded in another string
+@@ -3464,3 +3494,13 @@ DICT_ATTR const *dict_unknown_add(DICT_ATTR const *old)
+ da = dict_attrbyvalue(old->attr, old->vendor);
+ return da;
+ }
++
++size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da)
++{
++ return print_attr_oid(buffer, buflen, da->attr, da->vendor);
++}
++
++int dict_walk(fr_hash_table_walk_t callback, void *context)
++{
++ 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 <freeradius-devel/heap.h>
+ #include <freeradius-devel/event.h>
+
++#include <pthread.h>
++
+ #ifdef HAVE_KQUEUE
+ #ifndef HAVE_SYS_EVENT_H
+ #error kqueue requires <sys/event.h>
+@@ -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
++++ b/src/lib/hmacmd5.c
+@@ -34,8 +34,9 @@ RCSID("$Id$")
+
+ #include <freeradius-devel/libradius.h>
+ #include <freeradius-devel/md5.h>
++#include <freeradius-devel/openssl3.h>
+
+-#ifdef HAVE_OPENSSL_EVP_H
++#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS)
+ /** Calculate HMAC using OpenSSL's MD5 implementation
+ *
+ * @param digest Caller digest to be filled in.
+@@ -49,6 +50,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t
+ uint8_t const *key, size_t key_len)
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
++ unsigned int len = MD5_DIGEST_LENGTH;
+
+ #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
+ /* Since MD5 is not allowed by FIPS, explicitly allow it. */
+@@ -57,7 +59,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t
+
+ HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL);
+ HMAC_Update(ctx, text, text_len);
+- HMAC_Final(ctx, digest, NULL);
++ HMAC_Final(ctx, digest, &len);
+ HMAC_CTX_free(ctx);
+ }
+ #else
+diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c
+index 211470ea35..8711f983b7 100644
+--- a/src/lib/hmacsha1.c
++++ b/src/lib/hmacsha1.c
+@@ -13,6 +13,7 @@ RCSID("$Id$")
+ #ifdef HAVE_OPENSSL_EVP_H
+ #include <openssl/hmac.h>
+ #include <openssl/evp.h>
++#include <freeradius-devel/openssl3.h>
+ #endif
+
+ #include <freeradius-devel/libradius.h>
+@@ -35,9 +36,11 @@ void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_
+ uint8_t const *key, size_t key_len)
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
++ unsigned int len = SHA1_DIGEST_LENGTH;
++
+ HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL);
+ HMAC_Update(ctx, text, text_len);
+- HMAC_Final(ctx, digest, NULL);
++ HMAC_Final(ctx, digest, &len);
+ HMAC_CTX_free(ctx);
+ }
+
+diff --git a/src/lib/md4.c b/src/lib/md4.c
+index 0515fca1ae..7169000381 100644
+--- a/src/lib/md4.c
++++ b/src/lib/md4.c
+@@ -28,6 +28,7 @@ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen
+ fr_md4_init(&ctx);
+ fr_md4_update(&ctx, in, inlen);
+ fr_md4_final(out, &ctx);
++ fr_md4_destroy(&ctx);
+ }
+
+ #ifndef HAVE_OPENSSL_MD4_H
+diff --git a/src/lib/md5.c b/src/lib/md5.c
+index 9858175bd4..b5c1729148 100644
+--- a/src/lib/md5.c
++++ b/src/lib/md5.c
+@@ -30,6 +30,7 @@ void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen)
+ fr_md5_init(&ctx);
+ fr_md5_update(&ctx, in, inlen);
+ fr_md5_final(out, &ctx);
++ fr_md5_destroy(&ctx);
+ }
+
+ #ifndef HAVE_OPENSSL_MD5_H
+diff --git a/src/lib/pair.c b/src/lib/pair.c
+index d711f90c5d..146c82f95b 100644
+--- a/src/lib/pair.c
++++ b/src/lib/pair.c
+@@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) {
+ return 0;
+ }
+
+-static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx)
++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx)
+ {
+ VALUE_PAIR *vp;
+
+@@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v
+ DICT_ATTR const *da;
+
+ da = dict_attrbyvalue(attr, vendor);
+- if (!da) {
+- VALUE_PAIR *vp;
+-
+- vp = fr_pair_alloc(ctx);
+- if (!vp) return NULL;
+-
+- /*
+- * Ensure that the DA is parented by the VP.
+- */
+- da = dict_unknown_afrom_fields(vp, attr, vendor);
+- if (!da) {
+- talloc_free(vp);
+- return NULL;
+- }
+-
+- vp->da = da;
+- return vp;
+- }
++ if (!da) return NULL;
+
+ return fr_pair_afrom_da(ctx, da);
+ }
+@@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add)
+ i->next = add;
+ }
+
++/** Add a VP to the start of the list.
++ *
++ * Links the new VP to 'first', then points 'first' at the new VP.
++ *
++ * @param[in] first VP in linked list. Will add new VP to the start of this list.
++ * @param[in] add VP to add to list.
++ */
++void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add)
++{
++ VALUE_PAIR *i;
++
++ if (!add) return;
++
++ VERIFY_VP(add);
++
++ if (*first == NULL) {
++ *first = add;
++ return;
++ }
++
++ /*
++ * Find the end of the list to be prepended
++ */
++ for (i = add; i->next; i = i->next) {
++#ifdef WITH_VERIFY_PTR
++ VERIFY_VP(i);
++ /*
++ * The same VP should never by added multiple times
++ * to the same list.
++ */
++ fr_assert(*first != i);
++#endif
++ }
++ i->next = *first;
++ *first = add;
++}
++
+ /** Replace all matching VPs
+ *
+ * Walks over 'first', and replaces the first VP that matches 'replace'.
+@@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp)
+ * @param[in] ctx for talloc
+ * @param[in,out] to destination list.
+ * @param[in,out] from source list.
++ * @param[in] op operator for move.
+ *
+ * @see radius_pairmove
+ */
+-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op)
+ {
+ VALUE_PAIR *i, *found;
+ VALUE_PAIR *head_new, **tail_new;
++ VALUE_PAIR *head_prepend;
+ VALUE_PAIR **tail_from;
+
+ if (!to || !from || !*from) return;
+@@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
+ head_new = NULL;
+ tail_new = &head_new;
+
++ /*
++ * Any attributes that are requested to be prepended
++ * are added to a temporary list here
++ */
++ head_prepend = NULL;
++
+ /*
+ * We're looping over the "from" list, moving some
+ * attributes out, but leaving others in place.
+@@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
+ fr_pair_steal(ctx, i);
+ tail_new = &(i->next);
+ continue;
++ case T_OP_PREPEND:
++ i->next = head_prepend;
++ head_prepend = i;
++ fr_pair_steal(ctx, i);
++ continue;
+ }
+ } /* loop over the "from" list. */
+
+ /*
+- * Take the "new" list, and append it to the "to" list.
++ * If the op parameter was prepend, add the "new" list
++ * attributes first as those whose individual operator
++ * is prepend should be prepended to the resulting list
+ */
+- fr_pair_add(to, head_new);
++ if (op == T_OP_PREPEND) {
++ fr_pair_prepend(to, head_new);
++ }
++
++ /*
++ * If there are any items in the prepend list prepend
++ * it to the "to" list
++ */
++ fr_pair_prepend(to, head_prepend);
++
++ /*
++ * If the op parameter was not prepend, take the "new"
++ * list, and append it to the "to" list.
++ */
++ if (op != T_OP_PREPEND) {
++ fr_pair_add(to, head_new);
++ }
+ }
+
+ /** Move matching pairs between VALUE_PAIR lists
+@@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw)
+ break;
+
+ default:
+- fr_strerror_printf("Failed to find expected value on right hand side");
++ fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand);
+ return T_INVALID;
+ }
+
+diff --git a/src/lib/print.c b/src/lib/print.c
+index 9ac927358b..57455b6f30 100644
+--- a/src/lib/print.c
++++ b/src/lib/print.c
+@@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type)
+ * @param out Where to write the string.
+ * @param outlen Length of output buffer.
+ * @param vp to print.
++ * @param raw_value if true, the raw value is printed and not the enumerated attribute value
+ * @return the length of data written to out, or a value >= outlen on truncation.
+ */
+-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp)
++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value)
+ {
+ char const *q;
+ size_t len, freespace = outlen;
++ /* attempt to print raw_value when has_value is false, or raw_value is false, but only
++ if has_tag is also false */
++ bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag;
+
+- if (!vp->da->flags.has_tag) {
++ if (raw) {
+ switch (vp->da->type) {
+ case PW_TYPE_INTEGER:
+- if (vp->da->flags.has_value) break;
+-
+ return snprintf(out, freespace, "%u", vp->vp_integer);
+
+ case PW_TYPE_SHORT:
+- if (vp->da->flags.has_value) break;
+-
+ return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short);
+
+ case PW_TYPE_BYTE:
+- if (vp->da->flags.has_value) break;
+-
+ return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte);
+
+- case PW_TYPE_SIGNED:
+- return snprintf(out, freespace, "%d", vp->vp_signed);
+-
+ default:
+ break;
+ }
+@@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote)
+
+ value = vp_aprints_value(ctx, vp, quote);
+
+- if (vp->da->flags.has_tag) {
++ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) {
+ if (quote && (vp->da->type == PW_TYPE_STRING)) {
+ 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..6dcee141e2 100644
+--- a/src/lib/radius.c
++++ b/src/lib/radius.c
+@@ -28,6 +28,7 @@ RCSID("$Id$")
+ #include <freeradius-devel/libradius.h>
+
+ #include <freeradius-devel/md5.h>
++#include <freeradius-devel/rfc4849.h>
+
+ #include <fcntl.h>
+ #include <ctype.h>
+@@ -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];
+ }
++
++ fr_md5_destroy(&context);
+ }
+
+ #define MAX_PASS_LEN (128)
+@@ -562,8 +566,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen,
+ *outlen = len;
+
+ fr_md5_init(&context);
++ fr_md5_init(&old);
+ fr_md5_update(&context, (uint8_t const *) secret, strlen(secret));
+- old = context;
++ fr_md5_copy(old, context);
+
+ /*
+ * Do first pass.
+@@ -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) {
+- context = old;
++ fr_md5_copy(context, old);
+ fr_md5_update(&context,
+ passwd + n - AUTH_PASS_LEN,
+ AUTH_PASS_LEN);
+@@ -585,6 +590,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen,
+ }
+
+ memcpy(output, passwd, len);
++
++ fr_md5_destroy(&old);
++ fr_md5_destroy(&context);
+ }
+
+
+@@ -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);
++ fr_md5_init(&old);
+ fr_md5_update(&context, (uint8_t const *) secret, strlen(secret));
+- old = context;
++ fr_md5_copy(old, context);
+
+ fr_md5_update(&context, vector, AUTH_VECTOR_LEN);
+ fr_md5_update(&context, &output[0], 2);
+@@ -663,7 +672,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen,
+ size_t block_len;
+
+ if (n > 0) {
+- context = old;
++ fr_md5_copy(context, old);
+ fr_md5_update(&context,
+ output + 2 + n - AUTH_PASS_LEN,
+ AUTH_PASS_LEN);
+@@ -681,6 +690,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen,
+ output[i + 2 + n] ^= digest[i];
+ }
+ }
++ fr_md5_destroy(&old);
++ fr_md5_destroy(&context);
+ }
+
+ static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest)
+@@ -1552,6 +1563,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet,
+
+ VERIFY_VP(vp);
+
++ if (room < 2) return -1;
++
+ if (vp->da->vendor != 0) {
+ fr_strerror_printf("rad_vp2rfc called with VSA");
+ return -1;
+@@ -1594,6 +1607,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet,
+ return 18;
+ }
+
++ /*
++ * Hacks for NAS-Filter-Rule. They all get concatenated
++ * with 0x00 bytes in between the values. We rely on the
++ * decoder to do the opposite transformation on incoming
++ * packets.
++ */
++ if (vp->da->attr == PW_NAS_FILTER_RULE) {
++ uint8_t const *end = ptr + room;
++ uint8_t *p, *attr = ptr;
++ bool zero = false;
++
++ attr[0] = PW_NAS_FILTER_RULE;
++ attr[1] = 2;
++ p = ptr + 2;
++
++ while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) {
++ if ((p + zero + vp->vp_length) > end) {
++ break;
++ }
++
++ if (zero) {
++ if (attr[1] == 255) {
++ attr = p;
++ if ((attr + 3) >= end) break;
++
++ attr[0] = PW_NAS_FILTER_RULE;
++ attr[1] = 2;
++ p = attr + 2;
++ }
++
++ *(p++) = 0;
++ attr[1]++;
++ }
++
++ /*
++ * Check for overflow
++ */
++ if ((attr[1] + vp->vp_length) < 255) {
++ memcpy(p, vp->vp_strvalue, vp->vp_length);
++ attr[1] += vp->vp_length;
++ p += vp->vp_length;
++
++ } else if (attr + (attr[1] + 2 + vp->vp_length) > end) {
++ break;
++
++ } else if (vp->vp_length > 253) {
++ /*
++ * Drop VPs which are too long.
++ * We don't (yet) split one VP
++ * across multiple attributes.
++ */
++ vp = vp->next;
++ continue;
++
++ } else {
++ size_t first, second;
++
++ first = 255 - attr[1];
++ second = vp->vp_length - first;
++
++ memcpy(p, vp->vp_strvalue, first);
++ p += first;
++ attr[1] = 255;
++ attr = p;
++
++ attr[0] = PW_NAS_FILTER_RULE;
++ attr[1] = 2;
++ p = attr + 2;
++
++ memcpy(p, vp->vp_strvalue + first, second);
++ attr[1] += second;
++ p += second;
++ }
++
++ vp = vp->next;
++ zero = true;
++ }
++
++ *pvp = vp;
++ return p - ptr;
++ }
++
+ /*
+ * EAP-Message is special.
+ */
+@@ -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);
++ fr_md5_destroy(&context);
+
+ memcpy(hdr->vector, digest, AUTH_VECTOR_LEN);
+ memcpy(packet->vector, digest, AUTH_VECTOR_LEN);
+@@ -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);
++ fr_md5_destroy(&context);
+
+ /*
+ * Return 0 if OK, 2 if not OK.
+@@ -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);
++ fr_md5_destroy(&context);
+
+ /*
+ * Copy the packet's vector back to the packet.
+@@ -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
+ }
+
+
++/** Convert one or more NAS-Filter-Rule attributes to one or more
++ * attributes.
++ *
++ */
++static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx,
++ DICT_ATTR const *da, uint8_t const *start,
++ size_t const packetlen, VALUE_PAIR **pvp)
++{
++ uint8_t const *p = start;
++ uint8_t const *attr = start;
++ uint8_t const *end = start + packetlen;
++ uint8_t const *attr_end;
++ uint8_t *q;
++ VALUE_PAIR *vp;
++ uint8_t buffer[253];
++
++ q = buffer;
++
++ /*
++ * The packet has already been sanity checked, so we
++ * don't care about walking off of the end of it.
++ */
++ while (attr < end) {
++ if ((attr + 2) > end) {
++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok");
++ return -1;
++ }
++
++ if (attr[1] < 2) {
++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok");
++ return -1;
++ }
++ if (attr[0] != PW_NAS_FILTER_RULE) break;
++
++ /*
++ * Now decode one, or part of one rule.
++ */
++ p = attr + 2;
++ attr_end = attr + attr[1];
++
++ if (attr_end > end) {
++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok");
++ return -1;
++ }
++
++ /*
++ * Coalesce data until the zero byte.
++ */
++ while (p < attr_end) {
++ /*
++ * Once we hit the zero byte, create the
++ * VP, skip the zero byte, and reset the
++ * counters.
++ */
++ if (*p == 0) {
++ /*
++ * Discard consecutive zeroes.
++ */
++ if (q > buffer) {
++ vp = fr_pair_afrom_da(ctx, da);
++ if (!vp) {
++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory");
++ return -1;
++ }
++
++ fr_pair_value_bstrncpy(vp, buffer, q - buffer);
++
++ *pvp = vp;
++ pvp = &(vp->next);
++ q = buffer;
++ }
++
++ p++;
++ continue;
++ }
++ *(q++) = *(p++);
++
++ /*
++ * Not much reason to have rules which
++ * are too long.
++ */
++ if ((size_t) (q - buffer) > sizeof(buffer)) {
++ fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long");
++ return -1;
++ }
++ }
++
++ /*
++ * Done this attribute. There MAY be things left
++ * in the buffer.
++ */
++ attr = attr_end;
++ }
++
++ if (q == buffer) return attr + attr[2] - start;
++
++ vp = fr_pair_afrom_da(ctx, da);
++ if (!vp) {
++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory");
++ return -1;
++ }
++
++ fr_pair_value_bstrncpy(vp, buffer, q - buffer);
++
++ *pvp = vp;
++
++ return p - start;
++}
++
+ /** Convert a "concatenated" attribute to one long VP
+ *
+ */
+@@ -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
+ */
+- if ((vendor == VENDORPEC_WIMAX) && dv->flags) {
++ if (dv->flags) {
+ rcode = data2vp_wimax(ctx, packet, original, secret, vendor,
+ data, attrlen, packetlen, pvp);
+ return rcode;
+@@ -3610,19 +3893,12 @@ ssize_t data2vp(TALLOC_CTX *ctx,
+ return 0;
+ }
+
+-#if !defined(NDEBUG) || defined(__clang_analyzer__)
+ /*
+- * Hacks for Coverity. Editing the dictionary
+- * will break assumptions about CUI. We know
+- * this, but Coverity doesn't.
++ * Create a zero-length attribute.
+ */
+- if (da->type != PW_TYPE_OCTETS) return -1;
+-#endif
+-
+- data = buffer;
+- *buffer = '\0';
+- datalen = 0;
+- goto alloc_cui; /* skip everything */
++ vp = fr_pair_afrom_da(ctx, da);
++ if (!vp) return -1;
++ goto done;
+ }
+
+ /*
+@@ -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:
++ /*
++ * If it's already unknown, don't create a new
++ * unknown one.
++ */
++ if (da->flags.is_unknown) break;
++
+ /*
+ * Re-write the attribute to be "raw". It is
+ * therefore of type "octets", and will be
+ * handled below.
++ *
++ * We allocate the VP *first*, and then the da
++ * from it, so that there are no memory leaks.
+ */
+- da = dict_unknown_afrom_fields(ctx, da->attr, da->vendor);
++ vp = fr_pair_alloc(ctx);
++ if (!vp) return -1;
++
++ da = dict_unknown_afrom_fields(vp, da->attr, da->vendor);
+ if (!da) {
+ fr_strerror_printf("Internal sanity check %d", __LINE__);
+ return -1;
+ }
+ tag = TAG_NONE;
+-#ifndef NDEBUG
+- /*
+- * Fix for Coverity.
+- */
+- if (da->type != PW_TYPE_OCTETS) {
+- dict_attr_free(&da);
+- return -1;
+- }
+-#endif
+- break;
++ vp->da = da;
++ goto alloc_raw;
+ }
+
+ /*
+ * And now that we've verified the basic type
+ * information, decode the actual data.
+ */
+- alloc_cui:
+ vp = fr_pair_afrom_da(ctx, da);
+- if (!vp) return -1;
++ if (!vp) {
++ dict_attr_free(&da); /* only frees unknowns */
++ return -1;
++ }
+
++alloc_raw:
+ vp->vp_length = datalen;
+ vp->tag = tag;
+
+-#ifdef __clang_analyzer__
+- if (!datalen && da->type != PW_TYPE_OCTETS) return -1;
+-#endif
+-
+ switch (da->type) {
+ case PW_TYPE_STRING:
+ p = talloc_array(vp, char, vp->vp_length + 1);
+@@ -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;
+ }
++
++done:
+ vp->type = VT_DATA;
+ *pvp = vp;
+
+@@ -4112,6 +4396,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx,
+ return data2vp_concat(ctx, da, data, length, pvp);
+ }
+
++ if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) {
++ VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n");
++ return data2vp_nas_filter_rule(ctx, da, data, length, pvp);
++ }
++
+ /*
+ * Note that we pass the entire length, not just the
+ * length of this attribute. The Extended or WiMAX
+@@ -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;
+- VALUE_PAIR *head, **tail, *vp;
++ VALUE_PAIR *head, **tail, *vp = NULL;
+
+ /*
+ * Extract attribute-value pairs
+@@ -4385,8 +4677,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret,
+ secretlen = strlen(secret);
+
+ fr_md5_init(&context);
++ fr_md5_init(&old);
+ fr_md5_update(&context, (uint8_t const *) secret, secretlen);
+- old = context; /* save intermediate work */
++ fr_md5_copy(old, context); /* save intermediate work */
+
+ /*
+ * Encrypt it in place. Don't bother checking
+@@ -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 {
+- context = old;
++ fr_md5_copy(context, old);
+ fr_md5_update(&context,
+ (uint8_t *) passwd + n - AUTH_PASS_LEN,
+ AUTH_PASS_LEN);
+@@ -4409,6 +4702,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret,
+ }
+ }
+
++ fr_md5_destroy(&old);
++ fr_md5_destroy(&context);
++
+ return 0;
+ }
+
+@@ -4441,8 +4737,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret,
+ secretlen = strlen(secret);
+
+ fr_md5_init(&context);
++ fr_md5_init(&old);
+ fr_md5_update(&context, (uint8_t const *) secret, secretlen);
+- old = context; /* save intermediate work */
++ fr_md5_copy(old, context); /* save intermediate work */
+
+ /*
+ * The inverse of the code above.
+@@ -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);
+
+- context = old;
++ fr_md5_copy(context, old);
+ if (pwlen > AUTH_PASS_LEN) {
+ fr_md5_update(&context, (uint8_t *) passwd,
+ AUTH_PASS_LEN);
+@@ -4460,7 +4757,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret,
+ } else {
+ fr_md5_final(digest, &context);
+
+- context = old;
++ fr_md5_copy(context, old);
+ if (pwlen > (n + AUTH_PASS_LEN)) {
+ fr_md5_update(&context, (uint8_t *) passwd + n,
+ AUTH_PASS_LEN);
+@@ -4473,6 +4770,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret,
+ }
+
+ done:
++ fr_md5_destroy(&old);
++ fr_md5_destroy(&context);
++
+ passwd[pwlen] = '\0';
+ return strlen(passwd);
+ }
+@@ -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);
++ fr_md5_init(&old);
+ fr_md5_update(&context, (uint8_t const *) secret, secretlen);
+- old = context; /* save intermediate work */
++ fr_md5_copy(old, context); /* save intermediate work */
+
+ /*
+ * Set up the initial key:
+@@ -4637,7 +4938,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
+
+ fr_md5_final(digest, &context);
+
+- context = old;
++ fr_md5_copy(context, old);
+
+ /*
+ * A quick check: decrypt the first octet
+@@ -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);
+
+- context = old;
++ fr_md5_copy(context, old);
+ fr_md5_update(&context, passwd + n + 2, block_len);
+ }
+
+@@ -4669,6 +4972,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
+ *pwlen = reallen;
+ passwd[reallen] = 0;
+
++ fr_md5_destroy(&old);
++ fr_md5_destroy(&context);
++
+ return reallen;
+ }
+
+diff --git a/src/lib/token.c b/src/lib/token.c
+index 8ae41b392f..a7978622e6 100644
+--- a/src/lib/token.c
++++ b/src/lib/token.c
+@@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = {
+ { "=*", T_OP_CMP_TRUE, },
+ { "!*", T_OP_CMP_FALSE, },
+ { "==", T_OP_CMP_EQ, },
++ { "^=", T_OP_PREPEND, },
+ { "=", T_OP_EQ, },
+ { "!=", T_OP_NE, },
+ { ">=", T_OP_GE, },
+@@ -78,9 +79,10 @@ const bool fr_assignment_op[] = {
+ false, /* =* 20 */
+ false, /* !* */
+ false, /* == */
+- false, /* # */
+- false, /* bare word */
+- false, /* "foo" 25 */
++ true, /* ^= */
++ false, /* # */
++ false, /* bare word 25 */
++ false, /* "foo" */
+ false, /* 'foo' */
+ false, /* `foo` */
+ false
+@@ -108,12 +110,13 @@ const bool fr_equality_op[] = {
+ true, /* < */
+ true, /* =~ */
+ true, /* !~ */
+- true, /* =* 20 */
++ true, /* =* 20 */
+ true, /* !* */
+ true, /* == */
+- false, /* # */
+- false, /* bare word */
+- false, /* "foo" 25 */
++ false, /* ^= */
++ false, /* # */
++ false, /* bare word 25 */
++ false, /* "foo" */
+ false, /* 'foo' */
+ false, /* `foo` */
+ false
+@@ -144,9 +147,10 @@ const bool fr_str_tok[] = {
+ false, /* =* 20 */
+ false, /* !* */
+ false, /* == */
+- false, /* # */
+- true, /* bare word */
+- true, /* "foo" 25 */
++ false, /* ^= */
++ false, /* # */
++ true, /* bare word 25 */
++ true, /* "foo" */
+ true, /* 'foo' */
+ true, /* `foo` */
+ false
+diff --git a/src/main/cb.c b/src/main/cb.c
+index 4ae14e575b..f8b2edbecc 100644
+--- a/src/main/cb.c
++++ b/src/main/cb.c
+@@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ #ifdef WITH_TLS
+ void cbtls_info(SSL const *s, int where, int ret)
+ {
+- char const *str, *state;
++ char const *role, *state;
+ REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST);
+
+ if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) {
+- str="TLS_connect";
++ role = "Client ";
+ } else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) {
+- str="TLS_accept";
++ role = "Server ";
+ } else {
+- str="(other)";
++ role = "";
+ }
+
+ state = SSL_state_string_long(s);
+ state = state ? state : "<none>";
+
+ if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) {
+- RDEBUG2("%s: %s", str, state);
++ if (RDEBUG_ENABLED3) {
++ char const *abbrv = SSL_state_string(s);
++ size_t len;
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ STACK_OF(SSL_CIPHER) *client_ciphers;
++ STACK_OF(SSL_CIPHER) *server_ciphers;
++#endif
++
++ /*
++ * Trim crappy OpenSSL state strings...
++ */
++ len = strlen(abbrv);
++ if ((len > 1) && (abbrv[len - 1] == ' ')) len--;
++
++ RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)",
++ (int)len, abbrv, role, state, SSL_get_state(s));
++
++ /*
++ * After a ClientHello, list all the proposed ciphers from the client
++ */
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) {
++ int i;
++ int num_ciphers;
++ const SSL_CIPHER *this_cipher;
++
++ server_ciphers = SSL_get_ciphers(s);
++ if (server_ciphers) {
++ RDEBUG3("Server preferred ciphers (by priority)");
++ num_ciphers = sk_SSL_CIPHER_num(server_ciphers);
++ for (i = 0; i < num_ciphers; i++) {
++ this_cipher = sk_SSL_CIPHER_value(server_ciphers, i);
++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher));
++ }
++ }
++
++ client_ciphers = SSL_get_client_ciphers(s);
++ if (client_ciphers) {
++ RDEBUG3("Client preferred ciphers (by priority)");
++ num_ciphers = sk_SSL_CIPHER_num(client_ciphers);
++ for (i = 0; i < num_ciphers; i++) {
++ this_cipher = sk_SSL_CIPHER_value(client_ciphers, i);
++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher));
++ }
++ }
++ }
++#endif
++ } else {
++ RDEBUG2("(TLS) Handshake state - %s%s", role, state);
++ }
+ return;
+ }
+
+ if (where & SSL_CB_ALERT) {
+ if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return;
+
+- RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write",
++ RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write",
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ return;
+ }
+
+ if (where & SSL_CB_EXIT) {
+ if (ret == 0) {
+- RERROR("%s: Failed in %s", str, state);
++ RERROR("(TLS) %s: Failed in %s", role, state);
+ return;
+ }
+
+ if (ret < 0) {
+ if (SSL_want_read(s)) {
+- RDEBUG2("%s: Need to read more data: %s", str, state);
++ RDEBUG2("(TLS) %s: Need to read more data: %s", role, state);
+ return;
+ }
+- ERROR("tls: %s: Error in %s", str, state);
++ RERROR("(TLS) %s: Error in %s", role, state);
+ }
+ }
+ }
+@@ -87,14 +136,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
+ * content types. Which breaks our tracking of
+ * the SSL Session state.
+ */
++#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ if ((msg_version == 0) && (content_type > UINT8_MAX)) {
+- DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i",
++#else
++ /*
++ * "...we do not see the need to resolve application breakage
++ * just because the documentation now is incorrect."
++ *
++ * https://github.com/openssl/openssl/issues/17262
++ */
++ if ((content_type > UINT8_MAX) && (content_type != SSL3_RT_INNER_CONTENT_TYPE)) {
++#endif
++ DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i",
+ content_type, msg_version);
+ return;
+ }
+
+ if ((write_p != 0) && (write_p != 1)) {
+- DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p);
++ DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p);
+ return;
+ }
+
+@@ -104,6 +163,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
+ */
+ if (!state) return;
+
++ if (rad_debug_lvl > 3) {
++ size_t i, j, data_len = len;
++ char buffer[3*16 + 1];
++ uint8_t const *in = inbuf;
++
++ DEBUG("(TLS) Received %zu bytes of TLS data", len);
++ if (data_len > 256) data_len = 256;
++
++ for (i = 0; i < data_len; i += 16) {
++ for (j = 0; j < 16; j++) {
++ if ((i + j) >= data_len) break;
++
++ sprintf(buffer + 3 * j, "%02x ", in[i + j]);
++ }
++
++ DEBUG("(TLS) %s", buffer);
++ }
++ }
++
+ /*
+ * 0 - received (from peer)
+ * 1 - sending (to peer)
+@@ -111,7 +189,6 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
+ state->info.origin = write_p;
+ state->info.content_type = content_type;
+ state->info.record_len = len;
+- state->info.version = msg_version;
+ state->info.initialized = true;
+
+ if (content_type == SSL3_RT_ALERT) {
+@@ -124,6 +201,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
+ state->info.alert_level = 0x00;
+ state->info.alert_description = 0x00;
+
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) {
++ /* let tls_ack_handler set application_data */
++ state->info.content_type = SSL3_RT_HANDSHAKE;
++#endif
++
+ #ifdef SSL3_RT_HEARTBEAT
+ } else if (content_type == TLS1_RT_HEARTBEAT) {
+ uint8_t *p = buf;
+@@ -141,16 +224,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
+ }
+ #endif
+ }
++
+ tls_session_information(state);
+ }
+
+ int cbtls_password(char *buf,
+- int num UNUSED,
++ int num,
+ int rwflag UNUSED,
+ void *userdata)
+ {
+- strcpy(buf, (char *)userdata);
+- return(strlen((char *)userdata));
++ size_t len;
++
++ len = strlcpy(buf, (char *)userdata, num);
++ if (len >= (size_t) num) {
++ ERROR("Password too long. Maximum length is %i bytes", num - 1);
++ return 0;
++ }
++
++ return len;
+ }
+
+ #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 <freeradius-devel/modcall.h>
+ #include <freeradius-devel/md5.h>
+ #include <freeradius-devel/channel.h>
++#include <freeradius-devel/connection.h>
++#include <freeradius-devel/socket.h>
+
+ #include <libgen.h>
+ #ifdef HAVE_INTTYPES_H
+ #include <inttypes.h>
+ #endif
+
+-#ifdef HAVE_SYS_UN_H
+-#include <sys/un.h>
+-#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 <sys/stat.h>
+ #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 <ipaddr> <port> [udp|tcp] [src <ipaddr>] - 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, "<name> and <type> 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, "<file> 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 <ipaddr> [udp|tcp] [listen <ipaddr> <port>] - 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 <name> [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 <command> - Delete client configuration commands",
+ NULL, command_table_del_client },
+
++ { "home_server", FR_WRITE,
++ "del home_server <command> - 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 <filename> - Add new client definition from <filename>",
+@@ -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 <filename> - Add new home serverdefinition from <filename>",
++ command_add_home_server_file, NULL },
++
++ { NULL, 0, NULL, NULL, NULL }
++};
+
+ static fr_command_table_t command_table_add[] = {
+ { "client", FR_WRITE,
+ "add client <command> - Add client configuration commands",
+ NULL, command_table_add_client },
+
++ { "home_server", FR_WRITE,
++ "add home_server <command> - 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 <ipaddr> <port> [udp|tcp] [src <ipaddr>] [alive|dead] - set state for given home server",
++ "set home_server state <ipaddr> <port> [udp|tcp] [src <ipaddr>] [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 <sys/stat.h>
+ #endif
+
++#ifdef WITH_TLS
++#include <netinet/tcp.h>
++
++# 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
++++ b/src/main/map.c
+@@ -245,6 +245,18 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
+ }
+ break;
+
++ case T_BARE_WORD:
++ /*
++ * Foo = %{...}
++ *
++ * Not allowed!
++ */
++ if ((attr[0] == '%') && (attr[1] == '{')) {
++ cf_log_err_cp(cp, "Bare expansions are not permitted. They must be in a double-quoted string.");
++ goto error;
++ }
++ /* FALL-THROUGH */
++
+ default:
+ slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true);
+ if (slen <= 0) {
+@@ -285,14 +297,20 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
+ goto error;
+ }
+
+- /*
+- * We cannot assign a count to an attribute. That must
+- * be done in an xlat.
+- */
+- if ((map->rhs->type == TMPL_TYPE_ATTR) &&
+- (map->rhs->tmpl_num == NUM_COUNT)) {
+- cf_log_err_cp(cp, "Cannot assign from a count");
+- goto error;
++ if (map->rhs->type == TMPL_TYPE_ATTR) {
++ /*
++ * We cannot assign a count to an attribute. That must
++ * be done in an xlat.
++ */
++ if (map->rhs->tmpl_num == NUM_COUNT) {
++ cf_log_err_cp(cp, "Cannot assign from a count");
++ goto error;
++ }
++
++ if (map->rhs->tmpl_da->flags.virtual) {
++ cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name);
++ goto error;
++ }
+ }
+
+ VERIFY_MAP(map);
+@@ -1091,6 +1109,12 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
+ */
+ if (((map->lhs->tmpl_list == PAIR_LIST_COA) ||
+ (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) {
++ if ((request->packet->code == PW_CODE_COA_REQUEST) ||
++ (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) {
++ REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request. Use 'update request' instead.");
++ return -2;
++ }
++
+ if (!request_alloc_coa(context)) {
+ REDEBUG("Failed to create a CoA/Disconnect Request message");
+ return -2;
+@@ -1173,10 +1197,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
+ rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
+ /* FALL-THROUGH */
+ case T_OP_ADD:
+- fr_pair_list_move(parent, list, &head);
++ fr_pair_list_move(parent, list, &head, map->op);
+ fr_pair_list_free(&head);
+ }
+ goto finish;
++ case T_OP_PREPEND:
++ fr_pair_list_move(parent, list, &head, T_OP_PREPEND);
++ fr_pair_list_free(&head);
++ goto finish;
+
+ default:
+ fr_pair_list_free(&head);
+@@ -1341,6 +1369,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
+ fr_pair_list_free(&head);
+ break;
+
++ /*
++ * ^= - Prepend src_list attributes to the destination
++ */
++ case T_OP_PREPEND:
++ fr_pair_prepend(list, head);
++ head = NULL;
++ break;
++
+ /*
+ * += - 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..b48d97235e 100644
+--- a/src/main/radclient.c
++++ b/src/main/radclient.c
+@@ -28,6 +28,10 @@ RCSID("$Id$")
+ #include <freeradius-devel/radpaths.h>
+ #include <freeradius-devel/udpfromto.h>
+ #include <freeradius-devel/conf.h>
++#ifdef HAVE_OPENSSL_SSL_H
++#include <openssl/ssl.h>
++#include <freeradius-devel/openssl3.h>
++#endif
+ #include <ctype.h>
+
+ #ifdef HAVE_GETOPT_H
+@@ -36,6 +40,8 @@ RCSID("$Id$")
+
+ #include <assert.h>
+
++USES_APPLE_DEPRECATED_API
++
+ typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */
+
+ #include "smbdes.h"
+@@ -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, " <command> One of auth, acct, status, coa, disconnect or auto.\n");
+ fprintf(stderr, " -4 Use IPv4 address of server\n");
+ fprintf(stderr, " -6 Use IPv6 address of server.\n");
++ fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n");
+ fprintf(stderr, " -c <count> Send each packet 'count' times.\n");
+ fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
+ fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
+@@ -155,9 +163,60 @@ static int _rc_request_free(rc_request_t *request)
+ return 0;
+ }
+
++#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
++# include <openssl/provider.h>
++
++static OSSL_PROVIDER *openssl_default_provider = NULL;
++static OSSL_PROVIDER *openssl_legacy_provider = NULL;
++
++static int openssl3_init(void)
++{
++ /*
++ * Load the default provider for most algorithms
++ */
++ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default");
++ if (!openssl_default_provider) {
++ ERROR("(TLS) Failed loading default provider");
++ return -1;
++ }
++
++ /*
++ * Needed for MD4
++ *
++ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms
++ */
++ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
++ if (!openssl_legacy_provider) {
++ ERROR("(TLS) Failed loading legacy provider");
++ return -1;
++ }
++
++ return 0;
++}
++
++static void openssl3_free(void)
++{
++ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) {
++ ERROR("Failed unloading default provider");
++ }
++ openssl_default_provider = NULL;
++
++ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) {
++ ERROR("Failed unloading legacy provider");
++ }
++ openssl_legacy_provider = NULL;
++}
++#else
++#define openssl3_init()
++#define openssl3_free()
++#endif
++
++
++
+ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request,
+ char const *password)
+ {
++ int rcode;
+ unsigned int i;
+ uint8_t *p;
+ VALUE_PAIR *challenge, *reply;
+@@ -190,9 +249,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request,
+
+ p[1] = 0x01; /* NT hash */
+
+- if (mschap_ntpwdhash(nthash, password) < 0) {
+- return 0;
+- }
++ rcode = mschap_ntpwdhash(nthash, password);
++ if (rcode < 0) return 0;
+
+ smbdes_mschap(nthash, challenge->vp_octets, p + 26);
+ return 1;
+@@ -960,8 +1018,8 @@ static int send_one_packet(rc_request_t *request)
+ */
+ fr_packet_list_yank(pl, request->packet);
+
+- REDEBUG("No reply from server for ID %d socket %d",
+- request->packet->id, request->packet->sockfd);
++ RDEBUG("No reply from server for ID %d socket %d",
++ request->packet->id, request->packet->sockfd);
+ deallocate_id(request);
+
+ /*
+@@ -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((uint8_t) *optarg)) usage();
++
+ resend_count = atoi(optarg);
++
++ if (resend_count < 1) usage();
+ break;
+
+ case 'D':
+@@ -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);
+ }
+
++ openssl3_init();
++
+ /*
+ * Bind to the first specified IP address and port.
+ * This means we ignore later ones.
+@@ -1637,5 +1842,9 @@ int main(int argc, char **argv)
+ if ((stats.lost > 0) || (stats.failed > 0)) {
+ exit(1);
+ }
++
++ openssl3_free();
++
+ 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 <dirent.h>
++#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
++++ b/src/main/tls.c
+@@ -27,6 +27,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+
+ #include <freeradius-devel/radiusd.h>
+ #include <freeradius-devel/process.h>
++#include <freeradius-devel/modules.h>
+ #include <freeradius-devel/rad_assert.h>
+
+ #ifdef HAVE_SYS_STAT_H
+@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ #include <fcntl.h>
+ #endif
+
++#ifdef HAVE_DIRENT_H
++#include <dirent.h>
++#endif
++
+ #ifdef HAVE_UTIME_H
+ #include <utime.h>
+ #endif
+@@ -56,8 +61,25 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ # endif
+ # include <openssl/ssl.h>
+
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++# include <openssl/provider.h>
++
++static OSSL_PROVIDER *openssl_default_provider = NULL;
++static OSSL_PROVIDER *openssl_legacy_provider = NULL;
++#endif
++
+ #define LOG_PREFIX "tls"
+
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++#define ERR_get_error_line(_file, _line) ERR_get_error_all(_file, _line, NULL, NULL, NULL)
++
++#define FIPS_mode(_x) EVP_default_properties_is_fips_enabled(NULL)
++#define PEM_read_bio_DHparams(_bio, _x, _y, _z) PEM_read_bio_Parameters(_bio, &dh)
++#define SSL_CTX_set0_tmp_dh_pkey(_ctx, _dh) SSL_CTX_set_tmp_dh(_ctx, _dh)
++#define DH EVP_PKEY
++#define DH_free(_dh)
++#endif
++
+ #ifdef ENABLE_OPENSSL_VERSION_CHECK
+ typedef struct libssl_defect {
+ uint64_t high;
+@@ -153,6 +175,11 @@ static unsigned int record_plus(record_t *buf, void const *ptr,
+ static unsigned int record_minus(record_t *buf, void *ptr,
+ unsigned int size);
+
++typedef struct {
++ char const *name;
++ SSL_CTX *ctx;
++} fr_realm_ctx_t;
++
+ DIAG_OFF(format-nonliteral)
+ /** Print errors in the TLS thread local error stack
+ *
+@@ -191,9 +218,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
+
+ /* Extra verbose */
+ if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) {
+- ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer);
+ } else {
+- ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer);
+ }
+
+ talloc_free(p);
+@@ -205,7 +232,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
+ * Print the error we were given, irrespective
+ * of whether there were any OpenSSL errors.
+ */
+- ROPTIONAL(RERROR, ERROR, "%s", p);
++ ROPTIONAL(RERROR, ERROR, "(TLS) %s", p);
+ talloc_free(p);
+ }
+
+@@ -217,9 +244,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
+ ERR_error_string_n(error, buffer, sizeof(buffer));
+ /* Extra verbose */
+ if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) {
+- ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer);
+ } else {
+- ROPTIONAL(REDEBUG, ERROR, "%s", buffer);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer);
+ }
+ in_stack++;
+ } while ((error = ERR_get_error_line(&file, &line)));
+@@ -309,11 +336,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con
+ * being regarded as "dead".
+ */
+ case SSL_ERROR_SYSCALL:
+- ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret);
+ return 0;
+
+ case SSL_ERROR_SSL:
+- ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret);
+ return 0;
+
+ /*
+@@ -323,7 +350,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con
+ * the code needs updating here.
+ */
+ default:
+- ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret);
++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret);
+ return 0;
+ }
+
+@@ -338,7 +365,7 @@ static bool identity_is_safe(const char *identity)
+ if (!identity) return true;
+
+ while ((c = *(identity++)) != '\0') {
+- if (isalpha((int) c) || isdigit((int) c) || isspace((int) c) ||
++ if (isalpha((uint8_t) c) || isdigit((uint8_t) c) || isspace((uint8_t) c) ||
+ (c == '@') || (c == '-') || (c == '_') || (c == '.')) {
+ continue;
+ }
+@@ -369,24 +396,32 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
+ FR_TLS_EX_INDEX_REQUEST);
+ if (request && conf->psk_query) {
+ size_t hex_len;
+- VALUE_PAIR *vp;
++ VALUE_PAIR *vp, **certs;
++ TALLOC_CTX *talloc_ctx;
+ char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */
+
+ /*
+ * The passed identity is weird. Deny it.
+ */
+ if (!identity_is_safe(identity)) {
+- RWDEBUG("Invalid characters in PSK identity %s", identity);
++ RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity);
+ return 0;
+ }
+
+ vp = pair_make_request("TLS-PSK-Identity", identity, T_OP_SET);
+ if (!vp) return 0;
+
++ certs = (VALUE_PAIR **)SSL_get_ex_data(ssl, fr_tls_ex_index_certs);
++ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
++ fr_assert(certs != NULL); /* pointer to sock->certs */
++ fr_assert(talloc_ctx != NULL); /* sock */
++
++ fr_pair_add(certs, fr_pair_copy(talloc_ctx, vp));
++
+ hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query,
+ NULL, NULL);
+ if (!hex_len) {
+- RWDEBUG("PSK expansion returned an empty string.");
++ RWDEBUG("(TLS) PSK expansion returned an empty string.");
+ return 0;
+ }
+
+@@ -396,7 +431,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
+ * the truncation, and complain about it.
+ */
+ if (hex_len > (2 * max_psk_len)) {
+- RWDEBUG("Returned PSK is too long (%u > %u)",
++ RWDEBUG("(TLS) Returned PSK is too long (%u > %u)",
+ (unsigned int) hex_len, 2 * max_psk_len);
+ return 0;
+ }
+@@ -419,7 +454,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
+ * static identity.
+ */
+ if (strcmp(identity, conf->psk_identity) != 0) {
+- ERROR("Supplied PSK identity %s does not match configuration. Rejecting.",
++ ERROR("(TKS) Supplied PSK identity %s does not match configuration. Rejecting.",
+ identity);
+ return 0;
+ }
+@@ -475,8 +510,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize)
+ #endif
+ }
+
+-
+-
+ static int _tls_session_free(tls_session_t *ssn)
+ {
+ /*
+@@ -492,6 +525,52 @@ static int _tls_session_free(tls_session_t *ssn)
+ return 0;
+ }
+
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
++/*
++ * By setting the environment variable SSLKEYLOGFILE to a filename keying
++ * material will be exported that you may use with Wireshark to decode any
++ * TLS flows. Please see the following for more details:
++ *
++ * https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption
++ *
++ * An example logging session is (you should delete the file on each run):
++ *
++ * rm -f /tmp/sslkey.log; env SSLKEYLOGFILE=/tmp/sslkey.log freeradius -X | tee /tmp/debug
++ */
++static void tls_keylog_cb(UNUSED const SSL *ssl, const char *line)
++{
++ int fd;
++ size_t len;
++ const char *filename;
++ // less than _POSIX_PIPE_BUF (512) guarantees writes are atomic for O_APPEND
++ char buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH];
++
++ filename = getenv("SSLKEYLOGFILE");
++ if (!filename) return;
++
++ len = strlen(line);
++ if ((len + 1) > sizeof(buffer)) {
++ DEBUG("SSLKEYLOGFILE buffer not large enough, max %lu, required %lu", sizeof(buffer), len + 1);
++ return;
++ }
++
++ memcpy(buffer, line, len);
++ buffer[len] = '\n';
++
++ fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
++ if (fd < 0) {
++ fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno));
++ return;
++ }
++
++ if (write(fd, buffer, len + 1) == -1) {
++ DEBUG("Failed to write to file %s: %s", filename, strerror(errno));
++ }
++
++ close(fd);
++}
++#endif
++
+ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs)
+ {
+ int ret;
+@@ -506,6 +585,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
+
+ ssn->ctx = conf->ctx;
+ ssn->mtu = conf->fragment_size;
++ ssn->conf = conf;
+
+ SSL_CTX_set_mode(ssn->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY);
+
+@@ -516,8 +596,15 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
+ }
+
+ request = request_alloc(ssn);
++ request->packet = rad_alloc(request, false);
++ request->reply = rad_alloc(request, false);
++
+ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request);
+
++ if (conf->fix_cert_order) {
++ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER, (void *) &conf->fix_cert_order);
++ }
++
+ /*
+ * Add the message callback to identify what type of
+ * message/handshake is passed
+@@ -537,17 +624,19 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
+ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf);
+ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn);
+ if (certs) SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)certs);
++
+ SSL_set_fd(ssn->ssl, fd);
+- ret = SSL_connect(ssn->ssl);
+
++ ret = SSL_connect(ssn->ssl);
+ if (ret < 0) {
+ switch (SSL_get_error(ssn->ssl, ret)) {
+- default:
+- break;
+-
+-
++ default:
++ break;
+
+ case SSL_ERROR_WANT_READ:
++ ssn->connected = false;
++ return ssn;
++
+ case SSL_ERROR_WANT_WRITE:
+ ssn->connected = false;
+ return ssn;
+@@ -555,7 +644,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
+ }
+
+ if (ret <= 0) {
+- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)");
++ tls_error_io_log(NULL, ssn, ret, "Failed in connecting TLS session.");
+ talloc_free(ssn);
+
+ return NULL;
+@@ -575,18 +664,61 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
+ * @param conf to use to configure the tls session.
+ * @param request The current #REQUEST.
+ * @param client_cert Whether to require a client_cert.
++ * @param allow_tls13 Whether to allow or forbid TLS 1.3.
+ * @return a new session on success, or NULL on error.
+ */
+-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert)
++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert,
++#ifndef TLS1_3_VERSION
++ UNUSED
++#endif
++ bool allow_tls13)
+ {
+ tls_session_t *state = NULL;
+ SSL *new_tls = NULL;
+ int verify_mode = 0;
+ VALUE_PAIR *vp;
++ X509_STORE *new_cert_store;
+
+ rad_assert(request != NULL);
+
+- RDEBUG2("Initiating new TLS session");
++ RDEBUG2("(TLS) Initiating new session");
++
++ /*
++ * Replace X509 store if it is time to update CRLs/certs in ca_path
++ */
++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) {
++ pthread_mutex_lock(&conf->mutex);
++ /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */
++ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) {
++ RDEBUG2("Flushing X509 store to re-read data from ca_path dir");
++
++ if ((new_cert_store = fr_init_x509_store(conf)) == NULL) {
++ RERROR("(TLS) Error replacing X509 store, out of memory (?)");
++ } else {
++ if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store);
++ /*
++ * Swap empty store with the old one.
++ */
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
++ conf->old_x509_store = SSL_CTX_get_cert_store(conf->ctx);
++ /* Bump refcnt so the store is kept allocated till next store replacement */
++ X509_STORE_up_ref(conf->old_x509_store);
++ SSL_CTX_set_cert_store(conf->ctx, new_cert_store);
++#else
++ /*
++ * We do not use SSL_CTX_set_cert_store() call here because
++ * we are not sure that old X509 store is not in the use by some
++ * thread (i.e. cert check in progress).
++ * Keep it allocated till next store replacement.
++ */
++ conf->old_x509_store = conf->ctx->cert_store;
++ conf->ctx->cert_store = new_cert_store;
++#endif
++ conf->ca_path_last_reload = request->timestamp;
++ }
++ }
++ pthread_mutex_unlock(&conf->mutex);
++ }
+
+ new_tls = SSL_new(conf->ctx);
+ if (new_tls == NULL) {
+@@ -594,11 +726,33 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
+ return NULL;
+ }
+
++#ifdef TLS1_3_VERSION
++ /*
++ * Disallow TLS 1.3 for FAST.
++ *
++ * We need another magic configuration option to allow
++ * it.
++ */
++ if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) {
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ WARN("!! FORCING MAXIMUM TLS VERSION TO TLS 1.2 !!");
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ WARN("!! There is no standard for using this EAP method with TLS 1.3");
++ WARN("!! Please set tls_max_version = \"1.2\"");
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++
++ if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) {
++ tls_error_log(request, "Failed limiting maximum version to TLS 1.2");
++ return NULL;
++ }
++ }
++#endif
++
+ /* We use the SSL's "app_data" to indicate a call-back */
+ SSL_set_app_data(new_tls, NULL);
+
+ if ((state = talloc_zero(ctx, tls_session_t)) == NULL) {
+- RERROR("Error allocating memory for SSL state");
++ RERROR("(TLS) Error allocating memory for SSL state");
+ return NULL;
+ }
+ session_init(state);
+@@ -606,6 +760,14 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
+
+ state->ctx = conf->ctx;
+ state->ssl = new_tls;
++ state->conf = conf;
++
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
++ /*
++ * Set the keylog file if the admin requested it.
++ */
++ if (getenv("SSLKEYLOGFILE") != NULL) SSL_CTX_set_keylog_callback(state->ctx, tls_keylog_cb);
++#endif
+
+ /*
+ * Initialize callbacks
+@@ -637,6 +799,85 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
+ SSL_set_msg_callback_arg(new_tls, state);
+ SSL_set_info_callback(new_tls, cbtls_info);
+
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ /*
++ * Allow policies to load context-specific certificate chains.
++ */
++ vp = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_FILE, 0, TAG_ANY);
++ if (vp) {
++ VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY);
++ if (!key) key = vp;
++
++ RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue);
++
++ if (conf->realms) {
++ fr_realm_ctx_t my_r, *r;
++
++ /*
++ * Use a pre-existing SSL CTX, if
++ * available. Note that due to OpenSSL
++ * issues, this really changes only the
++ * certificate files, and leaves all
++ * other fields alone. e.g. you can't
++ * select a different TLS version.
++ *
++ * This is fine for our purposes in v3.
++ * Due to how we build them, the various
++ * additional SSL_CTXs are identical to
++ * the main one, except for certs.
++ */
++ my_r.name = vp->vp_strvalue;
++ r = fr_hash_table_finddata(conf->realms, &my_r);
++ if (r) {
++ (void) SSL_set_SSL_CTX(state->ssl, r->ctx);
++ goto after_chain;
++ }
++
++ /*
++ * Else fall through to trying to dynamically load the certs.
++ */
++ }
++
++ if (conf->file_type) {
++ if (SSL_use_certificate_chain_file(state->ssl, vp->vp_strvalue) != 1) {
++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
++ vp->vp_strvalue);
++ error:
++ talloc_free(state);
++ return NULL;
++ }
++ } else {
++ if (SSL_use_certificate_file(state->ssl, vp->vp_strvalue, SSL_FILETYPE_ASN1) != 1) {
++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
++ vp->vp_strvalue);
++ goto error;
++ }
++ }
++
++ /*
++ * Note that there is either no password, or it
++ * has to be the same as what's in the
++ * configuration.
++ *
++ * There is just no additional security to
++ * putting a password into the same file system
++ * as the private key.
++ */
++ if (SSL_use_PrivateKey_file(state->ssl, key->vp_strvalue, SSL_FILETYPE_PEM) != 1) {
++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
++ key->vp_strvalue);
++ goto error;
++ }
++
++ if (SSL_check_private_key(state->ssl) != 1) {
++ tls_error_log(request, "Failed validating TLS session certificate \"%s\"",
++ vp->vp_strvalue);
++ goto error;
++ }
++ }
++after_chain:
++#endif
++
+ /*
+ * In Server mode we only accept.
+ */
+@@ -646,7 +887,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
+ * Verify the peer certificate, if asked.
+ */
+ if (client_cert) {
+- RDEBUG2("Setting verify mode to require certificate from client");
++ RDEBUG2("(TLS) Setting verify mode to require certificate from client");
+ verify_mode = SSL_VERIFY_PEER;
+ verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ verify_mode |= SSL_VERIFY_CLIENT_ONCE;
+@@ -670,10 +911,41 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
+ * just too much.
+ */
+ state->mtu = conf->fragment_size;
++#define EAP_TLS_MAGIC_OVERHEAD (63)
++
++ /*
++ * If the packet contains an MTU, then use that. We
++ * trust the admin!
++ */
+ vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY);
+- if (vp && (vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) {
+- state->mtu = vp->vp_integer;
++ if (vp) {
++ if ((vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) {
++ state->mtu = vp->vp_integer;
++ }
++
++ } else if (request->parent) {
++ /*
++ * If there's a parent request, we look for what
++ * MTU was set there. Then, we use an MTU which
++ * accounts for the extra overhead of nesting EAP
++ * + TLS inside of EAP + TLS.
++ */
++ vp = fr_pair_find_by_num(request->parent->state, PW_FRAMED_MTU, 0, TAG_ANY);
++ if (vp && (vp->vp_integer > (100 + EAP_TLS_MAGIC_OVERHEAD)) && (vp->vp_integer <= state->mtu)) {
++ state->mtu = vp->vp_integer - EAP_TLS_MAGIC_OVERHEAD;
++ }
++ }
++
++ /*
++ * Cache / update the Framed-MTU in the session-state
++ * list.
++ */
++ vp = fr_pair_find_by_num(request->state, PW_FRAMED_MTU, 0, TAG_ANY);
++ if (!vp) {
++ vp = fr_pair_afrom_num(request->state_ctx, PW_FRAMED_MTU, 0);
++ fr_pair_add(&request->state, vp);
+ }
++ if (vp) vp->vp_integer = state->mtu;
+
+ if (conf->session_cache_enable) state->allow_session_resumption = true; /* otherwise it's false */
+
+@@ -697,12 +969,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
+ {
+ int err;
+
+- if (ssn->invalid_hb_used) return 0;
++ if (ssn->invalid_hb_used) {
++ REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection");
++ return 0;
++ }
+
+ if (ssn->dirty_in.used > 0) {
+ err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used);
+ if (err != (int) ssn->dirty_in.used) {
+- REDEBUG("TLS - Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
++ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
+ record_init(&ssn->dirty_in);
+ return 0;
+ }
+@@ -716,24 +991,26 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
+ return 1;
+ }
+
+- if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)")) return 0;
++ if (!tls_error_io_log(request, ssn, err, "Failed reading from OpenSSL")) return 0;
+
+ /* Some Extra STATE information for easy debugging */
+ if (!ssn->is_init_finished && SSL_is_init_finished(ssn->ssl)) {
+ VALUE_PAIR *vp;
+ char const *str_version;
+
+- RDEBUG2("TLS - Connection Established");
++ RDEBUG2("(TLS) Connection Established");
+ ssn->is_init_finished = true;
+
+ vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0);
+ if (vp) {
+ fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(SSL_get_current_cipher(ssn->ssl)));
+ fr_pair_add(&request->state, vp);
++ RINDENT();
+ rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
++ REXDENT();
+ }
+
+- switch (ssn->info.version) {
++ switch (SSL_version(ssn->ssl)) {
+ case SSL2_VERSION:
+ str_version = "SSL 2.0";
+ break;
+@@ -767,13 +1044,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
+ if (vp) {
+ fr_pair_value_strcpy(vp, str_version);
+ fr_pair_add(&request->state, vp);
++ RINDENT();
+ rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
++ REXDENT();
+ }
+ }
+- else if (SSL_in_init(ssn->ssl)) { RDEBUG2("TLS - In Handshake Phase"); }
+- else if (SSL_in_before(ssn->ssl)) { RDEBUG2("TLS - Before Handshake Phase"); }
+- else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("TLS - In Accept mode"); }
+- else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("TLS - In Connect mode"); }
++ else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); }
++ else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); }
++ else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); }
++ else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); }
+
+ #if OPENSSL_VERSION_NUMBER >= 0x10001000L
+ /*
+@@ -791,7 +1070,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
+ * to get the session is a hard fail.
+ */
+ if (!ssn->ssl_session && ssn->is_init_finished) {
+- RDEBUG("TLS - Failed getting session");
++ RDEBUG("(TLS) Failed getting session");
+ return 0;
+ }
+ }
+@@ -805,25 +1084,25 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
+ err = BIO_read(ssn->from_ssl, ssn->dirty_out.data,
+ sizeof(ssn->dirty_out.data));
+ if (err > 0) {
+- RDEBUG2("TLS - got %d bytes of data", err);
++ RDEBUG3("(TLS) got %d bytes of data", err);
+ ssn->dirty_out.used = err;
+
+ } else if (BIO_should_retry(ssn->from_ssl)) {
+ record_init(&ssn->dirty_in);
+- RDEBUG2("TLS - Asking for more data in tunnel.");
++ RDEBUG2("(TLS) Asking for more data in tunnel.");
+ return 1;
+
+ } else {
+- tls_error_log(NULL, "Error reading from SSL BIO");
++ tls_error_log(NULL, "Error reading from OpenSSL");
+ record_init(&ssn->dirty_in);
+- RDEBUG2("TLS - Tunnel data is established.");
+ return 0;
+ }
+ } else {
+-
+- RDEBUG2("TLS - Application data.");
+- /* Its clean application data, do whatever we want */
++ RDEBUG2("(TLS) Application data.");
++ /* Its clean application data, leave whatever is in the buffer */
++#if 0
+ record_init(&ssn->clean_out);
++#endif
+ }
+
+ /* We are done with dirty_in, reinitialize it */
+@@ -855,13 +1134,12 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn)
+ record_minus(&ssn->clean_in, NULL, written);
+
+ /* Get the dirty data from Bio to send it */
+- err = BIO_read(ssn->from_ssl, ssn->dirty_out.data,
+- sizeof(ssn->dirty_out.data));
++ err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used,
++ sizeof(ssn->dirty_out.data) - ssn->dirty_out.used);
+ if (err > 0) {
+- ssn->dirty_out.used = err;
++ ssn->dirty_out.used += err;
+ } else {
+- if (!tls_error_io_log(request, ssn, err,
+- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) {
++ if (!tls_error_io_log(request, ssn, err, "Failed writing to OpenSSL")) {
+ return 0;
+ }
+ }
+@@ -963,7 +1241,10 @@ void tls_session_information(tls_session_t *tls_session)
+ {
+ char const *str_write_p, *str_version, *str_content_type = "";
+ char const *str_details1 = "", *str_details2= "";
++ char const *details = NULL;
+ REQUEST *request;
++ VALUE_PAIR *vp;
++ char content_type[16], alert_buf[16];
+ char buffer[32];
+
+ /*
+@@ -972,9 +1253,20 @@ void tls_session_information(tls_session_t *tls_session)
+ */
+ if (rad_debug_lvl == 0) return;
+
+- str_write_p = tls_session->info.origin ? ">>> send" : "<<< recv";
++ /*
++ * OpenSSL calls this function with 'pseudo' content
++ * types. The user doesn't care about them, so suppress them.
++ */
++ if (tls_session->info.content_type > UINT8_MAX) return;
++
++ request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST);
++ if (!request) return;
++
++ str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv";
++
++#define FROM_CLIENT (tls_session->info.origin == 0)
+
+- switch (tls_session->info.version) {
++ switch (SSL_version(tls_session->ssl)) {
+ case SSL2_VERSION:
+ str_version = "SSL 2.0 ";
+ break;
+@@ -1001,13 +1293,12 @@ void tls_session_information(tls_session_t *tls_session)
+ #endif
+
+ default:
+- sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", tls_session->info.version);
++ sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", SSL_version(tls_session->ssl));
+ str_version = buffer;
+ break;
+ }
+
+- if (tls_session->info.version == SSL3_VERSION ||
+- tls_session->info.version == TLS1_VERSION) {
++ if (1) {
+ switch (tls_session->info.content_type) {
+ case SSL3_RT_CHANGE_CIPHER_SPEC:
+ str_content_type = "ChangeCipherSpec";
+@@ -1026,7 +1317,8 @@ void tls_session_information(tls_session_t *tls_session)
+ break;
+
+ default:
+- str_content_type = "UnknownContentType";
++ snprintf(content_type, sizeof(content_type), "content=%d", tls_session->info.content_type);
++ str_content_type = content_type;
+ break;
+ }
+
+@@ -1045,9 +1337,12 @@ void tls_session_information(tls_session_t *tls_session)
+ }
+
+ str_details2 = " ???";
++ details = "there is a failure inside the TLS protocol exchange";
++
+ switch (tls_session->info.alert_description) {
+ case SSL3_AD_CLOSE_NOTIFY:
+ str_details2 = " close_notify";
++ details = "the connection has been closed, and no further TLS exchanges will take place";
+ break;
+
+ case SSL3_AD_UNEXPECTED_MESSAGE:
+@@ -1074,24 +1369,34 @@ void tls_session_information(tls_session_t *tls_session)
+ str_details2 = " handshake_failure";
+ break;
+
++ case SSL3_AD_NO_CERTIFICATE:
++ str_details2 = " no_certificate";
++ details = "the server did not present a certificate to the client";
++ break;
++
+ case SSL3_AD_BAD_CERTIFICATE:
+ str_details2 = " bad_certificate";
++ details = "it believes the server certificate is invalid or malformed";
+ break;
+
+ case SSL3_AD_UNSUPPORTED_CERTIFICATE:
+ str_details2 = " unsupported_certificate";
++ details = "it does not understand the certificate presented by the server";
+ break;
+
+ case SSL3_AD_CERTIFICATE_REVOKED:
+ str_details2 = " certificate_revoked";
++ details = "it believes that the server certificate has been revoked";
+ break;
+
+ case SSL3_AD_CERTIFICATE_EXPIRED:
+ str_details2 = " certificate_expired";
++ details = "it believes that the server certificate has expired. Either renew the server certificate, or check the time on the client";
+ break;
+
+ case SSL3_AD_CERTIFICATE_UNKNOWN:
+ str_details2 = " certificate_unknown";
++ details = "it does not recognize the server certificate";
+ break;
+
+ case SSL3_AD_ILLEGAL_PARAMETER:
+@@ -1100,6 +1405,7 @@ void tls_session_information(tls_session_t *tls_session)
+
+ case TLS1_AD_UNKNOWN_CA:
+ str_details2 = " unknown_ca";
++ details = "it does not recognize the CA used to issue the server certificate. Please update the client so that it knows about the CA";
+ break;
+
+ case TLS1_AD_ACCESS_DENIED:
+@@ -1120,6 +1426,18 @@ void tls_session_information(tls_session_t *tls_session)
+
+ case TLS1_AD_PROTOCOL_VERSION:
+ str_details2 = " protocol_version";
++ details = "the client does not accept the version of TLS negotiated by the server";
++
++#ifdef TLS1_3_VERSION
++ /*
++ * Complain about OpenSSL bugs.
++ */
++ if ((SSL_version(tls_session->ssl) > tls_session->conf->max_version) &&
++ (rad_debug_lvl > 0)) {
++ WARN("TLS 1.3 has been negotiated even though it was disabled. This is an OpenSSL Bug.");
++ WARN("Please set: cipher_list = \"DEFAULT@SECLEVEL=1\" in the tls {...} section.");
++ }
++#endif
+ break;
+
+ case TLS1_AD_INSUFFICIENT_SECURITY:
+@@ -1137,12 +1455,69 @@ void tls_session_information(tls_session_t *tls_session)
+ case TLS1_AD_NO_RENEGOTIATION:
+ str_details2 = " no_renegotiation";
+ break;
++
++#ifdef TLS13_AD_MISSING_EXTENSIONS
++ case TLS13_AD_MISSING_EXTENSIONS:
++ str_details2 = " missing_extensions";
++ details = "the server did not present a TLS extension which the client expected to be present. Please check the TLS libraries on the client and server for compatibility";
++ break;
++#endif
++
++#ifdef TLS13_AD_CERTIFICATE_REQUIRED
++ case TLS13_AD_CERTIFICATE_REQUIRED:
++ str_details2 = " certificate_required";
++ details = "the server did not present a certificate";
++ break;
++#endif
++
++#ifdef TLS1_AD_UNSUPPORTED_EXTENSION
++ case TLS1_AD_UNSUPPORTED_EXTENSION:
++ str_details2 = " unsupported_extension";
++ details = "the server has sent a TLS message which the client does not recognize. Please check the TLS libraries on the client and server for compatibility";
++ break;
++#endif
++
++#ifdef TLS1_AD_CERTIFICATE_UNOBTAINABLE
++ case TLS1_AD_CERTIFICATE_UNOBTAINABLE:
++ str_details2 = " certificate_unobtainable";
++ break;
++#endif
++
++#ifdef TLS1_AD_UNRECOGNIZED_NAME
++ case TLS1_AD_UNRECOGNIZED_NAME:
++ str_details2 = " unrecognized_name";
++ break;
++#endif
++
++#ifdef TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE
++ case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE:
++ str_details2 = " bad_certificate_status_response";
++ break;
++#endif
++
++#ifdef TLS1_AD_BAD_CERTIFICATE_HASH_VALUE
++ case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE:
++ str_details2 = " bad_certificate_hash_value";
++ break;
++#endif
++
++#ifdef TLS1_AD_UNKNOWN_PSK_IDENTITY
++ case TLS1_AD_UNKNOWN_PSK_IDENTITY:
++ str_details2 = " unknown_psk_identity";
++ break;
++#endif
++
++#ifdef TLS1_AD_NO_APPLICATION_PROTOCOL
++ case TLS1_AD_NO_APPLICATION_PROTOCOL:
++ str_details2 = " no_application_protocol";
++ break;
++#endif
+ }
+ }
+ }
+
+ if (tls_session->info.content_type == SSL3_RT_HANDSHAKE) {
+- str_details1 = "???";
++ str_details1 = "";
+
+ if (tls_session->info.record_len > 0) switch (tls_session->info.handshake_type) {
+ case SSL3_MT_HELLO_REQUEST:
+@@ -1157,6 +1532,18 @@ void tls_session_information(tls_session_t *tls_session)
+ str_details1 = ", ServerHello";
+ break;
+
++#ifdef SSL3_MT_NEWSESSION_TICKET
++ case SSL3_MT_NEWSESSION_TICKET:
++ str_details1 = ", NewSessionTicket";
++ break;
++#endif
++
++#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS
++ case SSL3_MT_ENCRYPTED_EXTENSIONS:
++ str_details1 = ", EncryptedExtensions";
++ break;
++#endif
++
+ case SSL3_MT_CERTIFICATE:
+ str_details1 = ", Certificate";
+ break;
+@@ -1184,31 +1571,52 @@ void tls_session_information(tls_session_t *tls_session)
+ case SSL3_MT_FINISHED:
+ str_details1 = ", Finished";
+ break;
++
++#ifdef SSL3_MT_KEY_UPDATE
++ case SSL3_MT_KEY_UPDATE:
++ str_content_type = "KeyUpdate";
++ break;
++#endif
++
++ default:
++ snprintf(alert_buf, sizeof(alert_buf), ", type=%d", tls_session->info.handshake_type);
++ str_details1 = alert_buf;
++ break;
+ }
+ }
+ }
+
+ snprintf(tls_session->info.info_description,
+ sizeof(tls_session->info.info_description),
+- "%s %s%s [length %04lx]%s%s\n",
++ "%s %s%s%s%s",
+ str_write_p, str_version, str_content_type,
+- (unsigned long)tls_session->info.record_len,
+ str_details1, str_details2);
+
+- request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST);
+- if (!request) return;
++ /*
++ * Cache the TLS session information in the session-state
++ * list, so it can be accessed by Post-Auth-Type
++ * Client-Lost { ... }
++ */
++ vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_INFORMATION, 0);
++ if (vp) {
++ fr_pair_value_strcpy(vp, tls_session->info.info_description);
++ fr_pair_add(&request->state, vp);
++ }
+
+ RDEBUG2("%s", tls_session->info.info_description);
++
++ if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details);
+ }
+
+ static CONF_PARSER cache_config[] = {
+ { "enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, session_cache_enable), "no" },
+
+- { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_timeout), "24" },
++ { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_lifetime), "24" },
+ { "name", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_id_name), NULL },
+
+ { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_cache_size), "255" },
+ { "persist_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_path), NULL },
++ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_server), NULL },
+ CONF_PARSER_TERMINATOR
+ };
+
+@@ -1256,6 +1664,7 @@ static CONF_PARSER tls_server_config[] = {
+ #ifdef X509_V_FLAG_CRL_CHECK_ALL
+ { "check_all_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, check_all_crl), "no" },
+ #endif
++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" },
+ { "allow_expired_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, allow_expired_crl), NULL },
+ { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL },
+ { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL },
+@@ -1263,6 +1672,14 @@ static CONF_PARSER tls_server_config[] = {
+ { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL },
+ { "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, require_client_cert), NULL },
+
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ { "sigalgs_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, sigalgs_list), NULL },
++#endif
++
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ { "reject_unknown_intermediate_ca", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disallow_untrusted), .dflt = "no", },
++#endif
++
+ #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+ #ifndef OPENSSL_NO_ECDH
+ { "ecdh_curve", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, ecdh_curve), "prime256v1" },
+@@ -1281,9 +1698,23 @@ static CONF_PARSER tls_server_config[] = {
+ { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
+ #endif
+
+- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
++
++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
++#if defined(TLS1_2_VERSION)
++ "1.2"
++#elif defined(TLS1_1_VERSION)
++ "1.1"
++#else
++ "1.0"
++#endif
++ },
++
++#ifdef WITH_RADIUSV11
++ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL },
++#endif
+
+- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
++ { "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL },
+
+ { "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config },
+
+@@ -1312,6 +1743,9 @@ static CONF_PARSER tls_client_config[] = {
+ { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL },
+ { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL },
+ { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL },
++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" },
++
++ { "fix_cert_order", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, fix_cert_order), NULL },
+
+ #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+ #ifndef OPENSSL_NO_ECDH
+@@ -1331,9 +1765,23 @@ static CONF_PARSER tls_client_config[] = {
+ { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
+ #endif
+
+- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
++
++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
++#if defined(TLS1_2_VERSION)
++ "1.2"
++#elif defined(TLS1_1_VERSION)
++ "1.1"
++#else
++ "1.0"
++#endif
++ },
++
++#ifdef WITH_RADIUSV11
++ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL },
++#endif
+
+- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
++ { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL },
+
+ CONF_PARSER_TERMINATOR
+ };
+@@ -1347,7 +1795,44 @@ static int load_dh_params(SSL_CTX *ctx, char *file)
+ DH *dh = NULL;
+ BIO *bio;
+
+- if (!file) return 0;
++ /*
++ * Prior to trying to load the file, check what OpenSSL will do with it.
++ *
++ * Certain downstreams (such as RHEL) will ignore user-provided dhparams
++ * in FIPS mode, unless the specified parameters are FIPS-approved.
++ * However, since OpenSSL >= 1.1.1 will automatically select parameters
++ * anyways, there's no point in attempting to load them.
++ *
++ * Change suggested by @t8m
++ */
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ if (FIPS_mode() > 0) {
++ WARN(LOG_PREFIX ": Ignoring user-selected DH parameters in FIPS mode. Using defaults.");
++ file = NULL;
++ }
++
++ /*
++ * No dh file, set auto context.
++ */
++ if (!file) {
++ if (!SSL_CTX_set_dh_auto(ctx, 1)) {
++ ERROR(LOG_PREFIX ": Unable to set DH parameters");
++ return -1;
++ }
++
++ return 0;
++ }
++
++ WARN(LOG_PREFIX ": Setting DH parameters from %s - this is no longer necessary.", file);
++ WARN(LOG_PREFIX ": You should comment out the 'dh_file' configuration item.");
++
++#else
++ if (!file) {
++ WARN(LOG_PREFIX ": Cannot set DH parameters. DH cipher suites may not work.");
++ return 0;
++ }
++#endif
++
+
+ if ((bio = BIO_new_file(file, "r")) == NULL) {
+ ERROR(LOG_PREFIX ": Unable to open DH file - %s", file);
+@@ -1422,7 +1907,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+
+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
+ if (!conf) {
+- RWDEBUG("Failed to find TLS configuration in session");
++ RWDEBUG("(TLS) Failed to find TLS configuration in session");
+ return 0;
+ }
+
+@@ -1439,7 +1924,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+ blob_len = i2d_SSL_SESSION(sess, NULL);
+ if (blob_len < 1) {
+ /* something went wrong */
+- if (request) RWDEBUG("Session serialisation failed, couldn't determine required buffer length");
++ if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length");
+ return 0;
+ }
+
+@@ -1447,14 +1932,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+ /* alloc and convert to ASN.1 */
+ sess_blob = malloc(blob_len);
+ if (!sess_blob) {
+- RWDEBUG("Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len);
++ RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len);
+ return 0;
+ }
+ /* openssl mutates &p */
+ p = sess_blob;
+ rv = i2d_SSL_SESSION(sess, &p);
+ if (rv != blob_len) {
+- if (request) RWDEBUG("Session serialisation failed");
++ if (request) RWDEBUG("(TLS) Session serialisation failed");
+ goto error;
+ }
+
+@@ -1463,7 +1948,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+ conf->session_cache_path, FR_DIR_SEP, buffer);
+ fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR);
+ if (fd < 0) {
+- if (request) RERROR("Session serialisation failed, failed opening session file %s: %s",
++ if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s",
+ filename, fr_syserror(errno));
+ goto error;
+ }
+@@ -1486,7 +1971,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+ while (todo > 0) {
+ rv = write(fd, p, todo);
+ if (rv < 1) {
+- if (request) RWDEBUG("Failed writing session: %s", fr_syserror(errno));
++ if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno));
+ close(fd);
+ goto error;
+ }
+@@ -1494,7 +1979,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
+ todo -= rv;
+ }
+ close(fd);
+- if (request) RWDEBUG("Wrote session %s to %s (%d bytes)", buffer, filename, blob_len);
++ if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len);
+ }
+
+ error:
+@@ -1595,7 +2080,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+
+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
+ if (!conf) {
+- RWDEBUG("Failed to find TLS configuration in session");
++ RWDEBUG("(TLS) Failed to find TLS configuration in session");
+ return NULL;
+ }
+
+@@ -1617,20 +2102,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+ snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+- RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno));
++ RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno));
+ goto error;
+ }
+
+ rv = fstat(fd, &st);
+ if (rv < 0) {
+- RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno));
++ RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno));
+ close(fd);
+ goto error;
+ }
+
+ sess_data = talloc_array(NULL, unsigned char, st.st_size);
+ if (!sess_data) {
+- RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size);
++ RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size);
+ close(fd);
+ goto error;
+ }
+@@ -1640,7 +2125,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+ while (todo > 0) {
+ rv = read(fd, q, todo);
+ if (rv < 1) {
+- RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno));
++ RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno));
+ close(fd);
+ goto error;
+ }
+@@ -1664,7 +2149,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+ memcpy(&o, &p, sizeof(o));
+ sess = d2i_SSL_SESSION(NULL, o, st.st_size);
+ if (!sess) {
+- RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
+ goto error;
+ }
+
+@@ -1674,7 +2159,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+ rv = pairlist_read(talloc_ctx, filename, &pairlist, 1);
+ if (rv < 0) {
+ /* not safe to un-persist a session w/o VPs */
+- RWDEBUG("Failed loading persisted VPs for session %s", buffer);
++ RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer);
+ SSL_SESSION_free(sess);
+ sess = NULL;
+ goto error;
+@@ -1708,12 +2193,27 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
+ if (vp) {
+ if ((request->timestamp + vp->vp_integer) > expires) {
+ vp->vp_integer = expires - request->timestamp;
+- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration",
++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
+ vp->vp_integer);
+ }
+ }
+ }
+
++ /*
++ * Resumption MUST use the same EAP type as from
++ * the original packet.
++ */
++ vp = fr_pair_find_by_num(pairlist->reply, PW_EAP_TYPE, 0, TAG_ANY);
++ if (vp) {
++ VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
++
++ if (type && (type->vp_integer != vp->vp_integer)) {
++ REDEBUG("Resumption has changed EAP types for session %s", buffer);
++ REDEBUG("Rejecting session due to protocol violations");
++ goto error;
++ }
++ }
++
+ /* move the cached VPs into the session */
+ fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &pairlist->reply, 0, 0, TAG_ANY);
+
+@@ -1733,22 +2233,366 @@ error:
+ return sess;
+ }
+
+-#ifdef HAVE_OPENSSL_OCSP_H
+-
+-/** Extract components of OCSP responser URL from a certificate
+- *
+- * @param[in] cert to extract URL from.
+- * @param[out] host_out Portion of the URL (must be freed with free()).
+- * @param[out] port_out Port portion of the URL (must be freed with free()).
+- * @param[out] path_out Path portion of the URL (must be freed with free()).
+- * @param[out] is_https Whether the responder should be contacted using https.
+- * @return
+- * - 0 if no valid URL is contained in the certificate.
+- * - 1 if a URL was found and parsed.
+- * - -1 if at least one URL was found, but none could be parsed.
+- */
+-static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out,
+- char **path_out, int *is_https)
++static size_t tls_session_id_binary(SSL_SESSION *ssn, uint8_t *buffer, size_t bufsize)
++{
++#if OPENSSL_VERSION_NUMBER < 0x10001000L
++ size_t size;
++
++ size = ssn->session_id_length;
++ if (size > bufsize) size = bufsize;
++
++ memcpy(buffer, ssn->session_id, size);
++ return size;
++#else
++ unsigned int size;
++ uint8_t const *p;
++
++ p = SSL_SESSION_get_id(ssn, &size);
++ if (size > bufsize) size = bufsize;
++
++ memcpy(buffer, p, size);
++ return size;
++#endif
++}
++
++/*
++ * From TLS-Cache-Method
++ *
++ * All of the save / clear / load callbacks are done with any
++ * OpenSSL locks *unlocked*. So says the OpenSSL code.
++ */
++#define CACHE_SAVE (1)
++#define CACHE_LOAD (2)
++#define CACHE_CLEAR (3)
++#define CACHE_REFRESH (4)
++
++static REQUEST *cache_init_fake_request(fr_tls_server_conf_t const *conf, SSL_SESSION *sess, SSL *ssl,
++ uint8_t const *data, size_t size)
++{
++ VALUE_PAIR *vp;
++ REQUEST *fake, *request = NULL;
++ uint8_t buffer[MAX_SESSION_SIZE];
++
++ if (sess) {
++ size = tls_session_id_binary(sess, buffer, sizeof(buffer));
++ data = buffer;
++ }
++
++ /*
++ * We get called essentially at random by OpenSSL, with
++ * no information other than the session ID. As a
++ * result, we have to manually set up our own request.
++ */
++ if (ssl) request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
++
++ if (request) {
++ fake = request_alloc_fake(request);
++ } else {
++ fake = request_alloc(NULL);
++ fake->packet = rad_alloc(fake, false);
++ fake->reply = rad_alloc(fake, false);
++ }
++
++ vp = fr_pair_afrom_num(fake->packet, PW_TLS_SESSION_ID, 0);
++ if (!vp) {
++ talloc_free(fake);
++ return NULL;
++ }
++
++ fr_pair_value_memcpy(vp, data, size);
++ fr_pair_add(&fake->packet->vps, vp);
++
++ fake->server = conf->session_cache_server;
++
++ return fake;
++}
++
++/*
++ * Clear cached data
++ */
++static void cbtls_cache_clear(SSL_CTX *ctx, SSL_SESSION *sess)
++{
++ fr_tls_server_conf_t *conf;
++ REQUEST *fake;
++
++ conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx);
++ if (!conf) {
++ DEBUG(LOG_PREFIX ": Failed to find TLS configuration in session");
++ return;
++ }
++
++ /*
++ * Find the SSL ID from the session, and delete it.
++ *
++ * Don't bother with any parent request. We're in a
++ * timer callback, and there is no request available.
++ */
++ fake = cache_init_fake_request(conf, sess, NULL, NULL, 0);
++ if (!fake) return;
++
++ /*
++ * Use &request:TLS-Session-Id to clear the cache entry.
++ */
++ (void) process_post_auth(CACHE_CLEAR, fake);
++ talloc_free(fake);
++ return;
++}
++
++/*
++ * OpenSSL calls this function in order to save the session
++ * BEFORE it has sent the final TLS success. So our process here
++ * is to say "yes, we saved it", and then do the *actual* saving
++ * after the TLS success has been sent.
++ */
++static int cbtls_cache_save(UNUSED SSL *ssl, UNUSED SSL_SESSION *sess)
++{
++ return 0;
++}
++
++static int cbtls_cache_save_vps(SSL *ssl, SSL_SESSION *sess, VALUE_PAIR *vps)
++{
++ fr_tls_server_conf_t *conf;
++ VALUE_PAIR *vp;
++ REQUEST *fake = NULL;
++ size_t size, rv;
++ uint8_t *p, *sess_blob = NULL;
++
++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
++ if (!conf) return 0;
++
++ /*
++ * Find the SSL ID from the session, and save it.
++ *
++ * Save anything from the parent request.
++ */
++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0);
++ if (!fake) return 0;
++
++ /* find out what length data we need */
++ size = i2d_SSL_SESSION(sess, NULL);
++ if (size < 1) return 0;
++
++ /* Do not convert to TALLOC - it's passed to OpenSSL */
++ /* alloc and convert to ASN.1 */
++ MEM(sess_blob = malloc(size));
++
++ /* openssl mutates &p */
++ p = sess_blob;
++ rv = i2d_SSL_SESSION(sess, &p);
++ if (rv != size) goto error;
++
++ vp = fr_pair_afrom_num(fake->state_ctx, PW_TLS_SESSION_DATA, 0);
++ if (!vp) goto error;
++
++ fr_pair_value_memcpy(vp, sess_blob, size);
++ fr_pair_add(&fake->state, vp);
++
++ if (vps) fr_pair_add(&fake->reply->vps, fr_pair_list_copy(fake->reply, vps));
++
++ /*
++ * Use &request:TLS-Session-Id to save the
++ * &session-state:TLS-Session-Data values.
++ *
++ * The current &reply: list is the list of VPs which
++ * should be cached.
++ *
++ * Any other attributes which need to be saved can be
++ * read from the &outer.reply: list.
++ */
++ (void) process_post_auth(CACHE_SAVE, fake);
++
++error:
++ if (fake) talloc_free(fake);
++ free(sess_blob);
++
++ return 0;
++}
++
++static int cbtls_cache_refresh(SSL *ssl, SSL_SESSION *sess)
++{
++ fr_tls_server_conf_t *conf;
++ REQUEST *fake = NULL;
++
++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
++ if (!conf) return 0;
++
++ /*
++ * Find the SSL ID from the session, and save it.
++ *
++ * Save anything from the parent request.
++ */
++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0);
++ if (!fake) return 0;
++ /*
++ * Use &request:TLS-Session-Id to update the cache
++ * entry so that it doesn't not expire.
++ */
++ (void) process_post_auth(CACHE_REFRESH, fake);
++
++ talloc_free(fake);
++
++ return 0;
++}
++
++#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
++static SSL_SESSION *cbtls_cache_load(SSL *ssl, unsigned char *data, int len, int *copy)
++#else
++static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int len, int *copy)
++#endif
++{
++ fr_tls_server_conf_t *conf;
++ size_t size;
++ uint8_t const *p;
++ VALUE_PAIR *vp, *vps;
++ TALLOC_CTX *talloc_ctx;
++ SSL_SESSION *sess = NULL;
++ REQUEST *fake = NULL;
++ REQUEST *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
++ char buffer[2 * MAX_SESSION_SIZE + 1];
++
++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
++ if (!conf) return NULL;
++
++ rad_assert(request);
++
++ size = len;
++ if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;
++
++ if (fr_debug_lvl > 1) {
++ fr_bin2hex(buffer, data, size);
++ RDEBUG2("Peer requested cached session: %s", buffer);
++ }
++
++ *copy = 0;
++
++ /*
++ * Take the given SSL ID, and create a fake request.
++ *
++ * Don't bother parenting it from another request. We do
++ * this for a number of reasons.
++ *
++ * One is that rest of the code expects that the VPs will
++ * be added to fr_tls_ex_index_vps. So we don't want to
++ * be poking the request directly, as that will result in
++ * a change of behavior.
++ *
++ * The larger reason is that we do _not_ want to actually
++ * update the reply, until such time as we know that the
++ * user has been authenticated.
++ */
++ fake = cache_init_fake_request(conf, NULL, NULL, data, size);
++ if (!fake) return 0;
++
++ /*
++ * Use &request:TLS-Session-Id to load the cached
++ * session.
++ *
++ * The "cache load { ...}" section should put the reply
++ * attributes into the &reply: list, and the
++ * &session-state:TLS-Session-Data attribute.
++ *
++ * Why? Because v4 does it that way, and there aren't
++ * really good reasons for doing it differently.
++ */
++ (void) process_post_auth(CACHE_LOAD, fake);
++
++ /*
++ * Enforce client certificate expiration.
++ */
++ vp = fr_pair_find_by_num(fake->reply->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY);
++ if (vp) {
++ time_t expires;
++
++ if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) {
++ RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror());
++ SSL_SESSION_free(sess);
++ sess = NULL;
++ goto error;
++ }
++
++ if (expires <= request->timestamp) {
++ RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer);
++ SSL_SESSION_free(sess);
++ sess = NULL;
++ goto error;
++ }
++
++ /*
++ * Account for Session-Timeout, if it's available.
++ */
++ vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
++ if (vp) {
++ if ((request->timestamp + vp->vp_integer) > expires) {
++ vp->vp_integer = expires - request->timestamp;
++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
++ vp->vp_integer);
++ }
++ }
++ }
++
++ /*
++ * Try to de-serialize the session data.
++ */
++ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY);
++ if (!vp) {
++ RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer);
++ goto error;
++ }
++
++ /*
++ * OpenSSL mutates what's passed in, so we assign sess_data to q,
++ * so the value of q gets mutated, and not the value of sess_data.
++ *
++ * We then need a pointer to hold &q, but it can't be const, because
++ * clang complains about lack of consting in nested pointer types.
++ *
++ * So we memcpy the value of that pointer, to one that
++ * does have a const, which we then pass into d2i_SSL_SESSION *sigh*.
++ */
++ p = vp->vp_octets;
++ sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length);
++ if (!sess) {
++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
++ goto error;
++ }
++
++ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
++ vps = NULL;
++
++ /* move the cached VPs into the session */
++ fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &fake->reply->vps, 0, 0, TAG_ANY);
++
++ SSL_SESSION_set_ex_data(sess, fr_tls_ex_index_vps, vps);
++ RDEBUG("Successfully restored session %s", buffer);
++ rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:");
++
++ /*
++ * The "restore VPs from OpenSSL cache" code is
++ * now in eaptls_process()
++ */
++
++error:
++ if (fake) talloc_free(fake);
++
++ return sess;
++}
++
++#ifdef HAVE_OPENSSL_OCSP_H
++
++/** Extract components of OCSP responser URL from a certificate
++ *
++ * @param[in] cert to extract URL from.
++ * @param[out] host_out Portion of the URL (must be freed with free()).
++ * @param[out] port_out Port portion of the URL (must be freed with free()).
++ * @param[out] path_out Path portion of the URL (must be freed with free()).
++ * @param[out] is_https Whether the responder should be contacted using https.
++ * @return
++ * - 0 if no valid URL is contained in the certificate.
++ * - 1 if a URL was found and parsed.
++ * - -1 if at least one URL was found, but none could be parsed.
++ */
++static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out,
++ char **path_out, int *is_https)
+ {
+ int i;
+ bool found_uri = false;
+@@ -1811,7 +2655,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
+ VALUE_PAIR *vp;
+
+ if (issuer_cert == NULL) {
+- RWDEBUG("Could not get issuer certificate");
++ RWDEBUG("(TLS) Could not get issuer certificate");
+ goto skipped;
+ }
+
+@@ -1836,7 +2680,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
+ /* Reading the libssl src, they do a strdup on the URL, so it could of been const *sigh* */
+ OCSP_parse_url(url, &host, &port, &path, &use_ssl);
+ if (!host || !port || !path) {
+- RWDEBUG("ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url);
++ RWDEBUG("(TLS) ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url);
+ goto skipped;
+ }
+ } else {
+@@ -1845,15 +2689,15 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
+ ret = ocsp_parse_cert_url(client_cert, &host, &port, &path, &use_ssl);
+ switch (ret) {
+ case -1:
+- RWDEBUG("ocsp: Invalid URL in certificate. Not doing OCSP");
++ RWDEBUG("(TLS) ocsp: Invalid URL in certificate. Not doing OCSP");
+ break;
+
+ case 0:
+ if (conf->ocsp_url) {
+- RWDEBUG("ocsp: No OCSP URL in certificate, falling back to configured URL");
++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate, falling back to configured URL");
+ goto use_ocsp_url;
+ }
+- RWDEBUG("ocsp: No OCSP URL in certificate. Not doing OCSP");
++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate. Not doing OCSP");
+ goto skipped;
+
+ case 1:
+@@ -1865,7 +2709,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
+
+ /* Check host and port length are sane, then create Host: HTTP header */
+ if ((strlen(host) + strlen(port) + 2) > sizeof(hostheader)) {
+- RWDEBUG("ocsp: Host and port too long");
++ RWDEBUG("(TLS) ocsp: Host and port too long");
+ goto skipped;
+ }
+ snprintf(hostheader, sizeof(hostheader), "%s:%s", host, port);
+@@ -2038,15 +2882,15 @@ ocsp_end:
+ vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET);
+ vp->vp_integer = 2; /* skipped */
+ if (conf->ocsp_softfail) {
+- RWDEBUG("ocsp: Unable to check certificate, assuming it's valid");
+- RWDEBUG("ocsp: This may be insecure");
++ RWDEBUG("(TLS) ocsp: Unable to check certificate, assuming it's valid");
++ RWDEBUG("(TLS) ocsp: This may be insecure");
+
+ /* Remove OpenSSL errors from queue or handshake will fail */
+ while (ERR_get_error());
+
+ ocsp_status = OCSP_STATUS_SKIPPED;
+ } else {
+- REDEBUG("ocsp: Unable to check certificate, failing");
++ REDEBUG("(TLS) ocsp: Unable to check certificate, failing");
+ ocsp_status = OCSP_STATUS_FAILED;
+ }
+ break;
+@@ -2054,7 +2898,7 @@ ocsp_end:
+ default:
+ vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET);
+ vp->vp_integer = 0; /* no */
+- REDEBUG("ocsp: Certificate has been expired/revoked");
++ REDEBUG("(TLS) ocsp: Certificate has been expired/revoked");
+ break;
+ }
+
+@@ -2087,6 +2931,10 @@ static char const *cert_attr_names[9][2] = {
+ #define FR_TLS_SAN_UPN (7)
+ #define FR_TLS_VALID_SINCE (8)
+
++static const char *cert_names[2] = {
++ "client", "server",
++};
++
+ /*
+ * Before trusting a certificate, you must make sure that the
+ * certificate is 'valid'. There are several steps that your
+@@ -2152,12 +3000,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+
+ lookup = depth;
+
+- /*
+- * Log client/issuing cert. If there's an error, log
+- * issuing cert.
+- */
+- if ((lookup > 1) && !my_ok) lookup = 1;
+-
+ /*
+ * Retrieve the pointer to the SSL of the connection currently treated
+ * and the application specific data stored into the SSL object.
+@@ -2177,14 +3019,37 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+
+ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
+
++ /*
++ * Log client/issuing cert. If there's an error, log
++ * issuing cert.
++ *
++ * Inbound: 0 = client, 1 = server (intermediate CA), 2 = issuing CA
++ * Outbound: 0 = server, 2 = issuing CA.
++ *
++ * Our array of certificates uses 0 for client, and 1 for server. We
++ * also ignore subsequent certs.
++ */
++ if (lookup > 1) {
++ if (!my_ok) lookup = 1;
++
++ } else if (lookup == 0) {
++ /*
++ * This flag is only set for outbound
++ * connections. And then allows us to remap SSL
++ * offset 0 (server) to our offset 1 (also
++ * server).
++ */
++ lookup = (SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER) != NULL);
++ }
++
+ /*
+ * Get the Serial Number
+ */
+ buf[0] = '\0';
+ sn = X509_get_serialNumber(client_cert);
+
+- RDEBUG2("TLS - Creating attributes from certificate OIDs");
+- RINDENT();
++ RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup ]);
++ RINDENT();
+
+ /*
+ * For this next bit, we create the attributes *only* if
+@@ -2328,8 +3193,14 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+
+ if (!my_ok) {
+ char const *p = X509_verify_cert_error_string(err);
+- RERROR("SSL says error %d : %s", err, p);
++ RERROR("(TLS) OpenSSL says error %d : %s", err, p);
+ REXDENT();
++
++ /*
++ * Copy certs even on failure so that they can be logged.
++ */
++ if (certs && request) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs));
++
+ return my_ok;
+ }
+
+@@ -2405,7 +3276,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+ fr_bin2hex(value + 2, srcp, asn1len);
+ }
+
+-
+ vp = fr_pair_make(talloc_ctx, certs, attribute, value, T_OP_ADD);
+ if (!vp) {
+ RDEBUG3("Skipping %s += '%s'. Please check that both the "
+@@ -2446,20 +3316,28 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+
+ switch (X509_STORE_CTX_get_error(ctx)) {
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+- RERROR("issuer=%s", issuer);
++ RERROR("(TLS) unable to get issuer certificate for issuer=%s", issuer);
+ break;
+
+ case X509_V_ERR_CERT_NOT_YET_VALID:
++ RERROR("(TLS) Failed with certificate not yet valid.");
++ break;
++
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+- RERROR("notBefore=");
++ RERROR("(TLS) Failed with error in certificate 'not before' field.");
+ #if 0
+ ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert));
+ #endif
+ break;
+
+ case X509_V_ERR_CERT_HAS_EXPIRED:
++ RERROR("(TLS) Failed with certificate has expired.");
++ break;
++
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+- RERROR("notAfter=");
++ RERROR("(TLS) Failed with err in certificate 'no after' field..");
++ break;
++
+ #if 0
+ ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert));
+ #endif
+@@ -2471,12 +3349,49 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+ * checks.
+ */
+ if (depth == 0) {
++ tls_session_t *ssn = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_SSN);
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ STACK_OF(X509)* untrusted = NULL;
++#endif
++
++ rad_assert(ssn != NULL);
++
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++ /*
++ * See if there are any untrusted certificates.
++ * If so, complain about them.
++ */
++ untrusted = X509_STORE_CTX_get0_untrusted(ctx);
++ if (untrusted) {
++ if (conf->disallow_untrusted || RDEBUG_ENABLED2) {
++ int i;
++
++ WARN("Certificate chain - %i cert(s) untrusted",
++ X509_STORE_CTX_get_num_untrusted(ctx));
++ for (i = sk_X509_num(untrusted); i > 0 ; i--) {
++ X509 *this_cert = sk_X509_value(untrusted, i - 1);
++
++ X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject));
++ subject[sizeof(subject) - 1] = '\0';
++
++ WARN("(TLS) untrusted certificate with depth [%i] subject name %s",
++ i - 1, subject);
++ }
++ }
++
++ if (conf->disallow_untrusted) {
++ AUTH(LOG_PREFIX ": There are untrusted certificates in the certificate chain. Rejecting.");
++ my_ok = 0;
++ }
++ }
++#endif
++
+ /*
+ * If the conf tells us to, check cert issuer
+ * against the specified value and fail
+ * verification if they don't match.
+ */
+- if (conf->check_cert_issuer &&
++ if (my_ok && conf->check_cert_issuer &&
+ (strcmp(issuer, conf->check_cert_issuer) != 0)) {
+ AUTH(LOG_PREFIX ": Certificate issuer (%s) does not match specified value (%s)!",
+ issuer, conf->check_cert_issuer);
+@@ -2595,45 +3510,54 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
+ unlink(filename);
+ break;
+ }
++
++ /*
++ * Track that we've verified the client certificate.
++ */
++ ssn->client_cert_ok = (my_ok == 1);
+ } /* depth == 0 */
+
++ /*
++ * Copy certs to request even on failure, so that the
++ * user can log them.
++ */
+ if (certs && request && !my_ok) {
+ fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs));
+ }
+
+ if (RDEBUG_ENABLED3) {
+- RDEBUG3("chain-depth : %d", depth);
+- RDEBUG3("error : %d", err);
++ RDEBUG3("(TLS) chain-depth : %d", depth);
++ RDEBUG3("(TLS) error : %d", err);
+
+ if (identity) RDEBUG3("identity : %s", *identity);
+- RDEBUG3("common name : %s", common_name);
+- RDEBUG3("subject : %s", subject);
+- RDEBUG3("issuer : %s", issuer);
+- RDEBUG3("verify return : %d", my_ok);
++ RDEBUG3("(TLS) common name : %s", common_name);
++ RDEBUG3("(TLS) subject : %s", subject);
++ RDEBUG3("(TLS) issuer : %s", issuer);
++ RDEBUG3("(TLS) verify return : %d", my_ok);
+ }
+
+ return (my_ok != 0);
+ }
+
+
+-#ifdef HAVE_OPENSSL_OCSP_H
+ /*
+- * Create Global X509 revocation store and use it to verify
+- * OCSP responses
++ * Configure a X509 CA store to verify OCSP or client repsonses
+ *
+ * - Load the trusted CAs
+ * - Load the trusted issuer certificates
++ * - Configure CRLs check if needed
+ */
+-static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf)
++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf)
+ {
+- X509_STORE *store = NULL;
++ X509_STORE *store = X509_STORE_new();
+
+- store = X509_STORE_new();
++ if (store == NULL) return NULL;
+
+ /* Load the CAs we trust */
+ if (conf->ca_file || conf->ca_path)
+ if (!X509_STORE_load_locations(store, conf->ca_file, conf->ca_path)) {
+ tls_error_log(NULL, "Error reading Trusted root CA list \"%s\"", conf->ca_file);
++ X509_STORE_free(store);
+ return NULL;
+ }
+
+@@ -2645,38 +3569,65 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf)
+ if (conf->check_all_crl)
+ X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK_ALL);
+ #endif
++
++#if defined(X509_V_FLAG_PARTIAL_CHAIN)
++ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN);
++#endif
++
+ return store;
+ }
+-#endif /* HAVE_OPENSSL_OCSP_H */
+
+ #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+ #ifndef OPENSSL_NO_ECDH
+ static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use)
+ {
+- int nid;
+- EC_KEY *ecdh;
++ if (!disable_single_dh_use) {
++ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
++ }
+
+- if (!ecdh_curve || !*ecdh_curve) return 0;
++ if (!ecdh_curve) return 0;
+
+- nid = OBJ_sn2nid(ecdh_curve);
+- if (!nid) {
+- ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
+- return -1;
+- }
++#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
++ /*
++ * A colon-separated list of curves.
++ */
++ if (*ecdh_curve) {
++ char *list;
+
+- ecdh = EC_KEY_new_by_curve_name(nid);
+- if (!ecdh) {
+- ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve);
+- return -1;
++ memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */
++
++ if (SSL_CTX_set1_curves_list(ctx, list) == 0) {
++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
++ return -1;
++ }
+ }
+
+- SSL_CTX_set_tmp_ecdh(ctx, ecdh);
++ (void) SSL_CTX_set_ecdh_auto(ctx, 1);
++#else
++ /*
++ * Use APIs for older versions of OpenSSL.
++ */
++ {
++ int nid;
++ EC_KEY *ecdh;
+
+- if (!disable_single_dh_use) {
+- SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
+- }
++ nid = OBJ_sn2nid(ecdh_curve);
++ if (!nid) {
++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
++ return -1;
++ }
+
+- EC_KEY_free(ecdh);
++ ecdh = EC_KEY_new_by_curve_name(nid);
++ if (!ecdh) {
++ ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve);
++ return -1;
++ }
++
++ SSL_CTX_set_tmp_ecdh(ctx, ecdh);
++
++ EC_KEY_free(ecdh);
++ }
++#endif
+
+ return 0;
+ }
+@@ -2708,10 +3659,32 @@ int tls_global_init(bool spawn_flag, bool check)
+ * and we don't want to have tls.c depend on globals.
+ */
+ if (spawn_flag && !check && (tls_mutexes_init() < 0)) {
+- ERROR("FATAL: Failed to set up SSL mutexes");
++ ERROR("(TLS) FATAL: Failed to set up SSL mutexes");
++ return -1;
++ }
++
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ /*
++ * Load the default provider for most algorithms
++ */
++ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default");
++ if (!openssl_default_provider) {
++ ERROR("(TLS) Failed loading default provider");
+ return -1;
+ }
+
++ /*
++ * Needed for MD4
++ *
++ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms
++ */
++ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
++ if (!openssl_legacy_provider) {
++ ERROR("(TLS) Failed loading legacy provider");
++ return -1;
++ }
++#endif
++
+ return 0;
+ }
+
+@@ -2777,6 +3750,19 @@ void tls_global_cleanup(void)
+ #ifndef OPENSSL_NO_ENGINE
+ ENGINE_cleanup();
+ #endif
++
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) {
++ ERROR("Failed unloading default provider");
++ }
++ openssl_default_provider = NULL;
++
++ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) {
++ ERROR("Failed unloading legacy provider");
++ }
++ openssl_legacy_provider = NULL;
++#endif
++
+ CONF_modules_unload(1);
+ ERR_free_strings();
+ EVP_cleanup();
+@@ -2797,9 +3783,6 @@ static const FR_NAME_NUMBER version2int[] = {
+ #endif
+ #ifdef TLS1_3_VERSION
+ { "1.3", TLS1_3_VERSION },
+-#endif
+-#ifdef TLS1_4_VERSION
+- { "1.4", TLS1_4_VERSION },
+ #endif
+ { NULL, 0 }
+ };
+@@ -2816,18 +3799,18 @@ static const FR_NAME_NUMBER version2int[] = {
+ * - Load the Private key & the certificate
+ * - Set the Context options & Verify options
+ */
+-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file)
+ {
+ SSL_CTX *ctx;
+ X509_STORE *certstore;
+ int verify_mode = SSL_VERIFY_NONE;
+- int ctx_options = 0;
+- int ctx_tls_versions = 0;
++ int ctx_options = 0, ctx_available = 0;
+ int type;
+ #ifdef CHECK_FOR_PSK_CERTS
+ bool psk_and_certs = false;
+ #endif
+- bool insecure_tls_version = false;
++ int min_version;
++ int max_version;
+
+ /*
+ * SHA256 is in all versions of OpenSSL, but isn't
+@@ -2840,7 +3823,7 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
+
+ ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods". Idiots. */
+ if (!ctx) {
+- tls_error_log(NULL, "Failed creating TLS context");
++ tls_error_log(NULL, "Failed creating OpenSSL context");
+ return NULL;
+ }
+
+@@ -3033,39 +4016,56 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
+ * the cert chain needs to be given in PEM from
+ * openSSL.org
+ */
+- if (!conf->certificate_file) goto load_ca;
++ if (!chain_file) chain_file = conf->certificate_file;
++ if (!chain_file) goto load_ca;
+
+ if (type == SSL_FILETYPE_PEM) {
+- if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) {
++ if (!(SSL_CTX_use_certificate_chain_file(ctx, chain_file))) {
+ tls_error_log(NULL, "Failed reading certificate file \"%s\"",
+- conf->certificate_file);
++ chain_file);
+ return NULL;
+ }
+
+- } else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) {
++ } else if (!(SSL_CTX_use_certificate_file(ctx, chain_file, type))) {
+ tls_error_log(NULL, "Failed reading certificate file \"%s\"",
+- conf->certificate_file);
++ chain_file);
+ return NULL;
+ }
+
+- /* Load the CAs we trust */
+ load_ca:
++ /*
++ * Load the CAs we trust and configure CRL checks if needed
++ */
++ if (conf->ca_file || conf->ca_path) {
++ if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL;
++ SSL_CTX_set_cert_store(ctx, certstore);
++ } else {
+ #if defined(X509_V_FLAG_PARTIAL_CHAIN)
+- X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN);
++ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN);
+ #endif
+- if (conf->ca_file || conf->ca_path) {
+- if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) {
+- tls_error_log(NULL, "Failed reading Trusted root CA list \"%s\"",
+- conf->ca_file);
+- return NULL;
+- }
+ }
++
+ if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file));
+
+- if (conf->private_key_file) {
+- if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) {
++ conf->ca_path_last_reload = time(NULL);
++ conf->old_x509_store = NULL;
++
++ /*
++ * Disable reloading of cert store if we're not using CA path
++ */
++ if (!conf->ca_path) conf->ca_path_reload_interval = 0;
++
++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_reload_interval < 300) {
++ DEBUG2("ca_path_reload_interval is set too low, reset it to 300");
++ conf->ca_path_reload_interval = 300;
++ }
++
++ /* Load private key */
++ if (!private_key_file) private_key_file = conf->private_key_file;
++ if (private_key_file) {
++ if (!(SSL_CTX_use_PrivateKey_file(ctx, private_key_file, type))) {
+ tls_error_log(NULL, "Failed reading private key file \"%s\"",
+- conf->private_key_file);
++ private_key_file);
+ return NULL;
+ }
+
+@@ -3088,6 +4088,18 @@ post_ca:
+ ctx_options |= SSL_OP_NO_SSLv2;
+ ctx_options |= SSL_OP_NO_SSLv3;
+
++ /*
++ * If set then dummy Change Cipher Spec (CCS) messages are sent in
++ * TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2
++ * so that middleboxes that do not understand TLSv1.3 will not drop
++ * the connection. This isn't needed for EAP-TLS, so we disable it.
++ *
++ * EAP (hopefully) does not have middlebox deployments
++ */
++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
++ ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT;
++#endif
++
+ /*
+ * SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0
+ *
+@@ -3095,168 +4107,291 @@ post_ca:
+ * below, so we don't need to check for them explicitly.
+ *
+ * TLS1_3_VERSION is available in OpenSSL 1.1.1.
+- *
+- * TLS1_4_VERSION in speculative.
+ */
+- {
+- int min_version = 0;
+- int max_version = 0;
+
++ /*
++ * Get the max version from the configuration files.
++ */
++ if (conf->tls_max_version && *conf->tls_max_version) {
++ max_version = fr_str2int(version2int, conf->tls_max_version, 0);
++ if (!max_version) {
++ ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version);
++ return NULL;
++ }
++ } else {
+ /*
+- * Get the max version.
++ * Pick the maximum version available at compile
++ * time.
+ */
+- if (conf->tls_max_version && *conf->tls_max_version) {
+- max_version = fr_str2int(version2int, conf->tls_max_version, 0);
+- if (!max_version) {
+- ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version);
+- return NULL;
+- }
+- } else {
+- /*
+- * Pick the maximum one we know about.
+- */
+-#ifdef TLS1_4_VERSION
+- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */
+-#elif defined(TLS1_3_VERSION)
+- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */
++#if defined(TLS1_3_VERSION)
++#ifdef WITH_RADIUSV11
++ /*
++ * RADIUS 1.1 requires TLS 1.3 or later.
++ */
++ if (conf->radiusv11) {
++ max_version = TLS1_3_VERSION;
++ } else
++#endif
++
++
++ max_version = TLS1_2_VERSION; /* yes, we only use TLS 1.3 if it's EXPLICITELY ENABLED */
+ #elif defined(TLS1_2_VERSION)
+- max_version = TLS1_2_VERSION;
++ max_version = TLS1_2_VERSION;
+ #elif defined(TLS1_1_VERSION)
+- max_version = TLS1_1_VERSION;
++ max_version = TLS1_1_VERSION;
+ #else
+- max_version = TLS1_VERSION;
++ max_version = TLS1_VERSION;
+ #endif
+- }
++ }
+
++ /*
++ * Get the min version from the configuration files.
++ */
++ if (conf->tls_min_version && *conf->tls_min_version) {
++ min_version = fr_str2int(version2int, conf->tls_min_version, 0);
++ if (!min_version) {
++ ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version);
++ return NULL;
++ }
++ } else {
++#ifdef WITH_RADIUSV11
+ /*
+- * Set these for the rest of the code.
++ * RADIUS 1.1 requires TLS 1.3 or later.
+ */
+-#ifdef TLS1_2_VERSION
+- if (max_version < TLS1_2_VERSION) {
+- conf->disable_tlsv1_2 = true;
+- }
+-#endif
+-#ifdef TLS1_1_VERSION
+- if (max_version < TLS1_1_VERSION) {
+- conf->disable_tlsv1_1 = true;
+- }
++ if (conf->radiusv11) {
++ min_version = TLS1_3_VERSION;
++ } else
+ #endif
+-
+ /*
+- * Get the min version.
++ * Allow TLS 1.0. It is horribly insecure, but
++ * some systems still use it.
+ */
+- if (conf->tls_min_version && *conf->tls_min_version) {
+- min_version = fr_str2int(version2int, conf->tls_min_version, 0);
+- if (!min_version) {
+- ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version);
+- return NULL;
+- }
+- } else {
+- min_version = TLS1_VERSION;
+- }
++ min_version = TLS1_VERSION;
++ }
+
+- /*
+- * Compare the two.
+- */
+- if (min_version > max_version) {
+- ERROR("tls_min_version '%s' must be <= tls_max_version '%s'",
+- conf->tls_min_version, conf->tls_max_version);
+- return NULL;
+- }
++ /*
++ * Compare the two.
++ */
++ if ((min_version > max_version) || (max_version < min_version)) {
++ ERROR("tls_min_version '%s' must be <= tls_max_version '%s'",
++ conf->tls_min_version, conf->tls_max_version);
++ return NULL;
++ }
+
+-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ #ifdef CHECK_FOR_PSK_CERTS
+- /*
+- * Disable TLS 1.3 when using PSKs and certs.
+- * This doesn't work.
+- *
+- * It's best to disable the offending
+- * configuration and warn about it. The
+- * alternative is to have the admin wonder why it
+- * doesn't work.
+- *
+- * Note that the admin can over-ride this by
+- * setting "min_version = max_version = 1.3"
+- */
+- if (psk_and_certs &&
+- (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) {
+- max_version = TLS1_2_VERSION;
+- radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards.");
+- }
++ /*
++ * Disable TLS 1.3 when using PSKs and certs.
++ * This doesn't work.
++ *
++ * It's best to disable the offending
++ * configuration and warn about it. The
++ * alternative is to have the admin wonder why it
++ * doesn't work.
++ *
++ * Note that the admin can over-ride this by
++ * setting "min_version = max_version = 1.3"
++ */
++ if (psk_and_certs &&
++ (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) {
++ max_version = TLS1_2_VERSION;
++ radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards.");
++ }
+ #endif
+
+- if (!SSL_CTX_set_max_proto_version(ctx, max_version)) {
+- ERROR("Failed setting TLS maximum version");
+- return NULL;
++ /*
++ * No one should be using TLS 1.0 or TLS 1.1 any more
++ *
++ * If TLS1.2 isn't defined by OpenSSL, then we _know_
++ * it's an insecure version of OpenSSL.
++ */
++#ifdef TLS1_2_VERSION
++ if (max_version < TLS1_2_VERSION)
++#endif
++ {
++ if (rad_debug_lvl) {
++ WARN(LOG_PREFIX ": The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security");
++ WARN(LOG_PREFIX ": Please set: tls_min_version = '1.2'");
+ }
++ }
+
+- if (!SSL_CTX_set_min_proto_version(ctx, min_version)) {
+- ERROR("Failed setting TLS minimum version");
++#ifdef SSL_OP_NO_TLSv1
++ /*
++ * Check min / max against the old-style "disable" flag.
++ */
++ if (conf->disable_tlsv1) {
++ if (min_version == TLS1_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'min_version = 1.0'. These cannot both be true.");
+ return NULL;
+ }
++ if (max_version == TLS1_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'max_version = 1.0'. These cannot both be true.");
++ return NULL;
++ }
++ ctx_options |= SSL_OP_NO_TLSv1;
++ }
+
+- /*
+- * No one should be using TLS 1.0 or TLS 1.1 any more
+- */
+- if (min_version < TLS1_2_VERSION) insecure_tls_version = true;
+-#else /* OpenSSL version < 1.1.0 */
++ if (min_version > TLS1_VERSION) ctx_options |= SSL_OP_NO_TLSv1;
+
+-#ifdef SSL_OP_NO_TLSv1
+- insecure_tls_version |= (conf->disable_tlsv1 == false);
++ ctx_available |= SSL_OP_NO_TLSv1;
+ #endif
++
+ #ifdef SSL_OP_NO_TLSv1_1
+- insecure_tls_version |= (conf->disable_tlsv1_1 == false);
++ /*
++ * Check min / max against the old-style "disable" flag.
++ */
++ if (conf->disable_tlsv1_1) {
++ if (min_version <= TLS1_1_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'min_version <= 1.1'. These cannot both be true.");
++ return NULL;
++ }
++ if (max_version == TLS1_1_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.1'. These cannot both be true.");
++ return NULL;
++ }
++ ctx_options |= SSL_OP_NO_TLSv1_1;
++ }
++
++ if (min_version > TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1;
++ if (max_version < TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1;
++
++ ctx_available |= SSL_OP_NO_TLSv1_1;
+ #endif
+-#endif /* OpenSSL version ? 1.1.0 */
+
+- if (rad_debug_lvl && insecure_tls_version) {
+- WARN("The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security");
+- WARN("Please set: tls_min_version = \"1.2\"");
++#ifdef SSL_OP_NO_TLSv1_2
++ /*
++ * Check min / max against the old-style "disable" flag.
++ */
++ if (conf->disable_tlsv1_2) {
++ if (min_version <= TLS1_2_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'. These cannot both be true.");
++ return NULL;
++ }
++ if (max_version == TLS1_2_VERSION) {
++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'. These cannot both be true.");
++ return NULL;
+ }
++ ctx_options |= SSL_OP_NO_TLSv1_2;
+ }
++ ctx_available |= SSL_OP_NO_TLSv1_2;
++
++ if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
++ if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
++#endif
++
++#ifdef SSL_OP_NO_TLSv1_3
++ ctx_available |= SSL_OP_NO_TLSv1_3;
++ if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
++ if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
++#endif
+
++
++#ifdef WITH_RADIUSV11
+ /*
+- * For historical config compatibility, we also allow
+- * these, but complain if the admin uses them.
++ * RADIUS 1.1 requires TLS 1.3 or later.
+ */
+-#ifdef SSL_OP_NO_TLSv1
+- if (conf->disable_tlsv1) {
+- ctx_options |= SSL_OP_NO_TLSv1;
+-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1");
++ if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) {
++ ERROR(LOG_PREFIX ": Please set 'tls_min_version = 1.2' or greater to use 'radiusv1_1 = true'");
++ return NULL;
++ }
+ #endif
++
++ /*
++ * Set the cipher list if we were told to do so. We do
++ * this before setting min/max TLS version. In a sane
++ * world, OpenSSL would error out if we set the max TLS
++ * version to something which was unsupported by the
++ * current security level. However, this is OpenSSL. If
++ * you set conflicting options, it doesn't give an error.
++ * Instead, it just picks something to do.
++ */
++ if (conf->cipher_list) {
++ if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) {
++ tls_error_log(NULL, "Failed setting cipher list");
++ return NULL;
++ }
+ }
+
+- ctx_tls_versions |= SSL_OP_NO_TLSv1;
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ if (conf->sigalgs_list) {
++ char *list;
++
++ memcpy(&list, &(conf->sigalgs_list), sizeof(list)); /* const issues */
++
++ if (SSL_CTX_set1_sigalgs_list(ctx, list) == 0) {
++ tls_error_log(NULL, "Failed setting signature list '%s'", conf->sigalgs_list);
++ return NULL;
++ }
++ }
+ #endif
+-#ifdef SSL_OP_NO_TLSv1_1
+- if (conf->disable_tlsv1_1) {
+- ctx_options |= SSL_OP_NO_TLSv1_1;
+-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2");
++
++ /*
++ * Tell OpenSSL PRETTY PLEASE MAY WE USE TLS 1.1.
++ *
++ * Because saying "use TLS 1.1" isn't enough. We have to
++ * send it flowers and cake.
++ */
++ if (min_version <= TLS1_1_VERSION) {
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ int seclevel = SSL_CTX_get_security_level(ctx);
++ int required;;
++
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ required = 0;
++#else
++ required = 1;
+ #endif
+- }
+
+- ctx_tls_versions |= SSL_OP_NO_TLSv1_1;
++ if (seclevel != required) {
++ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=%d\"", required);
++ }
++
++#else
++ /*
++ * No API to get the security level. Just guess based on the string in the cipher_list.
++ */
++ if (conf->cipher_list &&
++ !strstr(conf->cipher_list, "DEFAULT@SECLEVEL=1")) {
++ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=1\"");
++ }
+ #endif
+-#ifdef SSL_OP_NO_TLSv1_2
++ }
+
+- if (conf->disable_tlsv1_2) {
+- ctx_options |= SSL_OP_NO_TLSv1_2;
+ #if OPENSSL_VERSION_NUMBER >= 0x10100000L
+- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2");
+-#endif
++ if (conf->disable_tlsv1) {
++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1'");
++ }
++ if (conf->disable_tlsv1_1) {
++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_1'");
++ }
++ if (conf->disable_tlsv1_2) {
++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_2'");
+ }
+
+- ctx_tls_versions |= SSL_OP_NO_TLSv1_2;
++ ctx_options &= ~(ctx_available); /* clear these flags, as they're not needed. */
+
+-#endif
++ if (!SSL_CTX_set_max_proto_version(ctx, max_version)) {
++ ERROR("Failed setting TLS maximum version");
++ return NULL;
++ }
++ if (!SSL_CTX_set_min_proto_version(ctx, min_version)) {
++ ERROR("Failed setting TLS minimum version");
++ return NULL;
++ }
++#endif /* OpenSSL version < 1.1.0 */
+
+- if ((ctx_options & ctx_tls_versions) == ctx_tls_versions) {
++ if ((ctx_options & ctx_available) == ctx_available) {
+ ERROR(LOG_PREFIX ": You have disabled all available TLS versions. EAP will not work");
+ return NULL;
+ }
+
++ /*
++ * Cache min / max TLS version so that we can
++ * programatically disable TLS 1.3 for TTLS, PEAP, and
++ * FAST.
++ */
++ conf->min_version = min_version;
++ conf->max_version = max_version;
++
+ #ifdef SSL_OP_NO_TICKET
+ ctx_options |= SSL_OP_NO_TICKET;
+ #endif
+@@ -3291,6 +4426,19 @@ post_ca:
+
+ SSL_CTX_set_options(ctx, ctx_options);
+
++ /*
++ * TLS 1.3 introduces the concept of early data (also known as zero
++ * round trip data or 0-RTT data). Early data allows a client to send
++ * data to a server in the first round trip of a connection, without
++ * waiting for the TLS handshake to complete if the client has spoken
++ * to the same server recently. This doesn't work for EAP, so we
++ * disable early data.
++ *
++ */
++#if OPENSSL_VERSION_NUMBER >= 0x10101000L
++ SSL_CTX_set_max_early_data(ctx, 0);
++#endif
++
+ /*
+ * TODO: Set the RSA & DH
+ * SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa);
+@@ -3336,12 +4484,21 @@ post_ca:
+ /*
+ * Cache sessions on disk if requested.
+ */
+- if (conf->session_cache_path) {
++ if (conf->session_cache_path && *conf->session_cache_path) {
+ SSL_CTX_sess_set_new_cb(ctx, cbtls_new_session);
+ SSL_CTX_sess_set_get_cb(ctx, cbtls_get_session);
+ SSL_CTX_sess_set_remove_cb(ctx, cbtls_remove_session);
+ }
+
++ /*
++ * Or run the cache through a virtual server.
++ */
++ if (conf->session_cache_server && *conf->session_cache_server) {
++ SSL_CTX_sess_set_new_cb(ctx, cbtls_cache_save);
++ SSL_CTX_sess_set_get_cb(ctx, cbtls_cache_load);
++ SSL_CTX_sess_set_remove_cb(ctx, cbtls_cache_clear);
++ }
++
+ SSL_CTX_set_quiet_shutdown(ctx, 1);
+ if (fr_tls_ex_index_vps < 0)
+ fr_tls_ex_index_vps = SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+@@ -3359,6 +4516,17 @@ post_ca:
+ }
+ X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
+
++#ifdef X509_V_FLAG_USE_DELTAS
++ /*
++ * If set, delta CRLs (if present) are used to
++ * determine certificate status. If not set
++ * deltas are ignored.
++ *
++ * So it's safe to always set this flag.
++ */
++ X509_STORE_set_flags(certstore, X509_V_FLAG_USE_DELTAS);
++#endif
++
+ #ifdef X509_V_FLAG_CRL_CHECK_ALL
+ if (conf->check_all_crl)
+ X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK_ALL);
+@@ -3389,16 +4557,6 @@ post_ca:
+ }
+ #endif
+
+- /*
+- * Set the cipher list if we were told to
+- */
+- if (conf->cipher_list) {
+- if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) {
+- tls_error_log(NULL, "Failed setting cipher list");
+- return NULL;
+- }
+- }
+-
+ /*
+ * Setup session caching
+ */
+@@ -3424,9 +4582,9 @@ post_ca:
+ (unsigned int) strlen(conf->session_context_id));
+
+ /*
+- * Our timeout is in hours, this is in seconds.
++ * Our lifetime is in hours, this is in seconds.
+ */
+- SSL_CTX_set_timeout(ctx, conf->session_timeout * 3600);
++ SSL_CTX_set_timeout(ctx, conf->session_lifetime * 3600);
+
+ /*
+ * Set the maximum number of entries in the
+@@ -3468,11 +4626,15 @@ static int _tls_server_conf_free(fr_tls_server_conf_t *conf)
+
+ if (conf->cache_ht) fr_hash_table_free(conf->cache_ht);
+
++ pthread_mutex_destroy(&conf->mutex);
++
+ #ifdef HAVE_OPENSSL_OCSP_H
+ if (conf->ocsp_store) X509_STORE_free(conf->ocsp_store);
+ conf->ocsp_store = NULL;
+ #endif
+
++ if (conf->realms) fr_hash_table_free(conf->realms);
++
+ #ifndef NDEBUG
+ memset(conf, 0, sizeof(*conf));
+ #endif
+@@ -3505,9 +4667,109 @@ static int store_cmp(void const *a, void const *b)
+ DICT_ATTR const *one = a;
+ DICT_ATTR const *two = b;
+
+- return one - two;
++ return (one < two) - (one > two);
++}
++
++static uint32_t realm_hash(void const *data)
++{
++ fr_realm_ctx_t const *r = data;
++
++ return fr_hash_string(r->name);
++}
++
++static int realm_cmp(void const *a, void const *b)
++{
++ fr_realm_ctx_t const *one = a;
++ fr_realm_ctx_t const *two = b;
++
++ return strcmp(one->name, two->name);
+ }
+
++static void realm_free(void *data)
++{
++ fr_realm_ctx_t *r = data;
++
++ SSL_CTX_free(r->ctx);
++}
++
++static int tls_realms_load(fr_tls_server_conf_t *conf)
++{
++ fr_hash_table_t *ht;
++ DIR *dir;
++ struct dirent *dp;
++ char buffer[PATH_MAX];
++ char buffer2[PATH_MAX];
++
++ ht = fr_hash_table_create(realm_hash, realm_cmp, realm_free);
++ if (!ht) return -1;
++
++ dir = opendir(conf->realm_dir);
++ if (!dir) {
++ ERROR("Error reading directory %s: %s", conf->realm_dir, fr_syserror(errno));
++ error:
++ if (dir) closedir(dir);
++ fr_hash_table_free(ht);
++ return -1;
++ }
++
++ /*
++ * Read only the PEM files
++ */
++ while ((dp = readdir(dir)) != NULL) {
++ char *p;
++ struct stat stat_buf;
++ SSL_CTX *ctx;
++ fr_realm_ctx_t *r;
++ char const *private_key_file = buffer;
++
++ if (dp->d_name[0] == '.') continue;
++
++ p = strrchr(dp->d_name, '.');
++ if (!p) continue;
++
++ if (memcmp(p, ".pem", 5) != 0) continue; /* must END in .pem */
++
++ snprintf(buffer, sizeof(buffer), "%s/%s", conf->realm_dir, dp->d_name); /* ignore directories */
++ if ((stat(buffer, &stat_buf) != 0) ||
++ S_ISDIR(stat_buf.st_mode)) continue;
++
++ strcpy(buffer2, buffer);
++ p = strchr(buffer2, '.'); /* which must be there... */
++ if (!p) continue;
++
++ /*
++ * If there's a key file, then use that.
++ * Otherwise assume that the private key is in
++ * the chain file.
++ */
++ strcpy(p, ".key");
++ if (stat(buffer2, &stat_buf) != 0) private_key_file = buffer2;
++
++ ctx = tls_init_ctx(conf, 1, buffer, private_key_file);
++ if (!ctx) goto error;
++
++ r = talloc_zero(conf, fr_realm_ctx_t);
++ if (!r) {
++ SSL_CTX_free(ctx);
++ goto error;
++ }
++
++ r->name = talloc_strdup(r, buffer);
++ r->ctx = ctx;
++
++ if (fr_hash_table_insert(ht, r) < 0) {
++ ERROR("Failed inserting certificate file %s into hash table", buffer);
++ goto error;
++ }
++ }
++
++ conf->realms = ht;
++ closedir(dir);
++
++ return 0;
++}
++
++
+ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
+ {
+ fr_tls_server_conf_t *conf;
+@@ -3535,6 +4797,16 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
+ */
+ if (conf->fragment_size < 100) conf->fragment_size = 100;
+
++ /*
++ * Disallow sessions of more than 7 days, as per RFC
++ * 8446.
++ *
++ * Note that we also enforce this on TLS 1.2, etc.
++ * Because there's just no reason to have month-long TLS
++ * sessions.
++ */
++ if (conf->session_lifetime > (7 * 24)) conf->session_lifetime = 7 * 24;
++
+ /*
+ * Only check for certificate things if we don't have a
+ * PSK query.
+@@ -3563,10 +4835,15 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
+ }
+ }
+
++ /*
++ * Initialize configuration mutex
++ */
++ pthread_mutex_init(&conf->mutex, NULL);
++
+ /*
+ * Initialize TLS
+ */
+- conf->ctx = tls_init_ctx(conf, 0);
++ conf->ctx = tls_init_ctx(conf, 0, NULL, NULL);
+ if (conf->ctx == NULL) {
+ goto error;
+ }
+@@ -3633,10 +4910,11 @@ skip_list:
+ * Initialize OCSP Revocation Store
+ */
+ if (conf->ocsp_enable) {
+- conf->ocsp_store = init_revocation_store(conf);
++ conf->ocsp_store = fr_init_x509_store(conf);
+ if (conf->ocsp_store == NULL) goto error;
+ }
+ #endif /*HAVE_OPENSSL_OCSP_H*/
++
+ {
+ char *dh_file;
+
+@@ -3655,7 +4933,7 @@ skip_list:
+ }
+
+ if (conf->verify_client_cert_cmd && !conf->verify_tmp_dir) {
+- ERROR(LOG_PREFIX ": You MUST set the verify directory in order to use verify_client_cmd");
++ ERROR(LOG_PREFIX ": You MUST set the 'tmpdir' directory in order to use '%s' cmd", conf->verify_client_cert_cmd);
+ goto error;
+ }
+
+@@ -3663,12 +4941,17 @@ skip_list:
+ /*
+ * OpenSSL 1.0.1f and 1.0.1g get the MS-MPPE keys wrong.
+ */
+-#if (OPENSSL_VERSION_NUMBER >= 0x10010060L) && (OPENSSL_VERSION_NUMBER < 0x10010060L)
++#if (OPENSSL_VERSION_NUMBER >= 0x1010106L) && (OPENSSL_VERSION_NUMBER <= 0x1010107L)
+ conf->disable_tlsv1_2 = true;
+ WARN(LOG_PREFIX ": Disabling TLSv1.2 due to OpenSSL bugs");
+ #endif
+ #endif
+
++ /*
++ * Load certificates and private keys from the realm directory.
++ */
++ if (conf->realm_dir && (tls_realms_load(conf) < 0)) goto error;
++
+ /*
+ * Cache conf in cs in case we're asked to parse this again.
+ */
+@@ -3703,7 +4986,7 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs)
+ /*
+ * Initialize TLS
+ */
+- conf->ctx = tls_init_ctx(conf, 1);
++ conf->ctx = tls_init_ctx(conf, 1, NULL, NULL);
+ if (conf->ctx == NULL) {
+ goto error;
+ }
+@@ -3755,7 +5038,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ * not allowed,
+ */
+ if (SSL_session_reused(ssn->ssl)) {
+- RDEBUG("Forcibly stopping session resumption as it is not allowed");
++ RDEBUG("(TLS) cache - Forcibly stopping session resumption as it is administratively disabled.");
+ return -1;
+ }
+
+@@ -3763,12 +5046,14 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ * Else resumption IS allowed, so we store the
+ * user data in the cache.
+ */
+- } else if (!SSL_session_reused(ssn->ssl)) {
++ } else if ((!SSL_session_reused(ssn->ssl)) || ssn->session_not_resumed) {
+ VALUE_PAIR **certs;
+ char buffer[2 * MAX_SESSION_SIZE + 1];
+
+ tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
+
++ RDEBUG("(TLS) cache - Setting up attributes for session resumption");
++
+ vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_USER_NAME, 0, TAG_ANY);
+ if (vp) fr_pair_add(&vps, vp);
+
+@@ -3778,6 +5063,9 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_STRIPPED_USER_DOMAIN, 0, TAG_ANY);
+ if (vp) fr_pair_add(&vps, vp);
+
++ vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
++ if (vp) fr_pair_add(&vps, vp);
++
+ vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_CHARGEABLE_USER_IDENTITY, 0, TAG_ANY);
+ if (vp) fr_pair_add(&vps, vp);
+
+@@ -3836,7 +5124,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ if (vp) {
+ if ((request->timestamp + vp->vp_integer) > expires) {
+ vp->vp_integer = expires - request->timestamp;
+- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration",
++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
+ vp->vp_integer);
+ }
+ }
+@@ -3858,7 +5146,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ FR_DIR_SEP, buffer);
+ vp_file = fopen(filename, "w");
+ if (vp_file == NULL) {
+- RWDEBUG("Could not write session VPs to persistent cache: %s",
++ RWDEBUG("(TLS) Could not write session VPs to persistent cache: %s",
+ fr_syserror(errno));
+ } else {
+ VALUE_PAIR *prev = NULL;
+@@ -3889,6 +5177,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ fprintf(vp_file, "\n");
+ fclose(vp_file);
+ }
++
++ } else if (conf->session_cache_server) {
++ cbtls_cache_save_vps(ssn->ssl, ssn->ssl_session, vps);
++
+ } else {
+ RDEBUG("Failed to find 'persist_dir' in TLS configuration. Session will not be cached on disk.");
+ }
+@@ -3901,15 +5193,27 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ * Else the session WAS allowed. Copy the cached reply.
+ */
+ } else {
+- char buffer[2 * MAX_SESSION_SIZE + 1];
+-
+- tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
++ RDEBUG("(TLS) cache - Refreshing entry for session resumption");
+
+ /*
+ * The "restore VPs from OpenSSL cache" code is
+ * now in eaptls_process()
+ */
+ if (conf->session_cache_path) {
++ char buffer[2 * MAX_SESSION_SIZE + 1];
++
++#if OPENSSL_VERSION_NUMBER >= 0x10001000L
++#ifdef TLS1_3_VERSION
++ /*
++ * OpenSSL frees the underlying session out from
++ * under us in TLS 1.3.
++ */
++ if (SSL_version(ssn->ssl) == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl);
++#endif
++#endif
++
++ tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
++
+ /* "touch" the cached session/vp file */
+ char filename[3 * MAX_SESSION_SIZE + 1];
+
+@@ -3921,6 +5225,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
+ utime(filename, NULL);
+ }
+
++ if (conf->session_cache_server) {
++ cbtls_cache_refresh(ssn->ssl, ssn->ssl_session);
++ }
++
+ /*
+ * Mark the request as resumed.
+ */
+@@ -3953,49 +5261,69 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request)
+ err = BIO_write(ssn->into_ssl, ssn->dirty_in.data,
+ ssn->dirty_in.used);
+ if (err != (int) ssn->dirty_in.used) {
++ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
+ record_init(&ssn->dirty_in);
+- RDEBUG("Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
+ return FR_TLS_FAIL;
+ }
++
++ record_init(&ssn->dirty_in);
+ }
+
+ /*
+- * Clear the dirty buffer now that we are done with it
+- * and init the clean_out buffer to store decrypted data
++ * tls_handshake_recv() may read application data. So
++ * don't touch clean_out. But only if the BIO_write()
++ * above didn't do anything.
+ */
+- record_init(&ssn->dirty_in);
+- record_init(&ssn->clean_out);
++ else if (ssn->clean_out.used > 0) {
++ RDEBUG("(TLS) We already have %zd bytes of application data, processing it.",
++ (ssn->clean_out.used));
++ goto add_certs;
++ }
+
+ /*
+ * Read (and decrypt) the tunneled data from the
+ * SSL session, and put it into the decrypted
+ * data buffer.
+ */
+- err = SSL_read(ssn->ssl, ssn->clean_out.data, sizeof(ssn->clean_out.data));
++ err = SSL_read(ssn->ssl, ssn->clean_out.data + ssn->clean_out.used,
++ sizeof(ssn->clean_out.data) - ssn->clean_out.used);
+ if (err <= 0) {
+ int code;
+
+- RDEBUG("SSL_read Error");
++ RDEBUG3("(TLS) SSL_read Error");
+
+ code = SSL_get_error(ssn->ssl, err);
+ switch (code) {
+ case SSL_ERROR_WANT_READ:
+- RDEBUG("Error in fragmentation logic: SSL_WANT_READ");
++ if (ssn->clean_out.used > 0) { /* just process what application data we have */
++ err = 0;
++ break;
++ }
++
++ RDEBUG("(TLS) OpenSSL says that it needs to read more data.");
+ return FR_TLS_MORE_FRAGMENTS;
+
+ case SSL_ERROR_WANT_WRITE:
+- RDEBUG("Error in fragmentation logic: SSL_WANT_WRITE");
++ if (ssn->clean_out.used > 0) { /* just process what application data we have */
++ err = 0;
++ break;
++ }
++
++ REDEBUG("(TLS) Error in fragmentation logic: SSL_WANT_WRITE");
+ return FR_TLS_FAIL;
+
+ case SSL_ERROR_NONE:
+- RDEBUG2("No application data received. Assuming handshake is continuing...");
++ RDEBUG2("(TLS) No application data received. Assuming handshake is continuing...");
+ err = 0;
+ break;
+
++ case SSL_ERROR_ZERO_RETURN:
++ RDEBUG2("(TLS) Other end closed the TLS tunnel.");
++ return FR_TLS_FAIL;
++
+ default:
+- REDEBUG("Error in fragmentation logic");
+- tls_error_io_log(request, ssn, err,
+- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)");
++ REDEBUG("(TLS) Error in fragmentation logic - code %d", code);
++ tls_error_io_log(request, ssn, err, "Failed reading application data from OpenSSL");
+ return FR_TLS_FAIL;
+ }
+ }
+@@ -4003,8 +5331,9 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request)
+ /*
+ * Passed all checks, successfully decrypted data
+ */
+- ssn->clean_out.used = err;
++ ssn->clean_out.used += err;
+
++add_certs:
+ /*
+ * Add the certificates to intermediate packets, so that
+ * the inner tunnel policies can use them.
+@@ -4026,27 +5355,33 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request)
+ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
+ {
+ if (ssn == NULL){
+- REDEBUG("Unexpected ACK received: No ongoing SSL session");
++ REDEBUG("(TLS) Unexpected ACK received: No ongoing SSL session");
+ return FR_TLS_INVALID;
+ }
+ if (!ssn->info.initialized) {
+- RDEBUG("No SSL info available. Waiting for more SSL data");
++ RDEBUG("(TLS) No SSL info available. Waiting for more SSL data");
+ return FR_TLS_REQUEST;
+ }
+
+ if ((ssn->info.content_type == handshake) && (ssn->info.origin == 0)) {
+- REDEBUG("Unexpected ACK received: We sent no previous messages");
++ REDEBUG("(TLS) Unexpected ACK received: We sent no previous messages");
+ return FR_TLS_INVALID;
+ }
+
+ switch (ssn->info.content_type) {
+ case alert:
+- RDEBUG2("Peer ACKed our alert");
++ RDEBUG2("(TLS) Peer ACKed our alert");
+ return FR_TLS_FAIL;
+
+ case handshake:
+- if ((ssn->is_init_finished) && (ssn->dirty_out.used == 0)) {
+- RDEBUG2("Peer ACKed our handshake fragment. handshake is finished");
++ if (ssn->dirty_out.used > 0) {
++ RDEBUG2("(TLS) Peer ACKed our handshake fragment");
++ /* Fragmentation handler, send next fragment */
++ return FR_TLS_REQUEST;
++ }
++
++ if (ssn->is_init_finished || SSL_is_init_finished(ssn->ssl)) {
++ RDEBUG2("(TLS) Peer ACKed our handshake fragment. handshake is finished");
+
+ /*
+ * From now on all the content is
+@@ -4057,12 +5392,11 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
+ return FR_TLS_SUCCESS;
+ } /* else more data to send */
+
+- RDEBUG2("Peer ACKed our handshake fragment");
+- /* Fragmentation handler, send next fragment */
+- return FR_TLS_REQUEST;
++ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving.");
++ return FR_TLS_FAIL;
+
+ case application_data:
+- RDEBUG2("Peer ACKed our application data fragment");
++ RDEBUG2("(TLS) Peer ACKed our application data fragment");
+ return FR_TLS_REQUEST;
+
+ /*
+@@ -4070,7 +5404,7 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
+ * to the default section below.
+ */
+ default:
+- REDEBUG("Invalid ACK received: %d", ssn->info.content_type);
++ REDEBUG("(TLS) Invalid ACK received: %d", ssn->info.content_type);
+ return FR_TLS_INVALID;
+ }
+ }
+diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c
+index 0eed87b64f..6d954d269f 100644
+--- a/src/main/tls_listen.c
++++ b/src/main/tls_listen.c
+@@ -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) Closing connection");
+ radius_update_listener(listener);
+
+ /*
+- * 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.
+ */
+ }
+
+-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,
+- (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p);
+- if (rcode <= 0) {
+- RDEBUG("Error writing to TLS 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);
++ /*
++ * 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;
+ }
+- p += rcode;
++#endif
++
++
++ ERROR("(TLS) Error writing to socket: %s", fr_syserror(errno));
++
++ tls_socket_close(listener);
++ 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;
++ 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,
+- listener->tls->require_client_cert);
++ listener->tls->require_client_cert, true);
+ if (!sock->ssn) {
+ TALLOC_FREE(sock->request);
+ sock->packet = NULL;
+@@ -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 */
++
+ doing_init = true;
+ }
+
+@@ -186,7 +230,18 @@ static int tls_socket_recv(rad_listen_t *listener)
+
+ request = sock->request;
+
+- RDEBUG3("Reading from socket %d", request->packet->sockfd);
++ if (sock->state == LISTEN_TLS_SETUP) {
++ RDEBUG3("(TLS) Setting connection state to RUNNING");
++ sock->state = LISTEN_TLS_RUNNING;
++
++ if (sock->ssn->clean_out.used < 20) {
++ goto get_application_data;
++ }
++
++ goto read_application_data;
++ }
++
++ RDEBUG3("(TLS) Reading from socket %d", request->packet->sockfd);
+ PTHREAD_MUTEX_LOCK(&sock->mutex);
+
+ /*
+@@ -195,33 +250,38 @@ static int tls_socket_recv(rad_listen_t *listener)
+ * the socket.
+ */
+ if (SSL_pending(sock->ssn->ssl)) {
+- RDEBUG3("Reading pending buffered data");
++ RDEBUG3("(TLS) Reading pending buffered data");
+ sock->ssn->dirty_in.used = 0;
+- goto get_application_data;
++ goto check_for_setup;
+ }
+
+- 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);
+- 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) {
+- RDEBUG("Error reading TLS socket: %s", fr_syserror(errno));
+- goto do_close;
+- }
++ if (rcode < 0) {
++ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno));
++ 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;
++ }
+
+ 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)) {
+- RDEBUG("Non-TLS data sent to TLS socket: closing");
++ RDEBUG("(TLS) Non-TLS data sent to TLS socket: closing");
+ goto do_close;
+ }
+
+ /*
+ * If we need to do more initialization, do that here.
+ */
++check_for_setup:
+ if (!sock->ssn->is_init_finished) {
+ if (!tls_handshake_recv(request, sock->ssn)) {
+- RDEBUG("FAILED in TLS handshake receive");
++ RDEBUG("(TLS) Failed in TLS handshake receive");
+ goto do_close;
+ }
+
+@@ -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;
+ }
++ }
++
++ /*
++ * Run the request through a virtual server in
++ * order to see if we like the certificate
++ * presented by the client.
++ */
++ if (sock->state == LISTEN_TLS_INIT) {
++ if (!SSL_is_init_finished(sock->ssn->ssl)) {
++ RDEBUG("(TLS) OpenSSL says that the TLS session is still negotiating, but there's no more data to send!");
++ goto do_close;
++ }
++
++ sock->ssn->is_init_finished = true;
++ if (!listener->check_client_connections) {
++ sock->state = LISTEN_TLS_RUNNING;
++ goto get_application_data;
++ }
++
++ request->packet->vps = fr_pair_list_copy(request->packet, sock->certs);
++
++ /*
++ * 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);
++
++ return 1;
+ }
+
+ /*
+ * 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 +405,16 @@ get_application_data:
+ return 0;
+ }
+
++ /*
++ * Hold application data if we're not yet in the RUNNING
++ * state.
++ */
++ if (sock->state != LISTEN_TLS_RUNNING) {
++ RDEBUG3("(TLS) Holding application data until setup is complete");
++ return 0;
++ }
++
++read_application_data:
+ /*
+ * We now have a bunch of 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)) {
+- RDEBUG("Received bad packet: Length %zd contents %d",
++ RDEBUG("(TLS) Received bad packet: Length %zd contents %d",
+ sock->ssn->clean_out.used,
+ (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]);
+ goto do_close;
+@@ -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");
++ DEBUG("(TLS) Closing TLS socket from client");
+ PTHREAD_MUTEX_LOCK(&sock->mutex);
+ tls_socket_close(listener);
+ PTHREAD_MUTEX_UNLOCK(&sock->mutex);
+@@ -315,7 +457,7 @@ get_application_data:
+ char host_ipaddr[128];
+
+ if (is_radius_code(packet->code)) {
+- RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d",
++ RDEBUG("(TLS): %s packet from host %s port %d, id=%d, length=%d",
+ fr_packet_codes[packet->code],
+ inet_ntop(packet->src_ipaddr.af,
+ &packet->src_ipaddr.ipaddr,
+@@ -323,7 +465,7 @@ get_application_data:
+ packet->src_port,
+ packet->id, (int) packet->data_len);
+ } else {
+- RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d",
++ RDEBUG("(TLS): Packet from host %s port %d code=%d, id=%d, length=%d",
+ inet_ntop(packet->src_ipaddr.af,
+ &packet->src_ipaddr.ipaddr,
+ host_ipaddr, sizeof(host_ipaddr)),
+@@ -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);
++ sock->request->packet = NULL;
+ sock->packet = NULL;
+
+ /*
+@@ -391,8 +532,26 @@ redo:
+ break;
+ #endif
+
++#ifdef WITH_COA
++ case PW_CODE_COA_REQUEST:
++ if (listener->type != RAD_LISTEN_COA) goto bad_packet;
++ FR_STATS_INC(coa, total_requests);
++ fun = rad_coa_recv;
++ break;
++
++ case PW_CODE_DISCONNECT_REQUEST:
++ if (listener->type != RAD_LISTEN_COA) goto bad_packet;
++ FR_STATS_INC(dsc, total_requests);
++ fun = rad_coa_recv;
++ break;
++#endif
++
+ case PW_CODE_STATUS_SERVER:
+- if (!main_config.status_server) {
++ if (!main_config.status_server
++#ifdef WITH_TLS
++ && !listener->check_client_connections
++#endif
++ ) {
+ FR_STATS_INC(auth, total_unknown_types);
+ WARN("Ignoring Status-Server request due to security configuration");
+ rad_free(&packet);
+@@ -405,7 +564,7 @@ redo:
+ bad_packet:
+ FR_STATS_INC(auth, total_unknown_types);
+
+- DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED",
++ DEBUG("(TLS) Invalid packet code %d sent from client %s port %d : IGNORED",
+ packet->code, client->shortname, packet->src_port);
+ rad_free(&packet);
+ return 0;
+@@ -432,7 +591,7 @@ redo:
+ int peek = SSL_peek(sock->ssn->ssl, buf, 1);
+
+ if (peek > 0) {
+- DEBUG("more TLS records after dual_tls_recv");
++ DEBUG("(TLS) more TLS records after dual_tls_recv");
+ goto redo;
+ }
+ }
+@@ -455,6 +614,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request)
+
+ if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
+
++ /*
++ * See if the policies allowed this connection.
++ */
++ if (sock->state == LISTEN_TLS_CHECKING) {
++ if (request->reply->code != PW_CODE_ACCESS_ACCEPT) {
++ listener->status = RAD_LISTEN_STATUS_EOL;
++ listener->tls = NULL; /* parent owns this! */
++
++ /*
++ * Tell the event handler that an FD has disappeared.
++ */
++ radius_update_listener(listener);
++ return 0;
++ }
++
++ /*
++ * Resume reading from the listener.
++ */
++ listener->status = RAD_LISTEN_STATUS_RESUME;
++ radius_update_listener(listener);
++
++ rad_assert(sock->request->packet != request->packet);
++
++ sock->state = LISTEN_TLS_SETUP;
++ (void) dual_tls_recv(listener);
++ return 0;
++ }
++
+ /*
+ * Accounting reject's are silently dropped.
+ *
+@@ -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
+
++#ifdef WITH_COA
++ case PW_CODE_COA_ACK:
++ case PW_CODE_COA_NAK:
++ case PW_CODE_DISCONNECT_ACK:
++ case PW_CODE_DISCONNECT_NAK:
++ break;
++#endif
++
+ default:
+ /*
+ * FIXME: Update MIB for packet types?
+@@ -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
++++ b/src/modules/proto_dhcp/rlm_dhcp.c
+@@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request,
+ decoded++;
+ }
+
+- fr_pair_list_move(request->packet, &(request->packet->vps), &head);
++ fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD);
+
+ /* Free any unmoved pairs */
+ fr_pair_list_free(&head);
+diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c
+index 83e7252fa8..2f37663df1 100644
+--- a/src/modules/rlm_eap/libeap/eap_tls.c
++++ b/src/modules/rlm_eap/libeap/eap_tls.c
+@@ -1,3 +1,4 @@
++
+ /*
+ * eap_tls.c
+ *
+@@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ *
+ * Fragment length is Framed-MTU - 4.
+ */
+-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert)
++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13)
+ {
+ tls_session_t *ssn;
+ REQUEST *request = handler->request;
+@@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_
+ * in Opaque. So that we can use these data structures
+ * when we get the response
+ */
+- ssn = tls_new_session(handler, tls_conf, request, client_cert);
++ ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13);
+ if (!ssn) {
+ return NULL;
+ }
+@@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag)
+ */
+ int eaptls_success(eap_handler_t *handler, int peap_flag)
+ {
+- EAPTLS_PACKET reply;
++ EAPTLS_PACKET reply;
+ REQUEST *request = handler->request;
+ tls_session_t *tls_session = handler->opaque;
+
+@@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag)
+ /*
+ * Automatically generate MPPE keying material.
+ */
+- if (tls_session->prf_label) {
+- eaptls_gen_mppe_keys(handler->request,
+- tls_session->ssl, tls_session->prf_label);
++ if (tls_session->label) {
++ uint8_t const *context = NULL;
++ size_t context_size = 0;
++#ifdef TLS1_3_VERSION
++ uint8_t const context_tls13[] = { handler->type };
++#endif
++
++ switch (SSL_version(tls_session->ssl)) {
++#ifdef TLS1_3_VERSION
++ case TLS1_3_VERSION:
++ context = context_tls13;
++ context_size = sizeof(context_tls13);
++ tls_session->label = "EXPORTER_EAP_TLS_Key_Material";
++ break;
++#endif
++ case TLS1_2_VERSION:
++ case TLS1_1_VERSION:
++ case TLS1_VERSION:
++ break;
++ case SSL2_VERSION:
++ case SSL3_VERSION:
++ default:
++ /* Should never happen */
++ rad_assert(0);
++ return 0;
++ break;
++ }
++ eaptls_gen_mppe_keys(request,
++ tls_session->ssl, tls_session->label,
++ context, context_size);
+ } else if (handler->type != PW_EAP_FAST) {
+- RWDEBUG("Not adding MPPE keys because there is no PRF label");
++ RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label");
+ }
+
+- eaptls_gen_eap_key(handler->request->reply, tls_session->ssl,
+- handler->type);
++ eaptls_gen_eap_key(handler);
++
+ return 1;
+ }
+
+@@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag)
+ EAPTLS_PACKET reply;
+ REQUEST *request = handler->request;
+
+- RDEBUG2("ACKing Peer's TLS record fragment");
++ RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data.");
+ reply.code = FR_TLS_ACK;
+ reply.length = TLS_HEADER_LEN + 1/*flags*/;
+ reply.flags = peap_flag;
+@@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ /*
+ * First output the flags (for debugging)
+ */
+- RDEBUG3("Peer sent flags %c%c%c",
++ RDEBUG3("(TLS) EAP Peer sent flags %c%c%c",
+ TLS_START(eaptls_packet->flags) ? 'S' : '-',
+ TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-',
+ TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-');
+@@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) {
+ return tls_ack_handler(handler->opaque, request);
+ } else {
+- REDEBUG("Received Invalid TLS ACK");
++ REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection");
+ return FR_TLS_INVALID;
+ }
+ }
+@@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ * We send TLS_START, but do not receive it.
+ */
+ if (TLS_START(eaptls_packet->flags)) {
+- REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)");
++ REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)");
+ return FR_TLS_INVALID;
+ }
+
+@@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3];
+
+ if (frag_len > total_len) {
+- RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len,
++ RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len,
+ total_len);
+ }
+
+- RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len);
++ RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len);
+ if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
+ /*
+ * The supplicant is free to send fragments of wildly varying
+@@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ * as they won't contain the length field.
+ */
+ if (frag_len + 4) { /* check for wrap, else clang scan gets excited */
+- RDEBUG2("Expecting %i TLS record fragments",
++ RDEBUG2("(TLS) EAP Expecting %i fragments",
+ (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1));
+ }
+
+@@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ */
+ if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) ||
+ !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) {
+- RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments "
+- "to follow", frag_len);
++ RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes). Peer says more fragments "
++ "will follow", frag_len);
+ tls_session->tls_record_in_total_len = total_len;
+ tls_session->tls_record_in_recvd_len = frag_len;
+
+ return FR_TLS_FIRST_FRAGMENT;
+ }
+
+- RDEBUG2("Got additional TLS record fragment with length (%zu bytes). "
+- "Peer indicated more fragments to follow", frag_len);
++ RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). "
++ "Peer says more fragments will follow", frag_len);
+
+ /*
+ * Check we've not exceeded the originally indicated TLS record size.
+ */
+ tls_session->tls_record_in_recvd_len += frag_len;
+ if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
+- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds "
+- "total TLS record length (%zu bytes)", frag_len, total_len);
++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
++ "total data length (%zu bytes)", frag_len, total_len);
+ }
+
+ return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH;
+@@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ * value of the four octet TLS length field.
+ */
+ if (total_len != frag_len) {
+- RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) "
+- "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len);
++ RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) "
++ "does not match expected data length (%zu bytes)", total_len, frag_len);
+ }
+
+ tls_session->tls_record_in_total_len = total_len;
+ tls_session->tls_record_in_recvd_len = frag_len;
+- RDEBUG2("Got complete TLS record (%zu bytes)", frag_len);
++ RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len);
+ return FR_TLS_LENGTH_INCLUDED;
+ }
+
+@@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
+ * this must be the final record fragment
+ */
+ if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
+- RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len);
++ RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len);
+ tls_session->tls_record_in_recvd_len += frag_len;
+ if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) {
+- RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated "
+- "TLS record length (%zu bytes)",
++ RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected "
++ "expected data length (%zu bytes)",
+ tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
+ }
+ }
+
+ if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
+- RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow",
++ RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes). Peer says more fragments will follow",
+ frag_len);
+ tls_session->tls_record_in_recvd_len += frag_len;
+ if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
+- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds "
+- "indicated TLS record length (%zu bytes)",
++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
++ "expected length (%zu bytes)",
+ tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
+ }
+ return FR_TLS_MORE_FRAGMENTS;
+@@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
+ */
+ if (TLS_LENGTH_INCLUDED(tlspacket->flags) &&
+ (tlspacket->length < 5)) { /* flags + TLS message length */
+- REDEBUG("Invalid EAP-TLS packet received: Length bit is set, "
++ REDEBUG("(TLS) EAP Invalid packet received: Length bit is set,"
+ "but packet too short to contain length field");
+ talloc_free(tlspacket);
+ return NULL;
+@@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
+ memcpy(&data_len, &eap_ds->response->type.data[1], 4);
+ data_len = ntohl(data_len);
+ if (data_len > MAX_RECORD_SIZE) {
+- REDEBUG("Reassembled TLS record will be %u bytes, "
+- "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)",
++ REDEBUG("(TLS) EAP Reassembled data will be %u bytes, "
++ "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)",
+ data_len);
+ talloc_free(tlspacket);
+ return NULL;
+@@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
+ case FR_TLS_LENGTH_INCLUDED:
+ case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH:
+ if (tlspacket->length < 5) { /* flags + TLS message length */
+- REDEBUG("Invalid EAP-TLS packet received: Expected length, got none");
++ REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none");
+ talloc_free(tlspacket);
+ return NULL;
+ }
+@@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
+ break;
+
+ default:
+- REDEBUG("Invalid EAP-TLS packet received");
++ REDEBUG("(TLS) EAP Invalid packet received");
+ talloc_free(tlspacket);
+ return NULL;
+ }
+@@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
+ * is required then send another request.
+ */
+ if (!tls_handshake_recv(handler->request, tls_session)) {
+- REDEBUG("TLS receive handshake failed during operation");
++ REDEBUG("(TLS) EAP Receive handshake failed during operation");
+ tls_fail(tls_session);
+ return FR_TLS_FAIL;
+ }
+
++#ifdef TLS1_3_VERSION
++ /*
++ * https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5
++ *
++ * We need to signal the other end that TLS negotiation
++ * is done. We can't send a zero-length application data
++ * message, so we send application data which is one byte
++ * of zero.
++ *
++ * Note this is only done for when there is no application
++ * data to be sent. So this is done always for EAP-TLS but
++ * notibly not for PEAP even on resumption.
++ */
++ if ((SSL_version(tls_session->ssl) == TLS1_3_VERSION) &&
++ (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) {
++ if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) {
++ tls_session->authentication_success = true;
++
++ RDEBUG("(TLS) EAP Sending final Commitment Message.");
++ tls_session->record_plus(&tls_session->clean_in, "\0", 1);
++ }
++
++ tls_handshake_send(request, tls_session);
++ }
++#endif
++
+ /*
+ * FIXME: return success/fail.
+ *
+@@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
+ /*
+ * If there is no data to send i.e
+ * dirty_out.used <=0 and if the SSL
+- * handshake is finished, then return a
+- * EPTLS_SUCCESS
++ * handshake is finished.
+ */
++ if (tls_session->is_init_finished) return FR_TLS_SUCCESS;
+
+- if (tls_session->is_init_finished) {
++ /*
++ * If session is established, skip round-trip and
++ * try to process any inner tunnel data if present.
++ *
++ * This occurs for EAP-TTLS/PAP with TLSv1.3.
++ */
++ if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) {
+ /*
+- * Init is finished. The rest is
+- * application data.
++ * Don't set is_init_finished, as that causes the
++ * rest of the code to make too many assumptions.
+ */
+- tls_session->info.content_type = application_data;
+- return FR_TLS_SUCCESS;
++ return FR_TLS_OK;
+ }
+
+ /*
+ * Who knows what happened...
+ */
+- REDEBUG("TLS failed during operation");
++ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving.");
+ return FR_TLS_FAIL;
+ }
+
+@@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+
+ if (!request) return FR_TLS_FAIL;
+
+- RDEBUG2("Continuing EAP-TLS");
++ RDEBUG3("(TLS) EAP Continuing ...");
+
+ SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request);
+
+@@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ */
+ status = eaptls_verify(handler);
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+- REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+
+ switch (status) {
+@@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ * data" phase.
+ */
+ case FR_TLS_OK:
+- RDEBUG2("Done initial handshake");
++ RDEBUG2("(TLS) EAP Done initial handshake");
+
+ /*
+ * Get the rest of the fragments.
+@@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ * Extract the TLS packet from the buffer.
+ */
+ if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) {
++ REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message");
+ status = FR_TLS_FAIL;
+ goto done;
+ }
+@@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ if (tlspacket->dlen !=
+ (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
+ talloc_free(tlspacket);
+- REDEBUG("Exceeded maximum record size");
++ REDEBUG("(TLS) EAP Exceeded maximum record size");
+ status = FR_TLS_FAIL;
+ goto done;
+ }
+@@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ * Send the ACK.
+ */
+ eaptls_send_ack(handler, tls_session->peap_flag);
+- RDEBUG2("Init is done, but tunneled data is fragmented");
++ RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented");
+ status = FR_TLS_HANDLED;
+ goto done;
+ }
+@@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+
+ vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps);
+ if (!vps) {
+- RWDEBUG("No information in cached session %s", buffer);
++ RWDEBUG("(TLS) EAP No information in cached session %s", buffer);
+ } else {
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+ fr_tls_server_conf_t *conf;
+
+- RDEBUG("Adding cached attributes from session %s", buffer);
++ RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer);
+
+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF);
+ rad_assert(conf != NULL);
+@@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
+ rdebug_pair(L_DBG_LVL_2, request, vp, "&request:");
+ fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp));
+ }
++
++ } else if ((vp->da->vendor == 0) &&
++ (vp->da->attr == PW_EAP_TYPE)) {
++ /*
++ * EAP-Type gets added to
++ * the control list, so
++ * that we can sanity check it.
++ */
++ rdebug_pair(L_DBG_LVL_2, request, vp, "&control:");
++ fr_pair_add(&request->config, fr_pair_copy(request, vp));
++
+ } else {
++
+ rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:");
+ fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
+ }
+diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h
+index 73c7fdd53b..8e5fc773d6 100644
+--- a/src/modules/rlm_eap/libeap/eap_tls.h
++++ b/src/modules/rlm_eap/libeap/eap_tls.h
+@@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull);
+ int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull);
+
+
+-void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6));
+-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label);
++void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6));
++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size);
+ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size);
+-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header);
+-void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull);
++void eaptls_gen_eap_key(eap_handler_t *handler);
++void eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull);
+
+ #define BUFFER_SIZE 1024
+
+@@ -100,7 +100,7 @@ typedef struct tls_packet {
+ /* EAP-TLS framework */
+ EAPTLS_PACKET *eaptls_alloc(void);
+ void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr);
+-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert);
++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13);
+ int eaptls_start(EAP_DS *eap_ds, int peap);
+ int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply);
+
+diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c
+index 3a9e864104..385441c62f 100644
+--- a/src/modules/rlm_eap/libeap/mppe_keys.c
++++ b/src/modules/rlm_eap/libeap/mppe_keys.c
+@@ -26,11 +26,16 @@ RCSID("$Id$")
+ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+
+ #include "eap_tls.h"
++#include <openssl/ssl.h>
+ #include <openssl/hmac.h>
++#include <freeradius-devel/openssl3.h>
+
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++#include <openssl/provider.h>
++#endif
+
+ /*
+- * TLS PRF from RFC 2246
++ * TLS P_hash from RFC 2246/5246 section 5
+ */
+ static void P_hash(EVP_MD const *evp_md,
+ unsigned char const *secret, unsigned int secret_len,
+@@ -38,23 +43,18 @@ static void P_hash(EVP_MD const *evp_md,
+ unsigned char *out, unsigned int out_len)
+ {
+ HMAC_CTX *ctx_a, *ctx_out;
+- unsigned char a[HMAC_MAX_MD_CBLOCK];
+- unsigned int size;
++ unsigned char a[EVP_MAX_MD_SIZE];
++ unsigned int size = EVP_MAX_MD_SIZE;
++ unsigned int digest_len;
+
+ ctx_a = HMAC_CTX_new();
+ ctx_out = HMAC_CTX_new();
+-#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
+- HMAC_CTX_set_flags(ctx_a, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+- HMAC_CTX_set_flags(ctx_out, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+-#endif
+ HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL);
+ HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL);
+
+- size = HMAC_size(ctx_out);
+-
+ /* Calculate A(1) */
+ HMAC_Update(ctx_a, seed, seed_len);
+- HMAC_Final(ctx_a, a, NULL);
++ HMAC_Final(ctx_a, a, &size);
+
+ while (1) {
+ /* Calculate next part of output */
+@@ -63,13 +63,15 @@ static void P_hash(EVP_MD const *evp_md,
+
+ /* Check if last part */
+ if (out_len < size) {
+- HMAC_Final(ctx_out, a, NULL);
++ digest_len = EVP_MAX_MD_SIZE;
++ HMAC_Final(ctx_out, a, &digest_len);
+ memcpy(out, a, out_len);
+ break;
+ }
+
+ /* Place digest in output buffer */
+- HMAC_Final(ctx_out, out, NULL);
++ digest_len = EVP_MAX_MD_SIZE;
++ HMAC_Final(ctx_out, out, &digest_len);
+ HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL);
+ out += size;
+ out_len -= size;
+@@ -77,7 +79,8 @@ static void P_hash(EVP_MD const *evp_md,
+ /* Calculate next A(i) */
+ HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL);
+ HMAC_Update(ctx_a, a, size);
+- HMAC_Final(ctx_a, a, NULL);
++ digest_len = EVP_MAX_MD_SIZE;
++ HMAC_Final(ctx_a, a, &digest_len);
+ }
+
+ HMAC_CTX_free(ctx_a);
+@@ -85,6 +88,82 @@ static void P_hash(EVP_MD const *evp_md,
+ memset(a, 0, sizeof(a));
+ }
+
++/*
++ * TLS PRF from RFC 2246 section 5
++ */
++static void PRF(unsigned char const *secret, unsigned int secret_len,
++ unsigned char const *seed, unsigned int seed_len,
++ unsigned char *out, unsigned int out_len)
++{
++ uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)];
++ unsigned int i;
++
++ unsigned int len = (secret_len + 1) / 2;
++ uint8_t const *s1 = secret;
++ uint8_t const *s2 = secret + (secret_len - len);
++
++ EVP_MD const *md5 = NULL;
++
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ EVP_MD *md5_to_free = NULL;
++
++ /*
++ * If we are using OpenSSL >= 3.0 and FIPS mode is
++ * enabled, we need to load the default provider in a
++ * standalone context in order to access MD5.
++ */
++ OSSL_LIB_CTX *libctx = NULL;
++ OSSL_PROVIDER *default_provider = NULL;
++
++ if (EVP_default_properties_is_fips_enabled(NULL)) {
++ libctx = OSSL_LIB_CTX_new();
++ default_provider = OSSL_PROVIDER_load(libctx, "default");
++
++ if (!default_provider) {
++ ERROR("Failed loading OpenSSL default provider.");
++ return;
++ }
++
++ md5_to_free = EVP_MD_fetch(libctx, "MD5", NULL);
++ if (!md5_to_free) {
++ ERROR("Failed loading OpenSSL MD5 function.");
++ return;
++ }
++
++ md5 = md5_to_free;
++ } else {
++ md5 = EVP_md5();
++ }
++#else
++ md5 = EVP_md5();
++#endif
++
++ P_hash(md5, s1, len, seed, seed_len, out, out_len);
++ P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len);
++
++ for (i = 0; i < out_len; i++) {
++ out[i] ^= buf[i];
++ }
++
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ if (libctx) {
++ OSSL_PROVIDER_unload(default_provider);
++ OSSL_LIB_CTX_free(libctx);
++ EVP_MD_free(md5_to_free);
++ }
++#endif
++}
++
++/*
++ * TLS 1.2 PRF from RFC 5246 section 5
++ */
++static void PRFv12(unsigned char const *secret, unsigned int secret_len,
++ unsigned char const *seed, unsigned int seed_len,
++ unsigned char *out, unsigned int out_len)
++{
++ P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len);
++}
++
+ /* EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */
+ void T_PRF(unsigned char const *secret, unsigned int secret_len,
+ char const *prf_label,
+@@ -128,60 +207,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len,
+ talloc_free(buf);
+ }
+
+-static void PRF(unsigned char const *secret, unsigned int secret_len,
+- unsigned char const *seed, unsigned int seed_len,
+- unsigned char *out, unsigned char *buf, unsigned int out_len)
+-{
+- unsigned int i;
+- unsigned int len = (secret_len + 1) / 2;
+- uint8_t const *s1 = secret;
+- uint8_t const *s2 = secret + (secret_len - len);
+-
+- P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len);
+- P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len);
+-
+- for (i=0; i < out_len; i++) {
+- out[i] ^= buf[i];
+- }
+-}
+-
+ #define EAPTLS_MPPE_KEY_LEN 32
+
+ /*
+ * Generate keys according to RFC 2716 and add to reply
+ */
+-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size)
+ {
+ uint8_t out[4 * EAPTLS_MPPE_KEY_LEN];
+ uint8_t *p;
+- size_t prf_size;
++ size_t len;
+
+- prf_size = strlen(prf_label);
++ len = strlen(label);
+
+ #if OPENSSL_VERSION_NUMBER >= 0x10001000L
+- if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) {
++ if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) {
+ ERROR("Failed generating keying material");
+ return;
+ }
+ #else
+ {
+- uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)];
++ uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)];
+ uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN];
+
+ p = seed;
+
+- memcpy(p, prf_label, prf_size);
+- p += prf_size;
++ memcpy(p, label, len);
++ p += len;
+
+ memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE);
+ p += SSL3_RANDOM_SIZE;
+- prf_size += SSL3_RANDOM_SIZE;
++ len += SSL3_RANDOM_SIZE;
+
+ memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE);
+- prf_size += SSL3_RANDOM_SIZE;
++ p += SSL3_RANDOM_SIZE;
++ len += SSL3_RANDOM_SIZE;
++
++ if (context) {
++ /* cloned and reversed FR_PUT_LE16 */
++ p[0] = ((uint16_t) (context_size)) >> 8;
++ p[1] = ((uint16_t) (context_size)) & 0xff;
++ p += 2;
++ len += 2;
++ memcpy(p, context, context_size);
++ p += context_size;
++ len += context_size;
++ }
+
+ PRF(s->session->master_key, s->session->master_key_length,
+- seed, prf_size, out, buf, sizeof(out));
++ seed, len, out, buf, sizeof(out));
+ }
+ #endif
+
+@@ -195,7 +269,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
+ }
+
+
+-#define FR_TLS_PRF_CHALLENGE "ttls challenge"
++#define FR_TLS_PRF_CHALLENGE "ttls challenge"
+
+ /*
+ * Generate the TTLS challenge
+@@ -206,9 +280,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
+ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size)
+ {
+ #if OPENSSL_VERSION_NUMBER >= 0x10001000L
+- SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE,
+- sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0);
+-
++ if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE,
++ sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) {
++ ERROR("Failed generating keying material");
++ }
+ #else
+ uint8_t out[32], buf[32];
+ uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE];
+@@ -226,14 +301,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size)
+ #endif
+ }
+
++#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id"
++
+ /*
+ * Actually generates EAP-Session-Id, which is an internal server
+ * attribute. Not all systems want to send EAP-Key-Name.
+ */
+-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
++void eaptls_gen_eap_key(eap_handler_t *handler)
+ {
++ RADIUS_PACKET *packet = handler->request->reply;
++ tls_session_t *tls_session = handler->opaque;
++ SSL *s = tls_session->ssl;
+ VALUE_PAIR *vp;
+ uint8_t *buff, *p;
++ uint8_t type = handler->type & 0xff;
+
+ vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0);
+ if (!vp) return;
+@@ -241,11 +322,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
+ vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE;
+ buff = p = talloc_array(vp, uint8_t, vp->vp_length);
+
+- *p++ = header & 0xff;
++ *p++ = type;
+
+- SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE);
+- p += SSL3_RANDOM_SIZE;
+- SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE);
++ switch (SSL_version(tls_session->ssl)) {
++ case TLS1_VERSION:
++ case TLS1_1_VERSION:
++ case TLS1_2_VERSION:
++ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE);
++ p += SSL3_RANDOM_SIZE;
++ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE);
++ break;
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++#ifdef TLS1_3_VERSION
++ case TLS1_3_VERSION:
++#endif
++ default:
++ {
++ uint8_t const context[] = { type };
++
++ if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE,
++ FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1,
++ context, sizeof(context), 1) != 1) {
++ ERROR("Failed generating keying material");
++ return;
++ }
++ }
++#endif
++ }
+
+ vp->vp_octets = buff;
+ fr_pair_add(&packet->vps, vp);
+@@ -254,7 +357,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
+ /*
+ * Same as before, but for EAP-FAST the order of {server,client}_random is flipped
+ */
+-void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label)
++void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label)
+ {
+ uint8_t *p;
+ size_t len, master_key_len;
+@@ -273,7 +376,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_
+ p += SSL3_RANDOM_SIZE;
+
+ master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key));
+- PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size);
+-}
+-
+
++ if (version == TLS1_2_VERSION)
++ PRFv12(master_key, master_key_len, seed, p - seed, buffer, size);
++ else
++ PRF(master_key, master_key_len, seed, p - seed, buffer, size);
++}
+diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c
+index 553a6a6a57..cd504a8363 100644
+--- a/src/modules/rlm_eap/radeapclient.c
++++ b/src/modules/rlm_eap/radeapclient.c
+@@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port);
+ static void rc_evprep_packet_timeout(rc_transaction_t *trans);
+ static void rc_deallocate_id(rc_transaction_t *trans);
+
++/*
++ * For cbtls_cache_*()
++ */
++rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request)
++{
++ return RLM_MODULE_FAIL;
++}
++
+
+ static void NEVER_RETURNS usage(void)
+ {
+diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
+index b0953aa1d4..bbb5a03c95 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
++++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
+@@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl)
+ return -1;
+
+ RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d "
+- "IV_len=%d", EVP_CIPHER_key_length(c), md_size,
+- EVP_CIPHER_iv_length(c));
++ "IV_len=%d", EVP_CIPHER_key_length(c), md_size,
++ EVP_CIPHER_iv_length(c));
+ return 2 * (EVP_CIPHER_key_length(c) +
+ md_size +
+ EVP_CIPHER_iv_length(c));
+ #else
+ const SSL_CIPHER *ssl_cipher;
+ int cipher, digest;
++ int mac_key_len, enc_key_len, fixed_iv_len;
+
+ ssl_cipher = SSL_get_current_cipher(ssl);
+ if (!ssl_cipher)
+ return -1;
+ cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher);
+ digest = SSL_CIPHER_get_digest_nid(ssl_cipher);
+- RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest);
++ RDEBUG3("OpenSSL: cipher nid %d digest nid %d",
++ cipher, digest);
+ if (cipher < 0 || digest < 0)
+ return -1;
++ if (cipher == NID_undef) {
++ RDEBUG3("OpenSSL: no cipher in use?!");
++ return -1;
++ }
+ c = EVP_get_cipherbynid(cipher);
+- h = EVP_get_digestbynid(digest);
+- if (!c || !h)
++ if (!c)
+ return -1;
++ enc_key_len = EVP_CIPHER_key_length(c);
++ if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE ||
++ EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE)
++ fixed_iv_len = 4; /* only part of IV from PRF */
++ else
++ fixed_iv_len = EVP_CIPHER_iv_length(c);
++ if (digest == NID_undef) {
++ RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)");
++ mac_key_len = 0;
++ } else {
++ h = EVP_get_digestbynid(digest);
++ if (!h)
++ return -1;
++ mac_key_len = EVP_MD_size(h);
++ }
+
+- RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d",
+- EVP_CIPHER_key_length(c), EVP_MD_size(h),
+- EVP_CIPHER_iv_length(c));
+- return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) +
+- EVP_CIPHER_iv_length(c));
++ RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d",
++ mac_key_len, enc_key_len, fixed_iv_len);
++ return 2 * (mac_key_len + enc_key_len + fixed_iv_len);
+ #endif
+ }
+
+@@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
+ {
+ eap_fast_tunnel_t *t = tls_session->opaque;
+ uint8_t *buf;
+- uint8_t *scratch;
+ size_t ksize;
+
+ RDEBUG2("Deriving EAP-FAST keys");
+@@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
+ ksize = openssl_get_keyblock_size(request, tls_session->ssl);
+ rad_assert(ksize > 0);
+ buf = talloc_size(request, ksize + sizeof(*t->keyblock));
+- scratch = talloc_size(request, ksize + sizeof(*t->keyblock));
+
+ t->keyblock = talloc(t, eap_fast_keyblock_t);
+
+- eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
++ eap_fast_tls_gen_challenge(tls_session->ssl, SSL_version(tls_session->ssl), buf, ksize + sizeof(*t->keyblock), "key expansion");
+ memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
+ memset(buf, 0, ksize + sizeof(*t->keyblock));
+
+@@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
+ t->imckc = 0;
+
+ talloc_free(buf);
+- talloc_free(scratch);
+ }
+
+ /**
+@@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio
+ dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
+ t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
+ pac.opaque.data, pac.opaque.tag);
++ if (dlen < 0) return;
+
+ pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE);
+ pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
+@@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session,
+ switch (reply->code) {
+ case PW_CODE_ACCESS_ACCEPT:
+ RDEBUG("Got tunneled Access-Accept");
++ tls_session->authentication_success = true;
+ rcode = RLM_MODULE_OK;
+
+ for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) {
+@@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session)
+ t->mode = EAP_FAST_PROVISIONING_AUTH;
+ }
+
+- if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6)
++ /*
++ * Send a new pac at ~0.6 times the lifetime.
++ */
++ if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) {
+ t->pac.send = true;
++ }
+ }
+
+ eap_fast_init_keys(request, tls_session);
+diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
+index 2ce2dd0c3b..093dc868cd 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
++++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
+@@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
+ return -1;
+ }
+
++#ifdef TLS1_3_VERSION
++ if (inst->tls_conf->min_version == TLS1_3_VERSION) {
++ ERROR("There are no standards for using TLS 1.3 with EAP-FAST.");
++ ERROR("You MUST enable TLS 1.2 for EAP-FAST to work.");
++ return -1;
++ }
++
++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) ||
++ (inst->tls_conf->min_version == TLS1_3_VERSION)) {
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ WARN("!! There is no standard for using EAP-FAST with TLS 1.3");
++ WARN("!! Please set tls_max_version = \"1.2\"");
++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows");
++ WARN("!! This limitation is likely to change in late 2021.");
++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade");
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ }
++#endif
++
+ rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH);
+ FR_MD5_CTX ctx;
+ fr_md5_init(&ctx);
+@@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg)
+ DICT_ATTR const *fast_da;
+ char const *errmsg;
+ int dlen, plen;
+- uint16_t length;
++ int length;
+ eap_fast_attr_pac_opaque_t const *opaque = (eap_fast_attr_pac_opaque_t const *) data;
+ eap_fast_attr_pac_opaque_t opaque_plaintext;
+
+@@ -274,7 +293,7 @@ error:
+ * so we have to use the length in the PAC-Opaque header
+ */
+ length = ntohs(opaque->hdr.length);
+- if (len - sizeof(opaque->hdr) < length) {
++ if (len < (int) (length + sizeof(opaque->hdr))) {
+ errmsg = "PAC has bad length in header";
+ goto error;
+ }
+@@ -293,7 +312,7 @@ error:
+ plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH,
+ (uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv,
+ (uint8_t *)&opaque_plaintext);
+- if (plen == -1) {
++ if (plen < 0) {
+ errmsg = "PAC failed to decrypt";
+ goto error;
+ }
+@@ -314,8 +333,8 @@ error:
+ break;
+ case PAC_INFO_PAC_LIFETIME:
+ rad_assert(t->pac.expires == 0);
+- t->pac.expires = vp->vp_integer;
+- t->pac.expired = (vp->vp_integer <= time(NULL));
++ t->pac.expires = vp->vp_integer + time(NULL);
++ t->pac.expired = false;
+ break;
+ case PAC_INFO_PAC_KEY:
+ rad_assert(t->pac.key == NULL);
+@@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+
+ /*
+@@ -553,7 +572,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ } else {
+ client_cert = inst->req_client_cert;
+ }
+- handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert);
++
++ /*
++ * Don't allow TLS 1.3 for us, even if it's allowed
++ * elsewhere. We haven't implemented the necessary
++ * changes, so we don't allow it.
++ */
++ handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false);
+
+ if (!tls_session) return 0;
+
+@@ -566,16 +591,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ }
+ }
+
+-#ifdef SSL_OP_NO_TLSv1_2
+- /*
+- * Forcibly disable TLSv1.2
+- *
+- * @fixme - TLSv1.2 uses a different PRF and
+- * SSL_export_keying_material("key expansion") is
+- * forbidden
+- */
+- SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2);
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
++ {
++ int i;
++ for (i = 0; ; i++) {
++ const char *cipher = SSL_get_cipher_list(tls_session->ssl, i);
++ if (!cipher) break;
++ if (!strstr(cipher, "ADH-")) continue;
++ RDEBUG("Setting security level to 0 to allow anonymous cipher suites");
++ SSL_set_security_level(tls_session->ssl, 0);
++ break;
++ }
++ }
+ #endif
++
+ #ifdef SSL_OP_NO_TLSv1_3
+ /*
+ * Forcibly disable TLSv1.3
+@@ -599,6 +628,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ rcode = eap_fast_tls_start(handler->eap_ds, tls_session);
+
+ if (rcode < 0) {
++ error:
+ talloc_free(tls_session);
+ return 0;
+ }
+@@ -607,7 +637,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+
+ if (!SSL_set_session_ticket_ext_cb(tls_session->ssl, _session_ticket, tls_session)) {
+ RERROR("Failed setting SSL session ticket callback");
+- return 0;
++ goto error;
+ }
+
+ handler->stage = PROCESS;
+diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
+index deaf702d61..5647f613af 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
++++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
+@@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se
+ switch (reply->code) {
+ case PW_CODE_ACCESS_ACCEPT:
+ RDEBUG2("Tunneled authentication was successful");
++ tls_session->authentication_success = true;
+ t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+ eappeap_success(handler, tls_session);
+ rcode = RLM_MODULE_HANDLED;
+@@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
+ /* send an identity request */
+ t->session_resumption_state = PEAP_RESUMPTION_NO;
+ t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
++ tls_session->session_not_resumed = true;
+ eappeap_identity(handler, tls_session);
+ }
+ return RLM_MODULE_HANDLED;
+@@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
+
+ return RLM_MODULE_REJECT;
+
+- case PEAP_STATUS_PHASE2_INIT:
+- RDEBUG("In state machine in phase2 init?");
++ case PEAP_STATUS_PHASE2_INIT:
++ RDEBUG("In state machine in phase2 init?");
+
+- case PEAP_STATUS_PHASE2:
+- break;
++ case PEAP_STATUS_PHASE2:
++ break;
+
+- default:
+- REDEBUG("Unhandled state in peap");
+- return RLM_MODULE_REJECT;
++ default:
++ REDEBUG("Unhandled state in peap");
++ return RLM_MODULE_REJECT;
+ }
+
+ fake = request_alloc_fake(request);
+@@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
+
+ if (vp) {
+ eap_tunnel_data_t *tunnel;
++ bool proxy_as_eap = t->proxy_tunneled_request_as_eap;
++ VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY);
++
++ if (flag) proxy_as_eap = flag->vp_integer;
+
+ /*
+ * The tunneled request was NOT handled,
+@@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
+ * Once the tunneled EAP session is ALMOST
+ * done, THEN we proxy it...
+ */
+- if (!t->proxy_tunneled_request_as_eap) {
++ if (!proxy_as_eap) {
+ fake->options |= RAD_REQUEST_OPTION_PROXY_EAP;
+
+ /*
+diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
+index 3d23322663..d9f850cef2 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
++++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
+@@ -192,7 +192,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ client_cert = inst->req_client_cert;
+ }
+
+- ssn = eaptls_session(handler, inst->tls_conf, client_cert);
++ /*
++ * Allow TLS 1.3, it works.
++ */
++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true);
+ if (!ssn) {
+ return 0;
+ }
+@@ -200,9 +203,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ handler->opaque = ((void *)ssn);
+
+ /*
+- * Set up type-specific information.
++ * Set the label to a fixed string. For TLS 1.3, the
++ * label is the same for all TLS-based EAP methods. If
++ * the client is using TLS 1.3, then eaptls_success()
++ * will over-ride this label with the correct label for
++ * TLS 1.3.
+ */
+- ssn->prf_label = "client EAP encryption";
++ ssn->label = "client EAP encryption";
+
+ /*
+ * As it is a poorly designed protocol, PEAP uses
+@@ -230,7 +237,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+ if (status == 0) return 0;
+
+@@ -274,7 +281,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+
+ /*
+@@ -311,6 +318,10 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ * data.
+ */
+ case FR_TLS_OK:
++ /*
++ * TLSv1.3 makes application data immediately avaliable
++ */
++ if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED;
+ break;
+
+ /*
+diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h
+new file mode 100644
+index 0000000000..b717dd51b3
+--- /dev/null
++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h
+@@ -0,0 +1,190 @@
++/*
++ * Helper functions for constant time operations
++ * Copyright (c) 2019, The Linux Foundation
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ *
++ * These helper functions can be used to implement logic that needs to minimize
++ * externally visible differences in execution path by avoiding use of branches,
++ * avoiding early termination or other time differences, and forcing same memory
++ * access pattern regardless of values.
++ */
++
++#ifndef CONST_TIME_H
++#define CONST_TIME_H
++
++
++#if defined(__clang__)
++#define NO_UBSAN_UINT_OVERFLOW \
++ __attribute__((no_sanitize("unsigned-integer-overflow")))
++#else
++#define NO_UBSAN_UINT_OVERFLOW
++#endif
++
++/**
++ * const_time_fill_msb - Fill all bits with MSB value
++ * @param val Input value
++ * @return Value with all the bits set to the MSB of the input val
++ */
++static inline unsigned int const_time_fill_msb(unsigned int val)
++{
++ /* Move the MSB to LSB and multiple by -1 to fill in all bits. */
++ return (val >> (sizeof(val) * 8 - 1)) * ~0U;
++}
++
++
++/* @return -1 if val is zero; 0 if val is not zero */
++static inline unsigned int const_time_is_zero(unsigned int val)
++ NO_UBSAN_UINT_OVERFLOW
++{
++ /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */
++ return const_time_fill_msb(~val & (val - 1));
++}
++
++
++/* @return -1 if a == b; 0 if a != b */
++static inline unsigned int const_time_eq(unsigned int a, unsigned int b)
++{
++ return const_time_is_zero(a ^ b);
++}
++
++
++/* @return -1 if a == b; 0 if a != b */
++static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b)
++{
++ return (unsigned char) const_time_eq(a, b);
++}
++
++
++/**
++ * const_time_eq_bin - Constant time memory comparison
++ * @param a First buffer to compare
++ * @param b Second buffer to compare
++ * @param len Number of octets to compare
++ * @return -1 if buffers are equal, 0 if not
++ *
++ * This function is meant for comparing passwords or hash values where
++ * difference in execution time or memory access pattern could provide external
++ * observer information about the location of the difference in the memory
++ * buffers. The return value does not behave like memcmp(), i.e.,
++ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike
++ * memcmp(), the execution time of const_time_eq_bin() does not depend on the
++ * contents of the compared memory buffers, but only on the total compared
++ * length.
++ */
++static inline unsigned int const_time_eq_bin(const void *a, const void *b,
++ size_t len)
++{
++ const unsigned char *aa = a;
++ const unsigned char *bb = b;
++ size_t i;
++ unsigned char res = 0;
++
++ for (i = 0; i < len; i++)
++ res |= aa[i] ^ bb[i];
++
++ return const_time_is_zero(res);
++}
++
++
++/**
++ * const_time_select - Constant time unsigned int selection
++ * @param mask 0 (false) or -1 (true) to identify which value to select
++ * @param true_val Value to select for the true case
++ * @param false_val Value to select for the false case
++ * @return true_val if mask == -1, false_val if mask == 0
++ */
++static inline unsigned int const_time_select(unsigned int mask,
++ unsigned int true_val,
++ unsigned int false_val)
++{
++ return (mask & true_val) | (~mask & false_val);
++}
++
++
++/**
++ * const_time_select_int - Constant time int selection
++ * @param mask 0 (false) or -1 (true) to identify which value to select
++ * @param true_val Value to select for the true case
++ * @param false_val Value to select for the false case
++ * @return true_val if mask == -1, false_val if mask == 0
++ */
++static inline int const_time_select_int(unsigned int mask, int true_val,
++ int false_val)
++{
++ return (int) const_time_select(mask, (unsigned int) true_val,
++ (unsigned int) false_val);
++}
++
++
++/**
++ * const_time_select_u8 - Constant time u8 selection
++ * @param mask 0 (false) or -1 (true) to identify which value to select
++ * @param true_val Value to select for the true case
++ * @param false_val Value to select for the false case
++ * @return true_val if mask == -1, false_val if mask == 0
++ */
++static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val)
++{
++ return (unsigned char) const_time_select(mask, true_val, false_val);
++}
++
++
++/**
++ * const_time_select_s8 - Constant time s8 selection
++ * @param mask 0 (false) or -1 (true) to identify which value to select
++ * @param true_val Value to select for the true case
++ * @param false_val Value to select for the false case
++ * @return true_val if mask == -1, false_val if mask == 0
++ */
++static inline char const_time_select_s8(char mask, char true_val, char false_val)
++{
++ return (char) const_time_select(mask, (unsigned int) true_val,
++ (unsigned int) false_val);
++}
++
++
++/**
++ * const_time_select_bin - Constant time binary buffer selection copy
++ * @param mask 0 (false) or -1 (true) to identify which value to copy
++ * @param true_val Buffer to copy for the true case
++ * @param false_val Buffer to copy for the false case
++ * @param len Number of octets to copy
++ * @param dst Destination buffer for the copy
++ *
++ * This function copies the specified buffer into the destination buffer using
++ * operations with identical memory access pattern regardless of which buffer
++ * is being copied.
++ */
++static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val,
++ const unsigned char *false_val, size_t len,
++ unsigned char *dst)
++{
++ size_t i;
++
++ for (i = 0; i < len; i++)
++ dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]);
++}
++
++
++static inline int const_time_memcmp(const void *a, const void *b, size_t len)
++{
++ const unsigned char *aa = a;
++ const unsigned char *bb = b;
++ int diff, res = 0;
++ unsigned int mask;
++
++ if (len == 0)
++ return 0;
++ do {
++ len--;
++ diff = (int) aa[len] - (int) bb[len];
++ mask = const_time_is_zero((unsigned int) diff);
++ res = const_time_select_int(mask, res, diff);
++ } while (len);
++
++ return res;
++}
++
++#endif /* CONST_TIME_H */
+diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
+index d94851c3aa..26260527a5 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
+@@ -1,7 +1,5 @@
+-/*
+- * Copyright (c) Dan Harkins, 2012
+- *
+- * Copyright holder grants permission for redistribution and use in source
++/**
++ * copyright holder grants permission for redistribution and use in source
+ * and binary forms, with or without modification, provided that the
+ * following conditions are met:
+ * 1. Redistribution of source code must retain the above copyright
+@@ -29,100 +27,237 @@
+ * This license and distribution terms cannot be changed. In other words,
+ * this code cannot simply be copied and put under a different distribution
+ * license (including the GNU public license).
++ *
++ * @copyright (c) Dan Harkins, 2012
+ */
+
+ RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $")
+ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+
+ #include "eap_pwd.h"
++#include "const_time.h"
++#include <freeradius-devel/openssl3.h>
+
+-#include <freeradius-devel/radiusd.h>
+-#include <freeradius-devel/modules.h>
++static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 };
+
+ /* The random function H(x) = HMAC-SHA256(0^32, x) */
+-static void H_Init(HMAC_CTX *ctx)
++static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest)
+ {
+- uint8_t allzero[SHA256_DIGEST_LENGTH];
++ unsigned int mdlen = SHA256_DIGEST_LENGTH;
++ HMAC_Final(hmac_ctx, digest, &mdlen);
++// HMAC_CTX_reset(hmac_ctx);
++}
+
+- memset(allzero, 0, SHA256_DIGEST_LENGTH);
++/* a counter-based KDF based on NIST SP800-108 */
++static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label,
++ int label_len, uint8_t *result, int result_bit_len)
++{
++ HMAC_CTX *hmac_ctx;
++ uint8_t digest[SHA256_DIGEST_LENGTH];
++ uint16_t i, ctr, L;
++ int result_byte_len, len = 0;
++ unsigned int mdlen = SHA256_DIGEST_LENGTH;
++ uint8_t mask = 0xff;
++
++ MEM(hmac_ctx = HMAC_CTX_new());
++ result_byte_len = (result_bit_len + 7) / 8;
++
++ ctr = 0;
++ L = htons(result_bit_len);
++ while (len < result_byte_len) {
++ ctr++; i = htons(ctr);
+
+- HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
++ HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL);
++ if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen);
++ HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t));
++ HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len);
++ HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t));
++ HMAC_Final(hmac_ctx, digest, &mdlen);
++ if ((len + (int) mdlen) > result_byte_len) {
++ memcpy(result + len, digest, result_byte_len - len);
++ } else {
++ memcpy(result + len, digest, mdlen);
++ }
++ len += mdlen;
++// HMAC_CTX_reset(hmac_ctx);
++ }
++
++ /* since we're expanding to a bit length, mask off the excess */
++ if (result_bit_len % 8) {
++ mask <<= (8 - (result_bit_len % 8));
++ result[result_byte_len - 1] &= mask;
++ }
++
++ HMAC_CTX_free(hmac_ctx);
+ }
+
+-static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len)
++static BIGNUM *consttime_BN (void)
+ {
+- HMAC_Update(ctx, data, len);
++ BIGNUM *bn;
++
++ bn = BN_new();
++ if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME);
++ return bn;
+ }
+
+-static void H_Final(HMAC_CTX *ctx, uint8_t *digest)
++/*
++ * compute the legendre symbol in constant time
++ */
++static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx)
+ {
+- unsigned int mdlen = SHA256_DIGEST_LENGTH;
++ int symbol;
++ unsigned int mask;
++ BIGNUM *res, *pm1over2;
++
++ pm1over2 = consttime_BN();
++ res = consttime_BN();
++
++ if (!BN_sub(pm1over2, p, BN_value_one()) ||
++ !BN_rshift1(pm1over2, pm1over2) ||
++ !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) {
++ BN_free(pm1over2);
++ BN_free(res);
++ return -2;
++ }
++
++ symbol = -1;
++ mask = const_time_eq(BN_is_word(res, 1), 1);
++ symbol = const_time_select_int(mask, 1, symbol);
++ mask = const_time_eq(BN_is_zero(res), 1);
++ symbol = const_time_select_int(mask, -1, symbol);
++
++ BN_free(pm1over2);
++ BN_free(res);
+
+- HMAC_Final(ctx, digest, &mdlen);
++ return symbol;
+ }
+
+-/* a counter-based KDF based on NIST SP800-108 */
+-static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen)
++static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx)
+ {
+- HMAC_CTX *hctx = NULL;
+- uint8_t digest[SHA256_DIGEST_LENGTH];
+- uint16_t i, ctr, L;
+- int resultbytelen, len = 0;
+- unsigned int mdlen = SHA256_DIGEST_LENGTH;
+- uint8_t mask = 0xff;
++ BIGNUM *p, *a, *b, *tmp1, *pm1;
+
+- hctx = HMAC_CTX_new();
+- if (hctx == NULL) {
+- DEBUG("failed allocating HMAC context");
+- return -1;
++ tmp1 = BN_new();
++ pm1 = BN_new();
++ p = BN_new();
++ a = BN_new();
++ b = BN_new();
++ EC_GROUP_get_curve(group, p, a, b, bnctx);
++
++ BN_sub(pm1, p, BN_value_one());
++
++ /*
++ * y2 = x^3 + ax + b
++ */
++ BN_mod_sqr(tmp1, x, p, bnctx);
++ BN_mod_mul(y2, tmp1, x, p, bnctx);
++ BN_mod_mul(tmp1, a, x, p, bnctx);
++ BN_mod_add_quick(y2, y2, tmp1, p);
++ BN_mod_add_quick(y2, y2, b, p);
++
++ BN_free(tmp1);
++ BN_free(pm1);
++ BN_free(p);
++ BN_free(a);
++ BN_free(b);
++
++ return;
++}
++
++static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx)
++{
++ int offset, check, ret = 0;
++ BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL;
++ unsigned int mask;
++ unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL;
++
++ if (((r = consttime_BN()) == NULL) ||
++ ((res = consttime_BN()) == NULL) ||
++ ((qr_or_qnr = consttime_BN()) == NULL) ||
++ ((pm1 = consttime_BN()) == NULL)) {
++ ret = -2;
++ goto fail;
+ }
+- resultbytelen = (resultbitlen + 7)/8;
+- ctr = 0;
+- L = htons(resultbitlen);
+- while (len < resultbytelen) {
+- ctr++; i = htons(ctr);
+- HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL);
+- if (ctr > 1) {
+- HMAC_Update(hctx, digest, mdlen);
+- }
+- HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t));
+- HMAC_Update(hctx, (uint8_t const *)label, labellen);
+- HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t));
+- HMAC_Final(hctx, digest, &mdlen);
+- if ((len + (int) mdlen) > resultbytelen) {
+- memcpy(result + len, digest, resultbytelen - len);
+- } else {
+- memcpy(result + len, digest, mdlen);
+- }
+- len += mdlen;
++
++ if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
++ ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
++ ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) {
++ ret = -2;
++ goto fail;
+ }
+- HMAC_CTX_free(hctx);
+
+- /* since we're expanding to a bit length, mask off the excess */
+- if (resultbitlen % 8) {
+- mask <<= (8 - (resultbitlen % 8));
+- result[resultbytelen - 1] &= mask;
++ /*
++ * we select binary in constant time so make them binary
++ */
++ memset(qr_bin, 0, BN_num_bytes(p));
++ memset(qnr_bin, 0, BN_num_bytes(p));
++ memset(qr_or_qnr_bin, 0, BN_num_bytes(p));
++
++ offset = BN_num_bytes(p) - BN_num_bytes(qr);
++ BN_bn2bin(qr, qr_bin + offset);
++
++ offset = BN_num_bytes(p) - BN_num_bytes(qnr);
++ BN_bn2bin(qnr, qnr_bin + offset);
++
++ /*
++ * r = (random() mod p-1) + 1
++ */
++ BN_sub(pm1, p, BN_value_one());
++ BN_rand_range(r, pm1);
++ BN_add(r, r, BN_value_one());
++
++ BN_copy(res, val);
++
++ /*
++ * res = val * r * r which ensures res != val but has same quadratic residocity
++ */
++ BN_mod_mul(res, res, r, p, bnctx);
++ BN_mod_mul(res, res, r, p, bnctx);
++
++ /*
++ * if r is even (mask is -1) then multiply by qnr and our check is qnr
++ * otherwise multiply by qr and our check is qr
++ */
++ mask = const_time_is_zero(BN_is_odd(r));
++ const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin);
++ BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr);
++ BN_mod_mul(res, res, qr_or_qnr, p, bnctx);
++ check = const_time_select_int(mask, -1, 1);
++
++ if ((ret = legendre(res, p, bnctx)) == -2) {
++ ret = -1; /* just say no it's not */
++ goto fail;
+ }
++ mask = const_time_eq(ret, check);
++ ret = const_time_select_int(mask, 1, 0);
+
+- return 0;
++fail:
++ if (qr_bin != NULL) free(qr_bin);
++ if (qnr_bin != NULL) free(qnr_bin);
++ if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin);
++ BN_free(r);
++ BN_free(res);
++ BN_free(qr_or_qnr);
++ BN_free(pm1);
++
++ return ret;
+ }
+
+-int compute_password_element (pwd_session_t *session, uint16_t grp_num,
++int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num,
+ char const *password, int password_len,
+ char const *id_server, int id_server_len,
+ char const *id_peer, int id_peer_len,
+ uint32_t *token)
+ {
+- BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL;
+- HMAC_CTX *ctx = NULL;
+- uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr;
+- int nid, is_odd, primebitlen, primebytelen, ret = 0;
+-
+- ctx = HMAC_CTX_new();
+- if (ctx == NULL) {
+- DEBUG("failed allocating HMAC context");
+- goto fail;
+- }
++ BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL;
++ EVP_MD_CTX *hmac_ctx;
++ EVP_PKEY *hmac_pkey;
++ uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = NULL, ctr;
++ int nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask;
++ int save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp;
++ unsigned int skip;
++
++ MEM(hmac_ctx = EVP_MD_CTX_new());
++ MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero)));
+
+ switch (grp_num) { /* from IANA registry for IKE D-H groups */
+ case 19:
+@@ -159,17 +294,23 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
+ goto fail;
+ }
+
+- if (((rnd = BN_new()) == NULL) ||
+- ((cofactor = BN_new()) == NULL) ||
++ if (((rnd = consttime_BN()) == NULL) ||
+ ((session->pwe = EC_POINT_new(session->group)) == NULL) ||
+- ((session->order = BN_new()) == NULL) ||
+- ((session->prime = BN_new()) == NULL) ||
+- ((x_candidate = BN_new()) == NULL)) {
++ ((session->order = consttime_BN()) == NULL) ||
++ ((session->prime = consttime_BN()) == NULL) ||
++ ((qr = consttime_BN()) == NULL) ||
++ ((qnr = consttime_BN()) == NULL) ||
++ ((x_candidate = consttime_BN()) == NULL) ||
++ ((y_sqrd = consttime_BN()) == NULL) ||
++ ((y1 = consttime_BN()) == NULL) ||
++ ((y2 = consttime_BN()) == NULL) ||
++ ((y = consttime_BN()) == NULL) ||
++ ((exp = consttime_BN()) == NULL)) {
+ DEBUG("unable to create bignums");
+ goto fail;
+ }
+
+- if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) {
++ if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) {
+ DEBUG("unable to get prime for GFp curve");
+ goto fail;
+ }
+@@ -179,46 +320,80 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
+ goto fail;
+ }
+
+- if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) {
+- DEBUG("unable to get cofactor for curve");
+- goto fail;
+- }
+-
+ primebitlen = BN_num_bits(session->prime);
+ primebytelen = BN_num_bytes(session->prime);
+ if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) {
+ DEBUG("unable to alloc space for prf buffer");
+ goto fail;
+ }
++ if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
++ DEBUG("unable to alloc space for x buffer");
++ goto fail;
++ }
++ if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
++ DEBUG("unable to alloc space for pm1 buffer");
++ goto fail;
++ }
++ if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
++ DEBUG("unable to alloc space for y1 buffer");
++ goto fail;
++ }
++ if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
++ DEBUG("unable to alloc space for y2 buffer");
++ goto fail;
++ }
++ if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
++ DEBUG("unable to alloc space for y buffer");
++ goto fail;
++ }
++
++
++ /*
++ * derive random quadradic residue and quadratic non-residue
++ */
++ do {
++ BN_rand_range(qr, session->prime);
++ } while (legendre(qr, session->prime, session->bnctx) != 1);
++
++ do {
++ BN_rand_range(qnr, session->prime);
++ } while (legendre(qnr, session->prime, session->bnctx) != -1);
++
++ if (!BN_sub(rnd, session->prime, BN_value_one())) {
++ goto fail;
++ }
++ BN_bn2bin(rnd, pm1buf);
++
++ save_is_odd = 0;
++ found = 0;
++ memset(xbuf, 0, primebytelen);
+ ctr = 0;
+- while (1) {
+- if (ctr > 100) {
+- DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num);
+- goto fail;
+- }
++ while (ctr < 40) {
+ ctr++;
+
+ /*
+ * compute counter-mode password value and stretch to prime
+- * pwd-seed = H(token | peer-id | server-id | password |
+- * counter)
++ * pwd-seed = H(token | peer-id | server-id | password |
++ * counter)
+ */
+- H_Init(ctx);
+- H_Update(ctx, (uint8_t *)token, sizeof(*token));
+- H_Update(ctx, (uint8_t const *)id_peer, id_peer_len);
+- H_Update(ctx, (uint8_t const *)id_server, id_server_len);
+- H_Update(ctx, (uint8_t const *)password, password_len);
+- H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr));
+- H_Final(ctx, pwe_digest);
++ EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey);
++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token));
++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len);
++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len);
++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len);
++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr));
++
++ {
++ size_t mdlen = SHA256_DIGEST_LENGTH;
++
++ EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen);
++ EVP_MD_CTX_reset(hmac_ctx);
++ }
+
+ BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd);
+- if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
+- strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) {
+- DEBUG("key derivation function failed");
+- goto fail;
+- }
++ eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
++ strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen);
+
+- BN_bin2bn(prfbuf, primebytelen, x_candidate);
+ /*
+ * eap_pwd_kdf() returns a string of bits 0..primebitlen but
+ * BN_bin2bn will treat that string of bits as a big endian
+@@ -226,49 +401,86 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
+ * then excessive bits-- those _after_ primebitlen-- so now
+ * we have to shift right the amount we masked off.
+ */
+- if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8)));
+- if (BN_ucmp(x_candidate, session->prime) >= 0) continue;
++ if (primebitlen % 8) {
++ rbits = 8 - (primebitlen % 8);
++ for (i = primebytelen - 1; i > 0; i--) {
++ prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits);
++ }
++ prfbuf[0] >>= rbits;
++ }
++ BN_bin2bn(prfbuf, primebytelen, x_candidate);
+
+ /*
+- * need to unambiguously identify the solution, if there is
+- * one...
+- */
+- is_odd = BN_is_odd(rnd) ? 1 : 0;
++ * it would've been better if the spec reduced the candidate
++ * modulo the prime but it didn't. So if the candidate >= prime
++ * we need to skip it but still run through the operations below
++ */
++ cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen);
++ skip = const_time_fill_msb((unsigned int)cmp);
+
+ /*
+- * solve the quadratic equation, if it's not solvable then we
+- * don't have a point
+- */
+- if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) {
+- continue;
+- }
++ * need to unambiguously identify the solution, if there is
++ * one..
++ */
++ is_odd = BN_is_odd(rnd);
+
+ /*
+- * If there's a solution to the equation then the point must be
+- * on the curve so why check again explicitly? OpenSSL code
+- * says this is required by X9.62. We're not X9.62 but it can't
+- * hurt just to be sure.
+- */
+- if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) {
+- DEBUG("EAP-pwd: point is not on curve");
+- continue;
+- }
++ * check whether x^3 + a*x + b is a quadratic residue
++ *
++ * save the first quadratic residue we find in the loop but do
++ * it in constant time.
++ */
++ do_equation(session->group, y_sqrd, x_candidate, session->bnctx);
++ qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx);
+
+- if (BN_cmp(cofactor, BN_value_one())) {
+- /* make sure the point is not in a small sub-group */
+- if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe,
+- cofactor, NULL)) {
+- DEBUG("EAP-pwd: cannot multiply generator by order");
+- continue;
+- }
++ /*
++ * if the candidate >= prime then we want to skip it
++ */
++ qr_or_qnr = const_time_select(skip, 0, qr_or_qnr);
+
+- if (EC_POINT_is_at_infinity(session->group, session->pwe)) {
+- DEBUG("EAP-pwd: point is at infinity");
+- continue;
+- }
+- }
+- /* if we got here then we have a new generator. */
+- break;
++ /*
++ * if we haven't found PWE yet (found = 0) then mask will be true,
++ * if we have found PWE then mask will be false
++ */
++ mask = const_time_select(found, 0, -1);
++
++ /*
++ * save will be 1 if we want to save this value-- i.e. we haven't
++ * found PWE yet and this is a quadratic residue-- and 0 otherwise
++ */
++ save = const_time_select(mask, qr_or_qnr, 0);
++
++ /*
++ * mask will be true (-1) if we want to save this and false (0)
++ * otherwise
++ */
++ mask = const_time_eq(save, 1);
++
++ const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf);
++ save_is_odd = const_time_select(mask, is_odd, save_is_odd);
++ found = const_time_select(mask, -1, found);
++ }
++
++ /*
++ * now we can savely construct PWE
++ */
++ BN_bin2bn(xbuf, primebytelen, x_candidate);
++ do_equation(session->group, y_sqrd, x_candidate, session->bnctx);
++ if ( !BN_add(exp, session->prime, BN_value_one()) ||
++ !BN_rshift(exp, exp, 2) ||
++ !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) ||
++ !BN_sub(y2, session->prime, y1) ||
++ !BN_bn2bin(y1, y1buf) ||
++ !BN_bn2bin(y2, y2buf)) {
++ DEBUG("unable to compute y");
++ goto fail;
++ }
++ mask = const_time_eq(save_is_odd, BN_is_odd(y1));
++ const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf);
++ if (BN_bin2bn(ybuf, primebytelen, y) == NULL ||
++ !EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) {
++ DEBUG("unable to set point coordinate");
++ goto fail;
+ }
+
+ session->group_num = grp_num;
+@@ -278,78 +490,89 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
+ }
+
+ /* cleanliness and order.... */
+- BN_clear_free(cofactor);
+ BN_clear_free(x_candidate);
++ BN_clear_free(y_sqrd);
++ BN_clear_free(qr);
++ BN_clear_free(qnr);
+ BN_clear_free(rnd);
+- talloc_free(prfbuf);
+- HMAC_CTX_free(ctx);
++ BN_clear_free(y1);
++ BN_clear_free(y2);
++ BN_clear_free(y);
++ BN_clear_free(exp);
++
++ if (prfbuf) talloc_free(prfbuf);
++ if (xbuf) talloc_free(xbuf);
++ if (pm1buf) talloc_free(pm1buf);
++ if (y1buf) talloc_free(y1buf);
++ if (y2buf) talloc_free(y2buf);
++ if (ybuf) talloc_free(ybuf);
++
++ EVP_MD_CTX_free(hmac_ctx);
++ EVP_PKEY_free(hmac_pkey);
+
+ return ret;
+ }
+
+-int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) {
++int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx)
++{
+ BIGNUM *mask = NULL;
+ int ret = -1;
+
+- if (((session->private_value = BN_new()) == NULL) ||
+- ((session->my_element = EC_POINT_new(session->group)) == NULL) ||
+- ((session->my_scalar = BN_new()) == NULL) ||
+- ((mask = BN_new()) == NULL)) {
+- DEBUG2("server scalar allocation failed");
+- goto fail;
+- }
++ MEM(session->private_value = BN_new());
++ MEM(session->my_element = EC_POINT_new(session->group));
++ MEM(session->my_scalar = BN_new());
++
++ MEM(mask = BN_new());
+
+ if (BN_rand_range(session->private_value, session->order) != 1) {
+- DEBUG2("Unable to get randomness for private_value");
+- goto fail;
++ REDEBUG("Unable to get randomness for private_value");
++ goto error;
+ }
+ if (BN_rand_range(mask, session->order) != 1) {
+- DEBUG2("Unable to get randomness for mask");
+- goto fail;
++ REDEBUG("Unable to get randomness for mask");
++ goto error;
+ }
+ BN_add(session->my_scalar, session->private_value, mask);
+- BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx);
++ BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx);
+
+- if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) {
+- DEBUG2("server element allocation failed");
+- goto fail;
++ if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) {
++ REDEBUG("Server element allocation failed");
++ goto error;
+ }
+
+- if (!EC_POINT_invert(session->group, session->my_element, bnctx)) {
+- DEBUG2("server element inversion failed");
+- goto fail;
++ if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) {
++ REDEBUG("Server element inversion failed");
++ goto error;
+ }
+
+ ret = 0;
+
+-fail:
++error:
+ BN_clear_free(mask);
+
+ return ret;
+ }
+
+-int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx)
++int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx)
+ {
+- uint8_t *ptr;
+- size_t data_len;
+- BIGNUM *x = NULL, *y = NULL, *cofactor = NULL;
+- EC_POINT *K = NULL, *point = NULL;
+- int res = 1;
+-
+- if (((session->peer_scalar = BN_new()) == NULL) ||
+- ((session->k = BN_new()) == NULL) ||
+- ((cofactor = BN_new()) == NULL) ||
+- ((x = BN_new()) == NULL) ||
+- ((y = BN_new()) == NULL) ||
+- ((point = EC_POINT_new(session->group)) == NULL) ||
+- ((K = EC_POINT_new(session->group)) == NULL) ||
+- ((session->peer_element = EC_POINT_new(session->group)) == NULL)) {
+- DEBUG2("pwd: failed to allocate room to process peer's commit");
+- goto finish;
+- }
++ uint8_t *ptr;
++ size_t data_len;
++ BIGNUM *x = NULL, *y = NULL, *cofactor = NULL;
++ EC_POINT *K = NULL, *point = NULL;
++ int ret = 1;
++
++ MEM(session->peer_scalar = BN_new());
++ MEM(session->k = BN_new());
++ MEM(session->peer_element = EC_POINT_new(session->group));
++ MEM(point = EC_POINT_new(session->group));
++ MEM(K = EC_POINT_new(session->group));
++
++ MEM(cofactor = BN_new());
++ MEM(x = BN_new());
++ MEM(y = BN_new());
+
+ if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) {
+- DEBUG2("pwd: unable to get group co-factor");
++ REDEBUG("Unable to get group co-factor");
+ goto finish;
+ }
+
+@@ -361,7 +584,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
+ * Did the peer send enough data?
+ */
+ if (in_len < (2 * data_len + BN_num_bytes(session->order))) {
+- DEBUG("pwd: Invalid commit packet");
++ REDEBUG("Invalid commit packet");
+ goto finish;
+ }
+
+@@ -377,54 +600,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
+ if (BN_is_zero(session->peer_scalar) ||
+ BN_is_one(session->peer_scalar) ||
+ BN_cmp(session->peer_scalar, session->order) >= 0) {
+- ERROR("Peer's scalar is not within the allowed range");
++ REDEBUG("Peer's scalar is not within the allowed range");
+ goto finish;
+ }
+
+- if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
+- DEBUG2("pwd: unable to get coordinates of peer's element");
++ if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
++ REDEBUG("Unable to get coordinates of peer's element");
+ goto finish;
+ }
+
+ /* validate received element */
+- if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) ||
++ if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) ||
+ EC_POINT_is_at_infinity(session->group, session->peer_element)) {
+- ERROR("Peer's element is not a point on the elliptic curve");
++ REDEBUG("Peer's element is not a point on the elliptic curve");
+ goto finish;
+ }
+
+ /* check to ensure peer's element is not in a small sub-group */
+ if (BN_cmp(cofactor, BN_value_one())) {
+ if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) {
+- DEBUG2("pwd: unable to multiply element by co-factor");
++ REDEBUG("Unable to multiply element by co-factor");
+ goto finish;
+ }
+
+ if (EC_POINT_is_at_infinity(session->group, point)) {
+- DEBUG2("pwd: peer's element is in small sub-group");
++ REDEBUG("Peer's element is in small sub-group");
+ goto finish;
+ }
+ }
+
+ /* detect reflection attacks */
+ if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 ||
+- EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) {
+- ERROR("Reflection attack detected");
++ EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) {
++ REDEBUG("Reflection attack detected");
+ goto finish;
+ }
+
+ /* compute the shared key, k */
+- if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) ||
+- (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) ||
+- (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) {
+- DEBUG2("pwd: unable to compute shared key, k");
++ if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) ||
++ (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) ||
++ (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) {
++ REDEBUG("Unable to compute shared key, k");
+ goto finish;
+ }
+
+ /* ensure that the shared key isn't in a small sub-group */
+ if (BN_cmp(cofactor, BN_value_one())) {
+ if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) {
+- DEBUG2("pwd: unable to multiply k by co-factor");
++ REDEBUG("Unable to multiply k by co-factor");
+ goto finish;
+ }
+ }
+@@ -436,15 +659,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
+ * sure" so let's be safe.
+ */
+ if (EC_POINT_is_at_infinity(session->group, K)) {
+- DEBUG2("pwd: k is point-at-infinity!");
++ REDEBUG("K is point-at-infinity");
+ goto finish;
+ }
+
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) {
+- DEBUG2("pwd: unable to get shared secret from K");
++ if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) {
++ REDEBUG("Unable to get shared secret from K");
+ goto finish;
+ }
+- res = 0;
++ ret = 0;
+
+ finish:
+ EC_POINT_clear_free(K);
+@@ -453,36 +676,29 @@ finish:
+ BN_clear_free(x);
+ BN_clear_free(y);
+
+- return res;
++ return ret;
+ }
+
+-int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
++int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
+ {
+- BIGNUM *x = NULL, *y = NULL;
+- HMAC_CTX *ctx = NULL;
+- uint8_t *cruft = NULL;
+- int offset, req = -1;
+-
+- ctx = HMAC_CTX_new();
+- if (ctx == NULL) {
+- DEBUG2("pwd: unable to allocate HMAC context!");
+- goto finish;
+- }
++ BIGNUM *x = NULL, *y = NULL;
++ HMAC_CTX *hmac_ctx = NULL;
++ uint8_t *cruft = NULL;
++ int offset, req = -1;
+
+ /*
+ * Each component of the cruft will be at most as big as the prime
+ */
+- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) ||
+- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
+- DEBUG2("pwd: unable to allocate space to compute confirm!");
+- goto finish;
+- }
++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
++ MEM(x = BN_new());
++ MEM(y = BN_new());
+
+ /*
+ * commit is H(k | server_element | server_scalar | peer_element |
+ * peer_scalar | ciphersuite)
+ */
+- H_Init(ctx);
++ MEM(hmac_ctx = HMAC_CTX_new());
++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
+
+ /*
+ * Zero the memory each time because this is mod prime math and some
+@@ -492,24 +708,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ */
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
+ BN_bn2bin(session->k, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * next is server element: x, y
+ */
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) {
+- DEBUG2("pwd: unable to get coordinates of server element");
++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
++ REDEBUG("Unable to get coordinates of server element");
+ goto finish;
+ }
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
+ BN_bn2bin(x, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
+ BN_bn2bin(y, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * and server scalar
+@@ -517,25 +733,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
+ BN_bn2bin(session->my_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+
+ /*
+ * next is peer element: x, y
+ */
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
+- DEBUG2("pwd: unable to get coordinates of peer's element");
++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
++ REDEBUG("Unable to get coordinates of peer's element");
+ goto finish;
+ }
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
+ BN_bn2bin(x, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
+ BN_bn2bin(y, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * and peer scalar
+@@ -543,52 +759,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
+ BN_bn2bin(session->peer_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+
+ /*
+ * finally, ciphersuite
+ */
+- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+
+- H_Final(ctx, out);
++ pwd_hmac_final(hmac_ctx, out);
+
+ req = 0;
++
+ finish:
++ HMAC_CTX_free(hmac_ctx);
+ talloc_free(cruft);
+ BN_free(x);
+ BN_free(y);
+- HMAC_CTX_free(ctx);
+
+ return req;
+ }
+
+-int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
++int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
+ {
+- BIGNUM *x = NULL, *y = NULL;
+- HMAC_CTX *ctx = NULL;
+- uint8_t *cruft = NULL;
+- int offset, req = -1;
+-
+- ctx = HMAC_CTX_new();
+- if (ctx == NULL) {
+- DEBUG2("pwd: unable to allocate HMAC context!");
+- goto finish;
+- }
++ BIGNUM *x = NULL, *y = NULL;
++ HMAC_CTX *hmac_ctx = NULL;
++ uint8_t *cruft = NULL;
++ int offset, req = -1;
+
+ /*
+ * Each component of the cruft will be at most as big as the prime
+ */
+- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) ||
+- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
+- DEBUG2("pwd: unable to allocate space to compute confirm!");
+- goto finish;
+- }
++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
++ MEM(x = BN_new());
++ MEM(y = BN_new());
+
+ /*
+ * commit is H(k | server_element | server_scalar | peer_element |
+ * peer_scalar | ciphersuite)
+ */
+- H_Init(ctx);
++ MEM(hmac_ctx = HMAC_CTX_new());
++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
+
+ /*
+ * Zero the memory each time because this is mod prime math and some
+@@ -598,25 +808,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ */
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
+ BN_bn2bin(session->k, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * then peer element: x, y
+ */
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
+- DEBUG2("pwd: unable to get coordinates of peer's element");
++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
++ REDEBUG("Unable to get coordinates of peer's element");
+ goto finish;
+ }
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
+ BN_bn2bin(x, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
+ BN_bn2bin(y, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * and peer scalar
+@@ -624,24 +834,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
+ BN_bn2bin(session->peer_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+
+ /*
+ * then server element: x, y
+ */
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) {
+- DEBUG2("pwd: unable to get coordinates of server element");
++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
++ REDEBUG("Unable to get coordinates of server element");
+ goto finish;
+ }
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
+ BN_bn2bin(x, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
+ BN_bn2bin(y, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+ /*
+ * and server scalar
+@@ -649,94 +859,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
+ BN_bn2bin(session->my_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+
+ /*
+ * finally, ciphersuite
+ */
+- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+
+- H_Final(ctx, out);
++ pwd_hmac_final(hmac_ctx, out);
+
+ req = 0;
+ finish:
++ HMAC_CTX_free(hmac_ctx);
+ talloc_free(cruft);
+ BN_free(x);
+ BN_free(y);
+- HMAC_CTX_free(ctx);
+
+ return req;
+ }
+
+-int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk)
++int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk)
+ {
+- HMAC_CTX *ctx = NULL;
+- uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL;
+- uint8_t session_id[SHA256_DIGEST_LENGTH + 1];
+- uint8_t msk_emsk[128]; /* 64 each */
+- int offset, ret = -1;
+-
+- ctx = HMAC_CTX_new();
+- if (ctx == NULL) {
+- DEBUG2("pwd: unable to allocate HMAC context!");
+- goto finish;
+- }
++ HMAC_CTX *hmac_ctx;
++ uint8_t mk[SHA256_DIGEST_LENGTH], *cruft;
++ uint8_t session_id[SHA256_DIGEST_LENGTH + 1];
++ uint8_t msk_emsk[128]; /* 64 each */
++ int offset;
+
+- if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) {
+- DEBUG2("pwd: unable to allocate space to compute keys");
+- goto finish;
+- }
++ MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime)));
++ MEM(hmac_ctx = HMAC_CTX_new());
+
+ /*
+ * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
+ * scal_s)
+ */
+ session_id[0] = PW_EAP_PWD;
+- H_Init(ctx);
+- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ BN_bn2bin(session->peer_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+ offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ BN_bn2bin(session->my_scalar, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->order));
+- H_Final(ctx, (uint8_t *)&session_id[1]);
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
++ pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]);
+
+ /* then compute MK = H(k | commit-peer | commit-server) */
+- H_Init(ctx);
++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
+
+ memset(cruft, 0, BN_num_bytes(session->prime));
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
+ BN_bn2bin(session->k, cruft + offset);
+- H_Update(ctx, cruft, BN_num_bytes(session->prime));
++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
+
+- H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH);
++ HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH);
+
+- H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH);
++ HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH);
+
+- H_Final(ctx, mk);
++ pwd_hmac_final(hmac_ctx, mk);
+
+ /* stretch the mk with the session-id to get MSK | EMSK */
+- if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id,
+- SHA256_DIGEST_LENGTH + 1, msk_emsk,
+- /* it's bits, ((64 + 64) * 8) */
+- 1024) != 0) {
+- DEBUG("key derivation function failed");
+- goto finish;
+- }
++ eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id,
++ SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024); /* it's bits, ((64 + 64) * 8) */
+
+ memcpy(msk, msk_emsk, 64);
+ memcpy(emsk, msk_emsk + 64, 64);
+
+- ret = 0;
+-finish:
++ HMAC_CTX_free(hmac_ctx);
+ talloc_free(cruft);
+- HMAC_CTX_free(ctx);
+- return ret;
++ return 0;
+ }
+-
+-
+-
+-
+diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
+index ca12778f61..a40a346069 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
+@@ -102,18 +102,22 @@ typedef struct _pwd_session_t {
+ EC_POINT *my_element;
+ EC_POINT *peer_element;
+ uint8_t my_confirm[SHA256_DIGEST_LENGTH];
++ uint8_t prep;
++ uint8_t salt_present;
++ uint8_t salt_len;
++ uint8_t salt[255];
+ } pwd_session_t;
+
+-int compute_password_element(pwd_session_t *sess, uint16_t grp_num,
++int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num,
+ char const *password, int password_len,
+ char const *id_server, int id_server_len,
+ char const *id_peer, int id_peer_len,
+ uint32_t *token);
+-int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx);
+-int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx);
+-int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
+-int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
+-int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm,
++int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx);
++int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx);
++int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
++int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
++int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm,
+ uint8_t *msk, uint8_t *emsk);
+ #ifdef PRINTBUF
+ void print_buf(char *str, uint8_t *buf, int len);
+diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
+index 18ab97f148..4992a2aeef 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
+@@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ #define MPPE_KEY_LEN 32
+ #define MSK_EMSK_LEN (2*MPPE_KEY_LEN)
+
++/* EAP-PWD can use different preprocessing (prep) modes to mangle the password
++ * before proving to both parties that they both know the same (mangled) password.
++ *
++ * The server advertises a preprocessing mode to the client. Only "none" is
++ * mandatory to implement.
++ *
++ * What is a good selection on the preprocessing mode?
++ *
++ * a) the server uses a hashed password
++ * b) the client uses a hashed password
++ *
++ * a | b | result
++ * --+---+---------------------------------------
++ * n | n | none
++ * n | y | hint needed (cannot know automatically)
++ * y | n | select by hash given
++ * y | y | only works if both have the same hash; select by hash given
++ *
++ * Which hash functions does the server or client need to implement?
++ *
++ * a | b | server | client
++ * --+---+------------------------+----------------------
++ * n | n | none | none
++ * n | y | as configured | none
++ * y | n | none | as selected by server
++ * y | y | none | none
++ *
++ * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement.
++ * Can we avoid implementing them all? Only if they are provided as hash by some
++ * other module, e.g. in SQL or statically in password database.
++ *
++ * Therefore we select the preprocessing mode by the type of password given if
++ * in automatic mode:
++ * a) Cleartext-Password or User-Password: None.
++ * If the client only supports a hash (e.g. on Windows it might only have an
++ * NT-Password), do not provide a Cleartext-Password attribute but instead
++ * preprocess the password externally (e.g. hash the Cleartext-Password
++ * into an NT-Password and drop the Cleartext-Password).
++ * b) NT-Password: rfc2759 (prep=MS).
++ * The NT-Password Hash is hashed into a HashNTPasswordHash hash.
++ * c) EAP-Pwd-Password-Hash - provides hash as binary
++ * EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client
++ * (RFC 8146)
++ * EAP-Pwd-Password-Prep - constant to transmit to client in prep field
++ *
++ * Though, there is one issue left. The method needs to be selected in
++ * EAP-PWD-ID/Request, that is the first message from server and thus before
++ * the client sent its peer-id. This is feasable using the EAP-Identity frame
++ * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway.
++ * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use
++ * only the peer_id (inner identity). This toogle is an integer to also support
++ * setting currently unknown nor not implemented preprocessing methods.
++ *
++ * The toogle is named "prep", is a module configuration item, and accepts the
++ * following values:
++ * prep | meaning
++ * -------+--------------------------------------------------------------------
++ * -1 | [automatic] discover using method described above from EAP-Identity
++ * | as User-Name before EAP-PWD-Id/Request
++ * 0..255 | [static] Fixed password preprocessing method. Expects virtual
++ * | server to provide matching password given EAP-PWD
++ * | peer-id as User-Name. The virtual server is provided
++ * | with EAP-Pwd-Password-Prep containing the configured
++ * | prep value.
++ * else | reserved/invalid
++ *
++ * Attributes to provide Password/Password-Hash and possibly salt.
++ * prep | accepted attributes
++ * -------+--------------------------------------------------------------------
++ * -1 | see above for automatic discovery
++ * 0 | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash
++ * 1 | Use NT-Password, Cleartext-Password, User-Password or
++ * | give hashed NT-Password hash in EAP-Pwd-Password-Hash
++ * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt.
++ *
++ * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex
++ * string, they are decoded as hex if module config option unhex=1 (default).
++ * Set it to zero if you provide binary input.
++ */
++
+ static CONF_PARSER pwd_module_config[] = {
+ { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" },
+ { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" },
+ { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL },
+ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL },
++ { "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" },
++ { "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" },
+ CONF_PARSER_TERMINATOR
+ };
+
+@@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance)
+ return -1;
+ }
+
++ if (inst->prep < -1 || inst->prep > 255) {
++ cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep);
++ return -1;
++ }
++
+ return 0;
+ }
+
+@@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds)
+ return 1;
+ }
+
++static void normify(REQUEST *request, VALUE_PAIR *vp)
++{
++ size_t decoded;
++ size_t expected_len;
++ uint8_t *buffer;
++
++ rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING));
++
++ if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return;
++
++ expected_len = vp->vp_length / 2;
++ buffer = talloc_zero_array(request, uint8_t, expected_len);
++ rad_assert(buffer);
++
++ decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length);
++ if (decoded == expected_len) {
++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes",
++ vp->da->name, vp->vp_length, decoded);
++ fr_pair_value_memcpy(vp, buffer, decoded);
++ } else {
++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes",
++ vp->da->name, vp->vp_length, expected_len, decoded);
++ }
++
++ talloc_free(buffer);
++}
++
++static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) {
++ REQUEST *fake;
++ VALUE_PAIR *vp, *pw;
++ const char *pwbuf;
++ int pw_len;
++ uint8_t nthash[MD4_DIGEST_LENGTH];
++ uint8_t nthashash[MD4_DIGEST_LENGTH];
++ int ret = -1;
++ eap_type_t old_eap_type = 0;
++
++ if ((fake = request_alloc_fake(request)) == NULL) {
++ RDEBUG("pwd unable to create fake request!");
++ return ret;
++ }
++ fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0);
++ if (!fake->username) {
++ RDEBUG("Failed creating pair for peer id");
++ goto out;
++ }
++ fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len);
++ fr_pair_add(&fake->packet->vps, fake->username);
++
++ if (inst->prep >= 0) {
++ vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0);
++ rad_assert(vp != NULL);
++ vp->vp_byte = inst->prep;
++ fr_pair_add(&fake->packet->vps, vp);
++ }
++
++ if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
++ fake->server = vp->vp_strvalue;
++ } else if (inst->virtual_server) {
++ fake->server = inst->virtual_server;
++ } /* else fake->server == request->server */
++
++ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) {
++ /* EAP-Type = NAK here if inst->prep == -1.
++ * But this does not help the virtual server to differentiate
++ * based on which EAP method was selected, that is to properly
++ * prepare session-state: for PWD.
++ * So fake EAP-Type = PWD here for the time of the inner request.
++ */
++ old_eap_type = vp->vp_integer;
++ vp->vp_integer = PW_EAP_PWD;
++ }
++ RDEBUG("Sending tunneled request");
++ rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL);
++
++ if (fake->server) {
++ RDEBUG("server %s {", fake->server);
++ } else {
++ RDEBUG("server {");
++ }
++
++ /*
++ * Call authorization recursively, which will
++ * get the password.
++ */
++ RINDENT();
++ process_authorize(0, fake);
++ REXDENT();
++
++ /*
++ * Note that we don't do *anything* with the reply
++ * attributes.
++ */
++ if (fake->server) {
++ RDEBUG("} # server %s", fake->server);
++ } else {
++ RDEBUG("}");
++ }
++
++ RDEBUG("Got tunneled reply code %d", fake->reply->code);
++ rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL);
++
++ if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) {
++ vp->vp_integer = old_eap_type;
++ }
++
++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
++ if (!pw) {
++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY);
++ }
++
++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) {
++ VERIFY_VP(pw);
++ session->prep = EAP_PWD_PREP_NONE;
++
++ RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication",
++ session->peer_id);
++
++ pwbuf = pw->vp_strvalue;
++ pw_len = pw->vp_length;
++
++ goto success;
++ }
++
++ pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY);
++
++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) {
++ VERIFY_VP(pw);
++ session->prep = EAP_PWD_PREP_MS;
++
++ RDEBUG("Use NT-Password for %s to do pwd authentication",
++ session->peer_id);
++
++ if (pw->vp_length != MD4_DIGEST_LENGTH) {
++ RDEBUG("NT-Password invalid length");
++ goto out;
++ }
++
++ fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length);
++ pwbuf = (const char*) nthashash;
++ pw_len = MD4_DIGEST_LENGTH;
++
++ goto success;
++ }
++
++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
++ if (!pw) {
++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY);
++ }
++
++ if (pw && inst->prep == EAP_PWD_PREP_MS) {
++ VERIFY_VP(pw);
++ session->prep = EAP_PWD_PREP_NONE;
++
++ RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication",
++ session->peer_id);
++
++ // compute NT-Hash from Cleartext-Password
++ ssize_t len;
++ uint8_t ucs2_password[512];
++ len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length);
++ if (len < 0) {
++ ERROR("rlm_eap_pwd: Error converting password to UCS2");
++ goto out;
++ }
++ fr_md4_calc(nthash, ucs2_password, len);
++
++ fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH);
++ pwbuf = (const char*) nthashash;
++ pw_len = MD4_DIGEST_LENGTH;
++
++ goto success;
++ }
++
++ vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY);
++ if (vp) {
++ VERIFY_VP(vp);
++ }
++ if (vp && inst->prep < 0) {
++ RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication",
++ vp->vp_byte, session->peer_id);
++ session->prep = vp->vp_byte;
++ } else if (vp && inst->prep != vp->vp_byte) {
++ RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s",
++ session->peer_id);
++ goto out;
++ } else if (inst->prep < 0) {
++ RDEBUG2("Missing EAP-Pwd-Password-Prep for %s",
++ session->peer_id);
++ goto out;
++ }
++
++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY);
++ if (pw) {
++ VERIFY_VP(pw);
++
++ RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication",
++ session->peer_id);
++
++ if (inst->unhex) normify(request, pw);
++
++ if (pw->vp_length > 255) {
++ /* salt len is 1 byte */
++ RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)");
++ goto out;
++ }
++ rad_assert(pw->vp_length <= sizeof(session->salt));
++
++ session->salt_present = 1;
++ session->salt_len = pw->vp_length;
++ memcpy(session->salt, pw->vp_octets, pw->vp_length);
++ }
++
++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY);
++ if (pw) {
++ VERIFY_VP(pw);
++
++ RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication",
++ session->peer_id);
++
++ if (inst->unhex) normify(request, pw);
++
++ pwbuf = (const char*) pw->vp_octets;
++ pw_len = pw->vp_length;
++
++ goto success;
++ }
++
++ RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s",
++ session->peer_id);
++ goto out;
++
++success:
++ if (RDEBUG_ENABLED4) {
++ char outbuf[1024];
++ char *p = outbuf;
++ for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) {
++ p += sprintf(p, "%02hhX", pwbuf[i]);
++ }
++ RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len);
++ }
++
++ if (compute_password_element(request, session, session->group_num,
++ pwbuf, pw_len,
++ inst->server_id, strlen(inst->server_id),
++ session->peer_id, strlen(session->peer_id),
++ &session->token)) {
++ RDEBUG("failed to obtain password element");
++ goto out;
++ }
++
++ ret = 0;
++out:
++ talloc_free(fake);
++ return ret;
++}
++
+ static int mod_session_init (void *instance, eap_handler_t *handler)
+ {
+ pwd_session_t *session;
+ eap_pwd_t *inst = (eap_pwd_t *)instance;
+ VALUE_PAIR *vp;
+ pwd_id_packet_t *packet;
++ REQUEST *request;
+
+ if (!inst || !handler) {
+ ERROR("rlm_eap_pwd: Initiate, NULL data provided");
+ return 0;
+ }
+
++ request = handler->request;
++ if (!request) {
++ ERROR("rlm_eap_pwd: NULL request provided");
++ return 0;
++ }
++
+ /*
+ * make sure the server's been configured properly
+ */
+@@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler)
+ session->out_pos = 0;
+ handler->opaque = session;
+
++ session->token = fr_rand();
++ if (inst->prep < 0) {
++ RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity);
++ session->peer_id_len = strlen(handler->identity);
++ if (session->peer_id_len >= sizeof(session->peer_id)) {
++ RDEBUG("identity is malformed");
++ return 0;
++ }
++ memcpy(session->peer_id, handler->identity, session->peer_id_len);
++ session->peer_id[session->peer_id_len] = '\0';
++
++ /*
++ * make fake request to get the password for the usable ID
++ * in order to identity prep
++ */
++ if (fetch_and_process_password(session, handler->request, inst) < 0) {
++ RDEBUG("failed to find password for %s to do pwd authentication (init)",
++ session->peer_id);
++ return 0;
++ }
++ } else {
++ session->prep = inst->prep;
++ }
++
+ /*
+ * construct an EAP-pwd-ID/Request
+ */
+@@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler)
+ packet->group_num = htons(session->group_num);
+ packet->random_function = EAP_PWD_DEF_RAND_FUN;
+ packet->prf = EAP_PWD_DEF_PRF;
+- session->token = fr_rand();
+ memcpy(packet->token, (char *)&session->token, 4);
+- packet->prep = EAP_PWD_PREP_NONE;
++ packet->prep = session->prep;
+ memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) );
+
+ handler->stage = PROCESS;
+@@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ pwd_session_t *session;
+ pwd_hdr *hdr;
+ pwd_id_packet_t *packet;
++ REQUEST *request;
+ eap_packet_t *response;
+- REQUEST *request, *fake;
+- VALUE_PAIR *pw, *vp;
+ EAP_DS *eap_ds;
+- size_t in_len;
++ size_t in_len, peer_id_len;
+ int ret = 0;
+ eap_pwd_t *inst = (eap_pwd_t *)arg;
+ uint16_t offset;
+ uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN];
+ uint8_t peer_confirm[SHA256_DIGEST_LENGTH];
++ char *peer_id;
+
+ if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0;
+
+@@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+
+ if ((packet->prf != EAP_PWD_DEF_PRF) ||
+ (packet->random_function != EAP_PWD_DEF_RAND_FUN) ||
+- (packet->prep != EAP_PWD_PREP_NONE) ||
++ (packet->prep != session->prep) ||
+ (CRYPTO_memcmp(packet->token, &session->token, 4)) ||
+ (packet->group_num != ntohs(session->group_num))) {
+ RDEBUG2("pwd id response is invalid");
+@@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ ptr += sizeof(uint8_t);
+ *ptr = EAP_PWD_DEF_PRF;
+
+- session->peer_id_len = in_len - sizeof(pwd_id_packet_t);
+- if (session->peer_id_len >= sizeof(session->peer_id)) {
++ peer_id_len = in_len - sizeof(pwd_id_packet_t);
++ if (peer_id_len >= sizeof(session->peer_id)) {
+ RDEBUG2("pwd id response is malformed");
+ return 0;
+ }
++ peer_id = packet->identity;
+
+- memcpy(session->peer_id, packet->identity, session->peer_id_len);
+- session->peer_id[session->peer_id_len] = '\0';
+-
+- /*
+- * make fake request to get the password for the usable ID
+- */
+- if ((fake = request_alloc_fake(handler->request)) == NULL) {
+- RDEBUG("pwd unable to create fake request!");
+- return 0;
+- }
+- fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0);
+- if (!fake->username) {
+- RDEBUG("Failed creating pair for peer id");
+- talloc_free(fake);
+- return 0;
+- }
+- fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len);
+- fr_pair_add(&fake->packet->vps, fake->username);
+-
+- if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
+- fake->server = vp->vp_strvalue;
+- } else if (inst->virtual_server) {
+- fake->server = inst->virtual_server;
+- } /* else fake->server == request->server */
+-
+- RDEBUG("Sending tunneled request");
+- rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL);
+-
+- if (fake->server) {
+- RDEBUG("server %s {", fake->server);
+- } else {
+- RDEBUG("server {");
+- }
++ if (inst->prep >= 0) {
++ /*
++ * make fake request to get the password for the usable ID
++ */
+
+- /*
+- * Call authorization recursively, which will
+- * get the password.
+- */
+- RINDENT();
+- process_authorize(0, fake);
+- REXDENT();
++ session->peer_id_len = peer_id_len;
++ memcpy(session->peer_id, peer_id, peer_id_len);
++ session->peer_id[peer_id_len] = '\0';
+
+- /*
+- * Note that we don't do *anything* with the reply
+- * attributes.
+- */
+- if (fake->server) {
+- RDEBUG("} # server %s", fake->server);
++ if (fetch_and_process_password(session, request, inst) < 0) {
++ RDEBUG2("failed to find password for %s to do pwd authentication",
++ session->peer_id);
++ return 0;
++ }
+ } else {
+- RDEBUG("}");
++ /* verify inner identity == outer identity */
++ if (session->peer_id_len != peer_id_len ||
++ memcmp(session->peer_id, peer_id, peer_id_len) != 0) {
++ char buf[sizeof(session->peer_id)];
++ memcpy(buf, peer_id, peer_id_len);
++ buf[peer_id_len] = '\0';
++
++ RDEBUG2("inner identity(peer_id) %s does not match outer identity %s",
++ buf, session->peer_id);
++ return 0;
++ }
++ RDEBUG2("inner identity matched for %s", session->peer_id);
+ }
+
+- RDEBUG("Got tunneled reply code %d", fake->reply->code);
+- rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL);
+-
+- if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) {
+- DEBUG2("failed to find password for %s to do pwd authentication",
+- session->peer_id);
+- talloc_free(fake);
+- return 0;
+- }
+-
+- if (compute_password_element(session, session->group_num,
+- pw->data.strvalue, strlen(pw->data.strvalue),
+- inst->server_id, strlen(inst->server_id),
+- session->peer_id, strlen(session->peer_id),
+- &session->token)) {
+- DEBUG2("failed to obtain password element");
+- talloc_free(fake);
+- return 0;
+- }
+- TALLOC_FREE(fake);
+-
+ /*
+ * compute our scalar and element
+ */
+- if (compute_scalar_element(session, session->bnctx)) {
++ if (compute_scalar_element(request, session, session->bnctx)) {
+ DEBUG2("failed to compute server's scalar and element");
+ return 0;
+ }
+@@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ /*
+ * element is a point, get both coordinates: x and y
+ */
+- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y,
++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y,
+ session->bnctx)) {
+ DEBUG2("server point assignment failed");
+ BN_clear_free(x);
+@@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ * construct request
+ */
+ session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime));
++ if (session->salt_present)
++ session->out_len += 1 + session->salt_len;
++
+ if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) {
+ return 0;
+ }
+ memset(session->out, 0, session->out_len);
+
+ ptr = session->out;
++ if (session->salt_present) {
++ *ptr = session->salt_len;
++ ptr++;
++
++ memcpy(ptr, session->salt, session->salt_len);
++ ptr += session->salt_len;
++ }
++
+ offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
+ BN_bn2bin(x, ptr + offset);
+ BN_clear_free(x);
+@@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ }
+ break;
+
+- case PWD_STATE_COMMIT:
++ case PWD_STATE_COMMIT:
+ if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) {
+ RDEBUG2("pwd exchange is incorrect: not commit!");
+ return 0;
+@@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ /*
+ * process the peer's commit and generate the shared key, k
+ */
+- if (process_peer_commit(session, in, in_len, session->bnctx)) {
++ if (process_peer_commit(request, session, in, in_len, session->bnctx)) {
+ RDEBUG2("failed to process peer's commit");
+ return 0;
+ }
+@@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ /*
+ * compute our confirm blob
+ */
+- if (compute_server_confirm(session, session->my_confirm, session->bnctx)) {
++ if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) {
+ ERROR("rlm_eap_pwd: failed to compute confirm!");
+ return 0;
+ }
+@@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ RDEBUG2("pwd exchange is incorrect: not commit!");
+ return 0;
+ }
+- if (compute_peer_confirm(session, peer_confirm, session->bnctx)) {
++ if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) {
+ RDEBUG2("pwd exchange cannot compute peer's confirm");
+ return 0;
+ }
+@@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ RDEBUG2("pwd exchange fails: peer confirm is incorrect!");
+ return 0;
+ }
+- if (compute_keys(session, peer_confirm, msk, emsk)) {
++ if (compute_keys(request, session, peer_confirm, msk, emsk)) {
+ RDEBUG2("pwd exchange cannot generate (E)MSK!");
+ return 0;
+ }
+diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
+index 2264566bb6..966646c360 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
+@@ -44,6 +44,8 @@ typedef struct _eap_pwd_t {
+ uint32_t fragment_size;
+ char const *server_id;
+ char const *virtual_server;
++ int32_t prep;
++ int32_t unhex;
+ } eap_pwd_t;
+
+ #endif /* _RLM_EAP_PWD_H */
+diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
+index 4d41cd42e6..d327c575fc 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
+@@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+ static CONF_PARSER module_config[] = {
+ { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL },
+ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL },
++ { "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL },
+ CONF_PARSER_TERMINATOR
+ };
+
+@@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
+ return -1;
+ }
+
++#ifdef TLS1_3_VERSION
++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) ||
++ (inst->tls_conf->min_version == TLS1_3_VERSION)) {
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3");
++ WARN("!! Please set tls_max_version = \"1.2\"");
++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows");
++ WARN("!! This limitation is likely to change in late 2021.");
++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade");
++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
++ }
++#endif
++
+ return 0;
+ }
+
+@@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ tls_session_t *ssn;
+ rlm_eap_tls_t *inst;
+ REQUEST *request = handler->request;
++ bool require_client_cert = true;
+
+ inst = type_arg;
+
+ handler->tls = true;
+
+ /*
+- * EAP-TLS always requires a client certificate.
++ * Respect EAP-TLS-Require-Client-Cert, but only if
++ * enabled in the module configuration.
++ *
++ * We can't change behavior of existing systems, so this
++ * change has to be enabled via a new configuration
++ * option.
+ */
+- ssn = eaptls_session(handler, inst->tls_conf, true);
++ if (inst->configurable_client_cert) {
++ VALUE_PAIR *vp;
++
++ vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY);
++ if (vp && !vp->vp_integer) require_client_cert = false;
++ }
++
++ /*
++ * EAP-TLS always requires a client certificate, and
++ * allows for TLS 1.3 if permitted.
++ */
++ ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true);
+ if (!ssn) {
+ return 0;
+ }
+
+ handler->opaque = ((void *)ssn);
+-
+- /*
+- * Set up type-specific information.
+- */
+- ssn->prf_label = "client EAP encryption";
++ ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */
+
+ /*
+ * TLS session initialization is over. Now handle TLS
+@@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+ if (status == 0) return 0;
+
+@@ -141,7 +168,7 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+
+
+@@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler)
+ /* success */
+ }
+
++ /*
++ * Set the label to a fixed string. For TLS 1.3,
++ * the label is the same for all TLS-based EAP
++ * methods.
++ */
++ tls_session->label = "client EAP encryption";
++
+ /*
+ * Success: Automatically return MPPE keys.
+ */
+diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
+index cebcb92f57..550cbbdce3 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
+@@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t {
+ * Virtual server for checking certificates
+ */
+ char const *virtual_server;
++
++ /*
++ * Configurable EAP-TLS-Require-Client-Cert
++ */
++ bool configurable_client_cert;
+ } rlm_eap_tls_t;
+
+ #endif /* _RLM_EAP_TLS_H */
+diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c
+index a3c575bceb..4e53c92244 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c
++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c
+@@ -181,7 +181,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ client_cert = inst->req_client_cert;
+ }
+
+- ssn = eaptls_session(handler, inst->tls_conf, client_cert);
++ /*
++ * Allow TLS 1.3, it works.
++ */
++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true);
+ if (!ssn) {
+ return 0;
+ }
+@@ -189,9 +192,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ handler->opaque = ((void *)ssn);
+
+ /*
+- * Set up type-specific information.
++ * Set the label to a fixed string. For TLS 1.3, the
++ * label is the same for all TLS-based EAP methods. If
++ * the client is using TLS 1.3, then eaptls_success()
++ * will over-ride this label with the correct label for
++ * TLS 1.3.
+ */
+- ssn->prf_label = "ttls keying material";
++ ssn->label = "ttls keying material";
+
+ /*
+ * TLS session initialization is over. Now handle TLS
+@@ -201,7 +208,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+ if (status == 0) return 0;
+
+@@ -238,7 +245,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
+
+ /*
+@@ -342,8 +349,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
+ * Success: Automatically return MPPE keys.
+ */
+ case PW_CODE_ACCESS_ACCEPT:
+- ret = eaptls_success(handler, 0);
+- goto done;
++ goto do_keys;
+
+ /*
+ * No response packet, MUST be proxying it.
+diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
+index 627d722ed7..cbe423951a 100644
+--- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
+@@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ size_t size;
+ size_t data_left = data_len;
+ VALUE_PAIR *first = NULL;
+- VALUE_PAIR *vp;
++ VALUE_PAIR *vp = NULL;
+ RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */
+ vp_cursor_t out;
++ DICT_ATTR const *da;
+
+ fr_cursor_init(&out, &first);
+
+@@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ if (decoded < 0) {
+ REDEBUG2("diameter2vp failed decoding attr: %s",
+ fr_strerror());
+- goto do_octets;
++ goto raw;
+ }
+
+ if ((size_t) decoded != size + 2) {
+ REDEBUG2("diameter2vp failed to entirely decode VSA");
+ fr_pair_list_free(&vp);
+- goto do_octets;
++ goto raw;
+ }
+
+ fr_cursor_merge(&out, vp);
+@@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ /*
+ * Create it. If this fails, it's because we're OOM.
+ */
+- do_octets:
+- vp = fr_pair_afrom_num(packet, attr, vendor);
++ da = dict_attrbyvalue(attr, vendor);
++ if (!da) goto raw;
++
++ vp = fr_pair_afrom_da(packet, da);
+ if (!vp) {
+ RDEBUG2("Failure in creating VP");
+ fr_pair_list_free(&first);
+@@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ case PW_TYPE_INTEGER:
+ case PW_TYPE_DATE:
+ if (size != vp->vp_length) {
+- DICT_ATTR const *da;
+-
+ /*
+ * Bad format. Create a "raw"
+ * attribute.
+@@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ */
+ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) ||
+ ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) {
+- uint8_t challenge[16];
++ uint8_t challenge[17];
+
+ if ((vp->vp_length < 8) ||
+ (vp->vp_length > 16)) {
+@@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
+ return NULL;
+ }
+
+- eapttls_gen_challenge(ssl, challenge,
+- sizeof(challenge));
++ /*
++ * TLSv1.3 exports a different key depending on the length
++ * requested so ask for *exactly* what the spec requires
++ */
++ eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1);
+
+ if (memcmp(challenge, vp->vp_octets,
+ vp->vp_length) != 0) {
+@@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se
+ */
+ switch (reply->code) {
+ case PW_CODE_ACCESS_ACCEPT:
++ tls_session->authentication_success = true;
+ RDEBUG("Got tunneled Access-Accept");
+
+ rcode = RLM_MODULE_OK;
+diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c
+index e7dbfa685e..f7e23628e6 100644
+--- a/src/modules/rlm_exec/rlm_exec.c
++++ b/src/modules/rlm_exec/rlm_exec.c
+@@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r
+ * If we're not waiting, then there are no output pairs.
+ */
+ if (inst->output) {
+- fr_pair_list_move(ctx, output_pairs, &answer);
++ fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD);
+ }
+ fr_pair_list_free(&answer);
+
+@@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ /*
+ * Always add the value-pairs to the reply.
+ */
+- fr_pair_list_move(request->reply, &request->reply->vps, &tmp);
++ fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD);
+ fr_pair_list_free(&tmp);
+
+ finish:
+diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c
+index c449d7776b..f835800376 100644
+--- a/src/modules/rlm_expr/rlm_expr.c
++++ b/src/modules/rlm_expr/rlm_expr.c
+@@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request,
+ return strlen(out);
+ }
+
++/** Calculate the MD4 hash of a string or attribute.
++ *
++ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2"
++ */
++static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request,
++ char const *fmt, char *out, size_t outlen)
++{
++ uint8_t digest[16];
++ ssize_t i, len, inlen;
++ uint8_t const *p;
++ FR_MD4_CTX ctx;
++
++ /*
++ * We need room for at least one octet of output.
++ */
++ if (outlen < 3) {
++ *out = '\0';
++ return 0;
++ }
++
++ inlen = xlat_fmt_to_ref(&p, request, fmt);
++ if (inlen < 0) {
++ return -1;
++ }
++
++ fr_md4_init(&ctx);
++ fr_md4_update(&ctx, p, inlen);
++ fr_md4_final(digest, &ctx);
++
++ /*
++ * Each digest octet takes two hex digits, plus one for
++ * the terminating NUL.
++ */
++ len = (outlen / 2) - 1;
++ if (len > 16) len = 16;
++
++ for (i = 0; i < len; i++) {
++ snprintf(out + i * 2, 3, "%02x", digest[i]);
++ }
++
++ return strlen(out);
++}
++
+ /** Calculate the SHA1 hash of a string or attribute.
+ *
+ * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+@@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request,
+ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now));
+ }
+
++/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s).
++ *
++ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520.
++ */
++static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request,
++ char const *fmt, char *out, size_t outlen)
++{
++ long num;
++
++ char const *p;
++ char *q;
++ time_t now;
++ struct tm *local, local_buff;
++
++ now = time(NULL);
++ local = localtime_r(&now, &local_buff);
++
++ p = fmt;
++
++ num = strtoul(p, &q, 10);
++ if (!q || *q == '\0') {
++ REDEBUG("nexttime: <int> must be followed by period specifier (h|d|w|m|y)");
++ return -1;
++ }
++
++ if (p == q) {
++ num = 1;
++ } else {
++ p += q - p;
++ }
++
++ local->tm_sec = 0;
++ local->tm_min = 0;
++
++ switch (*p) {
++ case 'h':
++ local->tm_hour -= num;
++ break;
++
++ case 'd':
++ local->tm_hour = 0;
++ local->tm_mday -= num;
++ break;
++
++ case 'w':
++ local->tm_hour = 0;
++ local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1));
++ break;
++
++ case 'm':
++ local->tm_hour = 0;
++ local->tm_mday = 1;
++ local->tm_mon -= num;
++ break;
++
++ case 'y':
++ local->tm_hour = 0;
++ local->tm_mday = 1;
++ local->tm_mon = 0;
++ local->tm_year -= num;
++ break;
++
++ default:
++ REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p);
++ return -1;
++ }
++
++ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local)));
++}
++
+
+ /*
+ * Parse the 3 arguments to lpad / rpad.
+@@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
+ xlat_register("unescape", unescape_xlat, NULL, inst);
+ xlat_register("tolower", tolower_xlat, NULL, inst);
+ xlat_register("toupper", toupper_xlat, NULL, inst);
++ xlat_register("md4", md4_xlat, NULL, inst);
+ xlat_register("md5", md5_xlat, NULL, inst);
+ xlat_register("sha1", sha1_xlat, NULL, inst);
+ #ifdef HAVE_OPENSSL_EVP_H
+@@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
+ xlat_register("explode", explode_xlat, NULL, inst);
+
+ xlat_register("nexttime", next_time_xlat, NULL, inst);
++ xlat_register("lasttime", last_time_xlat, NULL, inst);
+ xlat_register("lpad", lpad_xlat, NULL, inst);
+ xlat_register("rpad", rpad_xlat, NULL, inst);
+
+diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c
+index c825a9230b..9e77cd7ff1 100644
+--- a/src/modules/rlm_files/rlm_files.c
++++ b/src/modules/rlm_files/rlm_files.c
+@@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *
+ /* ctx may be reply or proxy */
+ reply_tmp = fr_pair_list_copy(reply_packet, pl->reply);
+ radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
+- fr_pair_list_move(request, &request->config, &check_tmp);
++ fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD);
+ fr_pair_list_free(&check_tmp);
+
+ /*
+diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h
+index 7dc874d35e..29e7cfd757 100644
+--- a/src/modules/rlm_ldap/ldap.h
++++ b/src/modules/rlm_ldap/ldap.h
+@@ -20,6 +20,7 @@
+ * always need to support that.
+ */
+ #define LDAP_DEPRECATED 1
++USES_APPLE_DEPRECATED_API /* Apple wants us to use OpenDirectory Framework, we don't want that */
+ #include <lber.h>
+ #include <ldap.h>
+ #include "config.h"
+@@ -265,6 +266,9 @@ typedef struct ldap_instance {
+
+ int tls_require_cert; //!< OpenLDAP constant representing the require cert string.
+
++ char const *tls_min_version_str; //!< Minimum TLS version
++ int tls_min_version;
++
+ /*
+ * Options
+ */
+diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c
+index 83c84a63e2..25bcd9c44e 100644
+--- a/src/modules/rlm_mschap/rlm_mschap.c
++++ b/src/modules/rlm_mschap/rlm_mschap.c
+@@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request,
+ data = response->vp_octets + 2;
+ data_len = 24;
+
++ /*
++ * Pull the domain name out of the User-Name, if it exists.
++ *
++ * This is the full domain name, not just the name after host/
++ */
++ } else if (strncasecmp(fmt, "Domain-Name", 11) == 0) {
++ char *p;
++
++ user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
++ if (!user_name) {
++ REDEBUG("No User-Name was found in the request");
++ return -1;
++ }
++
++ /*
++ * First check to see if this is a host/ style User-Name
++ * (a la Kerberos host principal)
++ */
++ if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
++ /*
++ * If we're getting a User-Name formatted in this way,
++ * it's likely due to PEAP. The Windows Domain will be
++ * the first domain component following the hostname,
++ * or the machine name itself if only a hostname is supplied
++ */
++ p = strchr(user_name->vp_strvalue, '.');
++ if (!p) {
++ RDEBUG2("setting NT-Domain to same as machine name");
++ strlcpy(out, user_name->vp_strvalue + 5, outlen);
++ } else {
++ p++; /* skip the period */
++ strlcpy(out, p, outlen);
++ }
++ } else {
++ p = strchr(user_name->vp_strvalue, '\\');
++ if (!p) {
++ REDEBUG("No NT-Domain was found in the User-Name");
++ return -1;
++ }
++
++ /*
++ * Hack. This is simpler than the alternatives.
++ */
++ *p = '\0';
++ strlcpy(out, user_name->vp_strvalue, outlen);
++ *p = '\\';
++ }
++
++ return strlen(out);
++
+ /*
+ * Pull the NT-Domain out of the User-Name, if it exists.
+ */
+@@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
+ return -1;
+ }
+ #else
+- cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time");
++ cf_log_err_cs(conf, "'winbind' is not enabled in this build.");
+ return -1;
+ #endif
+ }
+@@ -942,7 +992,6 @@ ntlm_auth_err:
+ ssize_t result_len;
+ char result[253];
+ uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
+- RC4_KEY key;
+
+ if (!nt_password) {
+ RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
+@@ -951,11 +1000,47 @@ ntlm_auth_err:
+ RDEBUG("Doing MS-CHAPv2 password change locally");
+ }
+
+- /*
+- * Decrypt the blob
+- */
+- RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
+- RC4(&key, 516, new_nt_password, nt_pass_decrypted);
++#if OPENSSL_VERSION_NUMBER >= 0x30000000L
++ {
++ EVP_CIPHER_CTX *ctx;
++ int ntlen = sizeof(nt_pass_decrypted);
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (!ctx) {
++ REDEBUG("Failed getting RC4 from OpenSSL");
++ error:
++ if (ctx) EVP_CIPHER_CTX_free(ctx);
++ return -1;
++ }
++
++ if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) {
++ REDEBUG("Failed setting key length");
++ goto error;
++ }
++
++ if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) {
++ REDEBUG("Failed setting key value");
++ goto error;;
++ }
++
++ if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) {
++ REDEBUG("Failed getting output");
++ goto error;
++ }
++
++ EVP_CIPHER_CTX_free(ctx);
++ }
++#else
++ {
++ RC4_KEY key;
++
++ /*
++ * Decrypt the blob
++ */
++ RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
++ RC4(&key, 516, new_nt_password, nt_pass_decrypted);
++ }
++#endif
+
+ /*
+ * pwblock is
+diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c
+index 399689abf3..932e44abe9 100644
+--- a/src/modules/rlm_otp/otp_mppe.c
++++ b/src/modules/rlm_otp/otp_mppe.c
+@@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+
+ #include <string.h>
+
++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L)
++#define UNUSED3
++#else
++#define UNUSED3 UNUSED
++#endif
++
+ /*
+ * Add MPPE attributes to a request, if required.
+ */
+-void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode)
++void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode)
+ {
+ VALUE_PAIR *cvp, *rvp;
+
+@@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const
+ case PWE_CHAP:
+ return;
+
++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L)
+ case PWE_MSCHAP:
+ /* First, set some related attributes. */
+ pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ);
+@@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const
+
+ break; /* PWE_MSCHAP2 */
+ } /* PWE_MSCHAP2 */
+-
++#else
++ case PWE_MSCHAP:
++ case PWE_MSCHAP2:
++ REDEBUG("MS-CHAP is unsupported for OpenSSL 3.");
++ break;
++#endif
+ } /* switch (pwe) */
+
+ return;
+diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c
+index 66fd8b4987..256437a552 100644
+--- a/src/modules/rlm_otp/otp_radstate.c
++++ b/src/modules/rlm_otp/otp_radstate.c
+@@ -113,6 +113,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN],
+ HMAC_CTX *hmac_ctx;
+ uint8_t hmac[MD5_DIGEST_LENGTH];
+ char *p;
++ unsigned int len = sizeof(hmac);
+
+ /*
+ * Generate the hmac. We already have a dependency on openssl for
+@@ -125,7 +126,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN],
+ HMAC_Update(hmac_ctx, (uint8_t const *) challenge, clen);
+ HMAC_Update(hmac_ctx, (uint8_t *) &flags, 4);
+ HMAC_Update(hmac_ctx, (uint8_t *) &when, 4);
+- HMAC_Final(hmac_ctx, hmac, NULL);
++ HMAC_Final(hmac_ctx, hmac, &len);
+ HMAC_CTX_free(hmac_ctx);
+
+ /*
+diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c
+index 8b85df0662..fcb3fd11fc 100644
+--- a/src/modules/rlm_rest/rest.c
++++ b/src/modules/rlm_rest/rest.c
+@@ -115,6 +115,15 @@ do {\
+ }\
+ } while (0)
+
++/*
++ * that macro is originally declared in include/curl/curlver.h
++ * We have to use this as curl uses lots of enums
++ */
++#ifndef CURL_AT_LEAST_VERSION
++# define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z))
++# define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
++#endif
++
+ const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
+ 0, // HTTP_AUTH_UNKNOWN
+ 0, // HTTP_AUTH_NONE
+@@ -221,6 +230,40 @@ const FR_NAME_NUMBER http_content_type_table[] = {
+ { NULL , -1 }
+ };
+
++/** Conversion table for "HTTP" protocol version to use.
++ *
++ * Used by rlm_rest_t for specify the http client version.
++ *
++ * Values we expect to use in curl_easy_setopt()
++ *
++ * @see fr_str2int
++ * @see fr_int2str
++ */
++const FR_NAME_NUMBER http_negotiation_table[] = {
++
++ { "1.0", CURL_HTTP_VERSION_1_0 }, //!< Enforce HTTP 1.0 requests.
++ { "1.1", CURL_HTTP_VERSION_1_1 }, //!< Enforce HTTP 1.1 requests.
++/*
++ * These are all enum values
++ */
++#if CURL_AT_LEAST_VERSION(7,49,0)
++ { "2.0", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, //!< Enforce HTTP 2.0 requests.
++#endif
++#if CURL_AT_LEAST_VERSION(7,33,0)
++ { "2.0+auto", CURL_HTTP_VERSION_2_0 }, //!< Attempt HTTP 2 requests. libcurl will fall back
++ ///< to HTTP 1.1 if HTTP 2 can't be negotiated with the
++ ///< server. (Added in 7.33.0)
++#endif
++#if CURL_AT_LEAST_VERSION(7,47,0)
++ { "2.0+tls", CURL_HTTP_VERSION_2TLS }, //!< Attempt HTTP 2 over TLS (HTTPS) only.
++ ///< libcurl will fall back to HTTP 1.1 if HTTP 2
++ ///< can't be negotiated with the HTTPS server.
++ ///< For clear text HTTP servers, libcurl will use 1.1.
++#endif
++ { "default", CURL_HTTP_VERSION_NONE } //!< We don't care about what version the library uses.
++ ///< libcurl will use whatever it thinks fit.
++};
++
+ /*
+ * Encoder specific structures.
+ * @todo split encoders/decoders into submodules.
+@@ -603,19 +646,30 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd
+ }
+
+ /*
+- * there are more attributes, insert a separator
++ * there are no more attributes, stop
+ */
+- if (fr_cursor_next(&ctx->cursor)) {
+- if (freespace < 1) goto no_space;
+- *p++ = '&';
+- freespace--;
++ if (!fr_cursor_next_peek(&ctx->cursor)) {
++ ctx->state = READ_STATE_END;
++ break;
+ }
+
++ if (freespace < 1) goto no_space;
++ *p++ = '&';
++ freespace--;
++ /*
++ * Only advance once we have a separator
++ * really we should have an additional
++ * state for encoding the separator,
++ * but, we don't, and v3.0.x is stable
++ * so let's do the easiest fix with the
++ * lowest risk.
++ */
++ fr_cursor_next(&ctx->cursor);
++
+ /*
+ * We wrote one full attribute value pair, record progress.
+ */
+ encoded = p;
+-
+ ctx->state = READ_STATE_ATTR_BEGIN;
+ }
+
+@@ -747,7 +801,13 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd
+
+ type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
+
+- len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
++ if (ctx->section->attr_num) {
++ len = snprintf(p, freespace + 1, "\"%s\":{\"attr_num\":%d,\"type\":\"%s\",\"value\":[",
++ vp->da->name, vp->da->attr, type);
++ } else {
++ len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
++ }
++
+ if (len >= freespace) goto no_space;
+ p += len;
+ freespace -= len;
+@@ -783,7 +843,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd
+ * write that out.
+ */
+ attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace;
+- len = vp_prints_value_json(p, attr_space + 1, vp);
++ len = vp_prints_value_json(p, attr_space + 1, vp, ctx->section->raw_value);
+ if (is_truncated(len, attr_space + 1)) goto no_space;
+
+ /*
+@@ -1575,8 +1635,9 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us
+ * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
+ *
+ * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
++ * "HTTP/2 " (8) + "100 " (4) + "\r\n" (2) = 12
+ */
+- if (s < 14) {
++ if (s < 12) {
+ REDEBUG("Malformed HTTP header: Status line too short");
+ goto malformed;
+ }
+@@ -1614,8 +1675,10 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us
+ p++;
+ s--;
+
+- /* Char after reason code must be a space, or \r */
+- if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
++ /*
++ * "xxx( |\r)" status code and terminator.
++ */
++ if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
+
+ ctx->code = atoi(p);
+
+@@ -1996,11 +2059,24 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
+ SET_OPTION(CURLOPT_NOSIGNAL, 1);
+ SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
+
++ /*
++ * As described in https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html,
++ * The libcurl decides which http version should be
++ * used by default accoring by library version.
++ */
++ if (instance->http_negotiation != CURL_HTTP_VERSION_NONE) {
++ RDEBUG3("Set HTTP negotiation for %s", instance->http_negotiation_str);
++ SET_OPTION(CURLOPT_HTTP_VERSION, instance->http_negotiation);
++ }
++
+ content_type = fr_int2str(http_content_type_table, type, section->body_str);
+ snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
+ ctx->headers = curl_slist_append(ctx->headers, buffer);
+ if (!ctx->headers) goto error_header;
+
++ // Pass configuration to the request
++ ctx->request.section = section;
++
+ SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout);
+ SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout);
+
+@@ -2322,6 +2398,7 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t
+ rlm_rest_handle_t *randle = handle;
+ CURL *candle = randle->handle;
+ CURLcode ret;
++ VALUE_PAIR *vp;
+
+ ret = curl_easy_perform(candle);
+ if (ret != CURLE_OK) {
+@@ -2330,6 +2407,14 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t
+ return -1;
+ }
+
++ /*
++ * Save the HTTP return status code.
++ */
++ vp = pair_make_reply("REST-HTTP-Status-Code", NULL, T_OP_SET);
++ vp->vp_integer = rest_get_handle_code(handle);
++
++ RDEBUG2("Adding reply:REST-HTTP-Status-Code = \"%d\"", vp->vp_integer);
++
+ return 0;
+ }
+
+diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h
+index 9e278d16c4..4c41cf46c2 100644
+--- a/src/modules/rlm_rest/rest.h
++++ b/src/modules/rlm_rest/rest.h
+@@ -31,6 +31,10 @@ RCSIDH(other_h, "$Id$")
+ #define CURL_NO_OLDIES 1
+ #include <curl/curl.h>
+
++#ifdef HAVE_WDOCUMENTATION
++DIAG_OFF(documentation)
++#endif
++
+ #ifdef HAVE_JSON
+ # if defined(HAVE_JSONMC_JSON_H)
+ # include <json-c/json.h>
+@@ -39,6 +43,10 @@ RCSIDH(other_h, "$Id$")
+ # endif
+ #endif
+
++#ifdef HAVE_WDOCUMENTATION
++DIAG_ON(documentation)
++#endif
++
+ #define REST_URI_MAX_LEN 2048
+ #define REST_BODY_MAX_LEN 8192
+ #define REST_BODY_INIT 1024
+@@ -102,6 +110,8 @@ extern const FR_NAME_NUMBER http_body_type_table[];
+
+ extern const FR_NAME_NUMBER http_content_type_table[];
+
++extern const FR_NAME_NUMBER http_negotiation_table[];
++
+ /*
+ * Structure for section configuration
+ */
+@@ -115,6 +125,9 @@ typedef struct rlm_rest_section_t {
+ char const *body_str; //!< The string version of the encoding/content type.
+ http_body_type_t body; //!< What encoding type should be used.
+
++ bool attr_num; //!< If true, the the attribute number is supplied for each attribute.
++ bool raw_value; //!< If true, enumerated attributes are provided as a numeric value
++
+ char const *force_to_str; //!< Force decoding with this decoder.
+ http_body_type_t force_to; //!< Override the Content-Type header in the response
+ //!< to force decoding as a particular type.
+@@ -154,6 +167,9 @@ typedef struct rlm_rest_t {
+ struct timeval connect_timeout_tv; //!< Connection timeout timeval.
+ long connect_timeout; //!< Connection timeout ms.
+
++ char const *http_negotiation_str; //!< The string version of the http_negotiation
++ long http_negotiation; //!< The HTTP protocol version to use
++
+ fr_connection_pool_t *pool; //!< Pointer to the connection pool.
+
+ rlm_rest_section_t authorize; //!< Configuration specific to authorisation.
+@@ -205,6 +221,8 @@ typedef struct rlm_rest_request_t {
+
+ size_t chunk; //!< Chunk size
+
++ rlm_rest_section_t *section; //!< Configuration data
++
+ void *encoder; //!< Encoder specific data.
+ } rlm_rest_request_t;
+
+diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c
+index 6fa1345614..1337ae5425 100644
+--- a/src/modules/rlm_rest/rlm_rest.c
++++ b/src/modules/rlm_rest/rlm_rest.c
+@@ -60,6 +60,8 @@ static const CONF_PARSER section_config[] = {
+ { "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), "" },
+ { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" },
+ { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" },
++ { "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" },
++ { "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" },
+ { "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL },
+ { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL },
+
+@@ -81,6 +83,8 @@ static const CONF_PARSER section_config[] = {
+ static const CONF_PARSER module_config[] = {
+ { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL },
+ { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" },
++ { "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" },
++
+ CONF_PARSER_TERMINATOR
+ };
+
+@@ -223,6 +227,8 @@ static ssize_t rest_xlat(void *instance, REQUEST *request,
+ .name = "xlat",
+ .method = HTTP_METHOD_GET,
+ .body = HTTP_BODY_NONE,
++ .attr_num = false,
++ .raw_value = false,
+ .body_str = "application/x-www-form-urlencoded",
+ .require_auth = false,
+ .force_to = HTTP_BODY_PLAIN
+@@ -926,6 +932,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
+ return -1;
+ }
+
++ inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1);
++ if (inst->http_negotiation == -1) {
++ cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str);
++ return -1;
++ }
++
+ /*
+ * Initialise REST libraries.
+ */
+diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c
+new file mode 100644
+index 0000000000..e14086e78c
+--- /dev/null
++++ b/src/modules/rlm_wimax/milenage.c
+@@ -0,0 +1,642 @@
++/**
++ * @file src/modules/rlm_wimax/milenage.c
++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
++ *
++ * This file implements an example authentication algorithm defined for 3GPP
++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
++ * EAP-AKA to be tested properly with real USIM cards.
++ *
++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in
++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
++ * be AES (Rijndael).
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ *
++ * @copyright 2017 The FreeRADIUS server project
++ * @copyright 2006-2007 (j@w1.fi)
++ */
++#include <stddef.h>
++#include <string.h>
++
++#include <freeradius-devel/radiusd.h>
++#include <freeradius-devel/modules.h>
++#include <openssl/evp.h>
++#include "milenage.h"
++
++#define MILENAGE_MAC_A_SIZE 8
++#define MILENAGE_MAC_S_SIZE 8
++
++static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx,
++ uint8_t const key[16], uint8_t const in[16], uint8_t out[16])
++{
++ size_t len;
++
++ if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) {
++ fr_strerror_printf("Failed initialising AES-128-ECB context");
++ return -1;
++ }
++
++ /*
++ * By default OpenSSL will try and pad out a 16 byte
++ * plaintext to 32 bytes so that it's detectable that
++ * there was padding.
++ *
++ * In this case we know the length of the plaintext
++ * we're trying to recover, so we explicitly tell
++ * OpenSSL not to pad here, and not to expected padding
++ * when decrypting.
++ */
++ EVP_CIPHER_CTX_set_padding(evp_ctx, 0);
++ if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) ||
++ unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) {
++ fr_strerror_printf("Failed encrypting data");
++ return -1;
++ }
++
++ return 0;
++}
++
++/** milenage_f1 - Milenage f1 and f1* algorithms
++ *
++ * @param[in] opc 128-bit value derived from OP and K.
++ * @param[in] k 128-bit subscriber key.
++ * @param[in] rand 128-bit random challenge.
++ * @param[in] sqn 48-bit sequence number.
++ * @param[in] amf 16-bit authentication management field.
++ * @param[out] mac_a Buffer for MAC-A = 64-bit network authentication code, or NULL
++ * @param[out] mac_s Buffer for MAC-S = 64-bit resync authentication code, or NULL
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ */
++static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE],
++ uint8_t mac_s[MILENAGE_MAC_S_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const k[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE],
++ uint8_t const sqn[MILENAGE_SQN_SIZE],
++ uint8_t const amf[MILENAGE_AMF_SIZE])
++{
++ uint8_t tmp1[16], tmp2[16], tmp3[16];
++ int i;
++ EVP_CIPHER_CTX *evp_ctx;
++
++ /* tmp1 = TEMP = E_K(RAND XOR OP_C) */
++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i];
++
++ evp_ctx = EVP_CIPHER_CTX_new();
++ if (!evp_ctx) {
++ //tls_strerror_printf("Failed allocating EVP context");
++ return -1;
++ }
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) {
++ error:
++ EVP_CIPHER_CTX_free(evp_ctx);
++ return -1;
++ }
++
++ /* tmp2 = IN1 = SQN || AMF || SQN || AMF */
++ memcpy(tmp2, sqn, 6);
++ memcpy(tmp2 + 6, amf, 2);
++ memcpy(tmp2 + 8, tmp2, 8);
++
++ /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */
++
++ /*
++ * rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes)
++ */
++ for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i];
++
++ /*
++ * XOR with TEMP = E_K(RAND XOR OP_C)
++ */
++ for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i];
++ /* XOR with c1 (= ..00, i.e., NOP) */
++
++ /*
++ * f1 || f1* = E_K(tmp3) XOR OP_c
++ */
++ if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */
++
++ for (i = 0; i < 16; i++) tmp1[i] ^= opc[i];
++
++ if (mac_a) memcpy(mac_a, tmp1, 8); /* f1 */
++ if (mac_s) memcpy(mac_s, tmp1 + 8, 8); /* f1* */
++
++ EVP_CIPHER_CTX_free(evp_ctx);
++
++ return 0;
++}
++
++/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms
++ *
++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL
++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL
++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL
++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL
++ * @param[out] ak_resync Buffer for AK = 48-bit anonymity key (f5*), or NULL
++ * @param[in] opc 128-bit value derived from OP and K.
++ * @param[in] k 128-bit subscriber key
++ * @param[in] rand 128-bit random challenge
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ */
++static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE],
++ uint8_t ik[MILENAGE_IK_SIZE],
++ uint8_t ck[MILENAGE_CK_SIZE],
++ uint8_t ak[MILENAGE_AK_SIZE],
++ uint8_t ak_resync[MILENAGE_AK_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const k[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE])
++{
++ uint8_t tmp1[16], tmp2[16], tmp3[16];
++ int i;
++ EVP_CIPHER_CTX *evp_ctx;
++
++ /* tmp2 = TEMP = E_K(RAND XOR OP_C) */
++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i];
++
++ evp_ctx = EVP_CIPHER_CTX_new();
++ if (!evp_ctx) {
++ fr_strerror_printf("Failed allocating EVP context");
++ return -1;
++ }
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) {
++ error:
++ EVP_CIPHER_CTX_free(evp_ctx);
++ return -1;
++ }
++
++ /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */
++ /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */
++ /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */
++ /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */
++
++ /* f2 and f5 */
++ /* rotate by r2 (= 0, i.e., NOP) */
++ for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i];
++ tmp1[15] ^= 1; /* XOR c2 (= ..01) */
++ /* f5 || f2 = E_K(tmp1) XOR OP_c */
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error;
++
++ for (i = 0; i < 16; i++) tmp3[i] ^= opc[i];
++ if (res) memcpy(res, tmp3 + 8, 8); /* f2 */
++ if (ak) memcpy(ak, tmp3, 6); /* f5 */
++
++ /* f3 */
++ if (ck) {
++ /* rotate by r3 = 0x20 = 4 bytes */
++ for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i];
++ tmp1[15] ^= 2; /* XOR c3 (= ..02) */
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error;
++
++ for (i = 0; i < 16; i++) ck[i] ^= opc[i];
++ }
++
++ /* f4 */
++ if (ik) {
++ /* rotate by r4 = 0x40 = 8 bytes */
++ for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i];
++ tmp1[15] ^= 4; /* XOR c4 (= ..04) */
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error;
++
++ for (i = 0; i < 16; i++) ik[i] ^= opc[i];
++ }
++
++ /* f5* */
++ if (ak_resync) {
++ /* rotate by r5 = 0x60 = 12 bytes */
++ for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i];
++ tmp1[15] ^= 8; /* XOR c5 (= ..08) */
++
++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error;
++
++ for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i];
++ }
++ EVP_CIPHER_CTX_free(evp_ctx);
++
++ return 0;
++}
++
++/** Derive OPc from OP and Ki
++ *
++ * @param[out] opc The derived Operator Code used as an input to other Milenage
++ * functions.
++ * @param[in] op Operator Code.
++ * @param[in] ki Subscriber key.
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ */
++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE],
++ uint8_t const op[MILENAGE_OP_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE])
++{
++ int ret;
++ uint8_t tmp[MILENAGE_OPC_SIZE];
++ EVP_CIPHER_CTX *evp_ctx;
++ size_t i;
++
++ evp_ctx = EVP_CIPHER_CTX_new();
++ if (!evp_ctx) {
++ fr_strerror_printf("Failed allocating EVP context");
++ return -1;
++ }
++ ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp);
++ EVP_CIPHER_CTX_free(evp_ctx);
++ if (ret < 0) return ret;
++
++ for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i];
++
++ return 0;
++}
++
++/** Generate AKA AUTN, IK, CK, RES
++ *
++ * @param[out] autn Buffer for AUTN = 128-bit authentication token.
++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL.
++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL.
++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL
++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL.
++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.).
++ * @param[in] amf 16-bit authentication management field.
++ * @param[in] ki 128-bit subscriber key.
++ * @param[in] sqn 48-bit sequence number (host byte order).
++ * @param[in] rand 128-bit random challenge.
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ */
++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE],
++ uint8_t ik[MILENAGE_IK_SIZE],
++ uint8_t ck[MILENAGE_CK_SIZE],
++ uint8_t ak[MILENAGE_AK_SIZE],
++ uint8_t res[MILENAGE_RES_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const amf[MILENAGE_AMF_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint64_t sqn,
++ uint8_t const rand[MILENAGE_RAND_SIZE])
++{
++ uint8_t mac_a[8], ak_buff[MILENAGE_AK_SIZE];
++ uint8_t sqn_buff[MILENAGE_SQN_SIZE];
++ uint8_t *p = autn;
++ size_t i;
++
++ if ((milenage_f1(mac_a, NULL, opc, ki, rand,
++ uint48_to_buff(sqn_buff, sqn), amf) < 0) ||
++ (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1;
++
++ /*
++ * AUTN = (SQN ^ AK) || AMF || MAC_A
++ */
++ for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i];
++ memcpy(p, amf, MILENAGE_AMF_SIZE);
++ p += MILENAGE_AMF_SIZE;
++ memcpy(p, mac_a, sizeof(mac_a));
++
++ /*
++ * Output the anonymity key if required
++ */
++ if (ak) memcpy(ak, ak_buff, sizeof(ak_buff));
++
++ return 0;
++}
++
++/** Milenage AUTS validation
++ *
++ * @param[out] sqn SQN = 48-bit sequence number (host byte order).
++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.).
++ * @param[in] ki 128-bit subscriber key.
++ * @param[in] rand 128-bit random challenge.
++ * @param[in] auts 112-bit authentication token from client.
++ * @return
++ * - 0 on success with sqn filled.
++ * - -1 on failure.
++ */
++int milenage_auts(uint64_t *sqn,
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE],
++ uint8_t const auts[MILENAGE_AUTS_SIZE])
++{
++ uint8_t amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
++ uint8_t ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE];
++ uint8_t sqn_buff[MILENAGE_SQN_SIZE];
++ size_t i;
++
++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1;
++ for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i];
++
++ if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1;
++
++ *sqn = uint48_from_buff(sqn_buff);
++
++ return 0;
++}
++
++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet
++ *
++ * @param[out] sres Buffer for SRES = 32-bit SRES.
++ * @param[out] kc 64-bit Kc.
++ * @param[in] ik 128-bit integrity.
++ * @param[in] ck Confidentiality key.
++ * @param[in] res 64-bit signed response.
++ */
++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE],
++ uint8_t kc[MILENAGE_KC_SIZE],
++ uint8_t const ik[MILENAGE_IK_SIZE],
++ uint8_t const ck[MILENAGE_CK_SIZE],
++ uint8_t const res[MILENAGE_RES_SIZE])
++{
++ int i;
++
++ for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8];
++
++#ifdef GSM_MILENAGE_ALT_SRES
++ memcpy(sres, res, 4);
++#else /* GSM_MILENAGE_ALT_SRES */
++ for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4];
++#endif /* GSM_MILENAGE_ALT_SRES */
++}
++
++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet
++ *
++ * @param[out] sres Buffer for SRES = 32-bit SRES.
++ * @param[out] kc 64-bit Kc.
++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.).
++ * @param[in] ki 128-bit subscriber key.
++ * @param[in] rand 128-bit random challenge.
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ */
++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE],
++ uint8_t kc[MILENAGE_KC_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE])
++{
++ uint8_t res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE];
++
++ if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1;
++
++ milenage_gsm_from_umts(sres, kc, ik, ck, res);
++
++ return 0;
++}
++
++/** Milenage check
++ *
++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL.
++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL.
++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL.
++ * @param[in] auts 112-bit buffer for AUTS.
++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.).
++ * @param[in] ki 128-bit subscriber key.
++ * @param[in] sqn 48-bit sequence number.
++ * @param[in] rand 128-bit random challenge.
++ * @param[in] autn 128-bit authentication token.
++ * @return
++ * - 0 on success.
++ * - -1 on failure.
++ * - -2 on synchronization failure
++ */
++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE],
++ uint8_t ck[MILENAGE_CK_SIZE],
++ uint8_t res[MILENAGE_RES_SIZE],
++ uint8_t auts[MILENAGE_AUTS_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint64_t sqn,
++ uint8_t const rand[MILENAGE_RAND_SIZE],
++ uint8_t const autn[MILENAGE_AUTN_SIZE])
++{
++
++ uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE];
++ uint8_t sqn_buff[MILENAGE_SQN_SIZE];
++ const uint8_t *amf;
++ size_t i;
++
++ uint48_to_buff(sqn_buff, sqn);
++
++ //FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN");
++ //FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND");
++
++ if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1;
++
++ //FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES");
++ //FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK");
++ //FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK");
++ //FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK");
++
++ /* AUTN = (SQN ^ AK) || AMF || MAC */
++ for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i];
++ //FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN");
++
++ if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) {
++ uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
++
++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1;
++
++ //FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*");
++ for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i];
++
++ if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1;
++ //FR_PROTO_HEX_DUMP(auts, 14, "AUTS");
++ return -2;
++ }
++
++ amf = autn + 6;
++ //FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF");
++ if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1;
++
++ //FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A");
++
++ if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) {
++ //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A");
++ fr_strerror_printf("MAC mismatch");
++ return -1;
++ }
++
++ return 0;
++}
++
++#ifdef TESTING_MILENAGE
++/*
++ * cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage
++ */
++#include <freeradius-devel/util/acutest.h>
++
++void test_set_1(void)
++{
++ /*
++ * Inputs
++ */
++ uint8_t ki[] = { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f,
++ 0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc };
++ uint8_t rand[] = { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d,
++ 0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35 };
++ uint8_t sqn[] = { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 };
++ uint8_t amf[] = { 0xb9, 0xb9 };
++ uint8_t op[] = { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6,
++ 0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 };
++ uint8_t opc[] = { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e,
++ 0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf };
++
++ /*
++ * Outputs
++ */
++ uint8_t opc_out[MILENAGE_OPC_SIZE];
++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE];
++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE];
++ uint8_t res_out[MILENAGE_RES_SIZE];
++ uint8_t ck_out[MILENAGE_CK_SIZE];
++ uint8_t ik_out[MILENAGE_IK_SIZE];
++ uint8_t ak_out[MILENAGE_AK_SIZE];
++ uint8_t ak_resync_out[MILENAGE_AK_SIZE];
++
++ /* function 1 */
++ uint8_t mac_a[] = { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 };
++ /* function 1* */
++ uint8_t mac_s[] = { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 };
++ /* function 2 */
++ uint8_t res[] = { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf };
++ /* function 3 */
++ uint8_t ck[] = { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05,
++ 0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb };
++ /* function 4 */
++ uint8_t ik[] = { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04,
++ 0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 };
++ /* function 5 */
++ uint8_t ak[] = { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 };
++ /* function 5* */
++ uint8_t ak_resync[] = { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b };
++
++ int ret = 0;
++
++/*
++ fr_debug_lvl = 4;
++*/
++ ret = milenage_opc_generate(opc_out, op, ki);
++ TEST_CHECK(ret == 0);
++
++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc");
++
++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0);
++
++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) ||
++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1;
++
++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a");
++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s");
++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik");
++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck");
++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res");
++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak");
++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync");
++
++ TEST_CHECK(ret == 0);
++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0);
++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0);
++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0);
++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0);
++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0);
++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0);
++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0);
++}
++
++void test_set_19(void)
++{
++ /*
++ * Inputs
++ */
++ uint8_t ki[] = { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72,
++ 0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 };
++ uint8_t rand[] = { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e,
++ 0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 };
++ uint8_t sqn[] = { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 };
++ uint8_t amf[] = { 0xc3, 0xab };
++ uint8_t op[] = { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff,
++ 0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b };
++ uint8_t opc[] = { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e,
++ 0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf };
++
++ /*
++ * Outputs
++ */
++ uint8_t opc_out[MILENAGE_OPC_SIZE];
++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE];
++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE];
++ uint8_t res_out[MILENAGE_RES_SIZE];
++ uint8_t ck_out[MILENAGE_CK_SIZE];
++ uint8_t ik_out[MILENAGE_IK_SIZE];
++ uint8_t ak_out[MILENAGE_AK_SIZE];
++ uint8_t ak_resync_out[MILENAGE_AK_SIZE];
++
++ /* function 1 */
++ uint8_t mac_a[] = { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 };
++ /* function 1* */
++ uint8_t mac_s[] = { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 };
++ /* function 2 */
++ uint8_t res[] = { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 };
++ /* function 3 */
++ uint8_t ck[] = { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94,
++ 0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f };
++ /* function 4 */
++ uint8_t ik[] = { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb,
++ 0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a };
++ /* function 5 */
++ uint8_t ak[] = { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 };
++ /* function 5* */
++ uint8_t ak_resync[] = { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d };
++
++ int ret = 0;
++
++/*
++ fr_debug_lvl = 4;
++*/
++
++ ret = milenage_opc_generate(opc_out, op, ki);
++ TEST_CHECK(ret == 0);
++
++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc");
++
++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0);
++
++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) ||
++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1;
++
++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a");
++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s");
++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik");
++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck");
++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res");
++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak");
++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync");
++
++ TEST_CHECK(ret == 0);
++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0);
++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0);
++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0);
++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0);
++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0);
++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0);
++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0);
++}
++
++TEST_LIST = {
++ { "test_set_1", test_set_1 },
++ { "test_set_19", test_set_19 },
++ { NULL }
++};
++#endif
+diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h
+new file mode 100644
+index 0000000000..758383aba2
+--- /dev/null
++++ b/src/modules/rlm_wimax/milenage.h
+@@ -0,0 +1,128 @@
++#pragma once
++/**
++ * @file src/modules/rlm_wimax/milenage.h
++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
++ *
++ * This file implements an example authentication algorithm defined for 3GPP
++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
++ * EAP-AKA to be tested properly with real USIM cards.
++ *
++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in
++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
++ * be AES (Rijndael).
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ *
++ * @copyright 2017 The FreeRADIUS server project
++ * @copyright 2006-2007 (j@w1.fi)
++ */
++#include <stddef.h>
++
++/*
++ * Inputs
++ */
++#define MILENAGE_KI_SIZE 16 //!< Subscriber key.
++#define MILENAGE_OP_SIZE 16 //!< Operator code (unique to the operator)
++#define MILENAGE_OPC_SIZE 16 //!< Derived operator code (unique to the operator and subscriber).
++#define MILENAGE_AMF_SIZE 2 //!< Authentication management field.
++#define MILENAGE_SQN_SIZE 6 //!< Sequence number.
++#define MILENAGE_RAND_SIZE 16 //!< Random challenge.
++
++/*
++ * UMTS Outputs
++ */
++#define MILENAGE_AK_SIZE 6 //!< Anonymisation key.
++#define MILENAGE_AUTN_SIZE 16 //!< Network authentication key.
++#define MILENAGE_IK_SIZE 16 //!< Integrity key.
++#define MILENAGE_CK_SIZE 16 //!< Ciphering key.
++#define MILENAGE_RES_SIZE 8
++#define MILENAGE_AUTS_SIZE 14
++
++/*
++ * GSM (COMP128-4) outputs
++ */
++#define MILENAGE_SRES_SIZE 4
++#define MILENAGE_KC_SIZE 8
++
++/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order
++ *
++ * There may be fast ways of doing this, but this is the *correct*
++ * way, and does not make assumptions about how integers are laid
++ * out in memory.
++ *
++ * @param[out] out 6 byte butter to store value.
++ * @param[in] i integer value.
++ * @return pointer to out.
++ */
++static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i)
++{
++ out[0] = (i & 0xff0000000000) >> 40;
++ out[1] = (i & 0x00ff00000000) >> 32;
++ out[2] = (i & 0x0000ff000000) >> 24;
++ out[3] = (i & 0x000000ff0000) >> 16;
++ out[4] = (i & 0x00000000ff00) >> 8;
++ out[5] = (i & 0x0000000000ff);
++
++ return out;
++}
++
++/** Convert a 48bit big endian value into a unsigned 64bit integer
++ *
++ */
++static inline uint64_t uint48_from_buff(uint8_t const in[6])
++{
++ uint64_t i = 0;
++
++ i |= ((uint64_t)in[0]) << 40;
++ i |= ((uint64_t)in[1]) << 32;
++ i |= ((uint32_t)in[2]) << 24;
++ i |= ((uint32_t)in[3]) << 16;
++ i |= ((uint16_t)in[4]) << 8;
++ i |= in[5];
++
++ return i;
++}
++
++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE],
++ uint8_t const op[MILENAGE_OP_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE]);
++
++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE],
++ uint8_t ik[MILENAGE_IK_SIZE],
++ uint8_t ck[MILENAGE_CK_SIZE],
++ uint8_t ak[MILENAGE_AK_SIZE],
++ uint8_t res[MILENAGE_RES_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const amf[MILENAGE_AMF_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint64_t sqn,
++ uint8_t const rand[MILENAGE_RAND_SIZE]);
++
++int milenage_auts(uint64_t *sqn,
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE],
++ uint8_t const auts[MILENAGE_AUTS_SIZE]);
++
++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE],
++ uint8_t kc[MILENAGE_KC_SIZE],
++ uint8_t const ik[MILENAGE_IK_SIZE],
++ uint8_t const ck[MILENAGE_CK_SIZE],
++ uint8_t const res[MILENAGE_RES_SIZE]);
++
++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint8_t const rand[MILENAGE_RAND_SIZE]);
++
++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE],
++ uint8_t ck[MILENAGE_CK_SIZE],
++ uint8_t res[MILENAGE_RES_SIZE],
++ uint8_t auts[MILENAGE_AUTS_SIZE],
++ uint8_t const opc[MILENAGE_OPC_SIZE],
++ uint8_t const ki[MILENAGE_KI_SIZE],
++ uint64_t sqn,
++ uint8_t const rand[MILENAGE_RAND_SIZE],
++ uint8_t const autn[MILENAGE_AUTN_SIZE]);
+diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c
+index f0fb394fcd..d2125eb3a5 100644
+--- a/src/modules/rlm_wimax/rlm_wimax.c
++++ b/src/modules/rlm_wimax/rlm_wimax.c
+@@ -26,16 +26,43 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
+
+ #include <freeradius-devel/radiusd.h>
+ #include <freeradius-devel/modules.h>
++#include "milenage.h"
+
+ #ifdef HAVE_OPENSSL_HMAC_H
+ #include <openssl/hmac.h>
+ #endif
+
++#include <freeradius-devel/openssl3.h>
++
++#define WIMAX_EPSAKA_RAND_SIZE 16
++#define WIMAX_EPSAKA_KI_SIZE 16
++#define WIMAX_EPSAKA_OPC_SIZE 16
++#define WIMAX_EPSAKA_AMF_SIZE 2
++#define WIMAX_EPSAKA_SQN_SIZE 6
++#define WIMAX_EPSAKA_MAC_A_SIZE 8
++#define WIMAX_EPSAKA_MAC_S_SIZE 8
++#define WIMAX_EPSAKA_XRES_SIZE 8
++#define WIMAX_EPSAKA_CK_SIZE 16
++#define WIMAX_EPSAKA_IK_SIZE 16
++#define WIMAX_EPSAKA_AK_SIZE 6
++#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6
++#define WIMAX_EPSAKA_KK_SIZE 32
++#define WIMAX_EPSAKA_KS_SIZE 14
++#define WIMAX_EPSAKA_PLMN_SIZE 3
++#define WIMAX_EPSAKA_KASME_SIZE 32
++#define WIMAX_EPSAKA_AUTN_SIZE 16
++#define WIMAX_EPSAKA_AUTS_SIZE 14
++
+ /*
+ * FIXME: Fix the build system to create definitions from names.
+ */
+ typedef struct rlm_wimax_t {
+ bool delete_mppe_keys;
++
++ DICT_ATTR const *resync_info;
++ DICT_ATTR const *xres;
++ DICT_ATTR const *autn;
++ DICT_ATTR const *kasme;
+ } rlm_wimax_t;
+
+ /*
+@@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = {
+ CONF_PARSER_TERMINATOR
+ };
+
++/*
++ * Print hex values in a readable format for debugging
++ * Example:
++ * FOO: 00 11 AA 22 00 FF
++ */
++static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len)
++{
++ int i;
++ char buffer[256]; /* large enough for largest len */
++
++ /*
++ * Leave a trailing space, we don't really care about that.
++ */
++ for (i = 0; i < len; i++) {
++ snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]);
++ }
++
++ RDEBUG("%s %s", prefix, buffer);
++}
++#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex
++
+ /*
+ * Find the named user in this modules database. Create the set
+ * of attribute-value pairs to check and reply with for this user
+ * from the database. The authentication code only needs to check
+ * the password, the rest is done here.
+ */
+-static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request)
++static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
+ {
+ VALUE_PAIR *vp;
++ rlm_wimax_t *inst = instance;
+
+ /*
+ * Fix Calling-Station-Id. Damn you, WiMAX!
+@@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST
+ return RLM_MODULE_OK;
+ }
+
++ /*
++ * Check for attr WiMAX-Re-synchronization-Info
++ * which contains the concatenation of RAND and AUTS
++ *
++ * If it is present then we proceed to verify the SIM and
++ * extract the new value of SQN
++ */
++ VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand;
++ int m_ret;
++
++ /* Look for the Re-synchronization-Info attribute in the request */
++ resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY);
++ if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) {
++ RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it");
++ resync_info = NULL;
++ }
++
++ /*
++ * These are the private keys which should be added to the control
++ * list after looking them up in a database by IMSI
++ *
++ * We grab them from the control list here
++ */
++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY);
++ if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it");
++ ki = NULL;
++ }
++
++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY);
++ if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it");
++ opc = NULL;
++ }
++
++ /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */
++ if (resync_info && ki && opc) {
++ uint64_t sqn_bin;
++ uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE];
++ uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE];
++
++ RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync");
++
++ /* Split Re-synchronization-Info into seperate RAND and AUTS */
++
++ memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE);
++ memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE);
++
++ RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE);
++ RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE);
++
++ /*
++ * This procedure uses the secret keys Ki and OPc to authenticate
++ * the SIM and extract the SQN
++ */
++ m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin);
++
++ /*
++ * If the SIM verification fails then we can't go any further as
++ * we don't have the keys. And that probably means something bad
++ * is happening so we bail out now
++ */
++ if (m_ret < 0) {
++ RDEBUG("SIM verification failed");
++ return RLM_MODULE_REJECT;
++ }
++
++ /*
++ * If we got this far it means have got a new SQN and RAND
++ * so we store them in:
++ * control:WiMAX-SIM-SQN
++ * control:WiMAX-SIM-RAND
++ *
++ * From there they can be grabbed by unlang and used later
++ */
++
++ /* SQN is six bytes so we extract what we need from the 64 bit variable */
++ uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = {
++ (sqn_bin & 0x0000FF0000000000ull) >> 40,
++ (sqn_bin & 0x000000FF00000000ull) >> 32,
++ (sqn_bin & 0x00000000FF000000ull) >> 24,
++ (sqn_bin & 0x0000000000FF0000ull) >> 16,
++ (sqn_bin & 0x000000000000FF00ull) >> 8,
++ (sqn_bin & 0x00000000000000FFull) >> 0
++ };
++
++ /* Add SQN to control:WiMAX-SIM-SQN */
++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY);
++ if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it");
++ sqn = NULL;
++ }
++
++ if (!sqn) {
++ MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET));
++ fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE);
++ }
++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE);
++
++ /* Add RAND to control:WiMAX-SIM-RAND */
++ rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY);
++ if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it");
++ rand = NULL;
++ }
++
++ if (!rand) {
++ MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET));
++ fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE);
++ }
++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE);
++
++ return RLM_MODULE_UPDATED;
++ }
++
+ return RLM_MODULE_NOOP;
+ }
+
+-
+ /*
+ * Massage the request before recording it or proxying it
+ */
+@@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request
+ return mod_authorize(instance, request);
+ }
+
+-/*
+- * Write accounting information to this modules database.
+- */
+-static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request)
+-{
+- return RLM_MODULE_OK;
+-}
+
+ /*
+- * Generate the keys after the user has been authenticated.
++ * This function generates the keys for old style WiMAX (v1 to v2.0)
+ */
+-static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
++static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk)
+ {
+ rlm_wimax_t *inst = instance;
+- VALUE_PAIR *msk, *emsk, *vp;
++ VALUE_PAIR *vp;
+ VALUE_PAIR *mn_nai, *ip, *fa_rk;
+ HMAC_CTX *hmac;
+ unsigned int rk1_len, rk2_len, rk_len;
+@@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE];
+ uint8_t mip_rk[2 * EVP_MAX_MD_SIZE];
+
+- msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY);
+- emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY);
+- if (!msk || !emsk) {
+- RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys");
+- return RLM_MODULE_NOOP;
+- }
+-
+ /*
+ * If we delete the MS-MPPE-*-Key attributes, then add in
+ * the WiMAX-MSK so that the client has a key available.
+@@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
+ fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
+
+- vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ);
+- if (vp) {
+- fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length);
+- }
++ MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ));
++ fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length);
+ }
+
+ /*
+@@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ */
+ hmac = HMAC_CTX_new();
+ HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL);
++ rk1_len = SHA256_DIGEST_LENGTH;
+
+ HMAC_Update(hmac, &usage_data[0], sizeof(usage_data));
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+@@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+
+ HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len);
+ HMAC_Update(hmac, &usage_data[0], sizeof(usage_data));
++ rk2_len = SHA256_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_2[0], &rk2_len);
+
+ memcpy(mip_rk, mip_rk_1, rk1_len);
+@@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL);
+
+ HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12);
++ rk1_len = SHA256_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+
+ /*
+@@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ (mip_rk_1[2] << 8) | mip_rk_1[3]);
+ if (mip_spi < 256) mip_spi += 256;
+
+- if (rad_debug_lvl) {
+- int len = rk_len;
+- char buffer[512];
+-
+- if (len > 128) len = 128; /* buffer size */
+-
+- fr_bin2hex(buffer, mip_rk, len);
+- RDEBUG("MIP-RK = 0x%s", buffer);
+- RDEBUG("MIP-SPI = %08x", ntohl(mip_spi));
+- }
++ RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len);
++ RDEBUG("MIP-SPI = %08x", ntohl(mip_spi));
+
+ /*
+ * FIXME: Perform SPI collision prevention
+@@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11);
+ HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4);
+ HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
++ rk1_len = SHA1_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+
+ /*
+@@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11);
+ HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4);
+ HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
++ rk1_len = SHA1_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+
+ /*
+@@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11);
+ HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16);
+ HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
++ rk1_len = SHA1_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+
+ /*
+@@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+
+ HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5);
+
++ rk1_len = SHA1_DIGEST_LENGTH;
+ HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
+
+ fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len);
+@@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
+ return RLM_MODULE_UPDATED;
+ }
+
++/*
++ * Generate the EPS-AKA authentication vector
++ *
++ * These are the keys needed for new style WiMAX (LTE / 3gpp authentication),
++ for WiMAX v2.1
++ */
++static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc,
++ VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn)
++{
++ size_t i;
++ VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme;
++
++ /*
++ * For most authentication requests we need to generate a fresh RAND
++ *
++ * The exception is after SQN re-syncronisation - in this case we
++ * get RAND in the request, and this module if called in authorize should
++ * have put it in control:WiMAX-SIM-RAND so we can grab it from there)
++ */
++ rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY);
++ if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it.");
++ rand_previous = NULL;
++ }
++
++ MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET));
++ if (!rand_previous) {
++ uint32_t lvalue;
++ uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE];
++
++ for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) {
++ lvalue = fr_rand();
++ memcpy(buffer + i * 4, &lvalue, sizeof(lvalue));
++ }
++
++ fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE);
++
++ } else {
++ fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE);
++ }
++
++ /*
++ * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5)
++ * which returns AUTN, AK, CK, IK, XRES.
++ */
++ uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE];
++ uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE];
++ uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE];
++ uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE];
++ uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE];
++
++ /* But first convert uint8 SQN to uint64 */
++ uint64_t sqn_bin = 0x0000000000000000;
++ for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i];
++
++ if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it");
++ return RLM_MODULE_NOOP;
++ }
++ if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it");
++ return RLM_MODULE_NOOP;
++ }
++ if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) {
++ RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it");
++ return RLM_MODULE_NOOP;
++ }
++
++ /* Call milenage */
++ milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets,
++ amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets);
++
++ /*
++ * Now we genertate KASME
++ *
++ * Officially described in 33401-g30.doc section A.2
++ * But an easier to read explanation can be found at:
++ * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec
++ *
++ */
++
++ /* k = CK || IK */
++ uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE];
++ memcpy(kk_bin, ck_bin, sizeof(ck_bin));
++ memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin));
++
++ /* Initialize a 14 byte buffer s */
++ uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE];
++
++ /* Assign the first byte of s as 0x10 */
++ ks_bin[0] = 0x10;
++
++ /* Copy the 3 bytes of PLMN into s */
++ memcpy(ks_bin + 1, plmn->vp_octets, 3);
++
++ /* Assign 5th and 6th byte as 0x00 and 0x03 */
++ ks_bin[4] = 0x00;
++ ks_bin[5] = 0x03;
++
++ /* Assign the next 6 bytes as SQN XOR AK */
++ for (i = 0; i < 6; i++) {
++ ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i];
++ }
++
++ /* Assign the last two bytes as 0x00 and 0x06 */
++ ks_bin[12] = 0x00;
++ ks_bin[13] = 0x06;
++
++ /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */
++ uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE];
++ HMAC_CTX *hmac;
++ unsigned int kasme_len = sizeof(kasme_bin);
++
++ hmac = HMAC_CTX_new();
++ HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL);
++ HMAC_Update(hmac, ks_bin, sizeof(ks_bin));
++ kasme_len = SHA256_DIGEST_LENGTH;
++ HMAC_Final(hmac, &kasme_bin[0], &kasme_len);
++ HMAC_CTX_free(hmac);
++
++ /*
++ * Add reply attributes XRES, AUTN and KASME (RAND we added earlier)
++ *
++ * Note that we can't call fr_pair_find_by_num(), as
++ * these attributes are buried deep inside of the WiMAX
++ * hierarchy.
++ */
++ xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY);
++ if (!xres) {
++ MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET));
++ fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE);
++ }
++
++ autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY);
++ if (!autn) {
++ MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET));
++ fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE);
++ }
++
++ kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY);
++ if (!kasme) {
++ MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET));
++ fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE);
++ }
++
++ /* Print keys to log for debugging */
++ if (rad_debug_lvl) {
++ RDEBUG("-------- Milenage in --------");
++ RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length);
++ RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length);
++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length);
++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length);
++ RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length);
++ RDEBUG("-------- Milenage out -------");
++ RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length);
++ RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin));
++ RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin));
++ RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin));
++ RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length);
++ RDEBUG("-----------------------------");
++ RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin));
++ RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin));
++ RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length);
++ }
++
++ return RLM_MODULE_UPDATED;
++}
++
++/*
++ * Generate the keys after the user has been authenticated.
++ */
++static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
++{
++ VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn;
++
++ /*
++ * If we have MSK and EMSK then assume we want MIP keys
++ * Else if we have the SIM keys then we want the EPS-AKA vector
++ */
++
++ msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY);
++ emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY);
++
++ if (msk && emsk) {
++ RDEBUG("MSK and EMSK found. Generating MIP keys");
++ return mip_keys_generate(instance, request, msk, emsk);
++ }
++
++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY);
++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY);
++ amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY);
++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY);
++ plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY);
++
++ if (ki && opc && amf && sqn && plmn) {
++ RDEBUG("AKA attributes found. Generating AKA keys.");
++ return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn);
++ }
++
++ RDEBUG("Input keys not found. Cannot create WiMAX keys");
++ return RLM_MODULE_NOOP;
++}
++
++
++static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
++{
++ rlm_wimax_t *inst = instance;
++
++ inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info");
++ inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES");
++ inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN");
++ inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME");
++
++ return 0;
++}
+
+ /*
+ * The module name should be the only globally exported symbol.
+@@ -472,10 +833,10 @@ module_t rlm_wimax = {
+ .type = RLM_TYPE_THREAD_SAFE,
+ .inst_size = sizeof(rlm_wimax_t),
+ .config = module_config,
++ .instantiate = mod_instantiate,
+ .methods = {
+ [MOD_AUTHORIZE] = mod_authorize,
+ [MOD_PREACCT] = mod_preacct,
+- [MOD_ACCOUNTING] = mod_accounting,
+ [MOD_POST_AUTH] = mod_post_auth
+ },
+ };
+diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4
+new file mode 100644
+index 0000000000..7e9b1ffcdf
+--- /dev/null
++++ b/src/tests/keywords/md4
+@@ -0,0 +1,58 @@
++#
++# PRE: update if
++#
++update reply {
++ Filter-Id := "filter"
++}
++
++update {
++ control:Cleartext-Password := 'hello'
++ request:Tmp-String-0 := "This is a string\n"
++ request:Tmp-Octets-0 := 0x000504030201
++ request:Tmp-String-1 := "what do ya want for nothing?"
++ request:Tmp-String-2 := "Jefe"
++}
++
++#
++# Put "This is a string" into a file and call "md5sum" on it.
++# You should get this string.
++#
++if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') {
++ update reply {
++ Filter-Id += 'fail 1'
++ }
++}
++
++if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
++ update reply {
++ Filter-Id += 'fail 2'
++ }
++}
++
++if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
++ update reply {
++ Filter-Id += 'fail 3'
++ }
++}
++
++if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') {
++ update reply {
++ Filter-Id += 'fail 4'
++ }
++}
++
++#
++# MD4 should also be able to cope with references to octet attributes
++#
++if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') {
++ update reply {
++ Filter-Id += 'fail 5'
++ }
++}
++
++if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') {
++ update reply {
++ Filter-Id += 'fail 6'
++ }
++}
++
diff --git a/freeradius-Fix-resource-hard-limit-error.patch b/freeradius-Fix-resource-hard-limit-error.patch
new file mode 100644
index 0000000..800c06c
--- /dev/null
+++ b/freeradius-Fix-resource-hard-limit-error.patch
@@ -0,0 +1,32 @@
+commit 1ce4508c92493cf03ea1b3c42e83540b387884fa
+Author: Antonio Torres <antorres@redhat.com>
+Date: Fri Jul 2 07:12:48 2021 -0400
+Subject: [PATCH] debug: don't set resource hard limit to zero
+
+ Setting the resource hard limit to zero is irreversible, meaning if it
+ is set to zero then there is no way to set it higher. This means
+ enabling core dump is not possible, since setting a new resource limit
+ for RLIMIT_CORE would fail. By only setting the soft limit to zero, we
+ can disable and enable core dumps without failures.
+
+ This fix is present in both main and 3.0.x upstream branches.
+
+ Ticket in RHEL Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1977572
+ Signed-off-by: Antonio Torres antorres@redhat.com
+---
+ src/lib/debug.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/lib/debug.c b/src/lib/debug.c
+index 576bcb2a65..6330c9cb66 100644
+--- a/src/lib/debug.c
++++ b/src/lib/debug.c
+@@ -599,7 +599,7 @@ int fr_set_dumpable(bool allow_core_dumps)
+ struct rlimit no_core;
+
+ no_core.rlim_cur = 0;
+- no_core.rlim_max = 0;
++ no_core.rlim_max = core_limits.rlim_max;
+
+ if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
+ fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno));
diff --git a/freeradius-Use-system-crypto-policy-by-default.patch b/freeradius-Use-system-crypto-policy-by-default.patch
new file mode 100644
index 0000000..199e583
--- /dev/null
+++ b/freeradius-Use-system-crypto-policy-by-default.patch
@@ -0,0 +1,86 @@
+From a7ed62fbcc043a9ec7a4f09962a2cd2acffa019b Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Wed, 8 May 2019 10:16:31 -0400
+Subject: [PATCH] Use system-provided crypto-policies by default
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+---
+ raddb/mods-available/eap | 4 ++--
+ raddb/mods-available/inner-eap | 2 +-
+ raddb/sites-available/abfab-tls | 2 +-
+ raddb/sites-available/tls | 4 ++--
+ 4 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap
+index 36849e10f2..b28c0f19c6 100644
+--- a/raddb/mods-available/eap
++++ b/raddb/mods-available/eap
+@@ -368,7 +368,7 @@ eap {
+ #
+ # For EAP-FAST, use "ALL:!EXPORT:!eNULL:!SSLv2"
+ #
+- cipher_list = "DEFAULT"
++ cipher_list = "PROFILE=SYSTEM"
+
+ # If enabled, OpenSSL will use server cipher list
+ # (possibly defined by cipher_list option above)
+@@ -912,7 +912,7 @@ eap {
+ # Note - for OpenSSL 1.1.0 and above you may need
+ # to add ":@SECLEVEL=0"
+ #
+- # cipher_list = "ALL:!EXPORT:!eNULL:!SSLv2"
++ # cipher_list = "PROFILE=SYSTEM"
+
+ # PAC lifetime in seconds (default: seven days)
+ #
+diff --git a/raddb/mods-available/inner-eap b/raddb/mods-available/inner-eap
+index 576eb7739e..ffa07188e2 100644
+--- a/raddb/mods-available/inner-eap
++++ b/raddb/mods-available/inner-eap
+@@ -77,7 +77,7 @@ eap inner-eap {
+ # certificates. If so, edit this file.
+ ca_file = ${cadir}/ca.pem
+
+- cipher_list = "DEFAULT"
++ cipher_list = "PROFILE=SYSTEM"
+
+ # You may want to set a very small fragment size.
+ # The TLS data here needs to go inside of the
+diff --git a/raddb/sites-available/abfab-tls b/raddb/sites-available/abfab-tls
+index 92f1d6330e..cd69b3905a 100644
+--- a/raddb/sites-available/abfab-tls
++++ b/raddb/sites-available/abfab-tls
+@@ -19,7 +19,7 @@ listen {
+ dh_file = ${certdir}/dh
+ fragment_size = 8192
+ ca_path = ${cadir}
+- cipher_list = "DEFAULT"
++ cipher_list = "PROFILE=SYSTEM"
+
+ cache {
+ enable = no
+diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls
+index bbc761b1c5..83cd35b851 100644
+--- a/raddb/sites-available/tls
++++ b/raddb/sites-available/tls
+@@ -215,7 +215,7 @@ listen {
+ # Set this option to specify the allowed
+ # TLS cipher suites. The format is listed
+ # in "man 1 ciphers".
+- cipher_list = "DEFAULT"
++ cipher_list = "PROFILE=SYSTEM"
+
+ # If enabled, OpenSSL will use server cipher list
+ # (possibly defined by cipher_list option above)
+@@ -517,7 +517,7 @@ home_server tls {
+ # Set this option to specify the allowed
+ # TLS cipher suites. The format is listed
+ # in "man 1 ciphers".
+- cipher_list = "DEFAULT"
++ cipher_list = "PROFILE=SYSTEM"
+ }
+
+ }
+--
+2.21.0
+
diff --git a/freeradius-bootstrap-create-only.patch b/freeradius-bootstrap-create-only.patch
new file mode 100644
index 0000000..17cab04
--- /dev/null
+++ b/freeradius-bootstrap-create-only.patch
@@ -0,0 +1,89 @@
+From acaf4be8e301a01041acba189194d9502994611d Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Wed, 13 May 2020 10:01:47 -0400
+Subject: [PATCH] Don't clobber existing files on bootstrap
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+---
+ raddb/certs/bootstrap | 31 +++++++++++++++----------------
+ 1 file changed, 15 insertions(+), 16 deletions(-)
+
+diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap
+index ede09bc..e555491 100755
+--- a/raddb/certs/bootstrap
++++ b/raddb/certs/bootstrap
+@@ -20,56 +20,55 @@ cd `dirname $0`
+ # Don't edit the following text. Instead, edit the Makefile, and
+ # re-generate these commands.
+ #
+-if [ ! -f dh ]; then
++if [ ! -e dh ]; then
+ openssl dhparam -out dh 2048 || exit 1
+- if [ -e /dev/urandom ] ; then
+- ln -sf /dev/urandom random
+- else
+- date > ./random;
+- fi
++ ln -sf /dev/urandom random
+ fi
+
+-if [ ! -f server.key ]; then
++if [ ! -e server.key ]; then
+ openssl req -new -out server.csr -keyout server.key -config ./server.cnf || exit 1
+ chmod g+r server.key
+ fi
+
+-if [ ! -f ca.key ]; then
++if [ ! -e ca.key ]; then
+ openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf || exit 1
+ fi
+
+-if [ ! -f index.txt ]; then
++if [ ! -e index.txt ]; then
+ touch index.txt
+ fi
+
+-if [ ! -f serial ]; then
++if [ ! -e serial ]; then
+ echo '01' > serial
+ fi
+
+-if [ ! -f server.crt ]; then
++if [ ! -e server.crt ]; then
+ openssl ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out server.crt -extensions xpserver_ext -extfile xpextensions -config ./server.cnf || exit 1
+ fi
+
+-if [ ! -f server.p12 ]; then
++if [ ! -e server.p12 ]; then
+ openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1
+ chmod g+r server.p12
+ fi
+
+-if [ ! -f server.pem ]; then
++if [ ! -e server.pem ]; then
+ openssl pkcs12 -in server.p12 -out server.pem -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1
+ openssl verify -CAfile ca.pem server.pem || exit 1
+ chmod g+r server.pem
+ fi
+
+-if [ ! -f ca.der ]; then
++if [ ! -e ca.der ]; then
+ openssl x509 -inform PEM -outform DER -in ca.pem -out ca.der || exit 1
+ fi
+
+-if [ ! -f client.key ]; then
++if [ ! -e client.key ]; then
+ openssl req -new -out client.csr -keyout client.key -config ./client.cnf
+ chmod g+r client.key
+ fi
+
+-if [ ! -f client.crt ]; then
++if [ ! -e client.crt ]; then
+ openssl ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out client.crt -extensions xpclient_ext -extfile xpextensions -config ./client.cnf
+ fi
++
++chown root:radiusd dh ca.* client.* server.*
++chmod 640 dh ca.* client.* server.*
+--
+2.26.2
+
diff --git a/freeradius-bootstrap-make-permissions.patch b/freeradius-bootstrap-make-permissions.patch
new file mode 100644
index 0000000..3548fa6
--- /dev/null
+++ b/freeradius-bootstrap-make-permissions.patch
@@ -0,0 +1,29 @@
+From ea164ceafa05f96079204a3f0ae379e46e64a455 Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Tue, 4 Aug 2020 10:08:15 -0400
+Subject: [PATCH] Fix permissions after generating certificates with make
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+---
+ raddb/certs/bootstrap | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap
+index 336a2bd..9920ecf 100755
+--- a/raddb/certs/bootstrap
++++ b/raddb/certs/bootstrap
+@@ -21,7 +21,10 @@ make -h > /dev/null 2>&1
+ #
+ if [ "$?" = "0" ]; then
+ make all
+- exit $?
++ ret=$?
++ chown root:radiusd dh ca.* client.* server.*
++ chmod 640 dh ca.* client.* server.*
++ exit $ret
+ fi
+
+ #
+--
+2.26.2
+
diff --git a/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch b/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch
new file mode 100644
index 0000000..b3dc68e
--- /dev/null
+++ b/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch
@@ -0,0 +1,136 @@
+From e089777942552c4fe3e58aa328566e7bb745dbf8 Mon Sep 17 00:00:00 2001
+From: Antonio Torres <antorres@redhat.com>
+Date: Fri, 22 Apr 2022 12:27:43 +0200
+Subject: [PATCH] bootstrap: pass -noenc to certificate generation
+
+Bootstrap script would fail to generate certificates if run on systems
+with FIPS enabled. By passing the -noenc option, we can skip the usage
+of unsupported algorithms on these systems.
+
+After generating the certificates, correct permissions are set.
+
+Signed-off-by: Antonio Torres <antorres@redhat.com>
+
+[antorres@redhat.com]: patch adapted to work together with freeradius-bootstrap-create-only.patch.
+In bootstrap diff, -f is changed to -e in conditionals.
+---
+ raddb/certs/Makefile | 20 ++++++++++++++++----
+ raddb/certs/bootstrap | 6 +++---
+ 2 files changed, 19 insertions(+), 7 deletions(-)
+
+diff --git a/raddb/certs/Makefile b/raddb/certs/Makefile
+index 5cbfd467ce..cb10394ec3 100644
+--- a/raddb/certs/Makefile
++++ b/raddb/certs/Makefile
+@@ -60,6 +60,8 @@ passwords.mk: server.cnf ca.cnf client.cnf inner-server.cnf
+ ######################################################################
+ dh:
+ $(OPENSSL) dhparam -out dh -2 $(DH_KEY_SIZE)
++ chown root:radiusd dh
++ chmod 640 dh
+
+ ######################################################################
+ #
+@@ -71,8 +73,10 @@ ca.key ca.pem: ca.cnf
+ @[ -f serial ] || $(MAKE) serial
+ $(OPENSSL) req -new -x509 -keyout ca.key -out ca.pem \
+ -days $(CA_DEFAULT_DAYS) -config ./ca.cnf \
+- -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA)
++ -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA) -noenc
+ chmod g+r ca.key
++ chown root:radiusd ca.*
++ chmod 640 ca.*
+
+ ca.der: ca.pem
+ $(OPENSSL) x509 -inform PEM -outform DER -in ca.pem -out ca.der
+@@ -81,6 +85,8 @@ ca.crl: ca.pem
+ $(OPENSSL) ca -gencrl -keyfile ca.key -cert ca.pem -config ./ca.cnf -out ca-crl.pem -key $(PASSWORD_CA)
+ $(OPENSSL) crl -in ca-crl.pem -outform der -out ca.crl
+ rm ca-crl.pem
++ chown root:radiusd ca.*
++ chmod 640 ca.*
+
+ ######################################################################
+ #
+@@ -88,7 +94,7 @@ ca.crl: ca.pem
+ #
+ ######################################################################
+ server.csr server.key: server.cnf
+- $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf
++ $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf -noenc
+ chmod g+r server.key
+
+ server.crt: server.csr ca.key ca.pem
+@@ -101,6 +107,8 @@ server.p12: server.crt
+ server.pem: server.p12
+ $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER)
+ chmod g+r server.pem
++ chown root:radiusd server.*
++ chmod 640 server.*
+
+ .PHONY: server.vrfy
+ server.vrfy: ca.pem
+@@ -113,7 +121,7 @@ server.vrfy: ca.pem
+ #
+ ######################################################################
+ client.csr client.key: client.cnf
+- $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf
++ $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf -noenc
+ chmod g+r client.key
+
+ client.crt: client.csr ca.pem ca.key
+@@ -127,6 +135,8 @@ client.pem: client.p12
+ $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT)
+ chmod g+r client.pem
+ cp client.pem $(USER_NAME).pem
++ chown root:radiusd client.*
++ chmod 640 client.*
+
+ .PHONY: client.vrfy
+ client.vrfy: ca.pem client.pem
+@@ -139,7 +149,7 @@ client.vrfy: ca.pem client.pem
+ #
+ ######################################################################
+ inner-server.csr inner-server.key: inner-server.cnf
+- $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf
++ $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf -noenc
+ chmod g+r inner-server.key
+
+ inner-server.crt: inner-server.csr ca.key ca.pem
+@@ -152,6 +162,8 @@ inner-server.p12: inner-server.crt
+ inner-server.pem: inner-server.p12
+ $(OPENSSL) pkcs12 -in inner-server.p12 -out inner-server.pem -passin pass:$(PASSWORD_INNER) -passout pass:$(PASSWORD_INNER)
+ chmod g+r inner-server.pem
++ chown root:radiusd inner-server.*
++ chmod 640 inner-server.*
+
+ .PHONY: inner-server.vrfy
+ inner-server.vrfy: ca.pem
+diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap
+index 57de8cf0d7..c258ec45e0 100755
+--- a/raddb/certs/bootstrap
++++ b/raddb/certs/bootstrap
+@@ -41,12 +41,12 @@ if [ ! -f dh ]; then
+ fi
+
+ if [ ! -e server.key ]; then
+- openssl req -new -out server.csr -keyout server.key -config ./server.cnf || exit 1
++ openssl req -new -out server.csr -keyout server.key -config ./server.cnf -noenc || exit 1
+ chmod g+r server.key
+ fi
+
+ if [ ! -e ca.key ]; then
+- openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf || exit 1
++ openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf -noenc || exit 1
+ fi
+
+ if [ ! -e index.txt ]; then
+@@ -77,7 +77,7 @@ if [ ! -f ca.der ]; then
+ fi
+
+ if [ ! -e client.key ]; then
+- openssl req -new -out client.csr -keyout client.key -config ./client.cnf
++ openssl req -new -out client.csr -keyout client.key -config ./client.cnf -noenc
+ chmod g+r client.key
+ fi
+ \ No newline at end of file
diff --git a/freeradius-fix-crash-on-invalid-abinary-data.patch b/freeradius-fix-crash-on-invalid-abinary-data.patch
new file mode 100644
index 0000000..862c6b5
--- /dev/null
+++ b/freeradius-fix-crash-on-invalid-abinary-data.patch
@@ -0,0 +1,47 @@
+From: Antonio Torres <antorres@redhat.com>
+Date: Fri, 09 Dec 2022
+Subject: Fix crash on invalid abinary data
+
+A malicious RADIUS client or home server can send a malformed abinary
+attribute which can cause the server to crash.
+
+Backport of https://github.com/FreeRADIUS/freeradius-server/commit/0ec2b39d260e
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151707
+Signed-off-by: Antonio Torres <antorres@redhat.com>
+---
+diff --git a/src/lib/filters.c b/src/lib/filters.c
+index 4868cd385d9f..3f3b63daeef3 100644
+--- a/src/lib/filters.c
++++ b/src/lib/filters.c
+@@ -1205,13 +1205,19 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in
+ }
+ }
+ } else if (filter->type == RAD_FILTER_GENERIC) {
+- int count;
++ size_t count, masklen;
++
++ masklen = ntohs(filter->u.generic.len);
++ if (masklen >= sizeof(filter->u.generic.mask)) {
++ *p = '\0';
++ return;
++ }
+
+ i = snprintf(p, outlen, " %u ", (unsigned int) ntohs(filter->u.generic.offset));
+ p += i;
+
+ /* show the mask */
+- for (count = 0; count < ntohs(filter->u.generic.len); count++) {
++ for (count = 0; count < masklen; count++) {
+ i = snprintf(p, outlen, "%02x", filter->u.generic.mask[count]);
+ p += i;
+ outlen -= i;
+@@ -1222,7 +1228,7 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in
+ outlen--;
+
+ /* show the value */
+- for (count = 0; count < ntohs(filter->u.generic.len); count++) {
++ for (count = 0; count < masklen; count++) {
+ i = snprintf(p, outlen, "%02x", filter->u.generic.value[count]);
+ p += i;
+ outlen -= i;
diff --git a/freeradius-fix-crash-unknown-eap-sim.patch b/freeradius-fix-crash-unknown-eap-sim.patch
new file mode 100644
index 0000000..d2b7956
--- /dev/null
+++ b/freeradius-fix-crash-unknown-eap-sim.patch
@@ -0,0 +1,115 @@
+From: Antonio Torres <antorres@redhat.com>
+Date: Fri, 09 Dec 2022
+Subject: Fix crash on unknown option in EAP-SIM
+
+When an EAP-SIM supplicant sends an unknown SIM option, the server will try to
+look that option up in the internal dictionaries. This lookup will fail, but the
+SIM code will not check for that failure. Instead, it will dereference a NULL
+pointer, and cause the server to crash.
+
+Backport of:
+https://github.com/FreeRADIUS/freeradius-server/commit/f1cdbb33ec61c4a64a
+https://github.com/FreeRADIUS/freeradius-server/commit/71128cac3ee236a88a05cc7bddd43e43a88a3089
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151705
+Signed-off-by: Antonio Torres <antorres@redhat.com>
+---
+diff --git a/src/modules/rlm_eap/libeap/eapsimlib.c b/src/modules/rlm_eap/libeap/eapsimlib.c
+index cf1e8a7dd92..e438a844eab 100644
+--- a/src/modules/rlm_eap/libeap/eapsimlib.c
++++ b/src/modules/rlm_eap/libeap/eapsimlib.c
+@@ -307,42 +307,77 @@ int unmap_eapsim_basictypes(RADIUS_PACKET *r,
+ newvp->vp_length = 1;
+ fr_pair_add(&(r->vps), newvp);
+
++ /*
++ * EAP-SIM has a 1 octet of subtype, and 2 octets
++ * reserved.
++ */
+ attr += 3;
+ attrlen -= 3;
+
+- /* now, loop processing each attribute that we find */
+- while(attrlen > 0) {
++ /*
++ * Loop over each attribute. The format is:
++ *
++ * 1 octet of type
++ * 1 octet of length (value 1..255)
++ * ((4 * length) - 2) octets of data.
++ */
++ while (attrlen > 0) {
+ uint8_t *p;
+
+- if(attrlen < 2) {
++ if (attrlen < 2) {
+ fr_strerror_printf("EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen);
+ return 0;
+ }
+
++ if (!attr[1]) {
++ fr_strerror_printf("EAP-Sim attribute %d (no.%d) has no data", attr[0],
++ es_attribute_count);
++ return 0;
++ }
++
+ eapsim_attribute = attr[0];
+ eapsim_len = attr[1] * 4;
+
++ /*
++ * The length includes the 2-byte header.
++ */
+ if (eapsim_len > attrlen) {
+ fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)",
+ eapsim_attribute, es_attribute_count, eapsim_len, attrlen);
+ return 0;
+ }
+
+- if(eapsim_len > MAX_STRING_LEN) {
+- eapsim_len = MAX_STRING_LEN;
+- }
+- if (eapsim_len < 2) {
+- fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length too small", eapsim_attribute,
+- es_attribute_count);
+- return 0;
+- }
++ newvp = fr_pair_afrom_num(r, eapsim_attribute + PW_EAP_SIM_BASE, 0);
++ if (!newvp) {
++ /*
++ * RFC 4186 Section 8.1 says 0..127 are
++ * "non-skippable". If one such
++ * attribute is found and we don't
++ * understand it, the server has to send:
++ *
++ * EAP-Request/SIM/Notification packet with an
++ * (AT_NOTIFICATION code, which implies general failure ("General
++ * failure after authentication" (0), or "General failure" (16384),
++ * depending on the phase of the exchange), which terminates the
++ * authentication exchange.
++ */
++ if (eapsim_attribute <= 127) {
++ fr_strerror_printf("Unknown mandatory attribute %d, failing",
++ eapsim_attribute);
++ return 0;
++ }
+
+- newvp = fr_pair_afrom_num(r, eapsim_attribute+PW_EAP_SIM_BASE, 0);
+- newvp->vp_length = eapsim_len-2;
+- newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length);
+- memcpy(p, &attr[2], eapsim_len-2);
+- fr_pair_add(&(r->vps), newvp);
+- newvp = NULL;
++ } else {
++ /*
++ * It's known, ccount for header, and
++ * copy the value over.
++ */
++ newvp->vp_length = eapsim_len - 2;
++
++ newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length);
++ memcpy(p, &attr[2], newvp->vp_length);
++ fr_pair_add(&(r->vps), newvp);
++ }
+
+ /* advance pointers, decrement length */
+ attr += eapsim_len;
diff --git a/freeradius-fix-python3-library-suffix.patch b/freeradius-fix-python3-library-suffix.patch
new file mode 100644
index 0000000..b6d6ab3
--- /dev/null
+++ b/freeradius-fix-python3-library-suffix.patch
@@ -0,0 +1,635 @@
+From: Antonio Torres <antorres@redhat.com>
+Date: Mon, 06 Nov 2023
+Subject: Fix Python3.8+ library name suffix
+
+Python 3.8 has removed the "m" suffix in the library name, add a check for it.
+
+Backport of https://github.com/FreeRADIUS/freeradius-server/commit/fa837465493158257e600f28bca009ba890db863
+
+Resolves: https://issues.redhat.com/browse/RHEL-15503
+Signed-off-by: Antonio Torres <antorres@redhat.com>
+---
+diff --git a/src/modules/rlm_python3/configure b/src/modules/rlm_python3/configure
+index f421558ac0c0..05907f12c359 100755
+--- a/src/modules/rlm_python3/configure
++++ b/src/modules/rlm_python3/configure
+@@ -588,7 +588,17 @@ LIBOBJS
+ targetname
+ mod_cflags
+ mod_ldflags
++AWK
+ PYTHON3_CONFIG_BIN
++pkgpyexecdir
++pyexecdir
++pkgpythondir
++pythondir
++PYTHON_PLATFORM
++PYTHON_EXEC_PREFIX
++PYTHON_PREFIX
++PYTHON_VERSION
++PYTHON
+ CPP
+ OBJEXT
+ EXEEXT
+@@ -648,7 +658,8 @@ CFLAGS
+ LDFLAGS
+ LIBS
+ CPPFLAGS
+-CPP'
++CPP
++PYTHON'
+
+
+ # Initialize some variables set by options.
+@@ -1266,6 +1277,7 @@ Some influential environment variables:
+ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+ you have headers in a nonstandard directory <include dir>
+ CPP C preprocessor
++ PYTHON the Python interpreter
+
+ Use these variables to override the choices made by `configure' or to help
+ it to find libraries and programs with nonstandard names/locations.
+@@ -1421,6 +1433,119 @@ fi
+ as_fn_set_status $ac_retval
+
+ } # ac_fn_c_try_cpp
++
++# ac_fn_c_try_link LINENO
++# -----------------------
++# Try to link conftest.$ac_ext, and return whether this succeeded.
++ac_fn_c_try_link ()
++{
++ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
++ rm -f conftest.$ac_objext conftest$ac_exeext
++ if { { ac_try="$ac_link"
++case "(($ac_try" in
++ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
++ *) ac_try_echo=$ac_try;;
++esac
++eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
++$as_echo "$ac_try_echo"; } >&5
++ (eval "$ac_link") 2>conftest.err
++ ac_status=$?
++ if test -s conftest.err; then
++ grep -v '^ *+' conftest.err >conftest.er1
++ cat conftest.er1 >&5
++ mv -f conftest.er1 conftest.err
++ fi
++ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
++ test $ac_status = 0; } && {
++ test -z "$ac_c_werror_flag" ||
++ test ! -s conftest.err
++ } && test -s conftest$ac_exeext && {
++ test "$cross_compiling" = yes ||
++ test -x conftest$ac_exeext
++ }; then :
++ ac_retval=0
++else
++ $as_echo "$as_me: failed program was:" >&5
++sed 's/^/| /' conftest.$ac_ext >&5
++
++ ac_retval=1
++fi
++ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
++ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
++ # interfere with the next link command; also delete a directory that is
++ # left behind by Apple's compiler. We do this before executing the actions.
++ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
++ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
++ as_fn_set_status $ac_retval
++
++} # ac_fn_c_try_link
++
++# ac_fn_c_check_func LINENO FUNC VAR
++# ----------------------------------
++# Tests whether FUNC exists, setting the cache variable VAR accordingly
++ac_fn_c_check_func ()
++{
++ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
++$as_echo_n "checking for $2... " >&6; }
++if eval \${$3+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
++/* end confdefs.h. */
++/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
++ For example, HP-UX 11i <limits.h> declares gettimeofday. */
++#define $2 innocuous_$2
++
++/* System header to define __stub macros and hopefully few prototypes,
++ which can conflict with char $2 (); below.
++ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
++ <limits.h> exists even on freestanding compilers. */
++
++#ifdef __STDC__
++# include <limits.h>
++#else
++# include <assert.h>
++#endif
++
++#undef $2
++
++/* Override any GCC internal prototype to avoid an error.
++ Use char because int might match the return type of a GCC
++ builtin and then its argument prototype would still apply. */
++#ifdef __cplusplus
++extern "C"
++#endif
++char $2 ();
++/* The GNU C library defines this for functions which it implements
++ to always fail with ENOSYS. Some functions are actually named
++ something starting with __ and the normal name is an alias. */
++#if defined __stub_$2 || defined __stub___$2
++choke me
++#endif
++
++int
++main ()
++{
++return $2 ();
++ ;
++ return 0;
++}
++_ACEOF
++if ac_fn_c_try_link "$LINENO"; then :
++ eval "$3=yes"
++else
++ eval "$3=no"
++fi
++rm -f core conftest.err conftest.$ac_objext \
++ conftest$ac_exeext conftest.$ac_ext
++fi
++eval ac_res=\$$3
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
++$as_echo "$ac_res" >&6; }
++ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
++
++} # ac_fn_c_check_func
+ cat >config.log <<_ACEOF
+ This file contains any messages produced by compilers while
+ running configure, to aid debugging if configure makes a mistake.
+@@ -2705,6 +2830,267 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
+ ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
++
++
++
++
++
++ if test -n "$PYTHON"; then
++ # If the user set $PYTHON, use it and don't search something else.
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 3.0" >&5
++$as_echo_n "checking whether $PYTHON version is >= 3.0... " >&6; }
++ prog="import sys
++# split strings by '.' and convert to numeric. Append some zeros
++# because we need at least 4 digits for the hex conversion.
++# map returns an iterator in Python 3.0 and a list in 2.x
++minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0]
++minverhex = 0
++# xrange is not present in Python 3.0 and range returns an iterator
++for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i]
++sys.exit(sys.hexversion < minverhex)"
++ if { echo "$as_me:$LINENO: $PYTHON -c "$prog"" >&5
++ ($PYTHON -c "$prog") >&5 2>&5
++ ac_status=$?
++ echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; then :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
++$as_echo "yes" >&6; }
++else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
++$as_echo "no" >&6; }
++ as_fn_error $? "Python interpreter is too old" "$LINENO" 5
++fi
++ am_display_PYTHON=$PYTHON
++ else
++ # Otherwise, try each interpreter until we find one that satisfies
++ # VERSION.
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 3.0" >&5
++$as_echo_n "checking for a Python interpreter with version >= 3.0... " >&6; }
++if ${am_cv_pathless_PYTHON+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++
++ for am_cv_pathless_PYTHON in python python2 python3 python3.9 python3.8 python3.7 python3.6 python3.5 python3.4 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do
++ test "$am_cv_pathless_PYTHON" = none && break
++ prog="import sys
++# split strings by '.' and convert to numeric. Append some zeros
++# because we need at least 4 digits for the hex conversion.
++# map returns an iterator in Python 3.0 and a list in 2.x
++minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0]
++minverhex = 0
++# xrange is not present in Python 3.0 and range returns an iterator
++for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i]
++sys.exit(sys.hexversion < minverhex)"
++ if { echo "$as_me:$LINENO: $am_cv_pathless_PYTHON -c "$prog"" >&5
++ ($am_cv_pathless_PYTHON -c "$prog") >&5 2>&5
++ ac_status=$?
++ echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; then :
++ break
++fi
++ done
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_pathless_PYTHON" >&5
++$as_echo "$am_cv_pathless_PYTHON" >&6; }
++ # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON.
++ if test "$am_cv_pathless_PYTHON" = none; then
++ PYTHON=:
++ else
++ # Extract the first word of "$am_cv_pathless_PYTHON", so it can be a program name with args.
++set dummy $am_cv_pathless_PYTHON; ac_word=$2
++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
++$as_echo_n "checking for $ac_word... " >&6; }
++if ${ac_cv_path_PYTHON+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ case $PYTHON in
++ [\\/]* | ?:[\\/]*)
++ ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path.
++ ;;
++ *)
++ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
++for as_dir in $PATH
++do
++ IFS=$as_save_IFS
++ test -z "$as_dir" && as_dir=.
++ for ac_exec_ext in '' $ac_executable_extensions; do
++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
++ ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext"
++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
++ break 2
++ fi
++done
++ done
++IFS=$as_save_IFS
++
++ ;;
++esac
++fi
++PYTHON=$ac_cv_path_PYTHON
++if test -n "$PYTHON"; then
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5
++$as_echo "$PYTHON" >&6; }
++else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
++$as_echo "no" >&6; }
++fi
++
++
++ fi
++ am_display_PYTHON=$am_cv_pathless_PYTHON
++ fi
++
++
++ if test "$PYTHON" = :; then
++ :
++ else
++
++
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON version" >&5
++$as_echo_n "checking for $am_display_PYTHON version... " >&6; }
++if ${am_cv_python_version+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[:3])"`
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_version" >&5
++$as_echo "$am_cv_python_version" >&6; }
++ PYTHON_VERSION=$am_cv_python_version
++
++
++
++ PYTHON_PREFIX='${prefix}'
++
++ PYTHON_EXEC_PREFIX='${exec_prefix}'
++
++
++
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON platform" >&5
++$as_echo_n "checking for $am_display_PYTHON platform... " >&6; }
++if ${am_cv_python_platform+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"`
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_platform" >&5
++$as_echo "$am_cv_python_platform" >&6; }
++ PYTHON_PLATFORM=$am_cv_python_platform
++
++
++ # Just factor out some code duplication.
++ am_python_setup_sysconfig="\
++import sys
++# Prefer sysconfig over distutils.sysconfig, for better compatibility
++# with python 3.x. See automake bug#10227.
++try:
++ import sysconfig
++except ImportError:
++ can_use_sysconfig = 0
++else:
++ can_use_sysconfig = 1
++# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs:
++# <https://github.com/pypa/virtualenv/issues/118>
++try:
++ from platform import python_implementation
++ if python_implementation() == 'CPython' and sys.version[:3] == '2.7':
++ can_use_sysconfig = 0
++except ImportError:
++ pass"
++
++
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5
++$as_echo_n "checking for $am_display_PYTHON script directory... " >&6; }
++if ${am_cv_python_pythondir+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ if test "x$prefix" = xNONE
++ then
++ am_py_prefix=$ac_default_prefix
++ else
++ am_py_prefix=$prefix
++ fi
++ am_cv_python_pythondir=`$PYTHON -c "
++$am_python_setup_sysconfig
++if can_use_sysconfig:
++ sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'})
++else:
++ from distutils import sysconfig
++ sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix')
++sys.stdout.write(sitedir)"`
++ case $am_cv_python_pythondir in
++ $am_py_prefix*)
++ am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'`
++ am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"`
++ ;;
++ *)
++ case $am_py_prefix in
++ /usr|/System*) ;;
++ *)
++ am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages
++ ;;
++ esac
++ ;;
++ esac
++
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pythondir" >&5
++$as_echo "$am_cv_python_pythondir" >&6; }
++ pythondir=$am_cv_python_pythondir
++
++
++
++ pkgpythondir=\${pythondir}/$PACKAGE
++
++
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON extension module directory" >&5
++$as_echo_n "checking for $am_display_PYTHON extension module directory... " >&6; }
++if ${am_cv_python_pyexecdir+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ if test "x$exec_prefix" = xNONE
++ then
++ am_py_exec_prefix=$am_py_prefix
++ else
++ am_py_exec_prefix=$exec_prefix
++ fi
++ am_cv_python_pyexecdir=`$PYTHON -c "
++$am_python_setup_sysconfig
++if can_use_sysconfig:
++ sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'})
++else:
++ from distutils import sysconfig
++ sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix')
++sys.stdout.write(sitedir)"`
++ case $am_cv_python_pyexecdir in
++ $am_py_exec_prefix*)
++ am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'`
++ am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"`
++ ;;
++ *)
++ case $am_py_exec_prefix in
++ /usr|/System*) ;;
++ *)
++ am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages
++ ;;
++ esac
++ ;;
++ esac
++
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pyexecdir" >&5
++$as_echo "$am_cv_python_pyexecdir" >&6; }
++ pyexecdir=$am_cv_python_pyexecdir
++
++
++
++ pkgpyexecdir=\${pyexecdir}/$PACKAGE
++
++
++
++ fi
++
++
++
+ PYTHON3_CONFIG_BIN=
+
+ # Check whether --with-rlm-python3-config-bin was given.
+@@ -2771,8 +3157,6 @@ test -n "$PYTHON3_CONFIG_BIN" || PYTHON3_CONFIG_BIN="not-found"
+ fi
+
+ if test "x$PYTHON3_CONFIG_BIN" = xnot-found; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python3-config not found" >&5
+-$as_echo "$as_me: WARNING: python3-config not found" >&2;}
+ fail="$fail python3-config"
+ else
+ old_CFLAGS="$CFLAGS"
+@@ -2794,7 +3178,85 @@ $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s cflags were \"${python3_cflags}\"" >&6
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Sanitized cflags were \"${mod_cflags}\"" >&5
+ $as_echo "$as_me: Sanitized cflags were \"${mod_cflags}\"" >&6;}
+
+- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags`
++ for ac_prog in gawk mawk nawk awk
++do
++ # Extract the first word of "$ac_prog", so it can be a program name with args.
++set dummy $ac_prog; ac_word=$2
++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
++$as_echo_n "checking for $ac_word... " >&6; }
++if ${ac_cv_prog_AWK+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ if test -n "$AWK"; then
++ ac_cv_prog_AWK="$AWK" # Let the user override the test.
++else
++as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
++for as_dir in $PATH
++do
++ IFS=$as_save_IFS
++ test -z "$as_dir" && as_dir=.
++ for ac_exec_ext in '' $ac_executable_extensions; do
++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
++ ac_cv_prog_AWK="$ac_prog"
++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
++ break 2
++ fi
++done
++ done
++IFS=$as_save_IFS
++
++fi
++fi
++AWK=$ac_cv_prog_AWK
++if test -n "$AWK"; then
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
++$as_echo "$AWK" >&6; }
++else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
++$as_echo "no" >&6; }
++fi
++
++
++ test -n "$AWK" && break
++done
++
++
++
++
++ # Used to indicate true or false condition
++ ax_compare_version=false
++
++ # Convert the two version strings to be compared into a format that
++ # allows a simple string comparison. The end result is that a version
++ # string of the form 1.12.5-r617 will be converted to the form
++ # 0001001200050617. In other words, each number is zero padded to four
++ # digits, and non digits are removed.
++
++ ax_compare_version_A=`echo "${PYTHON_VERSION}" | sed -e 's/\([0-9]*\)/Z\1Z/g' \
++ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \
++ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \
++ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \
++ -e 's/[^0-9]//g'`
++
++
++ ax_compare_version_B=`echo "3.8" | sed -e 's/\([0-9]*\)/Z\1Z/g' \
++ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \
++ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \
++ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \
++ -e 's/[^0-9]//g'`
++
++
++ ax_compare_version=`echo "x$ax_compare_version_A
++x$ax_compare_version_B" | sed 's/^ *//' | sort -r | sed "s/x${ax_compare_version_A}/true/;s/x${ax_compare_version_B}/false/;1q"`
++
++
++
++ if test "$ax_compare_version" = "true" ; then
++ EMBED="--embed"
++ fi
++
++
++ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&5
+ $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&6;}
+
+@@ -2811,6 +3273,18 @@ $as_echo "$as_me: Sanitized ldflags were \"${mod_ldflags}\"" >&6;}
+
+ targetname="rlm_python3"
+ fi
++
++for ac_func in dl_iterate_phdr
++do :
++ ac_fn_c_check_func "$LINENO" "dl_iterate_phdr" "ac_cv_func_dl_iterate_phdr"
++if test "x$ac_cv_func_dl_iterate_phdr" = xyes; then :
++ cat >>confdefs.h <<_ACEOF
++#define HAVE_DL_ITERATE_PHDR 1
++_ACEOF
++
++fi
++done
++
+ else
+ targetname=
+ echo \*\*\* module rlm_python3 is disabled.
+@@ -2833,11 +3307,7 @@ ac_config_headers="$ac_config_headers config.h"
+
+
+
+-
+- unset ac_cv_env_LIBS_set
+- unset ac_cv_env_LIBS_value
+-
+- ac_config_files="$ac_config_files all.mk"
++ac_config_files="$ac_config_files all.mk"
+
+ cat >confcache <<\_ACEOF
+ # This file is a shell script that caches the results of configure
+@@ -3417,6 +3887,7 @@ gives unlimited permission to copy, distribute and modify it."
+
+ ac_pwd='$ac_pwd'
+ srcdir='$srcdir'
++AWK='$AWK'
+ test -n "\$AWK" || AWK=awk
+ _ACEOF
+
+@@ -4111,4 +4582,3 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+ $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+ fi
+
+-
+diff --git a/src/modules/rlm_python3/configure.ac b/src/modules/rlm_python3/configure.ac
+index 295a2486d2ac..698a8c1d1874 100644
+--- a/src/modules/rlm_python3/configure.ac
++++ b/src/modules/rlm_python3/configure.ac
+@@ -7,6 +7,7 @@ if test x$with_[]modname != xno; then
+
+ AC_PROG_CC
+ AC_PROG_CPP
++ AM_PATH_PYTHON([3.0],, [:])
+
+ dnl extra argument: --with-rlm-python3-config-bin
+ PYTHON3_CONFIG_BIN=
+@@ -58,7 +59,11 @@ if test x$with_[]modname != xno; then
+ '`
+ AC_MSG_NOTICE([Sanitized cflags were \"${mod_cflags}\"])
+
+- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags`
++ dnl # From python 3.8, --embed is required
++ dnl # https://bugs.python.org/issue36721
++ AX_COMPARE_VERSION(${PYTHON_VERSION}, [ge], [3.8], [EMBED="--embed"], [])
++
++ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED`
+ AC_MSG_NOTICE([${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"])
+
+ dnl # Strip -Wl,-O1... Is -O even a valid linker flag??
+@@ -77,6 +82,7 @@ if test x$with_[]modname != xno; then
+
+ targetname="rlm_python3"
+ fi
++ AC_CHECK_FUNCS([dl_iterate_phdr])
+ else
+ targetname=
+ echo \*\*\* module modname is disabled.
+diff --git a/src/modules/rlm_python3/rlm_python3.c b/src/modules/rlm_python3/rlm_python3.c
+index df223f0f401b..5da23f4d7116 100644
+--- a/src/modules/rlm_python3/rlm_python3.c
++++ b/src/modules/rlm_python3/rlm_python3.c
+@@ -41,8 +41,17 @@ RCSID("$Id$")
+ #include <link.h>
+ #endif
+
++/*
++ * Since version 3.8, the "m" suffix is no longer available.
++ * https://bugs.python.org/issue36707
++ */
++#if PY_MINOR_VERSION >= 8
++#define LIBPYTHON_LINKER_NAME \
++ "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) LT_SHREXT
++#else
+ #define LIBPYTHON_LINKER_NAME \
+ "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) "m" LT_SHREXT
++#endif
+
+ static uint32_t python_instances = 0;
+ static void *python_dlhandle;
diff --git a/freeradius-ldap-infinite-timeout-on-starttls.patch b/freeradius-ldap-infinite-timeout-on-starttls.patch
new file mode 100644
index 0000000..40df134
--- /dev/null
+++ b/freeradius-ldap-infinite-timeout-on-starttls.patch
@@ -0,0 +1,31 @@
+From: Antonio Torres <antorres@redhat.com>
+Date: Fri, 28 Jan 2022
+Subject: Use infinite timeout when using LDAP+start-TLS
+
+This will ensure that the TLS connection to the LDAP server will complete
+before starting FreeRADIUS, as it forces libldap to use a blocking socket during
+the process. Infinite timeout is the OpenLDAP default.
+Avoids this: https://git.openldap.org/openldap/openldap/-/blob/87ffc60006298069a5a044b8e63dab27a61d3fdf/libraries/libldap/tls2.c#L1134
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1992551
+Signed-off-by: Antonio Torres <antorres@redhat.com>
+---
+ src/modules/rlm_ldap/ldap.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c
+index cf7a84e069..841bf888a1 100644
+--- a/src/modules/rlm_ldap/ldap.c
++++ b/src/modules/rlm_ldap/ldap.c
+@@ -1472,7 +1472,10 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
+ }
+
+ #ifdef LDAP_OPT_NETWORK_TIMEOUT
+- if (inst->net_timeout) {
++ bool using_tls = inst->start_tls ||
++ inst->port == 636 ||
++ strncmp(inst->server, "ldaps://", strlen("ldaps://")) == 0;
++ if (inst->net_timeout && !using_tls) {
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = inst->net_timeout;
+
diff --git a/freeradius-logrotate b/freeradius-logrotate
new file mode 100644
index 0000000..c962254
--- /dev/null
+++ b/freeradius-logrotate
@@ -0,0 +1,56 @@
+# You can use this to rotate the /var/log/radius/* files, simply copy
+# it to /etc/logrotate.d/radiusd
+
+# There are different detail-rotating strategies you can use. One is
+# to write to a single detail file per IP and use the rotate config
+# below. Another is to write to a daily detail file per IP with:
+# detailfile = ${radacctdir}/%{Client-IP-Address}/%Y%m%d-detail
+# (or similar) in radiusd.conf, without rotation. If you go with the
+# second technique, you will need another cron job that removes old
+# detail files. You do not need to comment out the below for method #2.
+/var/log/radius/radacct/*/detail {
+ monthly
+ rotate 4
+ nocreate
+ missingok
+ compress
+ su radiusd radiusd
+}
+
+/var/log/radius/checkrad.log {
+ monthly
+ rotate 4
+ create
+ missingok
+ compress
+ su radiusd radiusd
+}
+
+/var/log/radius/radius.log {
+ monthly
+ rotate 4
+ create
+ missingok
+ compress
+ su radiusd radiusd
+ postrotate
+ /usr/bin/systemctl reload-or-try-restart radiusd
+ endscript
+}
+
+/var/log/radius/radwtmp {
+ monthly
+ rotate 4
+ create
+ compress
+ missingok
+ su radiusd radiusd
+}
+/var/log/radius/sqltrace.sql {
+ monthly
+ rotate 4
+ create
+ compress
+ missingok
+ su radiusd radiusd
+}
diff --git a/freeradius-no-buildtime-cert-gen.patch b/freeradius-no-buildtime-cert-gen.patch
new file mode 100644
index 0000000..aa3be66
--- /dev/null
+++ b/freeradius-no-buildtime-cert-gen.patch
@@ -0,0 +1,104 @@
+From e6f7c9d4c2af1cda7760ca8155166bb5d4d541d0 Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Wed, 8 May 2019 12:58:02 -0400
+Subject: [PATCH] Don't generate certificates in reproducible builds
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+---
+ Make.inc.in | 5 +++++
+ configure | 4 ++++
+ configure.ac | 3 +++
+ raddb/all.mk | 4 ++++
+ 4 files changed, 16 insertions(+)
+
+diff --git a/Make.inc.in b/Make.inc.in
+index 0b2cd74de8..8c623cf95c 100644
+--- a/Make.inc.in
++++ b/Make.inc.in
+@@ -173,3 +173,8 @@ else
+ TESTBINDIR = ./$(BUILD_DIR)/bin
+ TESTBIN = ./$(BUILD_DIR)/bin
+ endif
++
++#
++# With reproducible builds, do not generate certificates during installation
++#
++ENABLE_REPRODUCIBLE_BUILDS = @ENABLE_REPRODUCIBLE_BUILDS@
+diff --git a/configure b/configure
+index c2c599c92b..3d4403a844 100755
+--- a/configure
++++ b/configure
+@@ -655,6 +655,7 @@ RUSERS
+ SNMPWALK
+ SNMPGET
+ PERL
++ENABLE_REPRODUCIBLE_BUILDS
+ openssl_version_check_config
+ WITH_DHCP
+ modconfdir
+@@ -5586,6 +5587,7 @@ else
+ fi
+
+
++ENABLE_REPRODUCIBLE_BUILDS=yes
+ # Check whether --enable-reproducible-builds was given.
+ if test "${enable_reproducible_builds+set}" = set; then :
+ enableval=$enable_reproducible_builds; case "$enableval" in
+@@ -5597,6 +5599,7 @@ $as_echo "#define ENABLE_REPRODUCIBLE_BUILDS 1" >>confdefs.h
+ ;;
+ *)
+ reproducible_builds=no
++ ENABLE_REPRODUCIBLE_BUILDS=no
+ esac
+
+ fi
+@@ -5604,6 +5607,7 @@ fi
+
+
+
++
+ CHECKRAD=checkrad
+ # Extract the first word of "perl", so it can be a program name with args.
+ set dummy perl; ac_word=$2
+diff --git a/configure.ac b/configure.ac
+index a7abf0025a..35b013f4af 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -619,6 +619,7 @@ AC_SUBST([openssl_version_check_config])
+ dnl #
+ dnl # extra argument: --enable-reproducible-builds
+ dnl #
++ENABLE_REPRODUCIBLE_BUILDS=yes
+ AC_ARG_ENABLE(reproducible-builds,
+ [AS_HELP_STRING([--enable-reproducible-builds],
+ [ensure the build does not change each time])],
+@@ -630,8 +631,10 @@ AC_ARG_ENABLE(reproducible-builds,
+ ;;
+ *)
+ reproducible_builds=no
++ ENABLE_REPRODUCIBLE_BUILDS=no
+ esac ]
+ )
++AC_SUBST(ENABLE_REPRODUCIBLE_BUILDS)
+
+
+ dnl #############################################################
+diff --git a/raddb/all.mk b/raddb/all.mk
+index c966edd657..c8e976a499 100644
+--- a/raddb/all.mk
++++ b/raddb/all.mk
+@@ -124,7 +124,11 @@ $(R)$(raddbdir)/users: $(R)$(modconfdir)/files/authorize
+ ifneq "$(LOCAL_CERT_PRODUCTS)" ""
+ $(LOCAL_CERT_PRODUCTS):
+ @echo BOOTSTRAP raddb/certs/
++ifeq "$(ENABLE_REPRODUCIBLE_BUILDS)" "yes"
++ @$(MAKE) -C $(R)$(raddbdir)/certs/ passwords.mk
++else
+ @$(MAKE) -C $(R)$(raddbdir)/certs/
++endif
+
+ # Bootstrap is special
+ $(R)$(raddbdir)/certs/bootstrap: | raddb/certs/bootstrap $(LOCAL_CERT_PRODUCTS)
+--
+2.21.0
+
diff --git a/freeradius-pam-conf b/freeradius-pam-conf
new file mode 100644
index 0000000..090c4a5
--- /dev/null
+++ b/freeradius-pam-conf
@@ -0,0 +1,6 @@
+#%PAM-1.0
+auth include password-auth
+account required pam_nologin.so
+account include password-auth
+password include password-auth
+session include password-auth
diff --git a/freeradius-tmpfiles.conf b/freeradius-tmpfiles.conf
new file mode 100644
index 0000000..bccd09d
--- /dev/null
+++ b/freeradius-tmpfiles.conf
@@ -0,0 +1,2 @@
+D /run/radiusd 0710 radiusd radiusd -
+D /run/radiusd/tmp 0700 radiusd radiusd -
diff --git a/freeradius.spec b/freeradius.spec
new file mode 100644
index 0000000..e88d7f2
--- /dev/null
+++ b/freeradius.spec
@@ -0,0 +1,2617 @@
+Summary: High-performance and highly configurable free RADIUS server
+Name: freeradius
+Version: 3.0.21
+Release: 42%{?dist}
+License: GPLv2+ and LGPLv2+
+URL: http://www.freeradius.org/
+
+# Is elliptic curve cryptography supported?
+%if 0%{?rhel} >= 7 || 0%{?fedora} >= 20
+%global HAVE_EC_CRYPTO 1
+%else
+%global HAVE_EC_CRYPTO 0
+%endif
+
+%global dist_base freeradius-server-%{version}
+
+Source0: ftp://ftp.freeradius.org/pub/radius/%{dist_base}.tar.bz2
+Source100: radiusd.service
+Source102: freeradius-logrotate
+Source103: freeradius-pam-conf
+Source104: freeradius-tmpfiles.conf
+Source105: freeradius.sysusers
+
+Patch1: freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch
+Patch2: freeradius-Use-system-crypto-policy-by-default.patch
+Patch3: freeradius-bootstrap-create-only.patch
+Patch4: freeradius-no-buildtime-cert-gen.patch
+Patch5: freeradius-bootstrap-make-permissions.patch
+Patch6: freeradius-Fix-resource-hard-limit-error.patch
+Patch7: freeradius-ldap-infinite-timeout-on-starttls.patch
+Patch8: freeradius-Backport-OpenSSL3-fixes.patch
+Patch9: freeradius-bootstrap-pass-noenc-to-certificate-generation.patch
+Patch10: freeradius-fix-crash-unknown-eap-sim.patch
+Patch11: freeradius-fix-crash-on-invalid-abinary-data.patch
+Patch12: freeradius-fix-python3-library-suffix.patch
+
+%global docdir %{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}}
+
+BuildRequires: autoconf
+BuildRequires: make
+BuildRequires: gcc
+BuildRequires: gdbm-devel
+BuildRequires: openssl
+BuildRequires: openssl-devel
+BuildRequires: pam-devel
+BuildRequires: zlib-devel
+BuildRequires: net-snmp-devel
+BuildRequires: net-snmp-utils
+BuildRequires: readline-devel
+BuildRequires: libpcap-devel
+BuildRequires: systemd-units
+BuildRequires: libtalloc-devel
+BuildRequires: pcre-devel
+BuildRequires: chrpath
+BuildRequires: systemd-rpm-macros
+
+%if ! 0%{?rhel}
+BuildRequires: libyubikey-devel
+BuildRequires: ykclient-devel
+%endif
+
+# Require OpenSSL version we built with, or newer, to avoid startup failures
+# due to runtime OpenSSL version checks.
+Requires: openssl >= %(rpm -q --queryformat '%%{VERSION}' openssl)
+Requires: openssl-perl
+Requires(pre): shadow-utils glibc-common
+Requires(post): systemd-sysv
+Requires(post): systemd-units
+# Needed for certificate generation as upstream bootstrap script isn't
+# compatible with Makefile equivalent.
+Requires: make
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+
+%description
+The FreeRADIUS Server Project is a high performance and highly configurable
+GPL'd free RADIUS server. The server is similar in some respects to
+Livingston's 2.0 server. While FreeRADIUS started as a variant of the
+Cistron RADIUS server, they don't share a lot in common any more. It now has
+many more features than Cistron or Livingston, and is much more configurable.
+
+FreeRADIUS is an Internet authentication daemon, which implements the RADIUS
+protocol, as defined in RFC 2865 (and others). It allows Network Access
+Servers (NAS boxes) to perform authentication for dial-up users. There are
+also RADIUS clients available for Web servers, firewalls, Unix logins, and
+more. Using RADIUS allows authentication and authorization for a network to
+be centralized, and minimizes the amount of re-configuration which has to be
+done when adding or deleting new users.
+
+%package doc
+Summary: FreeRADIUS documentation
+
+%description doc
+All documentation supplied by the FreeRADIUS project is included
+in this package.
+
+%package utils
+Summary: FreeRADIUS utilities
+Requires: %{name} = %{version}-%{release}
+Requires: libpcap >= 0.9.4
+
+%description utils
+The FreeRADIUS server has a number of features found in other servers,
+and additional features not found in any other server. Rather than
+doing a feature by feature comparison, we will simply list the features
+of the server, and let you decide if they satisfy your needs.
+
+Support for RFC and VSA Attributes Additional server configuration
+attributes Selecting a particular configuration Authentication methods
+
+%package devel
+Summary: FreeRADIUS development files
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+Development headers and libraries for FreeRADIUS.
+
+%package ldap
+Summary: LDAP support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: openldap-devel
+
+%description ldap
+This plugin provides the LDAP support for the FreeRADIUS server project.
+
+%package krb5
+Summary: Kerberos 5 support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: krb5-devel
+
+%description krb5
+This plugin provides the Kerberos 5 support for the FreeRADIUS server project.
+
+%package perl
+Summary: Perl support for freeradius
+Requires: %{name} = %{version}-%{release}
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+%{?fedora:BuildRequires: perl-devel}
+BuildRequires: perl-devel
+BuildRequires: perl-generators
+BuildRequires: perl(ExtUtils::Embed)
+
+%description perl
+This plugin provides the Perl support for the FreeRADIUS server project.
+
+%if 0%{?fedora} <= 30 && 0%{?rhel} < 8
+%package -n python2-freeradius
+Summary: Python 2 support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: python2-devel
+%{?python_provide:%python_provide python2-freeradius}
+# Remove before F30
+Provides: %{name}-python = %{version}-%{release}
+Provides: %{name}-python%{?_isa} = %{version}-%{release}
+Obsoletes: %{name}-python < %{version}-%{release}
+
+%description -n python2-freeradius
+This plugin provides the Python 2 support for the FreeRADIUS server project.
+%endif
+
+%package -n python3-freeradius
+Summary: Python 3 support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: python3-devel
+%{?python_provide:%python_provide python3-freeradius}
+
+%description -n python3-freeradius
+This plugin provides the Python 3 support for the FreeRADIUS server project.
+
+%package mysql
+Summary: MySQL support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: mariadb-connector-c-devel
+
+%description mysql
+This plugin provides the MySQL support for the FreeRADIUS server project.
+
+%package postgresql
+Summary: Postgresql support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: libpq-devel
+
+%description postgresql
+This plugin provides the postgresql support for the FreeRADIUS server project.
+
+%package sqlite
+Summary: SQLite support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: sqlite-devel
+
+%description sqlite
+This plugin provides the SQLite support for the FreeRADIUS server project.
+
+%package unixODBC
+Summary: Unix ODBC support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: unixODBC-devel
+
+%description unixODBC
+This plugin provides the unixODBC support for the FreeRADIUS server project.
+
+%package rest
+Summary: REST support for freeradius
+Requires: %{name} = %{version}-%{release}
+BuildRequires: libcurl-devel
+BuildRequires: json-c-devel
+
+%description rest
+This plugin provides the REST support for the FreeRADIUS server project.
+
+%prep
+%setup -q -n %{dist_base}
+# Note: We explicitly do not make patch backup files because 'make install'
+# mistakenly includes the backup files, especially problematic for raddb config files.
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
+%patch10 -p1
+%patch11 -p1
+%patch12 -p1
+
+%build
+# Force compile/link options, extra security for network facing daemon
+%global _hardened_build 1
+
+# Hack: rlm_python3 as stable; prevents building other unstable modules.
+sed 's/rlm_python/rlm_python3/g' src/modules/stable -i
+
+%global build_ldflags %{build_ldflags} $(python3-config --embed --libs)
+export PY3_LIB_DIR="$(python3-config --configdir)"
+export PY3_INC_DIR="$(python3 -c 'import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))')"
+
+# Enable FIPS support
+%global build_cflags %{build_cflags} -DWITH_FIPS
+
+# In order for the above hack to stick, do a fake configure so
+# we can run reconfig before cleaning up after ourselves and running
+# configure for real.
+./configure && make reconfig && (make clean distclean || true)
+
+%configure \
+ --libdir=%{_libdir}/freeradius \
+ --enable-reproducible-builds \
+ --disable-openssl-version-check \
+ --with-openssl \
+ --with-udpfromto \
+ --with-threads \
+ --with-docdir=%{docdir} \
+ --with-rlm-sql_postgresql-include-dir=/usr/include/pgsql \
+ --with-rlm-sql-postgresql-lib-dir=%{_libdir} \
+ --with-rlm-sql_mysql-include-dir=/usr/include/mysql \
+ --with-mysql-lib-dir=%{_libdir}/mariadb \
+ --with-unixodbc-lib-dir=%{_libdir} \
+ --with-rlm-dbm-lib-dir=%{_libdir} \
+ --with-rlm-krb5-include-dir=/usr/kerberos/include \
+ --with-rlm_python3 \
+ --with-rlm-python3-lib-dir=$PY3_LIB_DIR \
+ --with-rlm-python3-include-dir=$PY3_INC_DIR \
+ --without-rlm_eap_ikev2 \
+ --without-rlm_eap_tnc \
+ --without-rlm_sql_iodbc \
+ --without-rlm_sql_firebird \
+ --without-rlm_sql_db2 \
+ --without-rlm_sql_oracle \
+ --without-rlm_unbound \
+ --without-rlm_redis \
+ --without-rlm_rediswho \
+ --without-rlm_cache_memcached
+
+# Build fast, but get better errors if we fail
+make %{?_smp_mflags} || make -j1
+
+%install
+mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/radiusd
+make install R=$RPM_BUILD_ROOT
+
+# logs
+mkdir -p $RPM_BUILD_ROOT/var/log/radius/radacct
+touch $RPM_BUILD_ROOT/var/log/radius/{radutmp,radius.log}
+
+install -D -m 644 %{SOURCE100} $RPM_BUILD_ROOT/%{_unitdir}/radiusd.service
+install -D -m 644 %{SOURCE102} $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/radiusd
+install -D -m 644 %{SOURCE103} $RPM_BUILD_ROOT/%{_sysconfdir}/pam.d/radiusd
+
+mkdir -p %{buildroot}%{_tmpfilesdir}
+mkdir -p %{buildroot}%{_localstatedir}/run/
+install -d -m 0710 %{buildroot}%{_localstatedir}/run/radiusd/
+install -d -m 0700 %{buildroot}%{_localstatedir}/run/radiusd/tmp
+install -m 0644 %{SOURCE104} %{buildroot}%{_tmpfilesdir}/radiusd.conf
+install -p -D -m 0644 %{SOURCE105} %{buildroot}%{_sysusersdir}/freeradius.conf
+
+# install SNMP MIB files
+mkdir -p $RPM_BUILD_ROOT%{_datadir}/snmp/mibs/
+install -m 644 mibs/*RADIUS*.mib $RPM_BUILD_ROOT%{_datadir}/snmp/mibs/
+
+# remove rpath where needed
+chrpath --delete $RPM_BUILD_ROOT%{_libdir}/freeradius/*.so
+for f in $RPM_BUILD_ROOT/usr/sbin/*; do chrpath --delete $f || true; done
+for f in $RPM_BUILD_ROOT/usr/bin/*; do chrpath --delete $f || true; done
+
+# update ld with freeradius libs
+mkdir -p %{buildroot}/%{_sysconfdir}/ld.so.conf.d
+echo "%{_libdir}/freeradius" > %{buildroot}/%{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf
+
+# remove unneeded stuff
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.crt
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.crl
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.csr
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.der
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.key
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.pem
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.p12
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/index.*
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/serial*
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/dh
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/random
+
+rm -f $RPM_BUILD_ROOT/usr/sbin/rc.radiusd
+rm -f $RPM_BUILD_ROOT/usr/bin/rbmonkey
+rm -rf $RPM_BUILD_ROOT/%{_libdir}/freeradius/*.a
+rm -rf $RPM_BUILD_ROOT/%{_libdir}/freeradius/*.la
+
+rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/main/mssql
+
+rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool/oracle
+rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool/mssql
+rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool-dhcp/oracle
+rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/main/oracle
+rm -r $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/moonshot-targeted-ids
+
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/unbound
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/unbound/default.conf
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/couchbase
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/abfab*
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/moonshot-targeted-ids
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/policy.d/abfab*
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/policy.d/moonshot-targeted-ids
+rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/sites-available/abfab*
+
+rm $RPM_BUILD_ROOT/%{_libdir}/freeradius/rlm_test.so
+
+# remove unsupported config files
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/experimental.conf
+
+# Mongo will never be supported on Fedora or RHEL
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/sql/ippool/mongo/queries.conf
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/sql/main/mongo/queries.conf
+
+# install doc files omitted by standard install
+for f in COPYRIGHT CREDITS INSTALL.rst README.rst VERSION; do
+ cp $f $RPM_BUILD_ROOT/%{docdir}
+done
+cp LICENSE $RPM_BUILD_ROOT/%{docdir}/LICENSE.gpl
+cp src/lib/LICENSE $RPM_BUILD_ROOT/%{docdir}/LICENSE.lgpl
+cp src/LICENSE.openssl $RPM_BUILD_ROOT/%{docdir}/LICENSE.openssl
+
+# add Red Hat specific documentation
+cat >> $RPM_BUILD_ROOT/%{docdir}/REDHAT << EOF
+
+Red Hat, RHEL, Fedora, and CentOS specific information can be found on the
+FreeRADIUS Wiki in the Red Hat FAQ.
+
+http://wiki.freeradius.org/guide/Red-Hat-FAQ
+
+Please reference that document.
+
+All documentation is in the freeradius-doc sub-package.
+
+EOF
+
+
+# Make sure our user/group is present prior to any package or subpackage installation
+%pre
+%sysusers_create_compat %{SOURCE105}
+
+%preun
+%systemd_preun radiusd.service
+
+%postun
+%systemd_postun_with_restart radiusd.service
+
+/bin/systemctl try-restart radiusd.service >/dev/null 2>&1 || :
+
+
+%files
+
+# doc
+%license %{docdir}/LICENSE.gpl
+%license %{docdir}/LICENSE.lgpl
+%license %{docdir}/LICENSE.openssl
+%doc %{docdir}/REDHAT
+
+# system
+%config(noreplace) %{_sysconfdir}/pam.d/radiusd
+%config(noreplace) %{_sysconfdir}/logrotate.d/radiusd
+%config(noreplace) %{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf
+%{_unitdir}/radiusd.service
+%{_tmpfilesdir}/radiusd.conf
+%{_sysusersdir}/freeradius.conf
+%dir %attr(710,radiusd,radiusd) %{_localstatedir}/run/radiusd
+%dir %attr(700,radiusd,radiusd) %{_localstatedir}/run/radiusd/tmp
+%dir %attr(755,radiusd,radiusd) %{_localstatedir}/lib/radiusd
+
+# configs (raddb)
+%dir %attr(755,root,radiusd) /etc/raddb
+%defattr(-,root,radiusd)
+/etc/raddb/README.rst
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/panic.gdb
+
+%attr(644,root,radiusd) %config(noreplace) /etc/raddb/dictionary
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/clients.conf
+
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/templates.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/trigger.conf
+
+# symlink: /etc/raddb/hints -> ./mods-config/preprocess/hints
+%config /etc/raddb/hints
+
+# symlink: /etc/raddb/huntgroups -> ./mods-config/preprocess/huntgroups
+%config /etc/raddb/huntgroups
+
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/proxy.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/radiusd.conf
+
+# symlink: /etc/raddb/users -> ./mods-config/files/authorize
+%config(noreplace) /etc/raddb/users
+
+# certs
+%dir %attr(770,root,radiusd) /etc/raddb/certs
+%config(noreplace) /etc/raddb/certs/Makefile
+%config(noreplace) /etc/raddb/certs/passwords.mk
+/etc/raddb/certs/README
+%config(noreplace) /etc/raddb/certs/xpextensions
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/certs/*.cnf
+%attr(750,root,radiusd) /etc/raddb/certs/bootstrap
+
+# mods-config
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config
+/etc/raddb/mods-config/README.rst
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/attr_filter
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/attr_filter/*
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/files
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/files/*
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/preprocess
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/preprocess/*
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main
+
+# sites-available
+%dir %attr(750,root,radiusd) /etc/raddb/sites-available
+/etc/raddb/sites-available/README
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/control-socket
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/decoupled-accounting
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/robust-proxy-accounting
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/soh
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/coa
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/coa-relay
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/example
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/inner-tunnel
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dhcp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/check-eap-tls
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/status
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dhcp.relay
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/virtual.example.com
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/originate-coa
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/vmps
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/default
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/proxy-inner-tunnel
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dynamic-clients
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/copy-acct-to-home-server
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/buffered-sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/tls
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/channel_bindings
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/challenge
+
+# sites-enabled
+# symlink: /etc/raddb/sites-enabled/xxx -> ../sites-available/xxx
+%dir %attr(750,root,radiusd) /etc/raddb/sites-enabled
+%config(missingok) /etc/raddb/sites-enabled/inner-tunnel
+%config(missingok) /etc/raddb/sites-enabled/default
+
+# mods-available
+%dir %attr(750,root,radiusd) /etc/raddb/mods-available
+/etc/raddb/mods-available/README.rst
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/always
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/attr_filter
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache_eap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/chap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/counter
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cui
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/date
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail.example.com
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail.log
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp_sqlippool
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/digest
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dynamic_clients
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/eap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/echo
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/etc_group
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/exec
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/expiration
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/expr
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/files
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/idn
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/inner-eap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ippool
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/linelog
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/logintime
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mac2ip
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mac2vlan
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mschap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ntlm_auth
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/opendirectory
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/otp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/pam
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/pap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/passwd
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/preprocess
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/python
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/python3
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/radutmp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/realm
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/redis
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rediswho
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/replicate
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/smbpasswd
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/smsotp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/soh
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sometimes
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sqlcounter
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sqlippool
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sradutmp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/unix
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/unpack
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/utf8
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/wimax
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/yubikey
+
+# mods-enabled
+# symlink: /etc/raddb/mods-enabled/xxx -> ../mods-available/xxx
+%dir %attr(750,root,radiusd) /etc/raddb/mods-enabled
+%config(missingok) /etc/raddb/mods-enabled/always
+%config(missingok) /etc/raddb/mods-enabled/attr_filter
+%config(missingok) /etc/raddb/mods-enabled/cache_eap
+%config(missingok) /etc/raddb/mods-enabled/chap
+%config(missingok) /etc/raddb/mods-enabled/date
+%config(missingok) /etc/raddb/mods-enabled/detail
+%config(missingok) /etc/raddb/mods-enabled/detail.log
+%config(missingok) /etc/raddb/mods-enabled/digest
+%config(missingok) /etc/raddb/mods-enabled/dynamic_clients
+%config(missingok) /etc/raddb/mods-enabled/eap
+%config(missingok) /etc/raddb/mods-enabled/echo
+%config(missingok) /etc/raddb/mods-enabled/exec
+%config(missingok) /etc/raddb/mods-enabled/expiration
+%config(missingok) /etc/raddb/mods-enabled/expr
+%config(missingok) /etc/raddb/mods-enabled/files
+%config(missingok) /etc/raddb/mods-enabled/linelog
+%config(missingok) /etc/raddb/mods-enabled/logintime
+%config(missingok) /etc/raddb/mods-enabled/mschap
+%config(missingok) /etc/raddb/mods-enabled/ntlm_auth
+%config(missingok) /etc/raddb/mods-enabled/pap
+%config(missingok) /etc/raddb/mods-enabled/passwd
+%config(missingok) /etc/raddb/mods-enabled/preprocess
+%config(missingok) /etc/raddb/mods-enabled/radutmp
+%config(missingok) /etc/raddb/mods-enabled/realm
+%config(missingok) /etc/raddb/mods-enabled/replicate
+%config(missingok) /etc/raddb/mods-enabled/soh
+%config(missingok) /etc/raddb/mods-enabled/sradutmp
+%config(missingok) /etc/raddb/mods-enabled/unix
+%config(missingok) /etc/raddb/mods-enabled/unpack
+%config(missingok) /etc/raddb/mods-enabled/utf8
+
+# policy
+%dir %attr(750,root,radiusd) /etc/raddb/policy.d
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/accounting
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/canonicalization
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/control
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/cui
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/debug
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/dhcp
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/eap
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/filter
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/operator-name
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/rfc7542
+
+
+# binaries
+%defattr(-,root,root)
+/usr/sbin/checkrad
+/usr/sbin/raddebug
+/usr/sbin/radiusd
+/usr/sbin/radmin
+
+# dictionaries
+%dir %attr(755,root,root) /usr/share/freeradius
+/usr/share/freeradius/*
+
+# logs
+%dir %attr(700,radiusd,radiusd) /var/log/radius/
+%dir %attr(700,radiusd,radiusd) /var/log/radius/radacct/
+%ghost %attr(644,radiusd,radiusd) /var/log/radius/radutmp
+%ghost %attr(600,radiusd,radiusd) /var/log/radius/radius.log
+
+# libs
+%attr(755,root,root) %{_libdir}/freeradius/lib*.so*
+
+# loadable modules
+%dir %attr(755,root,root) %{_libdir}/freeradius
+%{_libdir}/freeradius/proto_dhcp.so
+%{_libdir}/freeradius/proto_vmps.so
+%{_libdir}/freeradius/rlm_always.so
+%{_libdir}/freeradius/rlm_attr_filter.so
+%{_libdir}/freeradius/rlm_cache.so
+%{_libdir}/freeradius/rlm_cache_rbtree.so
+%{_libdir}/freeradius/rlm_chap.so
+%{_libdir}/freeradius/rlm_counter.so
+%{_libdir}/freeradius/rlm_cram.so
+%{_libdir}/freeradius/rlm_date.so
+%{_libdir}/freeradius/rlm_detail.so
+%{_libdir}/freeradius/rlm_dhcp.so
+%{_libdir}/freeradius/rlm_digest.so
+%{_libdir}/freeradius/rlm_dynamic_clients.so
+%{_libdir}/freeradius/rlm_eap.so
+%{_libdir}/freeradius/rlm_eap_fast.so
+%{_libdir}/freeradius/rlm_eap_gtc.so
+%{_libdir}/freeradius/rlm_eap_leap.so
+%{_libdir}/freeradius/rlm_eap_md5.so
+%{_libdir}/freeradius/rlm_eap_mschapv2.so
+%{_libdir}/freeradius/rlm_eap_peap.so
+%if %{HAVE_EC_CRYPTO}
+%{_libdir}/freeradius/rlm_eap_pwd.so
+%endif
+%{_libdir}/freeradius/rlm_eap_sim.so
+%{_libdir}/freeradius/rlm_eap_tls.so
+%{_libdir}/freeradius/rlm_eap_ttls.so
+%{_libdir}/freeradius/rlm_exec.so
+%{_libdir}/freeradius/rlm_expiration.so
+%{_libdir}/freeradius/rlm_expr.so
+%{_libdir}/freeradius/rlm_files.so
+%{_libdir}/freeradius/rlm_ippool.so
+%{_libdir}/freeradius/rlm_linelog.so
+%{_libdir}/freeradius/rlm_logintime.so
+%{_libdir}/freeradius/rlm_mschap.so
+%{_libdir}/freeradius/rlm_otp.so
+%{_libdir}/freeradius/rlm_pam.so
+%{_libdir}/freeradius/rlm_pap.so
+%{_libdir}/freeradius/rlm_passwd.so
+%{_libdir}/freeradius/rlm_preprocess.so
+%{_libdir}/freeradius/rlm_radutmp.so
+%{_libdir}/freeradius/rlm_realm.so
+%{_libdir}/freeradius/rlm_replicate.so
+%{_libdir}/freeradius/rlm_soh.so
+%{_libdir}/freeradius/rlm_sometimes.so
+%{_libdir}/freeradius/rlm_sql.so
+%{_libdir}/freeradius/rlm_sqlcounter.so
+%{_libdir}/freeradius/rlm_sqlippool.so
+%{_libdir}/freeradius/rlm_sql_null.so
+%{_libdir}/freeradius/rlm_unix.so
+%{_libdir}/freeradius/rlm_unpack.so
+%{_libdir}/freeradius/rlm_utf8.so
+%{_libdir}/freeradius/rlm_wimax.so
+%{_libdir}/freeradius/rlm_yubikey.so
+
+# main man pages
+%doc %{_mandir}/man5/clients.conf.5.gz
+%doc %{_mandir}/man5/dictionary.5.gz
+%doc %{_mandir}/man5/radiusd.conf.5.gz
+%doc %{_mandir}/man5/radrelay.conf.5.gz
+%doc %{_mandir}/man5/rlm_always.5.gz
+%doc %{_mandir}/man5/rlm_attr_filter.5.gz
+%doc %{_mandir}/man5/rlm_chap.5.gz
+%doc %{_mandir}/man5/rlm_counter.5.gz
+%doc %{_mandir}/man5/rlm_detail.5.gz
+%doc %{_mandir}/man5/rlm_digest.5.gz
+%doc %{_mandir}/man5/rlm_expr.5.gz
+%doc %{_mandir}/man5/rlm_files.5.gz
+%doc %{_mandir}/man5/rlm_idn.5.gz
+%doc %{_mandir}/man5/rlm_mschap.5.gz
+%doc %{_mandir}/man5/rlm_pap.5.gz
+%doc %{_mandir}/man5/rlm_passwd.5.gz
+%doc %{_mandir}/man5/rlm_realm.5.gz
+%doc %{_mandir}/man5/rlm_sql.5.gz
+%doc %{_mandir}/man5/rlm_unix.5.gz
+%doc %{_mandir}/man5/unlang.5.gz
+%doc %{_mandir}/man5/users.5.gz
+%doc %{_mandir}/man8/raddebug.8.gz
+%doc %{_mandir}/man8/radiusd.8.gz
+%doc %{_mandir}/man8/radmin.8.gz
+%doc %{_mandir}/man8/radrelay.8.gz
+
+# MIB files
+%{_datadir}/snmp/mibs/*RADIUS*.mib
+
+%files doc
+
+%doc %{docdir}/
+
+
+%files utils
+/usr/bin/*
+
+# utils man pages
+%doc %{_mandir}/man1/radclient.1.gz
+%doc %{_mandir}/man1/radeapclient.1.gz
+%doc %{_mandir}/man1/radlast.1.gz
+%doc %{_mandir}/man1/radtest.1.gz
+%doc %{_mandir}/man1/radwho.1.gz
+%doc %{_mandir}/man1/radzap.1.gz
+%doc %{_mandir}/man1/rad_counter.1.gz
+%doc %{_mandir}/man1/smbencrypt.1.gz
+%doc %{_mandir}/man1/dhcpclient.1.gz
+%doc %{_mandir}/man5/checkrad.5.gz
+%doc %{_mandir}/man8/radcrypt.8.gz
+%doc %{_mandir}/man8/radsniff.8.gz
+%doc %{_mandir}/man8/radsqlrelay.8.gz
+%doc %{_mandir}/man8/rlm_ippool_tool.8.gz
+
+%files devel
+/usr/include/freeradius
+
+%files krb5
+%{_libdir}/freeradius/rlm_krb5.so
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/krb5
+
+%files perl
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/perl
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/perl
+%attr(640,root,radiusd) /etc/raddb/mods-config/perl/example.pl
+
+%{_libdir}/freeradius/rlm_perl.so
+
+%if 0%{?fedora} <= 30 && 0%{?rhel} < 8
+%files -n python2-freeradius
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/python
+/etc/raddb/mods-config/python/example.py*
+/etc/raddb/mods-config/python/radiusd.py*
+%{_libdir}/freeradius/rlm_python.so
+%endif
+
+%files -n python3-freeradius
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/python3
+/etc/raddb/mods-config/python3/example.py*
+/etc/raddb/mods-config/python3/radiusd.py*
+%{_libdir}/freeradius/rlm_python3.so
+
+%files mysql
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/mysql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/dailycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/expire_on_login.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/monthlycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/noresetcounter.conf
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/mysql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/mysql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/schema.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/procedure.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/mysql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/setup.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/schema.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/process-radacct.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql/extras
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql/extras/wimax
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/extras/wimax/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/extras/wimax/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/ndb
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/ndb/setup.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/ndb/schema.sql
+/etc/raddb/mods-config/sql/main/ndb/README
+
+%{_libdir}/freeradius/rlm_sql_mysql.so
+
+%files postgresql
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/postgresql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/dailycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/postgresql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/postgresql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/postgresql/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/postgresql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/schema.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/procedure.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/postgresql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/setup.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/schema.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/process-radacct.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/postgresql/extras
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/extras/voip-postpaid.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/extras/cisco_h323_db_schema.sql
+
+%{_libdir}/freeradius/rlm_sql_postgresql.so
+
+%files sqlite
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/sqlite
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/dailycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/sqlite
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/sqlite/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/sqlite/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/sqlite
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/sqlite/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/sqlite/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql
+
+%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/sqlite
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/queries.conf
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/schema.sql
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/process-radacct-refresh.sh
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/process-radacct-schema.sql
+
+%{_libdir}/freeradius/rlm_sql_sqlite.so
+
+%files ldap
+%{_libdir}/freeradius/rlm_ldap.so
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ldap
+
+%files unixODBC
+%{_libdir}/freeradius/rlm_sql_unixodbc.so
+
+%files rest
+%{_libdir}/freeradius/rlm_rest.so
+%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest
+
+%changelog
+* Wed Jul 10 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-42
+- Backport fixes for BlastRADIUS CVE
+ Resolves: RHEL-46567
+
+* Wed Apr 24 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-41
+- Rebuild for OpenSSL rebase to 3.2.1
+ Resolves: RHEL-33857
+
+* Mon Apr 01 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-40
+- Comment out unneeded options from mods-available/eap
+ Resolves: RHEL-30830
+
+* Mon Nov 06 2023 Antonio Torres <antorres@redhat.com> - 3.0.21-39
+- Fix Python3.8+ library name suffix
+ Resolves: #15503
+
+* Mon May 22 2023 Antonio Torres <antorres@redhat.com> - 3.0.21-38
+- Fix crash when verifying client certificate
+ Resolves: #2183447
+
+* Wed Dec 14 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-37
+- Fix defect found by covscan
+ Resolves: #2151705
+
+* Fri Dec 09 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-36
+- Fix multiple CVEs
+ Resolves: #2151705
+ Resolves: #2151703
+ Resolves: #2151707
+
+* Fri Sep 16 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-35
+- Rebuild to add subpackages to CRB report
+ Resolves: #2126380
+
+* Wed Jun 29 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-34
+- Use GID / UID 95 as it's reserved for FreeRADIUS (https://pagure.io/setup/blob/07f8debf03dfb0e5ed36051c13c86c8cd00cd241/f/uidgid#_107)
+ Resolves: #2095403
+
+* Fri Jun 24 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-33
+- Dynamically allocate users using sysusers.d format
+ Resolves: #2095403
+
+* Mon May 30 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-32
+- Add WITH_FIPS macro to CFLAGS
+ Related: rhbz#2083699
+
+* Tue May 24 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-31
+- Update OpenSSL 3.0 support backport to current v3.0.x branch state
+- Add "--enable-fips-workaround" to build options
+ Related: rhbz#2083699
+
+* Tue May 10 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-30
+- Add openssl-perl dependency
+ Related: rhbz#2078816
+
+* Thu Apr 28 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-29
+- Set correct permissions for certificates generated by bootstrap Makefile
+ Related: rhbz#2069224
+
+* Mon Apr 25 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-28
+- bootstrap: pass -noenc to certificate generation, do it on script as well
+ Related: rhbz#2069224
+
+* Fri Apr 22 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-27
+- bootstrap: pass -noenc to certificate generation
+ Related: rhbz#2069224
+
+* Mon Jan 31 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-26
+- Move remaining files from /var/run to /run
+ Related: rhbz#2047972
+
+* Fri Jan 28 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-25
+- Revert "Allow to connect to partially open LDAP handle"
+- Use infinite timeout (openldap default) when using LDAP+start-TLS
+- Update openssl dependency to not check epoch (was causing detection issues)
+ Related: rhbz#1992551
+
+* Thu Jan 13 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-24
+- Avoid segfault when trying to use MD4 without legacy provider
+ Related: rhbz#1978216
+
+* Wed Jan 12 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-23
+- Backport OpenSSL3 fixes
+ Related: rhbz#1978216
+
+* Wed Oct 13 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-22
+- Allow to connect to partially open LDAP handle
+ Related: rhbz#1992551
+
+* Mon Sep 27 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-21
+- Move FR's systemd unit PID file from /var/run to /run
+ Related: rhbz#2006368
+
+* Thu Aug 19 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-20
+- Rebuild to pick up new build flags from redhat-rpm-config
+ Related: rhbz#1984652
+
+* Thu Aug 12 2021 Filip Dvorak <fdvorak@redhat.com> - 3.0.21-19
+- Install psutil module and generate def. certs during test script
+ Resolves: rhbz#1990392
+
+* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-18
+- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
+ Related: rhbz#1991688
+
+* Tue Aug 03 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-17
+- Ignore badfuncs error in rpminspect
+ Resolves: bz#1986972
+
+* Mon Aug 02 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-16
+- Remove RPATH usage
+ Resolves: bz#1986968
+
+* Mon Jul 19 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-15
+- Fix coredump not being able to be enabled
+ Resolves: bz#1977722
+
+* Wed Jun 30 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-14
+- Fix Python3.8 not being linked correctly
+ Related: rhbz#1948622
+
+* Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-13
+- Rebuilt for RHEL 9 BETA for openssl 3.0
+ Related: rhbz#1971065
+
+* Thu Apr 15 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-12
+- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
+
+* Wed Mar 10 2021 Robbie Harwood <rharwood@redhat.com> - 3.0.21-11
+- Disable automatic bootstrap
+
+* Tue Mar 02 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.21-10
+- Rebuilt for updated systemd-rpm-macros
+ See https://pagure.io/fesco/issue/2583.
+
+* Mon Feb 08 2021 Pavel Raiskup <praiskup@redhat.com> - 3.0.21-9
+- rebuild for libpq ABI fix rhbz#1908268
+
+* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.21-8
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
+
+* Tue Aug 04 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-7
+- Fix certificate permissions after make-based generation
+ Resolves: bz#1835249
+
+* Tue Aug 04 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-6
+- Fix certificate permissions after make-based generation
+ Resolves: bz#1835249
+
+* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.21-5
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
+
+* Tue Jun 23 2020 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.21-4
+- Perl 5.32 rebuild
+
+* Wed May 13 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-3
+- Fix certificate generation
+ Resolves: bz#1835249
+
+* Tue Apr 21 2020 Björn Esser <besser82@fedoraproject.org> - 3.0.21-2
+- Rebuild (json-c)
+
+* Wed Apr 01 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-1
+- Rebased to 3.0.21
+ Resolves: bz#1816745
+
+* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.20-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
+
+* Sat Jan 11 2020 Paul Wouters <pwouters@redhat.com> - 3.0.20-2
+- fixup tmpfile to use /run instead of /var/run
+
+* Fri Nov 15 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.20-1
+- Rebased to 3.0.20
+ Resolves: bz#1772710
+- Introduced new rlm_python3 module
+
+* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.19-5
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
+
+* Fri May 31 2019 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.19-4
+- Perl 5.30 rebuild
+
+* Wed May 08 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-3
+- Update boostrap to change ownership of all certificates to root:radiusd
+
+* Wed May 08 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-2
+- Updated crypto-policies patch
+- Updated /etc/raddb/certs/bootstrap to only create certificates if missing: bz#1705165 bz#1672284
+- Updated logrotate definitions to run as radiusd:radiusd: bz#1705343
+- Drop python2 package on Fedora 31+
+- Add database dependencies: bz#1658697
+- Don't generate certificate during build
+
+* Wed Apr 10 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-1
+- Rebased to 3.0.19
+
+* Wed Mar 06 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.18-1
+- Rebased to 3.0.18
+
+* Sun Feb 17 2019 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.0.17-6
+- Rebuild for readline 8.0
+
+* Tue Feb 05 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.17-5
+- Unit file generates certificates if not present.
+ Resolves: bz#1672284
+
+* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.17-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
+
+* Mon Jan 14 2019 Björn Esser <besser82@fedoraproject.org> - 3.0.17-3
+- Rebuilt for libcrypt.so.2 (#1666033)
+
+* Fri Dec 14 2018 Alexander Scheel <ascheel@redhat.com> - 3.0.17-2
+- Updates radiusd.service to start after network-online.target
+ Resolves: bz#1637275
+
+* Thu Oct 18 2018 Alexander Scheel <ascheel@redhat.com> - 3.0.17-1
+- Update to FreeRADIUS server version 3.0.17
+- Adds OpenSSL HMAC patches from upstream (unreleased)
+- Adds Python2 shebang patches from upstream (unreleased)
+
+* Mon Sep 17 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-18
+- Actually apply patches added previously.
+ Related: Bug#1611286 Man page scan results for freeradius
+
+* Fri Sep 14 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-17
+- Fix a few minor manpage issues.
+ Resolves: Bug#1611286 Man page scan results for freeradius
+
+* Fri Sep 07 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-16
+- Add make to BuildRequires and Requires(post) to fix build and certificate
+ generation on install.
+ Resolves: Bug#1574783 Installing freeradius without make results in an
+ unworkable default configuration
+
+* Tue Sep 04 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-15
+- Add gcc to BuildRequires.
+ Resolves: Bug#1622470 FTBFS freeradius (rawhide)
+
+* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-14
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
+
+* Fri Jun 29 2018 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.15-13
+- Perl 5.28 rebuild
+
+* Tue Mar 06 2018 Björn Esser <besser82@fedoraproject.org> - 3.0.15-12
+- Rebuilt for libjson-c.so.4 (json-c v0.13.1)
+
+* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.0.15-11
+- Escape macros in %%changelog
+
+* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-10
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
+
+* Sat Jan 20 2018 Björn Esser <besser82@fedoraproject.org> - 3.0.15-9
+- Rebuilt for switch to libxcrypt
+
+* Fri Jan 05 2018 Iryna Shcherbina <ishcherb@redhat.com> - 3.0.15-8
+- Update Python 2 dependency declarations to new packaging standards
+ (See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3)
+
+* Sun Dec 10 2017 Björn Esser <besser82@fedoraproject.org> - 3.0.15-7
+- Rebuilt for libjson-c.so.3
+
+* Thu Oct 26 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-6
+- Use mariadb-connector-c-devel instead of mysql-devel or mariadb-devel
+ Resolves: Bug#1493904 Use mariadb-connector-c-devel instead of mysql-devel
+ or mariadb-devel
+
+* Sun Aug 20 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.15-5
+- Add Provides for the old name without %%_isa
+
+* Sat Aug 19 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.15-4
+- Python 2 binary package renamed to python2-freeradius
+ See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3
+
+* Wed Aug 02 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
+
+* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
+
+* Tue Jul 18 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-1
+- Upgrade to upstream v3.0.15 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+- Resolves: Bug#1471848 CVE-2017-10978 freeradius: Out-of-bounds read/write
+ due to improper output buffer size check in
+ make_secret()
+- Resolves: Bug#1471860 CVE-2017-10983 freeradius: Out-of-bounds read in
+ fr_dhcp_decode() when decoding option 63
+- Resolves: Bug#1471861 CVE-2017-10984 freeradius: Out-of-bounds write in
+ data2vp_wimax()
+- Resolves: Bug#1471863 CVE-2017-10985 freeradius: Infinite loop and memory
+ exhaustion with 'concat' attributes
+- Resolves: Bug#1471864 CVE-2017-10986 freeradius: Infinite read in
+ dhcp_attr2vp()
+- Resolves: Bug#1471865 CVE-2017-10987 freeradius: Buffer over-read in
+ fr_dhcp_decode_suboptions()
+- Resolves: Bug#1456220 freeradius-3.0.15 is available
+
+* Thu Jul 13 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.14-3
+- Rebuild with updated MySQL client library
+
+* Sun Jun 04 2017 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.14-2
+- Perl 5.26 rebuild
+
+* Tue May 30 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.14-1
+- Upgrade to upstream v3.0.14 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+- Fix TLS resumption authentication bypass (CVE-2017-9148)
+
+* Wed Mar 29 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-3
+- Explicitly disable rlm_cache_memcached to avoid error when the module's
+ dependencies are installed, and it is built, but not packaged.
+- Prevent segfaults by adding a missing handling of connection errors in
+ rlm_ldap.
+- Make radtest use Cleartext-Password for EAP, fixing its support for eap-md5.
+
+* Wed Mar 15 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-2
+- Fix permissions of default key files in raddb/certs.
+- Require OpenSSL version we built with, or newer, to avoid startup failures
+ due to runtime OpenSSL version checks.
+ Resolves: Bug#1299388
+- Fix some issues found with static analyzers.
+
+* Tue Mar 07 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-1
+- Upgrade to upstream v3.0.13 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+
+* Tue Feb 21 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-3
+- Do not fail logrotate if radiusd is not running.
+- Fix output to log file specified with -l option.
+- Fix long hostnames interpreted as IP addresses.
+- Avoid clashes with libtool library symbols.
+- Remove mentions of Auth-Type = System from docs.
+- Improve ip/v4/v6/addr documentation.
+
+* Mon Feb 20 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-2
+- Fix three cases of comparing pointers to zero characters
+- Support OpenSSL v1.1.0
+ Resolves: Bug#1385588
+
+* Fri Feb 17 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-1
+- Upgrade to upstream v3.0.12 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+
+* Fri Feb 17 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-7
+- Make sure FreeRADIUS starts after IPA, directory, and Kerberos servers
+- Don't rotate radutmp, as it's not a log file
+- Logrotate with "systemctl" instead of "service"
+- Remove executable bits from "radiusd.service"
+
+* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.11-6
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Mon Jan 16 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-5
+- Move tmpfiles.d config to %%{_tmpfilesdir}
+- Install license files as %%license
+
+* Thu Jan 12 2017 Igor Gnatenko <ignatenko@redhat.com> - 3.0.11-4
+- Rebuild for readline 7.x
+
+* Mon Sep 26 2016 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-3
+- Switch default configuration to use system's crypto policy.
+ Resolves: Bug#1179224
+
+* Tue May 17 2016 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.11-2
+- Perl 5.24 rebuild
+
+* Tue Apr 12 2016 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-1
+- Upgrade to upstream v3.0.10 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+
+* Wed Feb 03 2016 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.10-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
+
+* Wed Dec 09 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.10-1
+- Upgrade to upstream v3.0.10 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+ Related: Bug#1133959
+- Remove rlm_eap_tnc support as the required package "tncfhh" was retired.
+
+* Wed Aug 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.9-1
+- Upgrade to upstream v3.0.9 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+ Resolves: Bug#1133959
+
+* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.8-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
+
+* Fri Jun 05 2015 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.8-2
+- Perl 5.22 rebuild
+
+* Tue Apr 28 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.8-1
+- Upgrade to upstream v3.0.7 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+ Related: Bug#1133959
+
+* Thu Mar 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.7-1
+- Upgrade to upstream v3.0.7 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+ Resolves: Bug#1133959
+- Add freeradius-rest package containing rlm_rest module.
+ Resolves: Bug#1196276
+
+* Fri Feb 13 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-4
+- Bump release number to catch up with Fedora 21.
+
+* Mon Jan 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-3
+- Fix OpenSSL version parsing when checking for compatibility at run time.
+ Resolves: Bug#1173821
+- Don't remove backslash from unknown escape sequences in LDAP values.
+ Resolves: Bug#1173526
+- Improve dhcpclient and rad_counter online help.
+ Resolves: Bug#1146966
+- raddb: Move trigger.conf INCLUDE before modules, making it easier to refer
+ to trigger variables from module configurations.
+ Resolves: Bug#1155961
+- Fix ipaddr option fallback onto ipv6.
+ Resolves: Bug#1168868
+- raddb: Comment on ipaddr/ipv4addr/ipv6addr use.
+ Resolves: Bug#1168247
+- Disable rlm_rest building explicitly to avoid unintended builds on some
+ architectures breaking RPM build.
+ Resolves: Bug#1162156
+- Add -D option support to dhcpclient.
+ Related: Bug#1146939
+- Don't install rbmonkey - a test tool only useful to developers.
+ Related: Bug#1146966
+- Update clients(5) man page
+ Resolves: Bug#1147464
+- Fix possible group info corruption/segfault in rlm_unix' fr_getgrnam.
+- Fix file configuration item parsing.
+- Fix a number of trigger issues.
+ Resolves: Bug#1110407 radiusd doesn't send snmp trap after "radmin -e 'hup
+ files'"
+ Resolves: Bug#1110414 radiusd doesn't send snmp trap when sql connection is
+ opened,closed or fail
+ Resolves: Bug#1110186 radiusd doesn't send snmp trap when ldap connection
+ fails/opens/closes
+ Resolves: Bug#1109164 snmp trap messages send by radiusd are inconsistent
+ and incomplete
+- Fix two omissions from radtest manpage.
+ Resolves: Bug#1146898 'eap-md5' value is missing in -t option in SYNOPSIS
+ of radtest man page
+ Resolves: Bug#1114669 Missing -P option in radtest manpage
+- Disable OpenSSL version checking to avoid the need to edit radiusd.conf to
+ confirm heartbleed is fixed.
+ Resolves: Bug#1155070 FreeRADIUS doesn't start after upgrade due to failing
+ OpenSSL version check
+
+* Mon Oct 6 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-2
+- Fix abort on home server triggers.
+- Fix segfault upon example.pl read failure.
+- Fix example.pl permissions.
+- Fix integer handling in various cases.
+- Fix dhcpclient's dictionary.dhcp loading.
+
+* Mon Sep 15 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-1
+- Upgrade to upstream 3.0.4 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+- Resolves: Bug#1099620
+
+* Tue Sep 09 2014 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.4-0.2.rc2
+- Perl 5.20 mass
+
+* Mon Sep 8 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-0.1.rc2
+- Upgrade to upstream 3.0.4-rc2 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+
+* Tue Aug 26 2014 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.3-5
+- Perl 5.20 rebuild
+
+* Sat Aug 16 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.3-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
+
+* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.3-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
+
+* Mon Jun 2 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.3-2
+- Add explicit dependency on OpenSSL package with fixed CVE-2014-0160
+ (Heartbleed bug).
+- Add confirmation of CVE-2014-0160 being fixed in OpenSSL to radiusd.conf.
+
+* Wed May 14 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.3-1
+- Upgrade to upstream 3.0.3 release.
+ See upstream ChangeLog for details (in freeradius-doc subpackage).
+- Minor configuration parsing change: "Double-escaping of characters in Perl,
+ and octal characters has been fixed. If your configuration has text like
+ "\\000", you will need to remove one backslash."
+- Additionally includes post-release fixes for:
+ * case-insensitive matching in compiled regular expressions not working,
+ * upstream issue #634 "3.0.3 SIGSEGV on config parse",
+ * upstream issue #635 "3.0.x - rlm_perl - strings are still
+ escaped when passed to perl from FreeRADIUS",
+ * upstream issue #639 "foreach may cause ABORT".
+- Fixes bugs 1097266 1070447
+
+* Wed May 7 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.2-1
+- Upgrade to upstream 3.0.2 release, configuration compatible with 3.0.1.
+ See upstream ChangeLog for details (in freeradius-doc subpackage)
+- Fixes bugs 1058884 1061408 1070447 1079500
+
+* Mon Feb 24 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.1-4
+- Fix CVE-2014-2015 "freeradius: stack-based buffer overflow flaw in rlm_pap
+ module"
+- resolves: bug#1066984 (fedora 1066763)
+
+* Fri Feb 21 2014 John Dennis <jdennis@redhat.com> - 3.0.1-3
+- resolves: bug#1068798 (fedora 1068795)
+ rlm_perl attribute values truncated
+
+* Sun Jan 19 2014 John Dennis <jdennis@redhat.com> - 3.0.1-2
+- resolves: bug#1055073 (fedora 1055072)
+ rlm_ippool; bad config file attribute and fails to send reply attributes
+- resolves: bug#1055567 (fedora 1056227)
+ bad mysql sql syntax
+- change CFLAGS -imacros to -include to address gcc/gdb bug 1004526
+ where gdb will not display source information, only <command-line>
+
+* Tue Jan 14 2014 John Dennis <jdennis@redhat.com> - 3.0.1-1
+- Upgrade to upstream 3.0.1 release, full config compatible with 3.0.0.
+ This is a roll-up of all upstream bugs fixes found in 3.0.0
+ See upstream ChangeLog for details (in freeradius-doc subpackage)
+- fixes bugs 1053020 1044747 1048474 1043036
+
+* Tue Nov 26 2013 John Dennis <jdennis@redhat.com> - 3.0.0-4
+- resolves: bug#1031035
+ remove radeapclient man page,
+ upstream no longer supports radeapclient, use eapol_test instead
+- resolves: bug#1031061
+ rlm_eap_leap memory corruption, see freeradius-rlm_leap.patch
+- move man pages for utils into utils subpackage from doc subpackage
+- fix HAVE_EC_CRYPTO test to include f20
+- add new directory /var/run/radiusd/tmp
+ update mods-available/eap so tls-common.verify.tmpdir to point to it
+
+* Wed Nov 13 2013 John Dennis <jdennis@redhat.com> - 3.0.0-3
+- resolves: bug#1029941
+ PW_TYPE_BOOLEAN config item should be declared int, not bool
+
+* Mon Oct 28 2013 John Dennis <jdennis@redhat.com> - 3.0.0-2
+- resolves: bug#1024119
+ tncfhh-devel is now available in RHEL-7, remove conditional BuildRequires
+
+* Sun Oct 13 2013 John Dennis <jdennis@redhat.com> - 3.0.0-1
+- Offical 3.0 gold release from upstream
+- resolves: bug#1016873
+- resolves: bug#891297
+
+* Sun Sep 8 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.4.rc1
+- upgrade to second 3.0 release candidate rc1
+
+* Mon Aug 26 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.3.rc0
+- add missingok config attribute to /etc/raddb/sites-enabled/* symlinks
+
+* Sat Aug 03 2013 Petr Pisar <ppisar@redhat.com> - 3.0.0-0.2.rc0
+- Perl 5.18 rebuild
+
+* Fri Jul 26 2013 Ville Skyttä <ville.skytta@iki.fi> - 3.0.0-0.1.rc0
+- Install docs to %%{_pkgdocdir} where available.
+
+* Mon Jul 22 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.rc0
+- Upgrade to new upstream major version release
+
+* Wed Jul 17 2013 Petr Pisar <ppisar@redhat.com> - 2.2.0-7
+- Perl 5.18 rebuild
+
+* Wed Feb 13 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.2.0-6
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
+* Fri Dec 14 2012 John Dennis <jdennis@redhat.com> - 2.2.0-5
+- resolves: bug#850119 - Introduce new systemd-rpm macros (>= F18)
+
+* Thu Dec 13 2012 John Dennis <jdennis@redhat.com> - 2.2.0-4
+- add compile option -fno-strict-aliasing
+
+* Thu Dec 13 2012 John Dennis <jdennis@redhat.com> - 2.2.0-3
+- specify homedir (/var/lib/radiusd) for radiusd user in useradd,
+ do not permit useradd to default the homedir.
+
+* Wed Dec 12 2012 John Dennis <jdennis@redhat.com> - 2.2.0-2
+- add security options to compiler/linker
+
+* Mon Dec 10 2012 John Dennis <jdennis@redhat.com> - 2.2.0-1
+- resolves: bug#876564 - fails to start without freeradius-mysql
+- use upstream version of freeradius-exclude-config-file.patch
+
+* Wed Oct 3 2012 John Dennis <jdennis@redhat.com> - 2.2.0-0
+- Add new patch to avoid reading .rpmnew, .rpmsave and other invalid
+ files when loading config files
+- Upgrade to new 2.2.0 upstream release
+- Upstream changelog for 2.1.12:
+ Feature improvements
+ * 100% configuration file compatible with 2.1.x.
+ The only fix needed is to disallow "hashsize=0" for rlm_passwd
+ * Update Aruba, Alcatel Lucent, APC, BT, PaloAlto, Pureware,
+ Redback, and Mikrotik dictionaries
+ * Switch to using SHA1 for certificate digests instead of MD5.
+ See raddb/certs/*.cnf
+ * Added copyright statements to the dictionaries, so that we know
+ when people are using them.
+ * Better documentation for radrelay and detail file writer.
+ See raddb/modules/radrelay and raddb/radrelay.conf
+ * Added TLS-Cert-Subject-Alt-Name-Email from patch by Luke Howard
+ * Added -F <file> to radwho
+ * Added query timeouts to MySQL driver. Patch from Brian De Wolf.
+ * Add /etc/default/freeradius to debian package.
+ Patch from Matthew Newton
+ * Finalize DHCP and DHCP relay code. It should now work everywhere.
+ See raddb/sites-available/dhcp, src_ipaddr and src_interface.
+ * DHCP capabilitiies are now compiled in by default.
+ It runs as a DHCP server ONLY when manually enabled.
+ * Added one letter expansions: %%G - request minute and %%I request
+ ID.
+ * Added script to convert ISC DHCP lease files to SQL pools.
+ See scripts/isc2ippool.pl
+ * Added rlm_cache to cache arbitrary attributes.
+ * Added max_use to rlm_ldap to force connection to be re-established
+ after a given number of queries.
+ * Added configtest option to Debian init scripts, and automatic
+ config test on restart.
+ * Added cache config item to rlm_krb5. When set to "no" ticket
+ caching is disabled which may increase performance.
+
+ Bug fixes
+ * Fix CVE-2012-3547. All users of 2.1.10, 2.1.11, 2.1.12,
+ and 802.1X should upgrade immediately.
+ * Fix typo in detail file writer, to skip writing if the packet
+ was read from this detail file.
+ * Free cached replies when closing resumed SSL sessions.
+ * Fix a number of issues found by Coverity.
+ * Fix memory leak and race condition in the EAP-TLS session cache.
+ Thanks to Phil Mayers for tracking down OpenSSL APIs.
+ * Restrict ATTRIBUTE names to character sets that make sense.
+ * Fix EAP-TLS session Id length so that OpenSSL doesn't get
+ excited.
+ * Fix SQL IPPool logic for non-timer attributes. Closes bug #181
+ * Change some informational messages to DEBUG rather than error.
+ * Portability fixes for FreeBSD. Closes bug #177
+ * A much better fix for the _lt__PROGRAM__LTX_preloaded_symbols
+ nonsense.
+ * Safely handle extremely long lines in conf file variable expansion
+ * Fix for Debian bug #606450
+ * Mutex lock around rlm_perl Clone routines. Patch from Eike Dehling
+ * The passwd module no longer permits "hashsize = 0". Setting that
+ is pointless for a host of reasons. It will also break the server.
+ * Fix proxied inner-tunnel packets sometimes having zero authentication
+ vector. Found by Brian Julin.
+ * Added $(EXEEXT) to Makefiles for portability. Closes bug #188.
+ * Fix minor build issue which would cause rlm_eap to be built twice.
+ * When using "status_check=request" for a home server, the username
+ and password must be specified, or the server will not start.
+ * EAP-SIM now calculates keys from the SIM identity, not from the
+ EAP-Identity. Changing the EAP type via NAK may result in
+ identities changing. Bug reported by Microsoft EAP team.
+ * Use home server src_ipaddr when sending Status-Server packets
+ * Decrypt encrypted ERX attributes in CoA packets.
+ * Fix registration of internal xlat's so %%{mschap:...} doesn't
+ disappear after a HUP.
+ * Can now reference tagged attributes in expansions.
+ e.g. %%{Tunnel-Type:1} and %%{Tunnel-Type:1[0]} now work.
+ * Correct calculation of Message-Authenticator for CoA and Disconnect
+ replies. Patch from Jouni Malinen
+ * Install rad_counter, for managing rlm_counter files.
+ * Add unique index constraint to all SQL flavours so that alternate
+ queries work correctly.
+ * The TTLS diameter decoder is now more lenient. It ignores
+ unknown attributes, instead of rejecting the TTLS session.
+ * Use "globfree" in detail file reader. Prevents very slow leak.
+ Closes bug #207.
+ * Operator =~ shouldn't copy the attribute, like :=. It should
+ instead behave more like ==.
+ * Build main Debian package without SQL dependencies
+ * Use max_queue_size in threading code
+ * Update permissions in raddb/sql/postgresql/admin.sql
+ * Added OpenSSL_add_all_algorithms() to fix issues where OpenSSL
+ wouldn't use methods it knew about.
+ * Add more sanity checks in dynamic_clients code so the server won't
+ crash if it attempts to load a badly formated client definition.
+
+* Thu Jul 19 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.12-10
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
+
+* Fri Jun 08 2012 Petr Pisar <ppisar@redhat.com> - 2.1.12-9
+- Perl 5.16 rebuild
+
+* Tue May 15 2012 John Dennis <jdennis@redhat.com> - 2.1.12-8
+- resolves: bug#821407 - openssl dependency
+
+* Sat Apr 14 2012 John Dennis <jdennis@redhat.com> - 2.1.12-7
+- resolves: bug#810605 Segfault with freeradius-perl threading
+
+* Tue Feb 28 2012 John Dennis <jdennis@redhat.com> - 2.1.12-6
+ Fixing bugs in RHEL6 rebase, applying fixes here as well
+ resolves: bug#700870 freeradius not compiled with --with-udpfromto
+ resolves: bug#753764 shadow password expiration does not work
+ resolves: bug#712803 radtest script is not working with eap-md5 option
+ resolves: bug#690756 errors in raddb/sql/postgresql/admin.sql template
+
+* Tue Feb 7 2012 John Dennis <jdennis@redhat.com> - 2.1.12-5
+- resolves: bug#781877 (from RHEL5) rlm_dbm_parse man page misspelled
+- resolves: bug#760193 (from RHEL5) radtest PPPhint option is not parsed properly
+
+* Sun Jan 15 2012 John Dennis <jdennis@redhat.com> - 2.1.12-4
+- resolves: bug#781744
+ systemd service file incorrectly listed pid file as
+ /var/run/radiusd/radiusd which it should have been
+ /var/run/radiusd/radiusd.pid
+
+* Fri Jan 13 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.12-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild
+
+* Mon Oct 31 2011 John Dennis <jdennis@redhat.com> - 2.1.12-2
+- rename /etc/tmpfiles.d/freeradius.conf to /etc/tmpfiles.d/radiusd.conf
+ remove config(noreplace) because it must match files section and
+ permissions differ between versions.
+- fixup macro usage for /var/run & /var/lib
+
+* Mon Oct 3 2011 John Dennis <jdennis@redhat.com> - 2.1.12-1
+- Upgrade to latest upstream release: 2.1.12
+- Upstream changelog for 2.1.12:
+ Feature improvements
+ * Updates to dictionary.erx, dictionary.siemens, dictionary.starent,
+ dictionary.starent.vsa1, dictionary.zyxel, added dictionary.symbol
+ * Added support for PCRE from Phil Mayers
+ * Configurable file permission in rlm_linelog
+ * Added "relaxed" option to rlm_attr_filter. This copies attributes
+ if at least one match occurred.
+ * Added documentation on dynamic clients.
+ See raddb/modules/dynamic_clients.
+ * Added support for elliptical curve cryptography.
+ See ecdh_curve in raddb/eap.conf.
+ * Added support for 802.1X MIBs in checkrad
+ * Added support for %%{rand:...}, which generates a uniformly
+ distributed number between 0 and the number you specify.
+ * Created "man" pages for all installed commands, and documented
+ options for all commands. Patch from John Dennis.
+ * Allow radsniff to decode encrypted VSAs and CoA packets.
+ Patch from Bjorn Mork.
+ * Always send Message-Authenticator in radtest. Patch from John Dennis.
+ radclient continues to be more flexible.
+ * Updated Oracle schema and queries
+ * Added SecurID module. See src/modules/rlm_securid/README
+
+ Bug fixes
+ * Fix memory leak in rlm_detail
+ * Fix "failed to insert event"
+ * Allow virtual servers to be reloaded on HUP.
+ It no longer complains about duplicate virtual servers.
+ * Fix %%{string:...} expansion
+ * Fix "server closed socket" loop in radmin
+ * Set ownership of control socket when starting up
+ * Always allow root to connect to control socket, even if
+ "uid" is set. They're root. They can already do anything.
+ * Save all attributes in Access-Accept when proxying inner-tunnel
+ EAP-MSCHAPv2
+ * Fixes for DHCP relaying.
+ * Check certificate validity when using OCSP.
+ * Updated Oracle "configure" script
+ * Fixed typos in dictionary.alvarion
+ * WARNING on potential proxy loop.
+ * Be more aggressive about clearing old requests from the
+ internal queue
+ * Don't open network sockets when using -C
+
+* Wed Sep 21 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-7
+- restore defattr customization in the main package
+
+* Fri Sep 9 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-6
+- add missing systemd scriptlets
+
+* Thu Sep 8 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-5
+- convert to systemd
+
+* Thu Jul 21 2011 Petr Sabata <contyk@redhat.com> - 2.1.11-4
+- Perl mass rebuild
+
+* Wed Jul 20 2011 Petr Sabata <contyk@redhat.com> - 2.1.11-3
+- Perl mass rebuild
+
+* Thu Jun 23 2011 John Dennis <jdennis@redhat.com> - 2.1.11-2
+- reload the server (i.e. HUP) after logrotate
+
+* Wed Jun 22 2011 John Dennis <jdennis@redhat.com> - 2.1.11-1
+- Upgrade to latest upstream release: 2.1.11
+- Remove the following two patches as upstream has incorporated them:
+ freeradius-radtest-ipv6.patch
+ freeradius-lt-dladvise.patch
+- Upstream changelog for 2.1.11:
+ Feature improvements
+ * Added doc/rfc/rfc6158.txt: RADIUS Design Guidelines.
+ All vendors need to read it and follow its directions.
+ * Microsoft SoH support for PEAP from Phil Mayers.
+ See doc/SoH.txt
+ * Certificate "bootstrap" script now checks for certificate expiry.
+ See comments in raddb/eap.conf, and then "make_cert_command".
+ * Support for dynamic expansion of EAP-GTC challenges.
+ Patch from Alexander Clouter.
+ * OCSP support from Alex Bergmann. See raddb/eap.conf, "ocsp"
+ section.
+ * Updated dictionary.huawei, dictionary.3gpp, dictionary.3gpp3.
+ * Added dictionary.eltex, dictionary.motorola, and dictionary.ukerna.
+ * Experimental redis support from Gabriel Blanchard.
+ See raddb/modules/redis and raddb/modules/rediswho
+ * Add "key" to rlm_fastusers. Closes bug #126.
+ * Added scripts/radtee from original software at
+ http://horde.net/~jwm/software/misc/comparison-tee
+ * Updated radmin "man" page for new commands.
+ * radsniff now prints the hex decoding of the packet (-x -x -x)
+ * mschap module now reloads its configuration on HUP
+ * Added experimental "replicate" module. See raddb/modules/replicate
+ * Policy "foo" can now refer to module "foo". This lets you
+ over-ride the behavior of a module.
+ * Policy "foo.authorize" can now over-ride the behavior of module
+ "foo", "authorize" method.
+ * Produce errors in more situations when the configuration files
+ have invalid syntax.
+
+ Bug fixes
+ * Ignore pre/post-proxy sections if proxying is disabled
+ * Add configure checks for pcap_fopen*.
+ * Fix call to otp_write in rlm_otp
+ * Fix issue with Access-Challenge checking from 2.1.10, when the
+ debug flag was set after server startup. Closes #116 and #117.
+ * Fix typo in zombie period start time.
+ * Fix leak in src/main/valuepair.c. Patch from James Ballantine.
+ * Allow radtest to use spaces in shared secret.
+ Patch from Cedric Carree.
+ * Remove extra calls to HMAC_CTX_init() in rlm_wimax, fixing leak.
+ Patch from James Ballantine.
+ * Remove MN-FA key generation. The NAS does this, not AAA.
+ Patch from Ben Weichman.
+ * Include dictionary.mikrotik by default. Closes bug #121.
+ * Add group membership query to MS-SQL examples. Closes bug #120.
+ * Don't cast NAS-Port to integer in Postgresql queries.
+ Closes bug #112.
+ * Fixes for libtool and autoconf from Sam Hartman.
+ * radsniff should read the dictionaries in more situations.
+ * Use fnmatch to check for detail file reader==writer.
+ Closes bug #128.
+ * Check for short writes (i.e. disk full) in rlm_detail.
+ Closes bug #130. Patches and testing from John Morrissey.
+ * Fix typo in src/lib/token.c. Closes bug #124
+ * Allow workstation trust accounts to use MS-CHAP.
+ Closes bug #123.
+ * Assigning foo=`/bin/echo hello` now produces a syntax error
+ if it is done outside of an "update" section.
+ * Fix "too many open file descriptors" problem when using
+ "verify client" in eap.conf.
+ * Many fixes to dialup_admin for PHP5, by Stefan Winter.
+ * Allow preprocess module to have "hints = " and "huntgroups =",
+ which allows them to be empty or non-existent.
+ * Renamed "php3" files to "php" in dialup_admin/
+ * Produce error when sub-TLVs are used in a dictionary. They are
+ supported only in the "master" branch, and not in 2.1.x.
+ * Minor fix in dictionary.redback. Closes bug #138.
+ * Fixed MySQL "NULL" issues in ippool.conf. Closes bug #129.
+ * Fix to Access-Challenge warning from Ken-ichirou Matsuzawa.
+ Closes bug #118.
+ * DHCP fixes to send unicast packets in more situations.
+ * Fix to udpfromto, to enable it to work on IPv6 networks.
+ * Fixes to the Oracle accounting_onoff_query.
+ * When using both IPv4 and IPv6 home servers, ensure that we use the
+ correct local socket for proxying. Closes bug #143.
+ * Suppress messages when thread pool is nearly full, all threads
+ are busy, and we can't create new threads.
+ * IPv6 is now enabled for udpfromto. Closes bug #141
+ * Make sqlippool query buffer the same size as sql module.
+ Closes bug #139.
+ * Make Coa / Disconnect proxying work again.
+ * Configure scripts for rlm_caching from Nathaniel McCallum
+ * src/lib/dhcp.c and src/include/libradius.h are LGPL, not GPL.
+ * Updated password routines to use time-insensitive comparisons.
+ This prevents timing attacks (though none are known).
+ * Allow sqlite module to do normal SELECT queries.
+ * rlm_wimax now has a configure script
+ * Moved Ascend, USR, and Motorola "illegal" dictionaries to separate
+ files. See share/dictionary for explanations.
+ * Check for duplicate module definitions in the modules{} section,
+ and refuse to start if duplicates are found.
+ * Check for duplicate virtual servers, and refuse to start if
+ duplicates are found.
+ * Don't use udpfromto if source is INADDR_ANY. Closes bug #148.
+ * Check pre-conditions before running radmin "inject file".
+ * Don't over-ride "no match" with "match" for regexes.
+ Closes bug #152.
+ * Make retry and error message configurable in mschap.
+ See raddb/modules/mschap
+ * Allow EAP-MSCHAPv2 to send error message to client. This change
+ allows some clients to prompt the user for a new password.
+ See raddb/eap.conf, mschapv2 section, "send_error".
+ * Load the default virtual server before any others.
+ This matches what users expect, and reduces confusion.
+ * Fix configure checks for udpfromto. Fixes Debian bug #606866
+ * Definitive fix for bug #35, where the server could crash under
+ certain loads. Changes src/lib/packet.c to use RB trees.
+ * Updated "configure" checks to allow IPv6 udpfromto on Linux.
+ * SQL module now returns NOOP if the accounting start/interim/stop
+ queries don't do anything.
+ * Allow %%{outer.control: ... } in string expansions
+ * home_server coa config now matches raddb/proxy.conf
+ * Never send a reply to a DHCP Release.
+
+* Thu Jun 16 2011 Marcela Mašláňová <mmaslano@redhat.com> - 2.1.10-8
+- Perl mass rebuild
+
+* Wed Mar 23 2011 John Dennis <jdennis@redhat.com> - 2.1.10-7
+- Resolves: #689045 Using rlm_perl cause radiusd failed to start
+ Fix configure typo which caused lt_dladvise_* functions to be skipped.
+ run autogen.sh because HAVE_LT_DLADVISE_INIT isn't in src/main/autogen.h
+ Implemented by: freeradius-lt-dladvise.patch
+
+* Wed Mar 23 2011 John Dennis <jdennis@redhat.com> - 2.1.10-6
+- Resolves: #599528 - make radtest IPv6 compatible
+
+* Wed Mar 23 2011 Dan Horák <dan@danny.cz> - 2.1.10-5
+- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient)
+
+* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.10-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
+
+* Sat Jan 1 2011 John Dennis <jdennis@redhat.com> - 2.1.10-3
+- bug 666589 - removing freeradius from system does not delete the user "radiusd"
+ fix scriptlet argument testing, simplify always exiting with zero
+
+* Thu Dec 30 2010 John Dennis <jdennis@redhat.com> - 2.1.10-2
+- rebuild for new MySQL libs
+
+* Tue Oct 19 2010 John Dennis <jdennis@redhat.com> - 2.1.10-1
+ Feature improvements
+ * Install the "radcrypt" program.
+ * Enable radclient to send requests containing MS-CHAPv1
+ Send packets with: MS-CHAP-Password = "password". It will
+ be automatically converted to the correct MS-CHAP attributes.
+ * Added "-t" command-line option to radtest. You can use "-t pap",
+ "-t chap", "-t mschap", or "-t eap-md5". The default is "-t pap"
+ * Make the "inner-tunnel" virtual server listen on 127.0.0.1:18120
+ This change and the previous one makes PEAP testing much easier.
+ * Added more documentation and examples for the "passwd" module.
+ * Added dictionaries for RFC 5607 and RFC 5904.
+ * Added note in proxy.conf that we recommend setting
+ "require_message_authenticator = yes" for all home servers.
+ * Added example of second "files" configuration, with documentation.
+ This shows how and where to use two instances of a module.
+ * Updated radsniff to have it write pcap files, too. See '-w'.
+ * Print out large WARNING message if we send an Access-Challenge
+ for EAP, and receive no follow-up messages from the client.
+ * Added Cached-Session-Policy for EAP session resumption. See
+ raddb/eap.conf.
+ * Added support for TLS-Cert-* attributes. For details, see
+ raddb/sites-available/default, "post-auth" section.
+ * Added sample raddb/modules/{opendirectory,dynamic_clients}
+ * Updated Cisco and Huawei, HP, Redback, and ERX dictionaries.
+ * Added RFCs 5607, 5904, and 5997.
+ * For EAP-TLS, client certificates can now be validated using an
+ external command. See eap.conf, "validate" subsection of "tls".
+ * Made rlm_pap aware of {nthash} prefix, for compatibility with
+ legacy RADIUS systems.
+ * Add Module-Failure-Message for mschap module (ntlm_auth)
+ * made rlm_sql_sqlite database configurable. Use "filename"
+ in sql{} section.
+ * Added %%{tolower: ...string ... }, which returns the lowercase
+ version of the string. Also added %%{toupper: ... } for uppercase.
+
+ Bug fixes
+ * Fix endless loop when there are multiple sub-options for
+ DHCP option 82.
+ * More debug output when sending / receiving DHCP packets.
+ * EAP-MSCHAPv2 should return the MPPE keys when used outside
+ of a TLS tunnel. This is needed for IKE.
+ * Added SSL "no ticket" option to prevent SSL from creating sessions
+ without IDs. We need the IDs, so this option should be set.
+ * Fix proxying of packets from inside a TTLS/PEAP tunnel.
+ Closes bug #25.
+ * Allow IPv6 address attributes to be created from domain names
+ Closes bug #82.
+ * Set the string length to the correct value when parsing double
+ quotes. Closes bug #88.
+ * No longer look users up in /etc/passwd in the default configuration.
+ This can be reverted by enabling "unix" in the "authorize" section.
+ * More #ifdef's to enable building on systems without certain
+ features.
+ * Fixed SQL-Group comparison to register only if the group
+ query is defined.
+ * Fixed SQL-Group comparison to register <instance>-SQL-Group,
+ just like rlm_ldap. This lets you have multiple SQL group checks.
+ * Fix scanning of octal numbers in "unlang". Closes bug #89.
+ * Be less aggressive about freeing "stuck" requests. Closes bug #35.
+ * Fix example in "originate-coa" to refer to the correct packet.
+ * Change default timeout for dynamic clients to 1 hour, not 1 day.
+ * Allow passwd module to map IP addresses, too.
+ * Allow passwd module to be used for CoA packets
+ * Put boot filename into DHCP header when DHCP-Boot-Filename
+ is specified.
+ * raddb/certs/Makefile no longer has certs depend on index.txt and
+ serial. Closes bug #64.
+ * Ignore NULL errorcode in PostgreSQL client. Closes bug #39
+ * Made Exec-Program and Exec-Program-Wait work in accounting
+ section again. See sites-available/default.
+ * Fix long-standing memory leak in esoteric conditions. Found
+ by Jerry Nichols.
+ * Added "Password-With-Header == userPassword" to raddb/ldap.attrmap
+ This will automatically convert more passwords.
+ * Updated rlm_pap to decode Password-With-Header, if it was base64
+ encoded, and to treat the contents as potentially binary data.
+ * Fix Novell eDir code to use the right function parameters.
+ Closes bug #86.
+ * Allow spaces to be escaped when executing external programs.
+ Closes bug #93.
+ * Be less restrictive about checking permissions on control socket.
+ If we're root, allow connecting to a non-root socket.
+ * Remove control socket on normal server exit. If the server isn't
+ running, the control socket should not exist.
+ * Use MS-CHAP-User-Name as Name field from EAP-MSCHAPv2 for MS-CHAP
+ calculations. It *MAY* be different (upper / lower case) from
+ the User-Name attribute. Closes bug #17.
+ * If the EAP-TLS methods have problems, more SSL errors are now
+ available in the Module-Failure-Message attribute.
+ * Update Oracle configure scripts. Closes bug #57.
+ * Added text to DESC fields of doc/examples/openldap.schema
+ * Updated more documentation to use "Restructured Text" format.
+ Thanks to James Lockie.
+ * Fixed typos in raddb/sql/mssql/dialup.conf. Closes bug #11.
+ * Return error for potential proxy loops when using "-XC"
+ * Produce better error messages when slow databases block
+ the server.
+ * Added notes on DHCP broadcast packets for FreeBSD.
+ * Fixed crash when parsing some date strings. Closes bug #98
+ * Improperly formatted Attributes are now printed as "Attr-##".
+ If they are not correct, they should not use the dictionary name.
+ * Fix rlm_digest to be check the format of the Digest attributes,
+ and return "noop" rather than "fail" if they're not right.
+ * Enable "digest" in raddb/sites-available/default. This change
+ enables digest authentication to work "out of the box".
+ * Be less aggressive about marking home servers as zombie.
+ If they are responding to some packets, they are still alive.
+ * Added Packet-Transmit-Counter, to track detail file retransmits.
+ Closes bug #13.
+ * Added configure check for lt_dladvise_init(). If it exists, then
+ using it solves some issues related to libraries loading libraries.
+ * Added indexes to the MySQL IP Pool schema.
+ * Print WARNING message if too many attributes are put into a packet.
+ * Include dhcp test client (not built by default)
+ * Added checks for LDAP constraint violation. Closes bug #18.
+ * Change default raddebug timeout to 60 seconds.
+ * Made error / warning messages more consistent.
+ * Correct back-slash handling in variable expansion. Closes bug #46.
+ You SHOULD check your configuration for backslash expansion!
+ * Fix typo in "configure" script (--enable-libltdl-install)
+ * Use local libltdl in more situations. This helps to avoid
+ compile issues complaining about lt__PROGRAM__LTX_preloaded_symbols.
+ * Fix hang on startup when multiple home servers were defined
+ with "src_ipaddr" field.
+ * Fix 32/64 bit issue in rlm_ldap. Closes bug #105.
+ * If the first "listen" section defines 127.0.0.1, don't use that
+ as a source IP for proxying. It won't work.
+ * When Proxy-To-Realm is set to a non-existent realm, the EAP module
+ should handle the request, rather than expecting it to be proxied.
+ * Fix IPv4 issues with udpfromto. Closes bug #110.
+ * Clean up child processes of raddebug. Closes bugs #108 and #109
+ * retry OTP if the OTP daemon fails. Closes bug #58.
+ * Multiple calls to ber_printf seem to work better. Closes #106.
+ * Fix "unlang" so that "attribute not found" is treated as a "false"
+ comparison, rather than a syntax error in the configuration.
+ * Fix issue with "Group" attribute.
+
+* Sat Jul 31 2010 Orcan Ogetbil <oget[dot]fedora[at]gmail[dot]com> - 2.1.9-3
+- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild
+
+* Tue Jun 01 2010 Marcela Maslanova <mmaslano@redhat.com> - 2.1.9-2
+- Mass rebuild with perl-5.12.0
+
+* Mon May 24 2010 John Dennis <jdennis@redhat.com> - 2.1.9-1
+- update to latest upstream, mainly bug fix release
+ Feature improvements
+ * Add radmin command "stats detail <file>" to see what
+ is going on inside of a detail file reader.
+ * Added documentation for CoA. See raddb/sites-available/coa
+ * Add sub-option support for Option 82. See dictionary.dhcp
+ * Add "server" field to default SQL NAS table, and documented it.
+
+ Bug fixes
+ * Reset "received ping" counter for Status-Server checks. In some
+ corner cases it was not getting reset.
+ * Handle large VMPS attributes.
+ * Count accounting responses from a home server in SNMP / statistics
+ code.
+ * Set EAP-Session-Resumed = Yes, not "No" when session is resumed.
+ * radmin packet counter statistics are now unsigned, for numbers
+ 2^31..2^32. After that they roll over to zero.
+ * Be more careful about expanding data in PAP and MS-CHAP modules.
+ This prevents login failures when passwords contain '{'.
+ * Clean up zombie children if there were many "exec" modules being
+ run for one packet, all with "wait = no".
+ * re-open log file after HUP. Closes bug #63.
+ * Fix "no response to proxied packet" complaint for Coa / Disconnect
+ packets. It shouldn't ignore replies to packets it sent.
+ * Calculate IPv6 netmasks correctly. Closes bug #69.
+ * Fix SQL module to re-open sockets if they unexpectedly close.
+ * Track scope for IPv6 addresses. This lets us use link-local
+ addresses properly. Closes bug #70.
+ * Updated Makefiles to no longer use the shell for recursing into
+ subdirs. "make -j 2" should now work.
+ * Updated raddb/sql/mysql/ippool.conf to use "= NULL". Closes
+ bug #75.
+ * Updated Makefiles so that "make reconfig" no longer uses the shell
+ for recursing into subdirs, and re-builds all "configure" files.
+ * Used above method to regenerate all configure scripts.
+ Closes bug #34.
+ * Updated SQL module to allow "server" field of "nas" table
+ to be blank: "". This means the same as it being NULL.
+ * Fixed regex realm example. Create Realm attribute with value
+ of realm from User-Name, not from regex. Closes bug #40.
+ * If processing a DHCP Discover returns "fail / reject", ignore
+ the packet rather than sending a NAK.
+ * Allow '%%' to be escaped in sqlcounter module.
+ * Fix typo internal hash table.
+ * For PEAP and TTLS, the tunneled reply is added to the reply,
+ rather than integrated via the operators. This allows multiple
+ VSAs to be added, where they would previously be discarded.
+ * Make request number unsigned. This changes nothing other than
+ the debug output when the server receives more than 2^31 packets.
+ * Don't block when reading child output in 'exec wait'. This means
+ that blocked children get killed, instead of blocking the server.
+ * Enabled building without any proxy functionality
+ * radclient now prefers IPv4, to match the default server config.
+ * Print useful error when a realm regex is invalid
+ * relaxed rules for preprocess module "with_cisco_vsa_hack". The
+ attributes can now be integer, ipaddr, etc. (i.e. non-string)
+ * Allow rlm_ldap to build if ldap_set_rebind_proc() has only
+ 2 arguments.
+ * Update configure script for rlm_python to avoid dynamic linking
+ problems on some platforms.
+ * Work-around for bug #35
+ * Do suid to "user" when running in debug mode as root
+ * Make "allow_core_dumps" work in more situations.
+ * In detail file reader, treat bad records as EOF.
+ This allows it to continue working when the disk is full.
+ * Fix Oracle default accounting queries to work when there are no
+ gigawords attributes. Other databases already had the fix.
+ * Fix rlm_sql to show when it opens and closes sockets. It already
+ says when it cannot connect, so it should say when it can connect.
+ * "chmod -x" for a few C source files.
+ * Pull update spec files, etc. from RedHat into the redhat/ directory.
+ * Allow spaces when parsing integer values. This helps people who
+ put "too much" into an SQL value field.
+
+* Thu Jan 7 2010 John Dennis <jdennis@redhat.com> - 2.1.8-2
+- resolves: bug #526559 initial install should run bootstrap to create certificates
+ running radiusd in debug mode to generate inital temporary certificates
+ is no longer necessary, the /etc/raddb/certs/bootstrap is invoked on initial
+ rpm install (not upgrade) if there is no existing /etc/raddb/certs/server.pem file
+- resolves: bug #528493 use sha1 algorithm instead of md5 during cert generation
+ the certificate configuration (/etc/raddb/certs/{ca,server,client}.cnf) files
+ were modifed to use sha1 instead of md5 and the validity reduced from 1 year to 2 months
+
+* Wed Dec 30 2009 John Dennis <jdennis@redhat.com> - 2.1.8-1
+- update to latest upstream
+ Feature improvements
+ * Print more descriptive error message for too many EAP sessions.
+ This gives hints on what to do when "failed to store handler"
+ * Commands received from radmin are now printed on stdout when
+ in debugging mode.
+ * Allow accounting packets to be written to a detail file, even
+ if they were read from a different detail file.
+ * Added OpenSSL license exception (src/LICENSE.openssl)
+
+ Bug fixes
+ * DHCP sockets can now set the broadcast flag before binding to a
+ socket. You need to set "broadcast = yes" in the DHCP listener.
+ * Be more restrictive on string parsing in the config files
+ * Fix password length in scripts/create-users.pl
+ * Be more flexible about parsing the detail file. This allows
+ it to read files where the attributes have been edited.
+ * Ensure that requests read from the detail file are cleaned up
+ (i.e. don't leak) if they are proxied without a response.
+ * Write the PID file after opening sockets, not before
+ (closes bug #29)
+ * Proxying large numbers of packets no longer gives error
+ "unable to open proxy socket".
+ * Avoid mutex locks in libc after fork
+ * Retry packet from detail file if there was no response.
+ * Allow old-style dictionary formats, where the vendor name is the
+ last field in an ATTRIBUTE definition.
+ * Removed all recursive use of mutexes. Some systems just don't
+ support this.
+ * Allow !* to work as documented.
+ * make templates work (see templates.conf)
+ * Enabled "allow_core_dumps" to work again
+ * Print better errors when reading invalid dictionaries
+ * Sign client certificates with CA, rather than server certs.
+ * Fix potential crash in rlm_passwd when file was closed
+ * Fixed corner cases in conditional dynamic expansion.
+ * Use InnoDB for MySQL IP Pools, to gain transactional support
+ * Apply patch to libltdl for CVE-2009-3736.
+ * Fixed a few issues found by LLVM's static checker
+ * Keep track of "bad authenticators" for accounting packets
+ * Keep track of "dropped packets" for auth/acct packets
+ * Synced the "debian" directory with upstream
+ * Made "unlang" use unsigned 32-bit integers, to match the
+ dictionaries.
+
+* Wed Dec 30 2009 John Dennis <jdennis@redhat.com> - 2.1.7-7
+- Remove devel subpackage. It doesn't make much sense to have a devel package since
+ we don't ship libraries and it produces multilib conflicts.
+
+* Mon Dec 21 2009 John Dennis <jdennis@redhat.com> - 2.1.7-6
+- more spec file clean up from review comments
+- remove freeradius-libs subpackage, move libfreeradius-eap and
+ libfreeradius-radius into the main package
+- fix subpackage requires, change from freeradius-libs to main package
+- fix description of the devel subpackage, remove referene to non-shipped libs
+- remove execute permissions on src files included in debuginfo
+- remove unnecessary use of ldconfig
+- since all sub-packages now require main package remove user creation for sub-packages
+- also include the LGPL library license file in addition to the GPL license file
+- fix BuildRequires for perl so it's compatible with both Fedora, RHEL5 and RHEL6
+
+* Mon Dec 21 2009 John Dennis <jdennis@redhat.com> - 2.1.7-5
+- fix various rpmlint issues.
+
+* Fri Dec 4 2009 Stepan Kasal <skasal@redhat.com> - 2.1.7-4
+- rebuild against perl 5.10.1
+
+* Thu Dec 3 2009 John Dennis <jdennis@redhat.com> - 2.1.7-3
+- resolves: bug #522111 non-conformant initscript
+ also change permission of /var/run/radiusd from 0700 to 0755
+ so that "service radiusd status" can be run as non-root
+
+* Wed Sep 16 2009 Tomas Mraz <tmraz@redhat.com> - 2.1.7-2
+- use password-auth common PAM configuration instead of system-auth
+
+* Tue Sep 15 2009 John Dennis <jdennis@redhat.com> - 2.1.7-1
+- enable building of the rlm_wimax module
+- pcap wire analysis support is enabled and available in utils subpackage
+- Resolves bug #523053 radtest manpage in wrong package
+- update to latest upstream release, from upstream Changelog:
+ Feature improvements
+ * Full support for CoA and Disconnect packets as per RFC 3576
+ and RFC 5176. Both receiving and proxying CoA is supported.
+ * Added "src_ipaddr" configuration to "home_server". See
+ proxy.conf for details.
+ * radsniff now accepts -I, to read from a filename instead of
+ a device.
+ * radsniff also prints matching requests and any responses to those
+ requests when '-r' is used.
+ * Added example of attr_filter for Access-Challenge packets
+ * Added support for udpfromto in DHCP code
+ * radmin can now selectively mark modules alive/dead.
+ See "set module state".
+ * Added customizable messages on login success/fail.
+ See msg_goodpass && msg_badpass in log{} section of radiusd.conf
+ * Document "chase_referrals" and "rebind" in raddb/modules/ldap
+ * Preliminary implementation of DHCP relay.
+ * Made thread pool section optional. If it doesn't exist,
+ the server will run single-threaded.
+ * Added sample radrelay.conf for people upgrading from 1.x
+ * Made proxying more stable by failing over, rather than
+ rejecting the first request. See "response_window" in proxy.conf
+ * Allow home_server_pools to exist without realms.
+ * Add dictionary.iea (closes bug #7)
+ * Added support for RFC 5580
+ * Added experimental sql_freetds module from Gabriel Blanchard.
+ * Updated dictionary.foundry
+ * Added sample configuration for MySQL cluster in raddb/sql/ndb
+ See the README file for explanations.
+ Bug fixes
+ * Fixed corner case where proxied packets could have extra
+ character in User-Password attribute. Fix from Niko Tyni.
+ * Extended size of "attribute" field in SQL to 64.
+ * Fixes to ruby module to be more careful about when it builds.
+ * Updated Perl module "configure" script to check for broken
+ Perl installations.
+ * Fix "status_check = none". It would still send packets
+ in some cases.
+ * Set recursive flag on the proxy mutex, which enables safer
+ cleanup on some platforms.
+ * Copy the EAP username verbatim, rather than escaping it.
+ * Update handling so that robust-proxy-accounting works when
+ all home servers are down for extended periods of time.
+ * Look for DHCP option 53 anywhere in the packet, not just
+ at the start.
+ * Fix processing of proxy fail handler with virtual servers.
+ * DHCP code now prints out correct src/dst IP addresses
+ when sending packets.
+ * Removed requirement for DHCP to have clients
+ * Fixed handling of DHCP packets with message-type buried in the packet
+ * Fixed corner case with negation in unlang.
+ * Minor fixes to default MySQL & PostgreSQL schemas
+ * Suppress MSCHAP complaints in debugging mode.
+ * Fix SQL module for multiple instance, and possible crash on HUP
+ * Fix permissions for radius.log for sites that change user/group,
+ but which don't create the file before starting radiusd.
+ * Fix double counting of packets when proxying
+ * Make %%l work
+ * Fix pthread keys in rlm_perl
+ * Log reasons for EAP failure (closes bug #8)
+ * Load home servers and pools that aren't referenced from a realm.
+ * Handle return codes from virtual attributes in "unlang"
+ (e.g. LDAP-Group). This makes "!(expr)" work for them.
+ * Enable VMPS to see contents of virtual server again
+ * Fix WiMAX module to be consistent with examples. (closes bug #10)
+ * Fixed crash with policies dependent on NAS-Port comparisons
+ * Allowed vendor IDs to be be higher than 32767.
+ * Fix crash on startup with certain regexes in "hints" file.
+ * Fix crash in attr_filter module when packets don't exist
+ * Allow detail file reader to be faster when "load_factor = 100"
+ * Add work-around for build failures with errors related to
+ lt__PROGRAM__LTX_preloaded_symbols. libltdl / libtool are horrible.
+ * Made ldap module "rebind" option aware of older, incompatible
+ versions of OpenLDAP.
+ * Check value of Fall-Through in attr_filter module.
+
+* Fri Aug 21 2009 Tomas Mraz <tmraz@redhat.com> - 2.1.6-6
+- rebuilt with new openssl
+
+* Fri Jul 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.6-5
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild
+
+* Fri Jul 10 2009 John Dennis <jdennis@redhat.com> - 2.1.6-4
+- install COPYRIGHT CREDITS INSTALL LICENSE README into docdir
+
+* Tue Jun 23 2009 John Dennis <jdennis@redhat.com> - 2.1.6-3
+- resolves bug #507571 freeradius packages do not check for user/group existence
+
+* Tue Jun 2 2009 John Dennis <jdennis@redhat.com> - 2.1.6-2
+- make /etc/raddb/sites-available/* be config(noreplace)
+
+* Mon May 18 2009 John Dennis <jdennis@redhat.com> - 2.1.6-1
+- update to latest upstream release, from upstream Changelog:
+ Feature improvements
+ * radclient exits with 0 on successful (accept / ack), and 1
+ otherwise (no response / reject)
+ * Added support for %%{sql:UPDATE ..}, and insert/delete
+ Patch from Arran Cudbard-Bell
+ * Added sample "do not respond" policy. See raddb/policy.conf
+ and raddb/sites-available/do_not_respond
+ * Cleanups to Suse spec file from Norbert Wegener
+ * New VSAs for Juniper from Bjorn Mork
+ * Include more RFC dictionaries in the default install
+ * More documentation for the WiMAX module
+ * Added "chase_referrals" and "rebind" configuration to rlm_ldap.
+ This helps with Active Directory. See raddb/modules/ldap
+ * Don't load pre/post-proxy if proxying is disabled.
+ * Added %%{md5:...}, which returns MD5 hash in hex.
+ * Added configurable "retry_interval" and "poll_interval"
+ for "detail" listeners.
+ * Added "delete_mppe_keys" configuration option to rlm_wimax.
+ Apparently some WiMAX clients misbehave when they see those keys.
+ * Added experimental rlm_ruby from
+ http://github.com/Antti/freeradius-server/tree/master
+ * Add Tunnel attributes to ldap.attrmap
+ * Enable virtual servers to be reloaded on HUP. For now, only
+ the "authorize", "authenticate", etc. processing sections are
+ reloaded. Clients and "listen" sections are NOT reloaded.
+ * Updated "radwatch" script to be more robust. See scripts/radwatch
+ * Added certificate compatibility notes in raddb/certs/README,
+ for compatibility with different operating systems. (i.e. Windows)
+ * Permit multiple "-e" in radmin.
+ * Add support for originating CoA-Request and Disconnect-Request.
+ See raddb/sites-available/originate-coa.
+ * Added "lifetime" and "max_queries" to raddb/sql.conf.
+ This helps address the problem of hung SQL sockets.
+ * Allow packets to be injected via radmin. See "inject help"
+ in radmin.
+ * Answer VMPS reconfirmation request. Patch from Hermann Lauer.
+ * Sample logrotate script in scripts/logrotate.freeradius
+ * Add configurable poll interval for "detail" listeners
+ * New "raddebug" command. This prints debugging information from
+ a running server. See "man raddebug.
+ * Add "require_message_authenticator" configuration to home_server
+ configuration. This makes the server add Message-Authenticator
+ to all outgoing Access-Request packets.
+ * Added smsotp module, as contributed by Siemens.
+ * Enabled the administration socket in the default install.
+ See raddb/sites-available/control-socket, and "man radmin"
+ * Handle duplicate clients, such as with replicated or
+ load-balanced SQL servers and "readclients = yes"
+ Bug fixes
+ * Minor changes to allow building without VQP.
+ * Minor fixes from John Center
+ * Fixed raddebug example
+ * Don't crash when deleting attributes via unlang
+ * Be friendlier to very fast clients
+ * Updated the "detail" listener so that it only polls once,
+ and not many times in a row, leaking memory each time...
+ * Update comparison for Packet-Src-IP-Address (etc.) so that
+ the operators other than '==' work.
+ * Did autoconf magic to work around weird libtool bug
+ * Make rlm_perl keep tags for tagged attributes in more situations
+ * Update UID checking for radmin
+ * Added "include_length" field for TTLS. It's needed for RFC
+ compliance, but not (apparently) for interoperability.
+ * Clean up control sockets when they are closed, so that we don't
+ leak memory.
+ * Define SUN_LEN for systems that don't have it.
+ * Correct some boundary conditions in the conditional checker ("if")
+ in "unlang". Bug noted by Arran Cudbard-Bell.
+ * Work around minor building issues in gmake. This should only
+ have affected developers.
+ * Change how we manage unprivileged user/group, so that we do not
+ create control sockets owned by root.
+ * Fixed more minor issues found by Coverity.
+ * Allow raddb/certs/bootstrap to run when there is no "make"
+ command installed.
+ * In radiusd.conf, run_dir depends on the name of the program,
+ and isn't hard-coded to "..../radiusd"
+ * Check for EOF in more places in the "detail" file reader.
+ * Added Freeswitch dictionary.
+ * Chop ethernet frames in VMPS, rather than droppping packets.
+ * Fix EAP-TLS bug. Patch from Arnaud Ebalard
+ * Don't lose string for regex-compares in the "users" file.
+ * Expose more functions in rlm_sql to rlm_sqlippool, which
+ helps on systems where RTLD_GLOBAL is off.
+ * Fix typos in MySQL schemas for ippools.
+ * Remove macro that was causing build issues on some platforms.
+ * Fixed issues with dead home servers. Bug noted by Chris Moules.
+ * Fixed "access after free" with some dynamic clients.
+
+- fix packaging bug, some directories missing execute permission
+ /etc/raddb/dictionary now readable by all.
+
+* Tue Feb 24 2009 John Dennis <jdennis@redhat.com> - 2.1.3-4
+- fix type usage in unixodbc to match new type usage in unixodbc API
+
+* Thu Feb 19 2009 John Dennis <jdennis@redhat.com> - 2.1.3-3
+- add pointer to Red Hat documentation in docdir
+
+* Sat Jan 24 2009 Caolán McNamara <caolanm@redhat.com> - 2.1.3-2
+- rebuild for dependencies
+
+* Thu Dec 4 2008 John Dennis <jdennis@redhat.com> - 2.1.3-1
+- upgrade to latest upstream release, upstream summary follows:
+ The focus of this release is stability.
+ Feature Improvements:
+ * Allow running with "user=radiusd" and binding to secure sockets.
+ * Start sending Status-Server "are you alive" messages earlier, which
+ helps with proxying multiple realms to a home server.
+ * Removed thread pool code from rlm_perl. It's not necessary.
+ * Added example Perl configuration to raddb/modules/perl
+ * Force OpenSSL to support certificates with SHA256. This seems to be
+ necessary for WiMAX certs.
+ Bug fixes:
+ * Fix Debian patch to allow it to build.
+ * Fix potential NULL dereference in debugging mode on certain
+ platforms for TTLS and PEAP inner tunnels.
+ * Fix uninitialized memory in handling of vendor definitions
+ * Fix parsing of quoted (but non-string) attributes in the "users" file.
+ * Initialize uknown NAS IP to 255.255.255.255, rather than 0.0.0.0
+ * use SUN_LEN in control socket, to avoid truncation on some platforms.
+ * Correct internal handling of "debug condition" to prevent it from
+ being over-written.
+ * Check return code of regcomp in "unlang", so that invalid regular
+ expressions are caught rather than mishandled.
+ * Make rlm_sql use <ltdl.h>. Addresses bug #610.
+ * Document list "type = status" better. Closes bug #580.
+ * Set "default days" for certificates, because OpenSSL won't do it.
+ This closes bug #615.
+ * Reference correct list in example raddb/modules/ldap. Closes #596.
+ * Increase default schema size for Acct-Session-Id to 64. Closes #540.
+ * Fix use of temporary files in dialup-admin. Closes #605 and
+ addresses CVE-2008-4474.
+ * Addressed a number of minor issues found by Coverity.
+ * Added DHCP option 150 to the dictionary. Closes #618.
+
+* Wed Dec 3 2008 John Dennis <jdennis@redhat.com> - 2.1.1-8
+- add --with-system-libtool to configure as a workaround for
+undefined reference to lt__PROGRAM__LTX_preloaded_symbols
+
+* Mon Dec 1 2008 John Dennis <jdennis@redhat.com> - 2.1.1-7
+- add obsoletes tag for dialupadmin subpackages which were removed
+
+* Mon Dec 1 2008 John Dennis <jdennis@redhat.com> - 2.1.1-7
+- add readline-devel BuildRequires
+
+* Sun Nov 30 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm@gmail.com> - 2.1.1-4
+- Rebuild for Python 2.6
+
+* Fri Nov 21 2008 John Dennis <jdennis@redhat.com> - 2.1.1-3
+- make spec file buildable on RHEL5.2 by making perl-devel a fedora only dependency.
+- remove diaupadmin packages, it's not well supported and there are problems with it.
+
+* Fri Sep 26 2008 John Dennis <jdennis@redhat.com> - 2.1.1-1
+- Resolves: bug #464119 bootstrap code could not create initial certs in /etc/raddb/certs because
+ permissions were 750, radiusd running as euid radiusd could not write there, permissions now 770
+
+* Thu Sep 25 2008 John Dennis <jdennis@redhat.com> - 2.1.1-1
+- upgrade to new upstream 2.1.1 release
+
+* Wed Jul 30 2008 John Dennis <jdennis@redhat.com> - 2.0.5-2
+- Resolves: bug #453761: FreeRADIUS %%post should not include chown -R
+ specify file attributes for /etc/raddb/ldap.attrmap
+ fix consistent use of tabs/spaces (rpmlint warning)
+
+* Mon Jun 9 2008 John Dennis <jdennis@redhat.com> - 2.0.5-1
+- upgrade to latest upstream, see Changelog for details,
+ upstream now has more complete fix for bug #447545, local patch removed
+
+* Wed May 28 2008 John Dennis <jdennis@redhat.com> - 2.0.4-1
+- upgrade to latest upstream, see Changelog for details
+- resolves: bug #447545: freeradius missing /etc/raddb/sites-available/inner-tunnel
+
+* Fri May 16 2008 <jdennis@redhat.com> - 2.0.3-3
+- # Temporary fix for bug #446864, turn off optimization
+
+* Fri Apr 18 2008 John Dennis <jdennis@redhat.com> - 2.0.3-2
+- remove support for radrelay, it's different now
+- turn off default inclusion of SQL config files in radiusd.conf since SQL
+ is an optional RPM install
+- remove mssql config files
+
+* Thu Apr 17 2008 John Dennis <jdennis@redhat.com> - 2.0.3-1
+- Upgrade to current upstream 2.0.3 release
+- Many thanks to Enrico Scholz for his spec file suggestions incorporated here
+- Resolve: bug #438665: Contains files owned by buildsystem
+- Add dialupadmin-mysql, dialupadmin-postgresql, dialupadmin-ldap subpackages
+ to further partition external dependencies.
+- Clean up some unnecessary requires dependencies
+- Add versioned requires between subpackages
+
+* Tue Mar 18 2008 Tom "spot" Callaway <tcallawa@redhat.com> - 2.0.2-2
+- add Requires for versioned perl (libperl.so)
+
+* Thu Feb 28 2008 <jdennis@redhat.com> - 2.0.2-1
+- upgrade to new 2.0 release
+- split into subpackages for more fine grained installation
+
+* Tue Feb 19 2008 Fedora Release Engineering <rel-eng@fedoraproject.org> - 1.1.7-4.4.ipa
+- Autorebuild for GCC 4.3
+
+* Thu Dec 06 2007 Release Engineering <rel-eng at fedoraproject dot org> - 1.1.7-3.4.ipa
+- Rebuild for deps
+
+* Sat Nov 10 2007 <jdennis@redhat.com> - 1.1.7-3.3.ipa
+- add support in rlm_ldap for reading clients from ldap
+- fix TLS parameter controling if a cert which fails to validate
+ will be accepted (i.e. self-signed),
+ rlm_ldap config parameter=tls_require_cert
+ ldap LDAP_OPT_X_TLS_REQUIRE_CERT parameter was being passed to
+ ldap_set_option() when it should have been ldap_int_tls_config()
+
+* Sat Nov 3 2007 <jdennis@redhat.com> - 1.1.7-3.2.ipa
+- add support in rlm_ldap for SASL/GSSAPI binds to the LDAP server
+
+* Mon Sep 17 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-3.1
+- made init script fully lsb conform
+
+* Mon Sep 17 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-3
+- fixed initscript problem (rhbz#292521)
+
+* Tue Aug 28 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-2
+- fixed initscript for LSB (rhbz#243671, rhbz#243928)
+- fixed license tag
+
+* Tue Aug 7 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-1
+- new versin 1.1.7
+- install snmp MIB files
+- dropped LDAP_DEPRECATED flag, it is upstream
+- marked config files for sub packages as config (rhbz#240400)
+- moved db files to /var/lib/raddb (rhbz#199082)
+
+* Fri Jun 15 2007 Thomas Woerner <twoerner@redhat.com> 1.1.6-2
+- radiusd expects /etc/raddb to not be world readable or writable
+ /etc/raddb now belongs to radiusd, post script sets permissions
+
+* Fri Jun 15 2007 Thomas Woerner <twoerner@redhat.com> 1.1.6-1
+- new version 1.1.6
+
+* Fri Mar 9 2007 Thomas Woerner <twoerner@redhat.com> 1.1.5-1
+- new version 1.1.5
+ - no /etc/raddb/otppasswd.sample anymore
+ - build is pie by default, dropped pie patch
+- fixed build requirement for perl (perl-devel)
+
+* Fri Feb 23 2007 Karsten Hopp <karsten@redhat.com> 1.1.3-3
+- remove trailing dot from summary
+- fix buildroot
+- fix post/postun/preun requirements
+- use rpm macros
+
+* Fri Dec 8 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-2.1
+- rebuild for new postgresql library version
+
+* Thu Nov 30 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-2
+- fixed ldap code to not use internals, added LDAP_DEPRECATED compile time flag
+ (#210912)
+
+* Tue Aug 15 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-1
+- new version 1.1.3 with lots of upstream bug fixes, some security fixes
+ (#205654)
+
+* Tue Aug 15 2006 Thomas Woerner <twoerner@redhat.com> 1.1.2-2
+- commented out include for sql.conf in radiusd.conf (#202561)
+
+* Wed Jul 12 2006 Jesse Keating <jkeating@redhat.com> - 1.1.2-1.1
+- rebuild
+
+* Thu Jun 1 2006 Thomas Woerner <twoerner@redhat.com> 1.1.2-1
+- new version 1.1.2
+
+* Wed May 31 2006 Thomas Woerner <twoerner@redhat.com> 1.1.1-1
+- new version 1.1.1
+- fixed incorrect rlm_sql globbing (#189095)
+ Thanks to Yanko Kaneti for the fix.
+- fixed chown syntax in post script (#182777)
+- dropped gcc34, libdir and realloc-return patch
+- spec file cleanup with additional libtool build fixes
+
+* Fri Feb 10 2006 Jesse Keating <jkeating@redhat.com> - 1.0.5-1.2
+- bump again for double-long bug on ppc(64)
+
+* Tue Feb 07 2006 Jesse Keating <jkeating@redhat.com> - 1.0.5-1.1
+- rebuilt for new gcc4.1 snapshot and glibc changes
+
+* Tue Dec 13 2005 Thomas Woerner <twoerner@redhat.com> 1.0.5-1
+- new version 1.0.5
+
+* Fri Dec 09 2005 Jesse Keating <jkeating@redhat.com>
+- rebuilt
+
+* Sat Nov 12 2005 Tom Lane <tgl@redhat.com> - 1.0.4-5
+- Rebuild due to mysql update.
+
+* Wed Nov 9 2005 Tomas Mraz <tmraz@redhat.com> - 1.0.4-4
+- rebuilt with new openssl
+- fixed ignored return value of realloc
+
+* Fri Sep 30 2005 Tomas Mraz <tmraz@redhat.com> - 1.0.4-3
+- use include instead of pam_stack in pam config
+
+* Wed Jul 20 2005 Thomas Woerner <twoerner@redhat.com> 1.0.4-2
+- added missing build requires for libtool-ltdl-devel (#160877)
+- modified file list to get a report for missing plugins
+
+* Tue Jun 28 2005 Thomas Woerner <twoerner@redhat.com> 1.0.4-1
+- new version 1.0.4
+- droppend radrelay patch (fixed upstream)
+
+* Thu Apr 14 2005 Warren Togami <wtogami@redhat.com> 1.0.2-2
+- rebuild against new postgresql-libs
+
+* Mon Apr 4 2005 Thomas Woerner <twoerner@redhat.com> 1.0.2-1
+- new version 1.0.2
+
+* Fri Nov 19 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-3
+- rebuild for MySQL 4
+- switched over to installed libtool
+
+* Fri Nov 5 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-2
+- Fixed install problem of radeapclient (#138069)
+
+* Wed Oct 6 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-1
+- new version 1.0.1
+- applied radrelay CVS patch from Kevin Bonner
+
+* Wed Aug 25 2004 Warren Togami <wtogami@redhat.com> 1.0.0-3
+- BuildRequires pam-devel and libtool
+- Fix errant text in description
+- Other minor cleanups
+
+* Wed Aug 25 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-2.1
+- renamed /etc/pam.d/radius to /etc/pam.d/radiusd to match default
+ configuration (#130613)
+
+* Wed Aug 25 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-2
+- fixed BuildRequires for openssl-devel (#130606)
+
+* Mon Aug 16 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-1
+- 1.0.0 final
+
+* Mon Jul 5 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-0.pre3.2
+- added buildrequires for zlib-devel (#127162)
+- fixed libdir patch to prefer own libeap instead of installed one (#127168)
+- fixed samba account maps in LDAP for samba v3 (#127173)
+
+* Thu Jul 1 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-0.pre3.1
+- third "pre" release of version 1.0.0
+- rlm_ldap is using SASLv2 (#126507)
+
+* Tue Jun 15 2004 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Thu Jun 3 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-4.1
+- fixed BuildRequires for gdbm-devel
+
+* Tue Mar 30 2004 Harald Hoyer <harald@redhat.com> - 0.9.3-4
+- gcc34 compilation fixes
+
+* Tue Mar 02 2004 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Tue Feb 24 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-3.2
+- added sql scripts for rlm_sql to documentation (#116435)
+
+* Fri Feb 13 2004 Elliot Lee <sopwith@redhat.com>
+- rebuilt
+
+* Thu Feb 5 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-2.1
+- using -fPIC instead of -fpic for s390 ans s390x
+
+* Thu Feb 5 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-2
+- radiusd is pie, now
+
+* Tue Nov 25 2003 Thomas Woerner <twoerner@redhat.com> 0.9.3-1
+- new version 0.9.3 (bugfix release)
+
+* Fri Nov 7 2003 Thomas Woerner <twoerner@redhat.com> 0.9.2-1
+- new version 0.9.2
+
+* Mon Sep 29 2003 Thomas Woerner <twoerner@redhat.com> 0.9.1-1
+- new version 0.9.1
+
+* Mon Sep 22 2003 Nalin Dahyabhai <nalin@redhat.com> 0.9.0-2.2
+- modify default PAM configuration to remove the directory part of the module
+ name, so that 32- and 64-bit libpam (called from 32- or 64-bit radiusd) on
+ multilib systems will always load the right module for the architecture
+- modify default PAM configuration to use pam_stack
+
+* Mon Sep 1 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-2.1
+- com_err.h moved to /usr/include/et
+
+* Tue Jul 22 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-1
+- 0.9.0 final
+
+* Wed Jul 16 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-0.9.0
+- new version 0.9.0 pre3
+
+* Thu May 22 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-6
+- included directory /var/log/radius/radacct for logrotate
+
+* Wed May 21 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-5
+- moved log and run dir to files section, cleaned up post
+
+* Wed May 21 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-4
+- added missing run dir in post
+
+* Tue May 20 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-3
+- fixed module load patch
+
+* Fri May 16 2003 Thomas Woerner <twoerner@redhat.com>
+- removed la files, removed devel package
+- split into 4 packages: freeradius, freeradius-mysql, freeradius-postgresql,
+ freeradius-unixODBC
+- fixed requires and buildrequires
+- create logging dir in post if it does not exist
+- fixed module load without la files
+
+* Thu Apr 17 2003 Thomas Woerner <twoerner@redhat.com>
+- Initial build.
diff --git a/freeradius.sysusers b/freeradius.sysusers
new file mode 100644
index 0000000..c0d814e
--- /dev/null
+++ b/freeradius.sysusers
@@ -0,0 +1,3 @@
+#Type Name ID GECOS Home directory Shell
+u radiusd 95 "radiusd user" /var/lib/radiusd /sbin/nologin
+g radiusd 95 - - - \ No newline at end of file
diff --git a/radiusd.service b/radiusd.service
new file mode 100644
index 0000000..8ec47ba
--- /dev/null
+++ b/radiusd.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=FreeRADIUS high performance RADIUS server.
+After=syslog.target network-online.target ipa.service dirsrv.target krb5kdc.service mysql.service mariadb.service postgresql.service
+
+[Service]
+Type=forking
+PIDFile=/run/radiusd/radiusd.pid
+ExecStartPre=-/bin/chown -R radiusd.radiusd /var/run/radiusd
+ExecStartPre=/usr/sbin/radiusd -C
+ExecStart=/usr/sbin/radiusd -d /etc/raddb
+ExecReload=/usr/sbin/radiusd -C
+ExecReload=/bin/kill -HUP $MAINPID
+
+[Install]
+WantedBy=multi-user.target
diff --git a/sources b/sources
new file mode 100644
index 0000000..96a93d7
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+8b7f794f2ac0d686d9aecfa083a63614 freeradius-server-3.0.21.tar.bz2