diff --git a/Makefile b/Makefile
index 5ec0ab4fde..5975ab747b 100644
--- a/Makefile
+++ b/Makefile
@@ -97,7 +97,7 @@ $(BUILD_DIR)/tests/radiusd-c: raddb/test.conf ${BUILD_DIR}/bin/radiusd | build.r
 	@echo "ok"
 	@touch $@
 
-test: ${BUILD_DIR}/bin/radiusd ${BUILD_DIR}/bin/radclient tests.unit tests.xlat tests.keywords tests.auth test.sql_nas_table $(BUILD_DIR)/tests/radiusd-c | build.raddb
+test: ${BUILD_DIR}/bin/radiusd ${BUILD_DIR}/bin/radclient tests.unit tests.xlat tests.keywords tests.modules tests.auth test.sql_nas_table $(BUILD_DIR)/tests/radiusd-c | build.raddb
 	@$(MAKE) -C src/tests tests
 
 #
@@ -291,10 +291,10 @@ certs:
 ######################################################################
 BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
 
-freeradius-server-$(RADIUSD_VERSION_STRING).tar.gz: .git
+freeradius-server-$(RADIUSD_VERSION_STRING).tar.gz: .git/HEAD
 	git archive --format=tar --prefix=freeradius-server-$(RADIUSD_VERSION_STRING)/ $(BRANCH) | gzip > $@
 
-freeradius-server-$(RADIUSD_VERSION_STRING).tar.bz2: .git
+freeradius-server-$(RADIUSD_VERSION_STRING).tar.bz2: .git/HEAD
 	git archive --format=tar --prefix=freeradius-server-$(RADIUSD_VERSION_STRING)/ $(BRANCH) | bzip2 > $@
 
 %.sig: %
diff --git a/VERSION b/VERSION
index de2e889324..25510162d8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.0.25
+3.0.26
diff --git a/acinclude.m4 b/acinclude.m4
index da48acc08a..a953d0e1b6 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -37,66 +37,6 @@ AC_DEFUN([FR_CHECK_TYPE_INCLUDE],
   fi
 ])
 
-dnl #
-dnl #  Locate the directory in which a particular file is found.
-dnl #
-dnl #  Usage: FR_LOCATE_DIR(MYSQLLIB_DIR, libmysqlclient.a)
-dnl #
-dnl #    Defines the variable MYSQLLIB_DIR to be the directory(s) in
-dnl #    which the file libmysqlclient.a is to be found.
-dnl #
-dnl #
-AC_DEFUN([FR_LOCATE_DIR],
-[
-dnl # If we have the program 'locate', then the problem of finding a
-dnl # particular file becomes MUCH easier.
-dnl #
-
-dnl #
-dnl #  No 'locate' defined, do NOT do anything.
-dnl #
-if test "x$LOCATE" != "x"; then
-  dnl #
-  dnl #  Root through a series of directories, looking for the given file.
-  dnl #
-  DIRS=
-  file=$2
-
-  for x in `${LOCATE} $file 2>/dev/null`; do
-    dnl #
-    dnl #  When asked for 'foo', locate will also find 'foo_bar', which we
-    dnl #  don't want.  We want that EXACT filename.
-    dnl #
-    dnl #  We ALSO want to be able to look for files like 'mysql/mysql.h',
-    dnl #  and properly match them, too.  So we try to strip off the last
-    dnl #  part of the filename, using the name of the file we're looking
-    dnl #  for.  If we CANNOT strip it off, then the name will be unchanged.
-    dnl #
-    base=`echo $x | sed "s%/${file}%%"`
-    if test "x$x" = "x$base"; then
-      continue;
-    fi
-
-    dir=`${DIRNAME} $x 2>/dev/null`
-    dnl #
-    dnl #  Exclude a number of directories.
-    dnl #
-    exclude=`echo ${dir} | ${GREP} /home`
-    if test "x$exclude" != "x"; then
-      continue
-    fi
-
-    dnl #
-    dnl #  OK, we have an exact match.  Let's be sure that we only find ONE
-    dnl #  matching directory.
-    dnl #
-    already=`echo \$$1 ${DIRS} | ${GREP} ${dir}`
-    if test "x$already" = "x"; then
-      DIRS="$DIRS $dir"
-    fi
-  done
-fi
-
 dnl #
 dnl #  And remember the directory in which we found the file.
 dnl #
@@ -175,10 +115,7 @@ dnl #
 dnl #  Try to guess possible locations.
 dnl #
 if test "x$smart_lib" = "x"; then
-  FR_LOCATE_DIR(smart_lib_dir,[lib$1${libltdl_cv_shlibext}])
-  FR_LOCATE_DIR(smart_lib_dir,[lib$1.a])
-
-  for try in $smart_lib_dir /usr/local/lib /opt/lib; do
+  for try in /usr/local/lib /opt/lib; do
     AC_MSG_CHECKING([for $2 in -l$1 in $try])
     LIBS="-l$1 $old_LIBS"
     CPPFLAGS="-L$try -Wl,-rpath,$try $old_CPPFLAGS"
@@ -306,11 +243,6 @@ dnl #  Try to guess possible locations.
 dnl #
 if test "x$smart_include" = "x"; then
 
-  for prefix in $smart_prefix; do
-    FR_LOCATE_DIR(_smart_include_dir,"${_prefix}/${1}")
-  done
-  FR_LOCATE_DIR(_smart_include_dir, $1)
-
   for try in $_smart_include_dir; do
     AC_MSG_CHECKING([for $1 in $try])
     CPPFLAGS="-isystem $try $old_CPPFLAGS"
diff --git a/configure.ac b/configure.ac
index ce4d9b0ae5..e5b2ea9b2f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -822,7 +822,6 @@ AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir)
 AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir)
 AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir)
 
-AC_PATH_PROG(LOCATE,locate)
 AC_PATH_PROG(DIRNAME,dirname)
 AC_PATH_PROG(GREP,grep)
 
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 325a8f36bd..2669081158 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,3 +1,42 @@
+FreeRADIUS 3.0.26 Thu 07 Oct 2021 12:00:00 EDT urgency=low
+	Feature improvements
+	* Add support for OpenSSL3
+	* Add dictionary.mellanox
+	* Update dictionary.aruba, dictionary.roaringpenguin
+	* Removed haproxy support.  It's new and experimental,
+	  and belongs in the v3.2 release, where it will be
+	  fully supported.
+	* Support PEAP and TTLS with TLS 1.3.  This has been
+	  tested with wpa_supplicant and Windows 11.
+	* Add %{concat:foo[*] ;}, which concatenates a set
+	  of attributes, separated by a character.
+	* Added sample configuration for using Google LDAP.
+	  See sites-available/google-ldap-auth, and
+	  mods-available/google_ldap, and mods-available/cache_auth.
+	* Add Dockerfiles for Rocky8.
+	* Add raduat script to the freeradius-utils package.
+	* Add Debian freeradius-freetds package.
+	* Add client short name to "dropping packet" message,
+
+	Bug fixes
+	* Ensure PBKDF2 always uses at least one iteration
+	* Actually use the certificate in "realm_dir" hinted at by SNI.
+	* Removed the use of "locate" during the "configure" phase.
+	  Fixes #4318
+	* Fix for showing incomplete home server list after deleting a
+	  home server via radmin.
+	* Call closedir() when reading certificates from a directory.
+	  Found by Antonio Torres.  Fixes #4378.
+	* If we read more than 16K of data in RadSec, then read it all.
+	  Fixes #4388, patch from Aren Sandersen.
+	* Fix information leak in compute_password_element() function
+	  of EAP-PWD.  Found by Mohamed Sabt.
+	* Fix crash in EAP-SIM when unknown attributes are sent.
+	* Update linelog, etc. to allow the use of /dev/stdout
+	* Fix crash in race condition when 500+ sockets are open.
+	* Remove sample "cache_eap" module.  It does not work, and
+	  offers no benefit.
+
 FreeRADIUS 3.0.25 Thu 07 Oct 2021 12:00:00 EDT urgency=medium
 	Feature improvements
 	* Better debug output when proxying is disabled
@@ -38,6 +77,8 @@ FreeRADIUS 3.0.24 Wed 29 Sep 2021 12:00:00 EDT urgency=medium
 	* The Simultaneous-Use code is now IPv6 aware, and can deal with
 	  NAS-IPv6-Address.
 	* Add dictionary.cambium
