Blob Blame History Raw
From: Antonio Torres <antorres@redhat.com>
Date: Tue, 11 Jan 2022
Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.26

Backport TLS and OpenSSL3 fixes from the future 3.0.26 FreeRADIUS release.

Additionally include checks to avoid segfault when trying to use MD4 algorithm
while having OpenSSL legacy provider disabled.

Related: rhbz#1978216
Signed-off-by: Antonio Torres <antorres@redhat.com>
---
 share/dictionary.freeradius.internal               |   50 +-
 src/include/build.h                                |   25 +-
 src/include/libradius.h                            |   23 +-
 src/include/listen.h                               |   24 +-
 src/include/md4.h                                  |   49 +-
 src/include/md5.h                                  |   29 +-
 src/include/openssl3.h                             |  109 ++
 src/include/tls-h                                  |   32 +-
 src/include/token.h                                |    7 +-
 src/lib/hmacmd5.c                                  |    4 +-
 src/lib/hmacsha1.c                                 |    5 +-
 src/lib/md4.c                                      |    1 +
 src/lib/md5.c                                      |    1 +
 src/lib/pair.c                                     |   97 +-
 src/lib/print.c                                    |   19 +-
 src/lib/radius.c                                   |  300 +++-
 src/lib/token.c                                    |   24 +-
 src/main/cb.c                                      |  118 +-
 src/main/map.c                                     |   54 +-
 src/main/tls.c                                     | 1819 ++++++++++++++++----
 src/main/tls_listen.c                              |  171 +-
 src/modules/proto_dhcp/rlm_dhcp.c                  |    2 +-
 src/modules/rlm_eap/libeap/eap_tls.c               |  178 +-
 src/modules/rlm_eap/libeap/eap_tls.h               |   10 +-
 src/modules/rlm_eap/libeap/mppe_keys.c             |  161 +-
 src/modules/rlm_eap/radeapclient.c                 |    8 +
 src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c  |   51 +-
 .../rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c      |   60 +-
 src/modules/rlm_eap/types/rlm_eap_peap/peap.c      |   22 +-
 .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c      |   41 +-
 src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h |  190 ++
 src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c    |  719 +++++---
 src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h    |   16 +-
 .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c        |  508 +++++-
 .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h        |    2 +
 .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c        |   52 +-
 .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h        |    5 +
 .../rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c      |   40 +-
 src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c      |   25 +-
 src/modules/rlm_exec/rlm_exec.c                    |    4 +-
 src/modules/rlm_expr/rlm_expr.c                    |  115 ++
 src/modules/rlm_files/rlm_files.c                  |    2 +-
 src/modules/rlm_ldap/ldap.h                        |    1 +
 src/modules/rlm_mschap/rlm_mschap.c                |   97 +-
 src/modules/rlm_otp/otp_mppe.c                     |   16 +-
 src/modules/rlm_otp/otp_pwe.c                      |    8 -
 src/modules/rlm_otp/otp_radstate.c                 |    9 +-
 src/modules/rlm_rest/rest.c                        |  107 +-
 src/modules/rlm_rest/rest.h                        |   18 +
 src/modules/rlm_rest/rlm_rest.c                    |   12 +
 src/modules/rlm_wimax/milenage.c                   |  642 +++++++
 src/modules/rlm_wimax/milenage.h                   |  128 ++
 src/modules/rlm_wimax/rlm_wimax.c                  |  429 ++++-
 src/tests/keywords/md4                             |   58 +
 54 files changed, 5583 insertions(+), 1114 deletions(-)

diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal
index 724e1f7ff6..53dd04ec9a 100644
--- a/share/dictionary.freeradius.internal
+++ b/share/dictionary.freeradius.internal
@@ -254,6 +254,7 @@ ATTRIBUTE	FreeRADIUS-Response-Delay-USec		1155	integer
 
 ATTRIBUTE	REST-HTTP-Header			1160	string
 ATTRIBUTE	REST-HTTP-Body				1161	string
+ATTRIBUTE	REST-HTTP-Status-Code			1162	integer
 
 ATTRIBUTE	Cache-Expires				1170	date
 ATTRIBUTE	Cache-Created				1171	date
@@ -277,7 +278,24 @@ ATTRIBUTE	SSHA2-256-Password			1178	octets
 ATTRIBUTE	SSHA2-384-Password			1179	octets
 ATTRIBUTE	SSHA2-512-Password			1180	octets
 
+ATTRIBUTE	PBKDF2-Password				1181	octets
+ATTRIBUTE	SSHA3-224-Password			1182	octets
+ATTRIBUTE	SSHA3-256-Password			1183	octets
+ATTRIBUTE	SSHA3-384-Password			1184	octets
+ATTRIBUTE	SSHA3-512-Password			1185	octets
+
 ATTRIBUTE	MS-CHAP-Peer-Challenge			1192	octets
+ATTRIBUTE	Home-Server-Name			1193	string
+ATTRIBUTE	Originating-Realm-Key			1194	string
+ATTRIBUTE	Proxy-To-Originating-Realm		1195	string
+
+ATTRIBUTE	TOTP-Secret				1194	string # base32 encoded
+ATTRIBUTE	TOTP-Key				1195	octets # raw key
+ATTRIBUTE	TOTP-Password				1196	string
+
+ATTRIBUTE	Proxy-Tunneled-Request-As-EAP		1197	integer
+VALUE	Proxy-Tunneled-Request-As-EAP	No			0
+VALUE	Proxy-Tunneled-Request-As-EAP	Yes			1
 
 #
 #	Range:	1200-1279
@@ -318,6 +336,10 @@ ATTRIBUTE	EAP-Sim-Algo-Version			1216	integer
 ATTRIBUTE	Outer-Realm-Name			1218	string
 ATTRIBUTE	Inner-Realm-Name			1219	string
 
+ATTRIBUTE	EAP-Pwd-Password-Hash			1220	octets
+ATTRIBUTE	EAP-Pwd-Password-Salt			1221	octets
+ATTRIBUTE	EAP-Pwd-Password-Prep			1222	byte
+
 #
 #	Range:	1280 - 1535
 #		EAP-type specific attributes
@@ -516,6 +538,11 @@ ATTRIBUTE	Tmp-Cast-IPv4Prefix			1870	ipv4prefix
 #	these attributes.
 #
 ATTRIBUTE	WiMAX-MN-NAI				1900	string
+ATTRIBUTE	WiMAX-SIM-Ki				1901	octets
+ATTRIBUTE	WiMAX-SIM-OPc				1902	octets
+ATTRIBUTE	WiMAX-SIM-AMF				1903	octets
+ATTRIBUTE	WiMAX-SIM-SQN				1904	octets
+ATTRIBUTE	WiMAX-SIM-RAND				1905	octets
 
 ATTRIBUTE	TLS-Cert-Serial				1910	string
 ATTRIBUTE	TLS-Cert-Expiration			1911	string
@@ -526,7 +553,7 @@ ATTRIBUTE	TLS-Cert-Subject-Alt-Name-Email		1915	string
 ATTRIBUTE	TLS-Cert-Subject-Alt-Name-Dns		1916	string
 ATTRIBUTE	TLS-Cert-Subject-Alt-Name-Upn		1917	string
 ATTRIBUTE	TLS-Cert-Valid-Since			1918	string
-# 1919: reserved for future cert attribute
+ATTRIBUTE	TLS-Session-Information			1919	string
 ATTRIBUTE	TLS-Client-Cert-Serial			1920	string
 ATTRIBUTE	TLS-Client-Cert-Expiration		1921	string
 ATTRIBUTE	TLS-Client-Cert-Issuer			1922	string
@@ -543,10 +570,19 @@ ATTRIBUTE	TLS-Client-Cert-Subject-Alt-Name-Upn	1932	string
 ATTRIBUTE	TLS-PSK-Identity			1933	string
 ATTRIBUTE	TLS-Client-Cert-X509v3-Extended-Key-Usage-OID 1936	string
 ATTRIBUTE	TLS-Client-Cert-Valid-Since		1937	string
+ATTRIBUTE	TLS-Cache-Method			1938	integer
+VALUE	TLS-Cache-Method		save			1
+VALUE	TLS-Cache-Method		load			2
+VALUE	TLS-Cache-Method		clear			3
+VALUE	TLS-Cache-Method		refresh			4
 
-# 1938 - 1939: reserved for future cert attributes
 
-# 1940 - 1949: reserved for TLS session caching, mostly in 4.0
+# 1939: reserved for future cert attributes
+
+# 1940 - 1959: reserved for TLS session caching, mostly in 4.0
+
+ATTRIBUTE	TLS-Session-ID				1940	octets
+ATTRIBUTE	TLS-Session-Data			1942	octets
 
 # Set by EAP-TLS code
 ATTRIBUTE	TLS-OCSP-Cert-Valid			1943	integer
@@ -560,8 +596,13 @@ ATTRIBUTE	TLS-Cache-Filename			1946	string
 ATTRIBUTE	TLS-Session-Version			1947	string
 ATTRIBUTE	TLS-Session-Cipher-Suite		1948	string
 
+ATTRIBUTE	TLS-Session-Cert-File			1949	string
+ATTRIBUTE	TLS-Session-Cert-Private-Key-File	1950	string
+
+ATTRIBUTE	TLS-Server-Name-Indication		1951	string
+
 #
-#	Range:	1950-2099
+#	Range:	1960-2099
 #		Free
 #
 #	Range:	2100-2199
@@ -650,6 +691,7 @@ VALUE	Session-Type			Local			1
 VALUE	Post-Auth-Type			Local			1
 VALUE	Post-Auth-Type			Reject			2
 VALUE	Post-Auth-Type			Challenge		3
+VALUE	Post-Auth-Type			Client-Lost		4
 
 #
 #	And Post-Proxy
diff --git a/src/include/build.h b/src/include/build.h
index 5da940c2b7..e1c2a1c79b 100644
--- a/src/include/build.h
+++ b/src/include/build.h
@@ -46,9 +46,13 @@ extern "C" {
  *	compiler.
  */
 #ifdef __GNUC__
-#  define CC_HINT(_x) __attribute__ ((_x))
+#  define CC_HINT(...)  __attribute__ ((__VA_ARGS__))
+#  define likely(_x)	__builtin_expect((_x), 1)
+#  define unlikely(_x)	__builtin_expect((_x), 0)
 #else
-#  define CC_HINT(_x)
+#  define CC_HINT(...)
+#  define likely(_x)	_x
+#  define unlikely(_x)	_x
 #endif
 
 #ifdef HAVE_ATTRIBUTE_BOUNDED
@@ -57,6 +61,18 @@ extern "C" {
 #  define CC_BOUNDED(...)
 #endif
 
+/*
+ *      GCC uses __SANITIZE_ADDRESS__, clang uses __has_feature, which
+ *      GCC complains about.
+ */
+#ifndef __SANITIZE_ADDRESS__
+#ifdef __has_feature
+#if __has_feature(address_sanitizer)
+#define __SANITIZE_ADDRESS__ (1)
+#endif
+#endif
+#endif
+
 /*
  *	Macros to add pragmas
  */
@@ -137,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/libradius.h b/src/include/libradius.h
index ce2f713de1..757828f070 100644
--- a/src/include/libradius.h
+++ b/src/include/libradius.h
@@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$")
  *  Talloc memory allocation is used in preference to malloc throughout
  *  the libraries and server.
  */
+#ifdef HAVE_WDOCUMENTATION
+DIAG_OFF(documentation)
+#endif
 #include <talloc.h>
+#ifdef HAVE_WDOCUMENTATION
+DIAG_ON(documentation)
+#endif
 
 /*
  *  Defines signatures for any missing functions.
@@ -162,11 +168,6 @@ typedef void (*sig_t)(int);
 
 #define PAD(_x, _y)		(_y - ((_x) % _y))
 
-#define PRINTF_LIKE(n)		CC_HINT(format(printf, n, n+1))
-#define NEVER_RETURNS		CC_HINT(noreturn)
-#define UNUSED			CC_HINT(unused)
-#define BLANK_FORMAT		" "	/* GCC_LINT whines about empty formats */
-
 typedef struct attr_flags {
 	unsigned int 	is_unknown : 1;				//!< Attribute number or vendor is unknown.
 	unsigned int	is_tlv : 1;				//!< Is a sub attribute.
@@ -189,6 +190,10 @@ typedef struct attr_flags {
 
 	unsigned int	compare : 1;				//!< has a paircompare registered
 
+	unsigned int	is_dup : 1;				//!< is a duplicate of another attribute
+
+	unsigned int	secret : 1;				//!< is a secret thingy
+
 	uint8_t		encrypt;      				//!< Ecryption method.
 	uint8_t		length;
 } ATTR_FLAGS;
@@ -443,7 +448,7 @@ size_t   	vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q
 
 char     	*vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote);
 
-size_t    	vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp);
+size_t    	vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value);
 size_t		vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp);
 void		vp_print(FILE *, VALUE_PAIR const *);
 void		vp_printlist(FILE *, VALUE_PAIR const *);
@@ -472,6 +477,8 @@ int		dict_addvalue(char const *namestr, char const *attrstr, int value);
 int		dict_init(char const *dir, char const *fn);
 void		dict_free(void);
 int		dict_read(char const *dir, char const *filename);
+size_t		dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da);
+int		dict_walk(fr_hash_table_walk_t callback, void *context);
 
 void 		dict_attr_free(DICT_ATTR const **da);
 int		dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor);
@@ -586,6 +593,7 @@ int		rad_vp2attr(RADIUS_PACKET const *packet,
 			    VALUE_PAIR const **pvp, uint8_t *ptr, size_t room);
 
 /* pair.c */
+VALUE_PAIR	*fr_pair_alloc(TALLOC_CTX *ctx);
 VALUE_PAIR	*fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da);
 VALUE_PAIR	*fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor);
 int		fr_pair_to_unknown(VALUE_PAIR *vp);
@@ -611,6 +619,7 @@ VALUE_PAIR	*fr_cursor_remove(vp_cursor_t *cursor);
 VALUE_PAIR	*fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new);
 void		fr_pair_delete_by_num(VALUE_PAIR **, unsigned int attr, unsigned int vendor, int8_t tag);
 void		fr_pair_add(VALUE_PAIR **, VALUE_PAIR *);
+void		fr_pair_prepend(VALUE_PAIR **, VALUE_PAIR *);
 void		fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *add);
 int		fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b);
 int		fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b);
@@ -632,7 +641,7 @@ void		fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src);
 void		fr_pair_value_strcpy(VALUE_PAIR *vp, char const * src);
 void		fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const * src, size_t len);
 void		fr_pair_value_sprintf(VALUE_PAIR *vp, char const * fmt, ...) CC_HINT(format (printf, 2, 3));
-void		fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from);
+void		fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op);
 void		fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from,
 					 unsigned int attr, unsigned int vendor, int8_t tag);
 void		fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from,
diff --git a/src/include/listen.h b/src/include/listen.h
index 4f50bbf808..b395aeb046 100644
--- a/src/include/listen.h
+++ b/src/include/listen.h
@@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE {
 typedef enum RAD_LISTEN_STATUS {
 	RAD_LISTEN_STATUS_INIT = 0,
 	RAD_LISTEN_STATUS_KNOWN,
+	RAD_LISTEN_STATUS_PAUSE,
+	RAD_LISTEN_STATUS_RESUME,
 	RAD_LISTEN_STATUS_FROZEN,
 	RAD_LISTEN_STATUS_EOL,
 	RAD_LISTEN_STATUS_REMOVE_NOW
@@ -70,9 +72,10 @@ struct rad_listen {
 	int		status;
 #ifdef WITH_TCP
 	int		count;
-	bool		dual;
 	rbtree_t	*children;
 	rad_listen_t	*parent;
+
+	bool		dual;
 #endif
 	bool		nodup;
 	bool		synchronous;
@@ -80,12 +83,25 @@ struct rad_listen {
 
 #ifdef WITH_TLS
 	fr_tls_server_conf_t *tls;
+	bool		check_client_connections;
 #endif
 
 	rad_listen_recv_t recv;
 	rad_listen_send_t send;
+
+	/*
+	 *	We don't need a proxy_recv, because the main loop in
+	 *	process.c calls listener->recv(), and we don't know
+	 *	what kind of packet we're receiving until we receive
+	 *	it.
+	 */
+	rad_listen_send_t proxy_send;
+
+
 	rad_listen_encode_t encode;
 	rad_listen_decode_t decode;
+	rad_listen_encode_t proxy_encode;
+	rad_listen_decode_t proxy_decode;
 	rad_listen_print_t print;
 
 	CONF_SECTION const *cs;
@@ -146,6 +162,12 @@ typedef struct listen_socket_t {
 	pthread_mutex_t mutex;
 	uint8_t		*data;
 	size_t		partial;
+	enum {
+		LISTEN_TLS_INIT = 0,
+		LISTEN_TLS_CHECKING,
+		LISTEN_TLS_SETUP,
+		LISTEN_TLS_RUNNING,
+	} state;
 #endif
 
 	RADCLIENT_LIST	*clients;
diff --git a/src/include/md4.h b/src/include/md4.h
index b7bdd6a15e..f3801728c8 100644
--- a/src/include/md4.h
+++ b/src/include/md4.h
@@ -71,14 +71,61 @@ 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);
+	if (EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL) != 1) {
+		fprintf(stderr, "Couldn't init MD4 algorithm. Enable OpenSSL legacy provider.\n");
+		exit(EXIT_FAILURE);
+	}
+}
+
+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/tls-h b/src/include/tls-h
index 62f57c4715..67835881a7 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;
@@ -139,6 +139,9 @@ typedef struct _tls_session_t {
 	bool		invalid_hb_used;		//!< Whether heartbleed attack was detected.
 	bool		connected;			//!< whether the outgoing socket is connected
 	bool		is_init_finished;		//!< whether or not init is finished
+	bool		client_cert_ok;			//!< whether or not we validated the client certificate
+	bool		authentication_success;		//!< whether or not the user was authenticated (cert or PW)
+	bool		quick_session_tickets;		//!< for EAP-TLS.
 
 	/*
 	 *	Framed-MTU attribute in RADIUS, if present, can also be used to set this
@@ -160,8 +163,11 @@ typedef struct _tls_session_t {
 	void 		*opaque;
 	void 		(*free_opaque)(void *opaque);
 
-	char const	*prf_label;
+	char const	*label;
 	bool		allow_session_resumption;	//!< Whether session resumption is allowed.
+	bool		session_not_resumed;		//!< Whether our session was not resumed.
+
+	fr_tls_server_conf_t const *conf;		//! for better complaints
 } tls_session_t;
 
 /*
@@ -309,16 +315,17 @@ int		tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char co
 				 CC_HINT(format (printf, 4, 5));
 
 void		tls_global_cleanup(void);
-tls_session_t	*tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert);
+tls_session_t	*tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, bool allow_tls13);
 tls_session_t	*tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs);
 fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs);
 fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs);
 fr_tls_server_conf_t *tls_server_conf_alloc(TALLOC_CTX *ctx);
-SSL_CTX		*tls_init_ctx(fr_tls_server_conf_t *conf, int client);
+SSL_CTX		*tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file);
 int 		tls_handshake_recv(REQUEST *, tls_session_t *ssn);
 int 		tls_handshake_send(REQUEST *, tls_session_t *ssn);
 void 		tls_session_information(tls_session_t *ssn);
 void		tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize);
+X509_STORE	*fr_init_x509_store(fr_tls_server_conf_t *conf);
 
 /*
  *	Low-level TLS stuff
@@ -360,6 +367,11 @@ 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;
+	int		max_version;
 
 	char const	*tls_min_version;
 	char const	*tls_max_version;
@@ -371,16 +383,20 @@ struct fr_tls_server_conf_t {
 	bool		check_crl;
 	bool		check_all_crl;
 	bool		allow_expired_crl;
+	uint32_t	ca_path_reload_interval;
+	uint32_t	ca_path_last_reload;
+	X509_STORE	*old_x509_store;
 	char const	*check_cert_cn;
 	char const	*cipher_list;
 	bool		cipher_server_preference;
 	char const	*check_cert_issuer;
 
 	bool     	session_cache_enable;
-	uint32_t     	session_timeout;
+	uint32_t     	session_lifetime;
 	uint32_t     	session_cache_size;
 	char const	*session_id_name;
 	char const	*session_cache_path;
+	char const	*session_cache_server;
 	fr_hash_table_t *cache_ht;
 	char		session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH];
 
@@ -389,6 +405,8 @@ struct fr_tls_server_conf_t {
 	char const	*verify_client_cert_cmd;
 	bool		require_client_cert;
 
+	pthread_mutex_t	mutex;
+
 #ifdef HAVE_OPENSSL_OCSP_H
 	/*
 	 * OCSP Configuration
@@ -414,6 +432,10 @@ struct fr_tls_server_conf_t {
 	char const	*psk_query;
 #endif
 
+	char const	*realm_dir;
+	fr_hash_table_t	*realms;
+
+	char const	*client_hostname;
 };
 
 #ifdef __cplusplus
diff --git a/src/include/token.h b/src/include/token.h
index 6cbd05217a..c8bb748702 100644
--- a/src/include/token.h
+++ b/src/include/token.h
@@ -56,16 +56,17 @@ typedef enum fr_token_t {
 	T_OP_CMP_TRUE,			/* =* 		20 */
 	T_OP_CMP_FALSE,			/* !* */
 	T_OP_CMP_EQ,			/* == */
+	T_OP_PREPEND,			/* ^= */
 	T_HASH,				/* # */
-	T_BARE_WORD,			/* bare word */
-	T_DOUBLE_QUOTED_STRING,		/* "foo" 	25 */
+	T_BARE_WORD,			/* bare word    25 */
+	T_DOUBLE_QUOTED_STRING,		/* "foo" */
 	T_SINGLE_QUOTED_STRING,		/* 'foo' */
 	T_BACK_QUOTED_STRING,		/* `foo` */
 	T_TOKEN_LAST
 } FR_TOKEN;
 
 #define T_EQSTART	T_OP_ADD
-#define	T_EQEND		(T_OP_CMP_EQ + 1)
+#define	T_EQEND		(T_OP_PREPEND + 1)
 
 typedef struct FR_NAME_NUMBER {
 	char const	*name;
diff --git a/src/lib/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/pair.c b/src/lib/pair.c
index d711f90c5d..146c82f95b 100644
--- a/src/lib/pair.c
+++ b/src/lib/pair.c
@@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) {
 	return 0;
 }
 
-static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx)
+VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx)
 {
 	VALUE_PAIR *vp;
 
@@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v
 	DICT_ATTR const *da;
 
 	da = dict_attrbyvalue(attr, vendor);
-	if (!da) {
-		VALUE_PAIR *vp;
-
-		vp = fr_pair_alloc(ctx);
-		if (!vp) return NULL;
-
-		/*
-		 *	Ensure that the DA is parented by the VP.
-		 */
-		da = dict_unknown_afrom_fields(vp, attr, vendor);
-		if (!da) {
-			talloc_free(vp);
-			return NULL;
-		}
-
-		vp->da = da;
-		return vp;
-	}
+	if (!da) return NULL;
 
 	return fr_pair_afrom_da(ctx, da);
 }
@@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add)
 	i->next = add;
 }
 
+/** Add a VP to the start of the list.
+ *
+ * Links the new VP to 'first', then points 'first' at the new VP.
+ *
+ * @param[in] first VP in linked list. Will add new VP to the start of this list.
+ * @param[in] add VP to add to list.
+ */
+void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add)
+{
+	VALUE_PAIR *i;
+
+	if (!add) return;
+
+	VERIFY_VP(add);
+
+	if (*first == NULL) {
+		*first = add;
+		return;
+	}
+
+	/*
+	 *	Find the end of the list to be prepended
+	 */
+	for (i = add; i->next; i = i->next) {
+#ifdef WITH_VERIFY_PTR
+		VERIFY_VP(i);
+		/*
+		 *	The same VP should never by added multiple times
+		 *	to the same list.
+		 */
+		fr_assert(*first != i);
+#endif
+	}
+	i->next = *first;
+	*first = add;
+}
+
 /** Replace all matching VPs
  *
  * Walks over 'first', and replaces the first VP that matches 'replace'.
@@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp)
  * @param[in] ctx for talloc
  * @param[in,out] to destination list.
  * @param[in,out] from source list.
+ * @param[in] op operator for move.
  *
  * @see radius_pairmove
  */
-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
+void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op)
 {
 	VALUE_PAIR *i, *found;
 	VALUE_PAIR *head_new, **tail_new;
+	VALUE_PAIR *head_prepend;
 	VALUE_PAIR **tail_from;
 
 	if (!to || !from || !*from) return;
@@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
 	head_new = NULL;
 	tail_new = &head_new;
 
+	/*
+	 *	Any attributes that are requested to be prepended
+	 *	are added to a temporary list here
+	 */
+	head_prepend = NULL;
+
 	/*
 	 *	We're looping over the "from" list, moving some
 	 *	attributes out, but leaving others in place.
@@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from)
 			fr_pair_steal(ctx, i);
 			tail_new = &(i->next);
 			continue;
+		case T_OP_PREPEND:
+			i->next = head_prepend;
+			head_prepend = i;
+			fr_pair_steal(ctx, i);
+			continue;
 		}
 	} /* loop over the "from" list. */
 
 	/*
-	 *	Take the "new" list, and append it to the "to" list.
+	 * 	If the op parameter was prepend, add the "new" list
+	 *	attributes first as those whose individual operator
+	 *	is prepend should be prepended to the resulting list
 	 */
-	fr_pair_add(to, head_new);
+	if (op == T_OP_PREPEND) {
+		fr_pair_prepend(to, head_new);
+	}
+
+	/*
+	 *	If there are any items in the prepend list prepend
+	 *	it to the "to" list
+	 */
+	fr_pair_prepend(to, head_prepend);
+
+	/*
+	 *	If the op parameter was not prepend, take the "new"
+	 *	list, and append it to the "to" list.
+	 */
+	if (op != T_OP_PREPEND) {
+		fr_pair_add(to, head_new);
+	}
 }
 
 /** Move matching pairs between VALUE_PAIR lists
@@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw)
 		break;
 
 	default:
-		fr_strerror_printf("Failed to find expected value on right hand side");
+		fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand);
 		return T_INVALID;
 	}
 
diff --git a/src/lib/print.c b/src/lib/print.c
index 9ac927358b..57455b6f30 100644
--- a/src/lib/print.c
+++ b/src/lib/print.c
@@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type)
  * @param out Where to write the string.
  * @param outlen Length of output buffer.
  * @param vp to print.
+ * @param raw_value if true, the raw value is printed and not the enumerated attribute value
  * @return the length of data written to out, or a value >= outlen on truncation.
  */
-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp)
+size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value)
 {
 	char const	*q;
 	size_t		len, freespace = outlen;
+	/* attempt to print raw_value when has_value is false, or raw_value is false, but only
+	   if has_tag is also false */
+	bool		raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag;
 
-	if (!vp->da->flags.has_tag) {
+	if (raw) {
 		switch (vp->da->type) {
 		case PW_TYPE_INTEGER:
-			if (vp->da->flags.has_value) break;
-
 			return snprintf(out, freespace, "%u", vp->vp_integer);
 
 		case PW_TYPE_SHORT:
-			if (vp->da->flags.has_value) break;
-
 			return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short);
 
 		case PW_TYPE_BYTE:
-			if (vp->da->flags.has_value) break;
-
 			return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte);
 
-		case PW_TYPE_SIGNED:
-			return snprintf(out, freespace, "%d", vp->vp_signed);
-
 		default:
 			break;
 		}
@@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote)
 
 	value = vp_aprints_value(ctx, vp, quote);
 
-	if (vp->da->flags.has_tag) {
+	if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) {
 		if (quote && (vp->da->type == PW_TYPE_STRING)) {
 			str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote);
 		} else {
diff --git a/src/lib/radius.c b/src/lib/radius.c
index 3881111f7d..524e68088e 100644
--- a/src/lib/radius.c
+++ b/src/lib/radius.c
@@ -28,6 +28,7 @@ RCSID("$Id$")
 #include	<freeradius-devel/libradius.h>
 
 #include	<freeradius-devel/md5.h>
+#include	<freeradius-devel/rfc4849.h>
 
 #include	<fcntl.h>
 #include	<ctype.h>
@@ -528,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)
@@ -562,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.
@@ -572,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);
@@ -585,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);
 }
 
 
@@ -653,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);
@@ -663,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);
@@ -681,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)
@@ -1552,6 +1562,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet,
 
 	VERIFY_VP(vp);
 
+	if (room < 2) return -1;
+
 	if (vp->da->vendor != 0) {
 		fr_strerror_printf("rad_vp2rfc called with VSA");
 		return -1;
@@ -1594,6 +1606,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet,
 		return 18;
 	}
 
+	/*
+	 *	Hacks for NAS-Filter-Rule.  They all get concatenated
+	 *	with 0x00 bytes in between the values.  We rely on the
+	 *	decoder to do the opposite transformation on incoming
+	 *	packets.
+	 */
+	if (vp->da->attr == PW_NAS_FILTER_RULE) {
+		uint8_t const *end = ptr + room;
+		uint8_t *p, *attr = ptr;
+		bool zero = false;
+
+		attr[0] = PW_NAS_FILTER_RULE;
+		attr[1] = 2;
+		p = ptr + 2;
+
+		while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) {
+			if ((p + zero + vp->vp_length) > end) {				
+				break;
+			}
+
+			if (zero) {
+				if (attr[1] == 255) {
+					attr = p;
+					if ((attr + 3) >= end) break;
+
+					attr[0] = PW_NAS_FILTER_RULE;
+					attr[1] = 2;
+					p = attr + 2;
+				}
+
+				*(p++) = 0;
+				attr[1]++;
+			}
+
+			/*
+			 *	Check for overflow
+			 */
+			if ((attr[1] + vp->vp_length) < 255) {
+				memcpy(p, vp->vp_strvalue, vp->vp_length);
+				attr[1] += vp->vp_length;
+				p += vp->vp_length;
+
+			} else if (attr + (attr[1] + 2 + vp->vp_length) > end) {
+				break;
+
+			} else if (vp->vp_length > 253) {
+				/*
+				 *	Drop VPs which are too long.
+				 *	We don't (yet) split one VP
+				 *	across multiple attributes.
+				 */
+				vp = vp->next;
+				continue;
+
+			} else {
+				size_t first, second;
+
+				first = 255 - attr[1];
+				second = vp->vp_length - first;
+
+				memcpy(p, vp->vp_strvalue, first);
+				p += first;
+				attr[1] = 255;
+				attr = p;
+
+				attr[0] = PW_NAS_FILTER_RULE;
+				attr[1] = 2;
+				p = attr + 2;
+
+				memcpy(p, vp->vp_strvalue + first, second);
+				attr[1] += second;
+				p += second;
+			}
+
+			vp = vp->next;
+			zero = true;
+		}
+
+		*pvp = vp;
+		return p - ptr;
+	}
+
 	/*
 	 *	EAP-Message is special.
 	 */
@@ -2036,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);
@@ -2159,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.
@@ -2198,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.
@@ -2896,6 +2993,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre
 }
 
 
+/** Convert one or more NAS-Filter-Rule attributes to one or more
+ * attributes.
+ *
+ */
+static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx,
+				       DICT_ATTR const *da, uint8_t const *start,
+				       size_t const packetlen, VALUE_PAIR **pvp)
+{
+	uint8_t const *p = start;
+	uint8_t const *attr = start;
+	uint8_t const *end = start + packetlen;
+	uint8_t const *attr_end;
+	uint8_t *q;
+	VALUE_PAIR *vp;
+	uint8_t buffer[253];
+
+	q = buffer;
+
+	/*
+	 *	The packet has already been sanity checked, so we
+	 *	don't care about walking off of the end of it.
+	 */
+	while (attr < end) {
+		if ((attr + 2) > end) {
+			fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok");
+			return -1;
+		}
+
+		if (attr[1] < 2) {
+			fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok");
+			return -1;
+		}
+		if (attr[0] != PW_NAS_FILTER_RULE) break;
+
+		/*
+		 *	Now decode one, or part of one rule.
+		 */
+		p = attr + 2;
+		attr_end = attr + attr[1];
+
+		if (attr_end > end) {
+			fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok");
+			return -1;
+		}
+
+		/*
+		 *	Coalesce data until the zero byte.
+		 */
+		while (p < attr_end) {
+			/*
+			 *	Once we hit the zero byte, create the
+			 *	VP, skip the zero byte, and reset the
+			 *	counters.
+			 */
+			if (*p == 0) {
+				/*
+				 *	Discard consecutive zeroes.
+				 */
+				if (q > buffer) {
+					vp = fr_pair_afrom_da(ctx, da);
+					if (!vp) {
+						fr_strerror_printf("decode NAS-Filter-Rule: Out of memory");
+						return -1;
+					}
+
+					fr_pair_value_bstrncpy(vp, buffer, q - buffer);
+
+					*pvp = vp;
+					pvp = &(vp->next);
+					q = buffer;
+				}
+
+				p++;
+				continue;
+			}
+			*(q++) = *(p++);
+
+			/*
+			 *	Not much reason to have rules which
+			 *	are too long.
+			 */
+			if ((size_t) (q - buffer) > sizeof(buffer)) {
+				fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long");
+				return -1;
+			}
+		}
+
+		/*
+		 *	Done this attribute.  There MAY be things left
+		 *	in the buffer.
+		 */
+		attr = attr_end;
+	}
+
+	if (q == buffer) return attr + attr[2] - start;
+
+	vp = fr_pair_afrom_da(ctx, da);
+	if (!vp) {
+		fr_strerror_printf("decode NAS-Filter-Rule: Out of memory");
+		return -1;
+	}
+				
+	fr_pair_value_bstrncpy(vp, buffer, q - buffer);
+
+	*pvp = vp;
+
+	return p - start;
+}
+
 /** Convert a "concatenated" attribute to one long VP
  *
  */
@@ -3503,7 +3709,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet,
 	/*
 	 *	WiMAX craziness
 	 */
-	if ((vendor == VENDORPEC_WIMAX) && dv->flags) {
+	if (dv->flags) {
 		rcode = data2vp_wimax(ctx, packet, original, secret, vendor,
 				      data, attrlen, packetlen, pvp);
 		return rcode;
@@ -3610,19 +3816,12 @@ ssize_t data2vp(TALLOC_CTX *ctx,
 			return 0;
 		}
 
-#if !defined(NDEBUG) || defined(__clang_analyzer__)
 		/*
-		 *	Hacks for Coverity.  Editing the dictionary
-		 *	will break assumptions about CUI.  We know
-		 *	this, but Coverity doesn't.
+		 *	Create a zero-length attribute.
 		 */
-		if (da->type != PW_TYPE_OCTETS) return -1;
-#endif
-
-		data = buffer;
-		*buffer = '\0';
-		datalen = 0;
-		goto alloc_cui;	/* skip everything */
+		vp = fr_pair_afrom_da(ctx, da);
+		if (!vp) return -1;
+		goto done;
 	}
 
 	/*
@@ -3926,44 +4125,44 @@ ssize_t data2vp(TALLOC_CTX *ctx,
 
 	default:
 	raw:
+		/*
+		 *	If it's already unknown, don't create a new
+		 *	unknown one.
+		 */
+		if (da->flags.is_unknown) break;
+
 		/*
 		 *	Re-write the attribute to be "raw".  It is
 		 *	therefore of type "octets", and will be
 		 *	handled below.
+		 *
+		 *      We allocate the VP *first*, and then the da
+		 *      from it, so that there are no memory leaks.
 		 */
-		da = dict_unknown_afrom_fields(ctx, da->attr, da->vendor);
+		vp = fr_pair_alloc(ctx);
+		if (!vp) return -1;
+
+		da = dict_unknown_afrom_fields(vp, da->attr, da->vendor);
 		if (!da) {
 			fr_strerror_printf("Internal sanity check %d", __LINE__);
 			return -1;
 		}
 		tag = TAG_NONE;
-#ifndef NDEBUG
-		/*
-		 *	Fix for Coverity.
-		 */
-		if (da->type != PW_TYPE_OCTETS) {
-			dict_attr_free(&da);
-			return -1;
-		}
-#endif
-		break;
+		vp->da = da;
+		goto alloc_raw;
 	}
 
 	/*
 	 *	And now that we've verified the basic type
 	 *	information, decode the actual data.
 	 */
- alloc_cui:
 	vp = fr_pair_afrom_da(ctx, da);
 	if (!vp) return -1;
 
+alloc_raw:
 	vp->vp_length = datalen;
 	vp->tag = tag;
 
-#ifdef __clang_analyzer__
-	if (!datalen && da->type != PW_TYPE_OCTETS) return -1;
-#endif
-
 	switch (da->type) {
 	case PW_TYPE_STRING:
 		p = talloc_array(vp, char, vp->vp_length + 1);
@@ -4072,6 +4271,8 @@ ssize_t data2vp(TALLOC_CTX *ctx,
 		fr_strerror_printf("Internal sanity check %d", __LINE__);
 		return -1;
 	}
+
+done:
 	vp->type = VT_DATA;
 	*pvp = vp;
 
@@ -4112,6 +4313,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx,
 		return data2vp_concat(ctx, da, data, length, pvp);
 	}
 
+	if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) {
+		VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n");
+		return data2vp_nas_filter_rule(ctx, da, data, length, pvp);
+	}
+
 	/*
 	 *	Note that we pass the entire length, not just the
 	 *	length of this attribute.  The Extended or WiMAX
@@ -4261,7 +4467,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original,
 	uint32_t		num_attributes;
 	uint8_t			*ptr;
 	radius_packet_t		*hdr;
-	VALUE_PAIR *head, **tail, *vp;
+	VALUE_PAIR *head, **tail, *vp = NULL;
 
 	/*
 	 *	Extract attribute-value pairs
@@ -4385,8 +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
@@ -4397,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);
@@ -4409,6 +4616,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret,
 		}
 	}
 
+	fr_md5_destroy(&old);
+	fr_md5_destroy(&context);
+
 	return 0;
 }
 
@@ -4441,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.
@@ -4452,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);
@@ -4460,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);
@@ -4473,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);
 }
@@ -4609,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:
@@ -4637,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
@@ -4657,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);
 		}
 
@@ -4669,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/lib/token.c b/src/lib/token.c
index 8ae41b392f..a7978622e6 100644
--- a/src/lib/token.c
+++ b/src/lib/token.c
@@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = {
 	{ "=*", T_OP_CMP_TRUE,  },
 	{ "!*", T_OP_CMP_FALSE, },
 	{ "==",	T_OP_CMP_EQ,	},
+	{ "^=", T_OP_PREPEND,	},
 	{ "=",	T_OP_EQ,	},
 	{ "!=",	T_OP_NE,	},
 	{ ">=",	T_OP_GE,	},
@@ -78,9 +79,10 @@ const bool fr_assignment_op[] = {
 	false,		/* =* 		20 */
 	false,		/* !* */
 	false,		/* == */
-	false,				/* # */
-	false,		/* bare word */
-	false,		/* "foo" 	25 */
+	true,		/* ^= */
+	false,		/* # */
+	false,		/* bare word 	25 */
+	false,		/* "foo" */
 	false,		/* 'foo' */
 	false,		/* `foo` */
 	false
@@ -108,12 +110,13 @@ const bool fr_equality_op[] = {
 	true,		/* < */
 	true,		/* =~ */
 	true,		/* !~ */
-	true,		/* =* 		20 */
+	true,		/* =*		20 */
 	true,		/* !* */
 	true,		/* == */
-	false,				/* # */
-	false,		/* bare word */
-	false,		/* "foo" 	25 */
+	false,		/* ^= */
+	false,		/* # */
+	false,		/* bare word 	25 */
+	false,		/* "foo" */
 	false,		/* 'foo' */
 	false,		/* `foo` */
 	false
@@ -144,9 +147,10 @@ const bool fr_str_tok[] = {
 	false,		/* =* 		20 */
 	false,		/* !* */
 	false,		/* == */
-	false,				/* # */
-	true,		/* bare word */
-	true,		/* "foo" 	25 */
+	false,		/* ^= */
+	false,		/* # */
+	true,		/* bare word 	25 */
+	true,		/* "foo" */
 	true,		/* 'foo' */
 	true,		/* `foo` */
 	false
diff --git a/src/main/cb.c b/src/main/cb.c
index 4ae14e575b..0796914b41 100644
--- a/src/main/cb.c
+++ b/src/main/cb.c
@@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 #ifdef WITH_TLS
 void cbtls_info(SSL const *s, int where, int ret)
 {
-	char const *str, *state;
+	char const *role, *state;
 	REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST);
 
 	if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) {
-		str="TLS_connect";
+		role = "Client ";
 	} else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) {
-		str="TLS_accept";
+		role = "Server ";
 	} else {
-		str="(other)";
+		role = "";
 	}
 
 	state = SSL_state_string_long(s);
 	state = state ? state : "<none>";
 
 	if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) {
-		RDEBUG2("%s: %s", str, state);
+		if (RDEBUG_ENABLED3) {
+			char const *abbrv = SSL_state_string(s);
+			size_t len;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+			STACK_OF(SSL_CIPHER) *client_ciphers;
+			STACK_OF(SSL_CIPHER) *server_ciphers;
+#endif
+
+			/*
+			 *	Trim crappy OpenSSL state strings...
+			 */
+			len = strlen(abbrv);
+			if ((len > 1) && (abbrv[len - 1] == ' ')) len--;
+
+			RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)",
+				(int)len, abbrv, role, state, SSL_get_state(s));
+
+			/*
+			 *	After a ClientHello, list all the proposed ciphers from the client
+			 */
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+			if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) {
+				int i;
+				int num_ciphers;
+				const SSL_CIPHER *this_cipher;
+
+				server_ciphers = SSL_get_ciphers(s);
+				if (server_ciphers) {
+					RDEBUG3("Server preferred ciphers (by priority)");
+					num_ciphers = sk_SSL_CIPHER_num(server_ciphers);
+					for (i = 0; i < num_ciphers; i++) {
+						this_cipher = sk_SSL_CIPHER_value(server_ciphers, i);
+						RDEBUG3("(TLS)    [%i] %s", i, SSL_CIPHER_get_name(this_cipher));
+					}
+				}
+	
+				client_ciphers = SSL_get_client_ciphers(s);
+				if (client_ciphers) {
+					RDEBUG3("Client preferred ciphers (by priority)");
+					num_ciphers = sk_SSL_CIPHER_num(client_ciphers);
+					for (i = 0; i < num_ciphers; i++) {
+						this_cipher = sk_SSL_CIPHER_value(client_ciphers, i);
+						RDEBUG3("(TLS)    [%i] %s", i, SSL_CIPHER_get_name(this_cipher));
+					}
+				}
+			}
+#endif
+		} else {
+			RDEBUG2("(TLS) Handshake state - %s%s", role, state);
+		}
 		return;
 	}
 
 	if (where & SSL_CB_ALERT) {
 		if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return;
 
-		RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write",
+		RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write",
 		       SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
 		return;
 	}
 
 	if (where & SSL_CB_EXIT) {
 		if (ret == 0) {
-			RERROR("%s: Failed in %s", str, state);
+			RERROR("(TLS) %s: Failed in %s", role, state);
 			return;
 		}
 
 		if (ret < 0) {
 			if (SSL_want_read(s)) {
-				RDEBUG2("%s: Need to read more data: %s", str, state);
+				RDEBUG2("(TLS) %s: Need to read more data: %s", role, state);
 				return;
 			}
-			ERROR("tls: %s: Error in %s", str, state);
+			RERROR("(TLS) %s: Error in %s", role, state);
 		}
 	}
 }
@@ -83,18 +132,18 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
 	tls_session_t *state = (tls_session_t *)arg;
 
 	/*
-	 *	OpenSSL 1.0.2 calls this function with 'pseudo'
-	 *	content types.  Which breaks our tracking of
-	 *	the SSL Session state.
+	 *	OpenSSL calls this function with 'pseudo' content
+	 *	types.  Which breaks our tracking of the SSL Session
+	 *	state.
 	 */
-	if ((msg_version == 0) && (content_type > UINT8_MAX)) {
-		DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i",
+	if ((msg_version == 0) || (content_type >= UINT8_MAX)) {
+		DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i",
 		       content_type, msg_version);
 		return;
 	}
 
 	if ((write_p != 0) && (write_p != 1)) {
-		DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p);
+		DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p);
 		return;
 	}
 
@@ -104,6 +153,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
 	 */
 	if (!state) return;
 
+	if (rad_debug_lvl > 3) {
+		size_t i, j, data_len = len;
+		char buffer[3*16 + 1];
+		uint8_t const *in = inbuf;
+
+		DEBUG("(TLS) Received %zu bytes of TLS data", len);
+		if (data_len > 256) data_len = 256;
+
+		for (i = 0; i < data_len; i += 16) {
+			for (j = 0; j < 16; j++) {
+				if ((i + j) >= data_len) break;
+
+				sprintf(buffer + 3 * j, "%02x ", in[i + j]);
+			}
+
+			DEBUG("(TLS)        %s", buffer);
+		}
+	}
+
 	/*
 	 *	0 - received (from peer)
 	 *	1 - sending (to peer)
@@ -124,6 +192,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
 		state->info.alert_level = 0x00;
 		state->info.alert_description = 0x00;
 
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+	} else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) {
+		/* let tls_ack_handler set application_data */
+		state->info.content_type = SSL3_RT_HANDSHAKE;
+#endif
+
 #ifdef SSL3_RT_HEARTBEAT
 	} else if (content_type == TLS1_RT_HEARTBEAT) {
 		uint8_t *p = buf;
@@ -141,16 +215,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type,
 		}
 #endif
 	}
+
 	tls_session_information(state);
 }
 
 int cbtls_password(char *buf,
-		   int num UNUSED,
+		   int num,
 		   int rwflag UNUSED,
 		   void *userdata)
 {
-	strcpy(buf, (char *)userdata);
-	return(strlen((char *)userdata));
+	size_t len;
+
+	len = strlcpy(buf, (char *)userdata, num);
+	if (len >= (size_t) num) {
+		ERROR("Password too long.  Maximum length is %i bytes", num - 1);
+		return 0;
+	}
+
+	return len;
 }
 
 #endif
diff --git a/src/main/map.c b/src/main/map.c
index 6275ba124d..17988d27f9 100644
--- a/src/main/map.c
+++ b/src/main/map.c
@@ -245,6 +245,18 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
 		}
 		break;
 
+	case T_BARE_WORD:
+		/*
+		 *	Foo = %{...}
+		 *
+		 *	Not allowed!
+		 */
+		if ((attr[0] == '%') && (attr[1] == '{')) {
+			cf_log_err_cp(cp, "Bare expansions are not permitted.  They must be in a double-quoted string.");
+			goto error;
+		}
+		/* FALL-THROUGH */
+
 	default:
 		slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true);
 		if (slen <= 0) {
@@ -285,14 +297,20 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
 		goto error;
 	}
 
-	/*
-	 *	We cannot assign a count to an attribute.  That must
-	 *	be done in an xlat.
-	 */
-	if ((map->rhs->type == TMPL_TYPE_ATTR) &&
-	    (map->rhs->tmpl_num == NUM_COUNT)) {
-		cf_log_err_cp(cp, "Cannot assign from a count");
-		goto error;
+	if (map->rhs->type == TMPL_TYPE_ATTR) {
+		/*
+		 *	We cannot assign a count to an attribute.  That must
+		 *	be done in an xlat.
+		 */
+		if (map->rhs->tmpl_num == NUM_COUNT) {
+			cf_log_err_cp(cp, "Cannot assign from a count");
+			goto error;
+		}
+
+		if (map->rhs->tmpl_da->flags.virtual) {
+			cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name);
+			goto error;
+		}
 	}
 
 	VERIFY_MAP(map);
@@ -1091,6 +1109,12 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
 	 */
 	if (((map->lhs->tmpl_list == PAIR_LIST_COA) ||
 	     (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) {
+		if ((request->packet->code == PW_CODE_COA_REQUEST) ||
+		    (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) {
+			REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request.  Use 'update request' instead.");
+			return -2;
+		}
+
 		if (!request_alloc_coa(context)) {
 			REDEBUG("Failed to create a CoA/Disconnect Request message");
 			return -2;
@@ -1173,10 +1197,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
 				rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
 				/* FALL-THROUGH */
 		case T_OP_ADD:
-				fr_pair_list_move(parent, list, &head);
+				fr_pair_list_move(parent, list, &head, map->op);
 				fr_pair_list_free(&head);
 			}
 			goto finish;
+		case T_OP_PREPEND:
+			fr_pair_list_move(parent, list, &head, T_OP_PREPEND);
+			fr_pair_list_free(&head);
+			goto finish;
 
 		default:
 			fr_pair_list_free(&head);
@@ -1341,6 +1369,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t
 		fr_pair_list_free(&head);
 		break;
 
+	/*
+	 *	^= - Prepend src_list attributes to the destination
+	 */
+	case T_OP_PREPEND:
+		fr_pair_prepend(list, head);
+		head = NULL;
+		break;
+
 	/*
 	 *	+= - Add all src_list attributes to the destination
 	 */
diff --git a/src/main/tls.c b/src/main/tls.c
index 78c7370a63..cc8dc53178 100644
--- a/src/main/tls.c
+++ b/src/main/tls.c
@@ -27,6 +27,7 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/process.h>
+#include <freeradius-devel/modules.h>
 #include <freeradius-devel/rad_assert.h>
 
 #ifdef HAVE_SYS_STAT_H
@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 #include <fcntl.h>
 #endif
 
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+
 #ifdef HAVE_UTIME_H
 #include <utime.h>
 #endif
@@ -56,8 +61,19 @@ 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)
+#endif
+
 #ifdef ENABLE_OPENSSL_VERSION_CHECK
 typedef struct libssl_defect {
 	uint64_t	high;
@@ -153,6 +169,11 @@ static unsigned int 	record_plus(record_t *buf, void const *ptr,
 static unsigned int 	record_minus(record_t *buf, void *ptr,
 				     unsigned int size);
 
+typedef struct {
+	char const	*name;
+	SSL_CTX		*ctx;
+} fr_realm_ctx_t;
+
 DIAG_OFF(format-nonliteral)
 /** Print errors in the TLS thread local error stack
  *
@@ -191,9 +212,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
 
 			/* Extra verbose */
 			if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) {
-				ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer);
+				ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer);
 			} else {
-				ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer);
+				ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer);
 			}
 
 			talloc_free(p);
@@ -205,7 +226,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
 		 *	Print the error we were given, irrespective
 		 *	of whether there were any OpenSSL errors.
 		 */
-		ROPTIONAL(RERROR, ERROR, "%s", p);
+		ROPTIONAL(RERROR, ERROR, "(TLS) %s", p);
 		talloc_free(p);
 	}
 
@@ -217,9 +238,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap)
 		ERR_error_string_n(error, buffer, sizeof(buffer));
 		/* Extra verbose */
 		if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) {
-			ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer);
+			ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer);
 		} else {
-			ROPTIONAL(REDEBUG, ERROR, "%s", buffer);
+			ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer);
 		}
 		in_stack++;
 	} while ((error = ERR_get_error_line(&file, &line)));
@@ -309,11 +330,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con
 	 *	being regarded as "dead".
 	 */
 	case SSL_ERROR_SYSCALL:
-		ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret);
+		ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret);
 		return 0;
 
 	case SSL_ERROR_SSL:
-		ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret);
+		ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret);
 		return 0;
 
 	/*
@@ -323,7 +344,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con
 	 *	the code needs updating here.
 	 */
 	default:
-		ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret);
+		ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret);
 		return 0;
 	}
 
@@ -376,7 +397,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
 		 *	The passed identity is weird.  Deny it.
 		 */
 		if (!identity_is_safe(identity)) {
-			RWDEBUG("Invalid characters in PSK identity %s", identity);
+			RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity);
 			return 0;
 		}
 
@@ -386,7 +407,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
 		hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query,
 				      NULL, NULL);
 		if (!hex_len) {
-			RWDEBUG("PSK expansion returned an empty string.");
+			RWDEBUG("(TLS) PSK expansion returned an empty string.");
 			return 0;
 		}
 
@@ -396,7 +417,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
 		 *	the truncation, and complain about it.
 		 */
 		if (hex_len > (2 * max_psk_len)) {
-			RWDEBUG("Returned PSK is too long (%u > %u)",
+			RWDEBUG("(TLS) Returned PSK is too long (%u > %u)",
 				(unsigned int) hex_len, 2 * max_psk_len);
 			return 0;
 		}
@@ -419,7 +440,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
 	 *	static identity.
 	 */
 	if (strcmp(identity, conf->psk_identity) != 0) {
-		ERROR("Supplied PSK identity %s does not match configuration.  Rejecting.",
+		ERROR("(TKS) Supplied PSK identity %s does not match configuration.  Rejecting.",
 		      identity);
 		return 0;
 	}
@@ -475,8 +496,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize)
 #endif
 }
 
-
-
 static int _tls_session_free(tls_session_t *ssn)
 {
 	/*
@@ -492,6 +511,52 @@ static int _tls_session_free(tls_session_t *ssn)
 	return 0;
 }
 
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+/*
+ *  By setting the environment variable SSLKEYLOGFILE to a filename keying
+ *  material will be exported that you may use with Wireshark to decode any
+ *  TLS flows. Please see the following for more details:
+ *
+ *	https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption
+ *
+ *  An example logging session is (you should delete the file on each run):
+ *
+ *	rm -f /tmp/sslkey.log; env SSLKEYLOGFILE=/tmp/sslkey.log freeradius -X | tee /tmp/debug
+ */
+static void tls_keylog_cb(UNUSED const SSL *ssl, const char *line)
+{
+	int fd;
+	size_t len;
+	const char *filename;
+	// less than _POSIX_PIPE_BUF (512) guarantees writes are atomic for O_APPEND
+	char buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH];
+
+	filename = getenv("SSLKEYLOGFILE");
+	if (!filename) return;
+
+	len = strlen(line);
+	if ((len + 1) > sizeof(buffer)) {
+		DEBUG("SSLKEYLOGFILE buffer not large enough, max %lu, required %lu", sizeof(buffer), len + 1);
+		return;
+	}
+
+	memcpy(buffer, line, len);
+	buffer[len] = '\n';
+
+	fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+	if (fd < 0) {
+		fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno));
+		return;
+	}
+
+	if (write(fd, buffer, len + 1) == -1) {
+		DEBUG("Failed to write to file %s: %s", filename, strerror(errno));
+	}
+
+	close(fd);
+}
+#endif
+
 tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs)
 {
 	int ret;
@@ -506,6 +571,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
 
 	ssn->ctx = conf->ctx;
 	ssn->mtu = conf->fragment_size;
+	ssn->conf = conf;
 
 	SSL_CTX_set_mode(ssn->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY);
 
@@ -537,15 +603,14 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
 	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf);
 	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn);
 	if (certs) SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)certs);
+
 	SSL_set_fd(ssn->ssl, fd);
-	ret = SSL_connect(ssn->ssl);
 
+	ret = SSL_connect(ssn->ssl);
 	if (ret < 0) {
 		switch (SSL_get_error(ssn->ssl, ret)) {
-			default:
-				break;
-
-
+		default:
+			break;
 
 		case SSL_ERROR_WANT_READ:
 		case SSL_ERROR_WANT_WRITE:
@@ -555,7 +620,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
 	}
 
 	if (ret <= 0) {
-		tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)");
+		tls_error_io_log(NULL, ssn, ret, "Failed in connecting TLS session.");
 		talloc_free(ssn);
 
 		return NULL;
@@ -575,18 +640,61 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con
  * @param conf to use to configure the tls session.
  * @param request The current #REQUEST.
  * @param client_cert Whether to require a client_cert.
+ * @param allow_tls13 Whether to allow or forbid TLS 1.3.
  * @return a new session on success, or NULL on error.
  */
-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert)
+tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert,
+#ifndef TLS1_3_VERSION
+			       UNUSED
+#endif
+			       bool allow_tls13)
 {
 	tls_session_t	*state = NULL;
 	SSL		*new_tls = NULL;
 	int		verify_mode = 0;
 	VALUE_PAIR	*vp;
+	X509_STORE	*new_cert_store;
 
 	rad_assert(request != NULL);
 
-	RDEBUG2("Initiating new TLS session");
+	RDEBUG2("(TLS) Initiating new session");
+
+	/*
+	 *	Replace X509 store if it is time to update CRLs/certs in ca_path
+	 */
+	if (conf->ca_path_reload_interval > 0 && conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) {
+		pthread_mutex_lock(&conf->mutex);
+		/* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */
+		if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) {
+			RDEBUG2("Flushing X509 store to re-read data from ca_path dir");
+
+			if ((new_cert_store = fr_init_x509_store(conf)) == NULL) {
+				RERROR("(TLS) Error replacing X509 store, out of memory (?)");
+			} else {
+				if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store);
+				/*
+				 * Swap empty store with the old one.
+				 */
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+				conf->old_x509_store = SSL_CTX_get_cert_store(conf->ctx);
+				/* Bump refcnt so the store is kept allocated till next store replacement */
+				X509_STORE_up_ref(conf->old_x509_store);
+				SSL_CTX_set_cert_store(conf->ctx, new_cert_store);
+#else
+				/*
+				 * We do not use SSL_CTX_set_cert_store() call here because
+				 * we are not sure that old X509 store is not in the use by some
+				 * thread (i.e. cert check in progress).
+				 * Keep it allocated till next store replacement.
+				 */
+				conf->old_x509_store = conf->ctx->cert_store;
+				conf->ctx->cert_store = new_cert_store;
+#endif
+				conf->ca_path_last_reload = request->timestamp;
+			}
+		}
+		pthread_mutex_unlock(&conf->mutex);
+	}
 
 	new_tls = SSL_new(conf->ctx);
 	if (new_tls == NULL) {
@@ -594,11 +702,36 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 		return NULL;
 	}
 
+#ifdef TLS1_3_VERSION
+	/*
+	 *	Disallow TLS 1.3 for TTLS, PEAP, and FAST.
+	 *
+	 *	We need another magic configuration option to allow
+	 *	it.
+	 */
+	if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) {
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+		WARN("!!                    FORCING MAXIMUM TLS VERSION TO TLS 1.2                  !!");
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+		WARN("!! There is no standard for using this EAP method with TLS 1.3");
+		WARN("!! Please set tls_max_version = \"1.2\"");
+		WARN("!! 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("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+		if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) {
+			tls_error_log(request, "Failed limiting maximum version to TLS 1.2");
+			return NULL;
+		}
+	}
+#endif
+
 	/* We use the SSL's "app_data" to indicate a call-back */
 	SSL_set_app_data(new_tls, NULL);
 
 	if ((state = talloc_zero(ctx, tls_session_t)) == NULL) {
-		RERROR("Error allocating memory for SSL state");
+		RERROR("(TLS) Error allocating memory for SSL state");
 		return NULL;
 	}
 	session_init(state);
@@ -606,6 +739,14 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 
 	state->ctx = conf->ctx;
 	state->ssl = new_tls;
+	state->conf = conf;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+	/*
+	 *	Set the keylog file if the admin requested it.
+	 */
+	if (getenv("SSLKEYLOGFILE") != NULL) SSL_CTX_set_keylog_callback(state->ctx, tls_keylog_cb);
+#endif
 
 	/*
 	 *	Initialize callbacks
@@ -637,6 +778,85 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 	SSL_set_msg_callback_arg(new_tls, state);
 	SSL_set_info_callback(new_tls, cbtls_info);
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+	/*
+	 *	Allow policies to load context-specific certificate chains.
+	 */
+	vp = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_FILE, 0, TAG_ANY);
+	if (vp) {
+		VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY);
+		if (!key) key = vp;
+
+		RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue);
+
+		if (conf->realms) {
+			fr_realm_ctx_t my_r, *r;
+
+			/*
+			 *	Use a pre-existing SSL CTX, if
+			 *	available.  Note that due to OpenSSL
+			 *	issues, this really changes only the
+			 *	certificate files, and leaves all
+			 *	other fields alone.  e.g. you can't
+			 *	select a different TLS version.
+			 *
+			 *	This is fine for our purposes in v3.
+			 *	Due to how we build them, the various
+			 *	additional SSL_CTXs are identical to
+			 *	the main one, except for certs.
+			 */
+			my_r.name = vp->vp_strvalue;
+			r = fr_hash_table_finddata(conf->realms, &my_r);
+			if (r) {
+				(void) SSL_set_SSL_CTX(state->ssl, r->ctx);
+				goto after_chain;
+			}
+
+			/*
+			 *	Else fall through to trying to dynamically load the certs.
+			 */
+		}
+
+		if (conf->file_type) {
+			if (SSL_use_certificate_chain_file(state->ssl, vp->vp_strvalue) != 1) {
+				tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
+					      vp->vp_strvalue);
+			error:
+				talloc_free(state);
+				return NULL;
+			}
+		} else {
+			if (SSL_use_certificate_file(state->ssl, vp->vp_strvalue, SSL_FILETYPE_ASN1) != 1) {
+				tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
+					      vp->vp_strvalue);
+				goto error;
+			}
+		}
+
+		/*
+		 *	Note that there is either no password, or it
+		 *	has to be the same as what's in the
+		 *	configuration.
+		 *
+		 *	There is just no additional security to
+		 *	putting a password into the same file system
+		 *	as the private key.
+		 */
+		if (SSL_use_PrivateKey_file(state->ssl, key->vp_strvalue, SSL_FILETYPE_PEM) != 1) {
+			tls_error_log(request, "Failed loading TLS session certificate \"%s\"",
+				      key->vp_strvalue);
+			goto error;
+		}
+
+		if (SSL_check_private_key(state->ssl) != 1) {
+			tls_error_log(request, "Failed validating TLS session certificate \"%s\"",
+				      vp->vp_strvalue);
+			goto error;
+		}
+	}
+after_chain:
+#endif
+
 	/*
 	 *	In Server mode we only accept.
 	 */
@@ -646,7 +866,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 	 *	Verify the peer certificate, if asked.
 	 */
 	if (client_cert) {
-		RDEBUG2("Setting verify mode to require certificate from client");
+		RDEBUG2("(TLS) Setting verify mode to require certificate from client");
 		verify_mode = SSL_VERIFY_PEER;
 		verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 		verify_mode |= SSL_VERIFY_CLIENT_ONCE;
@@ -670,10 +890,41 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU
 	 *	just too much.
 	 */
 	state->mtu = conf->fragment_size;
+#define EAP_TLS_MAGIC_OVERHEAD (63)
+
+	/*
+	 *	If the packet contains an MTU, then use that.  We
+	 *	trust the admin!
+	 */
 	vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY);
-	if (vp && (vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) {
-		state->mtu = vp->vp_integer;
+	if (vp) {
+		if ((vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) {
+			state->mtu = vp->vp_integer;
+		}
+
+	} else if (request->parent) {
+		/*
+		 *	If there's a parent request, we look for what
+		 *	MTU was set there.  Then, we use an MTU which
+		 *	accounts for the extra overhead of nesting EAP
+		 *	+ TLS inside of EAP + TLS.
+		 */
+		vp = fr_pair_find_by_num(request->parent->state, PW_FRAMED_MTU, 0, TAG_ANY);
+		if (vp && (vp->vp_integer > (100 + EAP_TLS_MAGIC_OVERHEAD)) && (vp->vp_integer <= state->mtu)) {
+			state->mtu = vp->vp_integer - EAP_TLS_MAGIC_OVERHEAD;
+		}
+	}
+
+	/*
+	 *	Cache / update the Framed-MTU in the session-state
+	 *	list.
+	 */
+	vp = fr_pair_find_by_num(request->state, PW_FRAMED_MTU, 0, TAG_ANY);
+	if (!vp) {
+		vp = fr_pair_afrom_num(request->state_ctx, PW_FRAMED_MTU, 0);
+		fr_pair_add(&request->state, vp);
 	}
+	if (vp) vp->vp_integer = state->mtu;
 
 	if (conf->session_cache_enable) state->allow_session_resumption = true; /* otherwise it's false */
 
@@ -697,12 +948,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 {
 	int err;
 
-	if (ssn->invalid_hb_used) return 0;
+	if (ssn->invalid_hb_used) {
+		REDEBUG("(TLS) OpenSSL Heartbeat attack detected.  Closing connection");
+		return 0;
+	}
 
 	if (ssn->dirty_in.used > 0) {
 		err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used);
 		if (err != (int) ssn->dirty_in.used) {
-			REDEBUG("TLS - Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
+			REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err);
 			record_init(&ssn->dirty_in);
 			return 0;
 		}
@@ -716,21 +970,23 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 		return 1;
 	}
 
-	if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)")) return 0;
+	if (!tls_error_io_log(request, ssn, err, "Failed reading from OpenSSL")) return 0;
 
 	/* Some Extra STATE information for easy debugging */
 	if (!ssn->is_init_finished && SSL_is_init_finished(ssn->ssl)) {
 		VALUE_PAIR *vp;
 		char const *str_version;
 
-		RDEBUG2("TLS - Connection Established");
+		RDEBUG2("(TLS) Connection Established");
 		ssn->is_init_finished = true;
 
 		vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0);
 		if (vp) {
 			fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(SSL_get_current_cipher(ssn->ssl)));
 			fr_pair_add(&request->state, vp);
+			RINDENT();
 			rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
+			REXDENT();
 		}
 
 		switch (ssn->info.version) {
@@ -767,13 +1023,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 		if (vp) {
 			fr_pair_value_strcpy(vp, str_version);
 			fr_pair_add(&request->state, vp);
+			RINDENT();
 			rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
+			REXDENT();
 		}
 	}
-	else if (SSL_in_init(ssn->ssl)) { RDEBUG2("TLS - In Handshake Phase"); }
-	else if (SSL_in_before(ssn->ssl)) { RDEBUG2("TLS - Before Handshake Phase"); }
-	else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("TLS - In Accept mode"); }
-	else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("TLS - In Connect mode"); }
+	else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); }
+	else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); }
+	else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); }
+	else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); }
 
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
 	/*
@@ -791,7 +1049,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 		 *	to get the session is a hard fail.
 		 */
 		if (!ssn->ssl_session && ssn->is_init_finished) {
-			RDEBUG("TLS - Failed getting session");
+			RDEBUG("(TLS) Failed getting session");
 			return 0;
 		}
 	}
@@ -805,23 +1063,21 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn)
 		err = BIO_read(ssn->from_ssl, ssn->dirty_out.data,
 			       sizeof(ssn->dirty_out.data));
 		if (err > 0) {
-			RDEBUG2("TLS - got %d bytes of data", err);
+			RDEBUG3("(TLS) got %d bytes of data", err);
 			ssn->dirty_out.used = err;
 
 		} else if (BIO_should_retry(ssn->from_ssl)) {
 			record_init(&ssn->dirty_in);
-			RDEBUG2("TLS - Asking for more data in tunnel.");
+			RDEBUG2("(TLS) Asking for more data in tunnel.");
 			return 1;
 
 		} else {
-			tls_error_log(NULL, "Error reading from SSL BIO");
+			tls_error_log(NULL, "Error reading from OpenSSL");
 			record_init(&ssn->dirty_in);
-			RDEBUG2("TLS - Tunnel data is established.");
 			return 0;
 		}
 	} else {
-
-		RDEBUG2("TLS - Application data.");
+		RDEBUG2("(TLS) Application data.");
 		/* Its clean application data, do whatever we want */
 		record_init(&ssn->clean_out);
 	}
@@ -855,13 +1111,12 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn)
 		record_minus(&ssn->clean_in, NULL, written);
 
 		/* Get the dirty data from Bio to send it */
-		err = BIO_read(ssn->from_ssl, ssn->dirty_out.data,
-			       sizeof(ssn->dirty_out.data));
+		err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used,
+			       sizeof(ssn->dirty_out.data) - ssn->dirty_out.used);
 		if (err > 0) {
-			ssn->dirty_out.used = err;
+			ssn->dirty_out.used += err;
 		} else {
-			if (!tls_error_io_log(request, ssn, err,
-					      "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) {
+			if (!tls_error_io_log(request, ssn, err, "Failed writing to OpenSSL")) {
 				return 0;
 			}
 		}
@@ -963,7 +1218,10 @@ void tls_session_information(tls_session_t *tls_session)
 {
 	char const *str_write_p, *str_version, *str_content_type = "";
 	char const *str_details1 = "", *str_details2= "";
+	char const *details = NULL;
 	REQUEST *request;
+	VALUE_PAIR *vp;
+	char content_type[16], alert_buf[16];
 	char buffer[32];
 
 	/*
@@ -972,7 +1230,18 @@ void tls_session_information(tls_session_t *tls_session)
 	 */
 	if (rad_debug_lvl == 0) return;
 
-	str_write_p = tls_session->info.origin ? ">>> send" : "<<< recv";
+	/*
+	 *	OpenSSL calls this function with 'pseudo' content
+	 *	types.  The user doesn't care about them, so suppress them.
+	 */
+	if (tls_session->info.content_type > UINT8_MAX) return;
+
+	request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST);
+	if (!request) return;
+
+	str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv";
+
+#define FROM_CLIENT (tls_session->info.origin == 0)
 
 	switch (tls_session->info.version) {
 	case SSL2_VERSION:
@@ -1006,8 +1275,7 @@ void tls_session_information(tls_session_t *tls_session)
 		break;
 	}
 
-	if (tls_session->info.version == SSL3_VERSION ||
-	    tls_session->info.version == TLS1_VERSION) {
+	if (1) {
 		switch (tls_session->info.content_type) {
 		case SSL3_RT_CHANGE_CIPHER_SPEC:
 			str_content_type = "ChangeCipherSpec";
@@ -1026,7 +1294,8 @@ void tls_session_information(tls_session_t *tls_session)
 			break;
 
 		default:
-			str_content_type = "UnknownContentType";
+			snprintf(content_type, sizeof(content_type), "content=%d", tls_session->info.content_type);
+			str_content_type = content_type;
 			break;
 		}
 
@@ -1045,9 +1314,12 @@ void tls_session_information(tls_session_t *tls_session)
 				}
 
 				str_details2 = " ???";
+				details = "there is a failure inside the TLS protocol exchange";
+
 				switch (tls_session->info.alert_description) {
 				case SSL3_AD_CLOSE_NOTIFY:
 					str_details2 = " close_notify";
+					details = "the connection has been closed, and no further TLS exchanges will take place";
 					break;
 
 				case SSL3_AD_UNEXPECTED_MESSAGE:
@@ -1074,24 +1346,34 @@ void tls_session_information(tls_session_t *tls_session)
 					str_details2 = " handshake_failure";
 					break;
 
+				case SSL3_AD_NO_CERTIFICATE:
+					str_details2 = " no_certificate";
+					details = "the server did not present a certificate to the client";
+					break;
+
 				case SSL3_AD_BAD_CERTIFICATE:
 					str_details2 = " bad_certificate";
+					details = "it believes the server certificate is invalid or malformed";
 					break;
 
 				case SSL3_AD_UNSUPPORTED_CERTIFICATE:
 					str_details2 = " unsupported_certificate";
+					details = "it does not understand the certificate presented by the server";
 					break;
 
 				case SSL3_AD_CERTIFICATE_REVOKED:
 					str_details2 = " certificate_revoked";
+					details = "it believes that the server certificate has been revoked";
 					break;
 
 				case SSL3_AD_CERTIFICATE_EXPIRED:
 					str_details2 = " certificate_expired";
+					details = "it believes that the server certificate has expired.  Either renew the server certificate, or check the time on the client";
 					break;
 
 				case SSL3_AD_CERTIFICATE_UNKNOWN:
 					str_details2 = " certificate_unknown";
+					details = "it does not recognize the server certificate";
 					break;
 
 				case SSL3_AD_ILLEGAL_PARAMETER:
@@ -1100,6 +1382,7 @@ void tls_session_information(tls_session_t *tls_session)
 
 				case TLS1_AD_UNKNOWN_CA:
 					str_details2 = " unknown_ca";
+					details = "it does not recognize the CA used to issue the server certificate.  Please update the client so that it knows about the CA";
 					break;
 
 				case TLS1_AD_ACCESS_DENIED:
@@ -1120,6 +1403,18 @@ void tls_session_information(tls_session_t *tls_session)
 
 				case TLS1_AD_PROTOCOL_VERSION:
 					str_details2 = " protocol_version";
+					details = "the client does not accept the version of TLS negotiated by the server";
+
+#ifdef TLS1_3_VERSION
+					/*
+					 *	Complain about OpenSSL bugs.
+					 */
+					if ((tls_session->info.version > tls_session->conf->max_version) &&
+					    (rad_debug_lvl > 0)) {
+						WARN("TLS 1.3 has been negotiated even though it was disabled.  This is an OpenSSL Bug.");
+						WARN("Please set: cipher_list = \"DEFAULT@SECLEVEL=1\" in the tls {...} section.");
+					}
+#endif
 					break;
 
 				case TLS1_AD_INSUFFICIENT_SECURITY:
@@ -1137,12 +1432,69 @@ void tls_session_information(tls_session_t *tls_session)
 				case TLS1_AD_NO_RENEGOTIATION:
 					str_details2 = " no_renegotiation";
 					break;
+
+#ifdef TLS13_AD_MISSING_EXTENSIONS
+				case TLS13_AD_MISSING_EXTENSIONS:
+					str_details2 = " missing_extensions";
+					details = "the server did not present a TLS extension which the client expected to be present.  Please check the TLS libraries on the client and server for compatibility";
+					break;
+#endif
+
+#ifdef TLS13_AD_CERTIFICATE_REQUIRED
+				case TLS13_AD_CERTIFICATE_REQUIRED:
+					str_details2 = " certificate_required";
+					details = "the server did not present a certificate";
+					break;
+#endif
+
+#ifdef TLS1_AD_UNSUPPORTED_EXTENSION
+				case TLS1_AD_UNSUPPORTED_EXTENSION:
+					str_details2 = " unsupported_extension";
+					details = "the server has sent a TLS message which the client does not recognize.  Please check the TLS libraries on the client and server for compatibility";
+					break;
+#endif
+
+#ifdef TLS1_AD_CERTIFICATE_UNOBTAINABLE
+				case TLS1_AD_CERTIFICATE_UNOBTAINABLE:
+					str_details2 = " certificate_unobtainable";
+					break;
+#endif
+
+#ifdef TLS1_AD_UNRECOGNIZED_NAME
+				case TLS1_AD_UNRECOGNIZED_NAME:
+					str_details2 = " unrecognized_name";
+					break;
+#endif
+
+#ifdef TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE
+				case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE:
+					str_details2 = " bad_certificate_status_response";
+					break;
+#endif
+
+#ifdef TLS1_AD_BAD_CERTIFICATE_HASH_VALUE
+				case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE:
+					str_details2 = " bad_certificate_hash_value";
+					break;
+#endif
+
+#ifdef TLS1_AD_UNKNOWN_PSK_IDENTITY
+				case TLS1_AD_UNKNOWN_PSK_IDENTITY:
+					str_details2 = " unknown_psk_identity";
+					break;
+#endif
+
+#ifdef TLS1_AD_NO_APPLICATION_PROTOCOL
+				case TLS1_AD_NO_APPLICATION_PROTOCOL:
+					str_details2 = " no_application_protocol";
+					break;
+#endif
 				}
 			}
 		}
 
 		if (tls_session->info.content_type == SSL3_RT_HANDSHAKE) {
-			str_details1 = "???";
+			str_details1 = "";
 
 			if (tls_session->info.record_len > 0) switch (tls_session->info.handshake_type) {
 			case SSL3_MT_HELLO_REQUEST:
@@ -1157,6 +1509,18 @@ void tls_session_information(tls_session_t *tls_session)
 				str_details1 = ", ServerHello";
 				break;
 
+#ifdef SSL3_MT_NEWSESSION_TICKET
+			case SSL3_MT_NEWSESSION_TICKET:
+				str_details1 = ", NewSessionTicket";
+				break;
+#endif
+
+#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS
+			case SSL3_MT_ENCRYPTED_EXTENSIONS:
+				str_details1 = ", EncryptedExtensions";
+				break;
+#endif
+
 			case SSL3_MT_CERTIFICATE:
 				str_details1 = ", Certificate";
 				break;
@@ -1184,31 +1548,52 @@ void tls_session_information(tls_session_t *tls_session)
 			case SSL3_MT_FINISHED:
 				str_details1 = ", Finished";
 				break;
+
+#ifdef SSL3_MT_KEY_UPDATE
+			case SSL3_MT_KEY_UPDATE:
+				str_content_type = "KeyUpdate";
+				break;
+#endif
+
+			default:
+				snprintf(alert_buf, sizeof(alert_buf), ", type=%d", tls_session->info.handshake_type);
+				str_details1 = alert_buf;
+				break;
 			}
 		}
 	}
 
 	snprintf(tls_session->info.info_description,
 		 sizeof(tls_session->info.info_description),
-		 "%s %s%s [length %04lx]%s%s\n",
+		 "%s %s%s%s%s",
 		 str_write_p, str_version, str_content_type,
-		 (unsigned long)tls_session->info.record_len,
 		 str_details1, str_details2);
 
-	request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST);
-	if (!request) return;
+	/*
+	 *	Cache the TLS session information in the session-state
+	 *	list, so it can be accessed by Post-Auth-Type
+	 *	Client-Lost { ... }
+	 */
+	vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_INFORMATION, 0);
+	if (vp) {
+		fr_pair_value_strcpy(vp, tls_session->info.info_description);
+		fr_pair_add(&request->state, vp);
+	}
 
 	RDEBUG2("%s", tls_session->info.info_description);
+
+	if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details);
 }
 
 static CONF_PARSER cache_config[] = {
 	{ "enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, session_cache_enable), "no" },
 
-	{ "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_timeout), "24" },
+	{ "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_lifetime), "24" },
 	{ "name", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_id_name), NULL },
 
 	{ "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_cache_size), "255" },
 	{ "persist_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_path), NULL },
+	{ "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_server), NULL },
 	CONF_PARSER_TERMINATOR
 };
 
@@ -1256,6 +1641,7 @@ static CONF_PARSER tls_server_config[] = {
 #ifdef X509_V_FLAG_CRL_CHECK_ALL
 	{ "check_all_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, check_all_crl), "no" },
 #endif
+	{ "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" },
 	{ "allow_expired_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, allow_expired_crl), NULL },
 	{ "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL },
 	{ "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL },
@@ -1263,6 +1649,10 @@ static CONF_PARSER tls_server_config[] = {
 	{ "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL },
 	{ "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, require_client_cert), NULL },
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+	{ "reject_unknown_intermediate_ca", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disallow_untrusted), .dflt = "no", },
+#endif
+
 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
 #ifndef OPENSSL_NO_ECDH
 	{ "ecdh_curve", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, ecdh_curve), "prime256v1" },
@@ -1281,9 +1671,23 @@ static CONF_PARSER tls_server_config[] = {
 	{ "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
 #endif
 
-	{ "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
+	{ "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
+
+	{ "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
+#if defined(TLS1_2_VERSION)
+	  "1.2"
+#elif defined(TLS1_1_VERSION)
+	  "1.1"
+#else
+	  "1.0"
+#endif
+	},
+
+#ifdef TLS1_3_VERSION
+	{ "tls13_enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, tls13_enable_magic), NULL },
+#endif
 
-	{ "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
+	{ "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL },
 
 	{ "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config },
 
@@ -1312,6 +1716,7 @@ static CONF_PARSER tls_client_config[] = {
 	{ "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL },
 	{ "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL },
 	{ "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL },
+	{ "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" },
 
 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
 #ifndef OPENSSL_NO_ECDH
@@ -1331,14 +1736,25 @@ static CONF_PARSER tls_client_config[] = {
 	{ "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
 #endif
 
-	{ "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
+	{ "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
+
+	{ "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
+#if defined(TLS1_2_VERSION)
+	  "1.2"
+#elif defined(TLS1_1_VERSION)
+	  "1.1"
+#else
+	  "1.0"
+#endif
+	},
 
-	{ "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
+	{ "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL },
 
 	CONF_PARSER_TERMINATOR
 };
 
 
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
 /*
  *	TODO: Check for the type of key exchange * like conf->dh_key
  */
@@ -1349,6 +1765,23 @@ static int load_dh_params(SSL_CTX *ctx, char *file)
 
 	if (!file) return 0;
 
+	/*
+	 * Prior to trying to load the file, check what OpenSSL will do with it.
+	 *
+	 * Certain downstreams (such as RHEL) will ignore user-provided dhparams
+	 * in FIPS mode, unless the specified parameters are FIPS-approved.
+	 * However, since OpenSSL >= 1.1.1 will automatically select parameters
+	 * anyways, there's no point in attempting to load them.
+	 *
+	 * Change suggested by @t8m
+	 */
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+	if (FIPS_mode() > 0) {
+		WARN(LOG_PREFIX ": Ignoring user-selected DH parameters in FIPS mode. Using defaults.");
+		return 0;
+	}
+#endif
+
 	if ((bio = BIO_new_file(file, "r")) == NULL) {
 		ERROR(LOG_PREFIX ": Unable to open DH file - %s", file);
 		return -1;
@@ -1371,6 +1804,7 @@ static int load_dh_params(SSL_CTX *ctx, char *file)
 	DH_free(dh);
 	return 0;
 }
+#endif
 
 
 /*
@@ -1422,7 +1856,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 
 	conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
 	if (!conf) {
-		RWDEBUG("Failed to find TLS configuration in session");
+		RWDEBUG("(TLS) Failed to find TLS configuration in session");
 		return 0;
 	}
 
@@ -1439,7 +1873,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 		blob_len = i2d_SSL_SESSION(sess, NULL);
 		if (blob_len < 1) {
 			/* something went wrong */
-			if (request) RWDEBUG("Session serialisation failed, couldn't determine required buffer length");
+			if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length");
 			return 0;
 		}
 
@@ -1447,14 +1881,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 		/* alloc and convert to ASN.1 */
 		sess_blob = malloc(blob_len);
 		if (!sess_blob) {
-			RWDEBUG("Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len);
+			RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len);
 			return 0;
 		}
 		/* openssl mutates &p */
 		p = sess_blob;
 		rv = i2d_SSL_SESSION(sess, &p);
 		if (rv != blob_len) {
-			if (request) RWDEBUG("Session serialisation failed");
+			if (request) RWDEBUG("(TLS) Session serialisation failed");
 			goto error;
 		}
 
@@ -1463,7 +1897,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 			 conf->session_cache_path, FR_DIR_SEP, buffer);
 		fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR);
 		if (fd < 0) {
-			if (request) RERROR("Session serialisation failed, failed opening session file %s: %s",
+			if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s",
 					    filename, fr_syserror(errno));
 			goto error;
 		}
@@ -1486,7 +1920,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 		while (todo > 0) {
 			rv = write(fd, p, todo);
 			if (rv < 1) {
-				if (request) RWDEBUG("Failed writing session: %s", fr_syserror(errno));
+				if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno));
 				close(fd);
 				goto error;
 			}
@@ -1494,7 +1928,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess)
 			todo -= rv;
 		}
 		close(fd);
-		if (request) RWDEBUG("Wrote session %s to %s (%d bytes)", buffer, filename, blob_len);
+		if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len);
 	}
 
 error:
@@ -1595,7 +2029,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 
 	conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
 	if (!conf) {
-		RWDEBUG("Failed to find TLS configuration in session");
+		RWDEBUG("(TLS) Failed to find TLS configuration in session");
 		return NULL;
 	}
 
@@ -1617,20 +2051,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 		snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer);
 		fd = open(filename, O_RDONLY);
 		if (fd < 0) {
-			RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno));
+			RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno));
 			goto error;
 		}
 
 		rv = fstat(fd, &st);
 		if (rv < 0) {
-			RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno));
+			RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno));
 			close(fd);
 			goto error;
 		}
 
 		sess_data = talloc_array(NULL, unsigned char, st.st_size);
 		if (!sess_data) {
-			RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size);
+			RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size);
 			close(fd);
 			goto error;
 		}
@@ -1640,7 +2074,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 		while (todo > 0) {
 			rv = read(fd, q, todo);
 			if (rv < 1) {
-				RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno));
+				RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno));
 				close(fd);
 				goto error;
 			}
@@ -1664,7 +2098,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 		memcpy(&o, &p, sizeof(o));
 		sess = d2i_SSL_SESSION(NULL, o, st.st_size);
 		if (!sess) {
-			RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
+			RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
 			goto error;
 		}
 
@@ -1674,7 +2108,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 		rv = pairlist_read(talloc_ctx, filename, &pairlist, 1);
 		if (rv < 0) {
 			/* not safe to un-persist a session w/o VPs */
-			RWDEBUG("Failed loading persisted VPs for session %s", buffer);
+			RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer);
 			SSL_SESSION_free(sess);
 			sess = NULL;
 			goto error;
@@ -1708,12 +2142,27 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l
 			if (vp) {
 				if ((request->timestamp + vp->vp_integer) > expires) {
 					vp->vp_integer = expires - request->timestamp;
-					RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration",
+					RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
 						 vp->vp_integer);
 				}
 			}
 		}
 
+		/*
+		 *	Resumption MUST use the same EAP type as from
+		 *	the original packet.
+		 */
+		vp = fr_pair_find_by_num(pairlist->reply, PW_EAP_TYPE, 0, TAG_ANY);
+		if (vp) {
+			VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
+
+			if (type && (type->vp_integer != vp->vp_integer)) {
+				REDEBUG("Resumption has changed EAP types for session %s", buffer);
+				REDEBUG("Rejecting session due to protocol violations");
+				goto error;
+			}
+		}
+
 		/* move the cached VPs into the session */
 		fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &pairlist->reply, 0, 0, TAG_ANY);
 
@@ -1733,46 +2182,390 @@ error:
 	return sess;
 }
 
-#ifdef HAVE_OPENSSL_OCSP_H
-
-/** Extract components of OCSP responser URL from a certificate
- *
- * @param[in] cert to extract URL from.
- * @param[out] host_out Portion of the URL (must be freed with free()).
- * @param[out] port_out Port portion of the URL (must be freed with free()).
- * @param[out] path_out Path portion of the URL (must be freed with free()).
- * @param[out] is_https Whether the responder should be contacted using https.
- * @return
- *	- 0 if no valid URL is contained in the certificate.
- *	- 1 if a URL was found and parsed.
- *	- -1 if at least one URL was found, but none could be parsed.
- */
-static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out,
-			       char **path_out, int *is_https)
+static size_t tls_session_id_binary(SSL_SESSION *ssn, uint8_t *buffer, size_t bufsize)
 {
-	int			i;
-	bool			found_uri = false;
+#if OPENSSL_VERSION_NUMBER < 0x10001000L
+	size_t size;
 
-	AUTHORITY_INFO_ACCESS	*aia;
-	ACCESS_DESCRIPTION	*ad;
+	size = ssn->session_id_length;
+	if (size > bufsize) size = bufsize;
 
-	aia = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+	memcpy(buffer, ssn->session_id, size);
+	return size;
+#else
+	unsigned int size;
+	uint8_t const *p;
 
-	for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
-		ad = sk_ACCESS_DESCRIPTION_value(aia, i);
-		if (OBJ_obj2nid(ad->method) != NID_ad_OCSP) continue;
-		if (ad->location->type != GEN_URI) continue;
-		found_uri = true;
+	p = SSL_SESSION_get_id(ssn, &size);
+	if (size > bufsize) size = bufsize;
 
-		if (OCSP_parse_url((char *) ad->location->d.ia5->data, host_out,
-				   port_out, path_out, is_https)) return 1;
-	}
-	return found_uri ? -1 : 0;
+	memcpy(buffer, p, size);
+	return size;
+#endif
 }
 
 /*
- * This function sends a OCSP request to a defined OCSP responder
- * and checks the OCSP response for correctness.
+ *	From TLS-Cache-Method
+ *
+ *	All of the save / clear / load callbacks are done with any
+ *	OpenSSL locks *unlocked*.  So says the OpenSSL code.
+ */
+#define CACHE_SAVE (1)
+#define CACHE_LOAD (2)
+#define CACHE_CLEAR (3)
+#define CACHE_REFRESH (4)
+
+static REQUEST *cache_init_fake_request(fr_tls_server_conf_t const *conf, SSL_SESSION *sess, SSL *ssl,
+					uint8_t const *data, size_t size)
+{
+	VALUE_PAIR		*vp;
+	REQUEST			*fake, *request = NULL;
+	uint8_t			buffer[MAX_SESSION_SIZE];
+
+	if (sess) {
+		size = tls_session_id_binary(sess, buffer, sizeof(buffer));
+		data = buffer;
+	}
+
+	/*
+	 *	We get called essentially at random by OpenSSL, with
+	 *	no information other than the session ID.  As a
+	 *	result, we have to manually set up our own request.
+	 */
+	if (ssl) request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
+
+	if (request) {
+		fake = request_alloc_fake(request);
+	} else {
+		fake = request_alloc(NULL);
+		fake->packet = rad_alloc(fake, false);
+		fake->reply = rad_alloc(fake, false);
+	}
+
+	vp = fr_pair_afrom_num(fake->packet, PW_TLS_SESSION_ID, 0);
+	if (!vp) {
+		talloc_free(fake);
+		return NULL;
+	}
+
+	fr_pair_value_memcpy(vp, data, size);
+	fr_pair_add(&fake->packet->vps, vp);
+
+	fake->server = conf->session_cache_server;
+
+	return fake;
+}
+
+/*
+ *	Clear cached data
+ */
+static void cbtls_cache_clear(SSL_CTX *ctx, SSL_SESSION *sess)
+{
+	fr_tls_server_conf_t	*conf;
+	REQUEST			*fake;
+
+	conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx);
+	if (!conf) {
+		DEBUG(LOG_PREFIX ": Failed to find TLS configuration in session");
+		return;
+	}
+
+	/*
+	 *	Find the SSL ID from the session, and delete it.
+	 *
+	 *	Don't bother with any parent request.  We're in a
+	 *	timer callback, and there is no request available.
+	 */
+	fake = cache_init_fake_request(conf, sess, NULL, NULL, 0);
+	if (!fake) return;
+
+	/*
+	 *	Use &request:TLS-Session-Id to clear the cache entry.
+	 */
+	(void) process_post_auth(CACHE_CLEAR, fake);
+	talloc_free(fake);
+	return;
+}
+
+/*
+ *	OpenSSL calls this function in order to save the session
+ *	BEFORE it has sent the final TLS success.  So our process here
+ *	is to say "yes, we saved it", and then do the *actual* saving
+ *	after the TLS success has been sent.
+ */
+static int cbtls_cache_save(UNUSED SSL *ssl, UNUSED SSL_SESSION *sess)
+{
+	return 0;
+}
+
+static int cbtls_cache_save_vps(SSL *ssl, SSL_SESSION *sess, VALUE_PAIR *vps)
+{
+	fr_tls_server_conf_t	*conf;
+	VALUE_PAIR		*vp;
+	REQUEST			*fake = NULL;
+	size_t			size, rv;
+	uint8_t			*p, *sess_blob = NULL;
+
+	conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
+	if (!conf) return 0;
+
+	/*
+	 *	Find the SSL ID from the session, and save it.
+	 *
+	 *	Save anything from the parent request.
+	 */
+	fake = cache_init_fake_request(conf, sess, ssl, NULL, 0);
+	if (!fake) return 0;
+
+	/* find out what length data we need */
+	size = i2d_SSL_SESSION(sess, NULL);
+	if (size < 1) return 0;
+
+	/* Do not convert to TALLOC - it's passed to OpenSSL */
+	/* alloc and convert to ASN.1 */
+	MEM(sess_blob = malloc(size));
+
+	/* openssl mutates &p */
+	p = sess_blob;
+	rv = i2d_SSL_SESSION(sess, &p);
+	if (rv != size) goto error;
+
+	vp = fr_pair_afrom_num(fake->state_ctx, PW_TLS_SESSION_DATA, 0);
+	if (!vp) goto error;
+
+	fr_pair_value_memcpy(vp, sess_blob, size);
+	fr_pair_add(&fake->state, vp);
+
+	if (vps) fr_pair_add(&fake->reply->vps, fr_pair_list_copy(fake->reply, vps));
+
+	/*
+	 *	Use &request:TLS-Session-Id to save the
+	 *	&session-state:TLS-Session-Data values.
+	 *
+	 *	The current &reply: list is the list of VPs which
+	 *	should be cached.
+	 *
+	 *	Any other attributes which need to be saved can be
+	 *	read from the &outer.reply: list.
+	 */
+	(void) process_post_auth(CACHE_SAVE, fake);
+
+error:
+	if (fake) talloc_free(fake);
+	free(sess_blob);
+
+	return 0;
+}
+
+static int cbtls_cache_refresh(SSL *ssl, SSL_SESSION *sess)
+{
+	fr_tls_server_conf_t	*conf;
+	REQUEST			*fake = NULL;
+
+	conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
+	if (!conf) return 0;
+
+	/*
+	 *	Find the SSL ID from the session, and save it.
+	 *
+	 *	Save anything from the parent request.
+	 */
+	fake = cache_init_fake_request(conf, sess, ssl, NULL, 0);
+	if (!fake) return 0;
+	/*
+	 *	Use &request:TLS-Session-Id to update the cache
+	 *	entry so that it doesn't not expire.
+	 */
+	(void) process_post_auth(CACHE_REFRESH, fake);
+
+	talloc_free(fake);
+
+	return 0;
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+static SSL_SESSION *cbtls_cache_load(SSL *ssl, unsigned char *data, int len, int *copy)
+#else
+static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int len, int *copy)
+#endif
+{
+	fr_tls_server_conf_t	*conf;
+	size_t			size;
+	uint8_t const  		*p;
+	VALUE_PAIR		*vp, *vps;
+	TALLOC_CTX		*talloc_ctx;
+	SSL_SESSION		*sess = NULL;
+	REQUEST			*fake = NULL;
+	REQUEST			*request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
+	char			buffer[2 * MAX_SESSION_SIZE + 1];
+
+	conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
+	if (!conf) return NULL;
+
+	rad_assert(request);
+
+	size = len;
+	if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;
+
+	if (fr_debug_lvl > 1) {
+		fr_bin2hex(buffer, data, size);
+		RDEBUG2("Peer requested cached session: %s", buffer);
+	}
+
+	*copy = 0;
+
+	/*
+	 *	Take the given SSL ID, and create a fake request.
+	 *
+	 *	Don't bother parenting it from another request.  We do
+	 *	this for a number of reasons.
+	 *
+	 *	One is that rest of the code expects that the VPs will
+	 *	be added to fr_tls_ex_index_vps.  So we don't want to
+	 *	be poking the request directly, as that will result in
+	 *	a change of behavior.
+	 *
+	 *	The larger reason is that we do _not_ want to actually
+	 *	update the reply, until such time as we know that the
+	 *	user has been authenticated.
+	 */
+	fake = cache_init_fake_request(conf, NULL, NULL, data, size);
+	if (!fake) return 0;
+
+	/*
+	 *	Use &request:TLS-Session-Id to load the cached
+	 *	session.
+	 *
+	 *	The "cache load { ...}" section should put the reply
+	 *	attributes into the &reply: list, and the
+	 *	&session-state:TLS-Session-Data attribute.
+	 *
+	 *	Why?  Because v4 does it that way, and there aren't
+	 *	really good reasons for doing it differently.
+	 */
+	(void) process_post_auth(CACHE_LOAD, fake);
+
+	/*
+	 *	Enforce client certificate expiration.
+	 */
+	vp = fr_pair_find_by_num(fake->reply->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY);
+	if (vp) {
+		time_t expires;
+
+		if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) {
+			RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror());
+			SSL_SESSION_free(sess);
+			sess = NULL;
+			goto error;
+		}
+
+		if (expires <= request->timestamp) {
+			RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer);
+			SSL_SESSION_free(sess);
+			sess = NULL;
+			goto error;
+		}
+
+		/*
+		 *	Account for Session-Timeout, if it's available.
+		 */
+		vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
+		if (vp) {
+			if ((request->timestamp + vp->vp_integer) > expires) {
+				vp->vp_integer = expires - request->timestamp;
+				RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
+					 vp->vp_integer);
+			}
+		}
+	}
+
+	/*
+	 *	Try to de-serialize the session data.
+	 */
+	vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY);
+	if (!vp) {
+		RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer);
+		goto error;
+	}
+
+	/*
+	 *	OpenSSL mutates what's passed in, so we assign sess_data to q,
+	 *	so the value of q gets mutated, and not the value of sess_data.
+	 *
+	 *	We then need a pointer to hold &q, but it can't be const, because
+	 *	clang complains about lack of consting in nested pointer types.
+	 *
+	 *	So we memcpy the value of that pointer, to one that
+	 *	does have a const, which we then pass into d2i_SSL_SESSION *sigh*.
+	 */
+	p = vp->vp_octets;
+	sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length);
+	if (!sess) {
+		RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL));
+		goto error;
+	}
+
+	talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
+	vps = NULL;
+
+	/* move the cached VPs into the session */
+	fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &fake->reply->vps, 0, 0, TAG_ANY);
+
+	SSL_SESSION_set_ex_data(sess, fr_tls_ex_index_vps, vps);
+	RDEBUG("Successfully restored session %s", buffer);
+	rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:");
+
+	/*
+	 *	The "restore VPs from OpenSSL cache" code is
+	 *	now in eaptls_process()
+	 */
+
+error:
+	if (fake) talloc_free(fake);
+
+	return sess;
+}
+
+#ifdef HAVE_OPENSSL_OCSP_H
+
+/** Extract components of OCSP responser URL from a certificate
+ *
+ * @param[in] cert to extract URL from.
+ * @param[out] host_out Portion of the URL (must be freed with free()).
+ * @param[out] port_out Port portion of the URL (must be freed with free()).
+ * @param[out] path_out Path portion of the URL (must be freed with free()).
+ * @param[out] is_https Whether the responder should be contacted using https.
+ * @return
+ *	- 0 if no valid URL is contained in the certificate.
+ *	- 1 if a URL was found and parsed.
+ *	- -1 if at least one URL was found, but none could be parsed.
+ */
+static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out,
+			       char **path_out, int *is_https)
+{
+	int			i;
+	bool			found_uri = false;
+
+	AUTHORITY_INFO_ACCESS	*aia;
+	ACCESS_DESCRIPTION	*ad;
+
+	aia = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+
+	for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
+		ad = sk_ACCESS_DESCRIPTION_value(aia, i);
+		if (OBJ_obj2nid(ad->method) != NID_ad_OCSP) continue;
+		if (ad->location->type != GEN_URI) continue;
+		found_uri = true;
+
+		if (OCSP_parse_url((char *) ad->location->d.ia5->data, host_out,
+				   port_out, path_out, is_https)) return 1;
+	}
+	return found_uri ? -1 : 0;
+}
+
+/*
+ * This function sends a OCSP request to a defined OCSP responder
+ * and checks the OCSP response for correctness.
  */
 
 /* Maximum leeway in validity period: default 5 minutes */
@@ -1811,7 +2604,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
 	VALUE_PAIR	*vp;
 
 	if (issuer_cert == NULL) {
-		RWDEBUG("Could not get issuer certificate");
+		RWDEBUG("(TLS) Could not get issuer certificate");
 		goto skipped;
 	}
 
@@ -1836,7 +2629,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
 		/* Reading the libssl src, they do a strdup on the URL, so it could of been const *sigh* */
 		OCSP_parse_url(url, &host, &port, &path, &use_ssl);
 		if (!host || !port || !path) {
-			RWDEBUG("ocsp: Host or port or path missing from configured URL \"%s\".  Not doing OCSP", url);
+			RWDEBUG("(TLS) ocsp: Host or port or path missing from configured URL \"%s\".  Not doing OCSP", url);
 			goto skipped;
 		}
 	} else {
@@ -1845,15 +2638,15 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
 		ret = ocsp_parse_cert_url(client_cert, &host, &port, &path, &use_ssl);
 		switch (ret) {
 		case -1:
-			RWDEBUG("ocsp: Invalid URL in certificate.  Not doing OCSP");
+			RWDEBUG("(TLS) ocsp: Invalid URL in certificate.  Not doing OCSP");
 			break;
 
 		case 0:
 			if (conf->ocsp_url) {
-				RWDEBUG("ocsp: No OCSP URL in certificate, falling back to configured URL");
+				RWDEBUG("(TLS) ocsp: No OCSP URL in certificate, falling back to configured URL");
 				goto use_ocsp_url;
 			}
-			RWDEBUG("ocsp: No OCSP URL in certificate.  Not doing OCSP");
+			RWDEBUG("(TLS) ocsp: No OCSP URL in certificate.  Not doing OCSP");
 			goto skipped;
 
 		case 1:
@@ -1865,7 +2658,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue
 
 	/* Check host and port length are sane, then create Host: HTTP header */
 	if ((strlen(host) + strlen(port) + 2) > sizeof(hostheader)) {
-		RWDEBUG("ocsp: Host and port too long");
+		RWDEBUG("(TLS) ocsp: Host and port too long");
 		goto skipped;
 	}
 	snprintf(hostheader, sizeof(hostheader), "%s:%s", host, port);
@@ -2038,15 +2831,15 @@ ocsp_end:
 		vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET);
 		vp->vp_integer = 2;	/* skipped */
 		if (conf->ocsp_softfail) {
-			RWDEBUG("ocsp: Unable to check certificate, assuming it's valid");
-			RWDEBUG("ocsp: This may be insecure");
+			RWDEBUG("(TLS) ocsp: Unable to check certificate, assuming it's valid");
+			RWDEBUG("(TLS) ocsp: This may be insecure");
 
 			/* Remove OpenSSL errors from queue or handshake will fail */
 			while (ERR_get_error());
 
 			ocsp_status = OCSP_STATUS_SKIPPED;
 		} else {
-			REDEBUG("ocsp: Unable to check certificate, failing");
+			REDEBUG("(TLS) ocsp: Unable to check certificate, failing");
 			ocsp_status = OCSP_STATUS_FAILED;
 		}
 		break;
@@ -2054,7 +2847,7 @@ ocsp_end:
 	default:
 		vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET);
 		vp->vp_integer = 0;	/* no */
-		REDEBUG("ocsp: Certificate has been expired/revoked");
+		REDEBUG("(TLS) ocsp: Certificate has been expired/revoked");
 		break;
 	}
 
@@ -2087,6 +2880,10 @@ static char const *cert_attr_names[9][2] = {
 #define FR_TLS_SAN_UPN          (7)
 #define FR_TLS_VALID_SINCE	(8)
 
+static const char *cert_names[2] = {
+	"client", "server",
+};
+
 /*
  *	Before trusting a certificate, you must make sure that the
  *	certificate is 'valid'. There are several steps that your
@@ -2183,8 +2980,8 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 	buf[0] = '\0';
 	sn = X509_get_serialNumber(client_cert);
 
-	RDEBUG2("TLS - Creating attributes from certificate OIDs");
-	RINDENT();
+	RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup]);
+ 	RINDENT();
 
 	/*
 	 *	For this next bit, we create the attributes *only* if
@@ -2328,8 +3125,14 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 
 	if (!my_ok) {
 		char const *p = X509_verify_cert_error_string(err);
-		RERROR("SSL says error %d : %s", err, p);
+		RERROR("(TLS) OpenSSL says error %d : %s", err, p);
 		REXDENT();
+
+		/*
+		 *	Copy certs even on failure so that they can be logged.
+		 */
+		if (certs && request) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs));
+
 		return my_ok;
 	}
 
@@ -2405,7 +3208,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 				fr_bin2hex(value + 2, srcp, asn1len);
 			}
 
-
 			vp = fr_pair_make(talloc_ctx, certs, attribute, value, T_OP_ADD);
 			if (!vp) {
 				RDEBUG3("Skipping %s += '%s'.  Please check that both the "
@@ -2446,20 +3248,28 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 
 	switch (X509_STORE_CTX_get_error(ctx)) {
 	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
-		RERROR("issuer=%s", issuer);
+		RERROR("(TLS) unable to get issuer certificate for issuer=%s", issuer);
 		break;
 
 	case X509_V_ERR_CERT_NOT_YET_VALID:
+		RERROR("(TLS) Failed with certificate not yet valid.");
+		break;
+
 	case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
-		RERROR("notBefore=");
+		RERROR("(TLS) Failed with error in certificate 'not before' field.");
 #if 0
 		ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert));
 #endif
 		break;
 
 	case X509_V_ERR_CERT_HAS_EXPIRED:
+		RERROR("(TLS) Failed with certificate has expired.");
+		break;
+
 	case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
-		RERROR("notAfter=");
+		RERROR("(TLS) Failed with err in certificate 'no after' field..");
+		break;
+
 #if 0
 		ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert));
 #endif
@@ -2471,12 +3281,49 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 	 *	checks.
 	 */
 	if (depth == 0) {
+		tls_session_t *ssn = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_SSN);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+		STACK_OF(X509)* untrusted = NULL;
+#endif
+
+		rad_assert(ssn != NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+		/*
+		 *	See if there are any untrusted certificates.
+		 *	If so, complain about them.
+		 */
+		untrusted = X509_STORE_CTX_get0_untrusted(ctx);
+		if (untrusted) {
+			if (conf->disallow_untrusted || RDEBUG_ENABLED2) {
+				int  i;
+
+				WARN("Certificate chain - %i cert(s) untrusted",
+				     X509_STORE_CTX_get_num_untrusted(ctx));
+				for (i = sk_X509_num(untrusted); i > 0 ; i--) {
+					X509 *this_cert = sk_X509_value(untrusted, i - 1);
+
+					X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject));
+					subject[sizeof(subject) - 1] = '\0';
+
+					WARN("(TLS) untrusted certificate with depth [%i] subject name %s",
+					     i - 1, subject);
+				}
+			}
+
+			if (conf->disallow_untrusted) {
+				AUTH(LOG_PREFIX ": There are untrusted certificates in the certificate chain.  Rejecting.");
+				my_ok = 0;
+			}
+		}
+#endif
+
 		/*
 		 *	If the conf tells us to, check cert issuer
 		 *	against the specified value and fail
 		 *	verification if they don't match.
 		 */
-		if (conf->check_cert_issuer &&
+		if (my_ok && conf->check_cert_issuer &&
 		    (strcmp(issuer, conf->check_cert_issuer) != 0)) {
 			AUTH(LOG_PREFIX ": Certificate issuer (%s) does not match specified value (%s)!",
 			     issuer, conf->check_cert_issuer);
@@ -2595,45 +3442,54 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
 			unlink(filename);
 			break;
 		}
+
+		/*
+		 *	Track that we've verified the client certificate.
+		 */
+		ssn->client_cert_ok = (my_ok == 1);
 	} /* depth == 0 */
 
+	/*
+	 *	Copy certs to request even on failure, so that the
+	 *	user can log them.
+	 */
 	if (certs && request && !my_ok) {
 		fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs));
 	}
 
 	if (RDEBUG_ENABLED3) {
-		RDEBUG3("chain-depth   : %d", depth);
-		RDEBUG3("error         : %d", err);
+		RDEBUG3("(TLS) chain-depth   : %d", depth);
+		RDEBUG3("(TLS) error         : %d", err);
 
 		if (identity) RDEBUG3("identity      : %s", *identity);
-		RDEBUG3("common name   : %s", common_name);
-		RDEBUG3("subject       : %s", subject);
-		RDEBUG3("issuer        : %s", issuer);
-		RDEBUG3("verify return : %d", my_ok);
+		RDEBUG3("(TLS) common name   : %s", common_name);
+		RDEBUG3("(TLS) subject       : %s", subject);
+		RDEBUG3("(TLS) issuer        : %s", issuer);
+		RDEBUG3("(TLS) verify return : %d", my_ok);
 	}
 
 	return (my_ok != 0);
 }
 
 
-#ifdef HAVE_OPENSSL_OCSP_H
 /*
- * 	Create Global X509 revocation store and use it to verify
- * 	OCSP responses
+ * 	Configure a X509 CA store to verify OCSP or client repsonses
  *
  * 	- Load the trusted CAs
  * 	- Load the trusted issuer certificates
+ *	- Configure CRLs check if needed
  */
-static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf)
+X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf)
 {
-	X509_STORE *store = NULL;
+	X509_STORE *store = X509_STORE_new();
 
-	store = X509_STORE_new();
+	if (store == NULL) return NULL;
 
 	/* Load the CAs we trust */
 	if (conf->ca_file || conf->ca_path)
 		if (!X509_STORE_load_locations(store, conf->ca_file, conf->ca_path)) {
 			tls_error_log(NULL, "Error reading Trusted root CA list \"%s\"", conf->ca_file);
+			X509_STORE_free(store);
 			return NULL;
 		}
 
@@ -2647,36 +3503,58 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf)
 #endif
 	return store;
 }
-#endif	/* HAVE_OPENSSL_OCSP_H */
 
 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
 #ifndef OPENSSL_NO_ECDH
 static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use)
 {
-	int      nid;
-	EC_KEY  *ecdh;
+	if (!disable_single_dh_use) {
+		SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
+	}
 
-	if (!ecdh_curve || !*ecdh_curve) return 0;
+	if (!ecdh_curve) return 0;
 
-	nid = OBJ_sn2nid(ecdh_curve);
-	if (!nid) {
-		ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
-		return -1;
-	}
+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
+	/*
+	 *	A colon-separated list of curves.
+	 */
+	if (*ecdh_curve) {
+		char *list;
 
-	ecdh = EC_KEY_new_by_curve_name(nid);
-	if (!ecdh) {
-		ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve);
-		return -1;
+		memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */
+
+		if (SSL_CTX_set1_curves_list(ctx, list) == 0) {
+			ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
+			return -1;
+		}
 	}
 
-	SSL_CTX_set_tmp_ecdh(ctx, ecdh);
+	(void) SSL_CTX_set_ecdh_auto(ctx, 1);
+#else
+	/*
+	 *	Use APIs for older versions of OpenSSL.
+	 */
+	{
+		int      nid;
+		EC_KEY  *ecdh;
+
+		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;
+		}
+
+		SSL_CTX_set_tmp_ecdh(ctx, ecdh);
 
-	if (!disable_single_dh_use) {
-		SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
+		EC_KEY_free(ecdh);
 	}
-
-	EC_KEY_free(ecdh);
+#endif
 
 	return 0;
 }
@@ -2708,9 +3586,31 @@ int tls_global_init(bool spawn_flag, bool check)
 	 *	and we don't want to have tls.c depend on globals.
 	 */
 	if (spawn_flag && !check && (tls_mutexes_init() < 0)) {
-		ERROR("FATAL: Failed to set up SSL mutexes");
+		ERROR("(TLS) FATAL: Failed to set up SSL mutexes");
+		return -1;
+	}
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	/*
+	 *	Load the default provider for most algorithms
+	 */
+	openssl_default_provider = OSSL_PROVIDER_load(NULL, "default");
+	if (!openssl_default_provider) {
+		ERROR("(TLS) Failed loading default provider");
+		return -1;
+	}
+
+	/*
+	 *	Needed for MD4
+	 *
+	 *	https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms
+	 */
+	openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
+	if (!openssl_legacy_provider) {
+		ERROR("(TLS) Failed loading legacy provider");
 		return -1;
 	}
+#endif
 
 	return 0;
 }
@@ -2777,6 +3677,19 @@ void tls_global_cleanup(void)
 #ifndef OPENSSL_NO_ENGINE
 	ENGINE_cleanup();
 #endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) {
+		ERROR("Failed unloading default provider");
+	}
+	openssl_default_provider = NULL;
+
+	if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) {
+		ERROR("Failed unloading legacy provider");
+	}
+	openssl_legacy_provider = NULL;
+#endif
+
 	CONF_modules_unload(1);
 	ERR_free_strings();
 	EVP_cleanup();
@@ -2797,9 +3710,6 @@ static const FR_NAME_NUMBER version2int[] = {
 #endif
 #ifdef TLS1_3_VERSION
 	{ "1.3",    TLS1_3_VERSION },
-#endif
-#ifdef TLS1_4_VERSION
-	{ "1.4",    TLS1_4_VERSION },
 #endif
 	{ NULL, 0 }
 };
@@ -2816,18 +3726,18 @@ static const FR_NAME_NUMBER version2int[] = {
  * - Load the Private key & the certificate
  * - Set the Context options & Verify options
  */
-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
+SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file)
 {
 	SSL_CTX		*ctx;
 	X509_STORE	*certstore;
 	int		verify_mode = SSL_VERIFY_NONE;
-	int		ctx_options = 0;
-	int		ctx_tls_versions = 0;
+	int		ctx_options = 0, ctx_available = 0;
 	int		type;
 #ifdef CHECK_FOR_PSK_CERTS
 	bool		psk_and_certs = false;
 #endif
-	bool		insecure_tls_version = false;
+	int		min_version;
+	int		max_version;
 
 	/*
 	 *	SHA256 is in all versions of OpenSSL, but isn't
@@ -2840,7 +3750,7 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
 
 	ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods".  Idiots. */
 	if (!ctx) {
-		tls_error_log(NULL, "Failed creating TLS context");
+		tls_error_log(NULL, "Failed creating OpenSSL context");
 		return NULL;
 	}
 
@@ -3033,39 +3943,55 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client)
 	 *	the cert chain needs to be given in PEM from
 	 *	openSSL.org
 	 */
-	if (!conf->certificate_file) goto load_ca;
+	if (!chain_file) chain_file = conf->certificate_file;
+	if (!chain_file) goto load_ca;
 
 	if (type == SSL_FILETYPE_PEM) {
-		if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) {
+		if (!(SSL_CTX_use_certificate_chain_file(ctx, chain_file))) {
 			tls_error_log(NULL, "Failed reading certificate file \"%s\"",
-				      conf->certificate_file);
+				      chain_file);
 			return NULL;
 		}
 
-	} else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) {
+	} else if (!(SSL_CTX_use_certificate_file(ctx, chain_file, type))) {
 		tls_error_log(NULL, "Failed reading certificate file \"%s\"",
-			      conf->certificate_file);
+			      chain_file);
 		return NULL;
 	}
 
-	/* Load the CAs we trust */
 load_ca:
+	/*
+	 *	Load the CAs we trust and configure CRL checks if needed
+	 */
 #if defined(X509_V_FLAG_PARTIAL_CHAIN)
 	X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN);
 #endif
 	if (conf->ca_file || conf->ca_path) {
-		if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) {
-			tls_error_log(NULL, "Failed reading Trusted root CA list \"%s\"",
-				      conf->ca_file);
-			return NULL;
-		}
+		if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL;
+		SSL_CTX_set_cert_store(ctx, certstore);
 	}
+
 	if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file));
 
-	if (conf->private_key_file) {
-		if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) {
+	conf->ca_path_last_reload = time(NULL);
+	conf->old_x509_store = NULL;
+
+	/*
+	 * Disable reloading of cert store if we're not using CA path
+	 */
+	if (!conf->ca_path) conf->ca_path_reload_interval = 0;
+
+	if (conf->ca_path_reload_interval > 0 && conf->ca_path_reload_interval < 300) {
+		DEBUG2("ca_path_reload_interval is set too low, reset it to 300");
+		conf->ca_path_reload_interval = 300;
+	}
+
+	/* Load private key */
+	if (!private_key_file) private_key_file = conf->private_key_file;
+	if (private_key_file) {
+		if (!(SSL_CTX_use_PrivateKey_file(ctx, private_key_file, type))) {
 			tls_error_log(NULL, "Failed reading private key file \"%s\"",
-				      conf->private_key_file);
+				      private_key_file);
 			return NULL;
 		}
 
@@ -3088,6 +4014,18 @@ post_ca:
 	ctx_options |= SSL_OP_NO_SSLv2;
 	ctx_options |= SSL_OP_NO_SSLv3;
 
+	/*
+	 *	If set then dummy Change Cipher Spec (CCS) messages are sent in
+	 *	TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2
+	 *	so that middleboxes that do not understand TLSv1.3 will not drop
+	 *	the connection. This isn't needed for EAP-TLS, so we disable it.
+	 *
+	 *	EAP (hopefully) does not have middlebox deployments
+	 */
+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+	ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT;
+#endif
+
 	/*
 	 *	SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0
 	 *
@@ -3095,168 +4033,213 @@ post_ca:
 	 *	below, so we don't need to check for them explicitly.
 	 *
 	 *	TLS1_3_VERSION is available in OpenSSL 1.1.1.
-	 *
-	 *	TLS1_4_VERSION in speculative.
 	 */
-	{
-		int min_version = 0;
-		int max_version = 0;
 
+	/*
+	 *	Get the max version from the configuration files.
+	 */
+	if (conf->tls_max_version && *conf->tls_max_version) {
+		max_version = fr_str2int(version2int, conf->tls_max_version, 0);
+		if (!max_version) {
+			ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version);
+			return NULL;
+		}
+	} else {
 		/*
-		 *	Get the max version.
+		 *	Pick the maximum version available at compile
+		 *	time.
 		 */
-		if (conf->tls_max_version && *conf->tls_max_version) {
-			max_version = fr_str2int(version2int, conf->tls_max_version, 0);
-			if (!max_version) {
-				ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version);
-				return NULL;
-			}
-		} else {
-			/*
-			 *	Pick the maximum one we know about.
-			 */
-#ifdef TLS1_4_VERSION
-			max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */
-#elif defined(TLS1_3_VERSION)
-			max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */
+#if defined(TLS1_3_VERSION)
+		max_version = TLS1_2_VERSION; /* yes, we only use TLS 1.3 if it's EXPLICITELY ENABLED */
 #elif defined(TLS1_2_VERSION)
-			max_version = TLS1_2_VERSION;
+		max_version = TLS1_2_VERSION;
 #elif defined(TLS1_1_VERSION)
-			max_version = TLS1_1_VERSION;
+		max_version = TLS1_1_VERSION;
 #else
-			max_version = TLS1_VERSION;
+		max_version = TLS1_VERSION;
 #endif
-		}
+	}
 
+	/*
+	 *	Get the min version from the configuration files.
+	 */
+	if (conf->tls_min_version && *conf->tls_min_version) {
+		min_version = fr_str2int(version2int, conf->tls_min_version, 0);
+		if (!min_version) {
+			ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version);
+			return NULL;
+		}
+	} else {
 		/*
-		 *	Set these for the rest of the code.
+		 *	Allow TLS 1.0.  It is horribly insecure, but
+		 *	some systems still use it.
 		 */
+		min_version = TLS1_VERSION;
+	}
+
+	/*
+	 *	Compare the two.
+	 */
+	if ((min_version > max_version) || (max_version < min_version)) {
+		ERROR("tls_min_version '%s' must be <= tls_max_version '%s'",
+		      conf->tls_min_version, conf->tls_max_version);
+		return NULL;
+	}
+
+#ifdef CHECK_FOR_PSK_CERTS
+	/*
+	 *	Disable TLS 1.3 when using PSKs and certs.
+	 *	This doesn't work.
+	 *
+	 *	It's best to disable the offending
+	 *	configuration and warn about it.  The
+	 *	alternative is to have the admin wonder why it
+	 *	doesn't work.
+	 *
+	 *	Note that the admin can over-ride this by
+	 *	setting "min_version = max_version = 1.3"
+	 */
+	if (psk_and_certs &&
+	    (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) {
+		max_version = TLS1_2_VERSION;
+		radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously.  This is not supported by the standards.");
+	}
+#endif
+
+	/*
+	 *	No one should be using TLS 1.0 or TLS 1.1 any more
+	 *
+	 *	If TLS1.2 isn't defined by OpenSSL, then we _know_
+	 *	it's an insecure version of OpenSSL.
+	 */
 #ifdef TLS1_2_VERSION
-		if (max_version < TLS1_2_VERSION) {
-			conf->disable_tlsv1_2 = true;
-		}
+	if (max_version < TLS1_2_VERSION)
 #endif
-#ifdef TLS1_1_VERSION
-		if (max_version < TLS1_1_VERSION) {
-			conf->disable_tlsv1_1 = true;
+	{
+		if (rad_debug_lvl) {
+			WARN(LOG_PREFIX ": The configuration allows TLS 1.0 and/or TLS 1.1.  We STRONGLY recommned using only TLS 1.2 for security");
+			WARN(LOG_PREFIX ": Please set: tls_min_version = '1.2'");
 		}
-#endif
+	}
 
-		/*
-		 *	Get the min version.
-		 */
-		if (conf->tls_min_version && *conf->tls_min_version) {
-			min_version = fr_str2int(version2int, conf->tls_min_version, 0);
-			if (!min_version) {
-				ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version);
-				return NULL;
-			}
-		} else {
-			min_version = TLS1_VERSION;
+#ifdef SSL_OP_NO_TLSv1
+	/*
+	 *	Check min / max against the old-style "disable" flag.
+	 */
+	if (conf->disable_tlsv1) {
+		if (min_version == TLS1_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'min_version = 1.0'.  These cannot both be true.");
+			return NULL;
 		}
-
-		/*
-		 *	Compare the two.
-		 */
-		if (min_version > max_version) {
-			ERROR("tls_min_version '%s' must be <= tls_max_version '%s'",
-			      conf->tls_min_version, conf->tls_max_version);
+		if (max_version == TLS1_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'max_version = 1.0'.  These cannot both be true.");
 			return NULL;
 		}
+		ctx_options |= SSL_OP_NO_TLSv1;
+	}
 
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-#ifdef CHECK_FOR_PSK_CERTS
-		/*
-		 *	Disable TLS 1.3 when using PSKs and certs.
-		 *	This doesn't work.
-		 *
-		 *	It's best to disable the offending
-		 *	configuration and warn about it.  The
-		 *	alternative is to have the admin wonder why it
-		 *	doesn't work.
-		 *
-		 *	Note that the admin can over-ride this by
-		 *	setting "min_version = max_version = 1.3"
-		 */
-		if (psk_and_certs &&
-		    (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) {
-			max_version = TLS1_2_VERSION;
-			radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously.  This is not supported by the standards.");
-		}
+	if (min_version > TLS1_VERSION) ctx_options |= SSL_OP_NO_TLSv1;
+
+	ctx_available |= SSL_OP_NO_TLSv1;
 #endif
 
-		if (!SSL_CTX_set_max_proto_version(ctx, max_version)) {
-			ERROR("Failed setting TLS maximum version");
+#ifdef SSL_OP_NO_TLSv1_1
+	/*
+	 *	Check min / max against the old-style "disable" flag.
+	 */
+	if (conf->disable_tlsv1_1) {
+		if (min_version <= TLS1_1_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'min_version <= 1.1'.  These cannot both be true.");
 			return NULL;
 		}
-
-		if (!SSL_CTX_set_min_proto_version(ctx, min_version)) {
-			ERROR("Failed setting TLS minimum version");
+		if (max_version == TLS1_1_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.1'.  These cannot both be true.");
 			return NULL;
 		}
+		ctx_options |= SSL_OP_NO_TLSv1_1;
+	}
 
-		/*
-		 *	No one should be using TLS 1.0 or TLS 1.1 any more
-		 */
-		if (min_version < TLS1_2_VERSION) insecure_tls_version = true;
-#else  /* OpenSSL version < 1.1.0 */
+	if (min_version > TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1;
+	if (max_version < TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1;
 
-#ifdef SSL_OP_NO_TLSv1
-		insecure_tls_version |= (conf->disable_tlsv1 == false);
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
-		insecure_tls_version |= (conf->disable_tlsv1_1 == false);
+	ctx_available |= SSL_OP_NO_TLSv1_1;
 #endif
-#endif	/* OpenSSL version ? 1.1.0 */
 
-		if (rad_debug_lvl && insecure_tls_version) {
-			WARN("The configuration allows TLS 1.0 and/or TLS 1.1.  We STRONGLY recommned using only TLS 1.2 for security");
-			WARN("Please set: tls_min_version = \"1.2\"");
+#ifdef SSL_OP_NO_TLSv1_2
+	/*
+	 *	Check min / max against the old-style "disable" flag.
+	 */
+	if (conf->disable_tlsv1_2) {
+		if (min_version <= TLS1_2_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'.  These cannot both be true.");
+			return NULL;
 		}
+		if (max_version == TLS1_2_VERSION) {
+			ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'.  These cannot both be true.");
+			return NULL;
+		}
+		ctx_options |= SSL_OP_NO_TLSv1_2;
 	}
+	ctx_available |= SSL_OP_NO_TLSv1_2;
+
+	if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
+	if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_3
+	ctx_available |= SSL_OP_NO_TLSv1_3;
+	if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
+	if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
+#endif
 
 	/*
-	 *	For historical config compatibility, we also allow
-	 *	these, but complain if the admin uses them.
+	 *	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.
 	 */
-#ifdef SSL_OP_NO_TLSv1
-	if (conf->disable_tlsv1) {
-		ctx_options |= SSL_OP_NO_TLSv1;
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-		WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1");
-#endif
+	if ((min_version <= TLS1_1_VERSION) && 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\"");
 	}
 
-	ctx_tls_versions |= SSL_OP_NO_TLSv1;
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
-	if (conf->disable_tlsv1_1) {
-		ctx_options |= SSL_OP_NO_TLSv1_1;
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
-		WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2");
-#endif
+	if (conf->disable_tlsv1) {
+		WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1'");
+	}
+	if (conf->disable_tlsv1_1) {
+		WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_1'");
 	}
-
-	ctx_tls_versions |= SSL_OP_NO_TLSv1_1;
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
-
 	if (conf->disable_tlsv1_2) {
-		ctx_options |= SSL_OP_NO_TLSv1_2;
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-		WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2");
-#endif
+		WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_2'");
 	}
 
-	ctx_tls_versions |= SSL_OP_NO_TLSv1_2;
+	ctx_options &= ~(ctx_available); /* clear these flags, as they're not needed. */
 
-#endif
+	if (!SSL_CTX_set_max_proto_version(ctx, max_version)) {
+		ERROR("Failed setting TLS maximum version");
+		return NULL;
+	}
+
+	if (!SSL_CTX_set_min_proto_version(ctx, min_version)) {
+		ERROR("Failed setting TLS minimum version");
+		return NULL;
+	}
+#endif	/* OpenSSL version < 1.1.0 */
 
-	if ((ctx_options & ctx_tls_versions) == ctx_tls_versions) {
+	if ((ctx_options & ctx_available) == ctx_available) {
 		ERROR(LOG_PREFIX ": You have disabled all available TLS versions.  EAP will not work");
 		return NULL;
 	}
 
+	/*
+	 *	Cache min / max TLS version so that we can
+	 *	programatically disable TLS 1.3 for TTLS, PEAP, and
+	 *	FAST.
+	 */
+	conf->min_version = min_version;
+	conf->max_version = max_version;
+
 #ifdef SSL_OP_NO_TICKET
 	ctx_options |= SSL_OP_NO_TICKET;
 #endif
@@ -3291,6 +4274,19 @@ post_ca:
 
 	SSL_CTX_set_options(ctx, ctx_options);
 
+	/*
+	 *	TLS 1.3 introduces the concept of early data (also known as zero
+	 *	round trip data or 0-RTT data). Early data allows a client to send
+	 *	data to a server in the first round trip of a connection, without
+	 *	waiting for the TLS handshake to complete if the client has spoken
+	 *	to the same server recently. This doesn't work for EAP, so we
+	 *	disable early data.
+	 *
+	 */
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+	SSL_CTX_set_max_early_data(ctx, 0);
+#endif
+
 	/*
 	 *	TODO: Set the RSA & DH
 	 *	SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa);
@@ -3336,12 +4332,21 @@ post_ca:
 		/*
 		 *	Cache sessions on disk if requested.
 		 */
-		if (conf->session_cache_path) {
+		if (conf->session_cache_path && *conf->session_cache_path) {
 			SSL_CTX_sess_set_new_cb(ctx, cbtls_new_session);
 			SSL_CTX_sess_set_get_cb(ctx, cbtls_get_session);
 			SSL_CTX_sess_set_remove_cb(ctx, cbtls_remove_session);
 		}
 
+		/*
+		 *	Or run the cache through a virtual server.
+		 */
+		if (conf->session_cache_server && *conf->session_cache_server) {
+			SSL_CTX_sess_set_new_cb(ctx, cbtls_cache_save);
+			SSL_CTX_sess_set_get_cb(ctx, cbtls_cache_load);
+			SSL_CTX_sess_set_remove_cb(ctx, cbtls_cache_clear);
+		}
+
 		SSL_CTX_set_quiet_shutdown(ctx, 1);
 		if (fr_tls_ex_index_vps < 0)
 			fr_tls_ex_index_vps = SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL);
@@ -3359,6 +4364,17 @@ post_ca:
 		}
 		X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
 
+#ifdef X509_V_FLAG_USE_DELTAS
+		/*
+		 *	If set, delta CRLs (if present) are used to
+		 *	determine certificate status. If not set
+		 *	deltas are ignored.
+		 *
+		 *	So it's safe to always set this flag.
+		 */
+		X509_STORE_set_flags(certstore, X509_V_FLAG_USE_DELTAS);
+#endif
+
 #ifdef X509_V_FLAG_CRL_CHECK_ALL
 		if (conf->check_all_crl)
 			X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK_ALL);
@@ -3424,9 +4440,9 @@ post_ca:
 					       (unsigned int) strlen(conf->session_context_id));
 
 		/*
-		 *	Our timeout is in hours, this is in seconds.
+		 *	Our lifetime is in hours, this is in seconds.
 		 */
-		SSL_CTX_set_timeout(ctx, conf->session_timeout * 3600);
+		SSL_CTX_set_timeout(ctx, conf->session_lifetime * 3600);
 
 		/*
 		 *	Set the maximum number of entries in the
@@ -3468,11 +4484,15 @@ static int _tls_server_conf_free(fr_tls_server_conf_t *conf)
 
 	if (conf->cache_ht) fr_hash_table_free(conf->cache_ht);
 
+	pthread_mutex_destroy(&conf->mutex);
+
 #ifdef HAVE_OPENSSL_OCSP_H
 	if (conf->ocsp_store) X509_STORE_free(conf->ocsp_store);
 	conf->ocsp_store = NULL;
 #endif
 
+	if (conf->realms) fr_hash_table_free(conf->realms);
+
 #ifndef NDEBUG
 	memset(conf, 0, sizeof(*conf));
 #endif
@@ -3505,9 +4525,105 @@ static int store_cmp(void const *a, void const *b)
 	DICT_ATTR const *one = a;
 	DICT_ATTR const *two = b;
 
-	return one - two;
+	return (one < two) - (one > two);
+}
+
+static uint32_t realm_hash(void const *data)
+{
+	fr_realm_ctx_t const *r = data;
+
+	return fr_hash_string(r->name);
+}
+
+static int realm_cmp(void const *a, void const *b)
+{
+	fr_realm_ctx_t const *one = a;
+	fr_realm_ctx_t const *two = b;
+
+	return strcmp(one->name, two->name);
+}
+
+static void realm_free(void *data)
+{
+	fr_realm_ctx_t *r = data;
+
+	SSL_CTX_free(r->ctx);
+}
+
+static int tls_realms_load(fr_tls_server_conf_t *conf)
+{
+	fr_hash_table_t *ht;
+	DIR		*dir;
+	struct dirent	*dp;
+	char		buffer[PATH_MAX];
+	char		buffer2[PATH_MAX];
+
+	ht = fr_hash_table_create(realm_hash, realm_cmp, realm_free);
+	if (!ht) return -1;
+
+	dir = opendir(conf->realm_dir);
+	if (!dir) {
+		ERROR("Error reading directory %s: %s", conf->realm_dir, fr_syserror(errno));
+	error:
+		fr_hash_table_free(ht);
+		return -1;
+	}
+
+	/*
+	 *	Read only the PEM files
+	 */
+	while ((dp = readdir(dir)) != NULL) {
+		char *p;
+		struct stat stat_buf;
+		SSL_CTX *ctx;
+		fr_realm_ctx_t *r;
+		char const *private_key_file = buffer;
+
+		if (dp->d_name[0] == '.') continue;
+
+		p = strrchr(dp->d_name, '.');
+		if (!p) continue;
+
+		if (memcmp(p, ".pem", 5) != 0) continue; /* must END in .pem */
+
+		snprintf(buffer, sizeof(buffer), "%s/%s", conf->realm_dir, dp->d_name); /* ignore directories */
+		if ((stat(buffer, &stat_buf) != 0) ||
+		    S_ISDIR(stat_buf.st_mode)) continue;
+
+		strcpy(buffer2, buffer);
+		p = strchr(buffer2, '.'); /* which must be there... */
+		if (!p) continue;
+
+		/*
+		 *	If there's a key file, then use that.
+		 *	Otherwise assume that the private key is in
+		 *	the chain file.
+		 */
+		strcpy(p, ".key");
+		if (stat(buffer2, &stat_buf) != 0) private_key_file = buffer2;
+
+		ctx = tls_init_ctx(conf, 1, buffer, private_key_file);
+		if (!ctx) goto error;
+
+		r = talloc_zero(conf, fr_realm_ctx_t);
+		if (!r) {
+			SSL_CTX_free(ctx);
+			goto error;
+		}
+
+		r->name = talloc_strdup(r, buffer);
+		r->ctx = ctx;
+
+		if (fr_hash_table_insert(ht, r) < 0) {
+			ERROR("Failed inserting certificate file %s into hash table", buffer);
+			goto error;
+		}
+	}
+
+	return 0;
 }
 
+
 fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
 {
 	fr_tls_server_conf_t *conf;
@@ -3535,6 +4651,16 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
 	 */
 	if (conf->fragment_size < 100) conf->fragment_size = 100;
 
+	/*
+	 *	Disallow sessions of more than 7 days, as per RFC
+	 *	8446.
+	 *
+	 *	Note that we also enforce this on TLS 1.2, etc.
+	 *	Because there's just no reason to have month-long TLS
+	 *	sessions.
+	 */
+	if (conf->session_lifetime > (7 * 24)) conf->session_lifetime = 7 * 24;
+
 	/*
 	 *	Only check for certificate things if we don't have a
 	 *	PSK query.
@@ -3563,10 +4689,15 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs)
 		}
 	}
 
+	/*
+	 *	Initialize configuration mutex
+	 */
+	pthread_mutex_init(&conf->mutex, NULL);
+
 	/*
 	 *	Initialize TLS
 	 */
-	conf->ctx = tls_init_ctx(conf, 0);
+	conf->ctx = tls_init_ctx(conf, 0, NULL, NULL);
 	if (conf->ctx == NULL) {
 		goto error;
 	}
@@ -3633,10 +4764,12 @@ skip_list:
 	 * 	Initialize OCSP Revocation Store
 	 */
 	if (conf->ocsp_enable) {
-		conf->ocsp_store = init_revocation_store(conf);
+		conf->ocsp_store = fr_init_x509_store(conf);
 		if (conf->ocsp_store == NULL) goto error;
 	}
 #endif /*HAVE_OPENSSL_OCSP_H*/
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
 	{
 		char *dh_file;
 
@@ -3645,6 +4778,9 @@ skip_list:
 			goto error;
 		}
 	}
+#else
+	if (!SSL_CTX_set_dh_auto(conf->ctx, 1)) goto error;
+#endif
 
 	if (conf->verify_tmp_dir) {
 		if (chmod(conf->verify_tmp_dir, S_IRWXU) < 0) {
@@ -3663,12 +4799,17 @@ skip_list:
 	/*
 	 *	OpenSSL 1.0.1f and 1.0.1g get the MS-MPPE keys wrong.
 	 */
-#if (OPENSSL_VERSION_NUMBER >= 0x10010060L) && (OPENSSL_VERSION_NUMBER < 0x10010060L)
+#if (OPENSSL_VERSION_NUMBER >= 0x1010106L) && (OPENSSL_VERSION_NUMBER <= 0x1010107L)
 	conf->disable_tlsv1_2 = true;
 	WARN(LOG_PREFIX ": Disabling TLSv1.2 due to OpenSSL bugs");
 #endif
 #endif
 
+	/*
+	 *	Load certificates and private keys from the realm directory.
+	 */
+	if (conf->realm_dir && (tls_realms_load(conf) < 0)) goto error;
+
 	/*
 	 *	Cache conf in cs in case we're asked to parse this again.
 	 */
@@ -3703,11 +4844,12 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs)
 	/*
 	 *	Initialize TLS
 	 */
-	conf->ctx = tls_init_ctx(conf, 1);
+	conf->ctx = tls_init_ctx(conf, 1, NULL, NULL);
 	if (conf->ctx == NULL) {
 		goto error;
 	}
 
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
 	{
 		char *dh_file;
 
@@ -3716,6 +4858,9 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs)
 			goto error;
 		}
 	}
+#else
+	if (!SSL_CTX_set_dh_auto(conf->ctx, 1)) goto error;
+#endif
 
 	cf_data_add(cs, "tls-conf", conf, NULL);
 
@@ -3755,7 +4900,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 		 *	not allowed,
 		 */
 		if (SSL_session_reused(ssn->ssl)) {
-			RDEBUG("Forcibly stopping session resumption as it is not allowed");
+			RDEBUG("(TLS) cache - Forcibly stopping session resumption as it is administratively disabled.");
 			return -1;
 		}
 
@@ -3763,12 +4908,14 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 	 *	Else resumption IS allowed, so we store the
 	 *	user data in the cache.
 	 */
-	} else if (!SSL_session_reused(ssn->ssl)) {
+	} else if ((!SSL_session_reused(ssn->ssl)) || ssn->session_not_resumed) {
 		VALUE_PAIR **certs;
 		char buffer[2 * MAX_SESSION_SIZE + 1];
 
 		tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
 
+		RDEBUG("(TLS) cache - Setting up attributes for session resumption");
+
 		vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_USER_NAME, 0, TAG_ANY);
 		if (vp) fr_pair_add(&vps, vp);
 
@@ -3778,6 +4925,9 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 		vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_STRIPPED_USER_DOMAIN, 0, TAG_ANY);
 		if (vp) fr_pair_add(&vps, vp);
 
+		vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
+		if (vp) fr_pair_add(&vps, vp);
+
 		vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_CHARGEABLE_USER_IDENTITY, 0, TAG_ANY);
 		if (vp) fr_pair_add(&vps, vp);
 
@@ -3836,7 +4986,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 				if (vp) {
 					if ((request->timestamp + vp->vp_integer) > expires) {
 						vp->vp_integer = expires - request->timestamp;
-						RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration",
+						RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration",
 							 vp->vp_integer);
 					}
 				}
@@ -3858,7 +5008,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 					 FR_DIR_SEP, buffer);
 				vp_file = fopen(filename, "w");
 				if (vp_file == NULL) {
-					RWDEBUG("Could not write session VPs to persistent cache: %s",
+					RWDEBUG("(TLS) Could not write session VPs to persistent cache: %s",
 						fr_syserror(errno));
 				} else {
 					VALUE_PAIR *prev = NULL;
@@ -3889,6 +5039,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 					fprintf(vp_file, "\n");
 					fclose(vp_file);
 				}
+
+			} else if (conf->session_cache_server) {
+				cbtls_cache_save_vps(ssn->ssl, ssn->ssl_session, vps);
+
 			} else {
 				RDEBUG("Failed to find 'persist_dir' in TLS configuration.  Session will not be cached on disk.");
 			}
@@ -3901,15 +5055,27 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 	 *	Else the session WAS allowed.  Copy the cached reply.
 	 */
 	} else {
-		char buffer[2 * MAX_SESSION_SIZE + 1];
-
-		tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
+		RDEBUG("(TLS) cache - Refreshing entry for session resumption");
 
 		/*
 		 *	The "restore VPs from OpenSSL cache" code is
 		 *	now in eaptls_process()
 		 */
 		if (conf->session_cache_path) {
+			char buffer[2 * MAX_SESSION_SIZE + 1];
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+#ifdef TLS1_3_VERSION
+			/*
+			 *	OpenSSL frees the underlying session out from
+			 *	under us in TLS 1.3.
+			 */
+			if (ssn->info.version == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl);
+#endif
+#endif
+
+			tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE);
+
 			/* "touch" the cached session/vp file */
 			char filename[3 * MAX_SESSION_SIZE + 1];
 
@@ -3921,6 +5087,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request)
 			utime(filename, NULL);
 		}
 
+		if (conf->session_cache_server) {
+			cbtls_cache_refresh(ssn->ssl, ssn->ssl_session);
+		}
+
 		/*
 		 *	Mark the request as resumed.
 		 */
@@ -3975,27 +5145,30 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request)
 	if (err <= 0) {
 		int code;
 
-		RDEBUG("SSL_read Error");
+		RDEBUG3("SSL_read Error");
 
 		code = SSL_get_error(ssn->ssl, err);
 		switch (code) {
 		case SSL_ERROR_WANT_READ:
-			RDEBUG("Error in fragmentation logic: SSL_WANT_READ");
+			RDEBUG("(TLS) OpenSSL says that it needs to read more data.");
 			return FR_TLS_MORE_FRAGMENTS;
 
 		case SSL_ERROR_WANT_WRITE:
-			RDEBUG("Error in fragmentation logic: SSL_WANT_WRITE");
+			RDEBUG("(TLS) Error in fragmentation logic: SSL_WANT_WRITE");
 			return FR_TLS_FAIL;
 
 		case SSL_ERROR_NONE:
-			RDEBUG2("No application data received.  Assuming handshake is continuing...");
+			RDEBUG2("(TLS) No application data received.  Assuming handshake is continuing...");
 			err = 0;
 			break;
 
+		case SSL_ERROR_ZERO_RETURN:
+			RDEBUG2("(TLS) Other end closed the TLS tunnel.");
+			return FR_TLS_FAIL;
+
 		default:
-			REDEBUG("Error in fragmentation logic");
-			tls_error_io_log(request, ssn, err,
-					 "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)");
+			REDEBUG("(TLS) Error in fragmentation logic - code %d", code);
+			tls_error_io_log(request, ssn, err, "Failed reading application data from OpenSSL");
 			return FR_TLS_FAIL;
 		}
 	}
@@ -4026,27 +5199,27 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request)
 fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
 {
 	if (ssn == NULL){
-		REDEBUG("Unexpected ACK received:  No ongoing SSL session");
+		REDEBUG("(TLS) Unexpected ACK received:  No ongoing SSL session");
 		return FR_TLS_INVALID;
 	}
 	if (!ssn->info.initialized) {
-		RDEBUG("No SSL info available.  Waiting for more SSL data");
+		RDEBUG("(TLS) No SSL info available.  Waiting for more SSL data");
 		return FR_TLS_REQUEST;
 	}
 
 	if ((ssn->info.content_type == handshake) && (ssn->info.origin == 0)) {
-		REDEBUG("Unexpected ACK received:  We sent no previous messages");
+		REDEBUG("(TLS) Unexpected ACK received:  We sent no previous messages");
 		return FR_TLS_INVALID;
 	}
 
 	switch (ssn->info.content_type) {
 	case alert:
-		RDEBUG2("Peer ACKed our alert");
+		RDEBUG2("(TLS) Peer ACKed our alert");
 		return FR_TLS_FAIL;
 
 	case handshake:
 		if ((ssn->is_init_finished) && (ssn->dirty_out.used == 0)) {
-			RDEBUG2("Peer ACKed our handshake fragment.  handshake is finished");
+			RDEBUG2("(TLS) Peer ACKed our handshake fragment.  handshake is finished");
 
 			/*
 			 *	From now on all the content is
@@ -4057,12 +5230,12 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
 			return FR_TLS_SUCCESS;
 		} /* else more data to send */
 
-		RDEBUG2("Peer ACKed our handshake fragment");
+		RDEBUG2("(TLS) Peer ACKed our handshake fragment");
 		/* Fragmentation handler, send next fragment */
 		return FR_TLS_REQUEST;
 
 	case application_data:
-		RDEBUG2("Peer ACKed our application data fragment");
+		RDEBUG2("(TLS) Peer ACKed our application data fragment");
 		return FR_TLS_REQUEST;
 
 		/*
@@ -4070,7 +5243,7 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request)
 		 *	to the default section below.
 		 */
 	default:
-		REDEBUG("Invalid ACK received: %d", ssn->info.content_type);
+		REDEBUG("(TLS) Invalid ACK received: %d", ssn->info.content_type);
 		return FR_TLS_INVALID;
 	}
 }
diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c
index 0eed87b64f..c846507492 100644
--- a/src/main/tls_listen.c
+++ b/src/main/tls_listen.c
@@ -81,7 +81,7 @@ static void tls_socket_close(rad_listen_t *listener)
 	/*
 	 *	Tell the event handler that an FD has disappeared.
 	 */
-	DEBUG("Client has closed connection");
+	DEBUG("(TLS) Client has closed connection");
 	radius_update_listener(listener);
 
 	/*
@@ -102,11 +102,11 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re
 	p = sock->ssn->dirty_out.data;
 
 	while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) {
-		RDEBUG3("Writing to socket %d", request->packet->sockfd);
-		rcode = write(request->packet->sockfd, p,
+		RDEBUG3("(TLS) Writing to socket %d", listener->fd);
+		rcode = write(listener->fd, p,
 			      (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p);
 		if (rcode <= 0) {
-			RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno));
+			RDEBUG("(TLS) Error writing to socket: %s", fr_syserror(errno));
 
 			tls_socket_close(listener);
 			return 0;
@@ -118,8 +118,6 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re
 
 	return 1;
 }
-
-
 static int tls_socket_recv(rad_listen_t *listener)
 {
 	bool doing_init = false;
@@ -165,7 +163,7 @@ static int tls_socket_recv(rad_listen_t *listener)
 		rad_assert(sock->ssn == NULL);
 
 		sock->ssn = tls_new_session(sock, listener->tls, sock->request,
-					    listener->tls->require_client_cert);
+					    listener->tls->require_client_cert, true);
 		if (!sock->ssn) {
 			TALLOC_FREE(sock->request);
 			sock->packet = NULL;
@@ -175,6 +173,7 @@ static int tls_socket_recv(rad_listen_t *listener)
 		SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request);
 		SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs);
 		SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock);
+		sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */
 
 		doing_init = true;
 	}
@@ -186,7 +185,18 @@ static int tls_socket_recv(rad_listen_t *listener)
 
 	request = sock->request;
 
-	RDEBUG3("Reading from socket %d", request->packet->sockfd);
+	if (sock->state == LISTEN_TLS_SETUP) {
+		RDEBUG3("(TLS) Setting connection state to RUNNING");
+		sock->state = LISTEN_TLS_RUNNING;
+
+		if (sock->ssn->clean_out.used < 20) {
+			goto get_application_data;
+		}
+
+		goto read_application_data;
+	}
+
+	RDEBUG3("(TLS) Reading from socket %d", request->packet->sockfd);
 	PTHREAD_MUTEX_LOCK(&sock->mutex);
 
 	/*
@@ -195,9 +205,9 @@ static int tls_socket_recv(rad_listen_t *listener)
 	 *	the socket.
 	 */
 	if (SSL_pending(sock->ssn->ssl)) {
-		RDEBUG3("Reading pending buffered data");
+		RDEBUG3("(TLS) Reading pending buffered data");
 		sock->ssn->dirty_in.used = 0;
-		goto get_application_data;
+		goto check_for_setup;
 	}
 
 	rcode = read(request->packet->sockfd,
@@ -205,14 +215,14 @@ static int tls_socket_recv(rad_listen_t *listener)
 		     sizeof(sock->ssn->dirty_in.data));
 	if ((rcode < 0) && (errno == ECONNRESET)) {
 	do_close:
-		DEBUG("Closing TLS socket from client port %u", sock->other_port);
+		DEBUG("(TLS) Closing socket from client port %u", sock->other_port);
 		tls_socket_close(listener);
 		PTHREAD_MUTEX_UNLOCK(&sock->mutex);
 		return 0;
 	}
 
 	if (rcode < 0) {
-		RDEBUG("Error reading TLS socket: %s", fr_syserror(errno));
+		RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno));
 		goto do_close;
 	}
 
@@ -222,23 +232,23 @@ static int tls_socket_recv(rad_listen_t *listener)
 	if (rcode == 0) goto do_close;
 
 	sock->ssn->dirty_in.used = rcode;
-
 	dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used);
 
 	/*
 	 *	Catch attempts to use non-SSL.
 	 */
 	if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) {
-		RDEBUG("Non-TLS data sent to TLS socket: closing");
+		RDEBUG("(TLS) Non-TLS data sent to TLS socket: closing");
 		goto do_close;
 	}
 
 	/*
 	 *	If we need to do more initialization, do that here.
 	 */
+check_for_setup:
 	if (!sock->ssn->is_init_finished) {
 		if (!tls_handshake_recv(request, sock->ssn)) {
-			RDEBUG("FAILED in TLS handshake receive");
+			RDEBUG("(TLS) Failed in TLS handshake receive");
 			goto do_close;
 		}
 
@@ -252,10 +262,49 @@ static int tls_socket_recv(rad_listen_t *listener)
 		}
 
 		/*
-		 *	FIXME: Run the request through a virtual
-		 *	server in order to see if we like the
-		 *	certificate presented by the client.
+		 *	Else we MUST be finished the SSL setup.
+		 */
+	}
+
+	/*
+	 *	Run the request through a virtual server in
+	 *	order to see if we like the certificate
+	 *	presented by the client.
+	 */
+	if (sock->state == LISTEN_TLS_INIT) {
+		if (!SSL_is_init_finished(sock->ssn->ssl)) {
+			RDEBUG("(TLS) OpenSSL says that the TLS session is still negotiating, but there's no more data to send!");
+			goto do_close;
+		}
+
+		sock->ssn->is_init_finished = true;
+		if (!listener->check_client_connections) {
+			sock->state = LISTEN_TLS_RUNNING;
+			goto get_application_data;
+		}
+
+		request->packet->vps = fr_pair_list_copy(request->packet, sock->certs);
+
+		/*
+		 *	Fake out a Status-Server packet, which
+		 *	does NOT have a Message-Authenticator,
+		 *	or any other contents.
+		 */
+		request->packet->code = PW_CODE_STATUS_SERVER;
+		request->packet->data = talloc_zero_array(request->packet, uint8_t, 20);
+		request->packet->data[0] = PW_CODE_STATUS_SERVER;
+		request->packet->data[3] = 20;
+		sock->state = LISTEN_TLS_CHECKING;
+		PTHREAD_MUTEX_UNLOCK(&sock->mutex);
+
+		/*
+		 *	Don't read from the socket until the request
+		 *	returns.
 		 */
+		listener->status = RAD_LISTEN_STATUS_PAUSE;
+		radius_update_listener(listener);
+
+		return 1;
 	}
 
 	/*
@@ -263,7 +312,7 @@ static int tls_socket_recv(rad_listen_t *listener)
 	 */
 get_application_data:
 	status = tls_application_data(sock->ssn, request);
-	RDEBUG("Application data status %d", status);
+	RDEBUG3("(TLS) Application data status %d", status);
 
 	if (status == FR_TLS_MORE_FRAGMENTS) {
 		PTHREAD_MUTEX_UNLOCK(&sock->mutex);
@@ -275,6 +324,16 @@ get_application_data:
 		return 0;
 	}
 
+	/*
+	 *	Hold application data if we're not yet in the RUNNING
+	 *	state.
+	 */
+	if (sock->state != LISTEN_TLS_RUNNING) {
+		RDEBUG3("(TLS) Holding application data until setup is complete");
+		return 0;
+	}
+
+read_application_data:
 	/*
 	 *	We now have a bunch of application data.
 	 */
@@ -286,7 +345,7 @@ get_application_data:
 	 */
 	if ((sock->ssn->clean_out.used < 20) ||
 	    (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) {
-		RDEBUG("Received bad packet: Length %zd contents %d",
+		RDEBUG("(TLS) Received bad packet: Length %zd contents %d",
 		       sock->ssn->clean_out.used,
 		       (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]);
 		goto do_close;
@@ -301,7 +360,7 @@ get_application_data:
 
 	if (!rad_packet_ok(packet, 0, NULL)) {
 		if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
-		DEBUG("Closing TLS socket from client");
+		DEBUG("(TLS) Closing TLS socket from client");
 		PTHREAD_MUTEX_LOCK(&sock->mutex);
 		tls_socket_close(listener);
 		PTHREAD_MUTEX_UNLOCK(&sock->mutex);
@@ -315,7 +374,7 @@ get_application_data:
 		char host_ipaddr[128];
 
 		if (is_radius_code(packet->code)) {
-			RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d",
+			RDEBUG("(TLS): %s packet from host %s port %d, id=%d, length=%d",
 			       fr_packet_codes[packet->code],
 			       inet_ntop(packet->src_ipaddr.af,
 					 &packet->src_ipaddr.ipaddr,
@@ -323,7 +382,7 @@ get_application_data:
 			       packet->src_port,
 			       packet->id, (int) packet->data_len);
 		} else {
-			RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d",
+			RDEBUG("(TLS): Packet from host %s port %d code=%d, id=%d, length=%d",
 			       inet_ntop(packet->src_ipaddr.af,
 					 &packet->src_ipaddr.ipaddr,
 					 host_ipaddr, sizeof(host_ipaddr)),
@@ -359,6 +418,7 @@ redo:
 	rad_assert(client != NULL);
 
 	packet = talloc_steal(NULL, sock->packet);
+	sock->request->packet = NULL;
 	sock->packet = NULL;
 
 	/*
@@ -391,8 +451,26 @@ redo:
 		break;
 #endif
 
+#ifdef WITH_COA
+	case PW_CODE_COA_REQUEST:
+		if (listener->type != RAD_LISTEN_COA) goto bad_packet;
+		FR_STATS_INC(coa, total_requests);
+		fun = rad_coa_recv;
+		break;
+
+	case PW_CODE_DISCONNECT_REQUEST:
+		if (listener->type != RAD_LISTEN_COA) goto bad_packet;
+		FR_STATS_INC(dsc, total_requests);
+		fun = rad_coa_recv;
+		break;
+#endif
+
 	case PW_CODE_STATUS_SERVER:
-		if (!main_config.status_server) {
+		if (!main_config.status_server
+#ifdef WITH_TLS
+		    && !listener->check_client_connections
+#endif
+			) {
 			FR_STATS_INC(auth, total_unknown_types);
 			WARN("Ignoring Status-Server request due to security configuration");
 			rad_free(&packet);
@@ -405,7 +483,7 @@ redo:
 	bad_packet:
 		FR_STATS_INC(auth, total_unknown_types);
 
-		DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED",
+		DEBUG("(TLS) Invalid packet code %d sent from client %s port %d : IGNORED",
 		      packet->code, client->shortname, packet->src_port);
 		rad_free(&packet);
 		return 0;
@@ -432,7 +510,7 @@ redo:
 		int peek = SSL_peek(sock->ssn->ssl, buf, 1);
 
 		if (peek > 0) {
-			DEBUG("more TLS records after dual_tls_recv");
+			DEBUG("(TLS) more TLS records after dual_tls_recv");
 			goto redo;
 		}
 	}
@@ -455,6 +533,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request)
 
 	if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
 
+	/*
+	 *	See if the policies allowed this connection.
+	 */
+	if (sock->state == LISTEN_TLS_CHECKING) {
+		if (request->reply->code != PW_CODE_ACCESS_ACCEPT) {
+			listener->status = RAD_LISTEN_STATUS_EOL;
+			listener->tls = NULL; /* parent owns this! */
+
+			/*
+			 *	Tell the event handler that an FD has disappeared.
+			 */
+			radius_update_listener(listener);
+			return 0;
+		}
+
+		/*
+		 *	Resume reading from the listener.
+		 */
+		listener->status = RAD_LISTEN_STATUS_RESUME;
+		radius_update_listener(listener);
+
+		rad_assert(sock->request->packet != request->packet);
+
+		sock->state = LISTEN_TLS_SETUP;
+		(void) dual_tls_recv(listener);
+		return 0;
+	}
+
 	/*
 	 *	Accounting reject's are silently dropped.
 	 *
@@ -727,6 +833,15 @@ int proxy_tls_recv(rad_listen_t *listener)
 		break;
 #endif
 
+#ifdef WITH_COA
+	case PW_CODE_COA_ACK:
+	case PW_CODE_COA_NAK:
+	case PW_CODE_DISCONNECT_ACK:
+	case PW_CODE_DISCONNECT_NAK:
+		break;
+
+#endif
+
 	default:
 		/*
 		 *	FIXME: Update MIB for packet types?
@@ -765,8 +880,8 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request)
 	 *	if there's no packet, encode it here.
 	 */
 	if (!request->proxy->data) {
-		request->proxy_listener->encode(request->proxy_listener,
-						request);
+		request->proxy_listener->proxy_encode(request->proxy_listener,
+						      request);
 	}
 
 	if (!sock->ssn->connected) {
diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c
index 9fed166e62..1cd73ff246 100644
--- a/src/modules/proto_dhcp/rlm_dhcp.c
+++ b/src/modules/proto_dhcp/rlm_dhcp.c
@@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request,
 			decoded++;
 		}
 
-		fr_pair_list_move(request->packet, &(request->packet->vps), &head);
+		fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD);
 
 		/* Free any unmoved pairs */
 		fr_pair_list_free(&head);
diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c
index 83e7252fa8..76c38c6d3f 100644
--- a/src/modules/rlm_eap/libeap/eap_tls.c
+++ b/src/modules/rlm_eap/libeap/eap_tls.c
@@ -1,3 +1,4 @@
+
 /*
  * eap_tls.c
  *
@@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
  *
  *	Fragment length is Framed-MTU - 4.
  */
-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert)
+tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13)
 {
 	tls_session_t	*ssn;
 	REQUEST		*request = handler->request;
@@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_
 	 *	in Opaque.  So that we can use these data structures
 	 *	when we get the response
 	 */
-	ssn = tls_new_session(handler, tls_conf, request, client_cert);
+	ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13);
 	if (!ssn) {
 		return NULL;
 	}
@@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag)
  */
 int eaptls_success(eap_handler_t *handler, int peap_flag)
 {
-	EAPTLS_PACKET	reply;
+	EAPTLS_PACKET reply;
 	REQUEST *request = handler->request;
 	tls_session_t *tls_session = handler->opaque;
 
@@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag)
 	/*
 	 *	Automatically generate MPPE keying material.
 	 */
-	if (tls_session->prf_label) {
-		eaptls_gen_mppe_keys(handler->request,
-				     tls_session->ssl, tls_session->prf_label);
+	if (tls_session->label) {
+		uint8_t const *context = NULL;
+		size_t context_size = 0;
+#ifdef TLS1_3_VERSION
+		uint8_t const context_tls13[] = { handler->type };
+#endif
+
+		switch (tls_session->info.version) {
+#ifdef TLS1_3_VERSION
+		case TLS1_3_VERSION:
+			context = context_tls13;
+			context_size = sizeof(context_tls13);
+			tls_session->label = "EXPORTER_EAP_TLS_Key_Material";
+			break;
+#endif
+		case TLS1_2_VERSION:
+		case TLS1_1_VERSION:
+		case TLS1_VERSION:
+			break;
+		case SSL2_VERSION:
+		case SSL3_VERSION:
+		default:
+			/* Should never happen */
+			rad_assert(0);
+			return 0;
+			break;
+		}
+		eaptls_gen_mppe_keys(request,
+				     tls_session->ssl, tls_session->label,
+				     context, context_size);
 	} else if (handler->type != PW_EAP_FAST) {
-		RWDEBUG("Not adding MPPE keys because there is no PRF label");
+		RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label");
 	}
 
-	eaptls_gen_eap_key(handler->request->reply, tls_session->ssl,
-			   handler->type);
+	eaptls_gen_eap_key(handler);
+
 	return 1;
 }
 
@@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag)
 	EAPTLS_PACKET 	reply;
 	REQUEST		*request = handler->request;
 
-	RDEBUG2("ACKing Peer's TLS record fragment");
+	RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data.");
 	reply.code = FR_TLS_ACK;
 	reply.length = TLS_HEADER_LEN + 1/*flags*/;
 	reply.flags = peap_flag;
@@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 		/*
 		 *	First output the flags (for debugging)
 		 */
-		RDEBUG3("Peer sent flags %c%c%c",
+		RDEBUG3("(TLS) EAP Peer sent flags %c%c%c",
 			TLS_START(eaptls_packet->flags) ? 'S' : '-',
 			TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-',
 			TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-');
@@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 		if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) {
 			return tls_ack_handler(handler->opaque, request);
 		} else {
-			REDEBUG("Received Invalid TLS ACK");
+			REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection");
 			return FR_TLS_INVALID;
 		}
 	}
@@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 	 *	We send TLS_START, but do not receive it.
 	 */
 	if (TLS_START(eaptls_packet->flags)) {
-		REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)");
+		REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)");
 		return FR_TLS_INVALID;
 	}
 
@@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 		size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3];
 
 		if (frag_len > total_len) {
-			RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len,
+			RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len,
 				total_len);
 		}
 
-		RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len);
+		RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len);
 		if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
 			/*
 			 *	The supplicant is free to send fragments of wildly varying
@@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 			 *	as they won't contain the length field.
 			 */
 			if (frag_len + 4) {	/* check for wrap, else clang scan gets excited */
-				RDEBUG2("Expecting %i TLS record fragments",
+				RDEBUG2("(TLS) EAP Expecting %i fragments",
 					(int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1));
 			}
 
@@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 			 */
 			if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) ||
 			    !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) {
-			    	RDEBUG2("Got first TLS record fragment (%zu bytes).  Peer indicated more fragments "
-			    		"to follow", frag_len);
+			    	RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes).  Peer says more fragments "
+			    		"will follow", frag_len);
 			    	tls_session->tls_record_in_total_len = total_len;
 			    	tls_session->tls_record_in_recvd_len = frag_len;
 
 				return FR_TLS_FIRST_FRAGMENT;
 			}
 
-			RDEBUG2("Got additional TLS record fragment with length (%zu bytes).  "
-				"Peer indicated more fragments to follow", frag_len);
+			RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes).  "
+				"Peer says more fragments will follow", frag_len);
 
 			/*
 			 *	Check we've not exceeded the originally indicated TLS record size.
 			 */
 			tls_session->tls_record_in_recvd_len += frag_len;
 			if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
-				RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds "
-					"total TLS record length (%zu bytes)", frag_len, total_len);
+				RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
+					"total data length (%zu bytes)", frag_len, total_len);
 			}
 
 			return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH;
@@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 		 *	value of the four octet TLS length field.
 		 */
 		if (total_len != frag_len) {
-			RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) "
-				"does not match EAP-TLS data length (%zu bytes)", total_len, frag_len);
+			RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) "
+				"does not match expected data length (%zu bytes)", total_len, frag_len);
 		}
 
 		tls_session->tls_record_in_total_len = total_len;
 		tls_session->tls_record_in_recvd_len = frag_len;
-		RDEBUG2("Got complete TLS record (%zu bytes)", frag_len);
+		RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len);
 		return FR_TLS_LENGTH_INCLUDED;
 	}
 
@@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
 	 *	this must be the final record fragment
 	 */
 	if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
-		RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len);
+		RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len);
 		tls_session->tls_record_in_recvd_len += frag_len;
 		if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) {
-			RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated "
-				"TLS record length (%zu bytes)",
+			RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected "
+				"expected data length (%zu bytes)",
 				tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
 		}
 	}
 
 	if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
-		RDEBUG2("Got additional TLS record fragment (%zu bytes).  Peer indicated more fragments to follow",
+		RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes).  Peer says more fragments will follow",
 			frag_len);
 		tls_session->tls_record_in_recvd_len += frag_len;
 		if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
-			RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds "
-				"indicated TLS record length (%zu bytes)",
+			RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
+				"expected length (%zu bytes)",
 				tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
 		}
 		return FR_TLS_MORE_FRAGMENTS;
@@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
 	 */
 	if (TLS_LENGTH_INCLUDED(tlspacket->flags) &&
 	    (tlspacket->length < 5)) { /* flags + TLS message length */
-		REDEBUG("Invalid EAP-TLS packet received:  Length bit is set, "
+		REDEBUG("(TLS) EAP Invalid packet received: Length bit is set,"
 			"but packet too short to contain length field");
 		talloc_free(tlspacket);
 		return NULL;
@@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
 		memcpy(&data_len, &eap_ds->response->type.data[1], 4);
 		data_len = ntohl(data_len);
 		if (data_len > MAX_RECORD_SIZE) {
-			REDEBUG("Reassembled TLS record will be %u bytes, "
-				"greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)",
+			REDEBUG("(TLS) EAP Reassembled data will be %u bytes, "
+				"greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)",
 				data_len);
 			talloc_free(tlspacket);
 			return NULL;
@@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
 	case FR_TLS_LENGTH_INCLUDED:
 	case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH:
 		if (tlspacket->length < 5) { /* flags + TLS message length */
-			REDEBUG("Invalid EAP-TLS packet received: Expected length, got none");
+			REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none");
 			talloc_free(tlspacket);
 			return NULL;
 		}
@@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st
 		break;
 
 	default:
-		REDEBUG("Invalid EAP-TLS packet received");
+		REDEBUG("(TLS) EAP Invalid packet received");
 		talloc_free(tlspacket);
 		return NULL;
 	}
@@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
 	 *	is required then send another request.
 	 */
 	if (!tls_handshake_recv(handler->request, tls_session)) {
-		REDEBUG("TLS receive handshake failed during operation");
+		REDEBUG("(TLS) EAP Receive handshake failed during operation");
 		tls_fail(tls_session);
 		return FR_TLS_FAIL;
 	}
 
+#ifdef TLS1_3_VERSION
+	/*
+	 *	https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5
+	 *
+	 *	We need to signal the other end that TLS negotiation
+	 *	is done.  We can't send a zero-length application data
+	 *	message, so we send application data which is one byte
+	 *	of zero.
+	 *
+	 *	Note this is only done for when there is no application
+	 *	data to be sent. So this is done always for EAP-TLS but
+	 *	notibly not for PEAP even on resumption.
+	 */
+	if ((tls_session->info.version == TLS1_3_VERSION) &&
+	    (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) {
+		if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) {
+			tls_session->authentication_success = true;
+
+			RDEBUG("(TLS) EAP Sending final Commitment Message.");
+			tls_session->record_plus(&tls_session->clean_in, "\0", 1);
+		}
+
+		tls_handshake_send(request, tls_session);
+	}
+#endif
+
 	/*
 	 *	FIXME: return success/fail.
 	 *
@@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h
 	/*
 	 *	If there is no data to send i.e
 	 *	dirty_out.used <=0 and if the SSL
-	 *	handshake is finished, then return a
-	 *	EPTLS_SUCCESS
+	 *	handshake is finished.
 	 */
+	if (tls_session->is_init_finished) return FR_TLS_SUCCESS;
 
-	if (tls_session->is_init_finished) {
+	/*
+	 *	If session is established, skip round-trip and
+	 *	try to process any inner tunnel data if present.
+	 *
+	 *	This occurs for EAP-TTLS/PAP with TLSv1.3.
+	 */
+	if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) {
 		/*
-		 *	Init is finished.  The rest is
-		 *	application data.
+		 *	Don't set is_init_finished, as that causes the
+		 *	rest of the code to make too many assumptions.
 		 */
-		tls_session->info.content_type = application_data;
-		return FR_TLS_SUCCESS;
+		return FR_TLS_OK;
 	}
 
 	/*
 	 *	Who knows what happened...
 	 */
-	REDEBUG("TLS failed during operation");
+	REDEBUG("(TLS) Cannot continue, as the peer is misbehaving.");
 	return FR_TLS_FAIL;
 }
 
@@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 
 	if (!request) return FR_TLS_FAIL;
 
-	RDEBUG2("Continuing EAP-TLS");
+	RDEBUG3("(TLS) EAP Continuing ...");
 
 	SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request);
 
@@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 	 */
 	status = eaptls_verify(handler);
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
-		REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 
 	switch (status) {
@@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 	 *	data" phase.
 	 */
 	case FR_TLS_OK:
-		RDEBUG2("Done initial handshake");
+		RDEBUG2("(TLS) EAP Done initial handshake");
 
 	/*
 	 *	Get the rest of the fragments.
@@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 	 *	Extract the TLS packet from the buffer.
 	 */
 	if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) {
+		REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message");
 		status = FR_TLS_FAIL;
 		goto done;
 	}
@@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 	if (tlspacket->dlen !=
 	    (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
 		talloc_free(tlspacket);
-		REDEBUG("Exceeded maximum record size");
+		REDEBUG("(TLS) EAP Exceeded maximum record size");
 		status = FR_TLS_FAIL;
 		goto done;
 	}
@@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 			 *	Send the ACK.
 			 */
 			eaptls_send_ack(handler, tls_session->peap_flag);
-			RDEBUG2("Init is done, but tunneled data is fragmented");
+			RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented");
 			status = FR_TLS_HANDLED;
 			goto done;
 		}
@@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 
 		vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps);
 		if (!vps) {
-			RWDEBUG("No information in cached session %s", buffer);
+			RWDEBUG("(TLS) EAP No information in cached session %s", buffer);
 		} else {
 			vp_cursor_t cursor;
 			VALUE_PAIR *vp;
 			fr_tls_server_conf_t *conf;
 
-			RDEBUG("Adding cached attributes from session %s", buffer);
+			RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer);
 
 			conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF);
 			rad_assert(conf != NULL);
@@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler)
 						rdebug_pair(L_DBG_LVL_2, request, vp, "&request:");
 						fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp));
 					}
+
+				} else if ((vp->da->vendor == 0) &&
+					   (vp->da->attr == PW_EAP_TYPE)) {
+					/*
+					 *	EAP-Type gets added to
+					 *	the control list, so
+					 *	that we can sanity check it.
+					 */
+					rdebug_pair(L_DBG_LVL_2, request, vp, "&control:");
+					fr_pair_add(&request->config, fr_pair_copy(request, vp));
+
 				} else {
+
 					rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:");
 					fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
 				}
diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h
index 73c7fdd53b..8e5fc773d6 100644
--- a/src/modules/rlm_eap/libeap/eap_tls.h
+++ b/src/modules/rlm_eap/libeap/eap_tls.h
@@ -62,11 +62,11 @@ int	eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull);
 int	eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull);
 
 
-void			T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed,  unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6));
-void	eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label);
+void	T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed,  unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6));
+void	eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size);
 void	eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size);
-void	eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header);
-void			eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull);
+void	eaptls_gen_eap_key(eap_handler_t *handler);
+void	eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull);
 
 #define BUFFER_SIZE 1024
 
@@ -100,7 +100,7 @@ typedef struct tls_packet {
 /* EAP-TLS framework */
 EAPTLS_PACKET	*eaptls_alloc(void);
 void		eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr);
-tls_session_t	*eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert);
+tls_session_t	*eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13);
 int		eaptls_start(EAP_DS *eap_ds, int peap);
 int		eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply);
 
diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c
index 3a9e864104..e16756583c 100644
--- a/src/modules/rlm_eap/libeap/mppe_keys.c
+++ b/src/modules/rlm_eap/libeap/mppe_keys.c
@@ -26,11 +26,12 @@ RCSID("$Id$")
 USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include "eap_tls.h"
+#include <openssl/ssl.h>
 #include <openssl/hmac.h>
-
+#include <freeradius-devel/openssl3.h>
 
 /*
- * TLS PRF from RFC 2246
+ *	TLS P_hash from RFC 2246/5246 section 5
  */
 static void P_hash(EVP_MD const *evp_md,
 		   unsigned char const *secret, unsigned int secret_len,
@@ -38,8 +39,9 @@ static void P_hash(EVP_MD const *evp_md,
 		   unsigned char *out, unsigned int out_len)
 {
 	HMAC_CTX *ctx_a, *ctx_out;
-	unsigned char a[HMAC_MAX_MD_CBLOCK];
-	unsigned int size;
+	unsigned char a[EVP_MAX_MD_SIZE];
+	unsigned int size = EVP_MAX_MD_SIZE;
+	unsigned int digest_len;
 
 	ctx_a = HMAC_CTX_new();
 	ctx_out = HMAC_CTX_new();
@@ -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);
@@ -85,6 +88,38 @@ static void P_hash(EVP_MD const *evp_md,
 	memset(a, 0, sizeof(a));
 }
 
+/*
+ *	TLS PRF from RFC 2246 section 5
+ */
+static void PRF(unsigned char const *secret, unsigned int secret_len,
+		unsigned char const *seed,   unsigned int seed_len,
+		unsigned char *out, unsigned int out_len)
+{
+	uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)];
+	unsigned int i;
+
+	unsigned int len = (secret_len + 1) / 2;
+	uint8_t const *s1 = secret;
+	uint8_t const *s2 = secret + (secret_len - len);
+
+	P_hash(EVP_md5(),  s1, len, seed, seed_len, out, out_len);
+	P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len);
+
+	for (i = 0; i < out_len; i++) {
+		out[i] ^= buf[i];
+	}
+}
+
+/*
+ *	TLS 1.2 PRF from RFC 5246 section 5
+ */
+static void PRFv12(unsigned char const *secret, unsigned int secret_len,
+		   unsigned char const *seed,   unsigned int seed_len,
+		   unsigned char *out, unsigned int out_len)
+{
+	P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len);
+}
+
 /*  EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */
 void T_PRF(unsigned char const *secret, unsigned int secret_len,
 	   char const *prf_label,
@@ -128,60 +163,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len,
 	talloc_free(buf);
 }
 
-static void PRF(unsigned char const *secret, unsigned int secret_len,
-		unsigned char const *seed,   unsigned int seed_len,
-		unsigned char *out, unsigned char *buf, unsigned int out_len)
-{
-	unsigned int i;
-	unsigned int len = (secret_len + 1) / 2;
-	uint8_t const *s1 = secret;
-	uint8_t const *s2 = secret + (secret_len - len);
-
-	P_hash(EVP_md5(),  s1, len, seed, seed_len, out, out_len);
-	P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len);
-
-	for (i=0; i < out_len; i++) {
-		out[i] ^= buf[i];
-	}
-}
-
 #define EAPTLS_MPPE_KEY_LEN     32
 
 /*
  *	Generate keys according to RFC 2716 and add to reply
  */
-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
+void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size)
 {
 	uint8_t out[4 * EAPTLS_MPPE_KEY_LEN];
 	uint8_t *p;
-	size_t prf_size;
+	size_t len;
 
-	prf_size = strlen(prf_label);
+	len = strlen(label);
 
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
-	if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) {
+	if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) {
 		ERROR("Failed generating keying material");
 		return;
 	}
 #else
 	{
-		uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)];
+		uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)];
 		uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN];
 
 		p = seed;
 
-		memcpy(p, prf_label, prf_size);
-		p += prf_size;
+		memcpy(p, label, len);
+		p += len;
 
 		memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE);
 		p += SSL3_RANDOM_SIZE;
-		prf_size += SSL3_RANDOM_SIZE;
+		len += SSL3_RANDOM_SIZE;
 
 		memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE);
-		prf_size += SSL3_RANDOM_SIZE;
+		p += SSL3_RANDOM_SIZE;
+		len += SSL3_RANDOM_SIZE;
+
+		if (context) {
+			/* cloned and reversed FR_PUT_LE16 */
+			p[0] = ((uint16_t) (context_size)) >> 8;
+			p[1] = ((uint16_t) (context_size)) & 0xff;
+			p += 2;
+			len += 2;
+			memcpy(p, context, context_size);
+			p += context_size;
+			len += context_size;
+		}
 
 		PRF(s->session->master_key, s->session->master_key_length,
-		    seed, prf_size, out, buf, sizeof(out));
+		    seed, len, out, buf, sizeof(out));
 	}
 #endif
 
@@ -195,7 +225,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
 }
 
 
-#define FR_TLS_PRF_CHALLENGE	"ttls challenge"
+#define FR_TLS_PRF_CHALLENGE		"ttls challenge"
 
 /*
  *	Generate the TTLS challenge
@@ -206,9 +236,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label)
 void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size)
 {
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
-	SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE,
-				   sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0);
-
+	if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE,
+				       sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) {
+		ERROR("Failed generating keying material");
+	}
 #else
 	uint8_t out[32], buf[32];
 	uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE];
@@ -226,14 +257,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size)
 #endif
 }
 
+#define FR_TLS_EXPORTER_METHOD_ID	"EXPORTER_EAP_TLS_Method-Id"
+
 /*
  *	Actually generates EAP-Session-Id, which is an internal server
  *	attribute.  Not all systems want to send EAP-Key-Name.
  */
-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
+void eaptls_gen_eap_key(eap_handler_t *handler)
 {
+	RADIUS_PACKET *packet = handler->request->reply;
+	tls_session_t *tls_session = handler->opaque;
+	SSL *s = tls_session->ssl;
 	VALUE_PAIR *vp;
 	uint8_t *buff, *p;
+	uint8_t type = handler->type & 0xff;
 
 	vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0);
 	if (!vp) return;
@@ -241,11 +278,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
 	vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE;
 	buff = p = talloc_array(vp, uint8_t, vp->vp_length);
 
-	*p++ = header & 0xff;
+	*p++ = type;
 
-	SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE);
-	p += SSL3_RANDOM_SIZE;
-	SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE);
+	switch (tls_session->info.version) {
+	case TLS1_VERSION:
+	case TLS1_1_VERSION:
+	case TLS1_2_VERSION:
+		SSL_get_client_random(s, p, SSL3_RANDOM_SIZE);
+		p += SSL3_RANDOM_SIZE;
+		SSL_get_server_random(s, p, SSL3_RANDOM_SIZE);
+		break;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+#ifdef TLS1_3_VERSION
+	case TLS1_3_VERSION:
+#endif
+	default:
+	{
+		uint8_t const context[] = { type };
+
+		if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE,
+					       FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1,
+					       context, sizeof(context), 1) != 1) {
+			ERROR("Failed generating keying material");
+			return;
+		}
+	}
+#endif
+	}
 
 	vp->vp_octets = buff;
 	fr_pair_add(&packet->vps, vp);
@@ -254,7 +313,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header)
 /*
  *	Same as before, but for EAP-FAST the order of {server,client}_random is flipped
  */
-void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label)
+void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label)
 {
 	uint8_t *p;
 	size_t len, master_key_len;
@@ -273,7 +332,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_
 	p += SSL3_RANDOM_SIZE;
 
 	master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key));
-	PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size);
-}
-
 
+	if (version == TLS1_2_VERSION)
+		PRFv12(master_key, master_key_len, seed, p - seed, buffer, size);
+	else
+		PRF(master_key, master_key_len, seed, p - seed, buffer, size);
+}
diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c
index 553a6a6a57..cd504a8363 100644
--- a/src/modules/rlm_eap/radeapclient.c
+++ b/src/modules/rlm_eap/radeapclient.c
@@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port);
 static void rc_evprep_packet_timeout(rc_transaction_t *trans);
 static void rc_deallocate_id(rc_transaction_t *trans);
 
+/*
+ *	For cbtls_cache_*()
+ */
+rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request)
+{
+	return RLM_MODULE_FAIL;
+}
+
 
 static void NEVER_RETURNS usage(void)
 {
diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
index b0953aa1d4..8c63498586 100644
--- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
+++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
@@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl)
 		return -1;
 
 	RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d "
-		   "IV_len=%d", EVP_CIPHER_key_length(c), md_size,
-		   EVP_CIPHER_iv_length(c));
+		"IV_len=%d", EVP_CIPHER_key_length(c), md_size,
+		EVP_CIPHER_iv_length(c));
 	return 2 * (EVP_CIPHER_key_length(c) +
 		    md_size +
 		    EVP_CIPHER_iv_length(c));
 #else
 	const SSL_CIPHER *ssl_cipher;
 	int cipher, digest;
+	int mac_key_len, enc_key_len, fixed_iv_len;
 
 	ssl_cipher = SSL_get_current_cipher(ssl);
 	if (!ssl_cipher)
 		return -1;
 	cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher);
 	digest = SSL_CIPHER_get_digest_nid(ssl_cipher);
-	RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest);
+	RDEBUG3("OpenSSL: cipher nid %d digest nid %d",
+		cipher, digest);
 	if (cipher < 0 || digest < 0)
 		return -1;
+	if (cipher == NID_undef) {
+		RDEBUG3("OpenSSL: no cipher in use?!");
+		return -1;
+	}
 	c = EVP_get_cipherbynid(cipher);
-	h = EVP_get_digestbynid(digest);
-	if (!c || !h)
+	if (!c)
 		return -1;
+	enc_key_len = EVP_CIPHER_key_length(c);
+	if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE ||
+	    EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE)
+		fixed_iv_len = 4; /* only part of IV from PRF */
+	else
+		fixed_iv_len = EVP_CIPHER_iv_length(c);
+	if (digest == NID_undef) {
+		RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)");
+		mac_key_len = 0;
+	} else {
+		h = EVP_get_digestbynid(digest);
+		if (!h)
+			return -1;
+		mac_key_len = EVP_MD_size(h);
+	}
 
-	RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d",
-		   EVP_CIPHER_key_length(c), EVP_MD_size(h),
-		   EVP_CIPHER_iv_length(c));
-	return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) +
-		    EVP_CIPHER_iv_length(c));
+	RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d",
+		mac_key_len, enc_key_len, fixed_iv_len);
+	return 2 * (mac_key_len + enc_key_len + fixed_iv_len);
 #endif
 }
 
@@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
 {
 	eap_fast_tunnel_t *t = tls_session->opaque;
 	uint8_t *buf;
-	uint8_t *scratch;
 	size_t ksize;
 
 	RDEBUG2("Deriving EAP-FAST keys");
@@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
 	ksize = openssl_get_keyblock_size(request, tls_session->ssl);
 	rad_assert(ksize > 0);
 	buf = talloc_size(request, ksize + sizeof(*t->keyblock));
-	scratch = talloc_size(request, ksize + sizeof(*t->keyblock));
 
 	t->keyblock = talloc(t, eap_fast_keyblock_t);
 
-	eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
+	eap_fast_tls_gen_challenge(tls_session->ssl, tls_session->info.version, buf, ksize + sizeof(*t->keyblock), "key expansion");
 	memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
 	memset(buf, 0, ksize + sizeof(*t->keyblock));
 
@@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session)
 	t->imckc = 0;
 
 	talloc_free(buf);
-	talloc_free(scratch);
 }
 
 /**
@@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio
 	dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
 				    t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
 				    pac.opaque.data, pac.opaque.tag);
+	if (dlen < 0) return;
 
 	pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE);
 	pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
@@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session,
 	switch (reply->code) {
 	case PW_CODE_ACCESS_ACCEPT:
 		RDEBUG("Got tunneled Access-Accept");
+		tls_session->authentication_success = true;
 		rcode = RLM_MODULE_OK;
 
 		for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) {
@@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session)
 				t->mode = EAP_FAST_PROVISIONING_AUTH;
 			}
 
-			if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6)
+			/*
+			 *	Send a new pac at ~0.6 times the lifetime.
+			 */
+			if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) {
 				t->pac.send = true;
+			}
 		}
 
 		eap_fast_init_keys(request, tls_session);
diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
index 2ce2dd0c3b..7c91d34050 100644
--- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
+++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
@@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
 		return -1;
 	}
 
+#ifdef TLS1_3_VERSION
+	if (inst->tls_conf->min_version == TLS1_3_VERSION) {
+		ERROR("There are no standards for using TLS 1.3 with EAP-FAST.");
+		ERROR("You MUST enable TLS 1.2 for EAP-FAST to work.");
+		return -1;
+	}
+
+	if ((inst->tls_conf->max_version == TLS1_3_VERSION) ||
+	    (inst->tls_conf->min_version == TLS1_3_VERSION)) {
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+		WARN("!! There is no standard for using EAP-FAST with TLS 1.3");
+		WARN("!! Please set tls_max_version = \"1.2\"");
+		WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows");
+		WARN("!! This limitation is likely to change in late 2021.");
+		WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade");
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+	}
+#endif
+
 	rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH);
 	FR_MD5_CTX ctx;
 	fr_md5_init(&ctx);
@@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg)
 	DICT_ATTR const	*fast_da;
 	char const		*errmsg;
 	int			dlen, plen;
-	uint16_t		length;
+	int			length;
 	eap_fast_attr_pac_opaque_t const	*opaque = (eap_fast_attr_pac_opaque_t const *) data;
 	eap_fast_attr_pac_opaque_t		opaque_plaintext;
 
@@ -274,7 +293,7 @@ error:
 	 * so we have to use the length in the PAC-Opaque header
 	 */
 	length = ntohs(opaque->hdr.length);
-	if (len - sizeof(opaque->hdr) < length) {
+	if (len < (int) (length + sizeof(opaque->hdr))) {
 		errmsg = "PAC has bad length in header";
 		goto error;
 	}
@@ -293,7 +312,7 @@ error:
 	plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH,
 					(uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv,
 					(uint8_t *)&opaque_plaintext);
-	if (plen == -1) {
+	if (plen < 0) {
 		errmsg = "PAC failed to decrypt";
 		goto error;
 	}
@@ -314,8 +333,8 @@ error:
 			break;
 		case PAC_INFO_PAC_LIFETIME:
 			rad_assert(t->pac.expires == 0);
-			t->pac.expires = vp->vp_integer;
-			t->pac.expired = (vp->vp_integer <= time(NULL));
+			t->pac.expires = vp->vp_integer + time(NULL);
+			t->pac.expired = false;
 			break;
 		case PAC_INFO_PAC_KEY:
 			rad_assert(t->pac.key == NULL);
@@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 
 	/*
@@ -553,7 +572,12 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	} else {
 		client_cert = inst->req_client_cert;
 	}
-	handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert);
+
+	/*
+	 *	Don't allow TLS 1.3 for us, even if it's allowed
+	 *	elsewhere.
+	 */
+	handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false);
 
 	if (!tls_session) return 0;
 
@@ -566,16 +590,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 		}
 	}
 
-#ifdef SSL_OP_NO_TLSv1_2
-	/*
-	 *	Forcibly disable TLSv1.2
-	 *
-	 *	@fixme - TLSv1.2 uses a different PRF and
-	 *	SSL_export_keying_material("key expansion") is
-	 *	forbidden
-	 */
-	SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+	{
+		int i;
+		for (i = 0; ; i++) {
+			const char *cipher = SSL_get_cipher_list(tls_session->ssl, i);
+			if (!cipher) break;
+			if (!strstr(cipher, "ADH-")) continue;
+			RDEBUG("Setting security level to 0 to allow anonymous cipher suites");
+			SSL_set_security_level(tls_session->ssl, 0);
+			break;
+		}
+	}
 #endif
+
 #ifdef SSL_OP_NO_TLSv1_3
 	/*
 	 *	Forcibly disable TLSv1.3
diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
index deaf702d61..5647f613af 100644
--- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
+++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
@@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se
 	switch (reply->code) {
 	case PW_CODE_ACCESS_ACCEPT:
 		RDEBUG2("Tunneled authentication was successful");
+		tls_session->authentication_success = true;
 		t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
 		eappeap_success(handler, tls_session);
 		rcode = RLM_MODULE_HANDLED;
@@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
 			/* send an identity request */
 			t->session_resumption_state = PEAP_RESUMPTION_NO;
 			t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+			tls_session->session_not_resumed = true;
 			eappeap_identity(handler, tls_session);
 		}
 		return RLM_MODULE_HANDLED;
@@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
 
 		return RLM_MODULE_REJECT;
 
-		case PEAP_STATUS_PHASE2_INIT:
-			RDEBUG("In state machine in phase2 init?");
+	case PEAP_STATUS_PHASE2_INIT:
+		RDEBUG("In state machine in phase2 init?");
 
-		case PEAP_STATUS_PHASE2:
-			break;
+	case PEAP_STATUS_PHASE2:
+		break;
 
-		default:
-			REDEBUG("Unhandled state in peap");
-			return RLM_MODULE_REJECT;
+	default:
+		REDEBUG("Unhandled state in peap");
+		return RLM_MODULE_REJECT;
 	}
 
 	fake = request_alloc_fake(request);
@@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
 
 		if (vp) {
 			eap_tunnel_data_t *tunnel;
+			bool proxy_as_eap = t->proxy_tunneled_request_as_eap;
+			VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY);
+
+			if (flag) proxy_as_eap = flag->vp_integer;
 
 			/*
 			 *	The tunneled request was NOT handled,
@@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session,
 			 *	Once the tunneled EAP session is ALMOST
 			 *	done, THEN we proxy it...
 			 */
-			if (!t->proxy_tunneled_request_as_eap) {
+			if (!proxy_as_eap) {
 				fake->options |= RAD_REQUEST_OPTION_PROXY_EAP;
 
 				/*
diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
index 3d23322663..4bbf57330f 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,6 +135,25 @@ 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;
 }
 
@@ -192,7 +211,11 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 		client_cert = inst->req_client_cert;
 	}
 
-	ssn = eaptls_session(handler, inst->tls_conf, client_cert);
+	/*
+	 *	Don't allow TLS 1.3 for us, even if it's allowed
+	 *	elsewhere.
+	 */
+	ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic);
 	if (!ssn) {
 		return 0;
 	}
@@ -200,9 +223,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	handler->opaque = ((void *)ssn);
 
 	/*
-	 *	Set up type-specific information.
+	 *	Set the label to a fixed string.  For TLS 1.3, the
+	 *	label is the same for all TLS-based EAP methods.  If
+	 *	the client is using TLS 1.3, then eaptls_success()
+	 *	will over-ride this label with the correct label for
+	 *	TLS 1.3.
 	 */
-	ssn->prf_label = "client EAP encryption";
+	ssn->label = "client EAP encryption";
 
 	/*
 	 *	As it is a poorly designed protocol, PEAP uses
@@ -230,7 +257,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 	if (status == 0) return 0;
 
@@ -274,7 +301,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 
 	/*
@@ -311,6 +338,10 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	 *	data.
 	 */
 	case FR_TLS_OK:
+                /*
+                 *	TLSv1.3 makes application data immediately avaliable
+                 */
+		if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED;
 		break;
 
 		/*
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h
new file mode 100644
index 0000000000..b717dd51b3
--- /dev/null
+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h
@@ -0,0 +1,190 @@
+/*
+ * Helper functions for constant time operations
+ * Copyright (c) 2019, The Linux Foundation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ *
+ * These helper functions can be used to implement logic that needs to minimize
+ * externally visible differences in execution path by avoiding use of branches,
+ * avoiding early termination or other time differences, and forcing same memory
+ * access pattern regardless of values.
+ */
+
+#ifndef CONST_TIME_H
+#define CONST_TIME_H
+
+
+#if defined(__clang__)
+#define NO_UBSAN_UINT_OVERFLOW                                        \
+	__attribute__((no_sanitize("unsigned-integer-overflow")))
+#else
+#define NO_UBSAN_UINT_OVERFLOW
+#endif
+
+/**
+ * const_time_fill_msb - Fill all bits with MSB value
+ * @param	val Input value
+ * @return	Value with all the bits set to the MSB of the input val
+ */
+static inline unsigned int const_time_fill_msb(unsigned int val)
+{
+	/* Move the MSB to LSB and multiple by -1 to fill in all bits. */
+	return (val >> (sizeof(val) * 8 - 1)) * ~0U;
+}
+
+
+/* @return	-1 if val is zero; 0 if val is not zero */
+static inline unsigned int const_time_is_zero(unsigned int val)
+	NO_UBSAN_UINT_OVERFLOW
+{
+	/* Set MSB to 1 for 0 and fill rest of bits with the MSB value */
+	return const_time_fill_msb(~val & (val - 1));
+}
+
+
+/* @return	-1 if a == b; 0 if a != b */
+static inline unsigned int const_time_eq(unsigned int a, unsigned int b)
+{
+	return const_time_is_zero(a ^ b);
+}
+
+
+/* @return	-1 if a == b; 0 if a != b */
+static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b)
+{
+	return (unsigned char) const_time_eq(a, b);
+}
+
+
+/**
+ * const_time_eq_bin - Constant time memory comparison
+ * @param	a First buffer to compare
+ * @param	b Second buffer to compare
+ * @param	len Number of octets to compare
+ * @return	-1 if buffers are equal, 0 if not
+ *
+ * This function is meant for comparing passwords or hash values where
+ * difference in execution time or memory access pattern could provide external
+ * observer information about the location of the difference in the memory
+ * buffers. The return value does not behave like memcmp(), i.e.,
+ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike
+ * memcmp(), the execution time of const_time_eq_bin() does not depend on the
+ * contents of the compared memory buffers, but only on the total compared
+ * length.
+ */
+static inline unsigned int const_time_eq_bin(const void *a, const void *b,
+					     size_t len)
+{
+	const unsigned char *aa = a;
+	const unsigned char *bb = b;
+	size_t i;
+	unsigned char res = 0;
+
+	for (i = 0; i < len; i++)
+		res |= aa[i] ^ bb[i];
+
+	return const_time_is_zero(res);
+}
+
+
+/**
+ * const_time_select - Constant time unsigned int selection
+ * @param	mask 0 (false) or -1 (true) to identify which value to select
+ * @param	true_val Value to select for the true case
+ * @param	false_val Value to select for the false case
+ * @return	true_val if mask == -1, false_val if mask == 0
+ */
+static inline unsigned int const_time_select(unsigned int mask,
+					     unsigned int true_val,
+					     unsigned int false_val)
+{
+	return (mask & true_val) | (~mask & false_val);
+}
+
+
+/**
+ * const_time_select_int - Constant time int selection
+ * @param	mask 0 (false) or -1 (true) to identify which value to select
+ * @param	true_val Value to select for the true case
+ * @param	false_val Value to select for the false case
+ * @return	true_val if mask == -1, false_val if mask == 0
+ */
+static inline int const_time_select_int(unsigned int mask, int true_val,
+					int false_val)
+{
+	return (int) const_time_select(mask, (unsigned int) true_val,
+				       (unsigned int) false_val);
+}
+
+
+/**
+ * const_time_select_u8 - Constant time u8 selection
+ * @param	mask 0 (false) or -1 (true) to identify which value to select
+ * @param	true_val Value to select for the true case
+ * @param	false_val Value to select for the false case
+ * @return	true_val if mask == -1, false_val if mask == 0
+ */
+static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val)
+{
+	return (unsigned char) const_time_select(mask, true_val, false_val);
+}
+
+
+/**
+ * const_time_select_s8 - Constant time s8 selection
+ * @param	mask 0 (false) or -1 (true) to identify which value to select
+ * @param	true_val Value to select for the true case
+ * @param	false_val Value to select for the false case
+ * @return	true_val if mask == -1, false_val if mask == 0
+ */
+static inline char const_time_select_s8(char mask, char true_val, char false_val)
+{
+	return (char) const_time_select(mask, (unsigned int) true_val,
+				      (unsigned int) false_val);
+}
+
+
+/**
+ * const_time_select_bin - Constant time binary buffer selection copy
+ * @param	mask 0 (false) or -1 (true) to identify which value to copy
+ * @param	true_val Buffer to copy for the true case
+ * @param	false_val Buffer to copy for the false case
+ * @param	len Number of octets to copy
+ * @param	dst Destination buffer for the copy
+ *
+ * This function copies the specified buffer into the destination buffer using
+ * operations with identical memory access pattern regardless of which buffer
+ * is being copied.
+ */
+static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val,
+					 const unsigned char *false_val, size_t len,
+					 unsigned char *dst)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]);
+}
+
+
+static inline int const_time_memcmp(const void *a, const void *b, size_t len)
+{
+	const unsigned char *aa = a;
+	const unsigned char *bb = b;
+	int diff, res = 0;
+	unsigned int mask;
+
+	if (len == 0)
+		return 0;
+	do {
+		len--;
+		diff = (int) aa[len] - (int) bb[len];
+		mask = const_time_is_zero((unsigned int) diff);
+		res = const_time_select_int(mask, res, diff);
+	} while (len);
+
+	return res;
+}
+
+#endif /* CONST_TIME_H */
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
index d94851c3aa..d428644539 100644
--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
@@ -1,7 +1,5 @@
-/*
- * Copyright (c) Dan Harkins, 2012
- *
- *  Copyright holder grants permission for redistribution and use in source
+/**
+ *  copyright holder grants permission for redistribution and use in source
  *  and binary forms, with or without modification, provided that the
  *  following conditions are met:
  *     1. Redistribution of source code must retain the above copyright
@@ -29,94 +27,233 @@
  * This license and distribution terms cannot be changed. In other words,
  * this code cannot simply be copied and put under a different distribution
  * license (including the GNU public license).
+ *
+ * @copyright (c) Dan Harkins, 2012
  */
 
 RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $")
 USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include "eap_pwd.h"
+#include "const_time.h"
+#include <freeradius-devel/openssl3.h>
 
-#include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/modules.h>
+static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 };
 
 /* The random function H(x) = HMAC-SHA256(0^32, x) */
-static void H_Init(HMAC_CTX *ctx)
+static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest)
 {
-	uint8_t allzero[SHA256_DIGEST_LENGTH];
+	unsigned int mdlen = SHA256_DIGEST_LENGTH;
+	HMAC_Final(hmac_ctx, digest, &mdlen);
+//	HMAC_CTX_reset(hmac_ctx);
+}
 
-	memset(allzero, 0, SHA256_DIGEST_LENGTH);
+/* a counter-based KDF based on NIST SP800-108 */
+static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label,
+			int label_len, uint8_t *result, int result_bit_len)
+{
+	HMAC_CTX	*hmac_ctx;
+	uint8_t		digest[SHA256_DIGEST_LENGTH];
+	uint16_t	i, ctr, L;
+	int		result_byte_len, len = 0;
+	unsigned int	mdlen = SHA256_DIGEST_LENGTH;
+	uint8_t		mask = 0xff;
+
+	MEM(hmac_ctx = HMAC_CTX_new());
+	result_byte_len = (result_bit_len + 7) / 8;
+
+	ctr = 0;
+	L = htons(result_bit_len);
+	while (len < result_byte_len) {
+		ctr++; i = htons(ctr);
 
-	HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
+		HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL);
+		if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen);
+		HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t));
+		HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len);
+		HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t));
+		HMAC_Final(hmac_ctx, digest, &mdlen);
+		if ((len + (int) mdlen) > result_byte_len) {
+			memcpy(result + len, digest, result_byte_len - len);
+		} else {
+			memcpy(result + len, digest, mdlen);
+		}
+		len += mdlen;
+//		HMAC_CTX_reset(hmac_ctx);
+	}
+
+	/* since we're expanding to a bit length, mask off the excess */
+	if (result_bit_len % 8) {
+		mask <<= (8 - (result_bit_len % 8));
+		result[result_byte_len - 1] &= mask;
+	}
+
+	HMAC_CTX_free(hmac_ctx);
 }
 
-static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len)
+static BIGNUM *consttime_BN (void)
 {
-	HMAC_Update(ctx, data, len);
+	BIGNUM *bn;
+
+	bn = BN_new();
+	if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME);
+	return bn;
 }
 
-static void H_Final(HMAC_CTX *ctx, uint8_t *digest)
+/*
+ * compute the legendre symbol in constant time
+ */
+static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx)
 {
-	unsigned int mdlen = SHA256_DIGEST_LENGTH;
+	int		symbol;
+	unsigned int	mask;
+	BIGNUM		*res, *pm1over2;
+
+	pm1over2 = consttime_BN();
+	res = consttime_BN();
+
+	if (!BN_sub(pm1over2, p, BN_value_one()) ||
+	    !BN_rshift1(pm1over2, pm1over2) ||
+	    !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) {
+		BN_free(pm1over2);
+		BN_free(res);
+		return -2;
+	}
 
-	HMAC_Final(ctx, digest, &mdlen);
+	symbol = -1;
+	mask = const_time_eq(BN_is_word(res, 1), 1);
+	symbol = const_time_select_int(mask, 1, symbol);
+	mask = const_time_eq(BN_is_zero(res), 1);
+	symbol = const_time_select_int(mask, -1, symbol);
+
+	BN_free(pm1over2);
+	BN_free(res);
+
+	return symbol;
 }
 
-/* a counter-based KDF based on NIST SP800-108 */
-static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen)
+static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx)
 {
-	HMAC_CTX *hctx = NULL;
-	uint8_t digest[SHA256_DIGEST_LENGTH];
-	uint16_t i, ctr, L;
-	int resultbytelen, len = 0;
-	unsigned int mdlen = SHA256_DIGEST_LENGTH;
-	uint8_t mask = 0xff;
+	BIGNUM *p, *a, *b, *tmp1, *pm1;
 
-	hctx = HMAC_CTX_new();
-	if (hctx == NULL) {
-		DEBUG("failed allocating HMAC context");
-		return -1;
+	tmp1 = BN_new();
+	pm1 = BN_new();
+	p = BN_new();
+	a = BN_new();
+	b = BN_new();
+	EC_GROUP_get_curve(group, p, a, b, bnctx);
+
+	BN_sub(pm1, p, BN_value_one());
+
+	/*
+	 * y2 = x^3 + ax + b
+	 */
+	BN_mod_sqr(tmp1, x, p, bnctx);
+	BN_mod_mul(y2, tmp1, x, p, bnctx);
+	BN_mod_mul(tmp1, a, x, p, bnctx);
+	BN_mod_add_quick(y2, y2, tmp1, p);
+	BN_mod_add_quick(y2, y2, b, p);
+
+	BN_free(tmp1);
+	BN_free(pm1);
+	BN_free(p);
+	BN_free(a);
+	BN_free(b);
+
+	return;
+}
+
+static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx)
+{
+	int offset, check, ret = 0;
+	BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL;
+	unsigned int mask;
+	unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL;
+
+	if (((r = consttime_BN()) == NULL) ||
+	    ((res = consttime_BN()) == NULL) ||
+	    ((qr_or_qnr = consttime_BN()) == NULL) ||
+	    ((pm1 = consttime_BN()) == NULL)) {
+		ret = -2;
+		goto fail;
 	}
-	resultbytelen = (resultbitlen + 7)/8;
-	ctr = 0;
-	L = htons(resultbitlen);
-	while (len < resultbytelen) {
-		ctr++; i = htons(ctr);
-		HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL);
-		if (ctr > 1) {
-			HMAC_Update(hctx, digest, mdlen);
-		}
-		HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t));
-		HMAC_Update(hctx, (uint8_t const *)label, labellen);
-		HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t));
-		HMAC_Final(hctx, digest, &mdlen);
-		if ((len + (int) mdlen) > resultbytelen) {
-			memcpy(result + len, digest, resultbytelen - len);
-		} else {
-			memcpy(result + len, digest, mdlen);
-		}
-		len += mdlen;
+
+	if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
+	    ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
+	    ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) {
+		ret = -2;
+		goto fail;
 	}
-	HMAC_CTX_free(hctx);
 
-	/* since we're expanding to a bit length, mask off the excess */
-	if (resultbitlen % 8) {
-		mask <<= (8 - (resultbitlen % 8));
-		result[resultbytelen - 1] &= mask;
+	/*
+	 * we select binary in constant time so make them binary
+	 */
+	memset(qr_bin, 0, BN_num_bytes(p));
+	memset(qnr_bin, 0, BN_num_bytes(p));
+	memset(qr_or_qnr_bin, 0, BN_num_bytes(p));
+
+	offset = BN_num_bytes(p) - BN_num_bytes(qr);
+	BN_bn2bin(qr, qr_bin + offset);
+
+	offset = BN_num_bytes(p) - BN_num_bytes(qnr);
+	BN_bn2bin(qnr, qnr_bin + offset);
+
+	/*
+	 * r = (random() mod p-1) + 1
+	 */
+	BN_sub(pm1, p, BN_value_one());
+	BN_rand_range(r, pm1);
+	BN_add(r, r, BN_value_one());
+
+	BN_copy(res, val);
+
+	/*
+	 * res = val * r * r which ensures res != val but has same quadratic residocity
+	 */
+	BN_mod_mul(res, res, r, p, bnctx);
+	BN_mod_mul(res, res, r, p, bnctx);
+
+	/*
+	 * if r is even (mask is -1) then multiply by qnr and our check is qnr
+	 * otherwise multiply by qr and our check is qr
+	 */
+	mask = const_time_is_zero(BN_is_odd(r));
+	const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin);
+	BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr);
+	BN_mod_mul(res, res, qr_or_qnr, p, bnctx);
+	check = const_time_select_int(mask, -1, 1);
+
+	if ((ret = legendre(res, p, bnctx)) == -2) {
+		ret = -1;	/* just say no it's not */
+		goto fail;
 	}
+	mask = const_time_eq(ret, check);
+	ret = const_time_select_int(mask, 1, 0);
 
-	return 0;
+fail:
+	if (qr_bin != NULL) free(qr_bin);
+	if (qnr_bin != NULL) free(qnr_bin);
+	if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin);
+	BN_free(r);
+	BN_free(res);
+	BN_free(qr_or_qnr);
+	BN_free(pm1);
+
+	return ret;
 }
 
-int compute_password_element (pwd_session_t *session, uint16_t grp_num,
+int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num,
 			      char const *password, int password_len,
 			      char const *id_server, int id_server_len,
 			      char const *id_peer, int id_peer_len,
 			      uint32_t *token)
 {
-	BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL;
+	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, ctr;
-	int nid, is_odd, primebitlen, primebytelen, ret = 0;
+	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) {
@@ -159,17 +296,19 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
 		goto fail;
 	}
 
-	if (((rnd = BN_new()) == NULL) ||
-	    ((cofactor = BN_new()) == NULL) ||
+	if (((rnd = consttime_BN()) == NULL) ||
 	    ((session->pwe = EC_POINT_new(session->group)) == NULL) ||
-	    ((session->order = BN_new()) == NULL) ||
-	    ((session->prime = BN_new()) == NULL) ||
-	    ((x_candidate = BN_new()) == NULL)) {
+	    ((session->order = consttime_BN()) == NULL) ||
+	    ((session->prime = consttime_BN()) == NULL) ||
+	    ((qr = consttime_BN()) == NULL) ||
+	    ((qnr = consttime_BN()) == NULL) ||
+	    ((x_candidate = consttime_BN()) == NULL) ||
+	    ((y_sqrd = consttime_BN()) == NULL)) {
 		DEBUG("unable to create bignums");
 		goto fail;
 	}
 
-	if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) {
+	if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) {
 		DEBUG("unable to get prime for GFp curve");
 		goto fail;
 	}
@@ -179,46 +318,61 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
 		goto fail;
 	}
 
-	if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) {
-		DEBUG("unable to get cofactor for curve");
-		goto fail;
-	}
-
 	primebitlen = BN_num_bits(session->prime);
 	primebytelen = BN_num_bytes(session->prime);
 	if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) {
 		DEBUG("unable to alloc space for prf buffer");
 		goto fail;
 	}
+	if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
+		DEBUG("unable to alloc space for x buffer");
+		goto fail;
+	}
+	if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
+		DEBUG("unable to alloc space for pm1 buffer");
+		goto fail;
+	}
+
+	/*
+	* derive random quadradic residue and quadratic non-residue
+	*/
+	do {
+		BN_rand_range(qr, session->prime);
+	} while (legendre(qr, session->prime, session->bnctx) != 1);
+
+	do {
+		BN_rand_range(qnr, session->prime);
+	} while (legendre(qnr, session->prime, session->bnctx) != -1);
+
+	if (!BN_sub(rnd, session->prime, BN_value_one())) {
+		goto fail;
+	}
+	BN_bn2bin(rnd, pm1buf);
+
+	save_is_odd = 0;
+	found = 0;
+	memset(xbuf, 0, primebytelen);
 	ctr = 0;
-	while (1) {
-		if (ctr > 100) {
-			DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num);
-			goto fail;
-		}
+	while (ctr < 40) {
 		ctr++;
 
 		/*
 		 * compute counter-mode password value and stretch to prime
-		 *    pwd-seed = H(token | peer-id | server-id | password |
-		 *		   counter)
+		 *	pwd-seed = H(token | peer-id | server-id | password |
+		 *		     counter)
 		 */
-		H_Init(ctx);
-		H_Update(ctx, (uint8_t *)token, sizeof(*token));
-		H_Update(ctx, (uint8_t const *)id_peer, id_peer_len);
-		H_Update(ctx, (uint8_t const *)id_server, id_server_len);
-		H_Update(ctx, (uint8_t const *)password, password_len);
-		H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr));
-		H_Final(ctx, pwe_digest);
+		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);
 
 		BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd);
-		if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
-			        strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) {
-			DEBUG("key derivation function failed");
-			goto fail;
-		}
+		eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
+			    strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen);
 
-		BN_bin2bn(prfbuf, primebytelen, x_candidate);
 		/*
 		 * eap_pwd_kdf() returns a string of bits 0..primebitlen but
 		 * BN_bin2bn will treat that string of bits as a big endian
@@ -226,49 +380,73 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
 		 * then excessive bits-- those _after_ primebitlen-- so now
 		 * we have to shift right the amount we masked off.
 		 */
-		if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8)));
-		if (BN_ucmp(x_candidate, session->prime) >= 0) continue;
+		if (primebitlen % 8) {
+			rbits = 8 - (primebitlen % 8);
+			for (i = primebytelen - 1; i > 0; i--) {
+				prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits);
+			}
+			prfbuf[0] >>= rbits;
+		}
+		BN_bin2bn(prfbuf, primebytelen, x_candidate);
 
 		/*
-		 * need to unambiguously identify the solution, if there is
-		 * one...
-		 */
+		* it would've been better if the spec reduced the candidate
+		* modulo the prime but it didn't. So if the candidate >= prime
+		* we need to skip it but still run through the operations below
+		*/
+		cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen);
+		skip = const_time_fill_msb((unsigned int)cmp);
+
+		/*
+		* need to unambiguously identify the solution, if there is
+		* one..
+		*/
 		is_odd = BN_is_odd(rnd) ? 1 : 0;
 
 		/*
-		 * solve the quadratic equation, if it's not solvable then we
-		 * don't have a point
-		 */
-		if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) {
-			continue;
-		}
+		* check whether x^3 + a*x + b is a quadratic residue
+		*
+		* save the first quadratic residue we find in the loop but do
+		* it in constant time.
+		*/
+		do_equation(session->group, y_sqrd, x_candidate, session->bnctx);
+		qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx);
 
 		/*
-		 * If there's a solution to the equation then the point must be
-		 * on the curve so why check again explicitly? OpenSSL code
-		 * says this is required by X9.62. We're not X9.62 but it can't
-		 * hurt just to be sure.
-		 */
-		if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) {
-			DEBUG("EAP-pwd: point is not on curve");
-			continue;
-		}
+		* if the candidate >= prime then we want to skip it
+		*/
+		qr_or_qnr = const_time_select(skip, 0, qr_or_qnr);
 
-		if (BN_cmp(cofactor, BN_value_one())) {
-			/* make sure the point is not in a small sub-group */
-			if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe,
-				cofactor, NULL)) {
-				DEBUG("EAP-pwd: cannot multiply generator by order");
-				continue;
-			}
+		/*
+		* if we haven't found PWE yet (found = 0) then mask will be true,
+		* if we have found PWE then mask will be false
+		*/
+		mask = const_time_select(found, 0, -1);
 
-			if (EC_POINT_is_at_infinity(session->group, session->pwe)) {
-				DEBUG("EAP-pwd: point is at infinity");
-				continue;
-			}
-		}
-		/* if we got here then we have a new generator. */
-		break;
+		/*
+		* save will be 1 if we want to save this value-- i.e. we haven't
+		* found PWE yet and this is a quadratic residue-- and 0 otherwise
+		*/
+		save = const_time_select(mask, qr_or_qnr, 0);
+
+		/*
+		* mask will be true (-1) if we want to save this and false (0)
+		* otherwise
+		*/
+		mask = const_time_eq(save, 1);
+
+		const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf);
+		save_is_odd = const_time_select(mask, is_odd, save_is_odd);
+		found = const_time_select(mask, -1, found);
+	}
+
+	/*
+	* now we can savely construct PWE
+	*/
+	BN_bin2bn(xbuf, primebytelen, x_candidate);
+	if (!EC_POINT_set_compressed_coordinates(session->group, session->pwe,
+						     x_candidate, save_is_odd, NULL)) {
+		goto fail;
 	}
 
 	session->group_num = grp_num;
@@ -278,78 +456,81 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num,
 	}
 
 	/* cleanliness and order.... */
-	BN_clear_free(cofactor);
 	BN_clear_free(x_candidate);
+	BN_clear_free(y_sqrd);
+	BN_clear_free(qr);
+	BN_clear_free(qnr);
 	BN_clear_free(rnd);
-	talloc_free(prfbuf);
+
+	if (prfbuf) talloc_free(prfbuf);
+	if (xbuf) talloc_free(xbuf);
+	if (pm1buf) talloc_free(pm1buf);
+
 	HMAC_CTX_free(ctx);
 
 	return ret;
 }
 
-int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) {
+int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx)
+{
 	BIGNUM *mask = NULL;
 	int ret = -1;
 
-	if (((session->private_value = BN_new()) == NULL) ||
-	    ((session->my_element = EC_POINT_new(session->group)) == NULL) ||
-	    ((session->my_scalar = BN_new()) == NULL) ||
-	    ((mask = BN_new()) == NULL)) {
-		DEBUG2("server scalar allocation failed");
-		goto fail;
-	}
+	MEM(session->private_value = BN_new());
+	MEM(session->my_element = EC_POINT_new(session->group));
+	MEM(session->my_scalar = BN_new());
+
+	MEM(mask = BN_new());
 
 	if (BN_rand_range(session->private_value, session->order) != 1) {
-		DEBUG2("Unable to get randomness for private_value");
-		goto fail;
+		REDEBUG("Unable to get randomness for private_value");
+		goto error;
 	}
 	if (BN_rand_range(mask, session->order) != 1) {
-		DEBUG2("Unable to get randomness for mask");
-		goto fail;
+		REDEBUG("Unable to get randomness for mask");
+		goto error;
 	}
 	BN_add(session->my_scalar, session->private_value, mask);
-	BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx);
+	BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx);
 
-	if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) {
-		DEBUG2("server element allocation failed");
-		goto fail;
+	if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) {
+		REDEBUG("Server element allocation failed");
+		goto error;
 	}
 
-	if (!EC_POINT_invert(session->group, session->my_element, bnctx)) {
-		DEBUG2("server element inversion failed");
-		goto fail;
+	if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) {
+		REDEBUG("Server element inversion failed");
+		goto error;
 	}
 
 	ret = 0;
 
-fail:
+error:
 	BN_clear_free(mask);
 
 	return ret;
 }
 
-int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx)
+int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx)
 {
-	uint8_t *ptr;
-	size_t data_len;
-	BIGNUM *x = NULL, *y = NULL, *cofactor = NULL;
-	EC_POINT *K = NULL, *point = NULL;
-	int res = 1;
-
-	if (((session->peer_scalar = BN_new()) == NULL) ||
-	    ((session->k = BN_new()) == NULL) ||
-	    ((cofactor = BN_new()) == NULL) ||
-	    ((x = BN_new()) == NULL) ||
-	    ((y = BN_new()) == NULL) ||
-	    ((point = EC_POINT_new(session->group)) == NULL) ||
-	    ((K = EC_POINT_new(session->group)) == NULL) ||
-	    ((session->peer_element = EC_POINT_new(session->group)) == NULL)) {
-		DEBUG2("pwd: failed to allocate room to process peer's commit");
-		goto finish;
-	}
+	uint8_t		*ptr;
+	size_t		data_len;
+	BIGNUM		*x = NULL, *y = NULL, *cofactor = NULL;
+	EC_POINT	*K = NULL, *point = NULL;
+	int		ret = 1;
+
+	MEM(session->peer_scalar = BN_new());
+	MEM(session->k = BN_new());
+	MEM(session->peer_element = EC_POINT_new(session->group));
+	MEM(point = EC_POINT_new(session->group));
+	MEM(K = EC_POINT_new(session->group));
+
+	MEM(cofactor = BN_new());
+	MEM(x = BN_new());
+	MEM(y = BN_new());
 
 	if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) {
-		DEBUG2("pwd: unable to get group co-factor");
+		REDEBUG("Unable to get group co-factor");
 		goto finish;
 	}
 
@@ -361,7 +542,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
 	 *	Did the peer send enough data?
 	 */
 	if (in_len < (2 * data_len + BN_num_bytes(session->order))) {
-		DEBUG("pwd: Invalid commit packet");
+		REDEBUG("Invalid commit packet");
 		goto finish;
 	}
 
@@ -377,54 +558,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
 	if (BN_is_zero(session->peer_scalar) ||
 	    BN_is_one(session->peer_scalar) ||
 	    BN_cmp(session->peer_scalar, session->order) >= 0) {
-		ERROR("Peer's scalar is not within the allowed range");
+		REDEBUG("Peer's scalar is not within the allowed range");
 		goto finish;
 	}
 
-	if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
-		DEBUG2("pwd: unable to get coordinates of peer's element");
+	if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
+		REDEBUG("Unable to get coordinates of peer's element");
 		goto finish;
 	}
 
 	/* validate received element */
-	if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) ||
+	if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) ||
 	    EC_POINT_is_at_infinity(session->group, session->peer_element)) {
-		ERROR("Peer's element is not a point on the elliptic curve");
+		REDEBUG("Peer's element is not a point on the elliptic curve");
 		goto finish;
 	}
 
 	/* check to ensure peer's element is not in a small sub-group */
 	if (BN_cmp(cofactor, BN_value_one())) {
 		if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) {
-			DEBUG2("pwd: unable to multiply element by co-factor");
+			REDEBUG("Unable to multiply element by co-factor");
 			goto finish;
 		}
 
 		if (EC_POINT_is_at_infinity(session->group, point)) {
-			DEBUG2("pwd: peer's element is in small sub-group");
+			REDEBUG("Peer's element is in small sub-group");
 			goto finish;
 		}
 	}
 
 	/* detect reflection attacks */
 	if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 ||
-	    EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) {
-		ERROR("Reflection attack detected");
+	    EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) {
+		REDEBUG("Reflection attack detected");
 		goto finish;
 	}
 
 	/* compute the shared key, k */
-	if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) ||
-	    (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) ||
-	    (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) {
-		DEBUG2("pwd: unable to compute shared key, k");
+	if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) ||
+	    (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) ||
+	    (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) {
+		REDEBUG("Unable to compute shared key, k");
 		goto finish;
 	}
 
 	/* ensure that the shared key isn't in a small sub-group */
 	if (BN_cmp(cofactor, BN_value_one())) {
 		if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) {
-			DEBUG2("pwd: unable to multiply k by co-factor");
+			REDEBUG("Unable to multiply k by co-factor");
 			goto finish;
 		}
 	}
@@ -436,15 +617,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_
 	 * sure" so let's be safe.
 	 */
 	if (EC_POINT_is_at_infinity(session->group, K)) {
-		DEBUG2("pwd: k is point-at-infinity!");
+		REDEBUG("K is point-at-infinity");
 		goto finish;
 	}
 
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) {
-		DEBUG2("pwd: unable to get shared secret from K");
+	if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) {
+		REDEBUG("Unable to get shared secret from K");
 		goto finish;
 	}
-	res = 0;
+	ret = 0;
 
 finish:
 	EC_POINT_clear_free(K);
@@ -453,36 +634,29 @@ finish:
 	BN_clear_free(x);
 	BN_clear_free(y);
 
-	return res;
+	return ret;
 }
 
-int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
 {
-	BIGNUM *x = NULL, *y = NULL;
-	HMAC_CTX *ctx = NULL;
-	uint8_t *cruft = NULL;
-	int offset, req = -1;
-
-	ctx = HMAC_CTX_new();
-	if (ctx == NULL) {
-		DEBUG2("pwd: unable to allocate HMAC context!");
-		goto finish;
-	}
+	BIGNUM		*x = NULL, *y = NULL;
+	HMAC_CTX	*hmac_ctx = NULL;
+	uint8_t		*cruft = NULL;
+	int		offset, req = -1;
 
 	/*
 	 * Each component of the cruft will be at most as big as the prime
 	 */
-	if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) ||
-	    ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
-		DEBUG2("pwd: unable to allocate space to compute confirm!");
-		goto finish;
-	}
+	MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
+	MEM(x = BN_new());
+	MEM(y = BN_new());
 
 	/*
 	 * commit is H(k | server_element | server_scalar | peer_element |
 	 *	       peer_scalar | ciphersuite)
 	 */
-	H_Init(ctx);
+	MEM(hmac_ctx = HMAC_CTX_new());
+	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
 
 	/*
 	 * Zero the memory each time because this is mod prime math and some
@@ -492,24 +666,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	 */
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
 	BN_bn2bin(session->k, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	 * next is server element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) {
-		DEBUG2("pwd: unable to get coordinates of server element");
+	if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
+		REDEBUG("Unable to get coordinates of server element");
 		goto finish;
 	}
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
 	BN_bn2bin(x, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
 	BN_bn2bin(y, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	 * and server scalar
@@ -517,25 +691,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
 	BN_bn2bin(session->my_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
 
 	/*
 	 * next is peer element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
-		DEBUG2("pwd: unable to get coordinates of peer's element");
+	if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
+		REDEBUG("Unable to get coordinates of peer's element");
 		goto finish;
 	}
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
 	BN_bn2bin(x, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
 	BN_bn2bin(y, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	 * and peer scalar
@@ -543,52 +717,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
 	BN_bn2bin(session->peer_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
 
 	/*
 	 * finally, ciphersuite
 	 */
-	H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
 
-	H_Final(ctx, out);
+	pwd_hmac_final(hmac_ctx, out);
 
 	req = 0;
+
 finish:
+	HMAC_CTX_free(hmac_ctx);
 	talloc_free(cruft);
 	BN_free(x);
 	BN_free(y);
-	HMAC_CTX_free(ctx);
 
 	return req;
 }
 
-int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
+int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
 {
-	BIGNUM *x = NULL, *y = NULL;
-	HMAC_CTX *ctx = NULL;
-	uint8_t *cruft = NULL;
-	int offset, req = -1;
-
-	ctx = HMAC_CTX_new();
-	if (ctx == NULL) {
-		DEBUG2("pwd: unable to allocate HMAC context!");
-		goto finish;
-	}
+	BIGNUM		*x = NULL, *y = NULL;
+	HMAC_CTX	*hmac_ctx = NULL;
+	uint8_t		*cruft = NULL;
+	int		offset, req = -1;
 
 	/*
 	 * Each component of the cruft will be at most as big as the prime
 	 */
-	if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) ||
-	    ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
-		DEBUG2("pwd: unable to allocate space to compute confirm!");
-		goto finish;
-	}
+	MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
+	MEM(x = BN_new());
+	MEM(y = BN_new());
 
 	/*
 	 * commit is H(k | server_element | server_scalar | peer_element |
 	 *	       peer_scalar | ciphersuite)
 	 */
-	H_Init(ctx);
+	MEM(hmac_ctx = HMAC_CTX_new());
+	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
 
 	/*
 	 * Zero the memory each time because this is mod prime math and some
@@ -598,25 +766,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	 */
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
 	BN_bn2bin(session->k, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	* then peer element: x, y
 	*/
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) {
-		DEBUG2("pwd: unable to get coordinates of peer's element");
+	if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
+		REDEBUG("Unable to get coordinates of peer's element");
 		goto finish;
 	}
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
 	BN_bn2bin(x, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
 	BN_bn2bin(y, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	 * and peer scalar
@@ -624,24 +792,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
 	BN_bn2bin(session->peer_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
 
 	/*
 	 * then server element: x, y
 	 */
-	if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) {
-		DEBUG2("pwd: unable to get coordinates of server element");
+	if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
+		REDEBUG("Unable to get coordinates of server element");
 		goto finish;
 	}
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
 	BN_bn2bin(x, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
 	BN_bn2bin(y, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
 	/*
 	 * and server scalar
@@ -649,94 +817,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx)
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
 	BN_bn2bin(session->my_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
 
 	/*
 	 * finally, ciphersuite
 	 */
-	H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
 
-	H_Final(ctx, out);
+	pwd_hmac_final(hmac_ctx, out);
 
 	req = 0;
 finish:
+	HMAC_CTX_free(hmac_ctx);
 	talloc_free(cruft);
 	BN_free(x);
 	BN_free(y);
-	HMAC_CTX_free(ctx);
 
 	return req;
 }
 
-int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk)
+int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk)
 {
-	HMAC_CTX *ctx = NULL;
-	uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL;
-	uint8_t session_id[SHA256_DIGEST_LENGTH + 1];
-	uint8_t msk_emsk[128];		/* 64 each */
-	int offset, ret = -1;
-
-	ctx = HMAC_CTX_new();
-	if (ctx == NULL) {
-		DEBUG2("pwd: unable to allocate HMAC context!");
-		goto finish;
-	}
+	HMAC_CTX	*hmac_ctx;
+	uint8_t		mk[SHA256_DIGEST_LENGTH], *cruft;
+	uint8_t		session_id[SHA256_DIGEST_LENGTH + 1];
+	uint8_t		msk_emsk[128];		/* 64 each */
+	int	 	offset;
 
-	if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) {
-		DEBUG2("pwd: unable to allocate space to compute keys");
-		goto finish;
-	}
+	MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime)));
+	MEM(hmac_ctx = HMAC_CTX_new());
 
 	/*
 	 * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
 	 *	scal_s)
 	 */
 	session_id[0] = PW_EAP_PWD;
-	H_Init(ctx);
-	H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
+	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
+	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	BN_bn2bin(session->peer_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
 	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	BN_bn2bin(session->my_scalar, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->order));
-	H_Final(ctx, (uint8_t *)&session_id[1]);
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
+	pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]);
 
 	/* then compute MK = H(k | commit-peer | commit-server) */
-	H_Init(ctx);
+	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
 
 	memset(cruft, 0, BN_num_bytes(session->prime));
 	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
 	BN_bn2bin(session->k, cruft + offset);
-	H_Update(ctx, cruft, BN_num_bytes(session->prime));
+	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));
 
-	H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH);
+	HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH);
 
-	H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH);
+	HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH);
 
-	H_Final(ctx, mk);
+	pwd_hmac_final(hmac_ctx, mk);
 
 	/* stretch the mk with the session-id to get MSK | EMSK */
-	if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id,
-		        SHA256_DIGEST_LENGTH + 1, msk_emsk,
-			/* it's bits, ((64 + 64) * 8) */
-			1024) != 0) {
-		DEBUG("key derivation function failed");
-		goto finish;
-	}
+	eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id,
+		    SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024);  /* it's bits, ((64 + 64) * 8) */
 
 	memcpy(msk, msk_emsk, 64);
 	memcpy(emsk, msk_emsk + 64, 64);
 
-	ret = 0;
-finish:
+	HMAC_CTX_free(hmac_ctx);
 	talloc_free(cruft);
-	HMAC_CTX_free(ctx);
-	return ret;
+	return 0;
 }
-
-
-
-
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
index ca12778f61..a40a346069 100644
--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
@@ -102,18 +102,22 @@ typedef struct _pwd_session_t {
     EC_POINT *my_element;
     EC_POINT *peer_element;
     uint8_t my_confirm[SHA256_DIGEST_LENGTH];
+    uint8_t prep;
+    uint8_t salt_present;
+    uint8_t salt_len;
+    uint8_t salt[255];
 } pwd_session_t;
 
-int compute_password_element(pwd_session_t *sess, uint16_t grp_num,
+int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num,
 			     char const *password, int password_len,
 			     char const *id_server, int id_server_len,
 			     char const *id_peer, int id_peer_len,
 			     uint32_t *token);
-int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx);
-int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx);
-int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
-int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
-int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm,
+int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx);
+int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx);
+int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
+int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx);
+int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm,
 		 uint8_t *msk, uint8_t *emsk);
 #ifdef PRINTBUF
 void print_buf(char *str, uint8_t *buf, int len);
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
index 18ab97f148..4992a2aeef 100644
--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
@@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 #define MPPE_KEY_LEN    32
 #define MSK_EMSK_LEN    (2*MPPE_KEY_LEN)
 
+/* EAP-PWD can use different preprocessing (prep) modes to mangle the password
+ * before proving to both parties that they both know the same (mangled) password.
+ *
+ * The server advertises a preprocessing mode to the client. Only "none" is
+ * mandatory to implement.
+ *
+ * What is a good selection on the preprocessing mode?
+ *
+ * a) the server uses a hashed password
+ * b) the client uses a hashed password
+ *
+ * a | b | result
+ * --+---+---------------------------------------
+ * n | n | none
+ * n | y | hint needed (cannot know automatically)
+ * y | n | select by hash given
+ * y | y | only works if both have the same hash; select by hash given
+ *
+ * Which hash functions does the server or client need to implement?
+ *
+ * a | b | server                 | client
+ * --+---+------------------------+----------------------
+ * n | n | none                   | none
+ * n | y | as configured          | none
+ * y | n | none                   | as selected by server
+ * y | y | none                   | none
+ *
+ * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement.
+ * Can we avoid implementing them all? Only if they are provided as hash by some
+ * other module, e.g. in SQL or statically in password database.
+ *
+ * Therefore we select the preprocessing mode by the type of password given if
+ * in automatic mode:
+ * a) Cleartext-Password or User-Password: None.
+ *    If the client only supports a hash (e.g. on Windows it might only have an
+ *    NT-Password), do not provide a Cleartext-Password attribute but instead
+ *    preprocess the password externally (e.g. hash the Cleartext-Password
+ *    into an NT-Password and drop the Cleartext-Password).
+ * b) NT-Password: rfc2759 (prep=MS).
+ *    The NT-Password Hash is hashed into a HashNTPasswordHash hash.
+ * c) EAP-Pwd-Password-Hash - provides hash as binary
+ *    EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client
+ *                            (RFC 8146)
+ *    EAP-Pwd-Password-Prep - constant to transmit to client in prep field
+ *
+ * Though, there is one issue left. The method needs to be selected in
+ * EAP-PWD-ID/Request, that is the first message from server and thus before
+ * the client sent its peer-id. This is feasable using the EAP-Identity frame
+ * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway.
+ * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use
+ * only the peer_id (inner identity). This toogle is an integer to also support
+ * setting currently unknown nor not implemented preprocessing methods.
+ *
+ * The toogle is named "prep", is a module configuration item, and accepts the
+ * following values:
+ *   prep | meaning
+ * -------+--------------------------------------------------------------------
+ * -1     | [automatic] discover using method described above from EAP-Identity
+ *        |             as User-Name before EAP-PWD-Id/Request
+ * 0..255 | [static]    Fixed password preprocessing method. Expects virtual
+ *        |             server to provide matching password given EAP-PWD
+ *        |             peer-id as User-Name. The virtual server is provided
+ *        |             with EAP-Pwd-Password-Prep containing the configured
+ *        |             prep value.
+ * else   | reserved/invalid
+ *
+ * Attributes to provide Password/Password-Hash and possibly salt.
+ *   prep | accepted attributes
+ * -------+--------------------------------------------------------------------
+ * -1     | see above for automatic discovery
+ * 0      | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash
+ * 1      | Use NT-Password, Cleartext-Password, User-Password or
+ *        | give hashed NT-Password hash in EAP-Pwd-Password-Hash
+ * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt.
+ *
+ * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex
+ * string, they are decoded as hex if module config option unhex=1 (default).
+ * Set it to zero if you provide binary input.
+ */
+
 static CONF_PARSER pwd_module_config[] = {
 	{ "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" },
 	{ "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" },
 	{ "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL },
 	{ "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL },
+	{ "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" },
+	{ "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" },
 	CONF_PARSER_TERMINATOR
 };
 
@@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance)
 		return -1;
 	}
 
+	if (inst->prep < -1 || inst->prep > 255) {
+		cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep);
+		return -1;
+	}
+
 	return 0;
 }
 
@@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds)
 	return 1;
 }
 
+static void normify(REQUEST *request, VALUE_PAIR *vp)
+{
+	size_t decoded;
+	size_t expected_len;
+	uint8_t *buffer;
+
+	rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING));
+
+	if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return;
+
+	expected_len = vp->vp_length / 2;
+	buffer = talloc_zero_array(request, uint8_t, expected_len);
+	rad_assert(buffer);
+
+	decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length);
+	if (decoded == expected_len) {
+		RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes",
+			vp->da->name, vp->vp_length, decoded);
+		fr_pair_value_memcpy(vp, buffer, decoded);
+	} else {
+		RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes",
+			vp->da->name, vp->vp_length, expected_len, decoded);
+	}
+
+	talloc_free(buffer);
+}
+
+static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) {
+	REQUEST *fake;
+	VALUE_PAIR *vp, *pw;
+	const char *pwbuf;
+	int pw_len;
+	uint8_t nthash[MD4_DIGEST_LENGTH];
+	uint8_t nthashash[MD4_DIGEST_LENGTH];
+	int ret = -1;
+	eap_type_t old_eap_type = 0;
+
+	if ((fake = request_alloc_fake(request)) == NULL) {
+		RDEBUG("pwd unable to create fake request!");
+		return ret;
+	}
+	fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0);
+	if (!fake->username) {
+		RDEBUG("Failed creating pair for peer id");
+		goto out;
+	}
+	fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len);
+	fr_pair_add(&fake->packet->vps, fake->username);
+
+	if (inst->prep >= 0) {
+		vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0);
+		rad_assert(vp != NULL);
+		vp->vp_byte = inst->prep;
+		fr_pair_add(&fake->packet->vps, vp);
+	}
+
+	if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
+		fake->server = vp->vp_strvalue;
+	} else if (inst->virtual_server) {
+		fake->server = inst->virtual_server;
+	} /* else fake->server == request->server */
+
+	if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) {
+		/* EAP-Type = NAK here if inst->prep == -1.
+		 * But this does not help the virtual server to differentiate
+		 * based on which EAP method was selected, that is to properly
+		 * prepare session-state: for PWD.
+		 * So fake EAP-Type = PWD here for the time of the inner request.
+		 */
+		old_eap_type = vp->vp_integer;
+		vp->vp_integer = PW_EAP_PWD;
+	}
+	RDEBUG("Sending tunneled request");
+	rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL);
+
+	if (fake->server) {
+		RDEBUG("server %s {", fake->server);
+	} else {
+		RDEBUG("server {");
+	}
+
+	/*
+	 *	Call authorization recursively, which will
+	 *	get the password.
+	 */
+	RINDENT();
+	process_authorize(0, fake);
+	REXDENT();
+
+	/*
+	 *	Note that we don't do *anything* with the reply
+	 *	attributes.
+	 */
+	if (fake->server) {
+		RDEBUG("} # server %s", fake->server);
+	} else {
+		RDEBUG("}");
+	}
+
+	RDEBUG("Got tunneled reply code %d", fake->reply->code);
+	rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL);
+
+	if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) {
+		vp->vp_integer = old_eap_type;
+	}
+
+	pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
+	if (!pw) {
+		pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY);
+	}
+
+	if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) {
+		VERIFY_VP(pw);
+		session->prep = EAP_PWD_PREP_NONE;
+
+		RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication",
+			session->peer_id);
+
+		pwbuf = pw->vp_strvalue;
+		pw_len = pw->vp_length;
+
+		goto success;
+	}
+
+	pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY);
+
+	if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) {
+		VERIFY_VP(pw);
+		session->prep = EAP_PWD_PREP_MS;
+
+		RDEBUG("Use NT-Password for %s to do pwd authentication",
+			session->peer_id);
+
+		if (pw->vp_length != MD4_DIGEST_LENGTH) {
+			RDEBUG("NT-Password invalid length");
+			goto out;
+		}
+
+		fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length);
+		pwbuf = (const char*) nthashash;
+		pw_len = MD4_DIGEST_LENGTH;
+
+		goto success;
+	}
+
+	pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
+	if (!pw) {
+		pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY);
+	}
+
+	if (pw && inst->prep == EAP_PWD_PREP_MS) {
+		VERIFY_VP(pw);
+		session->prep = EAP_PWD_PREP_NONE;
+
+		RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication",
+			session->peer_id);
+
+		// compute NT-Hash from Cleartext-Password
+		ssize_t len;
+		uint8_t ucs2_password[512];
+		len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length);
+		if (len < 0) {
+			ERROR("rlm_eap_pwd: Error converting password to UCS2");
+			goto out;
+		}
+		fr_md4_calc(nthash, ucs2_password, len);
+
+		fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH);
+		pwbuf = (const char*) nthashash;
+		pw_len = MD4_DIGEST_LENGTH;
+
+		goto success;
+	}
+
+	vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY);
+	if (vp) {
+		VERIFY_VP(vp);
+	}
+	if (vp && inst->prep < 0) {
+		RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication",
+			vp->vp_byte, session->peer_id);
+		session->prep = vp->vp_byte;
+	} else if (vp && inst->prep != vp->vp_byte) {
+		RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s",
+			session->peer_id);
+		goto out;
+	} else if (inst->prep < 0) {
+		RDEBUG2("Missing EAP-Pwd-Password-Prep for %s",
+			session->peer_id);
+		goto out;
+	}
+
+	pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY);
+	if (pw) {
+		VERIFY_VP(pw);
+
+		RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication",
+			session->peer_id);
+
+		if (inst->unhex) normify(request, pw);
+
+		if (pw->vp_length > 255) {
+			/* salt len is 1 byte */
+			RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)");
+			goto out;
+		}
+		rad_assert(pw->vp_length <= sizeof(session->salt));
+
+		session->salt_present = 1;
+		session->salt_len = pw->vp_length;
+		memcpy(session->salt, pw->vp_octets, pw->vp_length);
+	}
+
+	pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY);
+	if (pw) {
+		VERIFY_VP(pw);
+
+		RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication",
+			session->peer_id);
+
+		if (inst->unhex) normify(request, pw);
+
+		pwbuf = (const char*) pw->vp_octets;
+		pw_len = pw->vp_length;
+
+		goto success;
+	}
+
+	RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s",
+		session->peer_id);
+	goto out;
+
+success:
+	if (RDEBUG_ENABLED4) {
+		char outbuf[1024];
+		char *p = outbuf;
+		for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) {
+			p += sprintf(p, "%02hhX", pwbuf[i]);
+		}
+		RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len);
+	}
+
+	if (compute_password_element(request, session, session->group_num,
+				     pwbuf, pw_len,
+				     inst->server_id, strlen(inst->server_id),
+				     session->peer_id, strlen(session->peer_id),
+				     &session->token)) {
+		RDEBUG("failed to obtain password element");
+		goto out;
+	}
+
+	ret = 0;
+out:
+	talloc_free(fake);
+	return ret;
+}
+
 static int mod_session_init (void *instance, eap_handler_t *handler)
 {
 	pwd_session_t *session;
 	eap_pwd_t *inst = (eap_pwd_t *)instance;
 	VALUE_PAIR *vp;
 	pwd_id_packet_t *packet;
+	REQUEST *request;
 
 	if (!inst || !handler) {
 		ERROR("rlm_eap_pwd: Initiate, NULL data provided");
 		return 0;
 	}
 
+	request = handler->request;
+	if (!request) {
+		ERROR("rlm_eap_pwd: NULL request provided");
+		return 0;
+	}
+
 	/*
 	* make sure the server's been configured properly
 	*/
@@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler)
 	session->out_pos = 0;
 	handler->opaque = session;
 
+	session->token = fr_rand();
+	if (inst->prep < 0) {
+		RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity);
+		session->peer_id_len = strlen(handler->identity);
+		if (session->peer_id_len >= sizeof(session->peer_id)) {
+			RDEBUG("identity is malformed");
+			return 0;
+		}
+		memcpy(session->peer_id, handler->identity, session->peer_id_len);
+		session->peer_id[session->peer_id_len] = '\0';
+
+		/*
+		 * make fake request to get the password for the usable ID
+		 * in order to identity prep
+		 */
+		if (fetch_and_process_password(session, handler->request, inst) < 0) {
+			RDEBUG("failed to find password for %s to do pwd authentication (init)",
+				session->peer_id);
+			return 0;
+		}
+	} else {
+		session->prep = inst->prep;
+	}
+
 	/*
 	 * construct an EAP-pwd-ID/Request
 	 */
@@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler)
 	packet->group_num = htons(session->group_num);
 	packet->random_function = EAP_PWD_DEF_RAND_FUN;
 	packet->prf = EAP_PWD_DEF_PRF;
-	session->token = fr_rand();
 	memcpy(packet->token, (char *)&session->token, 4);
-	packet->prep = EAP_PWD_PREP_NONE;
+	packet->prep = session->prep;
 	memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) );
 
 	handler->stage = PROCESS;
@@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	pwd_session_t *session;
 	pwd_hdr *hdr;
 	pwd_id_packet_t *packet;
+	REQUEST *request;
 	eap_packet_t *response;
-	REQUEST *request, *fake;
-	VALUE_PAIR *pw, *vp;
 	EAP_DS *eap_ds;
-	size_t in_len;
+	size_t in_len, peer_id_len;
 	int ret = 0;
 	eap_pwd_t *inst = (eap_pwd_t *)arg;
 	uint16_t offset;
 	uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN];
 	uint8_t peer_confirm[SHA256_DIGEST_LENGTH];
+	char *peer_id;
 
 	if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0;
 
@@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 
 		if ((packet->prf != EAP_PWD_DEF_PRF) ||
 		    (packet->random_function != EAP_PWD_DEF_RAND_FUN) ||
-		    (packet->prep != EAP_PWD_PREP_NONE) ||
+		    (packet->prep != session->prep) ||
 		    (CRYPTO_memcmp(packet->token, &session->token, 4)) ||
 		    (packet->group_num != ntohs(session->group_num))) {
 			RDEBUG2("pwd id response is invalid");
@@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		ptr += sizeof(uint8_t);
 		*ptr = EAP_PWD_DEF_PRF;
 
-		session->peer_id_len = in_len - sizeof(pwd_id_packet_t);
-		if (session->peer_id_len >= sizeof(session->peer_id)) {
+		peer_id_len = in_len - sizeof(pwd_id_packet_t);
+		if (peer_id_len >= sizeof(session->peer_id)) {
 			RDEBUG2("pwd id response is malformed");
 			return 0;
 		}
+		peer_id = packet->identity;
 
-		memcpy(session->peer_id, packet->identity, session->peer_id_len);
-		session->peer_id[session->peer_id_len] = '\0';
-
-		/*
-		 * make fake request to get the password for the usable ID
-		 */
-		if ((fake = request_alloc_fake(handler->request)) == NULL) {
-			RDEBUG("pwd unable to create fake request!");
-			return 0;
-		}
-		fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0);
-		if (!fake->username) {
-			RDEBUG("Failed creating pair for peer id");
-			talloc_free(fake);
-			return 0;
-		}
-		fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len);
-		fr_pair_add(&fake->packet->vps, fake->username);
-
-		if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
-			fake->server = vp->vp_strvalue;
-		} else if (inst->virtual_server) {
-			fake->server = inst->virtual_server;
-		} /* else fake->server == request->server */
-
-		RDEBUG("Sending tunneled request");
-		rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL);
-
-		if (fake->server) {
-			RDEBUG("server %s {", fake->server);
-		} else {
-			RDEBUG("server {");
-		}
+		if (inst->prep >= 0) {
+			/*
+			 * make fake request to get the password for the usable ID
+			 */
 
-		/*
-		 *	Call authorization recursively, which will
-		 *	get the password.
-		 */
-		RINDENT();
-		process_authorize(0, fake);
-		REXDENT();
+			session->peer_id_len = peer_id_len;
+			memcpy(session->peer_id, peer_id, peer_id_len);
+			session->peer_id[peer_id_len] = '\0';
 
-		/*
-		 *	Note that we don't do *anything* with the reply
-		 *	attributes.
-		 */
-		if (fake->server) {
-			RDEBUG("} # server %s", fake->server);
+			if (fetch_and_process_password(session, request, inst) < 0) {
+				RDEBUG2("failed to find password for %s to do pwd authentication",
+				session->peer_id);
+				return 0;
+			}
 		} else {
-			RDEBUG("}");
+			/* verify inner identity == outer identity */
+			if (session->peer_id_len != peer_id_len ||
+			    memcmp(session->peer_id, peer_id, peer_id_len) != 0) {
+				char buf[sizeof(session->peer_id)];
+				memcpy(buf, peer_id, peer_id_len);
+				buf[peer_id_len] = '\0';
+
+				RDEBUG2("inner identity(peer_id) %s does not match outer identity %s",
+				buf, session->peer_id);
+				return 0;
+			}
+			RDEBUG2("inner identity matched for %s", session->peer_id);
 		}
 
-		RDEBUG("Got tunneled reply code %d", fake->reply->code);
-		rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL);
-
-		if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) {
-			DEBUG2("failed to find password for %s to do pwd authentication",
-			session->peer_id);
-			talloc_free(fake);
-			return 0;
-		}
-
-		if (compute_password_element(session, session->group_num,
-			     		     pw->data.strvalue, strlen(pw->data.strvalue),
-					     inst->server_id, strlen(inst->server_id),
-					     session->peer_id, strlen(session->peer_id),
-					     &session->token)) {
-			DEBUG2("failed to obtain password element");
-			talloc_free(fake);
-			return 0;
-		}
-		TALLOC_FREE(fake);
-
 		/*
 		 * compute our scalar and element
 		 */
-		if (compute_scalar_element(session, session->bnctx)) {
+		if (compute_scalar_element(request, session, session->bnctx)) {
 			DEBUG2("failed to compute server's scalar and element");
 			return 0;
 		}
@@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		/*
 		 * element is a point, get both coordinates: x and y
 		 */
-		if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y,
+		if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y,
 							 session->bnctx)) {
 			DEBUG2("server point assignment failed");
 			BN_clear_free(x);
@@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		 * construct request
 		 */
 		session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime));
+		if (session->salt_present)
+			session->out_len += 1 + session->salt_len;
+
 		if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) {
 			return 0;
 		}
 		memset(session->out, 0, session->out_len);
 
 		ptr = session->out;
+		if (session->salt_present) {
+			*ptr = session->salt_len;
+			ptr++;
+
+			memcpy(ptr, session->salt, session->salt_len);
+			ptr += session->salt_len;
+		}
+
 		offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
 		BN_bn2bin(x, ptr + offset);
 		BN_clear_free(x);
@@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	}
 		break;
 
-		case PWD_STATE_COMMIT:
+	case PWD_STATE_COMMIT:
 		if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) {
 			RDEBUG2("pwd exchange is incorrect: not commit!");
 			return 0;
@@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		/*
 		 * process the peer's commit and generate the shared key, k
 		 */
-		if (process_peer_commit(session, in, in_len, session->bnctx)) {
+		if (process_peer_commit(request, session, in, in_len, session->bnctx)) {
 			RDEBUG2("failed to process peer's commit");
 			return 0;
 		}
@@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		/*
 		 * compute our confirm blob
 		 */
-		if (compute_server_confirm(session, session->my_confirm, session->bnctx)) {
+		if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) {
 			ERROR("rlm_eap_pwd: failed to compute confirm!");
 			return 0;
 		}
@@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 			RDEBUG2("pwd exchange is incorrect: not commit!");
 			return 0;
 		}
-		if (compute_peer_confirm(session, peer_confirm, session->bnctx)) {
+		if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) {
 			RDEBUG2("pwd exchange cannot compute peer's confirm");
 			return 0;
 		}
@@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 			RDEBUG2("pwd exchange fails: peer confirm is incorrect!");
 			return 0;
 		}
-		if (compute_keys(session, peer_confirm, msk, emsk)) {
+		if (compute_keys(request, session, peer_confirm, msk, emsk)) {
 			RDEBUG2("pwd exchange cannot generate (E)MSK!");
 			return 0;
 		}
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
index 2264566bb6..966646c360 100644
--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
@@ -44,6 +44,8 @@ typedef struct _eap_pwd_t {
     uint32_t	fragment_size;
     char const	*server_id;
     char const	*virtual_server;
+    int32_t	prep;
+    int32_t	unhex;
 } eap_pwd_t;
 
 #endif  /* _RLM_EAP_PWD_H */
diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
index 4d41cd42e6..d327c575fc 100644
--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
+++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c
@@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 static CONF_PARSER module_config[] = {
 	{ "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL },
 	{ "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL },
+	{ "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL },
 	CONF_PARSER_TERMINATOR
 };
 
@@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
 		return -1;
 	}
 
+#ifdef TLS1_3_VERSION
+	if ((inst->tls_conf->max_version == TLS1_3_VERSION) ||
+	    (inst->tls_conf->min_version == TLS1_3_VERSION)) {
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+		WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3");
+		WARN("!! Please set tls_max_version = \"1.2\"");
+		WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows");
+		WARN("!! This limitation is likely to change in late 2021.");
+		WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade");
+		WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+	}
+#endif
+
 	return 0;
 }
 
@@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	tls_session_t	*ssn;
 	rlm_eap_tls_t	*inst;
 	REQUEST		*request = handler->request;
+	bool		require_client_cert = true;
 
 	inst = type_arg;
 
 	handler->tls = true;
 
 	/*
-	 *	EAP-TLS always requires a client certificate.
+	 *	Respect EAP-TLS-Require-Client-Cert, but only if
+	 *	enabled in the module configuration.
+	 *
+	 *	We can't change behavior of existing systems, so this
+	 *	change has to be enabled via a new configuration
+	 *	option.
 	 */
-	ssn = eaptls_session(handler, inst->tls_conf, true);
+	if (inst->configurable_client_cert) {
+		VALUE_PAIR *vp;
+
+		vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY);
+		if (vp && !vp->vp_integer) require_client_cert = false;
+	}
+
+	/*
+	 *	EAP-TLS always requires a client certificate, and
+	 *	allows for TLS 1.3 if permitted.
+	 */
+	ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true);
 	if (!ssn) {
 		return 0;
 	}
 
 	handler->opaque = ((void *)ssn);
-
-	/*
-	 *	Set up type-specific information.
-	 */
-	ssn->prf_label = "client EAP encryption";
+	ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */
 
 	/*
 	 *	TLS session initialization is over.  Now handle TLS
@@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 	if (status == 0) return 0;
 
@@ -141,7 +168,7 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 
 
@@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler)
 			/* success */
 		}
 
+		/*
+		 *	Set the label to a fixed string.  For TLS 1.3,
+		 *	the label is the same for all TLS-based EAP
+		 *	methods.
+		 */
+		tls_session->label = "client EAP encryption";
+
 		/*
 		 *	Success: Automatically return MPPE keys.
 		 */
diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
index cebcb92f57..550cbbdce3 100644
--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
+++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h
@@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t {
 	 *	Virtual server for checking certificates
 	 */
 	char const *virtual_server;
+
+	/*
+	 * 	Configurable EAP-TLS-Require-Client-Cert
+	 */
+	bool configurable_client_cert;
 } rlm_eap_tls_t;
 
 #endif /* _RLM_EAP_TLS_H */
diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c
index a3c575bceb..3c77aa4877 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,6 +130,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance)
 		return -1;
 	}
 
+#ifdef TLS1_3_VERSION
+	if ((inst->tls_conf->min_version == TLS1_3_VERSION) && !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;
 }
 
@@ -181,7 +200,11 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 		client_cert = inst->req_client_cert;
 	}
 
-	ssn = eaptls_session(handler, inst->tls_conf, client_cert);
+	/*
+	 *	Don't allow TLS 1.3 for us, even if it's allowed
+	 *	elsewhere.
+	 */
+	ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic);
 	if (!ssn) {
 		return 0;
 	}
@@ -189,9 +212,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	handler->opaque = ((void *)ssn);
 
 	/*
-	 *	Set up type-specific information.
+	 *	Set the label to a fixed string.  For TLS 1.3, the
+	 *	label is the same for all TLS-based EAP methods.  If
+	 *	the client is using TLS 1.3, then eaptls_success()
+	 *	will over-ride this label with the correct label for
+	 *	TLS 1.3.
 	 */
-	ssn->prf_label = "ttls keying material";
+	ssn->label = "ttls keying material";
 
 	/*
 	 *	TLS session initialization is over.  Now handle TLS
@@ -201,7 +228,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 	if (status == 0) return 0;
 
@@ -238,7 +265,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
 		REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	} else {
-		RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+		RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
 	}
 
 	/*
@@ -342,8 +369,7 @@ static int mod_process(void *arg, eap_handler_t *handler)
 		 *	Success: Automatically return MPPE keys.
 		 */
 	case PW_CODE_ACCESS_ACCEPT:
-		ret = eaptls_success(handler, 0);
-		goto done;
+		goto do_keys;
 
 		/*
 		 *	No response packet, MUST be proxying it.
diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
index 627d722ed7..cbe423951a 100644
--- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
+++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
@@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 	size_t		size;
 	size_t		data_left = data_len;
 	VALUE_PAIR	*first = NULL;
-	VALUE_PAIR	*vp;
+	VALUE_PAIR	*vp = NULL;
 	RADIUS_PACKET	*packet = fake->packet; /* FIXME: api issues */
 	vp_cursor_t	out;
+	DICT_ATTR const *da;
 
 	fr_cursor_init(&out, &first);
 
@@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 			if (decoded < 0) {
 				REDEBUG2("diameter2vp failed decoding attr: %s",
 					fr_strerror());
-				goto do_octets;
+				goto raw;
 			}
 
 			if ((size_t) decoded != size + 2) {
 				REDEBUG2("diameter2vp failed to entirely decode VSA");
 				fr_pair_list_free(&vp);
-				goto do_octets;
+				goto raw;
 			}
 
 			fr_cursor_merge(&out, vp);
@@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 		/*
 		 *	Create it.  If this fails, it's because we're OOM.
 		 */
-	do_octets:
-		vp = fr_pair_afrom_num(packet, attr, vendor);
+		da = dict_attrbyvalue(attr, vendor);
+		if (!da) goto raw;
+
+		vp = fr_pair_afrom_da(packet, da);
 		if (!vp) {
 			RDEBUG2("Failure in creating VP");
 			fr_pair_list_free(&first);
@@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 		case PW_TYPE_INTEGER:
 		case PW_TYPE_DATE:
 			if (size != vp->vp_length) {
-				DICT_ATTR const *da;
-
 				/*
 				 *	Bad format.  Create a "raw"
 				 *	attribute.
@@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 		 */
 		if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) ||
 		    ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) {
-			uint8_t	challenge[16];
+			uint8_t	challenge[17];
 
 			if ((vp->vp_length < 8) ||
 			    (vp->vp_length > 16)) {
@@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl,
 				return NULL;
 			}
 
-			eapttls_gen_challenge(ssl, challenge,
-					      sizeof(challenge));
+			/*
+			 *	TLSv1.3 exports a different key depending on the length
+			 *	requested so ask for *exactly* what the spec requires
+			 */
+			eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1);
 
 			if (memcmp(challenge, vp->vp_octets,
 				   vp->vp_length) != 0) {
@@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se
 	 */
 	switch (reply->code) {
 	case PW_CODE_ACCESS_ACCEPT:
+		tls_session->authentication_success = true;
 		RDEBUG("Got tunneled Access-Accept");
 
 		rcode = RLM_MODULE_OK;
diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c
index e7dbfa685e..f7e23628e6 100644
--- a/src/modules/rlm_exec/rlm_exec.c
+++ b/src/modules/rlm_exec/rlm_exec.c
@@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r
 	 *	If we're not waiting, then there are no output pairs.
 	 */
 	if (inst->output) {
-		fr_pair_list_move(ctx, output_pairs, &answer);
+		fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD);
 	}
 	fr_pair_list_free(&answer);
 
@@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	/*
 	 *	Always add the value-pairs to the reply.
 	 */
-	fr_pair_list_move(request->reply, &request->reply->vps, &tmp);
+	fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD);
 	fr_pair_list_free(&tmp);
 
 	finish:
diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c
index c449d7776b..f835800376 100644
--- a/src/modules/rlm_expr/rlm_expr.c
+++ b/src/modules/rlm_expr/rlm_expr.c
@@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request,
 	return strlen(out);
 }
 
+/** Calculate the MD4 hash of a string or attribute.
+ *
+ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2"
+ */
+static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request,
+			char const *fmt, char *out, size_t outlen)
+{
+	uint8_t digest[16];
+	ssize_t i, len, inlen;
+	uint8_t const *p;
+	FR_MD4_CTX ctx;
+
+	/*
+	 *	We need room for at least one octet of output.
+	 */
+	if (outlen < 3) {
+		*out = '\0';
+		return 0;
+	}
+
+	inlen = xlat_fmt_to_ref(&p, request, fmt);
+	if (inlen < 0) {
+		return -1;
+	}
+
+	fr_md4_init(&ctx);
+	fr_md4_update(&ctx, p, inlen);
+	fr_md4_final(digest, &ctx);
+
+	/*
+	 *	Each digest octet takes two hex digits, plus one for
+	 *	the terminating NUL.
+	 */
+	len = (outlen / 2) - 1;
+	if (len > 16) len = 16;
+
+	for (i = 0; i < len; i++) {
+		snprintf(out + i * 2, 3, "%02x", digest[i]);
+	}
+
+	return strlen(out);
+}
+
 /** Calculate the SHA1 hash of a string or attribute.
  *
  * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
@@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request,
 	return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now));
 }
 
+/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s).
+ *
+ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520.
+ */
+static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request,
+			      char const *fmt, char *out, size_t outlen)
+{
+	long		num;
+
+	char const 	*p;
+	char 		*q;
+	time_t		now;
+	struct tm	*local, local_buff;
+
+	now = time(NULL);
+	local = localtime_r(&now, &local_buff);
+
+	p = fmt;
+
+	num = strtoul(p, &q, 10);
+	if (!q || *q == '\0') {
+		REDEBUG("nexttime: <int> must be followed by period specifier (h|d|w|m|y)");
+		return -1;
+	}
+
+	if (p == q) {
+		num = 1;
+	} else {
+		p += q - p;
+	}
+
+	local->tm_sec = 0;
+	local->tm_min = 0;
+
+	switch (*p) {
+	case 'h':
+		local->tm_hour -= num;
+		break;
+
+	case 'd':
+		local->tm_hour = 0;
+		local->tm_mday -= num;
+		break;
+
+	case 'w':
+		local->tm_hour = 0;
+		local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1));
+		break;
+
+	case 'm':
+		local->tm_hour = 0;
+		local->tm_mday = 1;
+		local->tm_mon -= num;
+		break;
+
+	case 'y':
+		local->tm_hour = 0;
+		local->tm_mday = 1;
+		local->tm_mon = 0;
+		local->tm_year -= num;
+		break;
+
+	default:
+		REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p);
+		return -1;
+	}
+
+	return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local)));
+}
+
 
 /*
  *	Parse the 3 arguments to lpad / rpad.
@@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
 	xlat_register("unescape", unescape_xlat, NULL, inst);
 	xlat_register("tolower", tolower_xlat, NULL, inst);
 	xlat_register("toupper", toupper_xlat, NULL, inst);
+	xlat_register("md4", md4_xlat, NULL, inst);
 	xlat_register("md5", md5_xlat, NULL, inst);
 	xlat_register("sha1", sha1_xlat, NULL, inst);
 #ifdef HAVE_OPENSSL_EVP_H
@@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
 	xlat_register("explode", explode_xlat, NULL, inst);
 
 	xlat_register("nexttime", next_time_xlat, NULL, inst);
+	xlat_register("lasttime", last_time_xlat, NULL, inst);
 	xlat_register("lpad", lpad_xlat, NULL, inst);
 	xlat_register("rpad", rpad_xlat, NULL, inst);
 
diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c
index c825a9230b..9e77cd7ff1 100644
--- a/src/modules/rlm_files/rlm_files.c
+++ b/src/modules/rlm_files/rlm_files.c
@@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *
 			/* ctx may be reply or proxy */
 			reply_tmp = fr_pair_list_copy(reply_packet, pl->reply);
 			radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
-			fr_pair_list_move(request, &request->config, &check_tmp);
+			fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD);
 			fr_pair_list_free(&check_tmp);
 
 			/*
diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h
index 7dc874d35e..bcec9af9dc 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_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c
index 83c84a63e2..4e4447a213 100644
--- a/src/modules/rlm_mschap/rlm_mschap.c
+++ b/src/modules/rlm_mschap/rlm_mschap.c
@@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request,
 		data = response->vp_octets + 2;
 		data_len = 24;
 
+	/*
+	 *	Pull the domain name out of the User-Name, if it exists.
+	 *
+	 *	This is the full domain name, not just the name after host/
+	 */
+	} else if (strncasecmp(fmt, "Domain-Name", 11) == 0) {
+		char *p;
+
+		user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
+		if (!user_name) {
+			REDEBUG("No User-Name was found in the request");
+			return -1;
+		}
+
+		/*
+		 *	First check to see if this is a host/ style User-Name
+		 *	(a la Kerberos host principal)
+		 */
+		if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
+			/*
+			 *	If we're getting a User-Name formatted in this way,
+			 *	it's likely due to PEAP.  The Windows Domain will be
+			 *	the first domain component following the hostname,
+			 *	or the machine name itself if only a hostname is supplied
+			 */
+			p = strchr(user_name->vp_strvalue, '.');
+			if (!p) {
+				RDEBUG2("setting NT-Domain to same as machine name");
+				strlcpy(out, user_name->vp_strvalue + 5, outlen);
+			} else {
+				p++;	/* skip the period */
+				strlcpy(out, p, outlen);
+			}
+		} else {
+			p = strchr(user_name->vp_strvalue, '\\');
+			if (!p) {
+				REDEBUG("No NT-Domain was found in the User-Name");
+				return -1;
+			}
+
+			/*
+			 *	Hack.  This is simpler than the alternatives.
+			 */
+			*p = '\0';
+			strlcpy(out, user_name->vp_strvalue, outlen);
+			*p = '\\';
+		}
+
+		return strlen(out);
+
 	/*
 	 *	Pull the NT-Domain out of the User-Name, if it exists.
 	 */
@@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
 			return -1;
 		}
 #else
-		cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time");
+		cf_log_err_cs(conf, "'winbind' is not enabled in this build.");
 		return -1;
 #endif
 	}
@@ -942,7 +992,6 @@ ntlm_auth_err:
 		ssize_t result_len;
 		char result[253];
 		uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
-		RC4_KEY key;
 
 		if (!nt_password) {
 			RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
@@ -951,11 +1000,45 @@ 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");
+				return -1;
+			}
+
+			if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) {
+				REDEBUG("Failed setting key length");
+				return -1;
+			}
+
+			if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) {
+				REDEBUG("Failed setting key value");
+				return -1;
+			}
+
+			if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) {
+				REDEBUG("Failed getting output");
+				return -1;
+			}
+
+			EVP_CIPHER_CTX_free(ctx);
+		}
+#else
+		{
+			RC4_KEY key;
+
+			/*
+			 *  Decrypt the blob
+			 */
+			RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
+			RC4(&key, 516, new_nt_password, nt_pass_decrypted);
+		}
+#endif
 
 		/*
 		 *  pwblock is
diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c
index 399689abf3..932e44abe9 100644
--- a/src/modules/rlm_otp/otp_mppe.c
+++ b/src/modules/rlm_otp/otp_mppe.c
@@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include <string.h>
 
+#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L)
+#define UNUSED3
+#else
+#define UNUSED3 UNUSED
+#endif
+
 /*
  * Add MPPE attributes to a request, if required.
  */
-void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode)
+void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode)
 {
 	VALUE_PAIR *cvp, *rvp;
 
@@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const
 	case PWE_CHAP:
 		return;
 
+#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L)
 	case PWE_MSCHAP:
 		/* First, set some related attributes. */
 		pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ);
@@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const
 
 		break; /* PWE_MSCHAP2 */
 	} /* PWE_MSCHAP2 */
-
+#else
+	case PWE_MSCHAP:
+	case PWE_MSCHAP2:
+		REDEBUG("MS-CHAP is unsupported for OpenSSL 3.");
+		break;
+#endif
 	} /* switch (pwe) */
 
 	return;
diff --git a/src/modules/rlm_otp/otp_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: 56a4dbc71b0117cb5eb788367e1fad7be9c8419a $")
 
 /* 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: 66fd8b4987c0353e5679da23efc31595aa7017db $")
 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_rest/rest.c b/src/modules/rlm_rest/rest.c
index 8b85df0662..fcb3fd11fc 100644
--- a/src/modules/rlm_rest/rest.c
+++ b/src/modules/rlm_rest/rest.c
@@ -115,6 +115,15 @@ do {\
 	}\
 } while (0)
 
+/*
+ * that macro is originally declared in include/curl/curlver.h
+ * We have to use this as curl uses lots of enums
+ */
+#ifndef CURL_AT_LEAST_VERSION
+#  define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z))
+#  define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
+#endif
+
 const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
 	0,			// HTTP_AUTH_UNKNOWN
 	0,			// HTTP_AUTH_NONE
@@ -221,6 +230,40 @@ const FR_NAME_NUMBER http_content_type_table[] = {
 	{  NULL , -1 }
 };
 
+/** Conversion table for "HTTP" protocol version to use.
+ *
+ * Used by rlm_rest_t for specify the http client version.
+ *
+ * Values we expect to use in curl_easy_setopt()
+ *
+ * @see fr_str2int
+ * @see fr_int2str
+ */
+const FR_NAME_NUMBER http_negotiation_table[] = {
+
+	{ "1.0", 	CURL_HTTP_VERSION_1_0 },		//!< Enforce HTTP 1.0 requests.
+	{ "1.1",	CURL_HTTP_VERSION_1_1 },		//!< Enforce HTTP 1.1 requests.
+/*
+ *	These are all enum values
+ */
+#if CURL_AT_LEAST_VERSION(7,49,0)
+	{ "2.0", 	CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE },	//!< Enforce HTTP 2.0 requests.
+#endif
+#if CURL_AT_LEAST_VERSION(7,33,0)
+	{ "2.0+auto",	CURL_HTTP_VERSION_2_0 },		//!< Attempt HTTP 2 requests. libcurl will fall back
+								///< to HTTP 1.1 if HTTP 2 can't be negotiated with the
+								///< server. (Added in 7.33.0)
+#endif
+#if CURL_AT_LEAST_VERSION(7,47,0)
+	{ "2.0+tls",	CURL_HTTP_VERSION_2TLS },		//!< Attempt HTTP 2 over TLS (HTTPS) only.
+								///< libcurl will fall back to HTTP 1.1 if HTTP 2
+								///< can't be negotiated with the HTTPS server.
+								///< For clear text HTTP servers, libcurl will use 1.1.
+#endif
+	{ "default", 	CURL_HTTP_VERSION_NONE }		//!< We don't care about what version the library uses.
+								///< libcurl will use whatever it thinks fit.
+};
+
 /*
  *	Encoder specific structures.
  *	@todo split encoders/decoders into submodules.
@@ -603,19 +646,30 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd
 		}
 
 		/*
-		 *  there are more attributes, insert a separator
+		 *  there are no more attributes, stop
 		 */
-		if (fr_cursor_next(&ctx->cursor)) {
-			if (freespace < 1) goto no_space;
-			*p++ = '&';
-			freespace--;
+		if (!fr_cursor_next_peek(&ctx->cursor)) {
+			ctx->state = READ_STATE_END;
+			break;
 		}
 
+		if (freespace < 1) goto no_space;
+		*p++ = '&';
+		freespace--;
+		/*
+		 *	Only advance once we have a separator
+		 *	really we should have an additional
+		 *	state for encoding the separator,
+		 *	but, we don't, and v3.0.x is stable
+		 *	so let's do the easiest fix with the
+		 *	lowest risk.
+		 */
+		fr_cursor_next(&ctx->cursor);
+
 		/*
 		 *  We wrote one full attribute value pair, record progress.
 		 */
 		encoded = p;
-
 		ctx->state = READ_STATE_ATTR_BEGIN;
 	}
 
@@ -747,7 +801,13 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd
 
 			type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
 
-			len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
+			if (ctx->section->attr_num) {
+				len = snprintf(p, freespace + 1, "\"%s\":{\"attr_num\":%d,\"type\":\"%s\",\"value\":[",
+						vp->da->name, vp->da->attr, type);
+			} else {
+				len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
+			}
+
 			if (len >= freespace) goto no_space;
 			p += len;
 			freespace -= len;
@@ -783,7 +843,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd
 				 *  write that out.
 				 */
 				attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace;
-				len = vp_prints_value_json(p, attr_space + 1, vp);
+				len = vp_prints_value_json(p, attr_space + 1, vp, ctx->section->raw_value);
 				if (is_truncated(len, attr_space + 1)) goto no_space;
 
 				/*
@@ -1575,8 +1635,9 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us
 		 *  HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
 		 *
 		 *  "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
+		 *  "HTTP/2 " (8) + "100 " (4) + "\r\n" (2) = 12
 		 */
-		if (s < 14) {
+		if (s < 12) {
 			REDEBUG("Malformed HTTP header: Status line too short");
 			goto malformed;
 		}
@@ -1614,8 +1675,10 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us
 		p++;
 		s--;
 
-		/*  Char after reason code must be a space, or \r */
-		if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
+		/*
+		 *  "xxx( |\r)" status code and terminator.
+		 */
+		if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
 
 		ctx->code = atoi(p);
 
@@ -1996,11 +2059,24 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
 	SET_OPTION(CURLOPT_NOSIGNAL, 1);
 	SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
 
+	/*
+	 *	As described in https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html,
+	 *	The libcurl decides which http version should be
+	 *  used by default accoring by library version.
+	 */
+	if (instance->http_negotiation != CURL_HTTP_VERSION_NONE) {
+		RDEBUG3("Set HTTP negotiation for %s", instance->http_negotiation_str);
+		SET_OPTION(CURLOPT_HTTP_VERSION, instance->http_negotiation);
+	}
+
 	content_type = fr_int2str(http_content_type_table, type, section->body_str);
 	snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
 	ctx->headers = curl_slist_append(ctx->headers, buffer);
 	if (!ctx->headers) goto error_header;
 
+	// Pass configuration to the request
+	ctx->request.section = section;
+
 	SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout);
 	SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout);
 
@@ -2322,6 +2398,7 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t
 	rlm_rest_handle_t	*randle = handle;
 	CURL			*candle = randle->handle;
 	CURLcode		ret;
+	VALUE_PAIR 		*vp;
 
 	ret = curl_easy_perform(candle);
 	if (ret != CURLE_OK) {
@@ -2330,6 +2407,14 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t
 		return -1;
 	}
 
+	/*
+	 *  Save the HTTP return status code.
+	 */
+	vp = pair_make_reply("REST-HTTP-Status-Code", NULL, T_OP_SET);
+	vp->vp_integer = rest_get_handle_code(handle);
+
+	RDEBUG2("Adding reply:REST-HTTP-Status-Code = \"%d\"", vp->vp_integer);
+
 	return 0;
 }
 
diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h
index 9e278d16c4..4c41cf46c2 100644
--- a/src/modules/rlm_rest/rest.h
+++ b/src/modules/rlm_rest/rest.h
@@ -31,6 +31,10 @@ RCSIDH(other_h, "$Id$")
 #define CURL_NO_OLDIES 1
 #include <curl/curl.h>
 
+#ifdef HAVE_WDOCUMENTATION
+DIAG_OFF(documentation)
+#endif
+
 #ifdef HAVE_JSON
 #  if defined(HAVE_JSONMC_JSON_H)
 #    include <json-c/json.h>
@@ -39,6 +43,10 @@ RCSIDH(other_h, "$Id$")
 #  endif
 #endif
 
+#ifdef HAVE_WDOCUMENTATION
+DIAG_ON(documentation)
+#endif
+
 #define REST_URI_MAX_LEN		2048
 #define REST_BODY_MAX_LEN		8192
 #define REST_BODY_INIT			1024
@@ -102,6 +110,8 @@ extern const FR_NAME_NUMBER http_body_type_table[];
 
 extern const FR_NAME_NUMBER http_content_type_table[];
 
+extern const FR_NAME_NUMBER http_negotiation_table[];
+
 /*
  *	Structure for section configuration
  */
@@ -115,6 +125,9 @@ typedef struct rlm_rest_section_t {
 	char const		*body_str;	//!< The string version of the encoding/content type.
 	http_body_type_t	body;		//!< What encoding type should be used.
 
+	bool			attr_num;	//!< If true, the the attribute number is supplied for each attribute.
+	bool			raw_value;	//!< If true, enumerated attributes are provided as a numeric value
+
 	char const		*force_to_str;	//!< Force decoding with this decoder.
 	http_body_type_t	force_to;	//!< Override the Content-Type header in the response
 						//!< to force decoding as a particular type.
@@ -154,6 +167,9 @@ typedef struct rlm_rest_t {
 	struct timeval		connect_timeout_tv;	//!< Connection timeout timeval.
 	long			connect_timeout;	//!< Connection timeout ms.
 
+	char const		*http_negotiation_str;	//!< The string version of the http_negotiation
+	long			http_negotiation;	//!< The HTTP protocol version to use
+
 	fr_connection_pool_t	*pool;		//!< Pointer to the connection pool.
 
 	rlm_rest_section_t	authorize;	//!< Configuration specific to authorisation.
@@ -205,6 +221,8 @@ typedef struct rlm_rest_request_t {
 
 	size_t			chunk;		//!< Chunk size
 
+	rlm_rest_section_t *section;	//!< Configuration data
+
 	void			*encoder;	//!< Encoder specific data.
 } rlm_rest_request_t;
 
diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c
index 6fa1345614..1337ae5425 100644
--- a/src/modules/rlm_rest/rlm_rest.c
+++ b/src/modules/rlm_rest/rlm_rest.c
@@ -60,6 +60,8 @@ static const CONF_PARSER section_config[] = {
 	{ "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), ""   },
 	{ "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" },
 	{ "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" },
+	{ "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" },
+	{ "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" },
 	{ "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL },
 	{ "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL },
 
@@ -81,6 +83,8 @@ static const CONF_PARSER section_config[] = {
 static const CONF_PARSER module_config[] = {
 	{ "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL },
 	{ "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" },
+	{ "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" },
+
 	CONF_PARSER_TERMINATOR
 };
 
@@ -223,6 +227,8 @@ static ssize_t rest_xlat(void *instance, REQUEST *request,
 		.name = "xlat",
 		.method = HTTP_METHOD_GET,
 		.body = HTTP_BODY_NONE,
+		.attr_num = false,
+		.raw_value = false,
 		.body_str = "application/x-www-form-urlencoded",
 		.require_auth = false,
 		.force_to = HTTP_BODY_PLAIN
@@ -926,6 +932,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
 		return -1;
 	}
 
+	inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1);
+	if (inst->http_negotiation == -1) {
+		cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str);
+		return -1;
+	}
+
 	/*
 	 *	Initialise REST libraries.
 	 */
diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c
new file mode 100644
index 0000000000..e14086e78c
--- /dev/null
+++ b/src/modules/rlm_wimax/milenage.c
@@ -0,0 +1,642 @@
+/**
+ * @file src/modules/rlm_wimax/milenage.c
+ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
+ *
+ * This file implements an example authentication algorithm defined for 3GPP
+ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
+ * EAP-AKA to be tested properly with real USIM cards.
+ *
+ * This implementations assumes that the r1..r5 and c1..c5 constants defined in
+ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
+ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
+ * be AES (Rijndael).
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ *
+ * @copyright 2017 The FreeRADIUS server project
+ * @copyright 2006-2007 (j@w1.fi)
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <openssl/evp.h>
+#include "milenage.h"
+
+#define MILENAGE_MAC_A_SIZE	8
+#define MILENAGE_MAC_S_SIZE	8
+
+static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx,
+					uint8_t const key[16], uint8_t const in[16], uint8_t out[16])
+{
+	size_t len;
+
+	if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) {
+		fr_strerror_printf("Failed initialising AES-128-ECB context");
+		return -1;
+	}
+
+	/*
+	 *	By default OpenSSL will try and pad out a 16 byte
+	 *	plaintext to 32 bytes so that it's detectable that
+	 *	there was padding.
+	 *
+	 *	In this case we know the length of the plaintext
+	 *	we're trying to recover, so we explicitly tell
+	 *	OpenSSL not to pad here, and not to expected padding
+	 *	when decrypting.
+	 */
+	EVP_CIPHER_CTX_set_padding(evp_ctx, 0);
+	if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) ||
+	    unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) {
+		fr_strerror_printf("Failed encrypting data");
+		return -1;
+	}
+
+	return 0;
+}
+
+/** milenage_f1 - Milenage f1 and f1* algorithms
+ *
+ * @param[in] opc	128-bit value derived from OP and K.
+ * @param[in] k		128-bit subscriber key.
+ * @param[in] rand	128-bit random challenge.
+ * @param[in] sqn	48-bit sequence number.
+ * @param[in] amf	16-bit authentication management field.
+ * @param[out] mac_a	Buffer for MAC-A = 64-bit network authentication code, or NULL
+ * @param[out] mac_s	Buffer for MAC-S = 64-bit resync authentication code, or NULL
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ */
+static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE],
+		       uint8_t mac_s[MILENAGE_MAC_S_SIZE],
+		       uint8_t const opc[MILENAGE_OPC_SIZE],
+		       uint8_t const k[MILENAGE_KI_SIZE],
+		       uint8_t const rand[MILENAGE_RAND_SIZE],
+		       uint8_t const sqn[MILENAGE_SQN_SIZE],
+		       uint8_t const amf[MILENAGE_AMF_SIZE])
+{
+	uint8_t		tmp1[16], tmp2[16], tmp3[16];
+	int		i;
+	EVP_CIPHER_CTX	*evp_ctx;
+
+	/* tmp1 = TEMP = E_K(RAND XOR OP_C) */
+	for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i];
+
+	evp_ctx = EVP_CIPHER_CTX_new();
+	if (!evp_ctx) {
+	  //tls_strerror_printf("Failed allocating EVP context");
+		return -1;
+	}
+
+ 	if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) {
+ 	error:
+		EVP_CIPHER_CTX_free(evp_ctx);
+		return -1;
+ 	}
+
+	/* tmp2 = IN1 = SQN || AMF || SQN || AMF */
+	memcpy(tmp2, sqn, 6);
+	memcpy(tmp2 + 6, amf, 2);
+	memcpy(tmp2 + 8, tmp2, 8);
+
+	/* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */
+
+	/*
+	 *  rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes)
+	 */
+	for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i];
+
+	/*
+	 *  XOR with TEMP = E_K(RAND XOR OP_C)
+	 */
+	for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i];
+	/* XOR with c1 (= ..00, i.e., NOP) */
+
+	/*
+	 *	f1 || f1* = E_K(tmp3) XOR OP_c
+	 */
+ 	if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */
+
+	for (i = 0; i < 16; i++) tmp1[i] ^= opc[i];
+
+	if (mac_a) memcpy(mac_a, tmp1, 8);	/* f1 */
+	if (mac_s) memcpy(mac_s, tmp1 + 8, 8);	/* f1* */
+
+	EVP_CIPHER_CTX_free(evp_ctx);
+
+	return 0;
+}
+
+/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms
+ *
+ * @param[out] res		Buffer for RES = 64-bit signed response (f2), or NULL
+ * @param[out] ck		Buffer for CK = 128-bit confidentiality key (f3), or NULL
+ * @param[out] ik		Buffer for IK = 128-bit integrity key (f4), or NULL
+ * @param[out] ak		Buffer for AK = 48-bit anonymity key (f5), or NULL
+ * @param[out] ak_resync	Buffer for AK = 48-bit anonymity key (f5*), or NULL
+ * @param[in] opc		128-bit value derived from OP and K.
+ * @param[in] k			128-bit subscriber key
+ * @param[in] rand		128-bit random challenge
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ */
+static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE],
+			  uint8_t ik[MILENAGE_IK_SIZE],
+			  uint8_t ck[MILENAGE_CK_SIZE],
+			  uint8_t ak[MILENAGE_AK_SIZE],
+			  uint8_t ak_resync[MILENAGE_AK_SIZE],
+			  uint8_t const opc[MILENAGE_OPC_SIZE],
+			  uint8_t const k[MILENAGE_KI_SIZE],
+			  uint8_t const rand[MILENAGE_RAND_SIZE])
+{
+	uint8_t			tmp1[16], tmp2[16], tmp3[16];
+	int			i;
+	EVP_CIPHER_CTX		*evp_ctx;
+
+	/* tmp2 = TEMP = E_K(RAND XOR OP_C) */
+	for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i];
+
+	evp_ctx = EVP_CIPHER_CTX_new();
+	if (!evp_ctx) {
+		fr_strerror_printf("Failed allocating EVP context");
+		return -1;
+	}
+
+	if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) {
+	error:
+		EVP_CIPHER_CTX_free(evp_ctx);
+		return -1;
+	}
+
+	/* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */
+	/* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */
+	/* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */
+	/* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */
+
+	/* f2 and f5 */
+	/* rotate by r2 (= 0, i.e., NOP) */
+	for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i];
+	tmp1[15] ^= 1; /* XOR c2 (= ..01) */
+	/* f5 || f2 = E_K(tmp1) XOR OP_c */
+
+	if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error;
+
+	for (i = 0; i < 16; i++) tmp3[i] ^= opc[i];
+	if (res) memcpy(res, tmp3 + 8, 8); /* f2 */
+	if (ak) memcpy(ak, tmp3, 6); /* f5 */
+
+	/* f3 */
+	if (ck) {
+		/* rotate by r3 = 0x20 = 4 bytes */
+		for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 2; /* XOR c3 (= ..02) */
+
+		if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error;
+
+		for (i = 0; i < 16; i++) ck[i] ^= opc[i];
+	}
+
+	/* f4 */
+	if (ik) {
+		/* rotate by r4 = 0x40 = 8 bytes */
+		for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 4; /* XOR c4 (= ..04) */
+
+		if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error;
+
+		for (i = 0; i < 16; i++) ik[i] ^= opc[i];
+	}
+
+	/* f5* */
+	if (ak_resync) {
+		/* rotate by r5 = 0x60 = 12 bytes */
+		for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 8; /* XOR c5 (= ..08) */
+
+		if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error;
+
+		for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i];
+	}
+	EVP_CIPHER_CTX_free(evp_ctx);
+
+	return 0;
+}
+
+/** Derive OPc from OP and Ki
+ *
+ * @param[out] opc	The derived Operator Code used as an input to other Milenage
+ *			functions.
+ * @param[in] op	Operator Code.
+ * @param[in] ki	Subscriber key.
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ */
+int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE],
+			  uint8_t const op[MILENAGE_OP_SIZE],
+			  uint8_t const ki[MILENAGE_KI_SIZE])
+{
+	int		ret;
+	uint8_t		tmp[MILENAGE_OPC_SIZE];
+	EVP_CIPHER_CTX	*evp_ctx;
+	size_t		i;
+
+	evp_ctx = EVP_CIPHER_CTX_new();
+	if (!evp_ctx) {
+		fr_strerror_printf("Failed allocating EVP context");
+		return -1;
+	}
+ 	ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp);
+ 	EVP_CIPHER_CTX_free(evp_ctx);
+	if (ret < 0) return ret;
+
+ 	for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i];
+
+ 	return 0;
+}
+
+/** Generate AKA AUTN, IK, CK, RES
+ *
+ * @param[out] autn	Buffer for AUTN = 128-bit authentication token.
+ * @param[out] ik	Buffer for IK = 128-bit integrity key (f4), or NULL.
+ * @param[out] ck	Buffer for CK = 128-bit confidentiality key (f3), or NULL.
+ * @param[out] ak	Buffer for AK = 48-bit anonymity key (f5), or NULL
+ * @param[out] res	Buffer for RES = 64-bit signed response (f2), or NULL.
+ * @param[in] opc	128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] amf	16-bit authentication management field.
+ * @param[in] ki	128-bit subscriber key.
+ * @param[in] sqn	48-bit sequence number (host byte order).
+ * @param[in] rand	128-bit random challenge.
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ */
+int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE],
+			   uint8_t ik[MILENAGE_IK_SIZE],
+			   uint8_t ck[MILENAGE_CK_SIZE],
+			   uint8_t ak[MILENAGE_AK_SIZE],
+			   uint8_t res[MILENAGE_RES_SIZE],
+			   uint8_t const opc[MILENAGE_OPC_SIZE],
+			   uint8_t const amf[MILENAGE_AMF_SIZE],
+			   uint8_t const ki[MILENAGE_KI_SIZE],
+			   uint64_t sqn,
+			   uint8_t const rand[MILENAGE_RAND_SIZE])
+{
+	uint8_t		mac_a[8], ak_buff[MILENAGE_AK_SIZE];
+	uint8_t		sqn_buff[MILENAGE_SQN_SIZE];
+	uint8_t		*p = autn;
+	size_t		i;
+
+	if ((milenage_f1(mac_a, NULL, opc, ki, rand,
+			 uint48_to_buff(sqn_buff, sqn), amf) < 0) ||
+	    (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1;
+
+	/*
+	 *	AUTN = (SQN ^ AK) || AMF || MAC_A
+	 */
+	for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i];
+	memcpy(p, amf, MILENAGE_AMF_SIZE);
+	p += MILENAGE_AMF_SIZE;
+	memcpy(p, mac_a, sizeof(mac_a));
+
+	/*
+	 *	Output the anonymity key if required
+	 */
+	if (ak) memcpy(ak, ak_buff, sizeof(ak_buff));
+
+	return 0;
+}
+
+/** Milenage AUTS validation
+ *
+ * @param[out] sqn	SQN = 48-bit sequence number (host byte order).
+ * @param[in] opc	128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] ki	128-bit subscriber key.
+ * @param[in] rand	128-bit random challenge.
+ * @param[in] auts	112-bit authentication token from client.
+ * @return
+ *	- 0 on success with sqn filled.
+ *	- -1 on failure.
+ */
+int milenage_auts(uint64_t *sqn,
+		  uint8_t const opc[MILENAGE_OPC_SIZE],
+		  uint8_t const ki[MILENAGE_KI_SIZE],
+		  uint8_t const rand[MILENAGE_RAND_SIZE],
+		  uint8_t const auts[MILENAGE_AUTS_SIZE])
+{
+	uint8_t		amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+	uint8_t		ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE];
+	uint8_t		sqn_buff[MILENAGE_SQN_SIZE];
+	size_t		i;
+
+	if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1;
+	for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i];
+
+	if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1;
+
+	*sqn = uint48_from_buff(sqn_buff);
+
+	return 0;
+}
+
+/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet
+ *
+ * @param[out] sres	Buffer for SRES = 32-bit SRES.
+ * @param[out] kc	64-bit Kc.
+ * @param[in] ik	128-bit integrity.
+ * @param[in] ck	Confidentiality key.
+ * @param[in] res	64-bit signed response.
+ */
+void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE],
+			    uint8_t kc[MILENAGE_KC_SIZE],
+			    uint8_t const ik[MILENAGE_IK_SIZE],
+			    uint8_t const ck[MILENAGE_CK_SIZE],
+			    uint8_t const res[MILENAGE_RES_SIZE])
+{
+	int i;
+
+	for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8];
+
+#ifdef GSM_MILENAGE_ALT_SRES
+	memcpy(sres, res, 4);
+#else	/* GSM_MILENAGE_ALT_SRES */
+	for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4];
+#endif	/* GSM_MILENAGE_ALT_SRES */
+}
+
+/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet
+ *
+ * @param[out] sres	Buffer for SRES = 32-bit SRES.
+ * @param[out] kc	64-bit Kc.
+ * @param[in] opc	128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] ki	128-bit subscriber key.
+ * @param[in] rand	128-bit random challenge.
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ */
+int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE],
+			  uint8_t kc[MILENAGE_KC_SIZE],
+			  uint8_t const opc[MILENAGE_OPC_SIZE],
+			  uint8_t const ki[MILENAGE_KI_SIZE],
+			  uint8_t const rand[MILENAGE_RAND_SIZE])
+{
+	uint8_t		res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE];
+
+	if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1;
+
+	milenage_gsm_from_umts(sres, kc, ik, ck, res);
+
+	return 0;
+}
+
+/** Milenage check
+ *
+ * @param[out] ik	Buffer for IK = 128-bit integrity key (f4), or NULL.
+ * @param[out] ck	Buffer for CK = 128-bit confidentiality key (f3), or NULL.
+ * @param[out] res	Buffer for RES = 64-bit signed response (f2), or NULL.
+ * @param[in] auts	112-bit buffer for AUTS.
+ * @param[in] opc	128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] ki	128-bit subscriber key.
+ * @param[in] sqn	48-bit sequence number.
+ * @param[in] rand	128-bit random challenge.
+ * @param[in] autn	128-bit authentication token.
+ * @return
+ *	- 0 on success.
+ *	- -1 on failure.
+ *	- -2 on synchronization failure
+ */
+int milenage_check(uint8_t ik[MILENAGE_IK_SIZE],
+		   uint8_t ck[MILENAGE_CK_SIZE],
+		   uint8_t res[MILENAGE_RES_SIZE],
+		   uint8_t auts[MILENAGE_AUTS_SIZE],
+		   uint8_t const opc[MILENAGE_OPC_SIZE],
+		   uint8_t const ki[MILENAGE_KI_SIZE],
+		   uint64_t sqn,
+		   uint8_t const rand[MILENAGE_RAND_SIZE],
+		   uint8_t const autn[MILENAGE_AUTN_SIZE])
+{
+
+	uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE];
+	uint8_t sqn_buff[MILENAGE_SQN_SIZE];
+	const uint8_t *amf;
+	size_t i;
+
+	uint48_to_buff(sqn_buff, sqn);
+
+	//FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN");
+	//FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND");
+
+	if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1;
+
+	//FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES");
+	//FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK");
+	//FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK");
+	//FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK");
+
+	/* AUTN = (SQN ^ AK) || AMF || MAC */
+	for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i];
+	//FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN");
+
+	if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) {
+		uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+
+		if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1;
+
+		//FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*");
+		for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i];
+
+		if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1;
+		//FR_PROTO_HEX_DUMP(auts, 14, "AUTS");
+		return -2;
+	}
+
+	amf = autn + 6;
+	//FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF");
+	if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1;
+
+	//FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A");
+
+	if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) {
+	  //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A");
+		fr_strerror_printf("MAC mismatch");
+		return -1;
+	}
+
+	return 0;
+}
+
+#ifdef TESTING_MILENAGE
+/*
+ *  cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage
+ */
+#include <freeradius-devel/util/acutest.h>
+
+void test_set_1(void)
+{
+	/*
+	 *	Inputs
+	 */
+	uint8_t ki[]		= { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f,
+				    0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc };
+	uint8_t rand[]		= { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d,
+				    0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35  };
+	uint8_t sqn[]		= { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 };
+	uint8_t amf[]		= { 0xb9, 0xb9 };
+	uint8_t op[]		= { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6,
+				    0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 };
+	uint8_t opc[]		= { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e,
+				    0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf };
+
+	/*
+	 *	Outputs
+	 */
+	uint8_t opc_out[MILENAGE_OPC_SIZE];
+	uint8_t	mac_a_out[MILENAGE_MAC_A_SIZE];
+	uint8_t	mac_s_out[MILENAGE_MAC_S_SIZE];
+	uint8_t res_out[MILENAGE_RES_SIZE];
+	uint8_t ck_out[MILENAGE_CK_SIZE];
+	uint8_t ik_out[MILENAGE_IK_SIZE];
+	uint8_t ak_out[MILENAGE_AK_SIZE];
+	uint8_t ak_resync_out[MILENAGE_AK_SIZE];
+
+	/* function 1 */
+	uint8_t mac_a[]		= { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 };
+	/* function 1* */
+	uint8_t mac_s[]		= { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 };
+	/* function 2 */
+	uint8_t res[]		= { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf };
+	/* function 3 */
+	uint8_t ck[]		= { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05,
+				    0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb };
+	/* function 4 */
+	uint8_t ik[]		= { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04,
+			    	    0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 };
+	/* function 5 */
+	uint8_t ak[]		= { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 };
+	/* function 5* */
+	uint8_t ak_resync[]	= { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b };
+
+	int ret = 0;
+
+/*
+	fr_debug_lvl = 4;
+*/
+	ret = milenage_opc_generate(opc_out, op, ki);
+	TEST_CHECK(ret == 0);
+
+	//FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc");
+
+	TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0);
+
+	if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) ||
+	    (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1;
+
+	//FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a");
+	//FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s");
+	//FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik");
+	//FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck");
+	//FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res");
+	//FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak");
+	//FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync");
+
+	TEST_CHECK(ret == 0);
+	TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0);
+	TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0);
+	TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0);
+	TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0);
+	TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0);
+	TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0);
+	TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0);
+}
+
+void test_set_19(void)
+{
+	/*
+	 *	Inputs
+	 */
+	uint8_t ki[]		= { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72,
+				    0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 };
+	uint8_t rand[]		= { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e,
+				    0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 };
+	uint8_t sqn[]		= { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 };
+	uint8_t amf[]		= { 0xc3, 0xab };
+	uint8_t op[]		= { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff,
+				    0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b };
+	uint8_t opc[]		= { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e,
+				    0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf };
+
+	/*
+	 *	Outputs
+	 */
+	uint8_t opc_out[MILENAGE_OPC_SIZE];
+	uint8_t	mac_a_out[MILENAGE_MAC_A_SIZE];
+	uint8_t	mac_s_out[MILENAGE_MAC_S_SIZE];
+	uint8_t res_out[MILENAGE_RES_SIZE];
+	uint8_t ck_out[MILENAGE_CK_SIZE];
+	uint8_t ik_out[MILENAGE_IK_SIZE];
+	uint8_t ak_out[MILENAGE_AK_SIZE];
+	uint8_t ak_resync_out[MILENAGE_AK_SIZE];
+
+	/* function 1 */
+	uint8_t mac_a[]		= { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 };
+	/* function 1* */
+	uint8_t mac_s[]		= { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 };
+	/* function 2 */
+	uint8_t res[]		= { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 };
+	/* function 3 */
+	uint8_t ck[]		= { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94,
+				    0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f };
+	/* function 4 */
+	uint8_t ik[]		= { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb,
+				    0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a };
+	/* function 5 */
+	uint8_t ak[]		= { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 };
+	/* function 5* */
+	uint8_t ak_resync[]	= { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d };
+
+	int ret = 0;
+
+/*
+	fr_debug_lvl = 4;
+*/
+
+	ret = milenage_opc_generate(opc_out, op, ki);
+	TEST_CHECK(ret == 0);
+
+	//FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc");
+
+	TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0);
+
+	if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) ||
+	    (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1;
+
+	//FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a");
+	//FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s");
+	//FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik");
+	//FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck");
+	//FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res");
+	//FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak");
+	//FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync");
+
+	TEST_CHECK(ret == 0);
+	TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0);
+	TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0);
+	TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0);
+	TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0);
+	TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0);
+	TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0);
+	TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0);
+}
+
+TEST_LIST = {
+	{ "test_set_1",		test_set_1 },
+	{ "test_set_19",	test_set_19 },
+	{ NULL }
+};
+#endif
diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h
new file mode 100644
index 0000000000..758383aba2
--- /dev/null
+++ b/src/modules/rlm_wimax/milenage.h
@@ -0,0 +1,128 @@
+#pragma once
+/**
+ * @file src/modules/rlm_wimax/milenage.h
+ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
+ *
+ * This file implements an example authentication algorithm defined for 3GPP
+ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
+ * EAP-AKA to be tested properly with real USIM cards.
+ *
+ * This implementations assumes that the r1..r5 and c1..c5 constants defined in
+ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
+ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
+ * be AES (Rijndael).
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ *
+ * @copyright 2017 The FreeRADIUS server project
+ * @copyright 2006-2007 (j@w1.fi)
+ */
+#include <stddef.h>
+
+/*
+ *	Inputs
+ */
+#define MILENAGE_KI_SIZE	16		//!< Subscriber key.
+#define MILENAGE_OP_SIZE	16		//!< Operator code (unique to the operator)
+#define MILENAGE_OPC_SIZE	16		//!< Derived operator code (unique to the operator and subscriber).
+#define MILENAGE_AMF_SIZE	2		//!< Authentication management field.
+#define MILENAGE_SQN_SIZE	6		//!< Sequence number.
+#define MILENAGE_RAND_SIZE	16		//!< Random challenge.
+
+/*
+ *	UMTS Outputs
+ */
+#define MILENAGE_AK_SIZE	6		//!< Anonymisation key.
+#define MILENAGE_AUTN_SIZE	16		//!< Network authentication key.
+#define MILENAGE_IK_SIZE	16		//!< Integrity key.
+#define	MILENAGE_CK_SIZE	16		//!< Ciphering key.
+#define MILENAGE_RES_SIZE	8
+#define MILENAGE_AUTS_SIZE	14
+
+/*
+ *	GSM (COMP128-4) outputs
+ */
+#define MILENAGE_SRES_SIZE	4
+#define MILENAGE_KC_SIZE	8
+
+/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order
+ *
+ * There may be fast ways of doing this, but this is the *correct*
+ * way, and does not make assumptions about how integers are laid
+ * out in memory.
+ *
+ * @param[out] out      6 byte butter to store value.
+ * @param[in] i         integer value.
+ * @return pointer to out.
+ */
+static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i)
+{
+	out[0] = (i & 0xff0000000000) >> 40;
+	out[1] = (i & 0x00ff00000000) >> 32;
+	out[2] = (i & 0x0000ff000000) >> 24;
+	out[3] = (i & 0x000000ff0000) >> 16;
+	out[4] = (i & 0x00000000ff00) >> 8;
+	out[5] = (i & 0x0000000000ff);
+
+	return out;
+}
+
+/** Convert a 48bit big endian value into a unsigned 64bit integer
+ *
+ */
+static inline uint64_t uint48_from_buff(uint8_t const in[6])
+{
+	uint64_t i = 0;
+
+	i |= ((uint64_t)in[0]) << 40;
+	i |= ((uint64_t)in[1]) << 32;
+	i |= ((uint32_t)in[2]) << 24;
+	i |= ((uint32_t)in[3]) << 16;
+	i |= ((uint16_t)in[4]) << 8;
+	i |= in[5];
+
+	return i;
+}
+
+int	milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE],
+			      uint8_t const op[MILENAGE_OP_SIZE],
+			      uint8_t const ki[MILENAGE_KI_SIZE]);
+
+int	milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE],
+			       uint8_t ik[MILENAGE_IK_SIZE],
+			       uint8_t ck[MILENAGE_CK_SIZE],
+			       uint8_t ak[MILENAGE_AK_SIZE],
+			       uint8_t res[MILENAGE_RES_SIZE],
+			       uint8_t const opc[MILENAGE_OPC_SIZE],
+			       uint8_t const amf[MILENAGE_AMF_SIZE],
+			       uint8_t const ki[MILENAGE_KI_SIZE],
+			       uint64_t sqn,
+			       uint8_t const rand[MILENAGE_RAND_SIZE]);
+
+int	milenage_auts(uint64_t *sqn,
+		      uint8_t const opc[MILENAGE_OPC_SIZE],
+		      uint8_t const ki[MILENAGE_KI_SIZE],
+		      uint8_t const rand[MILENAGE_RAND_SIZE],
+		      uint8_t const auts[MILENAGE_AUTS_SIZE]);
+
+void	milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE],
+			       uint8_t kc[MILENAGE_KC_SIZE],
+			       uint8_t const ik[MILENAGE_IK_SIZE],
+			       uint8_t const ck[MILENAGE_CK_SIZE],
+			       uint8_t const res[MILENAGE_RES_SIZE]);
+
+int	milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE],
+			      uint8_t const opc[MILENAGE_OPC_SIZE],
+			      uint8_t const ki[MILENAGE_KI_SIZE],
+			      uint8_t const rand[MILENAGE_RAND_SIZE]);
+
+int	milenage_check(uint8_t ik[MILENAGE_IK_SIZE],
+		       uint8_t ck[MILENAGE_CK_SIZE],
+		       uint8_t res[MILENAGE_RES_SIZE],
+		       uint8_t auts[MILENAGE_AUTS_SIZE],
+		       uint8_t const opc[MILENAGE_OPC_SIZE],
+		       uint8_t const ki[MILENAGE_KI_SIZE],
+		       uint64_t sqn,
+		       uint8_t const rand[MILENAGE_RAND_SIZE],
+		       uint8_t const autn[MILENAGE_AUTN_SIZE]);
diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c
index f0fb394fcd..d2125eb3a5 100644
--- a/src/modules/rlm_wimax/rlm_wimax.c
+++ b/src/modules/rlm_wimax/rlm_wimax.c
@@ -26,16 +26,43 @@ USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */
 
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/modules.h>
+#include "milenage.h"
 
 #ifdef HAVE_OPENSSL_HMAC_H
 #include <openssl/hmac.h>
 #endif
 
+#include <freeradius-devel/openssl3.h>
+
+#define WIMAX_EPSAKA_RAND_SIZE	16
+#define WIMAX_EPSAKA_KI_SIZE	16
+#define WIMAX_EPSAKA_OPC_SIZE	16
+#define WIMAX_EPSAKA_AMF_SIZE	2
+#define WIMAX_EPSAKA_SQN_SIZE	6
+#define WIMAX_EPSAKA_MAC_A_SIZE	8
+#define WIMAX_EPSAKA_MAC_S_SIZE	8
+#define WIMAX_EPSAKA_XRES_SIZE	8
+#define WIMAX_EPSAKA_CK_SIZE	16
+#define WIMAX_EPSAKA_IK_SIZE	16
+#define WIMAX_EPSAKA_AK_SIZE	6
+#define WIMAX_EPSAKA_AK_RESYNC_SIZE	6
+#define WIMAX_EPSAKA_KK_SIZE	32
+#define WIMAX_EPSAKA_KS_SIZE	14
+#define WIMAX_EPSAKA_PLMN_SIZE	3
+#define WIMAX_EPSAKA_KASME_SIZE	32
+#define WIMAX_EPSAKA_AUTN_SIZE	16
+#define WIMAX_EPSAKA_AUTS_SIZE  14
+
 /*
  *	FIXME: Fix the build system to create definitions from names.
  */
 typedef struct rlm_wimax_t {
 	bool	delete_mppe_keys;
+
+	DICT_ATTR const	*resync_info;
+	DICT_ATTR const	*xres;
+	DICT_ATTR const	*autn;
+	DICT_ATTR const	*kasme;
 } rlm_wimax_t;
 
 /*
@@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = {
 	CONF_PARSER_TERMINATOR
 };
 
+/*
+ *	Print hex values in a readable format for debugging
+ *	Example:
+ *	FOO: 00 11 AA 22 00 FF
+ */
+static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len)
+{
+	int i;
+	char buffer[256];	/* large enough for largest len */
+
+	/*
+	 *	Leave a trailing space, we don't really care about that.
+	 */
+	for (i = 0; i < len; i++) {
+		snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]);
+	}
+
+	RDEBUG("%s %s", prefix, buffer);
+}
+#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex
+
 /*
  *	Find the named user in this modules database.  Create the set
  *	of attribute-value pairs to check and reply with for this user
  *	from the database. The authentication code only needs to check
  *	the password, the rest is done here.
  */
-static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request)
+static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
 {
 	VALUE_PAIR *vp;
+	rlm_wimax_t *inst = instance;
 
 	/*
 	 *	Fix Calling-Station-Id.  Damn you, WiMAX!
@@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST
 		return RLM_MODULE_OK;
 	}
 
+	/*
+	 *	Check for attr WiMAX-Re-synchronization-Info
+	 *	which contains the concatenation of RAND and AUTS
+	 *
+	 *	If it is present then we proceed to verify the SIM and
+	 *	extract the new value of SQN
+	 */
+	VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand;
+	int m_ret;
+
+	/* Look for the Re-synchronization-Info attribute in the request */
+	resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY);
+	if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) {
+		RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it");
+		resync_info = NULL;
+	}
+
+	/*
+	 *	These are the private keys which should be added to the control
+	 *	list after looking them up in a database by IMSI
+	 *
+	 *	We grab them from the control list here
+	 */
+	ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY);
+	if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it");
+		ki = NULL;
+	}
+
+	opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY);
+	if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it");
+		opc = NULL;
+	}
+
+	/* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */
+	if (resync_info && ki && opc) {
+		uint64_t sqn_bin;
+		uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE];
+		uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE];
+
+		RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync");
+
+		/* Split Re-synchronization-Info into seperate RAND and AUTS */
+
+		memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE);
+		memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE);
+
+		RDEBUG_HEX(request, "RAND  ", rand_bin, WIMAX_EPSAKA_RAND_SIZE);
+		RDEBUG_HEX(request, "AUTS  ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE);
+
+		/*
+		 *	This procedure uses the secret keys Ki and OPc to authenticate
+		 *	the SIM and extract the SQN
+		 */
+		m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin);
+		
+		/*
+		 *	If the SIM verification fails then we can't go any further as
+		 *	we don't have the keys. And that probably means something bad
+		 *	is happening so we bail out now
+		 */
+		if (m_ret < 0) {
+			RDEBUG("SIM verification failed");
+		  	return RLM_MODULE_REJECT;
+		}
+
+		/*
+		 *	If we got this far it means have got a new SQN and RAND
+		 *	so we store them in:
+		 *	control:WiMAX-SIM-SQN
+		 *	control:WiMAX-SIM-RAND
+		 *
+		 *	From there they can be grabbed by unlang and used later
+		 */
+
+		/* SQN is six bytes so we extract what we need from the 64 bit variable */
+		uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = {
+			(sqn_bin & 0x0000FF0000000000ull) >> 40,
+			(sqn_bin & 0x000000FF00000000ull) >> 32,
+			(sqn_bin & 0x00000000FF000000ull) >> 24,
+			(sqn_bin & 0x0000000000FF0000ull) >> 16,
+			(sqn_bin & 0x000000000000FF00ull) >>  8,
+			(sqn_bin & 0x00000000000000FFull) >>  0
+		};
+
+		/* Add SQN to control:WiMAX-SIM-SQN */
+		sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY);
+		if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) {
+			RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it");
+			sqn = NULL;
+		}
+
+		if (!sqn) {
+			MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET));
+			fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE);
+		}
+		RDEBUG_HEX(request, "SQN   ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE);
+
+		/* Add RAND to control:WiMAX-SIM-RAND */
+		rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY);
+		if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) {
+			RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it");
+			rand = NULL;
+		}
+
+		if (!rand) {
+			MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET));
+			fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE);
+		}
+		RDEBUG_HEX(request, "RAND  ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE);
+
+		return RLM_MODULE_UPDATED;
+	}
+
 	return RLM_MODULE_NOOP;
 }
 
-
 /*
  *	Massage the request before recording it or proxying it
  */
@@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request
 	return mod_authorize(instance, request);
 }
 
-/*
- *	Write accounting information to this modules database.
- */
-static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request)
-{
-	return RLM_MODULE_OK;
-}
 
 /*
- *	Generate the keys after the user has been authenticated.
+ *	This function generates the keys for old style WiMAX (v1 to v2.0)
  */
-static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
+static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk)
 {
 	rlm_wimax_t *inst = instance;
-	VALUE_PAIR *msk, *emsk, *vp;
+	VALUE_PAIR *vp;
 	VALUE_PAIR *mn_nai, *ip, *fa_rk;
 	HMAC_CTX *hmac;
 	unsigned int rk1_len, rk2_len, rk_len;
@@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE];
 	uint8_t mip_rk[2 * EVP_MAX_MD_SIZE];
 
-	msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY);
-	emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY);
-	if (!msk || !emsk) {
-		RDEBUG("No EAP-MSK or EAP-EMSK.  Cannot create WiMAX keys");
-		return RLM_MODULE_NOOP;
-	}
-
 	/*
 	 *	If we delete the MS-MPPE-*-Key attributes, then add in
 	 *	the WiMAX-MSK so that the client has a key available.
@@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 		fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
 		fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
 
-		vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ);
-		if (vp) {
-			fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length);
-		}
+		MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ));
+		fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length);
 	}
 
 	/*
@@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	 */
 	hmac = HMAC_CTX_new();
 	HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL);
+	rk1_len = SHA256_DIGEST_LENGTH;
 
 	HMAC_Update(hmac, &usage_data[0], sizeof(usage_data));
 	HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
@@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 
 	HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len);
 	HMAC_Update(hmac, &usage_data[0], sizeof(usage_data));
+	rk2_len = SHA256_DIGEST_LENGTH;
 	HMAC_Final(hmac, &mip_rk_2[0], &rk2_len);
 
 	memcpy(mip_rk, mip_rk_1, rk1_len);
@@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL);
 
 	HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12);
+	rk1_len = SHA256_DIGEST_LENGTH;
 	HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
 
 	/*
@@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 		   (mip_rk_1[2] << 8) | mip_rk_1[3]);
 	if (mip_spi < 256) mip_spi += 256;
 
-	if (rad_debug_lvl) {
-		int len = rk_len;
-		char buffer[512];
-
-		if (len > 128) len = 128; /* buffer size */
-
-		fr_bin2hex(buffer, mip_rk, len);
-		RDEBUG("MIP-RK = 0x%s", buffer);
-		RDEBUG("MIP-SPI = %08x", ntohl(mip_spi));
-	}
+	RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len);
+	RDEBUG("MIP-SPI = %08x", ntohl(mip_spi));
 
 	/*
 	 *	FIXME: Perform SPI collision prevention
@@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 		HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11);
 		HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4);
 		HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
+		rk1_len = SHA1_DIGEST_LENGTH;
 		HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
 
 		/*
@@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 		HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11);
 		HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4);
 		HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
+		rk1_len = SHA1_DIGEST_LENGTH;
 		HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
 
 		/*
@@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 		HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11);
 		HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16);
 		HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length);
+		rk1_len = SHA1_DIGEST_LENGTH;
 		HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
 
 		/*
@@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 
 		HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5);
 
+		rk1_len = SHA1_DIGEST_LENGTH;
 		HMAC_Final(hmac, &mip_rk_1[0], &rk1_len);
 
 		fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len);
@@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque
 	return RLM_MODULE_UPDATED;
 }
 
+/*
+ *	Generate the EPS-AKA authentication vector
+ *
+ *	These are the keys needed for new style WiMAX (LTE / 3gpp authentication),
+ 	for WiMAX v2.1
+ */
+static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc,
+				     VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn)
+{  
+	size_t i;
+	VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme;
+
+	/*
+	 *	For most authentication requests we need to generate a fresh RAND
+	 *
+	 *	The exception is after SQN re-syncronisation - in this case we
+	 *	get RAND in the request, and this module if called in authorize should
+	 *	have put it in control:WiMAX-SIM-RAND so we can grab it from there)
+	 */
+	rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY);
+	if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size.  Ignoring it.");
+		rand_previous = NULL;
+	}
+
+	MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET));
+	if (!rand_previous) {
+		uint32_t lvalue;
+		uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE];
+
+		for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) {
+			lvalue = fr_rand();
+			memcpy(buffer + i * 4, &lvalue, sizeof(lvalue));
+		}
+
+		fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE);
+
+	} else {
+		fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE);
+	}
+
+   	/*
+	 *	Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5)
+	 *	which returns AUTN, AK, CK, IK, XRES.
+	 */
+	uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE];
+	uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE];
+	uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE];
+	uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE];
+	uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE];
+
+	/* But first convert uint8 SQN to uint64 */
+	uint64_t sqn_bin = 0x0000000000000000;
+	for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i];
+
+	if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size.  Ignoring it");
+		return RLM_MODULE_NOOP;
+	}
+	if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size.  Ignoring it");
+		return RLM_MODULE_NOOP;
+	}
+	if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) {
+		RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size.  Ignoring it");
+		return RLM_MODULE_NOOP;
+	}
+
+	/* Call milenage */
+	milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets,
+			       amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets);
+
+	/*
+	 *	Now we genertate KASME
+	 *
+	 *	Officially described in 33401-g30.doc section A.2
+	 *	But an easier to read explanation can be found at:
+	 *	https://medium.com/uw-ictd/lte-authentication-2d0810a061ec
+	 *
+	*/
+
+	/* k = CK || IK */
+	uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE];
+	memcpy(kk_bin, ck_bin, sizeof(ck_bin));
+	memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin));
+
+	/* Initialize a 14 byte buffer s */
+	uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE];
+
+	/* Assign the first byte of s as 0x10 */
+	ks_bin[0] = 0x10;
+      
+	/* Copy the 3 bytes of PLMN into s */
+	memcpy(ks_bin + 1, plmn->vp_octets, 3);
+
+	/* Assign 5th and 6th byte as 0x00 and 0x03 */
+	ks_bin[4] = 0x00;
+	ks_bin[5] = 0x03;
+      
+	/* Assign the next 6 bytes as SQN XOR AK */
+	for (i = 0; i < 6; i++) {
+		ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i];
+	}
+      
+	/* Assign the last two bytes as 0x00 and 0x06 */
+	ks_bin[12] = 0x00;
+	ks_bin[13] = 0x06;
+
+	/* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */
+	uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE];
+	HMAC_CTX *hmac;
+	unsigned int kasme_len = sizeof(kasme_bin);
+
+	hmac = HMAC_CTX_new();
+	HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL);
+	HMAC_Update(hmac, ks_bin, sizeof(ks_bin));
+	kasme_len = SHA256_DIGEST_LENGTH;
+	HMAC_Final(hmac, &kasme_bin[0], &kasme_len);
+	HMAC_CTX_free(hmac);
+
+	/*
+	 *	Add reply attributes XRES, AUTN and KASME (RAND we added earlier)
+	 *
+	 *	Note that we can't call fr_pair_find_by_num(), as
+	 *	these attributes are buried deep inside of the WiMAX
+	 *	hierarchy.
+	 */
+	xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY);
+	if (!xres) {
+		MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET));
+		fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE);
+	}
+
+	autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY);
+	if (!autn) {
+		MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET));
+		fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE);
+	}
+
+	kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY);
+	if (!kasme) {
+		MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET));
+		fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE);
+	}
+
+	/* Print keys to log for debugging */
+	if (rad_debug_lvl) {
+		RDEBUG("-------- Milenage in --------");
+		RDEBUG_HEX(request, "OPc   ", opc->vp_octets, opc->vp_length);
+		RDEBUG_HEX(request, "Ki    ", ki->vp_octets, ki->vp_length);
+		RDEBUG_HEX(request, "RAND  ", rand->vp_octets, rand->vp_length);
+		RDEBUG_HEX(request, "SQN   ", sqn->vp_octets, sqn->vp_length);
+		RDEBUG_HEX(request, "AMF   ", amf->vp_octets, amf->vp_length);
+		RDEBUG("-------- Milenage out -------");
+		RDEBUG_HEX(request, "XRES  ", xres->vp_octets, xres->vp_length);
+		RDEBUG_HEX(request, "Ck    ", ck_bin, sizeof(ck_bin));
+		RDEBUG_HEX(request, "Ik    ", ik_bin, sizeof(ik_bin));
+		RDEBUG_HEX(request, "Ak    ", ak_bin, sizeof(ak_bin));
+		RDEBUG_HEX(request, "AUTN  ", autn->vp_octets, autn->vp_length);
+		RDEBUG("-----------------------------");
+		RDEBUG_HEX(request, "Kk    ", kk_bin, sizeof(kk_bin));
+		RDEBUG_HEX(request, "Ks    ", ks_bin, sizeof(ks_bin));
+		RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length);
+	}
+
+	return RLM_MODULE_UPDATED;
+}
+
+/*
+ *	Generate the keys after the user has been authenticated.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
+{
+	VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn;
+
+	/*
+	 *	If we have MSK and EMSK then assume we want MIP keys
+	 *	Else if we have the SIM keys then we want the EPS-AKA vector
+	 */
+
+	msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY);
+	emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY);
+
+	if (msk && emsk) {
+		RDEBUG("MSK and EMSK found.  Generating MIP keys");
+		return mip_keys_generate(instance, request, msk, emsk);
+	}
+
+	ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY);
+	opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY);
+	amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY);
+	sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY);
+	plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY);
+	
+	if (ki && opc && amf && sqn && plmn) {
+		RDEBUG("AKA attributes found.  Generating AKA keys.");
+		return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn);
+	}
+
+	RDEBUG("Input keys not found.  Cannot create WiMAX keys");
+	return RLM_MODULE_NOOP;
+}
+
+
+static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
+{
+	rlm_wimax_t *inst = instance;
+      
+	inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info");
+	inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES");
+	inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN");
+	inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME");
+
+	return 0;
+}
 
 /*
  *	The module name should be the only globally exported symbol.
@@ -472,10 +833,10 @@ module_t rlm_wimax = {
 	.type		= RLM_TYPE_THREAD_SAFE,
 	.inst_size	= sizeof(rlm_wimax_t),
 	.config		= module_config,
+	.instantiate	= mod_instantiate,
 	.methods = {
 		[MOD_AUTHORIZE]		= mod_authorize,
 		[MOD_PREACCT]		= mod_preacct,
-		[MOD_ACCOUNTING]	= mod_accounting,
 		[MOD_POST_AUTH]		= mod_post_auth
 	},
 };
diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4
new file mode 100644
index 0000000000..7e9b1ffcdf
--- /dev/null
+++ b/src/tests/keywords/md4
@@ -0,0 +1,58 @@
+#
+# PRE: update if
+#
+update reply {
+	Filter-Id := "filter"
+}
+
+update {
+       control:Cleartext-Password := 'hello'
+       request:Tmp-String-0 := "This is a string\n"
+       request:Tmp-Octets-0 := 0x000504030201
+       request:Tmp-String-1 := "what do ya want for nothing?"
+       request:Tmp-String-2 := "Jefe"
+}
+
+#
+#  Put "This is a string" into a file and call "md5sum" on it.
+#  You should get this string.
+#
+if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+	update reply {
+		Filter-Id += 'fail 1'
+	}
+}
+
+if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+	update reply {
+		Filter-Id += 'fail 2'
+	}
+}
+
+if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+	update reply {
+		Filter-Id += 'fail 3'
+	}
+}
+
+if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+	update reply {
+		Filter-Id += 'fail 4'
+	}
+}
+
+#
+#  MD4 should also be able to cope with references to octet attributes
+#
+if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') {
+	update reply {
+		Filter-Id += 'fail 5'
+	}
+}
+
+if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') {
+	update reply {
+		Filter-Id += 'fail 6'
+	}
+}
+