+	* Add new %{lasttime:...} allowing calculation of a number of
+	  hours/days/weeks/years in the past.
 
 	Bug fixes
 	* Fix crash in trustrouter module (#4115). Patch from Alejandro Perez
diff --git a/doc/antora/antora.yml b/doc/antora/antora.yml
index 95a5b1cad7..7b08f25689 100644
--- a/doc/antora/antora.yml
+++ b/doc/antora/antora.yml
@@ -5,7 +5,7 @@
 #
 name: freeradius-server
 title: The FreeRADIUS Server
-version: '3.0.25'
+version: '3.0.26'
 start_page: ROOT:index.adoc
 nav:
 - modules/ROOT/nav.adoc
diff --git a/doc/antora/modules/howto/nav.adoc b/doc/antora/modules/howto/nav.adoc
index 351200b292..b30824e7e8 100644
--- a/doc/antora/modules/howto/nav.adoc
+++ b/doc/antora/modules/howto/nav.adoc
@@ -11,9 +11,3 @@
 ****** xref:protocols/dhcp/policy_subnet_options.adoc[Subnet options]
 ****** xref:protocols/dhcp/policy_device_options.adoc[Device, class and group options]
 ****** xref:protocols/dhcp/policy_ippool_access.adoc[IP pool access restriction]
-**** xref:protocols/proxy/index.adoc[PROXY Protocol]
-***** xref:protocols/proxy/enable_radsec.adoc[Enabling RadSec]
-***** xref:protocols/proxy/radsec_client.adoc[Configuring a test RadSec client]
-***** xref:protocols/proxy/radsec_with_haproxy.adoc[Proxying RadSec with HAproxy]
-***** xref:protocols/proxy/radsec_with_traefik.adoc[Proxying RadSec with Traefik]
-***** xref:protocols/proxy/enable_proxy_protocol.adoc[Enabling PROXY Protocol for RadSec]
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc b/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc
deleted file mode 100644
index b6898247d5..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc
+++ /dev/null
@@ -1,114 +0,0 @@
-== Enabling PROXY Protocol
-
-Now that we have a working configuration which used RadSec and HAproxy
-or Traefik, we are finally ready to enable PROXY Protocol.
-
-Configure FreeRADIUS on the `radsecsvr` host to expect the PROXY
-Protocol for RadSec connections.  This is done by editing the `listen
-{}` section of the `tls` virtual server to include a reference to the
-proxy protocol:
-
-.Enabling PROXY Protocol in a FreeRADIUS virtual server
-=======================================================
-
- listen {
-     ...
-     proxy_protocol = true
-     ...
- }
-
-=======================================================
-
-Now restart the debugging session:
-[source,shell]
-----
-radiusd -fxxl /dev/stdout
-----
-
-
-For HAproxy, you should enable the PROXY Protocol on connections to
-the RadSec backend, by editing the `backend` definition to add a
-`send-proxy` argument:
-
-.Example HAproxy backend configuration with PROXY Protocol
-==========================================================
-
- backend radsec_be
-     mode tcp
-     balance roundrobin
-     server radsecsvr 172.23.0.3:2083 send-proxy
-
-==========================================================
-
-Note the `send-proxy` argument in the `server` definition.
-
-Now reload the HAproxy service:
-
-[source,shell]
-----
-service haproxy reload
----
-
-
-For Traefik, enable the PROXY Protocol on connections to the RadSec
-backend by editing the `radsec-service` definition to add a reference
-to the proxy protocol"
-
-.Example Traefik service configuration with PROXY Protocol
-==========================================================
-
-     radsec-service:
-       loadBalancer:
-         servers:
-         - address: "172.23.0.3:2083"
-         proxyProtocol:
-           version: 1
-
-==========================================================
-
-Note the `proxyProtocol` and `version: 1` directives.
-
-Traefik should automatically detect the updates and reconfigure the
-service.
-
-
-=== Testing RadSec connectivity via a proxy using PROXY Protocol
-
-Finally, with your test client configured to use the proxy, perform a
-test authentication:
-
-[source,shell]
-----
- echo "User-Name = bob" | radclient 127.0.0.1 auth testing123
-----
-
-You should expect to see the familiar output:
-
-.Example output from radclient
-==============================
-
- Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27
- Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39
-
-==============================
-
-Now examine the FreeRADIUS debug output on the RadSec server:
-
-.Expected output from `radiusd -X` with PROXY Protocol
-======================================================
-
- ...
- (0) (TLS) Received PROXY protocol connection from client \
-   172.23.0.2:55343 -> 172.23.0.4:2083, via proxy 172.23.0.4:40268 -> 0.0.0.0:2083
- ...
- (0) Received Access-Request Id 227 from 172.23.0.2:55343 to 172.23.0.4:2083 length 49
- (0) Sent Access-Accept Id 227 from 172.23.0.4:2083 to 172.23.0.2:55343 length 0
- ...
-
-======================================================
-
-The output indicates that FreeRADIUS is receiving the originating
-connection information from the PROXY Protocol.  FreeRADIUS then
-handles the RadSec requests as though they have been received directly
-from the originating client.
-
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc b/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc
deleted file mode 100644
index f5e7603c10..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc
+++ /dev/null
@@ -1,188 +0,0 @@
-== Enabling RadSec with FreeRADIUS
-
-Our first task is to set up a RadSec server by configuring an instance of
-FreeRADIUS to accept RADIUS over TLS requests.
-
-The following steps should be performed on the host which will be the
-RadSec server, we will call it `radsecsvr`.
-
-You can install FreeRADIUS using the NetworkRADIUS packages by
-following the instructions provided here:
-
-<https://networkradius.com/packages/>
-
-Before making any configuration changes, you should stop the radiusd
-service:
-
-[source,shell]
-----
- service radiusd stop
-----
-
-Then, enable the `tls` virtual server:
-
-[source,shell]
-----
-cd /etc/raddb/sites-enabled
-ln -s ../sites-available/tls
-----
-
-The FreeRADIUS distribution contains an example Certificate Authority
-that will have generated the necessary CA, server and client
-certificates and keys during package installation.  You can use this
-CA, or you can use your own CA and certificates.
-
-[TIP]
-====
-If the example certificates are not present (for example if FreeRADIUS was
-installed from source) then FreeRADIUS will fail to start. The files can be
-regenerated by running `make` in the `/etc/raddb/certs` directory.
-====
-
-Edit the `tls` virtual server configuration, in order to add
-definitions for the clients by extending the `clients radsec {}` section:
-
-.Example radsec client definitions in `/etc/raddb/sites-available/tls`
-====
-
- clients radsec {
-    ...
-        # Direct connections from the test client
-        client radseccli {
-                ipaddr = 172.23.0.2
-                proto = tls
-                virtual_server = default
-        }
-        # Connections via HAproxy
-        client haproxy {
-                ipaddr = 172.23.0.4
-                proto = tls
-                virtual_server = default
-        }
-        # Connections via Traefik
-        client traefik {
-                ipaddr = 172.23.0.5
-                proto = tls
-                virtual_server = default
-        }
- }
-
-====
-
-The client `ipaddr` configuration item is used to match the source IP
-address of incoming connections. You must add client definitions for
-each of the clients which will connect.
-
-For RadSec, you can just list the IP address of the RadSec client.
-This client definition is used for processing RADIUS packets from the
-RadSec client.
-
-[NOTE]
-====
-A `secret` does not have to be specified for RadSec clients, as the
-default is `radsec`.  If you specify a secret, then that will be used
-instead of `radsec`.
-====
-
-When the PROXY protocol is used, you must _also_ define a client which
-matches the IP address of the proxy (haproxy, etc).  This client is
-only used to check that the source IP is permitted to connect to the
-server.  Fields other than `ipaddr` can be specified (and in some
-cases may be required).  However, all other fields will be ignored.
-
-For testing purposes, we want to amend the `default` virtual server so
-that it accepts all authentication reqeusts and immediately responds
-to accounting requests.
-
-Edit the `/etc/raddb/sites-enabled/default` file so that the beginning of
-the `authorize` and `preacct` sections looks as follows:
-
-.Example default virtual server modification to unconditionally accept Access-Requests
-====
-
- authorize {
-     accept
-     ...
- }
- ...
- preacct {
-     handled
-     ...
- }
-
-====
-
-This change makes the `authorize` section always "accept" the user,
-and makes the `preacct` section always say "we handled the accounting
-request".  These changes are only for testing, and should never be
-used in production.
-
-Start the FreeRADIUS service in the foreground with debugging enabled:
-
-[source,shell]
-----
-radiusd -fxxl /dev/stdout
-----
-
-Examine the output from FreeRADIUS to ensure that it is now listening for
-RadSec connection on TCP/2083:
-
-.Example output from running `radiusd -fxxl /dev/stdout`
-====
-
- FreeRADIUS Version 3.0.24
- Copyright (C) 1999-2021 The FreeRADIUS server project and contributors
- ...
- ... : Debug: Listening on auth+acct proto tcp address * port 2083 (TLS) bound to server default
- ... : Debug: Listening on auth address * port 1812 bound to server default
- ... : Debug: Listening on acct address * port 1813 bound to server default
- ... : Debug: Listening on auth address :: port 1812 bound to server default
- ... : Debug: Listening on acct address :: port 1813 bound to server default
- ...
- ... : Info: Ready to process requests
-
-====
-
-FreeRADIUS is now ready to process RadSec traffic.
-
-For testing, we first test normal RADIUS over UDP functionality, then
-the RadSec connection using a test client, then introduce a proxy
-server, and finally we enable PROXY Protocol.  Doing the tests in this
-way ensures that we know that all previous steps work before trying
-the next step.  This process allows us to quickly narrow down
-problems, and gets us to the final goal _faster_ than just "doing
-everything all at once".
-
-=== Testing the RADIUS policy
-
-Before moving on, verify that the FreeRADIUS policy is able to
-authenticate a local test RADIUS Access-Request over UDP:
-
-[source,shell]
-----
-echo "User-Name = terry" | radclient 127.0.0.1 auth testing123
-----
-
-Due to the `accept` we added in the `authorize` section, the expected
-output should be an Access-Accept:
-
-.Expected output from radclient
-===============================
-
- Sent Access-Request Id 157 from 0.0.0.0:36850 to 127.0.0.1:1812 length 27
- Received Access-Accept Id 157 from 127.0.0.1:1812 to 127.0.0.1:36850 length 20
-
-===============================
-
-Any other output indicates that there is a problem with the FreeRADIUS
-configuration which *must* be solved before testing RadSec. Carefully verify that
-you have carried out each of the above steps correctly and examine the debug
-output from FreeRADIUS, which will usually tell you what is wrong.
-
-See [how to read the debug
-output](http://wiki.freeradius.org/radiusd-X) for instructions on
-reading amd understanding the debug output.
-
-The next step is to xref:protocols/proxy/radsec_client.adoc[configure
-FreeRADIUS as a RadSec test client] so that we can verify that our
-RadSec server is working.
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/index.adoc b/doc/antora/modules/howto/pages/protocols/proxy/index.adoc
deleted file mode 100644
index 5100635921..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/index.adoc
+++ /dev/null
@@ -1,126 +0,0 @@
-= Proxying RadSec and enabling PROXY Protocol
-
-This guide shows how to set up FreeRADIUS to serve RadSec connections, fronted
-by either HAproxy or Traefik as Layer 4 proxies that pass on the original
-client connection information using PROXY Protocol.
-
-It is not a comprehensive guide to using RadSec with FreeRADIUS. It presents a
-basic configuration that uses an example CA and does not validate certificate
-attributes or perform revokation status.
-
-
-== Introduction
-
-FreeRADIUS supports receiving RADIUS requests over TLS-enabled TCP connections
-and supports proxying of requests over TCP connections to another TLS-enabled
-homeserver. The protocol for RADIUS over TLS is called "RadSec" and is defined
-in RFC 6614.
-
-FreeRADIUS is a capable and performant application-aware ("Layer 7") proxy /
-load-balancer for RadSec and other forms of RADIUS traffic.
-
-
-=== Layer 4 proxying
-
-Rather than use an application-aware proxy it is sometimes better to reduce the
-performance impact incurred by re-encoding an application protocol by using a
-"Layer 4" proxy that operates at the level of individual connections without
-regard for the application protocol. Such a proxy is more of a "bump in the
-wire" than a request buffer and minimises the latency incurred due to proxying.
-
-It is common to see software such as HAproxy and Traefik used in Layer 4 mode
-in place of FreeRADIUS for purposes such as connection load balancing. In
-addition to improved performance, these tools have the benefit that they
-typically support dynamic service discovery and "hitless" reloads to
-automatically adapt their connection routing based on changes to backend
-services such as the introduction of new nodes with even a momentary loss of
-service.
-
-
-=== Loss of connection information
-
-When TCP connections are relayed through Layer 4 proxies the information
-about the originating source of the connection is no longer known to the
-backend service, unless it is otherwise made available. Identifying the
-originator of connections is often necessary for security purposes and for
-request processing.
-
-Whilst many application protcols support headers that allow proxies to preserve
-connection information these are not helpful in the context of Layer 4
-proxying: The process of populating headers requires knowledge of the
-application protocol to re-encode requests as they are transmitted between the
-frontend and backend connections.
-
-
-=== PROXY Protocol
-
-PROXY Protocol overcomes this limitation by allowing the original connection
-information to be provided to the backend at the start of the TCP connection.
-After this initial data is encoded the remainder of the conversation then
-proceeds as normal. However now that the connection information is known to the
-backend server it is able to process requests made on the connection as though
-the connection were being made directly by the client and not via the proxy.
-
-PROXY Protocol is specified in this document:
-http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
-
-
-== Requirements
-
-PROXY Protocol Version 1 is supported by FreeRADIUS v3.0.24 and later versions.
-
-You will require the following set of VMs or containers, each with their own
-IP address:
-
-[cols="1,1,1"]
-|===
-|Hostname|IP address|Purpose
-
-|radseccli
-|172.23.0.2
-|FreeRADIUS configured to provide a RadSec test client
-
-|radsecsvr
-|172.23.0.3
-|FreeRADIUS configured as a RadSec server
-
-|haproxy
-|172.23.0.4
-|HAproxy in Layer 4 mode to the FreeRADIUS RadSec backend
-|===
-
-Optionally you may want to configure a host to run Traefik within a Docker
-container using host mode networking, perhaps configured by Docker Compose,
-however the installation is beyond the scope of this guide:
-
-[cols="1,1,1"]
-|===
-|traefik
-|172.23.0.5
-|Traefik configured as a TCP router with TLS passthrough to the FreeRADIUS RadSec backend
-|===
-
-The hostnames and IP addresses provided above are for examples purposes and are
-used throughout the remainder of this guide. This guide provides commands and
-output for CentOS. Other distributions will have minor differences, including
-the location of the FreeRADIUS configuration (the "raddb").
-
-[NOTE]
-====
-You can choose to use your own hostname, IP addresses and OS distribution. You
-could also use official Docker images provided by the respecitive projects,
-however these prescribe methods for configuring and managing the services
-that are not typical for a normal package installation which would provide a
-distraction if used for by guide.
-====
-
-
-== Sections in this guide
-
-This guide is organised into four parts that should be read in order:
-
-1. xref:protocols/proxy/enable_radsec.adoc[Enabling RadSec]
-2. xref:protocols/proxy/radsec_client.adoc[Configuring a test RadSec client]
-3. xref:protocols/proxy/radsec_with_haproxy.adoc[Proxying RadSec with HAproxy]
-4. xref:protocols/proxy/radsec_with_traefik.adoc[Proxying RadSec with Traefik]
-5. xref:protocols/proxy/enable_proxy_protocol.adoc[Enabling PROXY Protocol for RadSec]
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc
deleted file mode 100644
index d92345e9e8..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc
+++ /dev/null
@@ -1,181 +0,0 @@
-== Configuring FreeRADIUS as a RadSec test client
-
-Unfortunately, the `radclient` program does not support RadSec.  We
-must therefore configure an instance of FreeRADIUS as a "transport
-converter" which proxies UDP-based RADIUS requests to a RadSec
-destination of our choice.
-
-The following steps should be performed on a client system, which we
-will call `radseccli`.  This system should be a new system, with a
-different IP address.  That is, you shoudl not edit the configuration
-on the `radsecsvr` host.  Doing so will break the RadSec configuration.
-
-Install FreeRADIUS using the NetworkRADIUS packages by following the
-instructions provided here:
-
-<https://networkradius.com/packages/>
-
-Before making any configuration changes, you should stop the radiusd
-service:
-
-[source,shell]
-----
- service radiusd stop
-----
-
-Add a new `tls` home server definition, which will point to the RadSec
-server.  We do this by creating a file
-`/etc/raddb/sites-enabled/radsec-homeserver` with the following
-contents:
-
-.Example homeserver, pool and realm definitions for the RadSec service
-====
-
- home_server tls {
-         ipaddr = 172.23.0.3    # IP address of our RadSec server
-         port = 2083
-         type = auth+acct
-         proto = tcp
-         tls {
-             private_key_password = whatever
-             private_key_file = ${certdir}/client.pem
-             certificate_file = ${certdir}/client.pem
-             ca_file = ${cadir}/ca.pem
-          }
- }
- home_server_pool tls {
-         type = fail-over
-         home_server = tls
- }
- realm tls {
-         auth_pool = tls
-         acct_pool = tls
- }
-
-====
-
-[TIP]
-====
-Complete descriptions of each of the above configuration items can be found in the
-`[raddb]/sites-available/tls` example configuration file.  For simple tests, however,
-we can omit all of the comments from the file.
-====
-
-To use this `tls` home server, we change the `default` virtual server to proxy
-all authentication and accounting requests to it.
-
-Edit the `/etc/raddb/sites-enabled/default` file so that the beginning of
-the `authorize` and `preacct` sections looks as follows:
-
-.Example default virtual server modification to proxy requests to a RadSec proxy server
-====
-
- authorize {
-     update control {
-         &Proxy-To-Realm := tls
-     }
-     handled
-     ...
- }
- ...
- preacct {
-     update control {
-         &Proxy-To-Realm := tls
-     }
-     handled
-     ...
- }
-
-====
-
-These changes make the `tls` virtual server always proxy packets.
-These changes are only for testing, and should never be used in
-production.
-
-We must now copy the example CA certificate as well as the client
-certificate and key files which are on the `radsecsrv` host to this
-test client.
-
-Replace the following files on `radseccli` with the equivalent files from
-`radsecsrv`:
-
-[cols="1,1,1"]
-|===
-|File|Corresponding configuration item|Purpose
-
-|/etc/raddb/certs/ca.pem
-|`ca_file`
-|CA certificate which is used to authenticate the server certificate presented by the RadSec server to the client.
-
-|/etc/raddb/certs/client.pem
-|`certificate_file`
-|Client certificate (signed by the CA certificate) that is presented by the test client to the RadSec server.
-
-|/etc/raddb/certs/client.pem
-|`private_key_file` and `private_key_password`
-|Private key corresponding to the client certificate
-|===
-
-Note that the client certificate and key are typically bundled into a single file.
-
-[CAUTION]
-====
-If you do not correctly replace the CA, client certificate, and key
-material on the test client then the RadSec client and RadSec server
-will fail to mutually authenticate each other as they do not share a
-trusted CA.  If you see messages like `unknown CA`, then you know that
-the certificates have not been set up correctly.
-====
-
-Start the FreeRADIUS service in debug mode:
-
-[source,shell]
-----
-radiusd -X
-----
-
-
-=== Testing RadSec connectivity
-
-At this stage you should be able to cause the test client to send RadSec
-requests directly to the RadSec server.
-
-Run the following to send a RADUS (UDP) Access-Request to the local FreeRADIUS
-instance.  It  should then proxy the request over RadSec connection to
-the remote RadSec server:
-
-[source,shell]
-----
- echo "User-Name = bob" | radclient 127.0.0.1 auth testing123
-----
-
-If the test client is able to successfully establish the RadSec
-connection, and the RadSec server replies with an Access-Accept
-response, then the output will be as follows:
-
-.Expected output from radclient
-===============================
-
- Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27
- Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39
-
-===============================
-
-Lack of response or an Access-Reject response indicates that the RadSec
-connection is not being established successfully.
-
-There may be serveral reasons for broken connectivity including:
-
-  * The client not accepting the certificate presented by the server.
-  * The server not accepting the certificate presented by the client.
-
-Look at the debug output generated by both the test client and the RadSec
-server. In many cases it will tell you exactly what the problem is.
-
-Do not proceed with any further steps until direct connections between the
-RadSec client and Radsec Server are working properly.
-
-Once things are working we are ready to
-xref:protocols/proxy/radsec_with_haproxy.adoc[configure HAproxy to proxy RadSec
-connections] or to xref:protocols/proxy/radsec_with_traefik.adoc[configure
-Traefik to proxy RadSec connections].
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc
deleted file mode 100644
index e58abfe53b..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc
+++ /dev/null
@@ -1,134 +0,0 @@
-== Proxying RadSec with HAproxy
-
-This section shows how to configure HAproxy to proxy RadSec connections.
-
-The following steps should be performed on the `haproxy` host, unless otherwise
-stated.
-
-Install the HAproxy package supplied with the OS distribution:
-
-[source,shell]
-----
- yum install haproxy
-----
-
-Stop the haproxy service:
-
-[source,shell]
-----
- service haproxy stop
-----
-
-Modify the haproxy configuration (typically `/etc/haproxy/haproxy.conf`) so
-that it includes new frontend and backend configuration for the radsec service:
-
-.Example minimal HAproxy configuration
-======================================
-
- global
-     maxconn     100
- defaults
-     mode tcp
-     timeout connect 10s
-     timeout client 30s
-     timeout server 30s
- frontend radsec_fe
-     bind *:2083
-     default_backend radsec_be
- backend radsec_be
-     balance roundrobin
-     server radsecsvr 172.23.0.3:2083
-
-======================================
-
-Note the `mode tcp` directive which tells HAproxy to act as a Layer 4
-proxy, so that it doesn't attempt to perform SSL termination or
-decode the RADIUS protocol.
-
-[NOTE]
-====
-The above example is a minimal configuration. In practise you will want to
-retain many of the HAproxy configuration items already present in the
-configuration (e.g. `log`, `chroot`, `user`, `group`), but these vary across
-distributions. Other HTTP-related options that may already exist in the
-configuration will conflict with `mode tcp` (Layer 4 proxying) and should be
-removed if HAproxy complains about them.
-
-However, you should first get things working with the minimal
-configuration which is known to work, and then make customisations.
-If you start off with a complex configuration, then there may be a
-large number of things which are broken, and debugging them all will
-be difficult.  Start simple, and then add complexity!
-====
-
-Restart the haproxy service in foreground mode for debugging purposes:
-
-[source,shell]
-----
-haproxy -f /etc/haproxy/haproxy.cfg -db
-----
-
-
-=== Testing RadSec connectivity via HAproxy
-
-Now edit the test RadSec client, so that instead of making connections directly
-to the RadSec server it makes connections to the HAproxy server.
-
-On `radseccli` edit the `/etc/raddb/sites-enabled/tls` file, and set
-the IP address to the address of the `haproxy` host.
-
-.Example updated test client homeserver configuration
-=====================================================
-
- home_server tls {
-         ipaddr = 172.23.0.4    # Updated from radsecsvr to haproxy
-         ...
- }
-
-=====================================================
-
-Restart the debug mode session:
-
-[source,shell]
-----
-radiusd -X
-----
-
-Perform a test authentication:
-
-[source,shell]
-----
- echo "User-Name = bob" | radclient 127.0.0.1 auth testing123
-----
-
-If the test client is able to successfully establish the RadSec
-connection via HAproxy, and the RadSec server replies with an
-Access-Accept response, then the output will be as follows:
-
-.Expected output from radclient
-===============================
-
- Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27
- Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39
-
-===============================
-
-HAproxy should also log a message that indicates that the connection was
-proxied, such as the following:
-
-.Expected output from HAproxy
-=============================
-
- <150>...: Connect from 172.23.0.2:50087 to 172.23.0.4:2083 (radius_fr/TCP)
-
-=============================
-
-Any other output from radclient or HAproxy indicates that there is a
-problem with the HAproxy configuration, or that FreeRADIUS is not
-accepting connection from the `haproxy` host, which must be solved
-before continuing.
-
-Once proxied connections are working we are ready to
-xref:protocols/proxy/enable_proxy_protocol.adoc[enable the PROXY
-Protocol] on both HAproxy and the RadSec server.
-
diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc
deleted file mode 100644
index 11030e92fd..0000000000
--- a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc
+++ /dev/null
@@ -1,128 +0,0 @@
-== Proxying RadSec with Traefik
-
-This section shows how to configure Traefik to proxy RadSec connections. You
-should skip this section if you are not using Traefik as your proxy.
-
-Installing Traefik is beyond the scope of this guide. It is typically installed
-as a service mesh router within a Docker or Kubernetes environment using
-offical Docker images.
-
-Traefik configuration has two components of interest:
-
-  * Static configuration: Defines "entrypoints" on which Traefik listens for connections.
-  * Dynamic configuration: Defines backend service components and the routing policy.
-
-Traefik supports a number of providers of dynamic configuration data for the
-router and service definitions. For demonstration purposes the files provider
-is used here, however you can switch to another provide once you have things
-working using this method.
-
-The static configuration can be provided by starting Traefik with the following
-arguments:
-
-.Example Traefik static configuration
-=====================================
-
-  traefik \
-    --log.level=DEBUG \
-    --providers.file.filename=/etc/traefik/dynamic_config.yml
-    --providers.file.watch=true
-    --entryPoints.radsec.address=:2083
-
-=====================================
-
-Note that a `radsec` entrypoint is defined to listen on port 2083 and that a
-static `file` provider is used to defined the dynamic services.
-
-The backend for RadSec should be defined in this file as follows:
-
-.Example Traefik dynamic configuration
-======================================
-
- tcp:
-   routers:
-     radsec-router:
-       entryPoints:
-         - radsec
-       rule: "HostSNI(`*`)"
-       service: "radsec-service"
-       tls:
-         passthrough: true
-   services:
-     radsec-service:
-       loadBalancer:
-         servers:
-         - address: "172.23.0.3:2083"
-
-======================================
-
-Note the `passthrough: true` directive under `tls:` which tells Treafik not to
-attempt TLS termination which it would otherwise perform for all incoming TLS
-connections. We require that the connection is passed through from the RadSec
-client to the RadSec server without being reterminated since the end client's
-certificate is authenticated by the RadSec server and many be used for
-policy decisions.
-
-
-=== Testing RadSec connectivity via Traefik
-
-Now amend the test RadSec client so that instead of making connections directly
-to the RadSec server it makes them via Traefik.
-
-On `radseccli` amend `/etc/raddb/sites-enabled/tls` and set the IP address to
-that of the `traefik` host.
-
-.Example updated test client homeserver configuration
-=====================================================
-
- home_server tls {
-         ipaddr = 172.23.0.5    # Updated from radsecsvr to traefik
-         ...
- }
-
-=====================================================
-
-Restart the debug mode session:
-
-[source,shell]
-----
-radiusd -X
-----
-
-Perform a test authentication:
-
-[source,shell]
-----
- echo "User-Name = bob" | radclient 127.0.0.1 auth testing123
-----
-
-If the test client is able to successfully establish the RadSec connection via
-Traefik and the RadSec server replies with an Access-Accept response then the
-output will be as follows:
-
-.Example output from radclient
-==============================
-
- Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27
- Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39
-
-==============================
-
-Traefik should also log a message that indicates that the connection was
-proxied, such as the following:
-
-.Example output from Traefik
-============================
-
- time="..." level=debug msg="Handling connection from 172.23.0.2:57367"
-
-============================
-
-Any other output from radclient or Traefik indicates that there is a problem
-with the Traefik configuration or that FreeRADIUS is not accepting connection
-from the `traefik` host, which must be solved before continuing.
-
-Once proxied connections are working we are ready to
-xref:protocols/proxy/enable_proxy_protocol.adoc[enable the PROXY Protocol] on
-both Traefik and the RadSec server.
-
diff --git a/doc/antora/modules/unlang/pages/attr.adoc b/doc/antora/modules/unlang/pages/attr.adoc
index 373434196e..70afce41af 100644
--- a/doc/antora/modules/unlang/pages/attr.adoc
+++ b/doc/antora/modules/unlang/pages/attr.adoc
@@ -3,7 +3,7 @@
 .Syntax
 [source,unlang]
 ----
-&Attribute-Name
+[&]Attribute-Name
 ----
 
 The `&Attribute-Name` operator returns a reference to the named
@@ -33,8 +33,8 @@ looks in the input packet list for the named attribute.
 
 [source,unlang]
 ----
-&request.User-Name
-&reply.NAS-IP-Address
+&request:User-Name
+&reply:NAS-IP-Address
 ----
 
 == Array References
@@ -42,7 +42,7 @@ looks in the input packet list for the named attribute.
 .Syntax
 [source,unlang]
 ----
-&Attribute-Name[<integer]
+&Attribute-Name[<integer>]
 ----
 
 When an attribute appears multiple times in a list, this syntax allows
@@ -55,40 +55,7 @@ etc.
 [source,unlang]
 ----
 &EAP-Message[1]
-&reply.NAS-IP-Address[2]
-----
-
-== Parent / child references
-
-.Syntax
-[source,unlang]
-----
-&Attribute-Name.Attribute-Name
-----
-
-In some cases, attributes are nested or grouped.  The child reference
-syntax is used to address a particular child attribute.  Multiple
-levels of parent / child nesting can be performed by simply appending
-the name of another child attribute, separated by the `.` character.
-
-.Examples
-
-[source,unlang]
-----
-&TLV-One.Child-one
-&TLV-One.Child-one.Child-Three
-&reply.TLV[3].Child-Two
-----
-
-== Putting it All Together
-
-All of the above syntaxes can be used interchangeably, at any nesting level.  The examples are presented
-
-.Examples
-[source,unlang]
-----
-&TLV-One.Child-one[3].Child-Three
-&reply.TLV[3].Child-Two
+&reply:NAS-IP-Address[2]
 ----
 
 == Removing ambuguity from the configuration files
diff --git a/doc/modules/rlm_eap b/doc/modules/rlm_eap
index 122d14f0d4..4f903af5bc 100644
--- a/doc/modules/rlm_eap
+++ b/doc/modules/rlm_eap
@@ -156,20 +156,9 @@ EAP SIM server
 
 EAP CLIENTS
 
-  1. XSupplicant - freeradius (EAP/TLS) notes may be found at:
+  1.  eapol_test, from wpa_supplicant.
 
-   http://www.eax.com/802/
-   or http://www.missl.cs.umd.edu/wireless/eaptls/
-
-   XSupplicant is hosted by:
-
-   http://www.open1x.org/
-
-  2. XP - freeradius (EAP/TLS) notes may be found at:
-
-   http://www.denobula.com/EAPTLS.pdf
-
-  3. Freeradius has an "radeapclient" that can do EAP-MD5 (passwords),
+  2. Freeradius has an "radeapclient" that can do EAP-MD5 (passwords),
      as well as EAP-SIM. It is in modules/rlm_eap/radeapclient.
 
 TESTING
diff --git a/raddb/all.mk b/raddb/all.mk
index 3a4b3ea5ac..a7f4f14b3f 100644
--- a/raddb/all.mk
+++ b/raddb/all.mk
@@ -7,7 +7,7 @@ LOCAL_FILES :=		clients.conf dictionary templates.conf experimental.conf \
 DEFAULT_SITES :=	default inner-tunnel
 LOCAL_SITES :=		$(addprefix raddb/sites-enabled/,$(DEFAULT_SITES))
 
-DEFAULT_MODULES :=	always attr_filter cache_eap chap date \
+DEFAULT_MODULES :=	always attr_filter chap date \
 			detail detail.log digest dynamic_clients eap \
 			echo exec expiration expr files linelog logintime \
 			mschap ntlm_auth pap passwd preprocess radutmp realm \
diff --git a/raddb/certs/realms/README.md b/raddb/certs/realms/README.md
index 0922403f24..9f754fac19 100644
--- a/raddb/certs/realms/README.md
+++ b/raddb/certs/realms/README.md
@@ -174,7 +174,28 @@ For this functionality to work, the certificates for EAP and RadSec
 ### Clients
 
 RadSec clients can set the SNI to send in the `tls` subsection of the
-`home_server` definition.  See `sites-available/tls` for examples.
+`home_server` definition.  Look for "SNI" in `sites-available/tls`,
+and see the `hostname` configuration item for documentation.
+
+For example, an identity provider could host multiple sites, but
+present itself with one public IP address.  If the RadSec clients do
+not use SNI, then they must be configured with the certificate of the
+identity provider.
+
+When SNI is used, the RadSec clients can be configured with the
+certificate of the hosted system that they're connecting to.  This
+ability means that there is no need to change certificates when
+changing providers.  In addition, there is no need to change the
+configuration of all RadSec clients when the hosting system changes
+its certifiates.  Because the hosting system certificates are never
+used.
+
+Instead, each hosted company is responsible for its own certificates,
+and for its own RadSec clients.
+
+SNI also permits the use of a load balancer such as haproxy.  That
+load balancer can terminate the TLS connection, and then use SNI to
+route the underlying RADIUS TCP traffic to a particular host.
 
 ### Servers
 
diff --git a/raddb/mods-available/cache_auth b/raddb/mods-available/cache_auth
new file mode 100644
index 0000000000..7485f36d9c
--- /dev/null
+++ b/raddb/mods-available/cache_auth
@@ -0,0 +1,116 @@
+# -*- text -*-
+#
+#  $Id$
+
+#  This file contains a collection of cache module configurations
+#  which have been designed to be used to cache accepts, rejects, and
+#  LDAP User DNs.  The main use of these modules is Google Secure
+#  LDAP.
+#
+#  In scenarios where there is repeated authentication requests for the same
+#  user within a short time frame (e.g. 802.1x wifi), these modules can help to
+#  compensate for slow responses from poor LDAP servers (i.e. Google).
+#
+#  See also mods-available/ldap_google, and sites-available/google-ldap-auth.
+#
+#  The configurations in this file can be used for non-Google LDAP
+#  servers, too.
+#
+
+
+#
+#  This instance of the cache module caches successful
+#  authentications.
+#
+#  The TTL controls how often the authentication will be cached.  
+#
+#  In addition, if group membership is used as part of the policy, the
+#  &control:LDAP-Group attribute should be added to the "update: section here.
+#
+#  If a user's authentication is found in the cache, then any data
+#  which is normally retrieved from LDAP for local policies must also
+#  be stored in the cache via the "update" section.
+#
+cache cache_auth_accept {
+	driver = "rlm_cache_rbtree"
+	key = "%{md5:%{%{Stripped-User-Name}:-%{User-Name}}%{User-Password}}"
+	ttl = 7200
+	update {
+		#	
+		#  We need to cache something, so we just cache
+		#  a random attribute.  This attribute is not used
+		#  for anything else, just as a "place-holder" to
+		#  contain a cache entry.
+		#
+		#  If you add other attributes to this update section, then
+		#  this attribute can be deleted.
+		#
+		&control:User-Category = "success"
+	}
+}
+
+
+#
+#  This instance of the cache module caches failed authentications.
+#
+#  In many cases, rejected users will repeatedly try to authenticate.
+#  These repeated authentication attempts can cause significant load
+#  on the system.  By caching the reject, we can avoid hitting the database.
+#
+#  We index the cache by a hash of the client's MAC and the user name
+#  and password.  If a user corrects their user name or password, then
+#  that authentication attempt won't hit the cache, and their
+#  credentials will be immediately checked against the database.
+#
+#  The TTL controls how long a combination of device / user and
+#  password wil be rejected without looking at the database.  Once the
+#  cache entry expires, the server will delete the cache entry, and
+#  contact the database.
+#
+cache cache_auth_reject {
+        driver = "rlm_cache_rbtree"
+        key = "%{md5:%{Calling-Station-Id}%{Stripped-User-Name}%{User-Password}}"
+        ttl = 3600
+        update {
+		#	
+		#  We need to cache something, so we just cache
+		#  a random attribute.  This attribute is not used
+		#  for anything else, just as a "place-holder" to
+		#  contain a cache entry.
+		#
+		&control:User-Category = "failure"
+        }
+}
+
+
+#
+#  An instance of the cache module which caches the LDAP user DN.
+#
+#  If LDAP authentication is being used for a simple auth / reject without
+#  any need to retrieve other attributes (e.g. group membership), each LDAP
+#  bind authentication is three steps
+#
+#    - bind as admin user
+#    - lookup user's DN
+#    - bind as user using retrieved DN
+#
+#  By caching the DN after the first LDAP querry, the first two steps
+#  are skipped on subsequent authentications.
+#
+#  If an alternative attribute name is being used for the user DN, you
+#  should change the update section here appropriately.  But that is
+#  likely rare.
+#
+#  In scenarios where DNs may change, consideration should be given as
+#  to whether use of this cache may create issues.  i.e. if the cache
+#  doesn't help, then don't use it.
+#
+cache cache_ldap_user_dn {
+	driver = "rlm_cache_rbtree"
+	key = "%{Stripped-User-Name}"
+	ttl = 86400
+	update {
+		&control:LDAP-UserDN = &control:LDAP-UserDN
+	}
+}
+
diff --git a/raddb/mods-available/cache_eap b/raddb/mods-available/cache_eap
deleted file mode 100644
index 376fc5b520..0000000000
--- a/raddb/mods-available/cache_eap
+++ /dev/null
@@ -1,13 +0,0 @@
-#
-#	Cache EAP responses for resiliency on intermediary proxy fail-over
-#
-cache cache_eap {
-	key = "%{%{control:State}:-%{%{reply:State}:-%{State}}}"
-
-	ttl = 15
-
-	update reply {
-		reply: += &reply:
-		&control:State := &request:State
-	}
-}
diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap
index 61be516b1a..7eba82c7f4 100644
--- a/raddb/mods-available/eap
+++ b/raddb/mods-available/eap
@@ -270,12 +270,16 @@ eap {
 		#
 	#	psk_query = "%{sql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}"
 
-		#  For DH cipher suites to work, you have to
-		#  run OpenSSL to create the DH file first:
+		#  For DH cipher suites to work in OpenSSL < 1.1.0,
+		#  you have to run OpenSSL to create the DH file
+		#  first:
 		#
 		#    openssl dhparam -out certs/dh 2048
 		#
-		dh_file = ${certdir}/dh
+		#  For OpenSSL >= 1.1.0, just leave this commented
+		#  out, and OpenSSL will do the right thing.
+		#
+	#	dh_file = ${certdir}/dh
 
 		#  If your system doesn't have /dev/urandom,
 		#  you will need to create this file, and
@@ -453,6 +457,12 @@ eap {
 		#  In order to enable TLS 1.0 and TLS 1.1, you may
 		#  also need to update cipher_list below to:
 		#
+		#   * OpenSSL >= 3.x
+		#
+		#	cipher_list = "DEFAULT@SECLEVEL=0"
+		#
+		#   * OpenSSL < 3.x
+		#
 		#	cipher_list = "DEFAULT@SECLEVEL=1"
 		#
 		#  The values must be in quotes.
@@ -493,7 +503,7 @@ eap {
 		#
 		#	openssl ecparam -list_curves
 		#
-		ecdh_curve = "prime256v1"
+		ecdh_curve = ""
 
 		#  Session resumption / fast reauthentication
 		#  cache.
@@ -725,6 +735,20 @@ eap {
 			#
 		#	softfail = no
 		}
+
+		#
+		#  The server can present different certificates based
+		#  on the realm presented in EAP.  See
+		#  raddb/certs/realms/README.md for examples of how to
+		#  configure this.
+		#
+		#  Note that the default is to use the same set of
+		#  realm certificates for both EAP and RadSec!  If
+		#  this is not what you want, you should use different
+		#  subdirectories or each, e.g. ${certdir}/realms/radsec/,
+		#  and ${certdir}/realms/eap/
+		#
+	#	realm_dir = ${certdir}/realms/
 	}
 
 
diff --git a/raddb/mods-available/expr b/raddb/mods-available/expr
index 43dbea35e4..4cecfca478 100644
--- a/raddb/mods-available/expr
+++ b/raddb/mods-available/expr
@@ -130,12 +130,18 @@
 #  nexttime	calculate number of seconds until next n hour(s), day(s), week(s), year(s)
 #		if it were 16:18, %{nexttime:1h} would expand to 2520
 #
+#  lasttime	calculate number of seconds until last n hour(s), day(s), week(s), year(s)
+#		if it were 16:18, %{lasttime:1h} would expand to 4680
+#
 #  lpad		left-pad a string
 #		if User-Name is "foo": "%{lpad:&User-Name 6 x}" == "xxxfoo"
 #
 #  rpad		right-pad a string
 #		if User-Name is "foo": "%{rpad:&User-Name 5 -}" == "foo--"
 #
+#  concat 	concatenate a set of attributes, separated by a character.
+#		"%{concat:foo[*] ;}"
+#
 
 expr {
 	#
diff --git a/raddb/mods-available/ldap_google b/raddb/mods-available/ldap_google
new file mode 100644
index 0000000000..03c98d38ff
--- /dev/null
+++ b/raddb/mods-available/ldap_google
@@ -0,0 +1,262 @@
+# -*- text -*-
+#
+#  $Id$
+
+#
+#  This file contains an instance of the ldap module which has been
+#  configured for the G Suite / Google Workspace Secure LDAP server.
+#  There are a few steps which still need to be taken, but they are
+#  documented clearly below.
+#
+#  In order to use the Google LDAP server, a client must first be
+#  created. See Google's documentation for doing this:
+#
+#  https://support.google.com/a/answer/9048434?hl=en&ref_topic=9173976
+#
+#  Google LDAP requires that any system connecting to it use a client
+#  certificate.  However, FreeRADIUS also requires a username and
+#  password in the "ldap" module configuration.  Therere before
+#  downloading the client certificate from Google, you should choose
+#  the option to generate access credentials in order to obtain a
+#  username and password.  That username and password should be used
+#  below.
+#
+#  Ensure the Goolge client configuration which is used for FreeRADIUS
+#  has sufficient permissions to read user information, and, if group
+#  membership is part of the FreeRADIUS policy, ensure that the client
+#  can read group information.  This configuration is done on Google's
+#  systems.  Please see the Google documentation for more information.
+#
+#  NOTE: The Google LDAP database does NOT return user passwords in
+#  the search results!
+#
+#  Therefore, if Google LDAP is being used for authentication, it will
+#  ONLY work when using "LDAP bind as user".  The authentication
+#  method used there MUST also provide the user password in plain
+#  text.  This limits the use of Google LDAP to PAP, and TTLS+PAP.
+#  Anything else simply will not work, and nothing you do will ever
+#  make it work.
+#
+#  The Google LDAP service has been observed to have poor
+#  performance compared to a dedicated / local LDAP server like
+#  OpenLDAP. In order to improve performance, we simply bypass it
+#  completely by caching things associated with accept and reject.
+#  See mods-available/cache_auth for the cache configuration, and
+#  sites-available/google-ldap-auth for a sample virtual server which
+#  uses this module, and the cache.
+#
+#  In addition, if you are using Google LDAP service as part of WiFi
+#  authentication (remember, only TTLS+PAP will work!), then we also
+#  recommend enabling the "cache" configuration in mods-available/eap.
+#  That cache is a separate one from mods-available/cache_auth, and
+#  both caches can be used at the same time.
+#
+#
+#  The comments in this file are specific to using the Google Secure
+#  LDAP service.  For more general LDAP module configuration, see the
+#  mods-available/ldap.
+#
+ldap ldap_google {
+	#  The standard Google LDAP server URL
+	server = 'ldaps://ldap.google.com:636/'
+
+	#  Google LDAP client username and password as generated during
+	#  client creation.
+#	identity = 'myuser'
+#	password = 'mypass'
+
+	#  Base dn for your organisation.
+	base_dn = 'dc=example,dc=org'
+
+	#
+	#  The default Google LDAP schema can be seen here
+	#
+	# 	https://support.google.com/a/answer/9188164
+	#
+	#  Custom attributes can be added to user profiles, and those
+	#  custom attributes can then be accessed in the LDAP
+	#  directory:
+	#
+	#	https://support.google.com/a/answer/6208725
+	#
+	#  You can run the 'ldapsearch' command line tool using the
+	#  parameters from this module's configuration.
+	#
+	#    LDAPTLS_REQCERT=ALLOW \
+	#    LDAPTLS_CERT="<Google certificate file>" \
+	#    LDAPTLS_KEY="<Google key file>" \
+	#    ldapsearch -H ${server}  -b '${base_dn}' '(uid=user)'
+	#
+	#  That command will return the LDAP information for 'user'.
+	#
+	#  Group membership can be queried by using the above "ldapsearch" string,
+	#  and adding "memberof" qualifiers.
+	#
+
+#	valuepair_attribute = 'radiusAttribute'
+
+	update {
+#		reply:Reply-Message		:= 'radiusReplyMessage'
+#		reply:Tunnel-Type		:= 'radiusTunnelType'
+#		reply:Tunnel-Medium-Type	:= 'radiusTunnelMediumType'
+#		reply:Tunnel-Private-Group-ID	:= 'radiusTunnelPrivategroupId'
+
+		control:			+= 'radiusControlAttribute'
+		request:			+= 'radiusRequestAttribute'
+		reply:				+= 'radiusReplyAttribute'
+	}
+
+	#
+	#  In order to use LDAP "bind as user" authentication, you
+	#  should add following "if" statement to the authorize {}
+	#  section of the virtual server, after the "ldap" module.
+	#  For example:
+	#
+	#    ...
+	#    ldap_google
+	#    if ((ok || updated) && User-Password && !control:Auth-Type) {
+	#        update {
+	#            &control:Auth-Type := ldap
+	#        }
+	#    }
+	#    ...
+	#
+	#  You will also need to uncomment the "Auth-Type LDAP" block in the
+	#  "authenticate" section.
+	#
+	#  Note that these configuration steps have already been done
+	#  in the sample virtual server, in
+	#  sites-available/google-ldap-auth.
+	#
+
+	#
+	#  If you change this, you will also need to update the
+	#  "cache_ldap_user_dn" module in mods-available/cache_auth.
+	#
+	user_dn = "LDAP-UserDn"
+
+	#
+	#  User object identification.
+	#
+	user {
+		#  The typical Google LDAP configuration has users under "ou=Users..."
+		base_dn = "ou=Users,${..base_dn}"
+
+		filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
+
+		scope = 'sub'
+
+#		sort_by = '-uid'
+
+#		access_attribute = 'dialupAccess'
+
+#		access_positive = yes
+	}
+
+	#
+	#  User membership checking.
+	#
+	group {
+		#  The typical Google LDAP configuration has groups under "ou=Groups..."
+		base_dn = "ou=Groups,${..base_dn}"
+
+		filter = '(objectClass=posixGroup)'
+
+		scope = 'sub'
+
+		name_attribute = cn
+
+		#
+		#  Google Secure LDAP supports the "memberOf"
+		#  attribute, which is more efficient than using this
+		#  filter.
+		#
+		#  You should also check the permissions of the client
+		#  in Google's systems to ensure that it is allowed to
+		#  read group information.
+		#
+#		membership_filter = "(|(member=%{control:${..user_dn}})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))"
+
+		membership_attribute = 'memberOf'
+
+		#
+		#  If the "memberOf" attribute is used for retrieving group membership,
+		#  then you should also use "cacheable_dn", in orser to cache the group details.
+		#  "memberOf" is a list of fully quallified group DNs which the user belongs to,
+		#  so using the DN for the cache avoids further lookups to retrieve group names.
+		#
+#		cacheable_name = 'no'
+#		cacheable_dn = 'no'
+
+#		cache_attribute = 'LDAP-Cached-Membership'
+
+#		allow_dangling_group_ref = 'no'
+	}
+
+	options {
+#		dereference = 'always'
+
+		#  Google Secure LDAP does not appear to do referrals, so we might as well
+		#  turn this off.
+		chase_referrals = no
+#		rebind = yes
+
+		#  Some reasonable defaults for use with Google Secure LDAP
+		#
+		#  See mods-available/ldap for a complete description
+		#  of what these configuration options mean.
+		#
+		res_timeout = 10
+		srv_timelimit = 3
+		net_timeout = 3
+		idle = 60
+		probes = 3
+		interval = 3
+
+		ldap_debug = 0x0000
+	}
+
+	tls {
+
+		#
+		#  The certificate and key which were downloaded from the Google
+		#  client tools are configured here.
+		#
+		#  By default ${certdir} is raddb/certs/.  You can
+		#  please these files anywhere you want. The only
+		#  requirement is that they are readable by
+		#  FreeRADIUS, and NOT readable by anyone else on the
+		#  system!
+		#
+#		certificate_file = ${certdir}/google/certificate.crt
+#		private_key_file = ${certdir}/google/key.key
+#		random_file = /dev/urandom
+
+		#
+		#  Google Secure LDAP uses a self signed certificate
+		#  so this configuration needs to be set to 'allow'
+		#
+		require_cert	= 'allow'
+
+		#
+		#  We recommend not using TLS 1.0 or 1.1.
+		#
+#		tls_min_version = "1.2"
+	}
+
+	#
+	#  See mods-available/ldap for documentation on the "pool"
+	#  section and its configuration items.
+	#
+	pool {
+		start = ${thread[pool].start_servers}
+		min = ${thread[pool].min_spare_servers}
+		max = ${thread[pool].max_servers}
+		spare = ${thread[pool].max_spare_servers}
+
+		uses = 0
+		retry_delay = 30
+		lifetime = 0
+		idle_timeout = 60
+	}
+}
diff --git a/raddb/mods-available/linelog b/raddb/mods-available/linelog
index dc2a8195b3..4357b747d6 100644
--- a/raddb/mods-available/linelog
+++ b/raddb/mods-available/linelog
@@ -15,6 +15,9 @@ linelog {
 	#
 	#  If the filename is "syslog", then the log messages will
 	#  go to syslog.
+	#
+	#  The output can be directed to stdout by using /dev/stdout
+	#
 	filename = ${logdir}/linelog
 
 	#
diff --git a/raddb/mods-config/sql/counter/mysql/dailycounter.conf b/raddb/mods-config/sql/counter/mysql/dailycounter.conf
index dbfb097cd7..67c7f359de 100644
--- a/raddb/mods-config/sql/counter/mysql/dailycounter.conf
+++ b/raddb/mods-config/sql/counter/mysql/dailycounter.conf
@@ -31,3 +31,16 @@ query = "\
 #	FROM radacct \
 #	WHERE username = '%{${key}}' \
 #	AND acctstarttime BETWEEN FROM_UNIXTIME('%%b') AND FROM_UNIXTIME('%%e')"
+
+#
+#  This query allows retrieving the entries based on a
+#  period that resets on a particular day of the month.
+#
+#reset_day = 21
+#query = "\
+#	SELECT SUM(acctsessiontime) FROM radacct WHERE username = '%{${key}}' AND \
+#		IF (DAY(CURDATE()) >= ${reset_day}, \
+#			acctstarttime > DATE(DATE_FORMAT(NOW(), '%Y-%m-${reset_day}')), \
+#			acctstarttime > DATE(DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-${reset_day}')) \
+#		)"
+#
diff --git a/raddb/mods-config/sql/main/mssql/queries.conf b/raddb/mods-config/sql/main/mssql/queries.conf
index 3001b73ac9..6c39e6de28 100644
--- a/raddb/mods-config/sql/main/mssql/queries.conf
+++ b/raddb/mods-config/sql/main/mssql/queries.conf
@@ -209,7 +209,7 @@ accounting {
 					FramedIPv6Prefix, \
 					FramedInterfaceId, \
 					DelegatedIPv6Prefix \
-					${..class.column_name}) \
+					${....class.column_name}) \
 			VALUES(\
 				'%{Acct-Session-Id}', \
 				'%{Acct-Unique-Session-Id}', \
@@ -279,7 +279,7 @@ accounting {
 					AcctStartDelay, \
 					AcctStopDelay, \
 					XAscendSessionSvrKey \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'%{Acct-Session-Id}', \
 					'%{Acct-Unique-Session-Id}', \
@@ -387,7 +387,7 @@ accounting {
 					DelegatedIPv6Prefix, \
 					AcctStartDelay, \
 					XAscendSessionSvrKey \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'%{Acct-Session-Id}', \
 					'%{Acct-Unique-Session-Id}', \
@@ -488,7 +488,7 @@ accounting {
 					DelegatedIPv6Prefix, \
 					AcctStartDelay, \
 					AcctStopDelay \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'%{Acct-Session-Id}', \
 					'%{Acct-Unique-Session-Id}', \
diff --git a/raddb/mods-config/sql/main/oracle/queries.conf b/raddb/mods-config/sql/main/oracle/queries.conf
index 77eb618ac3..e3dd7670f9 100644
--- a/raddb/mods-config/sql/main/oracle/queries.conf
+++ b/raddb/mods-config/sql/main/oracle/queries.conf
@@ -356,7 +356,7 @@ accounting {
 					AcctStartDelay,	\
 					AcctStopDelay, \
 					XAscendSessionSvrKey \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'', \
 					'%{Acct-Session-Id}', \
@@ -474,7 +474,7 @@ accounting {
 					DelegatedIPv6Prefix, \
 					AcctStartDelay,	\
 					XAscendSessionSvrKey \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'', \
 					'%{Acct-Session-Id}', \
@@ -587,7 +587,7 @@ accounting {
 					DelegatedIPv6Prefix, \
 					AcctStartDelay,	\
 					AcctStopDelay \
-					${..class.column_name}) \
+					${....class.column_name}) \
 				VALUES(\
 					'', \
 					'%{Acct-Session-Id}', \
diff --git a/raddb/policy.d/accounting b/raddb/policy.d/accounting
index 7672e1bf1e..7c52637d11 100644
--- a/raddb/policy.d/accounting
+++ b/raddb/policy.d/accounting
@@ -54,6 +54,10 @@ acct_unique {
 			&Acct-Unique-Session-Id := "%{md5:%{User-Name},%{Acct-Session-ID},%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port}}"
 		 }
 	}
+
+	update request {
+		&Tmp-String-9 !* ANY
+	}
 }
 
 #
diff --git a/raddb/policy.d/eap b/raddb/policy.d/eap
index 17cf87331c..c8dac22f3d 100644
--- a/raddb/policy.d/eap
+++ b/raddb/policy.d/eap
@@ -1,46 +1,3 @@
-#
-#	Response caching to handle proxy failovers
-#
-Xeap.authorize {
-	cache_eap
-	if (ok) {
-		#
-		#	Expire previous cache entry
-		#
-		if (&control:State) {
-			update control {
-				&Cache-TTL := 0
-			}
-			cache_eap
-
-			update control {
-				&State !* ANY
-			}
-		}
-
-		handled
-	}
-	else {
-		eap.authorize
-	}
-}
-
-#
-#	Populate cache with responses from the EAP module
-#
-Xeap.authenticate {
-	eap {
-		handled = 1
-	}
-	if (handled) {
-		cache_eap.authorize
-
-		handled
-	}
-
-	cache_eap.authorize
-}
-
 #
 #       Forbid all EAP types.  Enable this by putting "forbid_eap"
 #       into the "authorize" section.
diff --git a/raddb/proxy.conf b/raddb/proxy.conf
index ac90a27352..f8d28e3518 100644
--- a/raddb/proxy.conf
+++ b/raddb/proxy.conf
@@ -729,6 +729,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.
diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in
index 70c0c32547..69f0367b38 100644
--- a/raddb/radiusd.conf.in
+++ b/raddb/radiusd.conf.in
@@ -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
diff --git a/raddb/sites-available/google-ldap-auth b/raddb/sites-available/google-ldap-auth
new file mode 100644
index 0000000000..3be530f078
--- /dev/null
+++ b/raddb/sites-available/google-ldap-auth
@@ -0,0 +1,225 @@
+# -*- text -*-
+#########################################################################
+#
+#	The file contains a sample virtual server which uses Google
+#	Secure LDAP for authentication
+#
+#	This file is designed to be used as an inner tunnel virtual
+#	server for EAP-TTLS-PAP authentication.
+#
+#	Use this virtual server in conjunction with the sample Google
+#	Secure LDAP module configuration, which is in
+#	mods-available/ldap_google.
+#
+#	Due to the poor performance of Google Secure LDAP, this
+#	configuration also caches information around accepts, rejects,
+#	and LDAP qeuries.  See mods-available/cache_auth for the
+#	configuration of the various "cache" modules used here.
+#
+#	The TTL on these caches should be tuned to match site policies
+#	- e.g. how long should a user be re-authenticated from a cache
+#	without performing an LDAP bind.
+#
+#	Typically the caches are beneficial when performing
+#	authentication for 802.1x wifi where repeated authentications
+#	occur as users roam.  We also recommend enabling the "cache"
+#	subsection of mods-available/eap.  Both kinds of caching can
+#	be done at the same time, and both kinds of caching will help
+#	improve system performance and stability.
+#
+#	$Id$
+#
+#########################################################################
+
+server google-ldap {
+
+#
+#  This is only for testing, and not needed in general operation.
+#
+listen {
+	ipaddr = 127.0.0.1
+	port = 18123
+	type = auth
+}
+
+authorize {
+	#
+	#  Perform sanity checks on the supplied user name
+	#
+	filter_username
+
+	#
+	#  Perform sanity checks comparing inner and outer user name
+	#
+	filter_inner_identity
+
+	#
+	#  Split up user names in the form user@domain
+	#
+	split_username_nai
+
+	#
+	#  Check the authentication cache to see if this user
+	#  recently sucessfully authenticated
+	#
+	update control {
+		&Cache-Status-Only := 'yes'
+	}
+	cache_auth_accept
+
+	#
+	#  If there's a cached User-Name / User-Password which matches
+	#  what the user sent here, then the user has been
+	#  authenticated.  We can then avoid interacting with Google's
+	#  LDAP server, which significantly improves the performance
+	#  of user authentication.
+	#
+	if (ok) {
+		update {
+			&control:Auth-Type := Accept
+		}
+		return
+	}
+
+	#
+	#  Check the reject cache to see if this user was
+	#  recently rejected
+	#
+	update control {
+		&Cache-Status-Only := 'yes'
+	}
+	cache_auth_reject
+
+	#
+	#  If there's a cached User-Name / User-Password which matches
+	#  what the user sent here, then the user has been rejected.
+	#  As with authentication above, we don't need to check
+	#  Google's LDAP server, and can improve performance.
+	#
+	#  Note that in may cases rejected users will try over and
+	#  over again.  This increased load can significantly affect
+	#  performance, and can even prevent other users from
+	#  authenticating!  The solution is to just tell the bad users
+	#  to "go away" as quickly as possible, while using minimal
+	#  resources.
+	#
+	if (ok) {
+		update {
+			&Module-Failure-Message := "Rejected by cache entry"
+		}
+		reject
+	}
+
+	#
+	#  If group membership checks are required, then ensure that
+	#  the relevant "cacheable_" option is set against the ldap
+	#  instance, and call the ldap module here.
+	#
+	#  If group membership is irrelevant, do not call ldap here
+	#  to improve performance
+	#
+	# ldap_google
+
+	#
+	#  As Google LDAP does not return user passwords,
+	#  authentication is only possible by LDAP "bind as user".  So
+	#  only PAP and TTLS+PAP will work.
+	#
+	#  If the request contains a password, then force LDAP "bind
+	#  as user".
+	#
+	if (&User-Password && !control:Auth-Type)  {
+		update {
+			&control:Auth-Type := ldap
+		}
+
+		#
+		#  Look up a user's DN in the cache.
+		#
+		#  The standard ldap auth mechanism is 3 steps
+		#    - bind as admin user
+		#    - lookup the user's DN
+		#    - bind as the user
+		#
+		#  Caching the DN removes the first two steps
+		#  during the lifetime of the cache entry.
+		#
+		#  If the ldap module is called above, then this cache
+		#  call can be commented out; the DN will have been
+		#  retrieved above by the "ldap_google" module.
+		#
+		update control {
+			&Cache-Read-Only := "yes"
+		}
+		cache_ldap_user_dn
+
+	}
+}
+
+authenticate {
+	#
+	#  Use an LDAP "bind as user" to authenticate.  Google will
+	#  check the users' password, and will return success / fail.
+	#
+	Auth-Type LDAP {
+		ldap_google
+	}
+
+}
+
+#
+#  Google LDAP has no specific session section configuration
+#
+session {
+
+}
+
+#
+#  In post-auth the various caches get updated.
+#
+#  Add in any additional policy required to set reply attributes
+#
+post-auth {
+	#
+	#  Cache the user's DN.  See the authorize section for
+	#  how and why this would be used
+	#
+	cache_ldap_user_dn
+
+	#
+	#  If a user was authenticated by ldap, add the users name /
+	#  password to the cache of successful authentications.
+	#
+	#  Otherwise the user was authenticated via the
+	#  cache_auth_accept call above, in the "authorize" section.
+	#
+	if (&control:Auth-Type == ldap) {
+		cache_auth_accept
+	}
+
+	Post-Auth-Type REJECT {
+		attr_filter.access_reject
+
+		#
+		#  Record rejects in a cache, as a protection against
+		#  repeated attempts from mis-configured clients.
+		#
+		if (&control:Auth-Type == ldap) {
+			cache_auth_reject
+		}
+
+		#
+		#  Clear the DN cache entry if it exists.
+		#  If the DN cache is in use, retaining an incorrect
+		#  DN entry could cause issues if the user's DN
+		#  has changed.
+		#
+		update control {
+			&Cache-TTL := 0
+		}
+		cache_ldap_user_dn
+
+	}
+}
+
+}
diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls
index d52c785acf..57c969d491 100644
--- a/raddb/sites-available/tls
+++ b/raddb/sites-available/tls
@@ -64,29 +64,6 @@ listen {
 
 	clients = radsec
 
-	#
-	#  Use the haproxy "PROXY protocol".
-	#
-	#  This configuration allows for many FreeRADIUS servers to be
-	#  behind a haproxy server.  The "PROXY protocol" allows
-	#  haproxy to send the actual client IP to FreeRADIUS.
-	#
-	#  This will work ONLY for RadSec (TLS).  Both the haproxy AND
-	#  the RadSec client MUST be listed as allowed RADIUS clients.
-	#
-	#  haproxy needs to have "send-proxy" configured for this server.
-	#  Health checks should be turned off, as haproxy does not
-	#  support RADIUS health checks.
-	#
-	#  The main use of this feature is for scalability.  There is no
-	#  longer any need to have a RADIUS proxy as a load balancer.
-	#  haproxy is fast, stable, and supports dynamic reloads!
-	#
-	#  The only problem is that many RADIUS clients do not support
-	#  RadSec.  That situation will hopefully change over time.
-	#
-#	proxy_protocol = no
-
 	#
 	#  When this is set to "yes", new TLS connections
 	#  are processed through a section called
@@ -196,13 +173,16 @@ listen {
 		#  this configuration item.
 		ca_file = ${cadir}/ca.pem
 
+		#  For DH cipher suites to work in OpenSSL < 1.1.0,
+		#  you have to run OpenSSL to create the DH file
+		#  first:
 		#
-		#  For DH cipher suites to work, you have to
-		#  run OpenSSL to create the DH file first:
+		#    openssl dhparam -out certs/dh 2048
 		#
-		#  	openssl dhparam -out certs/dh 1024
+		#  For OpenSSL >= 1.1.0, just leave this commented
+		#  out, and OpenSSL will do the right thing.
 		#
-		dh_file = ${certdir}/dh
+	#	dh_file = ${certdir}/dh
 
 		#
 		#  If your system doesn't have /dev/urandom,
@@ -438,6 +418,20 @@ listen {
 			#  returns.
 	#    		client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}"
 		}
+
+		#
+		#  When the RadSec clients use SNI, the server will
+		#  automatically choose the correct certificate from
+		#  "realm_dir".  See raddb/certs/realms/README.md for
+		#  more information.
+		#
+		#  Note that the default is to use the same set of
+		#  realm certificates for both EAP and RadSec!  If
+		#  this is not what you want, you should use different
+		#  subdirectories or each, e.g. ${certdir}/realms/radsec/,
+		#  and ${certdir}/realms/eap/
+		#
+	#	realm_dir = ${certdir}/realms/
 	}
 }
 
@@ -500,13 +494,27 @@ home_server tls {
 	tls {
 		#
 		#  Similarly to HTTP, the client can use Server Name
-		#  Indication to inform the RadSec server of which
+		#  Indication to inform the RadSec server as to which
 		#  domain it is requesting.  This selection allows
 		#  multiple sites to exist at the same IP address.
 		#
-		#  For example, and identity provider could host
+		#  For example, an identity provider could host
 		#  multiple sites, but present itself with one public
-		#  IP address.
+		#  IP address.  If the RadSec clients do not use SNI,
+		#  then they must be configured with the certificate
+		#  of the identity provider.
+		#
+		#  When SNI is used, the clients can be configured
+		#  with the certificate of the hosted system that
+		#  they're connecting to.  This ability means that
+		#  there is no need to change certificates when
+		#  changing providers.  In addition, there is no need
+		#  to change the configuration of all RadSec clients
+		#  when the hosting system changes its certifiates.
+		#  Because the hosting system certificates are never used.
+		#
+		#  Instead, each hosted company is responsible for its
+		#  own certificates, and for its own clients.
 		#
 		#  SNI also permits the use of a load balancer such as
 		#  haproxy.  That load balancer can terminate the TLS
diff --git a/redhat/freeradius.spec b/redhat/freeradius.spec
index b6d09edc13..751ab32ee2 100644
--- a/redhat/freeradius.spec
+++ b/redhat/freeradius.spec
@@ -30,7 +30,7 @@
 
 Summary: High-performance and highly configurable free RADIUS server
 Name: freeradius
-Version: 3.0.25
+Version: 3.0.26
 Release: 1%{?dist}
 License: GPLv2+ and LGPLv2+
 Group: System Environment/Daemons
@@ -420,6 +420,7 @@ perl -i -pe 's/^#group =.*$/group = radiusd/' $RADDB/radiusd.conf
 # logs
 mkdir -p $RPM_BUILD_ROOT/var/log/radius/radacct
 touch $RPM_BUILD_ROOT/var/log/radius/{radutmp,radius.log}
+install -m 755 scripts/raduat $RPM_BUILD_ROOT/%{_bindir}/raduat
 
 # For systemd based systems, that define _unitdir, install the radiusd unit
 %if %{?_unitdir:1}%{!?_unitdir:0}
@@ -806,6 +807,7 @@ fi
 /usr/bin/radtest
 /usr/bin/radsniff
 /usr/bin/radsqlrelay
+/usr/bin/raduat
 /usr/bin/radwho
 /usr/bin/radzap
 /usr/bin/rlm_ippool_tool
diff --git a/scripts/ci/eapol_test-build.sh b/scripts/ci/eapol_test-build.sh
index f5bc4ae46e..401302131b 100755
--- a/scripts/ci/eapol_test-build.sh
+++ b/scripts/ci/eapol_test-build.sh
@@ -19,7 +19,7 @@
 #
 
 #
-#  Extremely basic script for building eapol_test from hostapd's master branch
+#  Extremely basic script for building eapol_test from hostapd's main branch
 #
 #  On success will write progress to stderr, and a path to the eapol_test
 #  binary to stdout, exiting with 0.
@@ -34,8 +34,8 @@
 TMP_BUILD_DIR="${BUILD_DIR}"
 : ${TMP_BUILD_DIR:="$(mktemp -d -t eapol_test.XXXXX)"}
 : ${HOSTAPD_DIR:="${TMP_BUILD_DIR}/hostapd"}
-: ${HOSTAPD_GIT_BRANCH:="master"}
-: ${HOSTAPD_GIT_COMMIT:="de4d62dbc"}
+: ${HOSTAPD_GIT_BRANCH:="hostap_2_10"}
+#: ${HOSTAPD_GIT_COMMIT:=""}
 : ${WPA_SUPPLICANT_DIR:="${HOSTAPD_DIR}/wpa_supplicant"}
 
 : ${BUILD_CONF_DIR:="$(dirname $0)/eapol_test"}
@@ -54,6 +54,18 @@ if [ -z "${FORCE_BUILD}" ]; then
     fi
 fi
 
+#
+# If OpenSSL 3.x
+#
+if openssl version | grep -q "OpenSSL 3\."; then
+    export EAPOL_TEST_CFLAGS="${EAPOL_TEST_CFLAGS} -DOPENSSL_USE_DEPRECATED -DOPENSSL_API_COMPAT=0x10101000L"
+    (
+    echo "WARNING: Building against OpenSSL 3, setting:"
+    echo "  EAPOL_TEST_CFLAGS='${EAPOL_TEST_CFLAGS}'"
+    echo "  EAPOL_TEST_LDFLAGS='${EAPOL_TEST_LDFLAGS}'"
+    ) 1>&2
+fi
+
 case "$OSTYPE" in
 linux-gnu)
     BUILD_CONF_FILE="${BUILD_CONF_DIR}/config_linux"
diff --git a/scripts/ci/eapol_test/config_freebsd b/scripts/ci/eapol_test/config_freebsd
index 3a61525cd5..8cab937107 100644
--- a/scripts/ci/eapol_test/config_freebsd
+++ b/scripts/ci/eapol_test/config_freebsd
@@ -9,9 +9,15 @@
 # be modified from here. In most cases, these lines should use += in order not
 # to override previous values of the variables.
 
-CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations
-#CFLAGS += -I/usr/local/include/openssl
-#LIBS += -L/usr/local/lib
+CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations $(EAPOL_TEST_CFLAGS)
+LIBS += $(EAPOL_TEST_LDFLAGS)
+
+#
+# Disable some warnings only against CLANG
+#
+ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
+CFLAGS += -Wno-error=void-pointer-to-enum-cast -Wno-error=ignored-qualifiers
+endif
 
 # Some Red Hat versions seem to include kerberos header files from OpenSSL, but
 # the kerberos files are not in the default include path. Following line can be
diff --git a/scripts/ci/eapol_test/config_linux b/scripts/ci/eapol_test/config_linux
index 5c46ea24d1..e53e05a73a 100644
--- a/scripts/ci/eapol_test/config_linux
+++ b/scripts/ci/eapol_test/config_linux
@@ -9,9 +9,15 @@
 # be modified from here. In most cases, these lines should use += in order not
 # to override previous values of the variables.
 
-CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations
-#CFLAGS += -I/usr/local/include/openssl
-#LIBS += -L/usr/local/lib
+CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations $(EAPOL_TEST_CFLAGS)
+LIBS += $(EAPOL_TEST_LDFLAGS)
+
+#
+# Disable some warnings only against CLANG
+#
+ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
+CFLAGS += -Wno-error=ignored-qualifiers
+endif
 
 # Some Red Hat versions seem to include kerberos header files from OpenSSL, but
 # the kerberos files are not in the default include path. Following line can be
diff --git a/scripts/ci/eapol_test/config_osx b/scripts/ci/eapol_test/config_osx
index 1b2045d52b..214d08fbdb 100644
--- a/scripts/ci/eapol_test/config_osx
+++ b/scripts/ci/eapol_test/config_osx
@@ -9,9 +9,10 @@
 # be modified from here. In most cases, these lines should use += in order not
 # to override previous values of the variables.
 
-CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations -Wno-error=void-pointer-to-enum-cast
-CFLAGS += -I /usr/local/opt/openssl/include -I/usr/local/include/openssl
-LIBS += -L/usr/local/opt/openssl/lib -L/usr/local/lib
+CFLAGS += -g3 -O0 -Wno-error=deprecated-declarations -Wno-error=void-pointer-to-enum-cast $(EAPOL_TEST_CFLAGS)
+CFLAGS += -I/usr/local/opt/openssl/include -I/usr/local/include/openssl
+
+LIBS += $(EAPOL_TEST_LDFLAGS) -L/usr/local/opt/openssl/lib -L/usr/local/lib
 
 # Some Red Hat versions seem to include kerberos header files from OpenSSL, but
 # the kerberos files are not in the default include path. Following line can be
diff --git a/scripts/ci/ldap-setup.sh b/scripts/ci/ldap-setup.sh
new file mode 100755
index 0000000000..ec3ba2c943
--- /dev/null
+++ b/scripts/ci/ldap-setup.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# Allow setup script to work with homebrew too
+export PATH="/usr/local/opt/openldap/libexec:$PATH"
+
+# Clean out any existing DB
+rm -rf /tmp/ldap/db
+# Create directory we can write DB files to
+mkdir -p /tmp/ldap/db/
+
+# Change db location to /tmp as we can't write to /var
+sed -i -e 's/\/var\/lib\/ldap/\/tmp\/ldap\/db/' src/tests/salt-test-server/salt/ldap/base.ldif
+
+# Create a directory we can link schema files into
+if [ -d /tmp/ldap/schema ]; then
+    echo "Schema dir already linked"
+# Debian
+elif [ -d /etc/ldap/schema ]; then
+    ln -fs /etc/ldap/schema /tmp/ldap/schema
+# Redhat
+elif [ -d /etc/openldap/schema ]; then
+    ln -fs /etc/openldap/schema /tmp/ldap/schema
+# macOS (homebrew)
+elif [ -d /usr/local/etc/openldap/schema ]; then
+    ln -fs /usr/local/etc/openldap/schema /tmp/ldap/schema
+else
+    echo "Can't locate OpenLDAP schema dir"
+    exit 1
+fi
+
+# Start slapd
+slapd -h "ldap://127.0.0.1:3890/" -f scripts/ci/ldap/slapd.conf &
+
+# Wait for LDAP to start
+sleep 1
+
+# Add test data
+count=0
+while [ $count -lt 10 ] ; do
+    if ldapadd -x -H ldap://127.0.0.1:3890/ -D "cn=admin,cn=config" -w secret -f src/tests/salt-test-server/salt/ldap/base.ldif ; then
+        break 2
+    else
+        count=$((count+1))
+        sleep 1
+    fi
+done
+
+if [ $? -ne 0 ]; then
+	echo "Error configuring server"
+	exit 1
+fi
diff --git a/scripts/ci/ldap/slapd.conf b/scripts/ci/ldap/slapd.conf
new file mode 100644
index 0000000000..778261618a
--- /dev/null
+++ b/scripts/ci/ldap/slapd.conf
@@ -0,0 +1,51 @@
+#
+###### SAMPLE 1 - SIMPLE DIRECTORY ############
+#
+# NOTES: inetorgperson picks up attributes and objectclasses
+#        from all three schemas
+#
+# NB: RH Linux schemas in /etc/openldap
+#
+include         /tmp/ldap/schema/core.schema
+include         /tmp/ldap/schema/cosine.schema
+include         /tmp/ldap/schema/inetorgperson.schema
+include         /tmp/ldap/schema/nis.schema
+include         doc/schemas/ldap/openldap/freeradius.schema
+include         doc/schemas/ldap/openldap/freeradius-clients.schema
+pidfile /tmp/slapd.pid
+
+# enable a lot of logging - we might need it
+# but generates huge logs
+loglevel        -1
+
+# MODULELOAD definitions
+# not required (comment out) before version 2.3
+moduleload back_mdb.la
+
+database config
+rootdn "cn=admin,cn=config"
+rootpw secret
+
+#######################################################################
+# mdb database definitions
+#
+# replace example and com below with a suitable domain
+#
+# If you don't have a domain you can leave it since example.com
+# is reserved for experimentation or change them to my and inc
+#
+#######################################################################
+
+database mdb
+suffix "dc=nodomain"
+
+# root or superuser
+rootdn "cn=admin,dc=nodomain"
+rootpw secret
+# The database directory MUST exist prior to running slapd AND
+# change path as necessary
+directory       /tmp/ldap/db/
+
+# other database parameters
+# read more in slapd.conf reference section
+checkpoint 128 15
diff --git a/scripts/ci/ldap/slapd2.conf b/scripts/ci/ldap/slapd2.conf
new file mode 100644
index 0000000000..52c16a7281
--- /dev/null
+++ b/scripts/ci/ldap/slapd2.conf
@@ -0,0 +1,61 @@
+#
+###### SAMPLE 1 - SIMPLE DIRECTORY ############
+#
+# NOTES: inetorgperson picks up attributes and objectclasses
+#        from all three schemas
+#
+# NB: RH Linux schemas in /etc/openldap
+#
+include         /tmp/ldap2/schema/core.schema
+include         /tmp/ldap2/schema/cosine.schema
+include         /tmp/ldap2/schema/inetorgperson.schema
+include         /tmp/ldap2/schema/nis.schema
+include         doc/schemas/ldap/openldap/freeradius.schema
+include         doc/schemas/ldap/openldap/freeradius-clients.schema
+pidfile /tmp/slapd2.pid
+
+# enable a lot of logging - we might need it
+# but generates huge logs
+loglevel        -1
+
+# MODULELOAD definitions
+# not required (comment out) before version 2.3
+moduleload back_mdb.la
+
+database config
+rootdn "cn=admin,cn=config"
+rootpw secret
+
+#
+# Certificates for SSL/TLS connections
+# Note - these will not match the host name so clients need to use 
+#        the "allow" option when checking certificates
+#
+#TLSCACertificateFile /tmp/ldap2/certs/cacert.pem
+#TLSCertificateFile /tmp/ldap2/certs/servercert.pem
+#TLSCertificateKeyFile /tmp/ldap2/certs/serverkey.pem
+
+#######################################################################
+# mdb database definitions
+#
+# replace example and com below with a suitable domain
+#
+# If you don't have a domain you can leave it since example.com
+# is reserved for experimentation or change them to my and inc
+#
+#######################################################################
+
+database mdb
+suffix "dc=nodomain"
+
+# root or superuser
+rootdn "cn=admin,dc=nodomain"
+rootpw secret
+# The database directory MUST exist prior to running slapd AND
+# change path as necessary
+directory       /tmp/ldap2/db/
+
+# other database parameters
+# read more in slapd.conf reference section
+checkpoint 128 15
+
diff --git a/scripts/ci/ldap2-setup.sh b/scripts/ci/ldap2-setup.sh
new file mode 100755
index 0000000000..c274414972
--- /dev/null
+++ b/scripts/ci/ldap2-setup.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+# Allow setup script to work with homebrew too
+export PATH="/usr/local/opt/openldap/libexec:$PATH"
+
+# Clean out any existing DB
+rm -rf /tmp/ldap2/db
+# Create directory we can write DB files to
+mkdir -p /tmp/ldap2/db/
+
+# Change db location to /tmp as we can't write to /var
+sed -i -e 's/\/var\/lib\/ldap/\/tmp\/ldap2\/db/' src/tests/salt-test-server/salt/ldap/base2.ldif
+
+# Create a directory we can link schema files into
+if [ -d /tmp/ldap2/schema ]; then
+    echo "Schema dir already linked"
+# Debian
+elif [ -d /etc/ldap/schema ]; then
+    ln -fs /etc/ldap/schema /tmp/ldap2/schema
+# Redhat
+elif [ -d /etc/openldap/schema ]; then
+    ln -fs /etc/openldap/schema /tmp/ldap2/schema
+# macOS (homebrew)
+elif [ -d /usr/local/etc/openldap/schema ]; then
+    ln -fs /usr/local/etc/openldap/schema /tmp/ldap2/schema
+else
+    echo "Can't locate OpenLDAP schema dir"
+    exit 1
+fi
+
+# Clean out any old certificates
+##rm -rf /tmp/ldap2/certs
+# Create certificate directory
+##mkdir -p /tmp/ldap2/certs
+
+# Copy certificates - whilst not stricltly LDAP certs they work fine for these tests
+##cp src/tests/certs/rsa/ca.pem /tmp/ldap2/certs/cacert.pem
+##cp src/tests/certs/rsa/server.pem /tmp/ldap2/certs/servercert.pem
+# OpenLDAP wants an un-encrypted key
+##openssl rsa -in src/tests/certs/rsa/server.key -out /tmp/ldap2/certs/serverkey.pem -passin pass:whatever
+
+# Start slapd
+slapd -h "ldap://127.0.0.1:3891/" -f scripts/ci/ldap/slapd2.conf &
+
+# Wait for LDAP to start
+sleep 1
+
+# Add test data
+count=0
+while [ $count -lt 10 ] ; do
+    if ldapadd -x -H ldap://127.0.0.1:3891/ -D "cn=admin,cn=config" -w secret -f src/tests/salt-test-server/salt/ldap/base2.ldif ; then
+        break 2
+    else
+        count=$((count+1))
+        sleep 1
+    fi
+done
+
+if [ $? -ne 0 ]; then
+        echo "Error configuring server"
+        exit 1
+fi
+
diff --git a/scripts/ci/mysql-setup.sh b/scripts/ci/mysql-setup.sh
new file mode 100755
index 0000000000..12e0067ea0
--- /dev/null
+++ b/scripts/ci/mysql-setup.sh
@@ -0,0 +1,19 @@
+#!/bin/sh -e
+
+echo "MySQL - Dropping existing database"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root -e 'DROP DATABASE radius;' || true
+
+echo "MySQL - Dropping existing user"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root -e 'DROP USER radius@localhost;' || true
+
+echo "MySQL - Creating database"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root -e 'CREATE DATABASE radius;'
+
+echo "MySQL - Executing schema.sql"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root radius < raddb/mods-config/sql/main/mysql/schema.sql
+
+echo "MySQL - Executing setup.sql"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root radius < raddb/mods-config/sql/main/mysql/setup.sql
+
+echo "MySQL - Grant radius user permissions"
+mysql -h "${SQL_MYSQL_TEST_SERVER}" -u root -e "GRANT ALL on radius.* TO radius@localhost; FLUSH PRIVILEGES;"
diff --git a/scripts/ci/postgresql-setup.sh b/scripts/ci/postgresql-setup.sh
new file mode 100755
index 0000000000..1e1abfbe67
--- /dev/null
+++ b/scripts/ci/postgresql-setup.sh
@@ -0,0 +1,26 @@
+#!/bin/sh -e
+
+#
+# To initialise on MacOS
+#    sudo brew install postgresql
+#    pg_ctl -D /usr/local/var/postgres start
+#    /usr/local/opt/postgres/bin/createuser -s postgres
+#
+
+echo "Dropping existing database"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -c 'drop database radius;' -U postgres || true
+
+echo "Dropping existing database"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -c 'drop user radius;' -U postgres || true
+
+echo "PostgreSQL - Creating database"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -c 'create database radius;' -U postgres || true
+
+echo "PostgreSQL - Execute schema.sql"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -U postgres radius < raddb/mods-config/sql/main/postgresql/schema.sql
+
+echo "PostgreSQL - Execute setup.sql"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -U postgres radius < raddb/mods-config/sql/main/postgresql/setup.sql
+
+echo "PostgreSQL - Grant radius user permissions"
+psql -h "${SQL_POSTGRESQL_TEST_SERVER}" -c 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO radius;' -U postgres radius
diff --git a/scripts/docker/rocky8/Dockerfile b/scripts/docker/rocky8/Dockerfile
new file mode 100644
index 0000000000..38019dbed5
--- /dev/null
+++ b/scripts/docker/rocky8/Dockerfile
@@ -0,0 +1,102 @@
+ARG from=rockylinux/rockylinux:8
+FROM ${from} as build
+
+RUN rpmkeys --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial
+
+#
+#  Install build tools
+#
+RUN yum groupinstall -y "Development Tools"
+RUN yum install -y rpmdevtools openssl dnf-utils
+
+#
+#  Create build directory
+#
+RUN mkdir -p /usr/local/src/repositories
+WORKDIR /usr/local/src/repositories
+
+#
+#  Shallow clone the FreeRADIUS source
+#
+ARG source=https://github.com/FreeRADIUS/freeradius-server.git
+ARG release=v3.0.x
+
+RUN git clone --depth 1 --single-branch --branch ${release} ${source}
+WORKDIR freeradius-server
+
+#
+#  Other requirements
+#
+
+# Use LTB's openldap packages intead of the distribution version to avoid linking against NSS
+RUN echo $'[ltb-project]\n\
+name=LTB project packages\n\
+baseurl=https://ltb-project.org/rpm/$releasever/$basearch\n\
+enabled=1\n\
+gpgcheck=1\n\
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-LTB-project'\
+> /etc/yum.repos.d/ltb-project.repo
+RUN rpm --import https://ltb-project.org/lib/RPM-GPG-KEY-LTB-project
+
+# EPEL repository for freetds and hiredis
+RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
+
+RUN yum config-manager --enable powertools
+# Currently needed for hiredis-devel
+RUN yum config-manager --enable epel-testing
+
+#
+#  Install build dependencies
+#
+RUN [ -e redhat/freeradius.spec ] && yum-builddep -y redhat/freeradius.spec
+
+#
+#  Create RPM build environment
+#
+ENV BUILDDIR=/root/rpmbuild
+RUN rpmdev-setuptree
+
+RUN ./configure
+RUN make freeradius-server-$(cat VERSION).tar.bz2
+RUN cp freeradius-server-$(cat VERSION).tar.bz2 $BUILDDIR/SOURCES/
+RUN cp -r redhat/* $BUILDDIR/SOURCES/
+RUN cp -r redhat/freeradius.spec $BUILDDIR/SPECS/
+WORKDIR $BUILDDIR
+
+#
+#  Build the server
+#
+ENV QA_RPATHS=0x0003
+RUN rpmbuild -bb --define '_release $release' "$BUILDDIR/SPECS/freeradius.spec"
+
+RUN mkdir /root/rpms
+RUN mv $BUILDDIR/RPMS/*/*.rpm /root/rpms/
+
+#
+#  Clean environment and run the server
+#
+FROM ${from}
+COPY --from=build /root/rpms /tmp/
+
+# Use LTB's openldap packages intead of the distribution version to avoid linking against NSS
+RUN echo $'[ltb-project]\n\
+name=LTB project packages\n\
+baseurl=https://ltb-project.org/rpm/$releasever/$basearch\n\
+enabled=1\n\
+gpgcheck=1\n\
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-LTB-project'\
+> /etc/yum.repos.d/ltb-project.repo \
+    && rpm --import https://ltb-project.org/lib/RPM-GPG-KEY-LTB-project \
+    \
+# EPEL repository for freetds and hiredis
+    && yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \
+    && yum install -y dnf-utils \
+    && yum config-manager --enable epel-testing
+
+RUN yum install -y /tmp/*.rpm
+
+COPY docker-entrypoint.sh /
+
+EXPOSE 1812/udp 1813/udp
+ENTRYPOINT ["/docker-entrypoint.sh"]
+CMD ["radiusd"]
diff --git a/scripts/docker/rocky8/docker-entrypoint.sh b/scripts/docker/rocky8/docker-entrypoint.sh
new file mode 100755
index 0000000000..900ad6b20f
--- /dev/null
+++ b/scripts/docker/rocky8/docker-entrypoint.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+set -e
+
+# this if will check if the first argument is a flag
+# but only works if all arguments require a hyphenated flag
+# -v; -SL; -f arg; etc will work, but not arg1 arg2
+if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then
+    set -- radiusd "$@"
+fi
+
+# check for the expected command
+if [ "$1" = 'radiusd' ]; then
+    shift
+    exec radiusd -f "$@"
+fi
+
+# debian people are likely to call "freeradius" as well, so allow that
+if [ "$1" = 'freeradius' ]; then
+    shift
+    exec radiusd -f "$@"
+fi
+
+# else default to run whatever the user wanted like "bash" or "sh"
+exec "$@"
diff --git a/scripts/libtool.mk b/scripts/libtool.mk
index 57915e1053..3e9639ea8c 100644
--- a/scripts/libtool.mk
+++ b/scripts/libtool.mk
@@ -163,7 +163,7 @@ ifeq "${bm_shared_libs}" "yes"
     # RELINL : flags use to build executables that can be run
     #          from the build directory / source tree.
     RPATH_FLAGS := -rpath ${libdir}
-    LOCAL_FLAGS := -rpath $(abspath ${BUILD_DIR})/lib/${LOCAL}/.libs
+    LOCAL_FLAGS := -rpath $(subst //,/,$(abspath ${BUILD_DIR})/lib/${LOCAL}/.libs)
 
     LOCAL_FLAGS_MIN := -rpath ${libdir}
 
diff --git a/scripts/raduat b/scripts/raduat
index d194f97670..9778b2a6f2 100755
--- a/scripts/raduat
+++ b/scripts/raduat
@@ -75,7 +75,7 @@ function show_help
     echo "The directory containing the tests may have multiple subdirectories to group the tests."
 }
 
-RADCLIENT=$(which "$RADCLIENT")
+RADCLIENT=$(command -v "$RADCLIENT")
 if [ ! -x "$RADCLIENT" ]; then
     ERROR "Can't find radclient binary, modify your \$PATH or set RADCLIENT"
     exit 64
diff --git a/share/dictionary b/share/dictionary
index b954351fd5..467be942b3 100644
--- a/share/dictionary
+++ b/share/dictionary
@@ -244,6 +244,7 @@ $INCLUDE dictionary.localweb
 $INCLUDE dictionary.lucent
 $INCLUDE dictionary.manzara
 $INCLUDE dictionary.meinberg
+$INCLUDE dictionary.mellanox
 $INCLUDE dictionary.meraki
 $INCLUDE dictionary.merit
 $INCLUDE dictionary.meru
diff --git a/share/dictionary.aruba b/share/dictionary.aruba
index 694a984f6e..f4a4a0391a 100644
--- a/share/dictionary.aruba
+++ b/share/dictionary.aruba
@@ -73,6 +73,8 @@ ATTRIBUTE	Aruba-UBT-Gateway-CPPM-Role		59	string
 
 ATTRIBUTE  	Aruba-AP-MAC-Address        		60      string
 ATTRIBUTE  	Aruba-Device-MAC-Address    		61      string
+ATTRIBUTE	Aruba-Device-Traffic-Class		63	integer
+
 
 VALUE	Aruba-AirGroup-Device-Type	Personal-Device		1
 VALUE	Aruba-AirGroup-Device-Type	Shared-Device		2
@@ -87,6 +89,7 @@ VALUE	Aruba-PoE-Priority		Low			2
 
 VALUE	Aruba-Port-Auth-Mode		Infrastructure-Mode	1
 VALUE	Aruba-Port-Auth-Mode		Client-Mode		2
+VALUE	Aruba-Port-Auth-Mode		Multi-Domain-Mode	3
 
 VALUE	Aruba-QoS-Trust-Mode		DSCP			0
 VALUE	Aruba-QoS-Trust-Mode		QoS			1
diff --git a/share/dictionary.huawei b/share/dictionary.huawei
index 51ec522ca0..78bc25630e 100644
--- a/share/dictionary.huawei
+++ b/share/dictionary.huawei
@@ -8,6 +8,8 @@
 #
 #	$Id$
 #
+#  https://support.huawei.com/enterprise/en/doc/EDOC1100055480/6a2e56b2/description-of-radius-attributes
+#
 ##############################################################################
 
 VENDOR		Huawei				2011
@@ -152,10 +154,10 @@ ATTRIBUTE	Huawei-Framed-Pool-Group		157	string
 ATTRIBUTE	Huawei-Framed-IPv6-Address		158	ipv6addr
 ATTRIBUTE	Huawei-Acct-Update-Address		159	integer
 ATTRIBUTE	Huawei-NAT-Policy-Name			160	string
-ATTRIBUTE	Huawei-NAT-Public-Address		161	string
-ATTRIBUTE	Huawei-NAT-Start-Port			162	string
-ATTRIBUTE	Huawei-NAT-End-Port			163	string
-ATTRIBUTE	Huawei-NAT-Port-Forwarding		164	string
+ATTRIBUTE	Huawei-NAT-Public-Address		161	ipaddr
+ATTRIBUTE	Huawei-NAT-Start-Port			162	integer
+ATTRIBUTE	Huawei-NAT-End-Port			163	integer
+ATTRIBUTE	Huawei-NAT-Port-Forwarding		164	integer
 ATTRIBUTE	Huawei-NAT-Port-Range-Update		165	integer
 ATTRIBUTE	Huawei-DS-Lite-Tunnel-Name		166	string
 ATTRIBUTE	Huawei-PCP-Server-Name			167	string # manual says text?
diff --git a/share/dictionary.mellanox b/share/dictionary.mellanox
new file mode 100644
index 0000000000..4da5c3469f
--- /dev/null
+++ b/share/dictionary.mellanox
@@ -0,0 +1,18 @@
+# -*- text -*-
+# Copyright (C) 2021 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+#
+#  dictionary.mellanox
+#
+#       Discovered by Aaron Knister <aaron.knister@gmail.com>
+#
+#
+# Version $Id$
+
+VENDOR          Mellanox 33049
+
+BEGIN-VENDOR    Mellanox
+
+ATTRIBUTE       Mellanox-Local-User-Name                1       string
+
+END-VENDOR      Mellanox
diff --git a/share/dictionary.roaringpenguin b/share/dictionary.roaringpenguin
index 4ffde12ba2..95cba28a1b 100644
--- a/share/dictionary.roaringpenguin
+++ b/share/dictionary.roaringpenguin
@@ -16,7 +16,8 @@ BEGIN-VENDOR	Roaring-Penguin
 
 # Upstream speed limit in kb/s
 ATTRIBUTE	RP-Upstream-Speed-Limit			1	integer
- # Downstream speed limit in kb/s
+
+# Downstream speed limit in kb/s
 ATTRIBUTE	RP-Downstream-Speed-Limit		2	integer
 
 # Send a HURL
diff --git a/share/dictionary.ruckus b/share/dictionary.ruckus
index d1dab6bef1..41e895902d 100644
--- a/share/dictionary.ruckus
+++ b/share/dictionary.ruckus
@@ -95,6 +95,10 @@ ATTRIBUTE	Ruckus-DPSK-Cipher			153.2	byte
 ATTRIBUTE	Ruckus-DPSK-Anonce			153.3	octets
 ATTRIBUTE	Ruckus-DPSK-EAPOL-Key-Frame		153.4	octets
 
+# Ruckus SmartCell Insight Attributes
+ATTRIBUTE Ruckus-SCI-Role		200 string
+ATTRIBUTE Ruckus-SCI-Resource-Group		201 string
+
 #
 #  Integer Translations
 #
diff --git a/src/include/build.h b/src/include/build.h
index c25431b414..e1c2a1c79b 100644
--- a/src/include/build.h
+++ b/src/include/build.h
@@ -46,11 +46,11 @@ 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
@@ -153,6 +153,11 @@ extern "C" {
 #  endif
 #endif
 
+#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 */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/include/event.h b/src/include/event.h
index a29c9562a9..1e8bf8438a 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,
diff --git a/src/include/features-h b/src/include/features-h
index 349d37362a..f24a52d20e 100644
--- a/src/include/features-h
+++ b/src/include/features-h
@@ -65,17 +65,3 @@
 #    endif
 #  endif
 #endif
-
-#ifdef WITH_TLS
-#  ifdef WITH_COA
-#    ifndef WITHOUT_COA_TUNNEL
-//#      define WITH_COA_TUNNEL (1)
-#    endif
-#  endif
-#endif
-
-#ifdef WITH_COA_TUNNEL
-#  ifdef WITHOUT_TLS
-#    error Reverse CoA requests requires TLS
-#  endif
-#endif
diff --git a/src/include/libradius.h b/src/include/libradius.h
index 2f15b7aaf1..757828f070 100644
--- a/src/include/libradius.h
+++ b/src/include/libradius.h
@@ -168,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.
diff --git a/src/include/listen.h b/src/include/listen.h
index 986cf0e132..e8222a3f02 100644
--- a/src/include/listen.h
+++ b/src/include/listen.h
@@ -70,13 +70,12 @@ struct rad_listen {
 	int		fd;
 	char const	*server;
 	int		status;
-#ifdef WITH_TCP
 	int		count;
+#ifdef WITH_TCP
 	rbtree_t	*children;
 	rad_listen_t	*parent;
 
 	bool		dual;
-	bool		proxy_protocol;		//!< haproxy protocol
 #endif
 	bool		nodup;
 	bool		synchronous;
@@ -85,19 +84,6 @@ struct rad_listen {
 #ifdef WITH_TLS
 	fr_tls_server_conf_t *tls;
 	bool		check_client_connections;
-
-#ifdef WITH_COA_TUNNEL
-	char const	*key;		/* Originating-Realm-Key */
-	bool		send_coa;	/* to the NAS */
-	bool		dead;
-
-	uint32_t	coa_irt;
-	uint32_t	coa_mrc;
-	uint32_t	coa_mrt;
-	uint32_t	coa_mrd;
-
-	int		num_ids_used;	/* for proxying CoA packets */
-#endif
 #endif
 
 	rad_listen_recv_t recv;
@@ -167,11 +153,6 @@ typedef struct listen_socket_t {
 	RADCLIENT	*client;
 
 	RADIUS_PACKET   *packet; /* for reading partial packets */
-
-	fr_ipaddr_t	haproxy_src_ipaddr;	//!< for proxy_protocol
-	fr_ipaddr_t	haproxy_dst_ipaddr;
-	uint16_t	haproxy_src_port;
-	uint16_t	haproxy_dst_port;
 #endif
 
 #ifdef WITH_TLS
diff --git a/src/include/md4.h b/src/include/md4.h
index b7bdd6a15e..21317f2c72 100644
--- a/src/include/md4.h
+++ b/src/include/md4.h
@@ -71,14 +71,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..64025f4a4d 100644
--- a/src/include/md5.h
+++ b/src/include/md5.h
@@ -68,14 +68,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/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 e7a8e39264..fc14ed16cc 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;
 
 /*
diff --git a/src/include/radiusd.h b/src/include/radiusd.h
index 094af09c1d..37e50183b4 100644
--- a/src/include/radiusd.h
+++ b/src/include/radiusd.h
@@ -607,13 +607,6 @@ int dual_tls_recv(rad_listen_t *listener);
 int dual_tls_send(rad_listen_t *listener, REQUEST *request);
 int proxy_tls_recv(rad_listen_t *listener);
 int proxy_tls_send(rad_listen_t *listener, REQUEST *request);
-#ifdef WITH_COA_TUNNEL
-int proxy_tls_send_reply(rad_listen_t *listener, REQUEST *request);
-int dual_tls_send_coa_request(rad_listen_t *listener, REQUEST *request);
-void listen_coa_free(void);
-void listen_coa_add(rad_listen_t *listener, char const *key);
-int listen_coa_find(REQUEST *request, char const *key);
-#endif
 #endif
 
 /*
diff --git a/src/include/realms.h b/src/include/realms.h
index a26491098e..354f2a3840 100644
--- a/src/include/realms.h
+++ b/src/include/realms.h
@@ -25,11 +25,6 @@ typedef enum {
 
 #ifdef WITH_COA
 	HOME_TYPE_COA,		//!< CoA destination (NAS or Proxy)
-
-#ifdef WITH_COA_TUNNEL
-	HOME_TYPE_AUTH_COA,	//!< auth + coa
-	HOME_TYPE_AUTH_ACCT_COA, //!< auth + acct + coa
-#endif
 #endif
 } home_type_t;
 
@@ -66,9 +61,6 @@ typedef struct home_server {
 
 	bool			dual;			//!< One of a pair of homeservers on consecutive ports.
 	bool			dynamic;		//!< is this a dynamically added home server?
-#ifdef WITH_COA_TUNNEL
-	bool			recv_coa;		//!< receive CoA packets, too
-#endif
 	char const		*virtual_server;		//!< For internal proxying
 	char const		*parent_server;
 
@@ -129,9 +121,6 @@ typedef struct home_server {
 	uint32_t		coa_mrc;
 	uint32_t		coa_mrt;
 	uint32_t		coa_mrd;
-#ifdef WITH_COA_TUNNEL
-	char const		*recv_coa_server;      	//!< for accepting incoming CoA requests
-#endif
 #endif
 #ifdef WITH_TLS
 	fr_tls_server_conf_t	*tls;
@@ -215,6 +204,7 @@ 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);
diff --git a/src/include/tls-h b/src/include/tls-h
index 2a6683d471..206f55db79 100644
--- a/src/include/tls-h
+++ b/src/include/tls-h
@@ -94,7 +94,7 @@ typedef struct _record_t {
 } 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
@@ -367,7 +366,6 @@ struct fr_tls_server_conf_t {
 	bool		disable_tlsv1;
 	bool		disable_tlsv1_1;
 	bool		disable_tlsv1_2;
-	bool		tls13_enable_magic;
 	bool		disallow_untrusted;		//!< allow untrusted CAs to issue client certificates
 
 	int		min_version;
diff --git a/src/lib/debug.c b/src/lib/debug.c
index ef6d542002..5cac13b40e 100644
--- a/src/lib/debug.c
+++ b/src/lib/debug.c
@@ -26,6 +26,8 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 
+USES_APPLE_DEPRECATED_API
+
 #if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H)
 #  include <malloc.h>
 #endif
diff --git a/src/lib/event.c b/src/lib/event.c
index 0c2976b3c9..64403f6b3b 100644
--- a/src/lib/event.c
+++ b/src/lib/event.c
@@ -161,6 +161,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;
diff --git a/src/lib/filters.c b/src/lib/filters.c
index 4868cd385d..3f3b63daee 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/src/lib/hmacmd5.c b/src/lib/hmacmd5.c
index 1cca00fa2a..88ba171aa0 100644
--- a/src/lib/hmacmd5.c
+++ b/src/lib/hmacmd5.c
@@ -34,6 +34,7 @@ RCSID("$Id$")
 
 #include <freeradius-devel/libradius.h>
 #include <freeradius-devel/md5.h>
+#include <freeradius-devel/openssl3.h>
 
 #ifdef HAVE_OPENSSL_EVP_H
 /** Calculate HMAC using OpenSSL's MD5 implementation
@@ -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/radius.c b/src/lib/radius.c
index 6bf667898e..524e68088e 100644
--- a/src/lib/radius.c
+++ b/src/lib/radius.c
@@ -529,6 +529,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)
@@ -563,8 +565,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.
@@ -573,7 +576,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);
@@ -586,6 +589,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen,
 	}
 
 	memcpy(output, passwd, len);
+
+	fr_md5_destroy(&old);
+	fr_md5_destroy(&context);
 }
 
 
@@ -654,8 +660,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);
@@ -664,7 +671,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);
@@ -682,6 +689,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)
@@ -2121,6 +2130,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);
@@ -2244,6 +2254,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.
@@ -2283,6 +2294,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.
@@ -4579,8 +4591,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
@@ -4591,7 +4604,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);
@@ -4603,6 +4616,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret,
 		}
 	}
 
+	fr_md5_destroy(&old);
+	fr_md5_destroy(&context);
+
 	return 0;
 }
 
@@ -4635,8 +4651,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.
@@ -4646,7 +4663,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);
@@ -4654,7 +4671,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);
@@ -4667,6 +4684,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);
 }
@@ -4803,8 +4823,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:
@@ -4831,7 +4852,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
@@ -4851,7 +4872,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);
 		}
 
@@ -4863,6 +4884,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/main/cb.c b/src/main/cb.c
index 372b8fa822..f8b2edbecc 100644
--- a/src/main/cb.c
+++ b/src/main/cb.c
@@ -136,7 +136,17 @@ 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)) {
+#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;
@@ -179,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) {
diff --git a/src/main/command.c b/src/main/command.c
index 6d02e98075..ea1b40e200 100644
--- a/src/main/command.c
+++ b/src/main/command.c
@@ -1151,7 +1151,11 @@ static int command_show_home_servers(rad_listen_t *listener, int argc, char *arg
 
 	char buffer[256];
 
-	for (i = 0; (home = home_server_bynumber(i)) != NULL; i++) {
+	for (i = 0; i < home_server_max_number; i++) {
+
+		if ((home = home_server_bynumber(i)) == NULL)
+			continue;
+
 		/*
 		 *	Internal "virtual" home server.
 		 */
diff --git a/src/main/exfile.c b/src/main/exfile.c
index 12be01b557..74a6dd8ad3 100644
--- a/src/main/exfile.c
+++ b/src/main/exfile.c
@@ -152,6 +152,22 @@ static int exfile_open_mkdir(exfile_t *ef, char const *filename, mode_t permissi
 {
 	int fd;
 
+	/*
+	 *	Files in /dev/ are special.  We don't try to create
+	 *	their parent directories, and we don't try to create
+	 *	the files.
+	 */
+	if (strncmp(filename, "/dev/", 5) == 0) {
+		fd = open(filename, O_RDWR, permissions);
+		if (fd < 0) {
+			fr_strerror_printf("Failed to open file %s: %s",
+					   filename, strerror(errno));
+			return -1;
+		}
+
+		return fd;
+	}
+
 	fd = open(filename, O_RDWR | O_CREAT, permissions);
 	if (fd < 0) {
 		mode_t dirperm;
diff --git a/src/main/listen.c b/src/main/listen.c
index 1edb72a9c2..07683429dd 100644
--- a/src/main/listen.c
+++ b/src/main/listen.c
@@ -77,10 +77,6 @@ static int command_write_magic(int newfd, listen_socket_t *sock);
 #endif
 #endif
 
-#ifdef WITH_COA_TUNNEL
-static int listen_coa_init(void);
-#endif
-
 static fr_protocol_t master_listen[];
 
 #ifdef WITH_DYNAMIC_CLIENTS
@@ -353,10 +349,6 @@ RADCLIENT *client_listener_find(rad_listen_t *listener,
 
 static int listen_bind(rad_listen_t *this);
 
-#ifdef WITH_COA_TUNNEL
-static void listener_coa_update(rad_listen_t *this, VALUE_PAIR *vps);
-#endif
-
 /*
  *	Process and reply to a server-status request.
  *	Like rad_authenticate and rad_accounting this should
@@ -420,10 +412,6 @@ int rad_status_server(REQUEST *request)
 		case RLM_MODULE_OK:
 		case RLM_MODULE_UPDATED:
 			request->reply->code = PW_CODE_ACCESS_ACCEPT;
-
-#ifdef WITH_COA_TUNNEL
-			if (request->listener->send_coa) listener_coa_update(request->listener, request->packet->vps);
-#endif
 			break;
 
 		case RLM_MODULE_FAIL:
@@ -451,10 +439,6 @@ int rad_status_server(REQUEST *request)
 		case RLM_MODULE_OK:
 		case RLM_MODULE_UPDATED:
 			request->reply->code = PW_CODE_ACCOUNTING_RESPONSE;
-
-#ifdef WITH_COA_TUNNEL
-			if (request->listener->send_coa) listener_coa_update(request->listener, request->packet->vps);
-#endif
 			break;
 
 		default:
@@ -868,39 +852,6 @@ static int dual_tcp_accept(rad_listen_t *listener)
 #endif
 	}
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *	Originate CoA requests to a NAS.
-	 */
-	if (this->send_coa) {
-		home_server_t *home;
-
-		rad_assert(this->type != RAD_LISTEN_PROXY);
-
-		this->proxy_send = dual_tls_send_coa_request;
-		this->proxy_encode = master_listen[RAD_LISTEN_PROXY].encode;
-		this->proxy_decode = master_listen[RAD_LISTEN_PROXY].decode;
-
-		/*
-		 *	Automatically create a home server for this
-		 *	client.  There MAY be one already one for that
-		 *	IP in the configuration files, but it will not
-		 *	have this particular port.
-		 */
-		sock->home = home = talloc_zero(this, home_server_t);
-		home->ipaddr = sock->other_ipaddr;
-		home->port = sock->other_port;
-		home->proto = sock->proto;
-		home->secret = sock->client->secret;
-
-		home->coa_irt = this->coa_irt;
-		home->coa_mrt = this->coa_mrt;
-		home->coa_mrc = this->coa_mrc;
-		home->coa_mrd = this->coa_mrd;
-		home->recv_coa_server = this->server;
-	}
-#endif
-
 	/*
 	 *	FIXME: set O_NONBLOCK on the accept'd fd.
 	 *	See djb's portability rants for details.
@@ -963,12 +914,6 @@ int common_socket_print(rad_listen_t const *this, char *buffer, size_t bufsize)
 	}
 #endif
 
-#ifdef WITH_COA_TUNNEL
-	if (this->send_coa) {
-		ADDSTRING("+coa");
-	}
-#endif
-
 	if (sock->interface) {
 		ADDSTRING(" interface ");
 		ADDSTRING(sock->interface);
@@ -1104,16 +1049,6 @@ static CONF_PARSER limit_config[] = {
 	CONF_PARSER_TERMINATOR
 };
 
-#ifdef WITH_COA_TUNNEL
-static CONF_PARSER coa_config[] = {
-	{ "irt",  FR_CONF_OFFSET(PW_TYPE_INTEGER, rad_listen_t, coa_irt), STRINGIFY(2) },
-	{ "mrt",  FR_CONF_OFFSET(PW_TYPE_INTEGER, rad_listen_t, coa_mrt), STRINGIFY(16) },
-	{ "mrc",  FR_CONF_OFFSET(PW_TYPE_INTEGER, rad_listen_t, coa_mrc), STRINGIFY(5) },
-	{ "mrd",  FR_CONF_OFFSET(PW_TYPE_INTEGER, rad_listen_t, coa_mrd), STRINGIFY(30) },
-	CONF_PARSER_TERMINATOR
-};
-#endif
-
 #ifdef WITH_TCP
 /*
  *	TLS requires child threads to handle the listeners.  Which
@@ -1232,12 +1167,6 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
 				return -1;
 			}
 
-			/*
-			 *	Add support for http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
-			 */
-			rcode = cf_item_parse(cs, "proxy_protocol", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->proxy_protocol), NULL);
-			if (rcode < 0) return -1;
-
 			/*
 			 *	If unset, set to default.
 			 */
@@ -1924,10 +1853,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);
 
-#ifdef WITH_COA_TUNNEL
-	if (!vp) vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_ORIGINATING_REALM, 0, TAG_ANY);
-#endif
-
 	if (!vp) return 0;
 
 	return 1;
@@ -3155,44 +3080,6 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t
 
 		this->recv = proxy_tls_recv;
 		this->proxy_send = proxy_tls_send;
-
-#ifdef WITH_COA_TUNNEL
-		if (home->recv_coa) {
-			RADCLIENT *client;
-
-			this->send_coa = true;
-
-			/*
-			 *	Don't set this->send_coa, as we are
-			 *	not sending CoA-Request packets to
-			 *	this home server.  Instead, we are
-			 *	receiving CoA packets from this home
-			 *	server.
-			 */
-			this->send = proxy_tls_send_reply;
-			this->encode = master_listen[RAD_LISTEN_AUTH].encode;
-			this->decode = master_listen[RAD_LISTEN_AUTH].decode;
-
-			/*
-			 *	Automatically create a client for this
-			 *	home server.  There MAY be one already
-			 *	one for that IP in the configuration
-			 *	files, but there's no guarantee that
-			 *	it exists.
-			 *
-			 *	The only real reason to use an
-			 *	existing client is to track various
-			 *	statistics.
-			 */
-			sock->client = client = talloc_zero(sock, RADCLIENT);
-			client->ipaddr = sock->other_ipaddr;
-			client->src_ipaddr = sock->my_ipaddr;
-			client->longname = client->shortname = talloc_typed_strdup(client, home->name);
-			client->secret = talloc_typed_strdup(client, home->secret);
-			client->nas_type = "none";
-			client->server = talloc_typed_strdup(client, home->recv_coa_server);
-		}
-#endif
 	}
 #endif
 #endif
@@ -3239,15 +3126,9 @@ static const FR_NAME_NUMBER listen_compare[] = {
 	{ "status",	RAD_LISTEN_NONE },
 #endif
 	{ "auth",	RAD_LISTEN_AUTH },
-#ifdef WITH_COA_TUNNEL
-	{ "auth+coa",	RAD_LISTEN_AUTH },
-#endif
 #ifdef WITH_ACCOUNTING
 	{ "acct",	RAD_LISTEN_ACCT },
 	{ "auth+acct",	RAD_LISTEN_AUTH },
-#ifdef WITH_COA_TUNNEL
-	{ "auth+acct+coa",	RAD_LISTEN_AUTH },
-#endif
 #endif
 #ifdef WITH_DETAIL
 	{ "detail",	RAD_LISTEN_DETAIL },
@@ -3402,13 +3283,6 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server)
 	if (p) {
 		if (strncmp(p + 1, "acct", 4) == 0) {
 			this->dual = true;
-#ifdef WITH_COA_TUNNEL
-			p += 5;
-		}
-
-		if (strcmp(p, "+coa") == 0) {
-			this->send_coa = true;
-#endif
 		}
 	}
 #endif
@@ -3429,44 +3303,6 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server)
 		return NULL;
 	}
 
-#ifdef WITH_COA_TUNNEL
-	if (this->send_coa) {
-		CONF_SECTION	*coa;
-
-		if (!this->tls) {
-			cf_log_err_cs(cs, "TLS is required in order to use \"+coa\"");
-			listen_free(&this);
-			return NULL;
-		}
-
-		/*
-		 *	Parse the configuration if it exists.
-		 */
-		coa = cf_section_sub_find(cs, "coa");
-		if (coa) {
-			rcode = cf_section_parse(cs, this, coa_config);
-			if (rcode < 0) {
-				listen_free(&this);
-				return NULL;
-			}
-		}
-
-		/*
-		 *	Use the same boundary checks as for home
-		 *	server. See realm_home_server_sanitize().
-		 */
-		FR_INTEGER_BOUND_CHECK("coa_irt", this->coa_irt, >=, 1);
-		FR_INTEGER_BOUND_CHECK("coa_irt", this->coa_irt, <=, 5);
-
-		FR_INTEGER_BOUND_CHECK("coa_mrc", this->coa_mrc, <=, 20);
-
-		FR_INTEGER_BOUND_CHECK("coa_mrt", this->coa_mrt, <=, 30);
-
-		FR_INTEGER_BOUND_CHECK("coa_mrd", this->coa_mrd, >=, 5);
-		FR_INTEGER_BOUND_CHECK("coa_mrd", this->coa_mrd, <=, 60);
-	}
-#endif	/* WITH_COA_TUNNEL */
-
 	cf_log_info(cs, "}");
 
 	return this;
@@ -3773,10 +3609,6 @@ add_sockets:
 	 */
 	if (!*head) return -1;
 
-#ifdef WITH_COA_TUNNEL
-	if (listen_coa_init() < 0) return -1;
-#endif
-
 	return 0;
 }
 
@@ -3862,334 +3694,3 @@ rad_listen_t *listener_find_byipaddr(fr_ipaddr_t const *ipaddr, uint16_t port, i
 
 	return NULL;
 }
-
-#ifdef WITH_COA_TUNNEL
-/*
- *	This is easier than putting ifdef's everywhere.  And
- *	realistically, there aren't many systems which have OpenSSL,
- *	but not pthreads.
- */
-#ifndef HAVE_PTHREAD_H
-#error CoA tunnels require pthreads
-#endif
-
-#include <pthread.h>
-
-static rbtree_t *coa_tree = NULL;
-
-/*
- *	We have an RB tree of keys, and within each key, a hash table
- *	of one or more listeners associated with that key.
- */
-typedef struct {
-	char const     	*key;
-	fr_hash_table_t	*ht;
-
-	pthread_mutex_t	mutex;		/* per key, to lower contention */
-} coa_key_t;
-
-typedef struct {
-	coa_key_t	*coa_key;
-	rad_listen_t	*listener;
-} coa_entry_t;
-
-static int coa_key_cmp(void const *one, void const *two)
-{
-	coa_key_t const *a = one;
-	coa_key_t const *b = two;
-
-	return strcmp(a->key, b->key);
-}
-
-static void coa_key_free(void *data)
-{
-	coa_key_t *coa_key = data;
-
-	pthread_mutex_destroy(&coa_key->mutex);
-	fr_hash_table_free(coa_key->ht);
-	talloc_free(coa_key);
-}
-
-static uint32_t coa_entry_hash(void const *data)
-{
-	coa_entry_t const *a = (coa_entry_t const *) data;
-
-	return fr_hash(&a->listener, sizeof(a->listener));
-}
-
-static int coa_entry_cmp(void const *one, void const *two)
-{
-	coa_entry_t const *a = one;
-	coa_entry_t const *b = two;
-
-	return memcmp(&a->listener, &b->listener, sizeof(a->listener));
-}
-
-/*
- *	Delete the entry, without holding the parents lock.
- */
-static void coa_entry_free(void *data)
-{
-	talloc_free(data);
-}
-
-static int coa_entry_destructor(coa_entry_t *entry)
-{
-	pthread_mutex_lock(&entry->coa_key->mutex);
-	fr_hash_table_delete(entry->coa_key->ht, entry);
-	pthread_mutex_unlock(&entry->coa_key->mutex);
-
-	return 0;
-}
-
-static int listen_coa_init(void)
-{
-	/*
-	 *	We will be looking up listeners by key.  Each key
-	 *	points us to a list of listeners.  Each key has it's
-	 *	own mutex, so that it's thread-safe.
-	 */
-	coa_tree = rbtree_create(NULL, coa_key_cmp, coa_key_free, RBTREE_FLAG_LOCK);
-	if (!coa_tree) {
-		ERROR("Failed creating internal tracking tree for Originating-Realm-Key");
-		return -1;
-	}
-
-	return 0;
-}
-
-void listen_coa_free(void)
-{
-	/*
-	 *	If we are freeing the tree, then all of the listeners
-	 *	must have been freed first.
-	 */
-	rad_assert(rbtree_num_elements(coa_tree) == 0);
-	rbtree_free(coa_tree);
-	coa_tree = NULL;
-}
-
-/*
- *	Adds a listener to the hash of listeners, based on key.
- */
-void listen_coa_add(rad_listen_t *this, char const *key)
-{
-	int tries = 0;
-	coa_key_t my_key, *coa_key;
-	coa_entry_t *entry;
-
-	rad_assert(this->send_coa);
-	rad_assert(this->parent);
-	rad_assert(!this->key);
-
-	/*
-	 *	Find the key.  If we can't find it, then create it.
-	 */
-	my_key.key = key;
-
-retry:
-	coa_key = rbtree_finddata(coa_tree, &my_key);
-	if (!coa_key) {
-		coa_key = talloc_zero(NULL, coa_key_t);
-		if (!coa_key) return;
-		coa_key->key = talloc_strdup(coa_key, key);
-		if (!coa_key->key) {
-		fail:
-			talloc_free(coa_key);
-			return;
-		}
-
-		/*
-		 *	Create the hash table of listeners.
-		 */
-		coa_key->ht = fr_hash_table_create(coa_entry_hash, coa_entry_cmp, coa_entry_free);
-		if (!coa_key->ht) goto fail;
-
-		if (!rbtree_insert(coa_tree, coa_key)) {
-			talloc_free(coa_key);
-
-			/*
-			 *	The lookups are mutex protected, but
-			 *	if there's time between the lookup and
-			 *	the insert, another thread may have
-			 *	created the node.  In which case we
-			 *	try again.
-			 */
-			if (tries < 3) goto retry;
-			tries++;
-			return;
-		}
-
-		(void) pthread_mutex_init(&coa_key->mutex, NULL);
-	}
-
-	/*
-	 *	No need to strdup() this, coa_key will only be removed
-	 *	after the listener has been removed.
-	 */
-	if (!this->key) this->key = coa_key->key;
-
-	entry = talloc_zero(this, coa_entry_t);
-	if (!entry) return;
-	talloc_set_destructor(entry, coa_entry_destructor);
-
-	entry->coa_key = coa_key;
-	entry->listener = this;
-
-	/*
-	 *	Insert the entry into the hash table.
-	 */
-	pthread_mutex_lock(&coa_key->mutex);
-	fr_hash_table_insert(coa_key->ht, entry);
-	pthread_mutex_unlock(&coa_key->mutex);
-}
-
-/*
- *	Find an active listener by key.
- *
- *	This function will update request->home_server, and
- *	request->proxy_listener.
- */
-int listen_coa_find(REQUEST *request, char const *key)
-{
-	coa_key_t my_key, *coa_key;
-	rad_listen_t *this, *found;
-	listen_socket_t *sock;
-	fr_hash_iter_t iter;
-
-	/*
-	 *	Find the key.  If we can't find it, then error out.
-	 */
-	memcpy(&my_key.key, &key, sizeof(key)); /* const issues */
-	coa_key = rbtree_finddata(coa_tree, &my_key);
-	if (!coa_key) return -1;
-
-	/*
-	 *	We've found it.  Now find a listener which has free
-	 *	IDs.  i.e. where the number of used IDs is less tahn
-	 *	256.
-	 */
-	found = NULL;
-	pthread_mutex_lock(&coa_key->mutex);
-	for (this = fr_hash_table_iter_init(coa_key->ht, &iter);
-	     this != NULL;
-	     this = fr_hash_table_iter_next(coa_key->ht, &iter)) {
-		if (this->dead) continue;
-
-		if (!found) {
-			if (this->num_ids_used < 256) {
-				found = this;
-			}
-
-			/*
-			 *	Skip listeners which have all used IDs.
-			 */
-			continue;
-		}
-
-		/*
-		 *	Try to spread the load across all available
-		 *	sockets.
-		 */
-		if (found->num_ids_used > this->num_ids_used) {
-			found = this;
-			continue;
-		}
-
-		/*
-		 *	If they are equal, pick one at random.
-		 *
-		 *	@todo - pick one with equal probability from
-		 *	among the ones with the same IDs used.  This
-		 *	algorithm prefers the first one.
-		 */
-		if (found->num_ids_used == this->num_ids_used) {
-			if ((fr_rand() & 0x01) == 0) {
-				found = this;
-				continue;
-			}
-		}
-	}
-
-	pthread_mutex_unlock(&coa_key->mutex);
-	if (!found) return -1;
-
-	request->proxy_listener = found;
-
-	sock = found->data;
-	request->home_server = sock->home;
-	return 0;
-}
-
-/*
- *	Check for an active listener by key.
- */
-static bool listen_coa_exists(rad_listen_t *this, char const *key)
-{
-	coa_key_t my_key, *coa_key;
-	coa_entry_t my_entry, *entry;
-
-	/*
-	 *	Find the key.  If we can't find it, then error out.
-	 */
-	memcpy(&my_key.key, &key, sizeof(key)); /* const issues */
-	coa_key = rbtree_finddata(coa_tree, &my_key);
-	if (!coa_key) return false;
-
-	my_entry.listener = this;
-	pthread_mutex_lock(&coa_key->mutex);
-	entry = fr_hash_table_finddata(coa_key->ht, &my_entry);
-	pthread_mutex_unlock(&coa_key->mutex);
-
-	return (entry != NULL);
-}
-
-/*
- *	Delete a listener entry.
- */
-static void listen_coa_delete(rad_listen_t *this, char const *key)
-{
-	coa_key_t my_key, *coa_key;
-	coa_entry_t my_entry;
-
-	/*
-	 *	Find the key.  If we can't find it, then error out.
-	 */
-	memcpy(&my_key.key, &key, sizeof(key)); /* const issues */
-	coa_key = rbtree_finddata(coa_tree, &my_key);
-	if (!coa_key) return;
-
-	my_entry.listener = this;
-	pthread_mutex_lock(&coa_key->mutex);
-	(void) fr_hash_table_delete(coa_key->ht, &my_entry);
-	pthread_mutex_unlock(&coa_key->mutex);
-}
-
-
-static void listener_coa_update(rad_listen_t *this, VALUE_PAIR *vps)
-{
-	VALUE_PAIR *vp;
-	vp_cursor_t cursor;
-
-	fr_cursor_init(&cursor, &vps);
-
-	/*
-	 *	Add or delete Operator-Name realms
-	 */
-	while ((vp = fr_cursor_next_by_num(&cursor, PW_OPERATOR_NAME, 0, TAG_ANY)) != NULL) {
-		if (vp->vp_length <= 1) continue;
-
-		if (vp->vp_strvalue[0] == '+') {
-			if (listen_coa_exists(this, vp->vp_strvalue)) continue;
-
-			listen_coa_add(this, vp->vp_strvalue);
-			continue;
-		}
-
-		if (vp->vp_strvalue[0] == '-') {
-			listen_coa_delete(this, vp->vp_strvalue);
-			continue;
-		}
-	}
-}
-#endif
diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c
index 09799da0e2..b134712356 100644
--- a/src/main/mainconfig.c
+++ b/src/main/mainconfig.c
@@ -509,16 +509,6 @@ static ssize_t xlat_listen_common(REQUEST *request, rad_listen_t *listen,
 	}
 #endif
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *      Look for RADSEC CoA tunnel key.
-	 */
-	if (listen->key && (strcmp(fmt, "Originating-Realm-Key") == 0)) {
-		strlcpy(out, listen->key, outlen);
-		return strlen(out);
-	}
-#endif
-
 	cp = cf_pair_find(listen->cs, fmt);
 	if (!cp || !(value = cf_pair_value(cp))) {
 		RDEBUG("Listener does not contain config item \"%s\"", fmt);
diff --git a/src/main/modules.c b/src/main/modules.c
index 9919482c86..e18977d34f 100644
--- a/src/main/modules.c
+++ b/src/main/modules.c
@@ -1357,7 +1357,7 @@ static int load_byserver(CONF_SECTION *cs)
 	 *	This is a bit of a hack...
 	 */
 	if (!found) do {
-#if defined(WITH_VMPS) || defined(WITH_DHCP)
+#if defined(WITH_VMPS) || defined(WITH_DHCP) || defined(WITH_TLS)
 		CONF_SECTION *subcs;
 #endif
 #if defined(WITH_DHCP) || defined(WITH_TLS)
diff --git a/src/main/process.c b/src/main/process.c
index 98e3633906..52f9df8773 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
@@ -632,9 +638,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);
 
@@ -700,7 +707,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 +894,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;
@@ -994,8 +1002,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 +1034,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 +1104,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
@@ -1212,7 +1220,7 @@ static void request_cleanup_delay(REQUEST *request, int action)
 			return;
 		} /* else it's time to clean up */
 
-		request_done(request, FR_ACTION_DONE);
+		request_done(request, FR_ACTION_CLEANUP_DELAY);
 		break;
 
 	default:
@@ -1346,7 +1354,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;
 	}
@@ -1802,7 +1810,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;
 
 		/*
@@ -1891,7 +1899,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;
 		}
 
@@ -2094,11 +2102,7 @@ static void tcp_socket_timer(void *ctx)
 			 *	open to listen for replies to requests we had
 			 *	previously sent.
 			 */
-			if (listener->type == RAD_LISTEN_PROXY
-#ifdef WITH_COA_TUNNEL
-			    || listener->send_coa
-#endif
-				) {
+			if (listener->type == RAD_LISTEN_PROXY) {
 				PTHREAD_MUTEX_LOCK(&proxy_mutex);
 				if (!fr_packet_list_socket_freeze(proxy_list,
 								  listener->fd)) {
@@ -2223,16 +2227,6 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank)
 
 	if (!request->in_proxy_hash) return;
 
-#ifdef COA_TUNNEL
-	/*
-	 *	Track how many IDs are used.  This information
-	 *	helps the listen_coa_find() function get a
-	 *	listener which has free IDs.
-	 */
-	rad_assert(request->proxy_listener->num_ids_used > 0);
-	request->proxy_listener->num_ids_used--;
-#endif
-
 	fr_packet_list_id_free(proxy_list, request->proxy, yank);
 	request->in_proxy_hash = false;
 
@@ -2276,11 +2270,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;
 
 	/*
@@ -2335,9 +2327,6 @@ static int insert_into_proxy_hash(REQUEST *request)
 	int tries;
 	bool success = false;
 	void *proxy_listener;
-#ifdef WITH_COA_TUNNEL
-	bool reverse_coa = request->proxy_listener && (request->proxy_listener->type != RAD_LISTEN_PROXY);
-#endif
 
 	VERIFY_REQUEST(request);
 
@@ -2361,41 +2350,22 @@ static int insert_into_proxy_hash(REQUEST *request)
 						  &request->proxy, &proxy_listener);
 		if (success) break;
 
-#ifdef WITH_COA_TUNNEL
-		/*
-		 *	Can't allocate an ID here, try to grab another
-		 *	listener by key.
-		 */
-		if (reverse_coa) {
-			int rcode;
-			VALUE_PAIR *vp;
-
-			/*
-			 *	Find the Originating-Realm key, which
-			 *	might not be the same as
-			 *	proxy_listener->key.
-			 */
-			vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_ORIGINATING_REALM, 0, TAG_ANY);
-			if (!vp) break;
-
-			/*
-			 *	We don't want to hold multiple mutexes
-			 *	at the same time.
-			 */
-			PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-			rcode = listen_coa_find(request, vp->vp_strvalue);
-			PTHREAD_MUTEX_LOCK(&proxy_mutex);
-			if (rcode < 0) continue;
-			break;
-		}
-#endif
-
 		if (tries > 0) continue; /* try opening new socket only once */
 
 #ifdef HAVE_PTHREAD_H
 		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) {
@@ -2429,15 +2399,6 @@ static int insert_into_proxy_hash(REQUEST *request)
 			goto fail;
 		}
 
-#ifdef COA_TUNNEL
-		/*
-		 *	Track how many IDs are used.  This information
-		 *	helps the listen_coa_find() function get a
-		 *	listener which has free IDs.
-		 */
-		request->proxy_listener->num_ids_used++;
-#endif
-
 		/*
 		 *	Add it to the event loop.  Ensure that we have
 		 *	only one mutex locked at a time.
@@ -2469,9 +2430,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);
 
@@ -2490,9 +2449,6 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply)
 	int post_proxy_type = 0;
 	VALUE_PAIR *vp;
 	char const *old_server;
-#ifdef WITH_COA_TUNNEL
-	bool reverse_coa = false;
-#endif
 
 	VERIFY_REQUEST(request);
 
@@ -2556,15 +2512,6 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply)
 	if (post_proxy_type > 0) RDEBUG2("Found Post-Proxy-Type %s",
 					 dict_valnamebyattr(PW_POST_PROXY_TYPE, 0, post_proxy_type));
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *	Cache this, as request->proxy_listener will be
-	 *	NULL after removing the request from the proxy
-	 *	hash.
-	 */
-	if (request->proxy_listener) reverse_coa = request->proxy_listener->type != RAD_LISTEN_PROXY;
-#endif
-
 	if (reply) {
 		VERIFY_PACKET(reply);
 
@@ -2602,15 +2549,6 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply)
 	if (request->home_server && request->home_server->virtual_server) {
 		request->server = request->home_server->virtual_server;
 
-#ifdef WITH_COA_TUNNEL
-	} else if (reverse_coa) {
-		rad_assert((request->proxy->code == PW_CODE_COA_REQUEST) ||
-			   (request->proxy->code == PW_CODE_DISCONNECT_REQUEST));
-		rad_assert(request->home_server != NULL);
-		rad_assert(request->home_server->recv_coa_server != NULL);
-		request->server = request->home_server->recv_coa_server;
-#endif
-
 	} else if (request->home_pool && request->home_pool->virtual_server) {
 			request->server = request->home_pool->virtual_server;
 	}
@@ -3241,36 +3179,6 @@ static int request_will_proxy(REQUEST *request)
 
 		return 0;
 
-#ifdef WITH_COA_TUNNEL
-	} else if (((request->packet->code == PW_CODE_COA_REQUEST) ||
-		    (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) &&
-		   ((vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_ORIGINATING_REALM, 0, TAG_ANY)) != NULL)) {
-
-		/*
-		 *	This function will set request->home_server,
-		 *	and also request->proxy_listener.
-		 */
-		if (listen_coa_find(request, vp->vp_strvalue) < 0) {
-			vp_cursor_t cursor;
-
-			(void) fr_cursor_init(&cursor, &request->config); /* already checked it above */
-
-			while ((vp = fr_cursor_next(&cursor)) != NULL) {
-				if (listen_coa_find(request, vp->vp_strvalue) == 0) break;
-			}
-
-			/*
-			 *	Not found.
-			 */
-			return 0;
-		}
-
-		/*
-		 *	Initialise request->proxy, and copy VPs over.
-		 */
-		home_server_update_request(request->home_server, request);
-		goto add_proxy_state;
-#endif
 	} else {
 
 		return 0;
@@ -3303,7 +3211,7 @@ 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) request_done(request->coa, FR_ACTION_COA_CANCELLED);
 #endif
 
 	/*
@@ -3368,10 +3276,6 @@ do_home:
 	 *	The RFC's say we have to do this, but FreeRADIUS
 	 *	doesn't need it.
 	 */
-#ifdef WITH_COA_TUNNEL
-add_proxy_state:
-#endif
-
 	vp = radius_pair_create(request->proxy, &request->proxy->vps, PW_PROXY_STATE, 0);
 	fr_pair_value_sprintf(vp, "%u", request->packet->id);
 
@@ -3402,15 +3306,6 @@ add_proxy_state:
 	if (request->home_server && request->home_server->virtual_server) {
 		request->server = request->home_server->virtual_server;
 
-#ifdef WITH_COA_TUNNEL
-	} else if (request->proxy_listener && (request->proxy_listener->type != RAD_LISTEN_PROXY)) {
-		rad_assert((request->packet->code == PW_CODE_COA_REQUEST) ||
-			   (request->packet->code == PW_CODE_DISCONNECT_REQUEST));
-		rad_assert(request->home_server != NULL);
-		rad_assert(request->home_server->recv_coa_server != NULL);
-		request->server = request->home_server->recv_coa_server;
-#endif
-
 	} else {
 		char buffer[128];
 
@@ -3532,7 +3427,7 @@ 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);
 	}
 #endif
 
@@ -4444,26 +4339,6 @@ static void request_coa_originate(REQUEST *request)
 	coa = request->coa;
 	coa->listener = NULL;	/* copied here by request_alloc_fake(), but not needed */
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *	Proxy-To-Originating-Realm is preferred to any other
-	 *	method of originating CoA requests.
-	 */
-	vp = fr_pair_find_by_num(coa->proxy->vps, PW_PROXY_TO_ORIGINATING_REALM, 0, TAG_ANY);
-	if (vp) {
-		/*
-		 *	This function will set request->home_server,
-		 *	and also request->proxy_listener.
-		 */
-		if (listen_coa_find(coa, vp->vp_strvalue) < 0) {
-			RWDEBUG("Unknown Originating realm '%s'", vp->vp_strvalue);
-			return;
-		}
-
-		goto set_packet_type;
-	}
-#endif
-
 	/*
 	 *	src_ipaddr will be set up in proxy_encode.
 	 */
@@ -4529,9 +4404,6 @@ static void request_coa_originate(REQUEST *request)
 		}
 	}
 
-#ifdef WITH_COA_TUNNEL
-set_packet_type:
-#endif
 	vp = fr_pair_find_by_num(coa->proxy->vps, PW_PACKET_TYPE, 0, TAG_ANY);
 	if (vp) {
 		switch (vp->vp_integer) {
@@ -4585,15 +4457,6 @@ set_packet_type:
 	if (coa->home_server && coa->home_server->virtual_server) {
 		coa->server = coa->home_server->virtual_server;
 
-#ifdef WITH_COA_TUNNEL
-	} else if (coa->proxy_listener && (coa->proxy_listener->type != RAD_LISTEN_PROXY)) {
-		rad_assert((coa->proxy->code == PW_CODE_COA_REQUEST) ||
-			   (coa->proxy->code == PW_CODE_DISCONNECT_REQUEST));
-		rad_assert(coa->home_server != NULL);
-		rad_assert(coa->home_server->recv_coa_server != NULL);
-		coa->server = coa->home_server->recv_coa_server;
-#endif
-
 	} else if (coa->home_pool && coa->home_pool->virtual_server) {
 		coa->server = coa->home_pool->virtual_server;
 	}
@@ -4698,7 +4561,7 @@ static void coa_retransmit(REQUEST *request)
 	    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;
 	}
 
@@ -4853,7 +4716,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;
 	}
 
@@ -5311,23 +5174,20 @@ static void event_new_fd(rad_listen_t *this)
 		rad_assert(sock != NULL);
 		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) {
@@ -5416,31 +5276,6 @@ static void event_new_fd(rad_listen_t *this)
 					fr_exit(1);
 				}
 			}
-
-#ifdef WITH_COA_TUNNEL
-			/*
-			 *	If we're allowed to send CoA requests
-			 *	back down this incoming socket, then
-			 *	add the socket to the proxy listener
-			 *	list.  We need to check for "parent",
-			 *	as the main incoming listener has
-			 *	"send_coa" set, but it just calls
-			 *	accept(), and doesn't actually send
-			 *	any packets.
-			 */
-			if (this->send_coa && this->parent) {
-				PTHREAD_MUTEX_LOCK(&proxy_mutex);
-				if (!fr_packet_list_socket_add(proxy_list, this->fd,
-							       sock->proto,
-							       &sock->other_ipaddr, sock->other_port,
-							       this)) {
-					ERROR("Failed adding coa proxy socket");
-					fr_exit_now(1);
-				}
-				PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-			}
-#endif	/* WITH_COA_TUNNEL */
-
 #endif	/* WITH_TCP */
 			break;
 		} /* switch over listener types */
@@ -5455,8 +5290,18 @@ static void event_new_fd(rad_listen_t *this)
 			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) {
@@ -5509,15 +5354,12 @@ 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.
 		 */
-		if (this->type == RAD_LISTEN_PROXY
-#ifdef WITH_COA_TUNNEL
-		    || (this->send_coa && this->parent)
-#endif
-			) {
+		if (this->type == RAD_LISTEN_PROXY) {
 			PTHREAD_MUTEX_LOCK(&proxy_mutex);
 			if (!fr_packet_list_socket_freeze(proxy_list,
 							  this->fd)) {
@@ -5612,11 +5454,7 @@ static void event_new_fd(rad_listen_t *this)
 		 *	to stop using it.  And then remove it from the
 		 *	list of outgoing sockets.
 		 */
-		if (this->type == RAD_LISTEN_PROXY
-#ifdef WITH_COA_TUNNEL
-		    || (this->send_coa && this->parent)
-#endif
-			) {
+		if (this->type == RAD_LISTEN_PROXY) {
 			home_server_t *home;
 
 			home = sock->home;
@@ -5636,37 +5474,15 @@ static void event_new_fd(rad_listen_t *this)
 				fr_exit(1);
 			}
 			PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-
-#ifdef WITH_COA_TUNNEL
-			/*
-			 *	Clean up the proxied packets AND the
-			 *	normal one.
-			 */
-			if (this->send_coa && this->parent) goto shutdown;
-#endif
-
 		} else
 #endif	/* WITH_PROXY */
 		{
-#ifdef WITH_COA_TUNNEL
-		shutdown:
-#endif
 			INFO(" ... shutting down socket %s", buffer);
 
 			/*
 			 *	EOL all requests using this socket.
 			 */
 			rbtree_walk(pl, RBTREE_DELETE_ORDER, eol_listener, this);
-
-#ifdef WITH_COA_TUNNEL
-			/*
-			 *	Delete the listener from the set of
-			 *	listeners by key.  This is done early,
-			 *	so that it won't be used while the
-			 *	cleanup timers are being run.
-			 */
-			if (this->tls) this->dead = true;
-#endif
 		}
 
 		/*
diff --git a/src/main/radclient.c b/src/main/radclient.c
index 5d8f49f58a..09d27c8711 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"
@@ -155,9 +161,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 +247,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;
@@ -1423,6 +1479,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.
@@ -1639,5 +1697,8 @@ int main(int argc, char **argv)
 	if ((stats.lost > 0) || (stats.failed > 0)) {
 		exit(1);
 	}
+
+	openssl3_free();
+
 	exit(0);
 }
diff --git a/src/main/radiusd.c b/src/main/radiusd.c
index 06b566d073..9739514509 100644
--- a/src/main/radiusd.c
+++ b/src/main/radiusd.c
@@ -694,13 +694,6 @@ cleanup:
 	 */
 	main_config_free();
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *	This should be after freeing all of the listeners.
-	 */
-	listen_coa_free();
-#endif
-
 #ifdef WIN32
 	WSACleanup();
 #endif
diff --git a/src/main/realms.c b/src/main/realms.c
index ad5f546639..063c49d700 100644
--- a/src/main/realms.c
+++ b/src/main/realms.c
@@ -73,10 +73,6 @@ static const FR_NAME_NUMBER home_server_types[] = {
 	{ "acct",		HOME_TYPE_ACCT },
 	{ "auth+acct",		HOME_TYPE_AUTH_ACCT },
 	{ "coa",		HOME_TYPE_COA },
-#ifdef WITH_COA_TUNNEL
-	{ "auth+coa",		HOME_TYPE_AUTH_COA },
-	{ "auth+acct+coa",	HOME_TYPE_AUTH_ACCT_COA },
-#endif
 	{ NULL, 0 }
 };
 
@@ -100,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
 
@@ -437,16 +433,6 @@ static CONF_PARSER home_server_coa[] = {
 	{ "mrd",  FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, coa_mrd), STRINGIFY(30) },
 	CONF_PARSER_TERMINATOR
 };
-
-
-
-#ifdef WITH_COA_TUNNEL
-static CONF_PARSER home_server_recv_coa[] = {
-	{ "virtual_server",  FR_CONF_OFFSET(PW_TYPE_STRING, home_server_t, recv_coa_server), NULL },
-	CONF_PARSER_TERMINATOR
-};
-#endif
-
 #endif
 
 static CONF_PARSER home_server_config[] = {
@@ -496,9 +482,6 @@ static CONF_PARSER home_server_config[] = {
 
 #ifdef WITH_COA
 	{ "coa", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) home_server_coa },
-#ifdef WITH_COA_TUNNEL
-	{ "recv_coa", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) home_server_recv_coa },
-#endif
 #endif
 
 	CONF_PARSER_TERMINATOR
@@ -698,20 +681,6 @@ bool realm_home_server_add(home_server_t *home)
 		}
 	}
 
-#ifdef WITH_COA_TUNNEL
-	if (home->recv_coa) {
-		if (!home->tls) {
-			ERROR("TLS is required in order to accept CoA requests from a home server");
-			return false;
-		}
-
-		if (!home->recv_coa_server) {
-			ERROR("A 'virtual_server' configuration is required in order to accept CoA requests from a home server");
-			return false;
-		}
-	}
-#endif
-
 	/*
 	 *	Mark it as already processed
 	 */
@@ -824,17 +793,6 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
 				goto error;
 			}
 			break;
-
-#ifdef WITH_COA_TUNNEL
-		case HOME_TYPE_AUTH_ACCT_COA:
-			home->dual = true;
-			home->recv_coa = true;
-			break;
-
-		case HOME_TYPE_AUTH_COA:
-			home->recv_coa = true;
-			break;
-#endif
 #endif
 
   		case HOME_TYPE_INVALID:
@@ -861,10 +819,6 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
 			}
 
 			if (((home->type == HOME_TYPE_AUTH) ||
-#ifdef WITH_COA_TUNNEL
-			     (home->type == HOME_TYPE_AUTH_COA) ||
-			     (home->type == HOME_TYPE_AUTH_ACCT_COA) ||
-#endif
 			     (home->type == HOME_TYPE_AUTH_ACCT)) && !home->ping_user_password) {
 				cf_log_err_cs(cs, "You must supply a 'password' to enable status_check=request");
 				goto error;
@@ -930,38 +884,6 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
 	}
 #endif
 
-	/*
-	 *	Check the reverse CoA configuration.
-	 */
-#ifdef WITH_COA_TUNNEL
-	if (home->recv_coa) {
-		if (!tls) {
-			ERROR("TLS is required in order to accept CoA requests from a home server");
-			goto error;
-		}
-
-		if (!home->recv_coa_server) {
-			ERROR("A 'virtual_server' configuration is required in order to accept CoA requests from a home server");
-			goto error;
-		}
-
-		/*
-		 *	Try and find a 'server' section off the root of
-		 *	the config with a name that matches the coa
-		 *	virtual_server.
-		 */
-		if (!rc) {
-			ERROR("Dynamic home servers cannot accept CoA requests");
-			goto error;
-		}
-
-		if (!cf_section_sub_find_name2(rc->cs, "server", home->recv_coa_server)) {
-			cf_log_err_cs(cs, "No such coa server %s", home->recv_coa_server);
-			goto error;
-		}
-	}
-#endif
-
 	/*
 	 *	If were doing RADSEC (tls+tcp) the secret should default
 	 *	to radsec, else a secret must be set.
@@ -1206,16 +1128,6 @@ static int pool_check_home_server(UNUSED realm_config_t *rc, CONF_PAIR *cp,
 	case HOME_TYPE_ACCT:
 		myhome.type = HOME_TYPE_AUTH_ACCT;
 		home = rbtree_finddata(home_servers_byname, &myhome);
-#ifdef WITH_COA_TUNNEL
-		if (!home) {
-			myhome.type = HOME_TYPE_AUTH_COA;
-			home = rbtree_finddata(home_servers_byname, &myhome);
-			if(!home) {
-				myhome.type = HOME_TYPE_AUTH_ACCT_COA;
-				home = rbtree_finddata(home_servers_byname, &myhome);
-			}
-		}
-#endif
 		if (home) {
 			*phome = home;
 			return 1;
@@ -1507,16 +1419,6 @@ static int server_pool_add(realm_config_t *rc,
 			case HOME_TYPE_ACCT:
 				myhome.type = HOME_TYPE_AUTH_ACCT;
 				home = rbtree_finddata(home_servers_byname, &myhome);
-#ifdef WITH_COA_TUNNEL
-				if (!home) {
-					myhome.type = HOME_TYPE_AUTH_COA;
-					home = rbtree_finddata(home_servers_byname, &myhome);
-					if (!home) {
-						myhome.type = HOME_TYPE_AUTH_ACCT_COA;
-						home = rbtree_finddata(home_servers_byname, &myhome);
-					}
-				}
-#endif
 				break;
 
 			default:
@@ -3086,14 +2988,6 @@ int home_server_afrom_file(char const *filename)
 		goto error;
 	}
 
-#ifdef COA_TUNNEL
-	if (home->recv_coa) {
-		fr_strerror_printf("Dynamic home_server '%s' cannot receive CoA requests'", p);
-		talloc_free(home);
-		goto error;
-	}
-#endif
-
 	if (!realm_home_server_add(home)) {
 		fr_strerror_printf("Failed adding home_server to the internal data structures");
 		talloc_free(home);
diff --git a/src/main/tls.c b/src/main/tls.c
index 4149a473a9..63fefaeb65 100644
--- a/src/main/tls.c
+++ b/src/main/tls.c
@@ -61,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;
@@ -485,8 +502,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize)
 #endif
 }
 
-
-
 static int _tls_session_free(tls_session_t *ssn)
 {
 	/*
@@ -502,6 +517,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;
@@ -554,10 +615,8 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
 	ret = SSL_connect(ssn->ssl);
 	if (ret < 0) {
 		switch (SSL_get_error(ssn->ssl, ret)) {
-			default:
-				break;
-
-
+		default:
+			break;
 
 		case SSL_ERROR_WANT_READ:
 		case SSL_ERROR_WANT_WRITE:
@@ -660,11 +719,10 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 		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("!! 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("!! There is either no standard for using this EAP method with TLS 1.3,");
+		WARN("!! or FreeRADIUS does not fully support TLS 1.3 for this EAP method.");
+		WARN("!!");
+		WARN("!! This message can be removed by setting tls_max_version = \"1.2\"");
 		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
 
 		if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) {
@@ -688,6 +746,13 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 	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
 	 */
@@ -929,7 +994,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 			REXDENT();
 		}
 
-		switch (ssn->info.version) {
+		switch (SSL_version(ssn->ssl)) {
 		case SSL2_VERSION:
 			str_version = "SSL 2.0";
 			break;
@@ -966,7 +1031,6 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 			RINDENT();
 			rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
 			REXDENT();
-
 		}
 	}
 	else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); }
@@ -1018,10 +1082,11 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 			return 0;
 		}
 	} else {
-
 		RDEBUG2("(TLS) Application data.");
-		/* Its clean application data, do whatever we want */
+		/* 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 */
@@ -1160,6 +1225,7 @@ 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];
@@ -1182,7 +1248,9 @@ void tls_session_information(tls_session_t *tls_session)
 
 	str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv";
 
-	switch (tls_session->info.version) {
+#define FROM_CLIENT (tls_session->info.origin == 0)
+
+	switch (SSL_version(tls_session->ssl)) {
 	case SSL2_VERSION:
 		str_version = "SSL 2.0 ";
 		break;
@@ -1209,7 +1277,7 @@ 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;
 	}
@@ -1253,9 +1321,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:
@@ -1284,26 +1355,32 @@ void tls_session_information(tls_session_t *tls_session)
 
 				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:
@@ -1312,6 +1389,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:
@@ -1332,12 +1410,13 @@ 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 ((tls_session->info.version > tls_session->conf->max_version) &&
+					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.");
@@ -1364,18 +1443,21 @@ void tls_session_information(tls_session_t *tls_session)
 #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
 
@@ -1506,6 +1588,8 @@ void tls_session_information(tls_session_t *tls_session)
 	}
 
 	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[] = {
@@ -1606,10 +1690,6 @@ static CONF_PARSER tls_server_config[] = {
 #endif
 	},
 
-#ifdef TLS1_3_VERSION
-	{ "tls13_enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, tls13_enable_magic), NULL },
-#endif
-
 	{ "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 },
@@ -1685,8 +1765,6 @@ 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.
 	 *
@@ -1700,10 +1778,32 @@ static int load_dh_params(SSL_CTX *ctx, char *file)
 #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);
 		return -1;
@@ -3429,53 +3529,53 @@ X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf)
 #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;
 
 #if OPENSSL_VERSION_NUMBER >= 0x1000200fL
-	if (strchr(ecdh_curve, ':') != 0) {
-		char *list = strdup(ecdh_curve);
+	/*
+	 *	A colon-separated list of curves.
+	 */
+	if (*ecdh_curve) {
+		char *list;
+
+		memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */
 
 		if (SSL_CTX_set1_curves_list(ctx, list) == 0) {
-			free(list);
 			ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
 			return -1;
 		}
-		free(list);
-
-		(void) SSL_CTX_set_ecdh_auto(ctx, 1);
-		return 0;
 	}
 
+	(void) SSL_CTX_set_ecdh_auto(ctx, 1);
+#else
 	/*
-	 *	Just pick the right curve.
+	 *	Use APIs for older versions of OpenSSL.
 	 */
-	if (ecdh_curve && !*ecdh_curve) {
-		(void) SSL_CTX_set_ecdh_auto(ctx, 1);
-	}
-#endif
+	{
+		int      nid;
+		EC_KEY  *ecdh;
 
-	nid = OBJ_sn2nid(ecdh_curve);
-	if (!nid) {
-		ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
-		return -1;
-	}
+		nid = OBJ_sn2nid(ecdh_curve);
+		if (!nid) {
+			ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
+			return -1;
+		}
 
-	ecdh = EC_KEY_new_by_curve_name(nid);
-	if (!ecdh) {
-		ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve);
-		return -1;
-	}
+		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);
+		SSL_CTX_set_tmp_ecdh(ctx, ecdh);
 
-	EC_KEY_free(ecdh);
+		EC_KEY_free(ecdh);
+	}
+#endif
 
 	return 0;
 }
@@ -3511,6 +3611,28 @@ int tls_global_init(bool spawn_flag, bool check)
 		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;
 }
 
@@ -3576,6 +3698,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();
@@ -4078,15 +4213,52 @@ post_ca:
 	if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
 #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;
+		}
+	}
+
 	/*
 	 *	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) &&
-	    !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\"");
+	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
+
+		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
 	}
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
@@ -4291,16 +4463,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
 	 */
@@ -4451,6 +4613,7 @@ static int tls_realms_load(fr_tls_server_conf_t *conf)
 	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;
 	}
@@ -4506,6 +4669,9 @@ static int tls_realms_load(fr_tls_server_conf_t *conf)
 		}
 	}
 
+	conf->realms = ht;
+	closedir(dir);
+
 	return 0;
 }
 
@@ -4654,6 +4820,7 @@ skip_list:
 		if (conf->ocsp_store == NULL) goto error;
 	}
 #endif /*HAVE_OPENSSL_OCSP_H*/
+
 	{
 		char *dh_file;
 
@@ -4672,7 +4839,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;
 	}
 
@@ -4947,7 +5114,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 			 *	OpenSSL frees the underlying session out from
 			 *	under us in TLS 1.3.
 			 */
-			if (ssn->info.version == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl);
+			if (SSL_version(ssn->ssl) == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl);
 #endif
 #endif
 
@@ -5000,38 +5167,55 @@ 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;
 
-		RDEBUG3("SSL_read Error");
+		RDEBUG3("(TLS) SSL_read Error");
 
 		code = SSL_get_error(ssn->ssl, err);
 		switch (code) {
 		case SSL_ERROR_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("(TLS) 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:
@@ -5053,8 +5237,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.
diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c
index 66040e6980..c3c40d17ea 100644
--- a/src/main/tls_listen.c
+++ b/src/main/tls_listen.c
@@ -118,216 +118,9 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re
 
 	return 1;
 }
-
-/*
- *	Check for PROXY protocol.  Once that's done, clear
- *	listener->proxy_protocol.
- */
-static int proxy_protocol_check(rad_listen_t *listener, REQUEST *request)
-{
-	listen_socket_t *sock = listener->data;
-	uint8_t const *p, *end, *eol;
-	int af, argc, src_port, dst_port;
-	unsigned long num;
-	fr_ipaddr_t src, dst;
-	char *argv[5], *eos;
-	ssize_t rcode;
-	RADCLIENT *client;
-
-	/*
-	 *	Begin by trying to fill the buffer.
-	 */
-	rcode = read(request->packet->sockfd,
-		     sock->ssn->dirty_in.data + sock->ssn->dirty_in.used,
-		     sizeof(sock->ssn->dirty_in.data) - sock->ssn->dirty_in.used);
-	if (rcode < 0) {
-		if (errno == EINTR) return 0;
-		RDEBUG("(TLS) Closing PROXY socket from client port %u due to read error - %s", sock->other_port, fr_syserror(errno));
-		return -1;
-	}
-
-	if (rcode == 0) {
-		DEBUG("(TLS) Closing PROXY socket from client port %u - other end closed connection", sock->other_port);
-		return -1;
-	}
-
-	/*
-	 *	We've read data, scan the buffer for a CRLF.
-	 */
-	sock->ssn->dirty_in.used += rcode;
-
-	dump_hex("READ FROM PROXY PROTOCOL SOCKET", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used);
-
-	p = sock->ssn->dirty_in.data;
-
-	/*
-	 *	CRLF MUST be within the first 107 bytes.
-	 */
-	if (sock->ssn->dirty_in.used < 107) {
-		end = p + sock->ssn->dirty_in.used;
-	} else {
-		end = p + 107;
-	}
-	eol = NULL;
-
-	/*
-	 *	Scan for CRLF.
-	 */
-	while ((p + 1) < end) {
-		if ((p[0] == 0x0d) && (p[1] == 0x0a)) {
-			eol = p;
-			break;
-		}
-
-		/*
-		 *	Other control characters, or non-ASCII data.
-		 *	That's a problem.
-		 */
-		if ((*p < ' ') || (*p >= 0x80)) {
-		invalid_data:
-			DEBUG("(TLS) Closing PROXY socket from client port %u - received invalid data", sock->other_port);
-			return -1;
-		}
-
-		p++;
-	}
-
-	/*
-	 *	No CRLF, keep reading until we have it.
-	 */
-	if (!eol) return 0;
-
-	p = sock->ssn->dirty_in.data;
-
-	/*
-	 *	Let's see if the PROXY line is well-formed.
-	 */
-	if ((eol - p) < 14) goto invalid_data;
-	
-	/*
-	 *	We only support TCP4 and TCP6.
-	 */
-	if (memcmp(p, "PROXY TCP", 9) != 0) goto invalid_data;
-
-	p += 9;
-
-	if (*p == '4') {
-		af = AF_INET;
-
-	} else if (*p == '6') {
-		af = AF_INET6;
-
-	} else goto invalid_data;
-
-	p++;
-	if (*p != ' ') goto invalid_data;
-	p++;
-
-	sock->ssn->dirty_in.data[eol - sock->ssn->dirty_in.data] = '\0'; /* overwite the CRLF */
-
-	/*
-	 *	Parse the fields (being a little forgiving), while
-	 *	checking for too many / too few fields.
-	 */
-	argc = str2argv((char *) &sock->ssn->dirty_in.data[p - sock->ssn->dirty_in.data], (char **) &argv, 5);
-	if (argc != 4) goto invalid_data;
-
-	memset(&src, 0, sizeof(src));
-	memset(&dst, 0, sizeof(dst));
-
-	if (fr_pton(&src, argv[0], -1, af, false) < 0) goto invalid_data;
-	if (fr_pton(&dst, argv[1], -1, af, false) < 0) goto invalid_data;
-
-	num = strtoul(argv[2], &eos, 10);
-	if (num > 65535) goto invalid_data;
-	if (*eos) goto invalid_data;
-	src_port = num;
-
-	num = strtoul(argv[3], &eos, 10);
-	if (num > 65535) goto invalid_data;
-	if (*eos) goto invalid_data;
-	dst_port = num;
-
-	/*
-	 *	And copy the various fields around.
-	 */
-	sock->haproxy_src_ipaddr = sock->other_ipaddr;
-	sock->haproxy_src_port = sock->other_port;
-
-	sock->haproxy_dst_ipaddr = sock->my_ipaddr;
-	sock->haproxy_dst_port = sock->my_port;
-
-	sock->my_ipaddr = dst;
-	sock->my_port = dst_port;
-
-	sock->other_ipaddr = src;
-	sock->other_port = src_port;
-
-	/*
-	 *	Print out what we've changed.  Note that the TCP
-	 *	socket address family and the PROXY address family may
-	 *	be different!
-	 */
-	if (RDEBUG_ENABLED) {
-		char src_buf[128], dst_buf[128];
-
-		RDEBUG("(TLS) Received PROXY protocol connection from client %s:%s -> %s:%s, via proxy %s:%u -> %s:%u",
-		       argv[0], argv[2], argv[1], argv[3],
-		       inet_ntop(af, &sock->haproxy_src_ipaddr.ipaddr, src_buf, sizeof(src_buf)),
-		       sock->haproxy_src_port,
-		       inet_ntop(af, &sock->haproxy_dst_ipaddr.ipaddr, dst_buf, sizeof(dst_buf)),
-		       sock->haproxy_dst_port);
-	}
-
-        /*
-         *      Ensure that the source IP indicated by the PROXY
-         *      protocol is a known TLS client.
-         */
-        if ((client = client_listener_find(listener, &src, src_port)) == NULL ||
-             client->proto != IPPROTO_TCP) {
-		RDEBUG("(TLS) Unknown client %s - dropping PROXY protocol connection", argv[0]);
-		return -1;
-        }
-
-        /*
-         *      Use the client indicated by the proxy.
-         */
-        sock->client = client;
-
-	/*
-         *      Fix up the current request so that the first packet's
-         *      src/dst is valid.  Subsequent packets will get the
-         *      clients IP from the listener and listen_sock
-         *      structures.
-         */
-        request->packet->dst_ipaddr = dst;
-        request->packet->dst_port = dst_port;
-        request->packet->src_ipaddr = src;
-        request->packet->src_port = src_port;
-
-	/*
-	 *	Move any remaining TLS data to the start of the buffer.
-	 */
-	eol += 2;
-	end = sock->ssn->dirty_in.data + sock->ssn->dirty_in.used;
-	if (eol < end) {
-		memmove(sock->ssn->dirty_in.data, eol, end - eol);
-		sock->ssn->dirty_in.used = end - eol;
-	} else {
-		sock->ssn->dirty_in.used = 0;
-	}
-		
-	/*
-	 *	It's no longer a PROXY protocol, but just straight TLS.
-	 */
-	listener->proxy_protocol = false;
-
-	return 1;
-}
-
 static int tls_socket_recv(rad_listen_t *listener)
 {
-	bool doing_init = false, already_read = false;
+	bool doing_init = false;
 	ssize_t rcode;
 	RADIUS_PACKET *packet;
 	REQUEST *request;
@@ -392,30 +185,6 @@ static int tls_socket_recv(rad_listen_t *listener)
 
 	request = sock->request;
 
-	/*
-	 *	Bypass ALL of the TLS stuff until we've read the PROXY
-	 *	header.
-	 *
-	 *	If the PROXY header checks pass, then the flag is
-	 *	cleared, as we don't need it any more.
-	 */
-	if (listener->proxy_protocol) {
-		rcode = proxy_protocol_check(listener, request);
-		if (rcode < 0) {
-			RDEBUG("(TLS) Closing PROXY TLS socket from client port %u", sock->other_port);
-			tls_socket_close(listener);
-			return 0;
-		}
-		if (rcode == 0) return 1;
-
-		/*
-		 *	The buffer might already have data.  In that
-		 *	case, we don't want to do a blocking read
-		 *	later.
-		 */
-		already_read = (sock->ssn->dirty_in.used > 0);
-	}
-
 	if (sock->state == LISTEN_TLS_SETUP) {
 		RDEBUG3("(TLS) Setting connection state to RUNNING");
 		sock->state = LISTEN_TLS_RUNNING;
@@ -441,31 +210,28 @@ static int tls_socket_recv(rad_listen_t *listener)
 		goto check_for_setup;
 	}
 
-	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("(TLS) Error reading socket: %s", fr_syserror(errno));
-			goto do_close;
-		}
-
-		/*
-		 *	Normal socket close.
-		 */
-		if (rcode == 0) goto do_close;
+	rcode = read(request->packet->sockfd,
+		     sock->ssn->dirty_in.data,
+		     sizeof(sock->ssn->dirty_in.data));
+	if ((rcode < 0) && (errno == ECONNRESET)) {
+	do_close:
+		DEBUG("(TLS) Closing socket from client port %u", sock->other_port);
+		tls_socket_close(listener);
+		PTHREAD_MUTEX_UNLOCK(&sock->mutex);
+		return 0;
+	}
 
-		sock->ssn->dirty_in.used = rcode;
+	if (rcode < 0) {
+		RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno));
+		goto do_close;
 	}
 
+	/*
+	 *	Normal socket close.
+	 */
+	if (rcode == 0) goto do_close;
+
+	sock->ssn->dirty_in.used = rcode;
 	dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used);
 
 	/*
@@ -496,8 +262,14 @@ check_for_setup:
 		}
 
 		/*
-		 *	Else we MUST be finished the SSL setup.
+		 *      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;
+		}
 	}
 
 	/*
@@ -639,9 +411,6 @@ int dual_tls_recv(rad_listen_t *listener)
 	listen_socket_t *sock = listener->data;
 	RADCLIENT	*client = sock->client;
 	BIO		*rbio;
-#ifdef WITH_COA_TUNNEL
-	bool		is_reply = false;
-#endif
 
 	if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
 
@@ -700,14 +469,6 @@ redo:
 		FR_STATS_INC(dsc, total_requests);
 		fun = rad_coa_recv;
 		break;
-
-#ifdef WITH_COA_TUNNEL
-	case PW_CODE_COA_ACK:
-	case PW_CODE_COA_NAK:
-		if (!listener->send_coa) goto bad_packet;
-		is_reply = true;
-                break;
-#endif
 #endif
 
 	case PW_CODE_STATUS_SERVER:
@@ -734,15 +495,6 @@ redo:
 		return 0;
 	} /* switch over packet types */
 
-#ifdef WITH_COA_TUNNEL
-	if (is_reply) {
-		if (!request_proxy_reply(packet)) {
-			rad_free(&packet);
-			return 0;
-		}
-	} else
-#endif
-
 	if (!request_receive(NULL, listener, packet, client, fun)) {
 		FR_STATS_INC(auth, total_packets_dropped);
 		rad_free(&packet);
@@ -823,21 +575,6 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request)
 	 */
 	if (request->reply->code == 0) return 0;
 
-#ifdef WITH_COA_TUNNEL
-	/*
-	 *	Save the key, if we haven't already done that.
-	 */
-	if (listener->send_coa && !listener->key) {
-		VALUE_PAIR *vp = NULL;
-
-		vp = fr_pair_find_by_num(request->config, PW_ORIGINATING_REALM_KEY, 0, TAG_ANY);
-		if (vp) {
-			RDEBUG("Adding send CoA listener with key %s", vp->vp_strvalue);
-			listen_coa_add(request->listener, vp->vp_strvalue);
-		}
-	}
-#endif
-
 	/*
 	 *	Pack the VPs
 	 */
@@ -889,58 +626,6 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request)
 	return 0;
 }
 
-#ifdef WITH_COA_TUNNEL
-/*
- *	Send a CoA request to a NAS, as a proxied packet.
- *
- *	The proxied packet MUST already have been encoded.
- */
-int dual_tls_send_coa_request(rad_listen_t *listener, REQUEST *request)
-{
-	listen_socket_t *sock = listener->data;
-
-	VERIFY_REQUEST(request);
-
-	rad_assert(listener->proxy_send == dual_tls_send_coa_request);
-
-	if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
-
-	rad_assert(request->proxy->data);
-
-	if (request->proxy->data_len > (MAX_PACKET_LEN - 100)) {
-		RWARN("Packet is large, and possibly truncated - %zd vs max %d",
-		      request->proxy->data_len, MAX_PACKET_LEN);
-	}
-
-	PTHREAD_MUTEX_LOCK(&sock->mutex);
-
-	/*
-	 *	Write the packet to the SSL buffers.
-	 */
-	sock->ssn->record_plus(&sock->ssn->clean_in,
-			       request->proxy->data, request->proxy->data_len);
-
-	dump_hex("TUNNELED DATA < ", sock->ssn->clean_in.data, sock->ssn->clean_in.used);
-
-	/*
-	 *	Do SSL magic to get encrypted data.
-	 */
-	tls_handshake_send(request, sock->ssn);
-
-	/*
-	 *	And finally write the data to the socket.
-	 */
-	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);
-	}
-	PTHREAD_MUTEX_UNLOCK(&sock->mutex);
-
-	return 0;
-}
-#endif
-
 static int try_connect(tls_session_t *ssn)
 {
 	int ret;
@@ -1107,10 +792,6 @@ int proxy_tls_recv(rad_listen_t *listener)
 	RADIUS_PACKET *packet;
 	uint8_t *data;
 	ssize_t data_len;
-#ifdef WITH_COA_TUNNEL
-	bool is_request = false;
-	RADCLIENT *client = sock->client;
-#endif
 
 	if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
 
@@ -1165,25 +846,9 @@ int proxy_tls_recv(rad_listen_t *listener)
 	case PW_CODE_DISCONNECT_NAK:
 		break;
 
-#ifdef WITH_COA_TUNNEL
-	case PW_CODE_COA_REQUEST:
-		if (!listener->send_coa) goto bad_packet;
-		FR_STATS_INC(coa, total_requests);
-		is_request = true;
-		break;
-
-	case PW_CODE_DISCONNECT_REQUEST:
-		if (!listener->send_coa) goto bad_packet;
-		FR_STATS_INC(dsc, total_requests);
-		is_request = true;
-		break;
-#endif
 #endif
 
 	default:
-#ifdef WITH_COA_TUNNEL
-	bad_packet:
-#endif
 		/*
 		 *	FIXME: Update MIB for packet types?
 		 */
@@ -1196,15 +861,6 @@ int proxy_tls_recv(rad_listen_t *listener)
 		return 0;
 	}
 
-#ifdef WITH_COA_TUNNEL
-	if (is_request) {
-		if (!request_receive(NULL, listener, packet, client, rad_coa_recv)) {
-			FR_STATS_INC(auth, total_packets_dropped);
-			rad_free(&packet);
-			return 0;
-		}
-	} else
-#endif
 	if (!request_proxy_reply(packet)) {
 		rad_free(&packet);
 		return 0;
@@ -1275,71 +931,6 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request)
 
 	return 1;
 }
-
-#ifdef WITH_COA_TUNNEL
-int proxy_tls_send_reply(rad_listen_t *listener, REQUEST *request)
-{
-	int rcode;
-	listen_socket_t *sock = listener->data;
-
-	VERIFY_REQUEST(request);
-
-	rad_assert(sock->ssn->connected);
-
-	if ((listener->status != RAD_LISTEN_STATUS_INIT &&
-	    (listener->status != RAD_LISTEN_STATUS_KNOWN))) return 0;
-
-	/*
-	 *	Pack the VPs
-	 */
-	if (rad_encode(request->reply, request->packet,
-		       request->client->secret) < 0) {
-		RERROR("Failed encoding packet: %s", fr_strerror());
-		return 0;
-	}
-
-	if (request->reply->data_len > (MAX_PACKET_LEN - 100)) {
-		RWARN("Packet is large, and possibly truncated - %zd vs max %d",
-		      request->reply->data_len, MAX_PACKET_LEN);
-	}
-
-	/*
-	 *	Sign the packet.
-	 */
-	if (rad_sign(request->reply, request->packet,
-		       request->client->secret) < 0) {
-		RERROR("Failed signing packet: %s", fr_strerror());
-		return 0;
-	}
-
-	DEBUG3("Proxy is writing %u bytes to SSL",
-	       (unsigned int) request->reply->data_len);
-	PTHREAD_MUTEX_LOCK(&sock->mutex);
-	rcode = SSL_write(sock->ssn->ssl, request->reply->data,
-			  request->reply->data_len);
-	if (rcode < 0) {
-		int err;
-
-		err = ERR_get_error();
-		switch (err) {
-		case SSL_ERROR_NONE:
-		case SSL_ERROR_WANT_READ:
-		case SSL_ERROR_WANT_WRITE:
-			break;	/* let someone else retry */
-
-		default:
-			tls_error_log(NULL, "Failed in proxy send");
-			DEBUG("Closing TLS socket to home server");
-			tls_socket_close(listener);
-			PTHREAD_MUTEX_UNLOCK(&sock->mutex);
-			return 0;
-		}
-	}
-	PTHREAD_MUTEX_UNLOCK(&sock->mutex);
-
-	return 1;
-}
-#endif	/* WITH_COA_TUNNEL */
 #endif	/* WITH_PROXY */
 
 #endif	/* WITH_TLS */
diff --git a/src/main/version.c b/src/main/version.c
index 728be50a93..f1f1e87810 100644
--- a/src/main/version.c
+++ b/src/main/version.c
@@ -348,14 +348,6 @@ void version_init_features(CONF_SECTION *cs)
 				);
 
 
-	version_add_feature(cs, "recv-coa-from-home-server",
-#ifdef WITH_COA_TUNNEL
-			        true
-#else
-				false
-#endif
-				);
-
 	version_add_feature(cs, "control-socket",
 #ifdef WITH_COMMAND_SOCKET
 				true
diff --git a/src/main/xlat.c b/src/main/xlat.c
index 998751f9db..583686a7db 100644
--- a/src/main/xlat.c
+++ b/src/main/xlat.c
@@ -87,6 +87,50 @@ static char const * const xlat_foreach_names[] = {"Foreach-Variable-0",
 
 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };	/* up to 10 for foreach */
 
+static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
+			bool escape, bool return_null, char const *concat);
+
+/** Concatenation
+ *
+ */
+static ssize_t xlat_concat(UNUSED void *instance, REQUEST *request,
+			   char const *fmt, char *out, size_t outlen)
+{
+	ssize_t slen;
+	vp_tmpl_t vpt;
+	char *str;
+	char const *concat;
+	char buffer[2];
+
+	while (isspace((int) *fmt)) fmt++;
+
+	slen = tmpl_from_attr_substr(&vpt, fmt,  REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+	if (slen <= 0) {
+		RDEBUG("%s", fr_strerror());
+		return -1;
+	}
+
+	fmt += slen;
+	while (isspace((int) *fmt)) fmt++;
+
+	if (!*fmt) {
+		concat = ",";
+	} else {
+		buffer[0] = *fmt;
+		buffer[1] = '\0';
+
+		concat = buffer;
+	}
+
+	str = xlat_getvp(request, request, &vpt, true, true, concat);
+	if (!str) return 0;
+
+	strlcpy(out, str, outlen);
+	talloc_free(str);
+
+	return strlen(out);
+}
+
 /** Print length of its RHS.
  *
  */
@@ -775,6 +819,7 @@ int xlat_register(char const *name, xlat_func_t func, xlat_escape_t escape, void
 		rad_assert(c != NULL); \
 		c->internal = true
 
+		XLAT_REGISTER(concat);
 		XLAT_REGISTER(integer);
 		XLAT_REGISTER(strlen);
 		XLAT_REGISTER(length);
@@ -1804,7 +1849,7 @@ static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp
 
 
 static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
-			bool escape, bool return_null)
+			bool escape, bool return_null, char const *concat)
 {
 	VALUE_PAIR *vp = NULL, *virtual = NULL;
 	RADIUS_PACKET *packet = NULL;
@@ -2050,7 +2095,7 @@ do_print:
 		while ((vp = tmpl_cursor_next(&cursor, vpt)) != NULL) {
 			q = vp_aprints_value(ctx, vp, quote);
 			if (!q) return NULL;
-			p = talloc_strdup_append(p, ",");
+			p = talloc_strdup_append(p, concat);
 			p = talloc_strdup_append(p, q);
 		}
 
@@ -2243,7 +2288,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
 		/*
 		 *	Some attributes are virtual <sigh>
 		 */
-		str = xlat_getvp(ctx, request, &node->attr, escape ? false : true, true);
+		str = xlat_getvp(ctx, request, &node->attr, escape ? false : true, true, ",");
 		if (str) {
 			XLAT_DEBUG("%.*sEXPAND attr %s", lvl, xlat_spaces, node->attr.tmpl_da->name);
 			XLAT_DEBUG("%.*s       ---> %s", lvl ,xlat_spaces, str);
diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c
index 17e4cadca8..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
  *
@@ -167,7 +168,7 @@ int eaptls_success(eap_handler_t *handler, int peap_flag)
 		uint8_t const context_tls13[] = { handler->type };
 #endif
 
-		switch (tls_session->info.version) {
+		switch (SSL_version(tls_session->ssl)) {
 #ifdef TLS1_3_VERSION
 		case TLS1_3_VERSION:
 			context = context_tls13;
@@ -764,7 +765,7 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
 	 *	data to be sent. So this is done always for EAP-TLS but
 	 *	notibly not for PEAP even on resumption.
 	 */
-	if ((tls_session->info.version == TLS1_3_VERSION) &&
+	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;
@@ -794,6 +795,20 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
 	 */
 	if (tls_session->is_init_finished) return FR_TLS_SUCCESS;
 
+	/*
+	 *	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)) {
+		/*
+		 *	Don't set is_init_finished, as that causes the
+		 *	rest of the code to make too many assumptions.
+		 */
+		return FR_TLS_OK;
+	}
+
 	/*
 	 *	Who knows what happened...
 	 */
diff --git a/src/modules/rlm_eap/libeap/eapsimlib.c b/src/modules/rlm_eap/libeap/eapsimlib.c
index cf1e8a7dd9..67e21b2a78 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/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c
index 237c671d97..4f5b6a8e71 100644
--- a/src/modules/rlm_eap/libeap/mppe_keys.c
+++ b/src/modules/rlm_eap/libeap/mppe_keys.c
@@ -28,6 +28,7 @@ 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>
 
 /*
  *	TLS P_hash from RFC 2246/5246 section 5
@@ -39,7 +40,8 @@ static void P_hash(EVP_MD const *evp_md,
 {
 	HMAC_CTX *ctx_a, *ctx_out;
 	unsigned char a[EVP_MAX_MD_SIZE];
-	unsigned int size;
+	unsigned int size = EVP_MAX_MD_SIZE;
+	unsigned int digest_len;
 
 	ctx_a = HMAC_CTX_new();
 	ctx_out = HMAC_CTX_new();
@@ -50,11 +52,9 @@ static void P_hash(EVP_MD const *evp_md,
 	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);
@@ -178,35 +181,6 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t c
 		ERROR("Failed generating keying material");
 		return;
 	}
-
-	if (RDEBUG_ENABLED4) {
-		size_t i, client_len, master_len;
-		uint8_t client_random[SSL3_RANDOM_SIZE];
-		uint8_t master_key[SSL_MAX_MASTER_KEY_LENGTH];
-		char *q, buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH];
-
-		client_len = SSL_get_client_random(s, client_random, sizeof(client_random));
-		master_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key));
-
-		strcpy(buffer, "CLIENT_RANDOM ");
-		q = buffer + 14;
-
-		for (i = 0; i < client_len; i++) {
-			sprintf(q, "%02X", client_random[i]);
-			q += 2;
-		}
-		*(q++) = ' ';
-
-		for (i = 0; i < master_len; i++) {
-			sprintf(q, "%02X", master_key[i]);
-			q += 2;
-		}
-		*q = '\0';
-
-		RDEBUG("(TLS) KEYLOG: %s", buffer);
-
-	}
-
 #else
 	{
 		uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)];
@@ -239,33 +213,6 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t c
 		PRF(s->session->master_key, s->session->master_key_length,
 		    seed, len, out, buf, sizeof(out));
 	}
-
-	if (RDEBUG_ENABLED4) {
-		size_t i, master_len;
-		char *q, buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH];
-
-		client_len = SSL_get_client_random(s, client_random, sizeof(client_random));
-		master_len = s->session->master_key_length;
-		if (master_len > SSL_MAX_MASTER_KEY_LENGTH) master_len = SSL_MAX_MASTER_KEY_LENGTH;
-
-		strcpy(buffer, "CLIENT_RANDOM ");
-		q = buffer + 14;
-
-		for (i = 0; i < SSL3_RANDOM_SIZE; i++) {
-			sprintf(q, "%02X", s->s3->client_random[i]);
-			q += 2;
-		}
-		*(q++) = ' ';
-
-		for (i = 0; i < master_len; i++) {
-			sprintf(q, "%02X", s->session->master_key[i]);
-			q += 2;
-		}
-		*q = '\0';
-
-		RDEBUG("(TLS) KEYLOG: %s", buffer);
-
-	}
 #endif
 
 	p = out;
@@ -333,7 +280,7 @@ void eaptls_gen_eap_key(eap_handler_t *handler)
 
 	*p++ = type;
 
-	switch (tls_session->info.version) {
+	switch (SSL_version(tls_session->ssl)) {
 	case TLS1_VERSION:
 	case TLS1_1_VERSION:
 	case TLS1_2_VERSION:
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 8c63498586..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
@@ -128,7 +128,7 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
 
 	t->keyblock = talloc(t, eap_fast_keyblock_t);
 
-	eap_fast_tls_gen_challenge(tls_session->ssl, tls_session->info.version, buf, 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));
 
diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast_crypto.h b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast_crypto.h
index c2f40dd7d5..d00e9a250b 100644
--- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast_crypto.h
+++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast_crypto.h
@@ -1,3 +1,32 @@
+/*
+ * eap_fast_crypto.h
+ *
+ * Version:     $Id$
+ *
+ *   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
+ *
+ * Copyright 2003 Alan DeKok <aland@freeradius.org>
+ * Copyright 2006 The FreeRADIUS server project
+ */
+
+#ifndef _EAP_FAST_CRYPTO_H
+#define _EAP_FAST_CRYPTO_H
+
+RCSIDH(eap_fast_crypto_h, "$Id$")
+
+
 int eap_fast_encrypt(uint8_t const *plaintext, size_t plaintext_len,
 		     uint8_t const *aad, size_t aad_len,
 		     uint8_t const *key, uint8_t *iv, unsigned char *ciphertext,
@@ -6,3 +35,5 @@ int eap_fast_encrypt(uint8_t const *plaintext, size_t plaintext_len,
 int eap_fast_decrypt(uint8_t const *ciphertext, size_t ciphertext_len,
 		     uint8_t const *aad, size_t aad_len,
 		     uint8_t const *tag, uint8_t const *key, uint8_t const *iv, uint8_t *plaintext);
+
+#endif /* _EAP_FAST_CRYPTO_H */
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 7c91d34050..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
@@ -575,7 +575,8 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 
 	/*
 	 *	Don't allow TLS 1.3 for us, even if it's allowed
-	 *	elsewhere.
+	 *	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);
 
@@ -627,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;
 	}
@@ -635,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/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
index 4bbf57330f..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
@@ -135,25 +135,6 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
 		inst->auth_type_eap = dv->value;
 	}
 
-#ifdef TLS1_3_VERSION
-	if ((inst->tls_conf->min_version == TLS1_3_VERSION) && !inst->tls_conf->tls13_enable_magic) {
-		ERROR("There are no standards for using TLS 1.3 with PEAP.");
-		ERROR("You MUST enable TLS 1.2 for PEAP 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 PEAP 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;
 }
 
@@ -212,10 +193,9 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	}
 
 	/*
-	 *	Don't allow TLS 1.3 for us, even if it's allowed
-	 *	elsewhere.
+	 *	Allow TLS 1.3, it works.
 	 */
-	ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic);
+	ssn = eaptls_session(handler, inst->tls_conf, client_cert, true);
 	if (!ssn) {
 		return 0;
 	}
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 61a2fc69d4..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
@@ -36,6 +36,7 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include "eap_pwd.h"
 #include "const_time.h"
+#include <freeradius-devel/openssl3.h>
 
 static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 };
 
@@ -140,7 +141,7 @@ static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx)
 	p = BN_new();
 	a = BN_new();
 	b = BN_new();
-	EC_GROUP_get_curve_GFp(group, p, a, b, bnctx);
+	EC_GROUP_get_curve(group, p, a, b, bnctx);
 
 	BN_sub(pm1, p, BN_value_one());
 
@@ -247,18 +248,16 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 			      char const *id_peer, int id_peer_len,
 			      uint32_t *token)
 {
-	BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL;
-	HMAC_CTX *ctx = NULL;
-	uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = 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;
-
-	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:
@@ -302,12 +301,16 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 	    ((qr = consttime_BN()) == NULL) ||
 	    ((qnr = consttime_BN()) == NULL) ||
 	    ((x_candidate = consttime_BN()) == NULL) ||
-	    ((y_sqrd = 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;
 	}
@@ -331,6 +334,19 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 		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
@@ -360,13 +376,19 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 		 *	pwd-seed = H(token | peer-id | server-id | password |
 		 *		     counter)
 		 */
-		HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(),NULL);
-		HMAC_Update(ctx, (uint8_t *)token, sizeof(*token));
-		HMAC_Update(ctx, (uint8_t const *)id_peer, id_peer_len);
-		HMAC_Update(ctx, (uint8_t const *)id_server, id_server_len);
-		HMAC_Update(ctx, (uint8_t const *)password, password_len);
-		HMAC_Update(ctx, (uint8_t *)&ctr, sizeof(ctr));
-		pwd_hmac_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);
 		eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
@@ -400,7 +422,7 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 		* need to unambiguously identify the solution, if there is
 		* one..
 		*/
-		is_odd = BN_is_odd(rnd) ? 1 : 0;
+		is_odd = BN_is_odd(rnd);
 
 		/*
 		* check whether x^3 + a*x + b is a quadratic residue
@@ -443,8 +465,21 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 	* now we can savely construct PWE
 	*/
 	BN_bin2bn(xbuf, primebytelen, x_candidate);
-	if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe,
-						     x_candidate, save_is_odd, NULL)) {
+	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;
 	}
 
@@ -460,12 +495,20 @@ int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t
 	BN_clear_free(qr);
 	BN_clear_free(qnr);
 	BN_clear_free(rnd);
+	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);
 
-	HMAC_CTX_free(ctx);
+	EVP_MD_CTX_free(hmac_ctx);
+	EVP_PKEY_free(hmac_pkey);
 
 	return ret;
 }
@@ -561,7 +604,7 @@ int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, s
 		goto finish;
 	}
 
-	if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bn_ctx)) {
+	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;
 	}
@@ -620,7 +663,7 @@ int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, s
 		goto finish;
 	}
 
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bn_ctx)) {
+	if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) {
 		REDEBUG("Unable to get shared secret from K");
 		goto finish;
 	}
@@ -670,7 +713,7 @@ int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *ou
 	/*
 	 * next is server element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bn_ctx)) {
+	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;
 	}
@@ -695,7 +738,7 @@ int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *ou
 	/*
 	 * next is peer element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bn_ctx)) {
+	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;
 	}
@@ -770,7 +813,7 @@ int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out,
 	/*
 	* then peer element: x, y
 	*/
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bn_ctx)) {
+	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;
 	}
@@ -796,7 +839,7 @@ int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out,
 	/*
 	 * then server element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bn_ctx)) {
+	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;
 	}
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 f53a544d94..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
@@ -829,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);
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 3c77aa4877..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
@@ -130,25 +130,6 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
 		return -1;
 	}
 
-#ifdef TLS1_3_VERSION
-	if ((inst->tls_conf->min_version == TLS1_3_VERSION) && !inst->tls_conf->tls13_enable_magic) {
-		ERROR("There are no standards for using TLS 1.3 with TTLS.");
-		ERROR("You MUST enable TLS 1.2 for TTLS 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 TTLS 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;
 }
 
@@ -201,10 +182,9 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	}
 
 	/*
-	 *	Don't allow TLS 1.3 for us, even if it's allowed
-	 *	elsewhere.
+	 *	Allow TLS 1.3, it works.
 	 */
-	ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic);
+	ssn = eaptls_session(handler, inst->tls_conf, client_cert, true);
 	if (!ssn) {
 		return 0;
 	}
diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c
index 8e310abc29..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"
@@ -1831,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
diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h
index c86b097f74..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"
diff --git a/src/modules/rlm_ldap/sasl.c b/src/modules/rlm_ldap/sasl.c
index c5edb5f023..17a635676c 100644
--- a/src/modules/rlm_ldap/sasl.c
+++ b/src/modules/rlm_ldap/sasl.c
@@ -66,18 +66,24 @@ static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx,
 		switch (cb_p->id) {
 		case SASL_CB_AUTHNAME:
 			cb_p->result = this->identity;
+			cb_p->len = strlen(this->identity);
 			break;
 
 		case SASL_CB_PASS:
 			cb_p->result = this->password;
+			cb_p->len = strlen(this->password);
 			break;
 
 		case SASL_CB_USER:
 			cb_p->result = this->extra->proxy ? this->extra->proxy : this->identity;
+			cb_p->len = this->extra->proxy ? strlen(this->extra->proxy) : strlen(this->identity);
 			break;
 
 		case SASL_CB_GETREALM:
-			if (this->extra->realm) cb_p->result = this->extra->realm;
+			if (this->extra->realm) {
+				cb_p->result = this->extra->realm;
+				cb_p->len = strlen(this->extra->realm);
+			}
 			break;
 
 		default:
@@ -177,7 +183,7 @@ ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
 				MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response  : %s", escaped);
 
 				talloc_free(escaped);
-				ldap_memfree(srv_cred);
+				ber_bvfree(srv_cred);
 			}
 		}
 	}
diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c
index 5b17676a9f..25bcd9c44e 100644
--- a/src/modules/rlm_mschap/rlm_mschap.c
+++ b/src/modules/rlm_mschap/rlm_mschap.c
@@ -992,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");
@@ -1001,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_mschap/rlm_mschap.h b/src/modules/rlm_mschap/rlm_mschap.h
index 4109715dd2..7418a85104 100644
--- a/src/modules/rlm_mschap/rlm_mschap.h
+++ b/src/modules/rlm_mschap/rlm_mschap.h
@@ -8,7 +8,9 @@ RCSIDH(rlm_mschap_h, "$Id$")
 #include "config.h"
 
 #ifdef WITH_AUTH_WINBIND
+DIAG_OFF(documentation)
 #  include <wbclient.h>
+DIAG_ON(documentation)
 #endif
 
 /* Method of authentication we are going to use */
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_pwe.c b/src/modules/rlm_otp/otp_pwe.c
index 56a4dbc71b..99a6d07769 100644
--- a/src/modules/rlm_otp/otp_pwe.c
+++ b/src/modules/rlm_otp/otp_pwe.c
@@ -28,19 +28,11 @@
 RCSID("$Id$")
 
 /* avoid inclusion of these FR headers which conflict w/ OpenSSL */
-#define _FR_MD4_H
-#define _FR_SHA1_H
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/rad_assert.h>
 
 #include "extern.h"
 
-USES_APPLE_DEPRECATED_API
-#include <openssl/des.h>
-#include <openssl/md4.h>
-#include <openssl/md5.h>
-#include <openssl/sha.h>
-
 #include <string.h>
 
 /* Attribute IDs for supported password encodings. */
diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c
index 66fd8b4987..6e53430c2f 100644
--- a/src/modules/rlm_otp/otp_radstate.c
+++ b/src/modules/rlm_otp/otp_radstate.c
@@ -22,17 +22,13 @@
 RCSID("$Id$")
 USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
-/* avoid inclusion of these FR headers which conflict w/ OpenSSL */
-#define _FR_MD4_H
-#define _FR_SHA1_H
-
 #include "extern.h"
 
 #include <string.h>
 
-#include <openssl/des.h> /* des_cblock */
 #include <openssl/md5.h>
 #include <openssl/hmac.h>
+#include <freeradius-devel/openssl3.h>
 
 /*
  * Generate the State attribute, suitable for passing to fr_pair_make().
@@ -113,6 +109,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 +122,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_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c
index cc151f7299..318e9a806e 100644
--- a/src/modules/rlm_pap/rlm_pap.c
+++ b/src/modules/rlm_pap/rlm_pap.c
@@ -1003,6 +1003,15 @@ static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2_parse(REQUEST *reques
 		p = q + 1;
 	}
 
+	/*
+	 *	Sanitise iterations. Seems OpenSSL 1.0 did this, but at least
+	 *	version 1.1 in RH8 does not, so safest to check ourselves.
+	 */
+	if (iterations == 0) {
+		RWDEBUG("PBKDF2 can not have zero iterations; increasing to 1");
+		iterations = 1;
+	}
+
 	if (((end - p) < 1) || !(q = memchr(p, salt_sep, end - p))) {
 		REDEBUG("PBKDF2-Password missing salt component");
 		goto finish;
diff --git a/src/modules/rlm_python3/configure b/src/modules/rlm_python3/configure
index 363bf59e1f..52bc177dcd 100755
--- a/src/modules/rlm_python3/configure
+++ b/src/modules/rlm_python3/configure
@@ -3175,7 +3175,9 @@ $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s cflags were \"${python3_cflags}\"" >&6
 			s/ -W[^ ]*/ /g;\
 			s/ -DNDEBUG[[:blank:]]*/ /g;\
 			s/ -frecord-gcc-switches/ /g;\
-			s/ -specs=[^ ]*/ /g;
+			s/ -specs=[^ ]*/ /g;\
+			s/ -ffat-lto-objects/ /g;\
+			s/ -flto=[^ ]*/ /g;
 			'`
 		{ $as_echo "$as_me:${as_lineno-$LINENO}: Sanitized cflags were \"${mod_cflags}\"" >&5
 $as_echo "$as_me: Sanitized cflags were \"${mod_cflags}\"" >&6;}
diff --git a/src/modules/rlm_python3/configure.ac b/src/modules/rlm_python3/configure.ac
index 74d54bf5bc..bc0e97f9ba 100644
--- a/src/modules/rlm_python3/configure.ac
+++ b/src/modules/rlm_python3/configure.ac
@@ -53,6 +53,8 @@ if test x$with_[]modname != xno; then
 		dnl # Strip -DNDEBUG
 		dnl # Strip -frecord-gcc-switches, We decide if we need that, not python.
 		dnl # Strip -specs=/path/whatever.specs, We don't need the compiler .specs that comes from Python
+		dnl # Strip -ffat-lto-objects, We decide if we need that, not python.
+		dnl # Strip -flto=auto, We decide if we need that, not python.
 		mod_cflags=`echo " $python3_cflags" | sed -e '\
 			s/ -I/ -isystem/g;\
 			s/ -isysroot[[ =]]\{0,1\}[[^-]]*/ /g;\
@@ -62,7 +64,9 @@ if test x$with_[]modname != xno; then
 			s/ -W[[^ ]]*/ /g;\
 			s/ -DNDEBUG[[[:blank:]]]*/ /g;\
 			s/ -frecord-gcc-switches/ /g;\
-			s/ -specs=[[^ ]]*/ /g;
+			s/ -specs=[[^ ]]*/ /g; \
+			s/ -ffat-lto-objects/ /g; \
+			s/ -flto=[[^ ]]*/ /g;
 			'`
 		AC_MSG_NOTICE([Sanitized cflags were \"${mod_cflags}\"])
 
diff --git a/src/modules/rlm_sql/drivers/rlm_sql_db2/rlm_sql_db2.c b/src/modules/rlm_sql/drivers/rlm_sql_db2/rlm_sql_db2.c
index d8d9a3674a..01d1710b76 100644
--- a/src/modules/rlm_sql/drivers/rlm_sql_db2/rlm_sql_db2.c
+++ b/src/modules/rlm_sql/drivers/rlm_sql_db2/rlm_sql_db2.c
@@ -35,8 +35,8 @@ RCSID("$Id$")
 
 #include <sys/stat.h>
 
-#include <sql.h>
 #include <sqlcli.h>
+
 #include "rlm_sql.h"
 
 typedef struct rlm_sql_conn {
@@ -51,18 +51,18 @@ static int _sql_socket_destructor(rlm_sql_db2_conn_t *conn)
 
 	if (conn->stmt) {
 		SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
-		conn->stmt = NULL;
+		conn->stmt = 0;
 	}
 
 	if (conn->dbc_handle) {
 		SQLDisconnect(conn->dbc_handle);
 		SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc_handle);
-		conn->dbc_handle = NULL;
+		conn->dbc_handle = 0;
 	}
 
 	if (conn->env_handle) {
 		SQLFreeHandle(SQL_HANDLE_ENV, conn->env_handle);
-		conn->env_handle = NULL;
+		conn->env_handle = 0;
 	}
 
 	return RLM_SQL_OK;
@@ -123,7 +123,7 @@ static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *
 		retval = SQLExecDirect(conn->stmt, db2_query, SQL_NTS);
 		if (retval != SQL_SUCCESS) {
 			SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
-			conn->stmt = NULL;
+			conn->stmt = 0;
 
 			/* XXX Check if retval means we should return RLM_SQL_RECONNECT */
 			ERROR("Could not execute statement \"%s\"", query);
@@ -194,7 +194,7 @@ static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_conf
 
 	if (conn->stmt) {
 		SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
-		conn->stmt = NULL;
+		conn->stmt = 0;
 	}
 
 	return RLM_SQL_OK;
diff --git a/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c b/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c
index a7992647cd..78d1b8f566 100644
--- a/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c
+++ b/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c
@@ -250,7 +250,7 @@ static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *c
 #if HAVE_TLS_OPTIONS
 	{
 		enum mysql_option ssl_mysql_opt;
-		bool              ssl_mysql_arg = false;
+		unsigned int   	  ssl_mysql_arg;
 		bool              ssl_mode_isset = false;
 
 #  if defined(MARIADB_VERSION_ID) || defined(MARIADB_BASE_VERSION)
@@ -259,7 +259,16 @@ static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *c
 #    else
 		if (driver->tls_required) {
 #    endif
-			ssl_mode_isset = ssl_mysql_arg = true;
+			ssl_mode_isset = true;
+#  if defined(MARIADB_VERSION_ID) || defined(MARIADB_BASE_VERSION)
+			/**
+			 * For MariaDB, It should be true as can be seen in
+			 * https://github.com/MariaDB/server/blob/mariadb-5.5.68/sql-common/client.c#L4338
+			 */
+			ssl_mysql_arg = true;
+#else
+			ssl_mysql_arg = SSL_MODE_REQUIRED;
+#endif
 			ssl_mysql_opt = MYSQL_OPT_SSL_VERIFY_SERVER_CERT;
 		}
 #  else
diff --git a/src/modules/rlm_sqlippool/rlm_sqlippool.c b/src/modules/rlm_sqlippool/rlm_sqlippool.c
index 0e3679d60e..5a0a6f1509 100644
--- a/src/modules/rlm_sqlippool/rlm_sqlippool.c
+++ b/src/modules/rlm_sqlippool/rlm_sqlippool.c
@@ -576,7 +576,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
 	char allocation[MAX_STRING_LEN];
 	int allocation_len;
-	VALUE_PAIR *vp;
+	VALUE_PAIR *vp = NULL;
 	rlm_sql_handle_t *handle;
 	time_t now;
 	uint32_t diff_time;
@@ -697,7 +697,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 				 *	that case, we should return
 				 *	NOTFOUND
 				 */
-				RDEBUG("pool appears to be full");
+				REDEBUG("pool appears to be full");
 				return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND);
 
 			}
@@ -708,14 +708,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 			 *	sqlippool, so we should just ignore this
 			 *	allocation failure and return NOOP
 			 */
-			RDEBUG("IP address could not be allocated as no pool exists with that name");
+			REDEBUG("IP address could not be allocated as no pool exists with that name");
 			return RLM_MODULE_NOOP;
 
 		}
 
 		fr_connection_release(inst->sql_inst->pool, handle);
 
-		RDEBUG("IP address could not be allocated");
+		REDEBUG("IP address could not be allocated");
 		return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
 	}
 
@@ -727,26 +727,28 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) {
 		DO_PART(allocate_commit);
 
-		RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation);
+		talloc_free(vp);
+		REDEBUG("Invalid IP address [%s] returned from database query.", allocation);
 		fr_connection_release(inst->sql_inst->pool, handle);
 		return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
 	}
 
-	RDEBUG("Allocated IP %s", allocation);
-	fr_pair_add(&request->reply->vps, vp);
-
 	/*
 	 *	UPDATE
 	 */
 	if (sqlippool_command(inst->allocate_update, &handle, inst, request,
 			      allocation, allocation_len) < 0) {
 	error:
+		talloc_free(vp);
 		if (handle) fr_connection_release(inst->sql_inst->pool, handle);
 		return RLM_MODULE_FAIL;
 	}
 
 	DO_PART(allocate_commit);
 
+	RDEBUG("Allocated IP %s", allocation);
+	fr_pair_add(&request->reply->vps, vp);
+
 	if (handle) fr_connection_release(inst->sql_inst->pool, handle);
 
 	return do_logging(request, inst->log_success, RLM_MODULE_OK);
diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c
index 2d777b6b31..d2125eb3a5 100644
--- a/src/modules/rlm_wimax/rlm_wimax.c
+++ b/src/modules/rlm_wimax/rlm_wimax.c
@@ -32,6 +32,8 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 #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
@@ -307,6 +309,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 	 */
 	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);
@@ -318,6 +321,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 
 	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);
@@ -330,6 +334,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 	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);
 
 	/*
@@ -387,6 +392,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 		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);
 
 		/*
@@ -437,6 +443,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 		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);
 
 		/*
@@ -487,6 +494,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 		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);
 
 		/*
@@ -533,6 +541,7 @@ static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk,
 
 		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);
@@ -708,6 +717,7 @@ static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst,
 	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);
 
diff --git a/src/tests/Makefile b/src/tests/Makefile
index e3180b80cb..fe836c6bed 100644
--- a/src/tests/Makefile
+++ b/src/tests/Makefile
@@ -49,10 +49,10 @@ override EAPOL_TEST_BIN := $(shell $(top_builddir)/scripts/ci/eapol_test-build.s
 endif
 
 $(BUILD_PATH)/tests/eapol_test/eapol_test.mk: | $(BUILD_PATH)/tests/eapol_test
-	@echo "EAPOL_TEST=" $(EAPOL_TEST_BIN) > $@
-	@echo "TLS1_3=" $(shell strings "$(RADIUSD_BIN)" | grep -q '^tls13_enable$$' && \
-		strings "$(EAPOL_TEST_BIN)" | grep 'tlsv1_3' | head -n 1) >> $@
-	@echo "OPENSSL_OK=" $(shell openssl version | grep -v ' 1\.0') >> $@
+	@echo "EAPOL_TEST=$(EAPOL_TEST_BIN)" > $@
+	@echo "TLS1_3=$(shell openssl ciphers -s -v 'ECDHE:!COMPLEMENTOFDEFAULT'| grep -q 'TLSv1.3' && echo yes)" >> $@
+	@echo "OPENSSL_OK=$(shell openssl version | grep -v ' 1\.0' >/dev/null && echo yes)" >> $@
+	@echo "OPENSSL3_OK=$(shell openssl version | grep -q ' OpenSSL 3\.0' && echo yes)" >> $@
 else
 #
 #  No OpenSSL means that we don't even try to build eapol_test
@@ -76,6 +76,17 @@ else
 SECLEVEL=@SECLEVEL=1
 endif
 
+#
+#  For OpenSSL 3.0.x, as described in https://github.com/openssl/openssl/blob/master/doc/man7/migration_guide.pod
+#
+#  "The security strength of SHA1 and MD5 based signatures in TLS has been reduced.
+#   This results in SSL 3, TLS 1.0, TLS 1.1 and DTLS 1.0 no longer working at the
+#   default security level of 1 and instead requires security level 0."
+#
+ifeq "$(OPENSSL3_OK)" "yes"
+SECLEVEL=@SECLEVEL=0
+endif
+
 RADDB_PATH := $(top_builddir)/raddb/
 
 TESTS	= mschapv1 digest-01/digest* \
@@ -150,7 +161,6 @@ config/eap-test: $(RADDB_PATH)mods-available/eap config/eap-test-inner-tunnel
 	     -e 's/^\(.*\)persist_dir =/  persist_dir =/' \
 	     -e 's/tls_min_version = "1.2"/tls_min_version = "1.0"/' \
 	     -e '$(if $(TLS1_3),s/tls_max_version = "1.2"/tls_max_version = "1.3"/)' \
-	     -e  '/tls_max_version/s/^//p; /tls_max_version/s/^.*/        tls13_enable=true/' \
 	     -e 's/cipher_list = "DEFAULT"/cipher_list = "DEFAULT${SECLEVEL}"/' \
 		 < $< > $@
 
@@ -192,15 +202,16 @@ radiusd.kill:
 #  Otherwise do nothing.
 #
 ifneq "$(EAPOL_TEST)" ""
-EAP_FILES	= eap-md5.conf
-EAP_TLS_FILES	= eap-ttls-pap.conf eap-ttls-mschapv2.conf peap-mschapv2.conf
+EAP_FILES        = eap-md5.conf
+EAP_TLS_FILES    = eap-ttls-pap.conf eap-ttls-mschapv2.conf peap-mschapv2.conf
 EAP_TLS_VERSIONS = 1.1 1.2
+EAP_TLS_DISABLE_STRING = tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1
+
 ifneq "$(TLS1_3)" ""
-EAP_TLS_VERSIONS += 1.3
+EAP_TLS_VERSIONS       += 1.3
+EAP_TLS_DISABLE_STRING += tls_disable_tlsv1_3=1
 endif
 
-EAP_TLS_DISABLE_STRING=tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1 $(TLS1_3)
-
 .PHONY: $(BUILD_PATH)/tests/eap
 $(BUILD_PATH)/tests/eap:
 	@mkdir -p $@
diff --git a/src/tests/config/test.conf b/src/tests/config/test.conf
index bab2c4d339..832b1259f8 100644
--- a/src/tests/config/test.conf
+++ b/src/tests/config/test.conf
@@ -29,7 +29,7 @@ policy {
 	files.authorize {
 		if (User-Name == "bob") {
 			update control {
-				Cleartext-Password := "bob"
+				&Cleartext-Password := "bob"
 			}
 		}
 	}
@@ -44,12 +44,6 @@ policy {
 #	radiusd -Xd src/tests/ -i 127.0.0.1 -p 12340 -n test
 #
 server test {
-	listen {
-		type = detail
-		filename = ${radacctdir}/detail
-		load_factor = 10
-	}
-
 	listen {
 		ipaddr = 127.0.0.1
 		port = ${test_port}
@@ -58,7 +52,7 @@ server test {
 
 authorize {
 	update reply {
-		Test-Server-Port = "%{Packet-Dst-Port}"
+		&Test-Server-Port = "%{Packet-Dst-Port}"
 	}
 
 	if (User-Name == "bob") {
@@ -67,30 +61,30 @@ authorize {
 		#  Or, a hashed version thereof.
 		#
 		if (Digest-Response) {
-			if ("%{Test-Number}" == "1") {
+			if (&Test-Number == "1") {
 				update control {
-					Cleartext-Password := "zanzibar"
+					&Cleartext-Password := "zanzibar"
 				}
 			}
 			elsif (Test-Number == "2") {
 				update control {
-					Digest-HA1 := 12af60467a33e8518da5c68bbff12b11
+					&Digest-HA1 := 12af60467a33e8518da5c68bbff12b11
 				}
 			}
 		}	
 		else {
 			update control {
-				Cleartext-Password := "bob"
+				&Cleartext-Password := "bob"
 			}
 		}
 	}
 
 	if (User-Name =~ /^(.*)@test\.example\.com$/) {
 		update request {
-			Stripped-User-Name := "%{1}"
+			&Stripped-User-Name := "%{1}"
 		}
 		update control {
-			Proxy-To-Realm := test.example.com
+			&Proxy-To-Realm := test.example.com
 		}
 	}
 
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/src/tests/keywords/xlat-concat b/src/tests/keywords/xlat-concat
new file mode 100644
index 0000000000..e0c55a9a8f
--- /dev/null
+++ b/src/tests/keywords/xlat-concat
@@ -0,0 +1,40 @@
+#
+# PRE: xlat-list
+#
+#  concat xlat
+#
+
+update control {
+    control !* ANY
+}
+
+update control {
+	Tmp-IP-Address-0 := 192.0.2.1
+	Tmp-IP-Address-0 += 192.0.2.2
+}
+
+update request {
+	Tmp-String-0 := "%{concat:control:[*] ;}"
+}
+
+if (Tmp-String-0 != '192.0.2.1;192.0.2.2') {
+	update {
+		reply:Filter-Id += 'fail 1'
+	}
+}
+
+update request {
+	Tmp-String-0 := "%{concat:control:[*] X}"
+}
+
+if (Tmp-String-0 != '192.0.2.1X192.0.2.2') {
+	update {
+		reply:Filter-Id += 'fail 2'
+	}
+}
+
+# Boilerplate junk
+update {
+	control:Cleartext-Password := 'hello'
+	reply:Filter-Id := 'filter'
+}
diff --git a/src/tests/modules/ldap/all.mk b/src/tests/modules/ldap/all.mk
index 3464f6880c..1fc53d118c 100644
--- a/src/tests/modules/ldap/all.mk
+++ b/src/tests/modules/ldap/all.mk
@@ -6,6 +6,3 @@
 
 # Don't test ldap if TEST_SERVER ENV is not set
 ldap_require_test_server := 1
-
-ldap.test:
-	@echo OK: ldap.test
diff --git a/src/tests/modules/ldap/module.conf b/src/tests/modules/ldap/module.conf
index 3e5f1285b3..dcf3126dbd 100644
--- a/src/tests/modules/ldap/module.conf
+++ b/src/tests/modules/ldap/module.conf
@@ -20,7 +20,7 @@ ldap {
 #	server = 'ldap.rrdns.example.org'
 
 	#  Port to connect on, defaults to 389, will be ignored for LDAP URIs.
-#	port = 389
+	port = $ENV{LDAP_TEST_SERVER_PORT}
 
 	#  Administrator account for searching and possibly modifying.
 	identity = 'cn=admin,dc=example,dc=com'
@@ -431,7 +431,7 @@ ldap {
 		#
 		#	default: 0x0000 (no debugging messages)
 		#	Example:(LDAP_DEBUG_FILTER+LDAP_DEBUG_CONNS)
-		ldap_debug = 0x0028
+		ldap_debug = 0x0801
 	}
 
 	#
diff --git a/src/tests/radiusd.mk b/src/tests/radiusd.mk
index 85eb4eb497..eed4c79a91 100644
--- a/src/tests/radiusd.mk
+++ b/src/tests/radiusd.mk
@@ -72,7 +72,7 @@ $(TEST).radiusd_stop: | ${2}
 	${Q}if [ -f ${2}/radiusd.pid ]; then \
 		if ! ps `cat ${2}/radiusd.pid` >/dev/null 2>&1; then \
 		    rm -f ${2}/radiusd.pid; \
-		    echo "FreeRADIUS terminated during test called by $(TEST).radiusd_kill"; \
+		    echo "FreeRADIUS terminated during test called by $(TEST).radiusd_stop"; \
 		    echo "GDB output was:"; \
 		    cat "${2}/gdb.log" 2> /dev/null; \
 		    echo "--------------------------------------------------"; \
diff --git a/src/tests/radsec/.gitignore b/src/tests/radsec/.gitignore
deleted file mode 100644
index 9daa835c1e..0000000000
--- a/src/tests/radsec/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-dictionary
-radiusd.conf
-sites-enabled
-mods-enabled
-radrelay.conf
-test.conf
diff --git a/src/tests/radsec/1.basic-auth.reply b/src/tests/radsec/1.basic-auth.reply
deleted file mode 100644
index 77aa6b1029..0000000000
--- a/src/tests/radsec/1.basic-auth.reply
+++ /dev/null
@@ -1,2 +0,0 @@
-Received Access-Accept
-
diff --git a/src/tests/radsec/1.basic-auth.request b/src/tests/radsec/1.basic-auth.request
deleted file mode 100644
index a6ddb8e3f6..0000000000
--- a/src/tests/radsec/1.basic-auth.request
+++ /dev/null
@@ -1,3 +0,0 @@
-User-Name = "bob",
-NAS-IP-Address = "1.2.3.4",
-Called-Station-Id = "key0"
diff --git a/src/tests/radsec/2.ipaddrudp-coa.reply b/src/tests/radsec/2.ipaddrudp-coa.reply
deleted file mode 100644
index 8ea0bfdc0e..0000000000
--- a/src/tests/radsec/2.ipaddrudp-coa.reply
+++ /dev/null
@@ -1,4 +0,0 @@
-delay 2.5
-Received CoA-ACK
-Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"  "coa-buffered-reader:pre-proxy"  "proxy-tls-default:recv-coa"  "proxy-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "proxy-originate-coa-relay:post-proxy-coa-ack"  "proxy-tls-default:send-coa"  "coa-buffered-reader:post-proxy"$
-
diff --git a/src/tests/radsec/2.ipaddrudp-coa.request b/src/tests/radsec/2.ipaddrudp-coa.request
deleted file mode 100644
index 9d3f5eb619..0000000000
--- a/src/tests/radsec/2.ipaddrudp-coa.request
+++ /dev/null
@@ -1,3 +0,0 @@
-User-Name = "IpAddress",
-NAS-IP-Address = "127.0.0.1",
-Called-Station-Id = "12341",
diff --git a/src/tests/radsec/3.homepooludp-coa.reply b/src/tests/radsec/3.homepooludp-coa.reply
deleted file mode 100644
index ab9f0a1790..0000000000
--- a/src/tests/radsec/3.homepooludp-coa.reply
+++ /dev/null
@@ -1,4 +0,0 @@
-delay 2.5
-Received CoA-ACK
-Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"  "home-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "home-originate-coa-relay:post-proxy-coa-ack"$
-
diff --git a/src/tests/radsec/3.homepooludp-coa.request b/src/tests/radsec/3.homepooludp-coa.request
deleted file mode 100644
index e3bff09450..0000000000
--- a/src/tests/radsec/3.homepooludp-coa.request
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Name = "HomePoolCoA",
-Called-Station-Id = "coa-nas"
diff --git a/src/tests/radsec/4.homepooltls-coa.reply b/src/tests/radsec/4.homepooltls-coa.reply
deleted file mode 100644
index 46668948db..0000000000
--- a/src/tests/radsec/4.homepooltls-coa.reply
+++ /dev/null
@@ -1,4 +0,0 @@
-delay 2.5
-Received CoA-ACK
-Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"  "home-originate-coa-relay:pre-proxy"  "proxy-tls-default:recv-coa"  "proxy-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "proxy-originate-coa-relay:post-proxy-coa-ack"  "proxy-tls-default:send-coa"  "home-originate-coa-relay:post-proxy-coa-ack"$
-
diff --git a/src/tests/radsec/4.homepooltls-coa.request b/src/tests/radsec/4.homepooltls-coa.request
deleted file mode 100644
index 7038e25e4e..0000000000
--- a/src/tests/radsec/4.homepooltls-coa.request
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Name = "HomePoolCoA",
-Called-Station-Id = "coa-nas-tls"
diff --git a/src/tests/radsec/5.singletunnel_proxy-coa.reply b/src/tests/radsec/5.singletunnel_proxy-coa.reply
deleted file mode 100644
index 81a41736e9..0000000000
--- a/src/tests/radsec/5.singletunnel_proxy-coa.reply
+++ /dev/null
@@ -1,6 +0,0 @@
-# We don't need delay since proxy flow will be finished
-# just after final CoA home server will return response.
-#delay 2.5
-Received CoA-ACK
-Acct-Session-Id = "default:pre-proxy"  "coa_tls:recv-coa"  "proxy-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "proxy-originate-coa-relay:post-proxy-coa-ack"  "coa_tls:send-coa"  "default:post-proxy-coa-ack"$
-
diff --git a/src/tests/radsec/5.singletunnel_proxy-coa.request b/src/tests/radsec/5.singletunnel_proxy-coa.request
deleted file mode 100644
index 72ace4df81..0000000000
--- a/src/tests/radsec/5.singletunnel_proxy-coa.request
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Name = "TcpSessionKey-Proxy",
-Called-Station-Id = "key0"
diff --git a/src/tests/radsec/6.singletunnel_originate-coa.reply b/src/tests/radsec/6.singletunnel_originate-coa.reply
deleted file mode 100644
index 6a242b00e0..0000000000
--- a/src/tests/radsec/6.singletunnel_originate-coa.reply
+++ /dev/null
@@ -1,4 +0,0 @@
-delay 2.5
-Received CoA-ACK
-Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"  "default:pre-proxy"  "coa_tls:recv-coa"  "proxy-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "proxy-originate-coa-relay:post-proxy-coa-ack"  "coa_tls:send-coa"  "default:post-proxy-coa-ack"
-
diff --git a/src/tests/radsec/6.singletunnel_originate-coa.request b/src/tests/radsec/6.singletunnel_originate-coa.request
deleted file mode 100644
index a838730694..0000000000
--- a/src/tests/radsec/6.singletunnel_originate-coa.request
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Name = "TcpSessionKey",
-Called-Station-Id = "key0"
diff --git a/src/tests/radsec/7.coareply-auth.reply b/src/tests/radsec/7.coareply-auth.reply
deleted file mode 100644
index 62e680ecb5..0000000000
--- a/src/tests/radsec/7.coareply-auth.reply
+++ /dev/null
@@ -1,4 +0,0 @@
-delay 2.5
-Received Access-Accept
-Acct-Session-Id = "default:post-auth"  "default:pre-proxy"  "coa_tls:recv-coa"  "proxy-originate-coa-relay:pre-proxy"  "coa:recv-coa"  "coa:send-coa"  "proxy-originate-coa-relay:post-proxy-coa-ack"  "coa_tls:send-coa"  "default:post-proxy-coa-ack"$
-
diff --git a/src/tests/radsec/7.coareply-auth.request b/src/tests/radsec/7.coareply-auth.request
deleted file mode 100644
index bd2e2b4b41..0000000000
--- a/src/tests/radsec/7.coareply-auth.request
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Name = "PostAuthCoA",
-Called-Station-Id = "key0",
diff --git a/src/tests/radsec/Makefile b/src/tests/radsec/Makefile
deleted file mode 100644
index d732b291d0..0000000000
--- a/src/tests/radsec/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-include ../../../Make.inc
-
-all: tests.radsec
-	@echo "All tests done"
-
-include all.mk
-
-
-.PHONY: clean
-clean: clean.tests.radsec
\ No newline at end of file
diff --git a/src/tests/radsec/README.rst b/src/tests/radsec/README.rst
deleted file mode 100644
index a016a028ec..0000000000
--- a/src/tests/radsec/README.rst
+++ /dev/null
@@ -1,103 +0,0 @@
-=======================
-Tests for radsec flows.
-=======================
-
-                                 RADIUS CoA
-       ┌─────────────────────────────────────────────────────────────┐
-       │                                                             │
-┌──────▼───────┐             ┌────────────────┐              ┌───────┴────────┐
-│              │             │                │  RADSEC CoA  │                │
-│   radiusd    │  RADIUS CoA │    radiusd     ◄──────────────┤    radiusd     │
-│              ◄─────────────┤                │  RADSEC Auth │                │
-│  CoA Server  │             │  Proxy Server  ├──────────────►  Home Server   │
-│              │             │                │              │                │
-└──────────────┘             └───────▲────────┘              └───────▲────────┘
-                                     │                               │
-                                     │ RADIUS                        │ RADIUS
-                                     │ Auth                          │ CoA
-                             ┌───────┴────────┐              ┌───────┴────────┐
-                             │   radclient    │              │   radclient    │
-                             └────────────────┘              └────────────────┘
-
-
-FreeRADIUS common configuration is located (obviously) in
-src/tests/radsec/radddb directory. Specific configurations for separate radiusd
-instances are located under their respective directories: config-coa,
-config-proxy, config-home.
-
-Each test is a pair of two files ending with \*.request and \*.reply.
-
-To run these tests separately, make sure you run 'make test' from the root
-directory beforehand.
-
-Request files.
-==============
-
-\*.request file specifies attributes to be sent.
-
-The name of the file (the part after the dash) specifies the type of the request
-to be sent.
-
-For example 1.basic-auth.request sends an auth request and 2.basic-coa.request
-sends coa.
-
-* Authentication requests.
---------------------------
-Radclient sends plain RADIUS Access-Request to Proxy Server. Proxy Server then
-proxies this authentication request with RADSEC to Home Server. An opened TLS
-tunnel is used later to accept CoA requests from Home Server.
-
-* CoA requests.
----------------
-Radclient sends plain RADIUS CoA request to Home Server. Depending on the
-attributes Home Server does one of the following:
-
-- Originates CoA request to Proxy Server with RADSEC - original flow. This is
-the regular flow where Proxy Server acts as a TCP server and Home Server (as
-a TCP client) first needs to establish a connection to it.
-
-- Originates CoA request to Proxy Server with RADSEC - 'single tunnel flow'.
-This is the new flow where Proxy Server can accept CoA requests from Home Server
-within the same tunnel that it has opened for Access-Request. In this case, the
-Proxy Server is still a TCP client yet in terms of RADIUS protocol it acts as
-a CoA Server.
-
-In both of these two cases, the Proxy Server forwards a CoA request to CoA
-Server to complete the flow. As an example CoA Server responds with CoA-ACK,
-then in turn Proxy Server responds with CoA-ACK to Home Server and the flow
-completes.
-
-- Originates CoA request directly to CoA Server. Although this is not a RADSEC
-flow, that is also good to check.
-
-
-Reply files.
-============
-
-\*.reply file specify a result to be expected for the corresponding \*.request
-file.
-
-
-For each such pair of \*.request \*.reply files runtest.sh is run.
-
-This shell script sends a request with radclient.
-
-Several freeRADIUS instances process requests and add attributes to be checked.
-In the end of the flow all cumulative attributes are written to the detail_test
-file for later checking.
-
-The runtest.sh checks the result following a \*.reply file.
-
-After test is performed a new directory is created with name "$TEST_NAME.result"
-where all intermediate files realted to the test are located, an example of the
-directory structure is like follows:
-
-ok                           - status file: either ok or fail
-detail_test                  - helper file to save attributes by freeRADIUS
-2.ipaddrtls-coa.reply.tmp    - reply file w/o internal commands (e.g delay)
-fr-home-2.ipaddrtls-coa.log  - a part of freeRADIUS logs related to the test
-fr-coa-2.ipaddrtls-coa.log   - the same just for radiusd CoA Server
-fr-proxy-2.ipaddrtls-coa.log - the same just for radiusd Proxy Server
-radclient.log                - logs for radclient
-result-2.ipaddrtls-coa.log   - combined and aggregated radclient.log and
-                             - detail_test to be checked against \*.reply file
diff --git a/src/tests/radsec/all.mk b/src/tests/radsec/all.mk
deleted file mode 100644
index 1d6140e6ef..0000000000
--- a/src/tests/radsec/all.mk
+++ /dev/null
@@ -1,150 +0,0 @@
-BUILD_PATH      := $(top_builddir)/build
-TEST_PATH       := $(top_builddir)/src/tests/radsec
-BIN_PATH        := $(BUILD_PATH)/bin/local
-LIB_PATH        := $(BUILD_PATH)/lib/.libs/
-RADDB_PATH      := $(top_builddir)/raddb
-
-# Naming convention for ports is like follows: port-<owner>-<description>.
-# Owner may be either CoA Server, Proxy Server or Home Server
-port-proxy-auth 	= 12340
-port-proxy-coa 		= 12341
-port-home-auth 		= 12342
-port-home-coa 	 	= 12343
-port-coa 	        = 12344
-
-# Port difines for request types: auth or coa
-auth-port = $(port-proxy-auth)
-coa-port  = $(port-home-coa)
-
-
-#
-#  You can watch what it's doing by:
-#
-#	$ VERBOSE=1 make ... args ...
-#
-ifeq "${VERBOSE}" ""
-    Q=@
-else
-    Q=
-endif
-
-raddb:
-	${Q}echo "Setting up raddb directory"
-	${Q}cp -r $(top_builddir)/raddb $(TEST_PATH)
-	${Q}rm -rf $(TEST_PATH)/raddb/sites-enabled/* # we have per server config
-	${Q}echo 'detail detail_test {' >> $(TEST_PATH)/raddb/mods-enabled/detail
-	${Q}echo '	filename = $${radacctdir}/detail_test' >> $(TEST_PATH)/raddb/mods-enabled/detail
-	${Q}echo '}' >> $(TEST_PATH)/raddb/mods-enabled/detail
-	${Q}echo 'detail detail_coa {' >> $(TEST_PATH)/raddb/mods-enabled/detail
-	${Q}echo '	filename = $${radacctdir}/detail_coa' >> $(TEST_PATH)/raddb/mods-enabled/detail
-	${Q}echo '}' >> $(TEST_PATH)/raddb/mods-enabled/detail
-
-	${Q}$(MAKE) -C $(TEST_PATH)/raddb/certs
-
-dictionary:
-	${Q}echo "# test dictionary not install.  Delete at any time." > $(TEST_PATH)/dictionary
-	${Q}echo '$$INCLUDE ' $(top_builddir)/share/dictionary >> $(TEST_PATH)/dictionary
-
-
-define TEST_CONF
-	${Q}printf "Configuring radiusd $(1) ->  "
-	${Q}echo "# radiusd test configuration file.  Do not install.  Delete at any time." > $(TEST_PATH)/test-$(1).conf
-	${Q}echo "libdir =" $(LIB_PATH) >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo "testdir =" $(TEST_PATH) >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'logdir = $${testdir}' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'maindir = ${TEST_PATH}/raddb/' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'radacctdir = $${testdir}' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'pidfile = $${testdir}/radiusd-$(1).pid' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'panic_action = "gdb -batch -x $${testdir}/panic.gdb %e %p > $${testdir}/gdb-$(1).log 2>&1; cat $${testdir}/gdb-$(1).log"' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'security {' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo '        allow_vulnerable_openssl = yes' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo '}' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'modconfdir = $${maindir}mods-config' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'certdir = $${maindir}/certs' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo 'cadir   = $${maindir}/certs' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo '$$INCLUDE $${testdir}/config-$(1)/main.conf' >> $(TEST_PATH)/test-$(1).conf
-	${Q}echo '$$INCLUDE $${maindir}/radiusd.conf' >> $(TEST_PATH)/test-$(1).conf
-	${Q}rm -f $(TEST_PATH)/gdb-$(1).log $(TEST_PATH)/fr-$(1).log
-endef
-
-define START_SERVER
-	${Q}printf "Starting $(1) server... "
-	${Q}if ! $(BIN_PATH)/radiusd -Pxxxxml $(TEST_PATH)/fr-$(1).log -d $(TEST_PATH) -n test-$(1) -D $(TEST_PATH); then \
-		echo "failed"; \
-		echo "Last log entries were:"; \
-		tail -n 20 "$(TEST_PATH)/fr-$(1).log"; \
-	else \
-		echo "ok"; \
-	fi
-endef
-
-define PID_SERVER
-	${Q}sed 's/$${{port-proxy-auth}}/$(port-proxy-auth)/g; \
-		s/$${{port-proxy-coa}}/$(port-proxy-coa)/g; \
-		s/$${{port-home-auth}}/$(port-home-auth)/g; \
-		s/$${{port-home-coa}}/$(port-home-coa)/g; \
-		s/$${{port-coa}}/$(port-coa)/g' \
-			$(TEST_PATH)/config-$(1)/main.conf > $(TEST_PATH)/config-$(1)/main.conf
-	$(call TEST_CONF,$(1))
-	$(call START_SERVER,$(1))
-endef
-
-radiusd.pid: raddb dictionary
-	$(call PID_SERVER,coa)
-	$(call PID_SERVER,home)
-	$(call PID_SERVER,proxy)
-
-define KILL_SERVER
-	${Q}if [ -f $(TEST_PATH)/radiusd-$(1).pid ]; then \
-		if ! ps `cat $(TEST_PATH)/radiusd-$(1).pid` >/dev/null 2>&1; then \
-			rm -f $(TEST_PATH)/radiusd-$(1).pid; \
-			echo "FreeRADIUS terminated during test"; \
-			echo "GDB output was:"; \
-			cat "$(TEST_PATH)/gdb-$(1).log"; \
-			echo "Last log entries were:"; \
-			tail -n 20 $(TEST_PATH)/fr-$(1).log; \
-		fi; \
-		if ! kill -TERM `cat $(TEST_PATH)/radiusd-$(1).pid` >/dev/null 2>&1; then \
-			echo "Cannot kill $(TEST_PATH)/radiusd-$(1).pid"; \
-		fi; \
-	fi
-	${Q}rm -f $(TEST_PATH)/radiusd-$(1).pid $(TEST_PATH)/config-$(1)/*.conf
-endef
-
-radiusd-proxy.kill:
-	$(call KILL_SERVER,proxy)
-radiusd-home.kill:
-	$(call KILL_SERVER,home)
-radiusd-coa.kill:
-	$(call KILL_SERVER,coa)
-
-radiusd.kill: radiusd-proxy.kill radiusd-home.kill radiusd-coa.kill
-
-# E.g: basis-auth.request -> TEST_NAME=basic-auth TYPE=auth, PORT=$(auth-port)
-%.request.test:
-	${Q}printf "RADSEC-TEST $@... "
-	${Q}if ! TEST_NAME=$(patsubst %.request.test,%,$@) \
-		TYPE=$(word 2, $(subst -, ,$(patsubst %.request.test,%,$@))) \
-		PORT=$($(word 2, $(subst -, ,$(patsubst %.request.test,%,$@)))-port) \
-		TEST_PATH=$(TEST_PATH) $(TEST_PATH)/runtest.sh 2>&1 > /dev/null; then \
-		echo "failed"; \
-	else \
-		echo "ok"; \
-	fi
-
-# kill the server (if it's running)
-# start the server
-# run the tests
-# kill the server
-#TEST_FILES = 2.basic-coa.request.test
-TEST_FILES = $(sort $(addsuffix .test,$(notdir $(wildcard $(TEST_PATH)/*.request))))
-tests.radsec: radiusd.kill radiusd.pid $(TEST_FILES)
-	${Q}$(MAKE) radiusd.kill
-
-.PHONY: clean.tests.radsec
-clean.tests.radsec: radiusd.kill
-	${Q}cd $(TEST_PATH) && rm -rf raddb/ detail_coa detail_test *.result *.conf dictionary *.ok *.log *.tmp
-
-
-.PHONY: radiusd.kill radiusd-proxy.kill radiusd-home.kill radiusd-coa.kill dictionary raddb
diff --git a/src/tests/radsec/config-coa/main.conf.template b/src/tests/radsec/config-coa/main.conf.template
deleted file mode 100644
index 5baf4b7f1a..0000000000
--- a/src/tests/radsec/config-coa/main.conf.template
+++ /dev/null
@@ -1,37 +0,0 @@
-listen {
-	type = coa
-	ipaddr = 127.0.0.1
-	port = ${{port-coa}}
-	virtual_server = coa
-}
-
-server coa {
-
-	authenticate {
-		Auth-Type PAP {
-			pap
-		}
-
-		Auth-Type MS-CHAP {
-			mschap
-		}
-
-		Auth-Type EAP {
-			eap
-		}
-	}
-
-	recv-coa {
-		update request {
-			&Acct-Session-Id += "coa:recv-coa"
-		}
-	}
-
-	send-coa {
-		update reply {
-			&reply: += request:[*]
-			&reply:Acct-Session-Id += "coa:send-coa"
-		}
-	}
-}
-
diff --git a/src/tests/radsec/config-home/main.conf b/src/tests/radsec/config-home/main.conf
deleted file mode 100644
index 6fc52723fc..0000000000
--- a/src/tests/radsec/config-home/main.conf
+++ /dev/null
@@ -1,323 +0,0 @@
-listen {
-
-	ipaddr = 127.0.0.1
-	port = ${{port-home-auth}}
-	type = auth+coa
-	proto = tcp
-
-	virtual_server = default
-
-	clients = radsec
-
-	tls {
-		tls_max_version="1.2"
-		private_key_password = whatever
-		private_key_file = ${certdir}/server.pem
-		certificate_file = ${certdir}/server.pem
-		ca_file = ${cadir}/ca.pem
-		dh_file = ${certdir}/dh
-		fragment_size = 8192
-		ca_path = ${cadir}
-		cipher_list = "DEFAULT"
-		cipher_server_preference = no
-
-		cache {
-		      enable = no
-		      lifetime = 24 # hours
-		}
-
-		require_client_cert = yes
-	}
-
-	# Specify the CoA retransmit parameters for CoA single tunnel
-	coa {
-		irt = 1
-		mrt = 16
-		mrc = 0
-		mrd = 5
-	}
-}
-
-clients radsec {
-	client localhost {
-		ipaddr      = 127.0.0.1
-		secret      = radsec
-		proto       = tls
-	
-		limit {
-			max_connections = 16
-			lifetime = 0        # do not close connection
-			idle_timeout = 0    # do not close connection even after an idle period
-		}
-	}
-}
-
-server default {
-	authorize {
-		update control {
-			Originating-Realm-Key := &Called-Station-Id
-			Auth-Type := Accept
-		}
-	}
-
-	authenticate {
-		Auth-Type PAP {
-			pap
-		}
-
-		Auth-Type MS-CHAP {
-			mschap
-		}
-
-		Auth-Type EAP {
-			eap
-		}
-	}
-
-	post-auth {
-		if(User-Name && User-Name == "PostAuthCoA") {
-			update coa {
-				&Acct-Session-Id += "default:post-auth"
-			    &Proxy-To-Originating-Realm := &Called-Station-Id
-			}
-		}
-	}
-
-	pre-proxy {
-		update {
-			&proxy-request:Acct-Session-Id += "default:pre-proxy"
-		}
-	}
-
-	post-proxy {
-		switch &proxy-reply:Packet-Type {
-			case CoA-ACK {
-				update proxy-reply {
-					&Acct-Session-Id += "default:post-proxy-coa-ack"
-				}
-			}
-
-			case CoA-NAK {
-				update proxy-reply {
-					&Acct-Session-Id += "default:post-proxy-coa-nak"
-				}
-			}
-
-			case Disconnect-ACK {
-				update proxy-reply {
-					&Acct-Session-Id += "default:post-proxy-disconnect-ack"
-				}
-			}
-
-			case Disconnect-NAK {
-				update proxy-reply {
-					&Acct-Session-Id += "default:post-proxy-disconnect-nak"
-				}
-			}
-
-			case {
-				fail
-			}
-		}
-
-		# If there was no response at all
-		Post-Proxy-Type Fail-CoA {
-			ok
-		}
-
-		Post-Proxy-Type Fail-Disconnect {
-			ok
-		}
-
-		detail_test.post-proxy
-	}
-}
-
-#
-# CoA Relay
-#
-listen {
-	type = coa
-	ipaddr = 127.0.0.1
-	port = ${{port-home-coa}}
-	virtual_server = coa
-}
-
-server coa {
-	recv-coa {
-
-		update request {
-			COA-Packet-Type := "%{Packet-Type}"
-		}
-
-		if(&User-Name == "TcpSessionKey-Proxy") {
-			# Proxying CoA
-			update control {
-				&Proxy-To-Originating-Realm := &Called-Station-Id
-			}
-		} else {
-			# Originating CoA
-			detail_coa.accounting
-		}
-	}
-}
-
-server coa-buffered-reader {
-	listen {
-		type = detail
-		filename = "${radacctdir}/detail_coa"
-		load_factor = 90
-		track = yes
-	}
-
-	accounting {
-		switch &User-Name {
-			case "IpAddress" {
-				update {
-					coa:Packet-DST-IP-Address := &NAS-IP-Address
-					coa:Packet-DST-Port:= &Called-Station-Id
-				}
-			}
-			case "IpAddressSingleTunnel" {
-				update {
-					coa:Packet-DST-IP-Address := &NAS-IP-Address
-				}
-			}
-			case "HomePoolCoA" {
-				update {
-					coa:Home-Server-Pool := &Called-Station-Id
-				}
-			}
-			case "TcpSessionKey"{
-				update {
-					coa:Proxy-To-Originating-Realm := &Called-Station-Id
-				}
-			}
-		}
-
-		switch &COA-Packet-Type {
-			case "Disconnect-Request" {
-				update {
-					#  Include given attributes
-					&disconnect: += request:[*]
-					&disconnect:Packet-DST-IP-Address := &COA-Packet-DST-IP-Address
-					&disconnect:Packet-DST-Port := &COA-Packet-DST-Port
-					&disconnect:Acct-Session-Id := &COA-Acct-Session-Id
-					&disconnect:Acct-Delay-Time !* ANY
-				}
-			}
-
-			case "CoA-Request" {
-				update {
-					&coa:Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"
-				}
-			}
-		}
-		ok
-	} # accounting
-
-	pre-proxy {
-		update {
-			&proxy-request:Acct-Session-Id += "coa-buffered-reader:pre-proxy"
-		}
-	}
-
-	post-proxy {
-		update {
-			&proxy-reply:Acct-Session-Id += "coa-buffered-reader:post-proxy"
-		}
-		detail_test.post-proxy
-	}
-}
-
-server home-originate-coa-relay {
-
-	pre-proxy {
-		update {
-			&proxy-request:Acct-Session-Id += "home-originate-coa-relay:pre-proxy"
-		}
-	}
-
-	post-proxy {
-		switch &proxy-reply:Packet-Type {
-			case CoA-ACK {
-				update {
-					&proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-coa-ack"
-				}
-			}
-
-			case CoA-NAK {
-				update {
-					&proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-coa-nak"
-				}
-			}
-
-			case Disconnect-ACK {
-				update {
-					&proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-disconnect-ack"
-				}
-			}
-
-			case Disconnect-NAK {
-				update {
-					&proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-disconnect-nak"
-				}
-			}
-
-			case {
-				fail
-			}
-		}
-
-		# If there was no response at all
-		Post-Proxy-Type Fail-CoA {
-			ok
-		}
-
-		Post-Proxy-Type Fail-Disconnect {
-			ok
-		}
-
-		detail_test.post-proxy
-	}
-}
-
-home_server coa-nas {
-	type = coa
-	ipaddr = 127.0.0.1
-	port = ${{port-coa}} # A placeholder to be set in test makefile
-	secret =  testing123
-
-	coa {
-		irt = 2
-		mrt = 16
-		mrc = 5
-		mrd = 30
-	}
-}
-
-home_server_pool coa-nas {
-	type = fail-over
-	home_server = coa-nas
-	virtual_server = home-originate-coa-relay
-}
-
-home_server coa-nas-tls {
-	type = coa
-	ipaddr = 127.0.0.1
-	port = ${{port-proxy-coa}} # A placeholder to be set in test makefile
-	secret =  testing123
-
-	coa {
-		irt = 2
-		mrt = 16
-		mrc = 5
-		mrd = 30
-	}
-}
-
-home_server_pool coa-nas-tls {
-	type = fail-over
-	home_server = coa-nas-tls
-	virtual_server = home-originate-coa-relay
-}
diff --git a/src/tests/radsec/config-proxy/main.conf.template b/src/tests/radsec/config-proxy/main.conf.template
deleted file mode 100644
index 030d72a43b..0000000000
--- a/src/tests/radsec/config-proxy/main.conf.template
+++ /dev/null
@@ -1,208 +0,0 @@
-server proxy-default {
-
-	listen {
-		type = auth+acct
-		ipaddr = 127.0.0.1
-		port = ${{port-proxy-auth}}
-	}
-
-	authorize {
-		update control {
-			&Proxy-To-Realm := "tls"
-		}
-	}
-
-	authenticate {
-		Auth-Type PAP {
-			pap
-		}
-
-		Auth-Type MS-CHAP {
-			mschap
-		}
-
-		Auth-Type EAP {
-			eap
-		}
-	}
-
-	pre-proxy {
-		update {
-			&Acct-Session-Id += "proxy-default:pre-proxy"
-		}
-	}
-
-	post-proxy {
-		update {
-			&Acct-Session-Id += "proxy-default:post-proxy"
-		}
-		detail_test.recv-coa
-	}
-
-	recv-coa {
-		update {
-			&Acct-Session-Id += "proxy-default:recv-coa"
-		}
-		detail_test.recv-coa
-	}
-
-	send-coa {
-		update {
-			&Acct-Session-Id += "proxy-default:send-coa"
-		}
-	}
-}
-
-server proxy-tls-default {
-
-	listen {
-		type = coa
-		ipaddr = 127.0.0.1
-		port = ${{port-proxy-coa}}
-	}
-
-	recv-coa {
-		update {
-			&control:Home-Server-Pool := coa-nas
-			&request:Acct-Session-Id += "proxy-tls-default:recv-coa"
-		}
-	}
-
-	send-coa {
-		update {
-			&reply:Acct-Session-Id += "proxy-tls-default:send-coa"
-		}
-	}
-}
-
-#
-# Proxy To CoA server
-#
-server proxy-originate-coa-relay {
-	pre-proxy {
-		update {
-			&proxy-request:Acct-Session-Id += "proxy-originate-coa-relay:pre-proxy"
-		}
-	}
-	post-proxy {
-		switch &proxy-reply:Packet-Type {
-			case CoA-ACK {
-				update {
-					&proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-coa-ack"
-				}
-			}
-
-			case CoA-NAK {
-				update {
-					&proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-coa-nak"
-				}
-			}
-
-			case Disconnect-ACK {
-				update {
-					&proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-disconnect-ack"
-				}
-			}
-
-			case Disconnect-NAK {
-				update {
-					&proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-disconnect-nak"
-				}
-			}
-
-			case {
-				fail
-			}
-		}
-
-		Post-Proxy-Type Fail-CoA {
-			ok
-		}
-
-		Post-Proxy-Type Fail-Disconnect {
-			ok
-		}
-	}
-}
-
-home_server coa-nas {
-	type = coa
-	ipaddr = 127.0.0.1
-	port = ${{port-coa}} # A placeholder to be set in test makefile
-	secret =  testing123
-
-	coa {
-		irt = 2
-		mrt = 16
-		mrc = 5
-		mrd = 30
-	}
-}
-
-home_server_pool coa-nas {
-	type = fail-over
-	home_server = coa-nas
-	virtual_server = proxy-originate-coa-relay
-}
-
-
-#
-# Proxy To RADSEC Home server
-#
-server coa_tls {
-	recv-coa {
-		update control {
-			&request:Acct-Session-Id += "coa_tls:recv-coa"
-			&Home-Server-Pool := coa-nas
-		}
-	}
-
-	#  When a packet is sent, it is processed through the
-	#  send-coa section.  This applies to *both* CoA-Request and
-	#  Disconnect-Request packets.
-	send-coa {
-		update control {
-			&reply:Acct-Session-Id += "coa_tls:send-coa"
-		}
-	}
-
-	#  You can use pre-proxy and post-proxy sections here, too.
-	#  They will be processed for sending && receiving proxy packets.
-}
-
-home_server tls {
-	ipaddr = 127.0.0.1
-	port = ${{port-home-auth}} # A placeholder to be set in test makefile
-	type = auth+acct+coa
-	secret = radsec
-	proto = tcp
-	status_check = none
-
-	tls {
-		tls_max_version="1.2"
-		private_key_password = whatever
-		private_key_file = ${certdir}/client.key
-		certificate_file = ${certdir}/client.pem
-		ca_file = ${certdir}/ca.pem
-		dh_file = ${certdir}/dh
-		random_file = /dev/urandom
-		fragment_size = 8192
-		ca_path = ${cadir}
-		cipher_list = "DEFAULT"
-	}
-
-	recv_coa {
-		virtual_server = coa_tls
-	}
-}
-
-home_server_pool tls {
-	type = fail-over
-	home_server = tls
-	virtual_server = coa_tls
-}
-
-realm tls {
-	auth_pool = tls
-}
-
diff --git a/src/tests/radsec/runtest.sh b/src/tests/radsec/runtest.sh
deleted file mode 100755
index 811f6bb65b..0000000000
--- a/src/tests/radsec/runtest.sh
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/bin/sh
-#set -x
-
-: ${TYPE=auth}
-: ${TEST_NAME=1.basic-auth}
-: ${PORT=12340}
-: ${SECRET=testing123}
-
-cd $TEST_PATH
-
-BIN_PATH=../../../build/bin/local
-OUTPUT=radclient.log
-
-RES=result-$TEST_NAME.log
-
-clean() {
-    kill $tailcoa $tailhome $tailproxy 2>&1 > /dev/null
-    wait $tailcoa $tailhome $tailproxy 2>&1 > /dev/null # suppress terminated messages
-    echo "" > detail_test
-    rm ./$TEST_NAME.reply.tmp fr-*-$TEST_NAME.log fail ok $RES radclient.log 2>&1 > /dev/null
-}
-
-# Combine a list of several repeated attributes to a single attribute with delimeter:
-# This:
-#	Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"
-#	Acct-Session-Id = "default:send-coa"
-# Become:
-#	Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"  "default:send-coa"
-aggregate() {
-    sort -s -t= -k1,1 ./detail_test | awk -F= '
-        prev!=$1 && prev{
-          print prev FS val;
-          prev=val=""}
-        {
-          val=val?val OFS $2:$2;
-          prev=$1
-        }
-        END{
-          if(val){
-            print prev FS val}
-        }' >> $RES
-}
-
-echo "Running test: $TEST_NAME for port: $PORT type: $TYPE"
-
-clean
-
-tail -f fr-coa.log 2> /dev/null > fr-coa-$TEST_NAME.log &
-tailcoa=$(echo $!)
-tail -f fr-home.log 2> /dev/null > fr-home-$TEST_NAME.log &
-tailhome=$(echo $!)
-tail -f fr-proxy.log 2> /dev/null > fr-proxy-$TEST_NAME.log &
-tailproxy=$(echo $!)
-
-$BIN_PATH/radclient -f $TEST_NAME.request -xF -D ./ 127.0.0.1:$PORT $TYPE $SECRET 1> $OUTPUT
-
-# skip comments
-sed '/^\s*#/d' $TEST_NAME.reply > $TEST_NAME.reply.tmp
-
-# wait if needed
-delay=$(grep delay $TEST_NAME.reply.tmp | awk '{print $2}')
-sed '/delay/d' $TEST_NAME.reply.tmp > $TEST_NAME.reply.tmp
-sleep $delay 2>&1 > /dev/null
-
-cat radclient.log > $RES
-aggregate
-
-while read -r line; do
-    if ! grep "$line" $RES >/dev/null 2>&1; then
-        echo "This test failed!" >> fail
-        echo "Testing $TEST_NAME failed. Cannot find $line in $RES." > fail
-    fi
-done < $TEST_NAME.reply.tmp
-
-if [ ! -f fail ]; then echo "This test succeded!" >> ok; fi
-
-mkdir $TEST_NAME.result 2>&1 > /dev/null
-cp ./$TEST_NAME.reply.tmp fr-*-$TEST_NAME.log fail ok \
-    $RES radclient.log detail_test $TEST_NAME.result 2>&1 > /dev/null
-
-clean
-
-test -f $TEST_NAME.result/ok # exit with the status code
diff --git a/src/tests/salt-test-server/salt/ldap/base.ldif b/src/tests/salt-test-server/salt/ldap/base.ldif
index 1cf5d97c47..7a7a1eb58a 100644
--- a/src/tests/salt-test-server/salt/ldap/base.ldif
+++ b/src/tests/salt-test-server/salt/ldap/base.ldif
@@ -1,21 +1,17 @@
 # Database settings
-dn: olcDatabase=hdb,cn=config
+dn: olcDatabase=mdb,cn=config
 objectClass: olcDatabaseConfig
-objectClass: olcHdbConfig
-olcDatabase: {1}hdb
+objectClass: olcMdbConfig
+olcDatabase: {1}mdb
 olcSuffix: dc=example,dc=com
-olcDbDirectory: /var/lib/ldap
+olcDbDirectory: /tmp/ldap/db
 olcRootDN: cn=admin,dc=example,dc=com
 olcRootPW: {SSHA}SgCZuAcGQA5HlgKi+g5xwVyI2NhXRFYh
-olcDbConfig: set_cachesize 0 2097152 0
-olcDbConfig: set_lk_max_objects 1500
-olcDbConfig: set_lk_max_locks 1500
-olcDbConfig: set_lk_max_lockers 1500
 olcDbIndex: objectClass eq
 olcLastMod: TRUE
 olcDbCheckpoint: 512 30
-olcAccess: to * by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
 olcAccess: to attrs=userPassword by dn="cn=admin,dc=example,dc=com" write by anonymous auth by self write by * none
+olcAccess: to * by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,cn=config" manage
 olcAccess: to attrs=shadowLastChange by self write by * read
 olcAccess: to dn.base="" by * read
 olcAccess: to * by dn="cn=admin,dc=example,dc=com" write by * read
diff --git a/src/tests/salt-test-server/salt/ldap/base2.ldif b/src/tests/salt-test-server/salt/ldap/base2.ldif
new file mode 100644
index 0000000000..4ae6b07c33
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ldap/base2.ldif
@@ -0,0 +1,81 @@
+# Database settings
+dn: olcDatabase=mdb,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcMdbConfig
+olcDatabase: {1}mdb
+olcSuffix: dc=example,dc=com
+olcDbDirectory: /tmp/ldap2/db
+olcRootDN: cn=admin,dc=example,dc=com
+olcRootPW: {SSHA}SgCZuAcGQA5HlgKi+g5xwVyI2NhXRFYh
+olcDbIndex: objectClass eq
+olcLastMod: TRUE
+olcDbCheckpoint: 512 30
+olcAccess: to attrs=userPassword by dn="cn=admin,dc=example,dc=com" write by anonymous auth by self write by * none
+olcAccess: to * by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,cn=config" manage
+olcAccess: to attrs=shadowLastChange by self write by * read
+olcAccess: to dn.base="" by * read
+olcAccess: to * by dn="cn=admin,dc=example,dc=com" write by * read
+
+# Create top-level object in domain
+dn: dc=example,dc=com
+objectClass: top
+objectClass: dcObject
+objectclass: organization
+o: Example Organization
+dc: Example
+description: LDAP Example Two
+
+dn: dc=subdept,dc=example,dc=com
+objectClass: organization
+objectClass: dcObject
+o: Sub org
+dc: subdept
+
+dn: ou=people,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: people
+
+dn: ou=groups,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: groups
+
+dn: ou=profiles,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: profiles
+
+dn: cn=radprofile,ou=profiles,dc=subdept,dc=example,dc=com
+objectClass: radiusObjectProfile
+objectClass: radiusprofile
+cn: radprofile
+radiusFramedIPNetmask: 255.255.255.0
+
+dn: uid=fred,ou=people,dc=subdept,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: shadowAccount
+objectClass: radiusprofile
+uid: fred
+sn: Jones
+givenName: Fred
+cn: Fred Jones
+displayName: Fred Jones
+userPassword: password
+uidNumber: 100
+gidNumber: 100
+homeDirectory: /home/fred
+radiusIdleTimeout: 3600
+radiusAttribute: reply.Session-Timeout := 7200
+radiusAttribute: control.NAS-IP-Address := 1.2.3.4
+radiusProfileDN: cn=radprofile,ou=profiles,ou=subdept,dc=example,dc=com
+
+dn: ou=offsite,dc=subdept,dc=example,dc=com
+objectClass: referral
+objectClass: extensibleObject
+ou: offsite
+ref: ldap://127.0.0.1:3890/dc=example,dc=com??sub
+
+dn: ou=bounce1,dc=subdept,dc=example,dc=com
+objectClass: referral
+objectClass: extensibleObject
+ou: bounce1
+ref: ldap://127.0.0.1:3890/ou=bounce2,dc=example,dc=com??sub
diff --git a/suse/freeradius.spec b/suse/freeradius.spec
index 4c5e0d8e5a..6ac216db2b 100644
--- a/suse/freeradius.spec
+++ b/suse/freeradius.spec
@@ -1,5 +1,5 @@
 Name:         freeradius-server
-Version:      3.0.25
+Version:      3.0.26
 Release:      0
 License:      GPLv2 ; LGPLv2.1
 Group:        Productivity/Networking/Radius/Servers
