diff --git a/.freeradius.metadata b/.freeradius.metadata index 0a034f2..c81a328 100644 --- a/.freeradius.metadata +++ b/.freeradius.metadata @@ -1 +1 @@ -3d90d63bf1452794cf9d0b04147745a254872c3f SOURCES/freeradius-server-3.0.21.tar.bz2 +ed60f0b090ad118b38687c3b825fc115c5820f0a SOURCES/freeradius-server-3.0.27.tar.bz2 diff --git a/.gitignore b/.gitignore index 40e6bae..c69a601 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/freeradius-server-3.0.21.tar.bz2 +SOURCES/freeradius-server-3.0.27.tar.bz2 diff --git a/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch b/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch deleted file mode 100644 index 4b6efb4..0000000 --- a/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch +++ /dev/null @@ -1,18836 +0,0 @@ -Date: Tue, 11 Jan 2022 -Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.x - -Backport TLS and OpenSSL3 fixes from the 3.0.x branch as of May 24th, 2022. - -Related: rhbz#1978216 -Related: rhbz#2083699 -Related: rhbz#2263240 -Related: RHEL-93552 -Signed-off-by: Antonio Torres - -[antorres@redhat.com]: these changes include the macro WITH_FIPS, which allows FreeRADIUS -to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this macro on the specfile. -[antorres@redhat.com]: backported tls.c, tls-h changes from 3.2.x branch. -[antorres@redhat.com]: the sites-available/tls file has been modified to add the fix_cert_order option. -[antorres@redhat.com]: mods-available/eap has been modified to comment out 'disable_tlsv1' and 'dh_file' options. -[antorres@redhat.com]: add fix for BlastRADIUS CVE, commit range backported: 3a00a6ecc188629b0441fd45ad61ca8986de156e^..da643f1edc267ce95260dc36069e6f1a7a4d66f8, -this backport includes changes from other files not included in the commit range, to ensure correct compilation. -[antorres@redhat.com]: add backport from https://github.com/FreeRADIUS/freeradius-server/commit/3a9449539e4c5a74c85685cad6abe6edf412f701. -[antorres@redhat.com]: sites-available/tls and mods-available/eap have been updated to include the ca_path_reload_interval option. ---- - man/man1/radclient.1 | 10 +- - man/man1/radtest.1 | 13 +- - raddb/clients.conf | 98 +- - raddb/mods-available/eap | 17 +- - raddb/proxy.conf | 83 +- - raddb/radiusd.conf.in | 280 ++- - raddb/sites-available/tls | 18 + - share/dictionary.freeradius | 5 + - share/dictionary.freeradius.internal | 54 +- - src/include/build.h | 29 +- - src/include/clients.h | 16 +- - src/include/conffile.h | 2 + - src/include/event.h | 3 + - src/include/libradius.h | 43 +- - src/include/listen.h | 30 +- - src/include/md4.h | 50 +- - src/include/md5.h | 33 +- - src/include/missing-h | 4 + - src/include/openssl3.h | 109 ++ - src/include/process.h | 9 + - src/include/radius.h | 1 + - src/include/radiusd.h | 27 +- - src/include/realms.h | 23 +- - src/include/socket.h | 53 + - src/include/tls-h | 45 +- - src/include/token.h | 7 +- - src/lib/dict.c | 150 +- - src/lib/event.c | 153 +- - src/lib/hmacmd5.c | 6 +- - src/lib/hmacsha1.c | 5 +- - src/lib/md4.c | 1 + - src/lib/md5.c | 1 + - src/lib/pair.c | 97 +- - src/lib/print.c | 19 +- - src/lib/radius.c | 428 ++++- - src/lib/token.c | 24 +- - src/main/cb.c | 121 +- - src/main/client.c | 255 ++- - src/main/command.c | 220 ++- - src/main/conffile.c | 71 +- - src/main/listen.c | 582 +++++- - src/main/mainconfig.c | 130 +- - src/main/map.c | 54 +- - src/main/process.c | 640 +++++-- - src/main/radclient.c | 231 ++- - src/main/radiusd.c | 1 + - src/main/radtest.in | 8 +- - src/main/realms.c | 354 +++- - src/main/session.c | 33 +- - src/main/stats.c | 185 +- - src/main/tls.c | 2012 ++++++++++++++++---- - src/main/tls_listen.c | 509 ++++- - src/modules/proto_dhcp/rlm_dhcp.c | 2 +- - src/modules/rlm_eap/libeap/eap_tls.c | 178 +- - src/modules/rlm_eap/libeap/eap_tls.h | 10 +- - src/modules/rlm_eap/libeap/mppe_keys.c | 211 +- - src/modules/rlm_eap/radeapclient.c | 8 + - src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c | 51 +- - .../rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c | 64 +- - src/modules/rlm_eap/types/rlm_eap_peap/peap.c | 22 +- - .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c | 21 +- - src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h | 190 ++ - src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c | 779 +++++--- - src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h | 16 +- - .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c | 508 ++++- - .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h | 2 + - .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c | 52 +- - .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h | 5 + - .../rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c | 20 +- - src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c | 25 +- - src/modules/rlm_exec/rlm_exec.c | 4 +- - src/modules/rlm_expr/rlm_expr.c | 115 ++ - src/modules/rlm_files/rlm_files.c | 2 +- - src/modules/rlm_ldap/ldap.h | 4 + - src/modules/rlm_mschap/rlm_mschap.c | 99 +- - src/modules/rlm_otp/otp_mppe.c | 16 +- - src/modules/rlm_otp/otp_radstate.c | 3 +- - src/modules/rlm_rest/rest.c | 107 +- - src/modules/rlm_rest/rest.h | 18 + - src/modules/rlm_rest/rlm_rest.c | 12 + - src/modules/rlm_wimax/milenage.c | 642 +++++++ - src/modules/rlm_wimax/milenage.h | 128 ++ - src/modules/rlm_wimax/rlm_wimax.c | 429 ++++- - src/tests/keywords/md4 | 58 + - 84 files changed, 9251 insertions(+), 1902 deletions(-) - -diff --git a/man/man1/radclient.1 b/man/man1/radclient.1 -index 229dcae0c7..b83bee931a 100644 ---- a/man/man1/radclient.1 -+++ b/man/man1/radclient.1 -@@ -1,10 +1,11 @@ --.TH RADCLIENT 1 "22 March 2019" "" "FreeRADIUS Daemon" -+.TH RADCLIENT 1 "21 May 2024" "" "FreeRADIUS Daemon" - .SH NAME - radclient - send packets to a RADIUS server, show reply - .SH SYNOPSIS - .B radclient - .RB [ \-4 ] - .RB [ \-6 ] -+.RB [ \-b ] - .RB [ \-c - .IR count ] - .RB [ \-d -@@ -52,6 +53,13 @@ automatically encrypted before the packet is sent to the server. - Use IPv4 (default) - .IP \-6 - Use IPv6 -+.IP \-b -+Enforce the Blast RADIUS checks. All replies to an Access-Request packet -+must contain a Message-Authenticator as the first attribute. -+ -+For compatibility with old servers, this flag is not set by default. -+However, radclient still checks for the Blast RADIUS signature, and -+discards packets which match the attack. - .IP \-c\ \fIcount\fP - Send each packet \fIcount\fP times. - .IP \-d\ \fIraddb_directory\fP -diff --git a/man/man1/radtest.1 b/man/man1/radtest.1 -index b3184779c0..d90651aba8 100644 ---- a/man/man1/radtest.1 -+++ b/man/man1/radtest.1 -@@ -1,4 +1,4 @@ --.TH RADTEST 1 "5 April 2010" "" "FreeRADIUS Daemon" -+.TH RADTEST 1 "21 May 2024" "" "FreeRADIUS Daemon" - .SH NAME - radtest - send packets to a RADIUS server, show reply - .SH SYNOPSIS -@@ -15,6 +15,8 @@ radtest - send packets to a RADIUS server, show reply - .IR ] - .RB [ \-6 - .IR ] -+.RB [ \-b -+.IR ] - .I user password radius-server nas-port-number secret - .RB [ ppphint ] - .RB [ nasname ] -@@ -26,6 +28,13 @@ way to test a radius server. - - .SH OPTIONS - -+.IP \-b -+Enforce the Blast RADIUS checks. All replies to an Access-Request packet -+must contain a Message-Authenticator as the first attribute. -+ -+For compatibility with old servers, this flag is not set by default. -+However, radclient still checks for the Blast RADIUS signature, and -+discards packets which match the attack. - .IP "\-d \fIraddb_directory\fP" - The directory that contains the RADIUS dictionary files. Defaults to - \fI/etc/raddb\fP. -@@ -35,7 +44,7 @@ Use \fIproto\fP transport protocol ("tcp" or "udp"). - Only available if FreeRADIUS is compiled with TCP transport support. - - .IP "\-t \fIpap/chap/mschap/eap-md5\fP" --Choose the authentiction method to use. e.g. "-t pap", "-t chap", "-t -+Choose the authentication method to use. e.g. "-t pap", "-t chap", "-t - mschap", or "-t eap-md5",. Defaults to "pap". Using EAP-MD5 requires - that the "radeapclient" program is installed. - -diff --git a/raddb/clients.conf b/raddb/clients.conf -index 76b300d3c5..28bd6863b5 100644 ---- a/raddb/clients.conf -+++ b/raddb/clients.conf -@@ -82,17 +82,33 @@ client localhost { - # Quotation marks can be entered by escaping them, - # e.g. "foo\"bar" - # -- # A note on security: The security of the RADIUS protocol -+ # A note on security: The security of the RADIUS protocol - # depends COMPLETELY on this secret! We recommend using a -- # shared secret that is composed of: -+ # shared secret that at LEAST 16 characters long. It should -+ # preferably be 32 characters in length. The secret MUST be -+ # random, and should not be words, phrase, or anything else -+ # that is recognisable. - # -- # upper case letters -- # lower case letters -- # numbers -+ # Computing power has increased enormously since RADIUS was -+ # first defined. A hobbyist with a high-end GPU can try ALL -+ # of the 8-character shared secrets in about a day. The -+ # security of shared secrets increases MUCH more with the -+ # length of the shared secret, than with number of different -+ # characters used in it. So don't bother trying to use -+ # "special characters" or anything else in an attempt to get -+ # un-guessable secrets. Instead, just get data from a secure -+ # random number generator, and use that. - # -- # And is at LEAST 8 characters long, preferably 16 characters in -- # length. The secret MUST be random, and should not be words, -- # phrase, or anything else that is recognisable. -+ # You should create shared secrets using a method like this: -+ # -+ # dd if=/dev/random bs=1 count=24 | base64 -+ # -+ # This process will give output which takes 24 random bytes, -+ # and converts them to 32 characters of ASCII. The output -+ # should be accepted by all RADIUS clients. -+ # -+ # You should NOT create shared secrets by hand. They will -+ # not be random. They will will be trivial to crack. - # - # The default secret below is only for testing, and should - # not be used in any real environment. -@@ -100,15 +116,45 @@ client localhost { - secret = testing123 - - # -- # Old-style clients do not send a Message-Authenticator -- # in an Access-Request. RFC 5080 suggests that all clients -- # SHOULD include it in an Access-Request. The configuration -- # item below allows the server to require it. If a client -- # is required to include a Message-Authenticator and it does -- # not, then the packet will be silently discarded. -+ # The global configuration "security.require_message_authenticator" -+ # flag sets the default for all clients. That default can be -+ # over-ridden here, by setting it to a value. If no value is set, -+ # then the default from the "radiusd.conf" file is used. -+ # -+ # See that file for full documentation on the flag, along -+ # with allowed values and meanings. -+ # -+ # This flag exists solely for legacy clients which do not send -+ # Message-Authenticator in all Access-Request packets. We do not -+ # recommend setting it to "no". - # -- # allowed values: yes, no -- require_message_authenticator = no -+ # The number one way to protect yourself from the BlastRADIUS -+ # attack is to update all RADIUS servers, and then set this -+ # flag to "yes". If all RADIUS servers are updated, and if -+ # all of them have this flag set to "yes" for all clients, -+ # then your network is safe. You can then upgrade the -+ # clients when it is convenient, instead of rushing the -+ # upgrades. -+ # -+ # allowed values: yes, no, auto -+ # -+# require_message_authenticator = no -+ -+ # -+ # The global configuration "security.limit_proxy_state" -+ # flag sets the default for all clients. That default can be -+ # over-ridden here, by setting it to "no". -+ # -+ # See that file for full documentation on the flag, along -+ # with allowed values,and meanings. -+ # -+ # This flag exists solely for legacy clients which do not send -+ # Message-Authenticator in all Access-Request packets. We do not -+ # recommend setting it to "no". -+ # -+ # allowed values: yes, no, auto -+ # -+# limit_proxy_state = yes - - # - # The short name is used as an alias for the fully qualified -@@ -260,6 +306,26 @@ client localhost_ipv6 { - # "clients = per_socket_clients". That IP address/port combination - # will then accept ONLY the clients listed in this section. - # -+# There are additional considerations when using clients from SQL. -+# -+# A client can be link to a virtual server via modules such as SQL. -+# This link is done via the following process: -+# -+# If there is no listener in a virtual server, SQL clients are added -+# to the global list for that virtual server. -+# -+# If there is a listener, and the first listener does not have a -+# "clients=..." configuration item, SQL clients are added to the -+# global list. -+# -+# If there is a listener, and the first one does have a "clients=..." -+# configuration item, SQL clients are added to that list. The client -+# { ...} ` configured in that list are also added for that listener. -+# -+# The only issue is if you have multiple listeners in a virtual -+# server, each with a different client list, then the SQL clients are -+# added only to the first listener. -+# - #clients per_socket_clients { - # client socket_client { - # ipaddr = 192.0.2.4 -diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap -index a89a783663..6a8693fe48 100644 ---- a/raddb/mods-available/eap -+++ b/raddb/mods-available/eap -@@ -281,7 +281,7 @@ eap { - # - # openssl dhparam -out certs/dh 2048 - # -- dh_file = ${certdir}/dh -+ # dh_file = ${certdir}/dh - - # If your system doesn't have /dev/urandom, - # you will need to create this file, and -@@ -328,6 +328,17 @@ eap { - - ca_path = ${cadir} - -+ # OpenSSL does not reload contents of ca_path dir over time. -+ # That means that if check_crl is enabled and CRLs are loaded -+ # from ca_path dir, at some point CRLs will expire and -+ # the server will stop authenticating users. -+ # -+ # If ca_path_reload_interval is non-zero, it will force OpenSSL -+ # to reload all data from ca_path periodically -+ # -+ # Flush ca_path each hour -+ # ca_path_reload_interval = 3600 -+ - # Accept an expired Certificate Revocation List - # - # allow_expired_crl = no -@@ -392,8 +403,8 @@ eap { - # tls_max_version. - # - # disable_tlsv1_2 = no -- disable_tlsv1_1 = yes -- disable_tlsv1 = yes -+ # disable_tlsv1_1 = yes -+ # disable_tlsv1 = yes - - # Set min / max TLS version. Mainly for Debian - # "trusty", which disables older versions of TLS, and -diff --git a/raddb/proxy.conf b/raddb/proxy.conf -index 91b4b37930..7295538d5e 100644 ---- a/raddb/proxy.conf -+++ b/raddb/proxy.conf -@@ -66,6 +66,46 @@ proxy server { - # - default_fallback = no - -+ # -+ # Whether or not we allow dynamic home servers. -+ # -+ # This setting should be "no" by default. If set to "yes", -+ # it can slow the server down, due to mutex locking across -+ # multiple threads. -+ # -+ # Dynamic servers will work ONLY with the "directory" -+ # configuration below. -+ # -+# dynamic = yes -+ -+ # -+ # The directory which contains dynamic home servers. Each -+ # file in the directory should be a normal "home_server" -+ # definitions. This directory does not exist by default. -+ # -+ # e.g: The content of home_servers/example.com should be -+ # a home server definition. -+ # -+ # The name of the home server MUST be the same as the -+ # filename. -+ # -+ # Each home server must be set to only one type. e.g. -+ # "type = auth", and not "type = auth+acct" -+ # -+ # For example: -+ # -+ # home_server example.com { -+ # type = auth -+ # ipaddr = ... -+ # ... -+ # } -+ # -+ # For complete documentation, please see -+ # -+ # doc/configuration/dynamic_home_servers.md -+ # -+# directory = ${confdir}/home_servers -+ - } - - ####################################################################### -@@ -111,6 +151,12 @@ proxy server { - # which was awkward. In 2.0, they have been made independent - # from realms, which is better for a number of reasons. - # -+# You can proxy to a specific home server by doing: -+# -+# update control { -+# Home-Server-Name = "name of home server" -+# } -+# - home_server localhost { - # - # Home servers can be sent Access-Request packets -@@ -204,6 +250,24 @@ home_server localhost { - # - secret = testing123 - -+ # -+ # The global configuration "security.require_message_authenticator" -+ # flag sets the default for all home servers. That default can be -+ # over-ridden here, by setting it to a value. If no value is set, -+ # then the default from the "radiusd.conf" file is used. -+ # -+ # See that file for full documentation on the flag, along -+ # with allowed values and meanings. -+ # -+ # This flag exists solely for legacy home servers which do -+ # not send Message-Authenticator in all Access-Accept, -+ # Access-Reject, or Access-Challenge packets. We do not -+ # recommend setting it to "no". -+ # -+ # allowed values: yes, no, auto -+ # -+# require_message_authenticator = no -+ - ############################################################ - # - # The rest of the configuration items listed here are optional, -@@ -510,6 +574,12 @@ home_server localhost { - # 10 'realm" sections, and one "home_server_pool" section to tie the - # two together. - # -+# You can proxy to a specific home server pool by doing: -+# -+# update control { -+# Home-Server-Pool = "name of pool" -+# } -+# - home_server_pool my_auth_failover { - # - # The type of this pool controls how home servers are chosen. -@@ -664,10 +734,9 @@ realm example.com { - auth_pool = my_auth_failover - # acct_pool = acct - -- # As of Version 3.0, the server can proxy CoA packets -- # based on the Operator-Name attribute. This requires -- # that the "suffix" module be listed in the "recv-coa" -- # section. -+ # The server can proxy CoA packets based on the Operator-Name -+ # attribute. This requires that the "suffix" module be -+ # listed in the "recv-coa" section. - # - # See raddb/sites-available/coa - # -@@ -689,6 +758,9 @@ realm example.com { - # - # If you do not want this to happen, uncomment "nostrip" below. - # -+ # Note that if the system is doing EAP, you MUST set the "nostrip" -+ # option for realms used in EAP. Otherwise EAP will fail. -+ # - # nostrip - - # There are no more configuration entries for a realm. -@@ -790,9 +862,6 @@ realm LOCAL { - # Yes, this rule is different than the normal "unlang" rules for - # regular expressions. That may be fixed in a future release. - # --# - for version 3.0.4 and following, with "correct_escapes = true", --# use normal regex backslash rules. Just one. Not two. --# - # - If you are matching domain names, put a '$' at the end of the regex - # that matches the domain name. This tells the regex matching code - # that the realm ENDS with the domain name, so it does not match -diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in -index e8aee3c001..4909c1b901 100644 ---- a/raddb/radiusd.conf.in -+++ b/raddb/radiusd.conf.in -@@ -25,7 +25,7 @@ - # The "sites-available" directory contains many - # worked examples of common configurations. - # --# raddb/certs/README -+# raddb/certs/README.md - # How to create certificates for EAP or RadSec. - # - # Every configuration item in the server is documented -@@ -151,13 +151,21 @@ pidfile = ${run_dir}/${name}.pid - # - # correct_escapes: use correct backslash escaping - # -+# This setting is for compatibility with 3.0.4 and earlier. If -+# you're running a copy of the configuration from 3.0.4, you can -+# change this setting to "no" in order to run a new binary using the -+# old configuration files. -+# -+# If you've created the configuration after 2014, this should be set -+# to "true", and you can ignore it. -+# - # Prior to version 3.0.5, the handling of backslashes was a little - # awkward, i.e. "wrong". In some cases, to get one backslash into - # a regex, you had to put 4 in the config files. - # --# Version 3.0.5 fixes that. However, for backwards compatibility, -+# Version 3.0.5 fixed that. However, for backwards compatibility, - # the new method of escaping is DISABLED BY DEFAULT. This means --# that upgrading to 3.0.5 won't break your configuration. -+# that upgrading from a 3.0.4 or below won't break your configuration. - # - # If you don't have double backslashes (i.e. \\) in your configuration, - # this won't matter to you. If you do have them, fix that to use only -@@ -207,8 +215,8 @@ correct_escapes = true - - # max_request_time: The maximum time (in seconds) to handle a request. - # --# Requests which take more time than this to process may be killed, and --# a REJECT message is returned. -+# Requests which take more time than this to process are discarded, -+# and no reply is returned. - # - # WARNING: If you notice that requests take a long time to be handled, - # then this MAY INDICATE a bug in the server, in one of the modules -@@ -219,6 +227,12 @@ correct_escapes = true - # then it probably means that you haven't indexed the database. See your - # SQL server documentation for more information. - # -+# In general, values larger than 30 or so are useless. If the server -+# replies to the NAS after 60 seconds, the NAS will almost always -+# have given up on the request, and gone to another one. You can see -+# this happening when the logs have messages like "received conflicting -+# packet", and "discarding old request". -+# - # Useful range of values: 5 to 120 - # - max_request_time = 30 -@@ -279,6 +293,17 @@ max_requests = 16384 - # - hostname_lookups = no - -+# -+# Run a "Post-Auth-Type Client-Lost" section. This ONLY happens when -+# the server sends an Access-Challenge, and then client does not -+# respond to it. The goal is to allow administrators to log -+# something when the client does not respond. -+# -+# See sites-available/default, "Post-Auth-Type Client-Lost" for more -+# information. -+# -+#postauth_client_lost = no -+ - # - # Logging section. The various "log_*" configuration items - # will eventually be moved here. -@@ -377,6 +402,25 @@ log { - # The message when the user exceeds the Simultaneous-Use limit. - # - msg_denied = "You are already logged in - access denied" -+ -+ # Suppress "secret" attributes when printing them in debug mode. -+ # -+ # Secrets are NOT tracked across xlat expansions. If your -+ # configuration puts secrets into other strings, they will -+ # still get printed. -+ # -+ # Setting this to "yes" means that the server prints -+ # -+ # <<< secret >>> -+ # -+ # instead of the value, for attriburtes which contain secret -+ # information. e.g. User-Name, Tunnel-Password, etc. -+ # -+ # This configuration is disabled by default. It is extremely -+ # important for administrators to be able to debug user logins -+ # by seeing what is actually being sent. -+ # -+# suppress_secrets = no - } - - # The program to execute to do concurrency checks. -@@ -436,6 +480,24 @@ ENV { - # LD_PRELOAD = /path/to/library2.so - } - -+# -+# TEMPLATES -+# -+# Template files hold common definitions that can be used in other -+# server sections. When a template is referenced, the configuration -+# items within the referenced template are copied to the referencing -+# section. -+# -+# Using templates reduces repetition of common configuration items, -+# which in turn makes the server configuration easier to maintain. -+# -+# See templates.conf for examples of using templates, and the -+# referencing syntax. -+# -+ -+# $INCLUDE templates.conf -+ -+ - # SECURITY CONFIGURATION - # - # There may be multiple methods of attacking on the server. This -@@ -462,7 +524,7 @@ security { - # - # If you are worried about security issues related to this - # use of chdir, then simply ensure that the "raddb" directory -- # is inside of the chroot, end be sure to do "cd raddb" -+ # is inside of the chroot, and be sure to do "cd raddb" - # BEFORE starting the server. - # - # If the server is statically linked, then the only files -@@ -538,8 +600,7 @@ security { - # rejects will be sent at 'cleanup_delay' time, when the request - # is deleted from the internal cache of requests. - # -- # As of Version 3.0.5, "reject_delay" has sub-second resolution. -- # e.g. "reject_delay = 1.4" seconds is possible. -+ # This number can be a decimal, e.g. 3.4 - # - # Useful ranges: 1 to 5 - reject_delay = 1 -@@ -564,6 +625,191 @@ security { - # - status_server = yes - -+ # -+ # Global configuration for requiring Message-Authenticator in -+ # all Access-* packets sent over UDP or TCP. This flag is -+ # ignored for TLS. -+ # -+ # The number one way to protect yourself from the BlastRADIUS -+ # attack is to update all RADIUS servers, and then set this -+ # flag to "yes". If all RADIUS servers are updated, and if -+ # all of them have this flag set to "yes" for all clients, -+ # then your network is safe. You can then upgrade the -+ # clients when it is convenient, instead of rushing the -+ # upgrades. -+ # -+ # This flag sets the global default for all clients and home -+ # servers. It can be over-ridden in an individual client or -+ # home_server definition by adding the same flag to that -+ # section with an appropriate value. -+ # -+ # All upgraded RADIUS implementations should send -+ # Message-Authenticator in all Access-Request, Access-Accept, -+ # Access-Reject, and Access-Challenge packets. Once all -+ # systems are upgraded, setting this flag to "yes" is the -+ # best protection from the attack. -+ # -+ # The possible values and meanings for -+ # "require_message_authenticator" are; -+ # -+ # * "no" - allow Access-* packet which do not contain -+ # Message-Authenticator -+ # -+ # For a client, if this flag is set to "no", then the -+ # "limit_proxy_state" flag, below, is also checked. -+ # -+ # For a home_server, if this flag is set to "no", then the -+ # Access-Accept, Access-Reject, and Access-Challenge -+ # packets do not need to contain Message-Authenticator. -+ # -+ # The only reason to set this flag to "no" is when the -+ # RADIUS client or home server has not been updated. It is -+ # always safer to set this flag "no" in the individual -+ # client or home_server definition. The global flag SHOULD -+ # still be set to a safe value: "yes". -+ # -+ # WARNING: Setting this flag and the "limit_proxy_state" -+ # flag to "no" will allow MITM attackers to create fake -+ # Access-Accept packets to the NAS! At least one of them -+ # MUST be set to "yes" for the system to have any -+ # protection against the attack. -+ # -+ # * "yes" - Require that all Access-* packets (client and -+ # home_server) contain Message-Authenticator. If a packet -+ # does not contain Message-Authenticator, then it is -+ # discarded. -+ # -+ # * "auto" - Automatically determine the value of the flag, -+ # based on the first packet received from that client or -+ # home_server. -+ # -+ # If the packet does not contain Message-Authenticator, -+ # then the value of the flag is automatically switched to -+ # "no". -+ # -+ # If the packet contains Message-Authenticator but not -+ # EAP-Message, then the value of the flag is automatically -+ # switched to "yes". The server has to check for -+ # EAP-Message, because the previous RFCs require that the -+ # packet contains Message-Authenticator when it also -+ # contains EAP-Message. So having a Message-Authenticator -+ # in those packets doesn't give the server enough -+ # information to determined if the client or home_server -+ # has been updated. -+ # -+ # If the packet contains Message-Authenticator and -+ # EAP-Message, then the flag is left at the "auto" value. -+ # -+ # WARNING: This switch is done for the first packet -+ # received from that client or home server. The change -+ # does NOT persist across server restarts. You MUST change -+ # the to "yes" manually, in order to make a permanent -+ # change to the configuration. -+ # -+ # WARNING: If there are multiple NASes with the same source -+ # IP and client definitions, BUT the NASes have different -+ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. -+ # -+ # That is, when there are multiple different RADIUS clients -+ # behind one NATed IP address, then these security settings -+ # have to be set to allow the MOST INSECURE packets to be -+ # processed. This is a terrible idea, and will leave your -+ # network vulnerable to the attack. Please upgrade all -+ # clients immediately. -+ # -+ # The only solution to that rare configuration is to set -+ # this flag to "no", in which case the network will work, -+ # but will be vulnerable to the attack. -+ # -+ require_message_authenticator = auto -+ -+ # -+ # Global configuration for limiting the combination of -+ # Proxy-State and Message-Authenticator. This flag only -+ # applies to packets sent over UDP or TCP. This flag is -+ # ignored for TLS. -+ # -+ # This flag sets the global default for all clients. It can -+ # be over-ridden in an individual client definition by adding -+ # the same flag to that section with an appropriate value. -+ # -+ # If "require_message_authenticator" is set to "yes", this -+ # configuration item is ignored. -+ # -+ # If "require_message_authenticator" is set to "no", this -+ # configuration item is checked. -+ # -+ # The possible values and meanings for "limit_proxy_state" are; -+ # -+ # * "no" - allow any packets from the client, even packets -+ # which contain the BlastRADIUS attack. Please be aware -+ # that in this configuration the server will complain for -+ # EVERY packet which it receives. -+ # -+ # The only reason to set this flag to "no" is when the -+ # client is a proxy, AND the proxy does not send -+ # Message-Authenticator in Access-Request packets. Even -+ # then, the best approach to fix the issue is to (1) update -+ # the proxy to send Message-Authenticator, and if that -+ # can't be done, then (2) set this flag to "no", but ONLY -+ # for that one client. The global flag SHOULD still be set -+ # to a safe value: "yes". -+ # -+ # WARNING: Setting both this flag and the -+ # "require_message_authenticator" flag to "no" will allow -+ # MITM attackers to create fake Access-Accept packets to the -+ # NAS! At least one of them MUST be set to "yes" for the -+ # system to have any protection against the attack. -+ # -+ # * "yes" - Allow packets without Message-Authenticator, -+ # but only when they do not contain Proxy-State. -+ # packets which contain Proxy-State MUST also contain -+ # Message-Authenticator, otherwise they are discarded. -+ # -+ # This setting is safe for most NASes, GGSNs, BRAS, etc. -+ # Most regular RADIUS clients do not send Proxy-State -+ # attributes for Access-Request packets that they originate. -+ # However some aggregators (e.g. Wireless LAN Controllers) -+ # may act as a RADIUS proxy for requests from their cohort -+ # of managed devices, and in such cases will provide a -+ # Proxy-State attribute. For those systems, you _must_ look -+ # at the actual packets to determine what to do. It may be -+ # that the only way to fix the vulnerability is to upgrade -+ # the WLC, and set "require_message_authenticator" to "yes". -+ # -+ # * "auto" - Automatically determine the value of the flag, -+ # based on the first packet received from that client. -+ # -+ # If the packet contains Proxy-State but no -+ # Message-Authenticator, then the value of the flag is -+ # automatically switched to "no". -+ # -+ # For all other situations, the value of the flag is -+ # automatically switched to "yes". -+ # -+ # WARNING: This switch is done for the first packet -+ # received from that client. The change does NOT persist -+ # across server restarts. You MUST change the to "yes" -+ # manually, in order to make a permanent change to the -+ # configuration. -+ # -+ # WARNING: If there are multiple NASes with the same source -+ # IP and client definitions, BUT the NASes have different -+ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. -+ # -+ # That is, when there are multiple different RADIUS clients -+ # behind one NATed IP address, then these security settings -+ # have to be set to allow the MOST INSECURE packets to be -+ # processed. This is a terrible idea, and will leave your -+ # network vulnerable to the attack. Please upgrade all -+ # clients immediately. -+ # -+ # The only solution to that rare configuration is to set -+ # this flag to "no", in which case the network will work, -+ # but will be vulnerable to the attack. -+ # -+ limit_proxy_state = auto -+ - @openssl_version_check_config@ - } - -@@ -754,19 +1000,19 @@ modules { - # directory from start to finish. Which means that the - # modules are read off of disk randomly. - # -- # As of 3.0.18, you can list individual modules *before* the -- # directory inclusion. Those modules will be loaded first. -- # Then, when the directory is read, those modules will be -- # skipped and not read twice. -+ # You can list individual modules *before* the directory -+ # inclusion. Those modules will be loaded first. Then, when -+ # the directory is read, those modules will be skipped and -+ # not read twice. - # - # $INCLUDE mods-enabled/sql - - # -- # As of 3.0, modules are in mods-enabled/. Files matching -- # the regex /[a-zA-Z0-9_.]+/ are loaded. The modules are -- # initialized ONLY if they are referenced in a processing -- # section, such as authorize, authenticate, accounting, -- # pre/post-proxy, etc. -+ # All modules are in ther mods-enabled/ directory. Files -+ # matching the regex /[a-zA-Z0-9_.]+/ are read. The -+ # modules are initialized ONLY if they are referenced in a -+ # processing section, such as authorize, authenticate, -+ # accounting, pre/post-proxy, etc. - # - $INCLUDE mods-enabled/ - } -diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls -index e2a3b080ca..654d304c83 100644 ---- a/raddb/sites-available/tls -+++ b/raddb/sites-available/tls -@@ -203,6 +203,16 @@ listen { - # check_crl = yes - ca_path = ${cadir} - -+ # OpenSSL does not reload contents of ca_path dir over time. -+ # That means that if check_crl is enabled and CRLs are loaded -+ # from ca_path dir, at some point CRLs will expire and -+ # RADIUSd will stop authenticating NASes. -+ # If ca_path_reload_interval is non-zero, it will force OpenSSL -+ # to reload all data from ca_path periodically -+ # -+ # Flush ca_path each hour -+ ca_path_reload_interval = 3600 -+ - # - # If check_cert_issuer is set, the value will - # be checked against the DN of the issuer in -@@ -468,6 +478,14 @@ home_server tls { - # this configuration item. - ca_file = ${cadir}/ca.pem - -+ # In previous versions, outbound RadSec connections -+ # would put the home server certificate into the -+ # TLS-Client-Cert* attributes. Set this configuration -+ # item to "yes" in order to have the home server -+ # certificates placed into the "TLS-Cert-*" attributes. -+ # -+# fix_cert_order = yes -+ - # - # For TLS-PSK, the key should be specified - # dynamically, instead of using a hard-coded -diff --git a/share/dictionary.freeradius b/share/dictionary.freeradius -index c92c5214ad..bfab9296c4 100644 ---- a/share/dictionary.freeradius -+++ b/share/dictionary.freeradius -@@ -107,6 +107,8 @@ VALUE FreeRADIUS-Stats-Server-State Alive 0 - VALUE FreeRADIUS-Stats-Server-State Zombie 1 - VALUE FreeRADIUS-Stats-Server-State Dead 2 - VALUE FreeRADIUS-Stats-Server-State Idle 3 -+VALUE FreeRADIUS-Stats-Server-State Admin-Down 4 -+VALUE FreeRADIUS-Stats-Server-State Connection-Fail 5 - - # - # When a home server is marked "dead" or "alive" -@@ -182,4 +184,7 @@ ATTRIBUTE FreeRADIUS-EAP-FAST-PKCS 186.20 octets - - ATTRIBUTE FreeRADIUS-Stats-Error 187 string - -+ATTRIBUTE FreeRADIUS-Stats-Client-IPv6-Address 188 ipv6addr -+ATTRIBUTE FreeRADIUS-Stats-Server-IPv6-Address 189 ipv6addr -+ - END-VENDOR FreeRADIUS -diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal -index 724e1f7ff6..347e3e59f3 100644 ---- a/share/dictionary.freeradius.internal -+++ b/share/dictionary.freeradius.internal -@@ -148,7 +148,7 @@ VALUE EAP-IKEv2-IDType DER_ASN1_GN 10 - VALUE EAP-IKEv2-IDType KEY_ID 11 - - ATTRIBUTE EAP-IKEv2-ID 1104 string --ATTRIBUTE EAP-IKEv2-Secret 1105 string -+ATTRIBUTE EAP-IKEv2-Secret 1105 string secret - ATTRIBUTE EAP-IKEv2-AuthType 1106 integer - - VALUE EAP-IKEv2-AuthType none 0 -@@ -196,7 +196,7 @@ ATTRIBUTE FreeRADIUS-Client-Require-MA 1122 integer - VALUE FreeRADIUS-Client-Require-MA no 0 - VALUE FreeRADIUS-Client-Require-MA yes 1 - --ATTRIBUTE FreeRADIUS-Client-Secret 1123 string -+ATTRIBUTE FreeRADIUS-Client-Secret 1123 string secret - ATTRIBUTE FreeRADIUS-Client-Shortname 1124 string - ATTRIBUTE FreeRADIUS-Client-NAS-Type 1125 string - ATTRIBUTE FreeRADIUS-Client-Virtual-Server 1126 string -@@ -254,6 +254,7 @@ ATTRIBUTE FreeRADIUS-Response-Delay-USec 1155 integer - - ATTRIBUTE REST-HTTP-Header 1160 string - ATTRIBUTE REST-HTTP-Body 1161 string -+ATTRIBUTE REST-HTTP-Status-Code 1162 integer - - ATTRIBUTE Cache-Expires 1170 date - ATTRIBUTE Cache-Created 1171 date -@@ -277,7 +278,24 @@ ATTRIBUTE SSHA2-256-Password 1178 octets - ATTRIBUTE SSHA2-384-Password 1179 octets - ATTRIBUTE SSHA2-512-Password 1180 octets - -+ATTRIBUTE PBKDF2-Password 1181 octets -+ATTRIBUTE SSHA3-224-Password 1182 octets -+ATTRIBUTE SSHA3-256-Password 1183 octets -+ATTRIBUTE SSHA3-384-Password 1184 octets -+ATTRIBUTE SSHA3-512-Password 1185 octets -+ - ATTRIBUTE MS-CHAP-Peer-Challenge 1192 octets -+ATTRIBUTE Home-Server-Name 1193 string -+ATTRIBUTE Originating-Realm-Key 1194 string -+ATTRIBUTE Proxy-To-Originating-Realm 1195 string -+ -+ATTRIBUTE TOTP-Secret 1194 string # base32 encoded -+ATTRIBUTE TOTP-Key 1195 octets # raw key -+ATTRIBUTE TOTP-Password 1196 string -+ -+ATTRIBUTE Proxy-Tunneled-Request-As-EAP 1197 integer -+VALUE Proxy-Tunneled-Request-As-EAP No 0 -+VALUE Proxy-Tunneled-Request-As-EAP Yes 1 - - # - # Range: 1200-1279 -@@ -318,6 +336,10 @@ ATTRIBUTE EAP-Sim-Algo-Version 1216 integer - ATTRIBUTE Outer-Realm-Name 1218 string - ATTRIBUTE Inner-Realm-Name 1219 string - -+ATTRIBUTE EAP-Pwd-Password-Hash 1220 octets -+ATTRIBUTE EAP-Pwd-Password-Salt 1221 octets -+ATTRIBUTE EAP-Pwd-Password-Prep 1222 byte -+ - # - # Range: 1280 - 1535 - # EAP-type specific attributes -@@ -516,6 +538,11 @@ ATTRIBUTE Tmp-Cast-IPv4Prefix 1870 ipv4prefix - # these attributes. - # - ATTRIBUTE WiMAX-MN-NAI 1900 string -+ATTRIBUTE WiMAX-SIM-Ki 1901 octets -+ATTRIBUTE WiMAX-SIM-OPc 1902 octets -+ATTRIBUTE WiMAX-SIM-AMF 1903 octets -+ATTRIBUTE WiMAX-SIM-SQN 1904 octets -+ATTRIBUTE WiMAX-SIM-RAND 1905 octets - - ATTRIBUTE TLS-Cert-Serial 1910 string - ATTRIBUTE TLS-Cert-Expiration 1911 string -@@ -526,7 +553,7 @@ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Email 1915 string - ATTRIBUTE TLS-Cert-Subject-Alt-Name-Dns 1916 string - ATTRIBUTE TLS-Cert-Subject-Alt-Name-Upn 1917 string - ATTRIBUTE TLS-Cert-Valid-Since 1918 string --# 1919: reserved for future cert attribute -+ATTRIBUTE TLS-Session-Information 1919 string - ATTRIBUTE TLS-Client-Cert-Serial 1920 string - ATTRIBUTE TLS-Client-Cert-Expiration 1921 string - ATTRIBUTE TLS-Client-Cert-Issuer 1922 string -@@ -543,10 +570,19 @@ ATTRIBUTE TLS-Client-Cert-Subject-Alt-Name-Upn 1932 string - ATTRIBUTE TLS-PSK-Identity 1933 string - ATTRIBUTE TLS-Client-Cert-X509v3-Extended-Key-Usage-OID 1936 string - ATTRIBUTE TLS-Client-Cert-Valid-Since 1937 string -+ATTRIBUTE TLS-Cache-Method 1938 integer -+VALUE TLS-Cache-Method save 1 -+VALUE TLS-Cache-Method load 2 -+VALUE TLS-Cache-Method clear 3 -+VALUE TLS-Cache-Method refresh 4 - --# 1938 - 1939: reserved for future cert attributes - --# 1940 - 1949: reserved for TLS session caching, mostly in 4.0 -+# 1939: reserved for future cert attributes -+ -+# 1940 - 1959: reserved for TLS session caching, mostly in 4.0 -+ -+ATTRIBUTE TLS-Session-ID 1940 octets -+ATTRIBUTE TLS-Session-Data 1942 octets - - # Set by EAP-TLS code - ATTRIBUTE TLS-OCSP-Cert-Valid 1943 integer -@@ -560,8 +596,13 @@ ATTRIBUTE TLS-Cache-Filename 1946 string - ATTRIBUTE TLS-Session-Version 1947 string - ATTRIBUTE TLS-Session-Cipher-Suite 1948 string - -+ATTRIBUTE TLS-Session-Cert-File 1949 string -+ATTRIBUTE TLS-Session-Cert-Private-Key-File 1950 string -+ -+ATTRIBUTE TLS-Server-Name-Indication 1951 string -+ - # --# Range: 1950-2099 -+# Range: 1960-2099 - # Free - # - # Range: 2100-2199 -@@ -650,6 +691,7 @@ VALUE Session-Type Local 1 - VALUE Post-Auth-Type Local 1 - VALUE Post-Auth-Type Reject 2 - VALUE Post-Auth-Type Challenge 3 -+VALUE Post-Auth-Type Client-Lost 4 - - # - # And Post-Proxy -diff --git a/src/include/build.h b/src/include/build.h -index 5da940c2b7..c5eaa45457 100644 ---- a/src/include/build.h -+++ b/src/include/build.h -@@ -46,9 +46,13 @@ extern "C" { - * compiler. - */ - #ifdef __GNUC__ --# define CC_HINT(_x) __attribute__ ((_x)) -+# define CC_HINT(...) __attribute__ ((__VA_ARGS__)) -+# define likely(_x) __builtin_expect((_x), 1) -+# define unlikely(_x) __builtin_expect((_x), 0) - #else --# define CC_HINT(_x) -+# define CC_HINT(...) -+# define likely(_x) _x -+# define unlikely(_x) _x - #endif - - #ifdef HAVE_ATTRIBUTE_BOUNDED -@@ -57,6 +61,18 @@ extern "C" { - # define CC_BOUNDED(...) - #endif - -+/* -+ * GCC uses __SANITIZE_ADDRESS__, clang uses __has_feature, which -+ * GCC complains about. -+ */ -+#ifndef __SANITIZE_ADDRESS__ -+#ifdef __has_feature -+#if __has_feature(address_sanitizer) -+#define __SANITIZE_ADDRESS__ (1) -+#endif -+#endif -+#endif -+ - /* - * Macros to add pragmas - */ -@@ -66,6 +82,7 @@ extern "C" { - * Macros for controlling warnings in GCC >= 4.2 and clang >= 2.8 - */ - #if defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 -+# define DIAG_UNKNOWN_PRAGMAS pragmas - # define DIAG_PRAGMA(_x) PRAGMA(GCC diagnostic _x) - # if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 - # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) -@@ -75,10 +92,12 @@ extern "C" { - # define DIAG_ON(_x) DIAG_PRAGMA(warning JOINSTR(-W,_x)) - # endif - #elif defined(__clang__) && ((__clang_major__ * 100) + __clang_minor__ >= 208) -+# define DIAG_UNKNOWN_PRAGMAS unknown-pragmas - # define DIAG_PRAGMA(_x) PRAGMA(clang diagnostic _x) - # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) - # define DIAG_ON(_x) DIAG_PRAGMA(pop) - #else -+# define DIAG_UNKNOWN_PRAGMAS - # define DIAG_OFF(_x) - # define DIAG_ON(_x) - #endif -@@ -137,6 +156,12 @@ extern "C" { - # endif - #endif - -+#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) -+#define NEVER_RETURNS CC_HINT(noreturn) -+#define HIDDEN CC_HINT(visibility("hidden")) -+#define UNUSED CC_HINT(unused) -+#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ -+ - #ifdef __cplusplus - } - #endif -diff --git a/src/include/clients.h b/src/include/clients.h -index 560211557f..7d3288a489 100644 ---- a/src/include/clients.h -+++ b/src/include/clients.h -@@ -26,10 +26,14 @@ - * @copyright 2015 The FreeRADIUS server project - */ - -+typedef struct radclient_list RADCLIENT_LIST; -+ -+ - /** Describes a host allowed to send packets to the server - * - */ - typedef struct radclient { -+ RADCLIENT_LIST *list; //!< parent list - fr_ipaddr_t ipaddr; //!< IPv4/IPv6 address of the host. - fr_ipaddr_t src_ipaddr; //!< IPv4/IPv6 address to send responses - //!< from (family must match ipaddr). -@@ -39,7 +43,11 @@ typedef struct radclient { - - char const *secret; //!< Secret PSK. - -- bool message_authenticator; //!< Require RADIUS message authenticator in requests. -+ fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests. -+ -+ bool dynamic_require_ma; //!< for dynamic clients -+ -+ fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests - - char const *nas_type; //!< Type of client (arbitrary). - -@@ -91,16 +99,14 @@ typedef struct radclient { - - #ifdef WITH_COA - char const *coa_name; //!< Name of the CoA home server or pool. -- home_server_t *coa_server; //!< The CoA home_server_t the client is associated with. -+ home_server_t *coa_home_server; //!< The CoA home_server_t the client is associated with. - //!< Must be used exclusively from coa_pool. -- home_pool_t *coa_pool; //!< The CoA home_pool_t the client is associated with. -+ home_pool_t *coa_home_pool; //!< The CoA home_pool_t the client is associated with. - //!< Must be used exclusively from coa_server. - bool defines_coa_server; //!< Client also defines a home_server. - #endif - } RADCLIENT; - --typedef struct radclient_list RADCLIENT_LIST; -- - /** Callback for retrieving values when building client sections - * - * Example: -diff --git a/src/include/conffile.h b/src/include/conffile.h -index 8cb045c946..237469c880 100644 ---- a/src/include/conffile.h -+++ b/src/include/conffile.h -@@ -140,6 +140,7 @@ typedef struct timeval _timeval_t; - #define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies. - #define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value. - #define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist -+#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing - /* @} **/ - - #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\ -@@ -273,6 +274,7 @@ int cf_pair_count(CONF_SECTION const *cs); - CONF_SECTION *cf_item_parent(CONF_ITEM const *ci); - bool cf_item_is_section(CONF_ITEM const *item); - bool cf_item_is_pair(CONF_ITEM const *item); -+bool cf_item_is_data(CONF_ITEM const *item); - CONF_PAIR *cf_item_to_pair(CONF_ITEM const *item); - CONF_SECTION *cf_item_to_section(CONF_ITEM const *item); - CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp); -diff --git a/src/include/event.h b/src/include/event.h -index a29c9562a9..884edec526 100644 ---- a/src/include/event.h -+++ b/src/include/event.h -@@ -40,6 +40,7 @@ typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); - fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status); - - int fr_event_list_num_fds(fr_event_list_t *el); -+int fr_event_list_full(fr_event_list_t *el); - int fr_event_list_num_elements(fr_event_list_t *el); - - int fr_event_insert(fr_event_list_t *el, -@@ -53,6 +54,8 @@ int fr_event_now(fr_event_list_t *el, struct timeval *when); - - int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, - fr_event_fd_handler_t handler, void *ctx); -+int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, -+ fr_event_fd_handler_t write_handler, void *ctx); - int fr_event_fd_delete(fr_event_list_t *el, int type, int fd); - int fr_event_loop(fr_event_list_t *el); - void fr_event_loop_exit(fr_event_list_t *el, int code); -diff --git a/src/include/libradius.h b/src/include/libradius.h -index ce2f713de1..1b975517b5 100644 ---- a/src/include/libradius.h -+++ b/src/include/libradius.h -@@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$") - * Talloc memory allocation is used in preference to malloc throughout - * the libraries and server. - */ -+#ifdef HAVE_WDOCUMENTATION -+DIAG_OFF(documentation) -+#endif - #include -+#ifdef HAVE_WDOCUMENTATION -+DIAG_ON(documentation) -+#endif - - /* - * Defines signatures for any missing functions. -@@ -162,11 +168,6 @@ typedef void (*sig_t)(int); - - #define PAD(_x, _y) (_y - ((_x) % _y)) - --#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) --#define NEVER_RETURNS CC_HINT(noreturn) --#define UNUSED CC_HINT(unused) --#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ -- - typedef struct attr_flags { - unsigned int is_unknown : 1; //!< Attribute number or vendor is unknown. - unsigned int is_tlv : 1; //!< Is a sub attribute. -@@ -189,6 +190,10 @@ typedef struct attr_flags { - - unsigned int compare : 1; //!< has a paircompare registered - -+ unsigned int is_dup : 1; //!< is a duplicate of another attribute -+ -+ unsigned int secret : 1; //!< is a secret thingy -+ - uint8_t encrypt; //!< Ecryption method. - uint8_t length; - } ATTR_FLAGS; -@@ -402,6 +407,11 @@ typedef struct radius_packet { - size_t partial; - int proto; - #endif -+ bool tls; //!< uses secure transport -+ -+ bool message_authenticator; -+ bool proxy_state; -+ bool eap_message; - } RADIUS_PACKET; - - typedef enum { -@@ -443,7 +453,7 @@ size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q - - char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote); - --size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp); -+size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value); - size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp); - void vp_print(FILE *, VALUE_PAIR const *); - void vp_printlist(FILE *, VALUE_PAIR const *); -@@ -472,6 +482,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value); - int dict_init(char const *dir, char const *fn); - void dict_free(void); - int dict_read(char const *dir, char const *filename); -+size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da); -+int dict_walk(fr_hash_table_walk_t callback, void *context); - - void dict_attr_free(DICT_ATTR const **da); - int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor); -@@ -507,6 +519,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor); - /* radius.c */ - int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret); - bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason); -+ -+/* -+ * 1 == require_ma -+ * 2 == msg_peek -+ * 4 == limit_proxy_state -+ * 8 == require_ma for Access-* replies and Protocol-Error -+ */ - RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags); - ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code); - void rad_recv_discard(int sockfd); -@@ -586,6 +605,7 @@ int rad_vp2attr(RADIUS_PACKET const *packet, - VALUE_PAIR const **pvp, uint8_t *ptr, size_t room); - - /* pair.c */ -+VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx); - VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da); - VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor); - int fr_pair_to_unknown(VALUE_PAIR *vp); -@@ -611,6 +631,7 @@ VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor); - VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new); - void fr_pair_delete_by_num(VALUE_PAIR **, unsigned int attr, unsigned int vendor, int8_t tag); - void fr_pair_add(VALUE_PAIR **, VALUE_PAIR *); -+void fr_pair_prepend(VALUE_PAIR **, VALUE_PAIR *); - void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *add); - int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b); - int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b); -@@ -632,7 +653,7 @@ void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src); - void fr_pair_value_strcpy(VALUE_PAIR *vp, char const * src); - void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const * src, size_t len); - void fr_pair_value_sprintf(VALUE_PAIR *vp, char const * fmt, ...) CC_HINT(format (printf, 2, 3)); --void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from); -+void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op); - void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, - unsigned int attr, unsigned int vendor, int8_t tag); - void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, -@@ -694,7 +715,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */ - extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */ - extern int fr_debug_lvl; /* 0 = no debugging information */ - extern uint32_t fr_max_attributes; /* per incoming packet */ --#define FR_MAX_PACKET_CODE (52) -+#define FR_MAX_PACKET_CODE (53) - extern char const *fr_packet_codes[FR_MAX_PACKET_CODE]; - #define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE)) - extern FILE *fr_log_fp; -@@ -932,6 +953,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout); - } - #endif - -+typedef enum { -+ FR_BOOL_FALSE = 0, -+ FR_BOOL_TRUE, -+ FR_BOOL_AUTO, -+} fr_bool_auto_t; -+ - #include - - #ifdef WITH_TCP -diff --git a/src/include/listen.h b/src/include/listen.h -index 4f50bbf808..2b4e02bd54 100644 ---- a/src/include/listen.h -+++ b/src/include/listen.h -@@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE { - typedef enum RAD_LISTEN_STATUS { - RAD_LISTEN_STATUS_INIT = 0, - RAD_LISTEN_STATUS_KNOWN, -+ RAD_LISTEN_STATUS_PAUSE, -+ RAD_LISTEN_STATUS_RESUME, - RAD_LISTEN_STATUS_FROZEN, - RAD_LISTEN_STATUS_EOL, - RAD_LISTEN_STATUS_REMOVE_NOW -@@ -68,24 +70,41 @@ struct rad_listen { - int fd; - char const *server; - int status; --#ifdef WITH_TCP - int count; -- bool dual; -+#ifdef WITH_TCP - rbtree_t *children; - rad_listen_t *parent; -+ -+ bool dual; - #endif - bool nodup; - bool synchronous; -+ bool dead; - uint32_t workers; - - #ifdef WITH_TLS - fr_tls_server_conf_t *tls; -+ bool check_client_connections; -+ bool nonblock; -+ bool blocked; - #endif - - rad_listen_recv_t recv; - rad_listen_send_t send; -+ -+ /* -+ * We don't need a proxy_recv, because the main loop in -+ * process.c calls listener->recv(), and we don't know -+ * what kind of packet we're receiving until we receive -+ * it. -+ */ -+ rad_listen_send_t proxy_send; -+ -+ - rad_listen_encode_t encode; - rad_listen_decode_t decode; -+ rad_listen_encode_t proxy_encode; -+ rad_listen_decode_t proxy_decode; - rad_listen_print_t print; - - CONF_SECTION const *cs; -@@ -143,9 +162,16 @@ typedef struct listen_socket_t { - tls_session_t *ssn; - REQUEST *request; /* horrible hacks */ - VALUE_PAIR *certs; -+ uint32_t connect_timeout; - pthread_mutex_t mutex; - uint8_t *data; - size_t partial; -+ enum { -+ LISTEN_TLS_INIT = 0, -+ LISTEN_TLS_CHECKING, -+ LISTEN_TLS_SETUP, -+ LISTEN_TLS_RUNNING, -+ } state; - #endif - - RADCLIENT_LIST *clients; -diff --git a/src/include/md4.h b/src/include/md4.h -index b7bdd6a15e..1492bd4a5c 100644 ---- a/src/include/md4.h -+++ b/src/include/md4.h -@@ -26,6 +26,10 @@ RCSIDH(md4_h, "$Id$") - - #include - -+#ifdef WITH_FIPS -+#undef HAVE_OPENSSL_MD4_H -+#endif -+ - #ifdef HAVE_OPENSSL_MD4_H - # include - #endif -@@ -71,14 +75,58 @@ void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) - void fr_md4_transform(uint32_t buf[4], uint8_t const inc[MD4_BLOCK_LENGTH]) - CC_BOUNDED(__size__, 1, 4, 4) - CC_BOUNDED(__minbytes__, 2, MD4_BLOCK_LENGTH); -+# define fr_md4_destroy(_x) - #else /* HAVE_OPENSSL_MD4_H */ -+#if OPENSSL_VERSION_NUMBER < 0x30000000L - USES_APPLE_DEPRECATED_API - # define FR_MD4_CTX MD4_CTX - # define fr_md4_init MD4_Init - # define fr_md4_update MD4_Update - # define fr_md4_final MD4_Final - # define fr_md4_transform MD4_Transform --#endif -+# define fr_md4_destroy(_x) -+#else -+#include -+ -+/* -+ * Wrappers for OpenSSL3, so we don't have to butcher the rest of -+ * the code too much. -+ */ -+typedef struct FR_MD4_CTX { -+ EVP_MD_CTX *ctx; -+ EVP_MD const *md; -+ unsigned int len; -+} FR_MD4_CTX; -+ -+static inline void fr_md4_init(FR_MD4_CTX *ctx) -+{ -+ ctx->ctx = EVP_MD_CTX_new(); -+// ctx->md = EVP_MD_fetch(NULL, "MD4", "provider=legacy"); -+ ctx->md = EVP_md4(); -+ ctx->len = MD4_DIGEST_LENGTH; -+ -+ EVP_MD_CTX_set_flags(ctx->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); -+ EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL); -+} -+ -+static inline void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen) -+{ -+ EVP_DigestUpdate(ctx->ctx, in, inlen); -+} -+ -+static inline void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) -+{ -+ EVP_DigestFinal_ex(ctx->ctx, out, &(ctx->len)); -+} -+ -+static inline void fr_md4_destroy(FR_MD4_CTX *ctx) -+{ -+ EVP_MD_CTX_destroy(ctx->ctx); -+// EVP_MD_free(ctx->md); -+} -+ -+#endif /* OPENSSL3 */ -+#endif /* HAVE_OPENSSL_MD4_H */ - - /* md4.c */ - void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen); -diff --git a/src/include/md5.h b/src/include/md5.h -index a44584564f..b7d571ac7b 100644 ---- a/src/include/md5.h -+++ b/src/include/md5.h -@@ -26,6 +26,10 @@ RCSIDH(md5_h, "$Id$") - - # include - -+#ifdef WITH_FIPS -+#undef HAVE_OPENSSL_MD5_H -+#endif -+ - #ifdef HAVE_OPENSSL_MD5_H - # include - #endif -@@ -68,14 +72,41 @@ void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx) - void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) - CC_BOUNDED(__size__, 1, 4, 4) - CC_BOUNDED(__minbytes__, 2, MD5_BLOCK_LENGTH); -+# define fr_md5_destroy(_x) -+# define fr_md5_copy(_dst, _src) _dst = _src - #else /* HAVE_OPENSSL_MD5_H */ -+#if OPENSSL_VERSION_NUMBER < 0x30000000L - USES_APPLE_DEPRECATED_API - # define FR_MD5_CTX MD5_CTX - # define fr_md5_init MD5_Init - # define fr_md5_update MD5_Update - # define fr_md5_final MD5_Final - # define fr_md5_transform MD5_Transform --#endif -+# define fr_md5_copy(_dst, _src) _dst = _src -+# define fr_md5_destroy(_x) -+#else -+#include -+ -+/* -+ * Wrappers for OpenSSL3, so we don't have to butcher the rest of -+ * the code too much. -+ */ -+typedef EVP_MD_CTX* FR_MD5_CTX; -+ -+# define fr_md5_init(_ctx) \ -+ do { \ -+ *_ctx = EVP_MD_CTX_new(); \ -+ EVP_MD_CTX_set_flags(*_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); \ -+ EVP_DigestInit_ex(*_ctx, EVP_md5(), NULL); \ -+ } while (0) -+# define fr_md5_update(_ctx, _str, _len) \ -+ EVP_DigestUpdate(*_ctx, _str, _len) -+# define fr_md5_final(_out, _ctx) \ -+ EVP_DigestFinal_ex(*_ctx, _out, NULL) -+# define fr_md5_destroy(_ctx) EVP_MD_CTX_destroy(*_ctx) -+# define fr_md5_copy(_dst, _src) EVP_MD_CTX_copy_ex(_dst, _src) -+#endif /* OPENSSL3 */ -+#endif /* HAVE_OPENSSL_MD5_H */ - - /* hmac.c */ - void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, -diff --git a/src/include/missing-h b/src/include/missing-h -index bb74e060b9..5698797b0e 100644 ---- a/src/include/missing-h -+++ b/src/include/missing-h -@@ -516,6 +516,10 @@ size_t SSL_SESSION_get_master_key(const SSL_SESSION *s, - #define O_DIRECTORY 0 - #endif - -+#ifndef O_NOFOLLOW -+#define O_NOFOLLOW 0 -+#endif -+ - /* - * Not really missing, but may be submitted as patches - * to the talloc project at some point in the future. -diff --git a/src/include/openssl3.h b/src/include/openssl3.h -new file mode 100644 -index 0000000000..4423ee538a ---- /dev/null -+++ b/src/include/openssl3.h -@@ -0,0 +1,109 @@ -+/* -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA -+ */ -+#ifndef FR_OPENSSL3_H -+#define FR_OPENSSL3_H -+/** -+ * $Id$ -+ * -+ * @file openssl3.h -+ * @brief Wrappers to shut up OpenSSL3 -+ * -+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) -+ */ -+ -+RCSIDH(openssl3_h, "$Id$") -+ -+/* -+ * The HMAC APIs are deprecated in OpenSSL3. We don't want to -+ * fill the code with ifdef's, so we define some horrific -+ * wrappers here. -+ * -+ * This file should be included AFTER all OpenSSL header files. -+ */ -+#ifdef HAVE_OPENSSL_SSL_H -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+#include -+#include -+#include -+ -+typedef struct { -+ EVP_MAC *mac; -+ EVP_MAC_CTX *ctx; -+} HMAC3_CTX; -+#define HMAC_CTX HMAC3_CTX -+ -+#define HMAC_CTX_new HMAC3_CTX_new -+static inline HMAC3_CTX *HMAC3_CTX_new(void) -+{ -+ HMAC3_CTX *h = calloc(1, sizeof(*h)); -+ -+ return h; -+} -+ -+#define HMAC_Init_ex(_ctx, _key, _keylen, _md, _engine) HMAC3_Init_ex(_ctx, _key, _keylen, _md, _engine) -+static inline int HMAC3_Init_ex(HMAC3_CTX *ctx, const unsigned char *key, unsigned int keylen, const EVP_MD *md, UNUSED void *engine) -+{ -+ OSSL_PARAM params[2], *p = params; -+ char const *name; -+ char *unconst; -+ -+ ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); -+ if (!ctx->mac) return 0; -+ -+ ctx->ctx = EVP_MAC_CTX_new(ctx->mac); -+ if (!ctx->ctx) return 0; -+ -+ name = EVP_MD_get0_name(md); -+ memcpy(&unconst, &name, sizeof(name)); /* const issues */ -+ -+ p[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, unconst, 0); -+ p[1] = OSSL_PARAM_construct_end(); -+ -+ return EVP_MAC_init(ctx->ctx, key, keylen, params); -+} -+ -+#define HMAC_Update HMAC3_Update -+static inline int HMAC3_Update(HMAC3_CTX *ctx, const unsigned char *data, unsigned int datalen) -+{ -+ return EVP_MAC_update(ctx->ctx, data, datalen); -+} -+ -+#define HMAC_Final HMAC3_Final -+static inline int HMAC3_Final(HMAC3_CTX *ctx, unsigned char *out, unsigned int *len) -+{ -+ size_t mylen = *len; -+ -+ if (!EVP_MAC_final(ctx->ctx, out, &mylen, mylen)) return 0; -+ -+ *len = mylen; -+ return 1; -+} -+ -+#define HMAC_CTX_free HMAC3_CTX_free -+static inline void HMAC3_CTX_free(HMAC3_CTX *ctx) -+{ -+ if (!ctx) return; -+ -+ EVP_MAC_free(ctx->mac); -+ EVP_MAC_CTX_free(ctx->ctx); -+ free(ctx); -+} -+ -+#define HMAC_CTX_set_flags(_ctx, _flags) -+ -+#endif /* OPENSSL_VERSION_NUMBER */ -+#endif -+#endif /* FR_OPENSSL3_H */ -diff --git a/src/include/process.h b/src/include/process.h -index 8c3c7275e0..35a91bfa55 100644 ---- a/src/include/process.h -+++ b/src/include/process.h -@@ -47,6 +47,11 @@ typedef enum fr_state_action_t { /* server action */ - FR_ACTION_PROXY_REPLY, - #endif - FR_ACTION_CANCELLED, -+ FR_ACTION_CONFLICT, -+ FR_ACTION_MAX_TIME, -+ FR_ACTION_INTERNAL_FAILURE, -+ FR_ACTION_CLEANUP_DELAY, -+ FR_ACTION_COA_CANCELLED, - } fr_state_action_t; - - /* -@@ -66,9 +71,13 @@ int request_enqueue(REQUEST *request); - - int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *packet, - RADCLIENT *client, RAD_REQUEST_FUNP fun); -+void request_inject(REQUEST *request); - - #ifdef WITH_PROXY - int request_proxy_reply(RADIUS_PACKET *packet); -+ -+void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler); -+void proxy_listener_thaw(rad_listen_t *listener); - #endif - - #ifdef __cplusplus -diff --git a/src/include/radius.h b/src/include/radius.h -index 473528d65d..147d674eed 100644 ---- a/src/include/radius.h -+++ b/src/include/radius.h -@@ -61,6 +61,7 @@ typedef enum { - PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request - PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive) - PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform) -+ PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue - PW_CODE_MAX = 255, //!< Maximum possible code - } PW_CODE; - -diff --git a/src/include/radiusd.h b/src/include/radiusd.h -index b2a0a0f642..2b9bc19f15 100644 ---- a/src/include/radiusd.h -+++ b/src/include/radiusd.h -@@ -114,6 +114,7 @@ typedef struct main_config { - fr_ipaddr_t myip; //!< IP to bind to. Set on command line. - uint16_t port; //!< Port to bind to. Set on command line. - -+ bool suppress_secrets; //!< for debug levels < 3 - bool log_auth; //!< Log all authentication attempts. - bool log_accept; //!< Log Access-Accept - bool log_reject; //!< Log Access-Reject -@@ -140,6 +141,8 @@ typedef struct main_config { - uint32_t cleanup_delay; //!< How long before cleaning up cached responses. - uint32_t max_requests; - -+ bool postauth_client_lost; //!< Whether to run Post-Auth-Type Client-Lost section -+ - uint32_t debug_level; - char const *log_file; - int syslog_facility; -@@ -171,6 +174,9 @@ typedef struct main_config { - - bool exiting; //!< are we exiting? - -+ fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers -+ -+ fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients - - #ifdef ENABLE_OPENSSL_VERSION_CHECK - char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged. -@@ -191,9 +197,8 @@ typedef struct main_config { - typedef enum { - REQUEST_ACTIVE = 1, - REQUEST_STOP_PROCESSING, -- REQUEST_COUNTED - } rad_master_state_t; --#define REQUEST_MASTER_NUM_STATES (REQUEST_COUNTED + 1) -+#define REQUEST_MASTER_NUM_STATES (REQUEST_STOP_PROCESSING + 1) - - typedef enum { - REQUEST_QUEUED = 1, -@@ -312,8 +317,10 @@ struct rad_request { - #define RAD_REQUEST_LVL_DEBUG3 (3) - #define RAD_REQUEST_LVL_DEBUG4 (4) - --#define RAD_REQUEST_OPTION_COA (1 << 0) --#define RAD_REQUEST_OPTION_CTX (1 << 1) -+#define RAD_REQUEST_OPTION_COA (1 << 0) -+#define RAD_REQUEST_OPTION_CTX (1 << 1) -+#define RAD_REQUEST_OPTION_CANCELLED (1 << 2) -+#define RAD_REQUEST_OPTION_STATS (1 << 3) - - #define SECONDS_PER_DAY 86400 - #define MAX_REQUEST_TIME 30 -@@ -351,8 +358,8 @@ extern char const *radacct_dir; - extern char const *radlog_dir; - extern char const *radlib_dir; - extern bool log_stripped_names; --extern char const *radiusd_version; --extern char const *radiusd_version_short; -+extern HIDDEN char const *radiusd_version; -+extern HIDDEN char const *radiusd_version_short; - void radius_signal_self(int flag); - - typedef enum { -@@ -374,8 +381,8 @@ int rad_accounting(REQUEST *); - int rad_coa_recv(REQUEST *request); - - /* session.c */ --int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, char const *sessionid); --int session_zap(REQUEST *request, uint32_t nasaddr, -+int rad_check_ts(fr_ipaddr_t const *nas_addr, uint32_t nas_port, char const *user, char const *sessionid); -+int session_zap(REQUEST *request, fr_ipaddr_t const *nas_addr, - uint32_t nas_port, char const *user, - char const *sessionid, uint32_t cliaddr, - char proto, int session_time); -@@ -558,6 +565,8 @@ int main_config_free(void); - void main_config_hup(void); - void hup_logfile(void); - -+int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str); -+ - /* listen.c */ - void listen_free(rad_listen_t **head); - int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag); -@@ -583,7 +592,7 @@ void radius_event_free(void); - int radius_event_process(void); - void radius_update_listener(rad_listen_t *listener); - void revive_home_server(void *ctx); --void mark_home_server_dead(home_server_t *home, struct timeval *when); -+void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down); - - /* evaluate.c */ - typedef struct fr_cond_t fr_cond_t; -diff --git a/src/include/realms.h b/src/include/realms.h -index 6dae8b4f85..171a449112 100644 ---- a/src/include/realms.h -+++ b/src/include/realms.h -@@ -21,10 +21,10 @@ typedef enum { - HOME_TYPE_INVALID = 0, - HOME_TYPE_AUTH, //!< Authentication server - HOME_TYPE_ACCT, //!< Accounting server -- HOME_TYPE_AUTH_ACCT //!< Authentication and accounting server -+ HOME_TYPE_AUTH_ACCT, //!< Authentication and accounting server - - #ifdef WITH_COA -- ,HOME_TYPE_COA //!< CoA destination (NAS or Proxy) -+ HOME_TYPE_COA, //!< CoA destination (NAS or Proxy) - #endif - } home_type_t; - -@@ -36,13 +36,16 @@ typedef enum { - } home_ping_check_t; - - typedef enum { -- HOME_STATE_UNKNOWN = 0, -- HOME_STATE_ALIVE, -+ HOME_STATE_ALIVE = 0, - HOME_STATE_ZOMBIE, - HOME_STATE_IS_DEAD, -+ HOME_STATE_UNKNOWN, -+ HOME_STATE_ADMIN_DOWN, - HOME_STATE_CONNECTION_FAIL, - } home_state_t; - -+#define HOME_SERVER_IS_DEAD(_x) (((_x)->state == HOME_STATE_IS_DEAD) || ((_x)->state == HOME_STATE_ADMIN_DOWN) || ((_x)->state == HOME_STATE_CONNECTION_FAIL)) -+ - typedef struct fr_socket_limit_t { - uint32_t max_connections; - uint32_t num_connections; -@@ -59,7 +62,11 @@ typedef struct home_server { - //!< stats or when specifying home servers for a pool. - - bool dual; //!< One of a pair of homeservers on consecutive ports. -- char const *server; //!< For internal proxying -+ bool dynamic; //!< is this a dynamically added home server? -+ bool nonblock; //!< Enable a socket non-blocking to the home server. -+ fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server -+ -+ char const *virtual_server; //!< For internal proxying - char const *parent_server; - - fr_ipaddr_t ipaddr; //!< IP address of home server. -@@ -122,6 +129,8 @@ typedef struct home_server { - #endif - #ifdef WITH_TLS - fr_tls_server_conf_t *tls; -+ uint32_t connect_timeout; -+ rbtree_t *listeners; - #endif - - #ifdef WITH_STATS -@@ -202,10 +211,14 @@ CONF_SECTION *home_server_cs_afrom_client(CONF_SECTION *client); - home_server_t *home_server_byname(char const *name, int type); - #endif - #ifdef WITH_STATS -+extern int home_server_max_number; - home_server_t *home_server_bynumber(int number); - #endif - home_pool_t *home_pool_byname(char const *name, int type); - -+int home_server_afrom_file(char const *filename); -+int home_server_delete(char const *name, char const *type); -+ - #ifdef __cplusplus - } - #endif -diff --git a/src/include/socket.h b/src/include/socket.h -new file mode 100644 -index 0000000000..821c2a0b7a ---- /dev/null -+++ b/src/include/socket.h -@@ -0,0 +1,53 @@ -+#pragma once -+ -+/* -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or (at -+ * your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA -+ */ -+ -+/** Functions for establishing and managing low level sockets -+ * -+ * @file src/include/socket.h -+ * -+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) -+ * @author Alan DeKok (aland@freeradius.org) -+ * -+ * @copyright 2015 The FreeRADIUS project -+ */ -+RCSIDH(socket_h, "$Id$") -+ -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+#include -+#include -+ -+#ifdef HAVE_SYS_UN_H -+# include -+/* -+ * The linux headers define the macro as: -+ * -+ * # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ -+ * + strlen ((ptr)->sun_path)) -+ * -+ * Which trips UBSAN, because it sees an operation on a NULL pointer. -+ */ -+# undef SUN_LEN -+# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) -+#endif -+ -+#ifdef __cplusplus -+} -+#endif -diff --git a/src/include/tls-h b/src/include/tls-h -index 62f57c4715..4bf1665483 100644 ---- a/src/include/tls-h -+++ b/src/include/tls-h -@@ -67,7 +67,7 @@ typedef enum { - } fr_tls_status_t; - extern FR_NAME_NUMBER const fr_tls_status_table[]; - --#define MAX_RECORD_SIZE 16384 -+#define MAX_RECORD_SIZE 65536 - - /* - * A single TLS record may be up to 16384 octets in length, but a -@@ -89,12 +89,12 @@ extern FR_NAME_NUMBER const fr_tls_status_table[]; - * or configure TLS not to exceed MAX_RECORD_SIZE. - */ - typedef struct _record_t { -- uint8_t data[MAX_RECORD_SIZE]; - size_t used; -+ uint8_t data[MAX_RECORD_SIZE]; - } record_t; - - typedef struct _tls_info_t { -- int origin; -+ int origin; // 0 - received (from peer), 1 - sending (to peer) - int content_type; - uint8_t handshake_type; - uint8_t alert_level; -@@ -103,7 +103,6 @@ typedef struct _tls_info_t { - - char info_description[256]; - size_t record_len; -- int version; - } tls_info_t; - - #if OPENSSL_VERSION_NUMBER < 0x10001000L -@@ -139,6 +138,9 @@ typedef struct _tls_session_t { - bool invalid_hb_used; //!< Whether heartbleed attack was detected. - bool connected; //!< whether the outgoing socket is connected - bool is_init_finished; //!< whether or not init is finished -+ bool client_cert_ok; //!< whether or not we validated the client certificate -+ bool authentication_success; //!< whether or not the user was authenticated (cert or PW) -+ bool quick_session_tickets; //!< for EAP-TLS. - - /* - * Framed-MTU attribute in RADIUS, if present, can also be used to set this -@@ -160,8 +162,11 @@ typedef struct _tls_session_t { - void *opaque; - void (*free_opaque)(void *opaque); - -- char const *prf_label; -+ char const *label; - bool allow_session_resumption; //!< Whether session resumption is allowed. -+ bool session_not_resumed; //!< Whether our session was not resumed. -+ -+ fr_tls_server_conf_t const *conf; //! for better complaints - } tls_session_t; - - /* -@@ -309,16 +314,17 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char co - CC_HINT(format (printf, 4, 5)); - - void tls_global_cleanup(void); --tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert); -+tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, bool allow_tls13); - tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs); - fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs); - fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs); - fr_tls_server_conf_t *tls_server_conf_alloc(TALLOC_CTX *ctx); --SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client); -+SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file); - int tls_handshake_recv(REQUEST *, tls_session_t *ssn); - int tls_handshake_send(REQUEST *, tls_session_t *ssn); - void tls_session_information(tls_session_t *ssn); - void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize); -+X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf); - - /* - * Low-level TLS stuff -@@ -335,6 +341,7 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request); - #define FR_TLS_EX_INDEX_STORE (14) - #define FR_TLS_EX_INDEX_SSN (15) - #define FR_TLS_EX_INDEX_TALLOC (16) -+#define FR_TLS_EX_INDEX_FIX_CERT_ORDER (17) - - extern int fr_tls_ex_index_certs; - extern int fr_tls_ex_index_vps; -@@ -360,6 +367,10 @@ struct fr_tls_server_conf_t { - bool disable_tlsv1; - bool disable_tlsv1_1; - bool disable_tlsv1_2; -+ bool disallow_untrusted; //!< allow untrusted CAs to issue client certificates -+ -+ int min_version; -+ int max_version; - - char const *tls_min_version; - char const *tls_max_version; -@@ -371,16 +382,21 @@ struct fr_tls_server_conf_t { - bool check_crl; - bool check_all_crl; - bool allow_expired_crl; -+ uint32_t ca_path_reload_interval; -+ uint32_t ca_path_last_reload; -+ X509_STORE *old_x509_store; - char const *check_cert_cn; - char const *cipher_list; - bool cipher_server_preference; - char const *check_cert_issuer; -+ char const *sigalgs_list; - - bool session_cache_enable; -- uint32_t session_timeout; -+ uint32_t session_lifetime; - uint32_t session_cache_size; - char const *session_id_name; - char const *session_cache_path; -+ char const *session_cache_server; - fr_hash_table_t *cache_ht; - char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH]; - -@@ -389,6 +405,10 @@ struct fr_tls_server_conf_t { - char const *verify_client_cert_cmd; - bool require_client_cert; - -+ bool fix_cert_order; -+ -+ pthread_mutex_t mutex; -+ - #ifdef HAVE_OPENSSL_OCSP_H - /* - * OCSP Configuration -@@ -414,6 +434,15 @@ struct fr_tls_server_conf_t { - char const *psk_query; - #endif - -+ char const *realm_dir; -+ fr_hash_table_t *realms; -+ -+ char const *client_hostname; -+ -+#ifdef WITH_RADIUSV11 -+ char const *radiusv11_name; -+ fr_radiusv11_t radiusv11; -+#endif - }; - - #ifdef __cplusplus -diff --git a/src/include/token.h b/src/include/token.h -index 6cbd05217a..c8bb748702 100644 ---- a/src/include/token.h -+++ b/src/include/token.h -@@ -56,16 +56,17 @@ typedef enum fr_token_t { - T_OP_CMP_TRUE, /* =* 20 */ - T_OP_CMP_FALSE, /* !* */ - T_OP_CMP_EQ, /* == */ -+ T_OP_PREPEND, /* ^= */ - T_HASH, /* # */ -- T_BARE_WORD, /* bare word */ -- T_DOUBLE_QUOTED_STRING, /* "foo" 25 */ -+ T_BARE_WORD, /* bare word 25 */ -+ T_DOUBLE_QUOTED_STRING, /* "foo" */ - T_SINGLE_QUOTED_STRING, /* 'foo' */ - T_BACK_QUOTED_STRING, /* `foo` */ - T_TOKEN_LAST - } FR_TOKEN; - - #define T_EQSTART T_OP_ADD --#define T_EQEND (T_OP_CMP_EQ + 1) -+#define T_EQEND (T_OP_PREPEND + 1) - - typedef struct FR_NAME_NUMBER { - char const *name; -diff --git a/src/lib/dict.c b/src/lib/dict.c -index 96e06b5287..479bf1104e 100644 ---- a/src/lib/dict.c -+++ b/src/lib/dict.c -@@ -725,7 +725,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, - { - size_t namelen; - DICT_ATTR const *parent; -- DICT_ATTR *n; -+ DICT_ATTR *n; -+ DICT_ATTR const *old; - static int max_attr = 0; - - namelen = strlen(name); -@@ -882,6 +883,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, - return -1; - } - -+ if (flags.encrypt) flags.secret = 1; -+ - if (flags.length && (type != PW_TYPE_OCTETS)) { - fr_strerror_printf("The \"length\" flag can only be set for attributes of type \"octets\""); - return -1; -@@ -1080,6 +1083,18 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, - n->type = type; - n->flags = flags; - -+ /* -+ * Allow old-style names, but they always end up as -+ * new-style names. -+ */ -+ old = dict_attrbyvalue(n->attr, n->vendor); -+ if (old && (old->type == n->type)) { -+ DICT_ATTR *mutable; -+ -+ memcpy(&mutable, &old, sizeof(old)); /* const issues */ -+ mutable->flags.is_dup = true; -+ } -+ - /* - * Insert the attribute, only if it's not a duplicate. - */ -@@ -1258,6 +1273,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value) - fr_int2str(dict_attr_types, da->type, "?Unknown?")); - return -1; - } -+ /* in v4 this is done with the UNCONST #define */ -+ ((DICT_ATTR *)((uintptr_t)(da)))->flags.has_value = 1; - } else { - value_fixup_t *fixup; - -@@ -1742,6 +1759,10 @@ static int process_attribute(char const* fn, int const line, - "\"encrypt=3\" flag set", fn, line); - return -1; - } -+ flags.secret = 1; -+ -+ } else if (strncmp(key, "secret", 6) == 0) { -+ flags.secret = 1; - - } else if (strncmp(key, "array", 6) == 0) { - flags.array = 1; -@@ -2022,7 +2043,7 @@ static int process_value_alias(char const* fn, int const line, char **argv, - } - - --static int parse_format(char const *fn, int line, char const *format, int *pvalue, int *ptype, int *plength, bool *pcontinuation) -+static int parse_format(char const *fn, int line, char const *format, int *ptype, int *plength, bool *pcontinuation) - { - char const *p; - int type, length; -@@ -2075,9 +2096,8 @@ static int parse_format(char const *fn, int line, char const *format, int *pvalu - } - continuation = true; - -- if ((*pvalue != VENDORPEC_WIMAX) || -- (type != 1) || (length != 1)) { -- fr_strerror_printf("dict_init: %s[%d]: Only WiMAX VSAs can have continuations", -+ if ((type != 1) || (length != 1)) { -+ fr_strerror_printf("dict_init: %s[%d]: Only 'format=1,1' VSAs can have continuations", - fn, line); - return -1; - } -@@ -2132,7 +2152,7 @@ static int process_vendor(char const* fn, int const line, char **argv, - * Look for a format statement. Allow it to over-ride the hard-coded formats below. - */ - if (argc == 3) { -- if (parse_format(fn, line, argv[2], &value, &type, &length, &continuation) < 0) { -+ if (parse_format(fn, line, argv[2], &type, &length, &continuation) < 0) { - return -1; - } - -@@ -2409,7 +2429,8 @@ static int my_dict_init(char const *parent, char const *filename, - /* - * Optionally include a dictionary - */ -- if (strcasecmp(argv[0], "$INCLUDE-") == 0) { -+ if ((strcasecmp(argv[0], "$INCLUDE-") == 0) || -+ (strcasecmp(argv[0], "$-INCLUDE") == 0)) { - int rcode = my_dict_init(dir, argv[1], fn, line); - - if (rcode == -2) continue; -@@ -2783,45 +2804,72 @@ int dict_init(char const *dir, char const *fn) - return 0; - } - --static size_t print_attr_oid(char *buffer, size_t size, unsigned int attr, -- int dv_type) -+static size_t print_attr_oid(char *buffer, size_t bufsize, unsigned int attr, unsigned int vendor) - { -- int nest; -- size_t outlen; -+ int nest, dv_type = 1; - size_t len; -+ char *p = buffer; -+ -+ if (vendor > FR_MAX_VENDOR) { -+ len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); -+ p += len; -+ bufsize -= len; -+ vendor &= (FR_MAX_VENDOR) - 1; -+ } -+ -+ if (vendor) { -+ DICT_VENDOR *dv; -+ -+ /* -+ * dv_type is the length of the vendor's type field -+ * RFC 2865 never defined a mandatory length, so -+ * different vendors have different length type fields. -+ */ -+ dv = dict_vendorbyvalue(vendor); -+ if (dv) dv_type = dv->type; -+ -+ len = snprintf(p, bufsize, "26.%u.", vendor); -+ -+ p += len; -+ bufsize -= len; -+ } -+ - - switch (dv_type) { - default: - case 1: -- len = snprintf(buffer, size, "%u", attr & 0xff); -+ len = snprintf(p, bufsize, "%u", attr & 0xff); -+ p += len; -+ bufsize -= len; -+ if ((attr >> 8) == 0) return p - buffer; - break; - -- case 4: -- return snprintf(buffer, size, "%u", attr); -- - case 2: -- return snprintf(buffer, size, "%u", attr & 0xffff); -- -- } -+ len = snprintf(p, bufsize, "%u", attr & 0xffff); -+ p += len; -+ return p - buffer; - -- if ((attr >> 8) == 0) return len; -+ case 4: -+ len = snprintf(p, bufsize, "%u", attr); -+ p += len; -+ return p - buffer; - -- outlen = len; -- buffer += len; -- size -= len; -+ } - -+ /* -+ * "attr" is a sequence of packed numbers. Unpack them. -+ */ - for (nest = 1; nest <= fr_attr_max_tlv; nest++) { - if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break; - -- len = snprintf(buffer, size, ".%u", -+ len = snprintf(p, bufsize, ".%u", - (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]); - -- outlen = len; -- buffer += len; -- size -= len; -+ p += len; -+ bufsize -= len; - } - -- return outlen; -+ return p - buffer; - } - - /** Free dynamically allocated (unknown attributes) -@@ -2862,7 +2910,6 @@ void dict_attr_free(DICT_ATTR const **da) - int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor) - { - char *p; -- int dv_type = 1; - size_t len = 0; - size_t bufsize = DICT_ATTR_MAX_NAME_LEN; - -@@ -2888,32 +2935,7 @@ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vend - p += len; - bufsize -= len; - -- if (vendor > FR_MAX_VENDOR) { -- len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); -- p += len; -- bufsize -= len; -- vendor &= (FR_MAX_VENDOR) - 1; -- } -- -- if (vendor) { -- DICT_VENDOR *dv; -- -- /* -- * dv_type is the length of the vendor's type field -- * RFC 2865 never defined a mandatory length, so -- * different vendors have different length type fields. -- */ -- dv = dict_vendorbyvalue(vendor); -- if (dv) { -- dv_type = dv->type; -- } -- len = snprintf(p, bufsize, "26.%u.", vendor); -- -- p += len; -- bufsize -= len; -- } -- -- print_attr_oid(p, bufsize , attr, dv_type); -+ print_attr_oid(p, bufsize , attr, vendor); - - return 0; - } -@@ -3270,7 +3292,15 @@ DICT_ATTR const *dict_attrbyname(char const *name) - da = (DICT_ATTR *) buffer; - strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1); - -- return fr_hash_table_finddata(attributes_byname, da); -+ da = fr_hash_table_finddata(attributes_byname, da); -+ if (!da) return NULL; -+ -+ if (!da->flags.is_dup) return da; -+ -+ /* -+ * This MUST exist if the dup flag is set. -+ */ -+ return dict_attrbyvalue(da->attr, da->vendor); - } - - /** Look up a dictionary attribute by name embedded in another string -@@ -3464,3 +3494,13 @@ DICT_ATTR const *dict_unknown_add(DICT_ATTR const *old) - da = dict_attrbyvalue(old->attr, old->vendor); - return da; - } -+ -+size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da) -+{ -+ return print_attr_oid(buffer, buflen, da->attr, da->vendor); -+} -+ -+int dict_walk(fr_hash_table_walk_t callback, void *context) -+{ -+ return fr_hash_table_walk(attributes_byname, callback, context); -+} -diff --git a/src/lib/event.c b/src/lib/event.c -index 0c2976b3c9..d912955fe8 100644 ---- a/src/lib/event.c -+++ b/src/lib/event.c -@@ -28,6 +28,8 @@ RCSID("$Id$") - #include - #include - -+#include -+ - #ifdef HAVE_KQUEUE - #ifndef HAVE_SYS_EVENT_H - #error kqueue requires -@@ -39,7 +41,9 @@ RCSID("$Id$") - - typedef struct fr_event_fd_t { - int fd; -+ - fr_event_fd_handler_t handler; -+ fr_event_fd_handler_t write_handler; - void *ctx; - } fr_event_fd_t; - -@@ -61,13 +65,15 @@ struct fr_event_list_t { - int num_readers; - #ifndef HAVE_KQUEUE - int max_readers; -+ int max_fd; - -- bool changed; -- -+ fd_set read_fds; -+ fd_set write_fds; - #else - int kq; - struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ - #endif -+ - fr_event_fd_t readers[FR_EV_MAX_FDS]; - }; - -@@ -139,8 +145,9 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) - } - - #ifndef HAVE_KQUEUE -- el->changed = true; /* force re-set of fds's */ -- -+ el->max_fd = 0; -+ FD_ZERO(&el->read_fds); -+ FD_ZERO(&el->write_fds); - #else - el->kq = kqueue(); - if (el->kq < 0) { -@@ -161,6 +168,11 @@ int fr_event_list_num_fds(fr_event_list_t *el) - return el->num_readers; - } - -+int fr_event_list_full(fr_event_list_t *el) -+{ -+ return (el->num_readers >= FR_EV_MAX_FDS); -+} -+ - int fr_event_list_num_elements(fr_event_list_t *el) - { - if (!el) return 0; -@@ -432,6 +444,9 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, - el->num_readers++; - - if (i == el->max_readers) el->max_readers = i + 1; -+ -+ FD_SET(fd, &el->read_fds); -+ if (el->max_fd <= fd) el->max_fd = fd; - break; - } - } -@@ -446,13 +461,69 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, - ef->handler = handler; - ef->ctx = ctx; - --#ifndef HAVE_KQUEUE -- el->changed = true; --#endif -- - return 1; - } - -+int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, -+ fr_event_fd_handler_t write_handler, void *ctx) -+{ -+ int i; -+ -+ if (!el || (fd < 0)) return 0; -+ -+ if (type != 0) return 0; -+ -+#ifdef HAVE_KQUEUE -+ for (i = 0; i < FR_EV_MAX_FDS; i++) { -+ int j; -+ struct kevent evset; -+ -+ j = (i + fd) & (FR_EV_MAX_FDS - 1); -+ -+ if (el->readers[j].fd != fd) continue; -+ -+ fr_assert(ctx = el->readers[j].ctx); -+ -+ /* -+ * Tell us when the socket is ready for writing -+ */ -+ if (write_handler) { -+ fr_assert(!el->readers[j].write_handler); -+ -+ el->readers[j].write_handler = write_handler; -+ -+ EV_SET(&evset, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); -+ } else { -+ fr_assert(el->readers[j].write_handler); -+ -+ el->readers[j].write_handler = NULL; -+ -+ EV_SET(&evset, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); -+ } -+ if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { -+ fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); -+ return 0; -+ } -+ -+ return 1; -+ } -+ -+#else -+ -+ for (i = 0; i < el->max_readers; i++) { -+ if (el->readers[i].fd != fd) continue; -+ -+ fr_assert(ctx = el->readers[i].ctx); -+ el->readers[i].write_handler = write_handler; -+ -+ FD_SET(fd, &el->write_fds); /* fd MUST already be in the set of readers! */ -+ return 1; -+ } -+#endif /* HAVE_KQUEUE */ -+ -+ return 0; -+} -+ - int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) - { - int i; -@@ -480,6 +551,14 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) - EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); - (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); - -+ /* -+ * Delete the write handler if it exits. -+ */ -+ if (el->readers[j].write_handler) { -+ EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); -+ (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); -+ } -+ - el->readers[j].fd = -1; - el->num_readers--; - -@@ -487,14 +566,18 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) - } - - #else -- - for (i = 0; i < el->max_readers; i++) { - if (el->readers[i].fd == fd) { - el->readers[i].fd = -1; - el->num_readers--; - - if ((i + 1) == el->max_readers) el->max_readers = i; -- el->changed = true; -+ FD_CLR(fd, &el->read_fds); -+ FD_CLR(fd, &el->write_fds); -+ -+ /* -+ * @todo - update el->max_fd, too. -+ */ - return 1; - } - } -@@ -523,39 +606,13 @@ int fr_event_loop(fr_event_list_t *el) - #ifdef HAVE_KQUEUE - struct timespec ts_when, *ts_wake; - #else -- int maxfd = 0; -- fd_set read_fds, master_fds; -- -- el->changed = true; -+ fd_set read_fds, write_fds; - #endif - - el->exit = 0; - el->dispatch = true; - - while (!el->exit) { --#ifndef HAVE_KQUEUE -- /* -- * Cache the list of FD's to watch. -- */ -- if (el->changed) { --#ifdef __clang_analyzer__ -- memset(&master_fds, 0, sizeof(master_fds)); --#else -- FD_ZERO(&master_fds); --#endif -- for (i = 0; i < el->max_readers; i++) { -- if (el->readers[i].fd < 0) continue; -- -- if (el->readers[i].fd > maxfd) { -- maxfd = el->readers[i].fd; -- } -- FD_SET(el->readers[i].fd, &master_fds); -- } -- -- el->changed = false; -- } --#endif /* HAVE_KQUEUE */ -- - /* - * Find the first event. If there's none, we wait - * on the socket forever. -@@ -604,8 +661,9 @@ int fr_event_loop(fr_event_list_t *el) - if (el->status) el->status(wake); - - #ifndef HAVE_KQUEUE -- read_fds = master_fds; -- rcode = select(maxfd + 1, &read_fds, NULL, NULL, wake); -+ read_fds = el->read_fds; -+ write_fds = el->write_fds; -+ rcode = select(el->max_fd + 1, &read_fds, &write_fds, NULL, wake); - if ((rcode < 0) && (errno != EINTR)) { - fr_strerror_printf("Failed in select: %s", fr_syserror(errno)); - el->dispatch = false; -@@ -618,6 +676,7 @@ int fr_event_loop(fr_event_list_t *el) - ts_wake = &ts_when; - ts_when.tv_sec = when.tv_sec; - ts_when.tv_nsec = when.tv_usec * 1000; -+ - } else { - ts_wake = NULL; - } -@@ -644,11 +703,16 @@ int fr_event_loop(fr_event_list_t *el) - - if (ef->fd < 0) continue; - -+ /* -+ * Check if the socket is available for writing. -+ */ -+ if (ef->write_handler && FD_ISSET(ef->fd, &write_fds)) { -+ ef->write_handler(el, ef->fd, ef->ctx); -+ } -+ - if (!FD_ISSET(ef->fd, &read_fds)) continue; - - ef->handler(el, ef->fd, ef->ctx); -- -- if (el->changed) break; - } - - #else /* HAVE_KQUEUE */ -@@ -673,6 +737,11 @@ int fr_event_loop(fr_event_list_t *el) - continue; - } - -+ if (el->events[i].filter == EVFILT_WRITE) { -+ ef->write_handler(el, ef->fd, ef->ctx); -+ continue; -+ } -+ - /* - * Else it's our event. We only set - * EVFILT_READ, so it must be a read -@@ -719,7 +788,7 @@ static uint32_t event_rand(void) - { - uint32_t num; - -- num = rand_pool.randrsl[rand_pool.randcnt++]; -+ num = rand_pool.randrsl[rand_pool.randcnt++ & 0xff]; - if (rand_pool.randcnt == 256) { - fr_isaac(&rand_pool); - rand_pool.randcnt = 0; -diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c -index 1cca00fa2a..2aad490e30 100644 ---- a/src/lib/hmacmd5.c -+++ b/src/lib/hmacmd5.c -@@ -34,8 +34,9 @@ RCSID("$Id$") - - #include - #include -+#include - --#ifdef HAVE_OPENSSL_EVP_H -+#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS) - /** Calculate HMAC using OpenSSL's MD5 implementation - * - * @param digest Caller digest to be filled in. -@@ -49,6 +50,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t - uint8_t const *key, size_t key_len) - { - HMAC_CTX *ctx = HMAC_CTX_new(); -+ unsigned int len = MD5_DIGEST_LENGTH; - - #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW - /* Since MD5 is not allowed by FIPS, explicitly allow it. */ -@@ -57,7 +59,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t - - HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL); - HMAC_Update(ctx, text, text_len); -- HMAC_Final(ctx, digest, NULL); -+ HMAC_Final(ctx, digest, &len); - HMAC_CTX_free(ctx); - } - #else -diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c -index 211470ea35..8711f983b7 100644 ---- a/src/lib/hmacsha1.c -+++ b/src/lib/hmacsha1.c -@@ -13,6 +13,7 @@ RCSID("$Id$") - #ifdef HAVE_OPENSSL_EVP_H - #include - #include -+#include - #endif - - #include -@@ -35,9 +36,11 @@ void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_ - uint8_t const *key, size_t key_len) - { - HMAC_CTX *ctx = HMAC_CTX_new(); -+ unsigned int len = SHA1_DIGEST_LENGTH; -+ - HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL); - HMAC_Update(ctx, text, text_len); -- HMAC_Final(ctx, digest, NULL); -+ HMAC_Final(ctx, digest, &len); - HMAC_CTX_free(ctx); - } - -diff --git a/src/lib/md4.c b/src/lib/md4.c -index 0515fca1ae..7169000381 100644 ---- a/src/lib/md4.c -+++ b/src/lib/md4.c -@@ -28,6 +28,7 @@ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen - fr_md4_init(&ctx); - fr_md4_update(&ctx, in, inlen); - fr_md4_final(out, &ctx); -+ fr_md4_destroy(&ctx); - } - - #ifndef HAVE_OPENSSL_MD4_H -diff --git a/src/lib/md5.c b/src/lib/md5.c -index 9858175bd4..b5c1729148 100644 ---- a/src/lib/md5.c -+++ b/src/lib/md5.c -@@ -30,6 +30,7 @@ void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen) - fr_md5_init(&ctx); - fr_md5_update(&ctx, in, inlen); - fr_md5_final(out, &ctx); -+ fr_md5_destroy(&ctx); - } - - #ifndef HAVE_OPENSSL_MD5_H -diff --git a/src/lib/pair.c b/src/lib/pair.c -index d711f90c5d..146c82f95b 100644 ---- a/src/lib/pair.c -+++ b/src/lib/pair.c -@@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) { - return 0; - } - --static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) -+VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) - { - VALUE_PAIR *vp; - -@@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v - DICT_ATTR const *da; - - da = dict_attrbyvalue(attr, vendor); -- if (!da) { -- VALUE_PAIR *vp; -- -- vp = fr_pair_alloc(ctx); -- if (!vp) return NULL; -- -- /* -- * Ensure that the DA is parented by the VP. -- */ -- da = dict_unknown_afrom_fields(vp, attr, vendor); -- if (!da) { -- talloc_free(vp); -- return NULL; -- } -- -- vp->da = da; -- return vp; -- } -+ if (!da) return NULL; - - return fr_pair_afrom_da(ctx, da); - } -@@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add) - i->next = add; - } - -+/** Add a VP to the start of the list. -+ * -+ * Links the new VP to 'first', then points 'first' at the new VP. -+ * -+ * @param[in] first VP in linked list. Will add new VP to the start of this list. -+ * @param[in] add VP to add to list. -+ */ -+void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add) -+{ -+ VALUE_PAIR *i; -+ -+ if (!add) return; -+ -+ VERIFY_VP(add); -+ -+ if (*first == NULL) { -+ *first = add; -+ return; -+ } -+ -+ /* -+ * Find the end of the list to be prepended -+ */ -+ for (i = add; i->next; i = i->next) { -+#ifdef WITH_VERIFY_PTR -+ VERIFY_VP(i); -+ /* -+ * The same VP should never by added multiple times -+ * to the same list. -+ */ -+ fr_assert(*first != i); -+#endif -+ } -+ i->next = *first; -+ *first = add; -+} -+ - /** Replace all matching VPs - * - * Walks over 'first', and replaces the first VP that matches 'replace'. -@@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp) - * @param[in] ctx for talloc - * @param[in,out] to destination list. - * @param[in,out] from source list. -+ * @param[in] op operator for move. - * - * @see radius_pairmove - */ --void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) -+void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op) - { - VALUE_PAIR *i, *found; - VALUE_PAIR *head_new, **tail_new; -+ VALUE_PAIR *head_prepend; - VALUE_PAIR **tail_from; - - if (!to || !from || !*from) return; -@@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) - head_new = NULL; - tail_new = &head_new; - -+ /* -+ * Any attributes that are requested to be prepended -+ * are added to a temporary list here -+ */ -+ head_prepend = NULL; -+ - /* - * We're looping over the "from" list, moving some - * attributes out, but leaving others in place. -@@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) - fr_pair_steal(ctx, i); - tail_new = &(i->next); - continue; -+ case T_OP_PREPEND: -+ i->next = head_prepend; -+ head_prepend = i; -+ fr_pair_steal(ctx, i); -+ continue; - } - } /* loop over the "from" list. */ - - /* -- * Take the "new" list, and append it to the "to" list. -+ * If the op parameter was prepend, add the "new" list -+ * attributes first as those whose individual operator -+ * is prepend should be prepended to the resulting list - */ -- fr_pair_add(to, head_new); -+ if (op == T_OP_PREPEND) { -+ fr_pair_prepend(to, head_new); -+ } -+ -+ /* -+ * If there are any items in the prepend list prepend -+ * it to the "to" list -+ */ -+ fr_pair_prepend(to, head_prepend); -+ -+ /* -+ * If the op parameter was not prepend, take the "new" -+ * list, and append it to the "to" list. -+ */ -+ if (op != T_OP_PREPEND) { -+ fr_pair_add(to, head_new); -+ } - } - - /** Move matching pairs between VALUE_PAIR lists -@@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw) - break; - - default: -- fr_strerror_printf("Failed to find expected value on right hand side"); -+ fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand); - return T_INVALID; - } - -diff --git a/src/lib/print.c b/src/lib/print.c -index 9ac927358b..57455b6f30 100644 ---- a/src/lib/print.c -+++ b/src/lib/print.c -@@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) - * @param out Where to write the string. - * @param outlen Length of output buffer. - * @param vp to print. -+ * @param raw_value if true, the raw value is printed and not the enumerated attribute value - * @return the length of data written to out, or a value >= outlen on truncation. - */ --size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp) -+size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) - { - char const *q; - size_t len, freespace = outlen; -+ /* attempt to print raw_value when has_value is false, or raw_value is false, but only -+ if has_tag is also false */ -+ bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; - -- if (!vp->da->flags.has_tag) { -+ if (raw) { - switch (vp->da->type) { - case PW_TYPE_INTEGER: -- if (vp->da->flags.has_value) break; -- - return snprintf(out, freespace, "%u", vp->vp_integer); - - case PW_TYPE_SHORT: -- if (vp->da->flags.has_value) break; -- - return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); - - case PW_TYPE_BYTE: -- if (vp->da->flags.has_value) break; -- - return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); - -- case PW_TYPE_SIGNED: -- return snprintf(out, freespace, "%d", vp->vp_signed); -- - default: - break; - } -@@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) - - value = vp_aprints_value(ctx, vp, quote); - -- if (vp->da->flags.has_tag) { -+ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { - if (quote && (vp->da->type == PW_TYPE_STRING)) { - str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); - } else { -diff --git a/src/lib/radius.c b/src/lib/radius.c -index 3881111f7d..6dcee141e2 100644 ---- a/src/lib/radius.c -+++ b/src/lib/radius.c -@@ -28,6 +28,7 @@ RCSID("$Id$") - #include - - #include -+#include - - #include - #include -@@ -142,8 +143,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = { - "47", - "48", - "49", -- "IP-Address-Allocate", -- "IP-Address-Release", //!< 50 -+ "IP-Address-Allocate", //!< 50 -+ "IP-Address-Release", -+ "Protocol-Error", - }; - - -@@ -528,6 +530,8 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, - for ( i = 0; i < length; i++ ) { - digest[i] ^= value[i]; - } -+ -+ fr_md5_destroy(&context); - } - - #define MAX_PASS_LEN (128) -@@ -562,8 +566,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, - *outlen = len; - - fr_md5_init(&context); -+ fr_md5_init(&old); - fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); -- old = context; -+ fr_md5_copy(old, context); - - /* - * Do first pass. -@@ -572,7 +577,7 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, - - for (n = 0; n < len; n += AUTH_PASS_LEN) { - if (n > 0) { -- context = old; -+ fr_md5_copy(context, old); - fr_md5_update(&context, - passwd + n - AUTH_PASS_LEN, - AUTH_PASS_LEN); -@@ -585,6 +590,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, - } - - memcpy(output, passwd, len); -+ -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); - } - - -@@ -653,8 +661,9 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, - output[2] = inlen; /* length of the password string */ - - fr_md5_init(&context); -+ fr_md5_init(&old); - fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); -- old = context; -+ fr_md5_copy(old, context); - - fr_md5_update(&context, vector, AUTH_VECTOR_LEN); - fr_md5_update(&context, &output[0], 2); -@@ -663,7 +672,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, - size_t block_len; - - if (n > 0) { -- context = old; -+ fr_md5_copy(context, old); - fr_md5_update(&context, - output + 2 + n - AUTH_PASS_LEN, - AUTH_PASS_LEN); -@@ -681,6 +690,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, - output[i + 2 + n] ^= digest[i]; - } - } -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); - } - - static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest) -@@ -1552,6 +1563,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, - - VERIFY_VP(vp); - -+ if (room < 2) return -1; -+ - if (vp->da->vendor != 0) { - fr_strerror_printf("rad_vp2rfc called with VSA"); - return -1; -@@ -1594,6 +1607,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, - return 18; - } - -+ /* -+ * Hacks for NAS-Filter-Rule. They all get concatenated -+ * with 0x00 bytes in between the values. We rely on the -+ * decoder to do the opposite transformation on incoming -+ * packets. -+ */ -+ if (vp->da->attr == PW_NAS_FILTER_RULE) { -+ uint8_t const *end = ptr + room; -+ uint8_t *p, *attr = ptr; -+ bool zero = false; -+ -+ attr[0] = PW_NAS_FILTER_RULE; -+ attr[1] = 2; -+ p = ptr + 2; -+ -+ while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) { -+ if ((p + zero + vp->vp_length) > end) { -+ break; -+ } -+ -+ if (zero) { -+ if (attr[1] == 255) { -+ attr = p; -+ if ((attr + 3) >= end) break; -+ -+ attr[0] = PW_NAS_FILTER_RULE; -+ attr[1] = 2; -+ p = attr + 2; -+ } -+ -+ *(p++) = 0; -+ attr[1]++; -+ } -+ -+ /* -+ * Check for overflow -+ */ -+ if ((attr[1] + vp->vp_length) < 255) { -+ memcpy(p, vp->vp_strvalue, vp->vp_length); -+ attr[1] += vp->vp_length; -+ p += vp->vp_length; -+ -+ } else if (attr + (attr[1] + 2 + vp->vp_length) > end) { -+ break; -+ -+ } else if (vp->vp_length > 253) { -+ /* -+ * Drop VPs which are too long. -+ * We don't (yet) split one VP -+ * across multiple attributes. -+ */ -+ vp = vp->next; -+ continue; -+ -+ } else { -+ size_t first, second; -+ -+ first = 255 - attr[1]; -+ second = vp->vp_length - first; -+ -+ memcpy(p, vp->vp_strvalue, first); -+ p += first; -+ attr[1] = 255; -+ attr = p; -+ -+ attr[0] = PW_NAS_FILTER_RULE; -+ attr[1] = 2; -+ p = attr + 2; -+ -+ memcpy(p, vp->vp_strvalue + first, second); -+ attr[1] += second; -+ p += second; -+ } -+ -+ vp = vp->next; -+ zero = true; -+ } -+ -+ *pvp = vp; -+ return p - ptr; -+ } -+ - /* - * EAP-Message is special. - */ -@@ -1700,6 +1795,14 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, - return rad_vp2vsa(packet, original, secret, pvp, start, room); - } - -+static const bool code2ma[FR_MAX_PACKET_CODE] = { -+ [ PW_CODE_ACCESS_REQUEST ] = true, -+ [ PW_CODE_ACCESS_ACCEPT ] = true, -+ [ PW_CODE_ACCESS_REJECT ] = true, -+ [ PW_CODE_ACCESS_CHALLENGE ] = true, -+ [ PW_CODE_STATUS_SERVER ] = true, -+ [ PW_CODE_PROTOCOL_ERROR ] = true, -+}; - - /** Encode a packet - * -@@ -1712,6 +1815,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - uint16_t total_length; - int len; - VALUE_PAIR const *reply; -+ bool seen_ma = false; - - /* - * A 4K packet, aligned on 64-bits. -@@ -1775,6 +1879,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - * memcpy. - */ - -+ /* -+ * Always add Message-Authenticator for replies to -+ * Access-Request packets, and for all Access-Accept, -+ * Access-Reject, Access-Challenge. -+ * -+ * It must be the FIRST attribute in the packet. -+ */ -+ if (!packet->tls && -+ ((code2ma[packet->code]) || (original && code2ma[original->code]))) { -+ seen_ma = true; -+ -+ packet->offset = RADIUS_HDR_LEN; -+ -+ ptr[0] = PW_MESSAGE_AUTHENTICATOR; -+ ptr[1] = 18; -+ memset(ptr + 2, 0, 16); -+ -+ ptr += 18; -+ total_length += 18; -+ } -+ - /* - * Loop over the reply attributes for the packet. - */ -@@ -1832,6 +1957,14 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - * length and initial value. - */ - if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { -+ /* -+ * We have already encoded the Message-Authenticator, don't do it again. -+ */ -+ if (seen_ma) { -+ reply = reply->next; -+ continue; -+ } -+ - if (room < 18) break; - - /* -@@ -1973,11 +2106,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - - case PW_CODE_ACCOUNTING_REQUEST: - case PW_CODE_DISCONNECT_REQUEST: -- case PW_CODE_DISCONNECT_ACK: -- case PW_CODE_DISCONNECT_NAK: - case PW_CODE_COA_REQUEST: -- case PW_CODE_COA_ACK: -- case PW_CODE_COA_NAK: - memset(hdr->vector, 0, AUTH_VECTOR_LEN); - break; - -@@ -1985,6 +2114,10 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - case PW_CODE_ACCESS_ACCEPT: - case PW_CODE_ACCESS_REJECT: - case PW_CODE_ACCESS_CHALLENGE: -+ case PW_CODE_DISCONNECT_ACK: -+ case PW_CODE_DISCONNECT_NAK: -+ case PW_CODE_COA_ACK: -+ case PW_CODE_COA_NAK: - memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); - break; - -@@ -2036,6 +2169,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, - fr_md5_update(&context, (uint8_t const *) secret, - strlen(secret)); - fr_md5_final(digest, &context); -+ fr_md5_destroy(&context); - - memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); - memcpy(packet->vector, digest, AUTH_VECTOR_LEN); -@@ -2159,6 +2293,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) - fr_md5_update(&context, packet->data, packet->data_len); - fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); - fr_md5_final(digest, &context); -+ fr_md5_destroy(&context); - - /* - * Return 0 if OK, 2 if not OK. -@@ -2198,6 +2333,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, - fr_md5_update(&context, packet->data, packet->data_len); - fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); - fr_md5_final(calc_digest, &context); -+ fr_md5_destroy(&context); - - /* - * Copy the packet's vector back to the packet. -@@ -2323,6 +2459,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - radius_packet_t *hdr; - char host_ipaddr[128]; - bool require_ma = false; -+ bool limit_proxy_state = false; -+ bool seen_proxy_state = false; - bool seen_ma = false; - uint32_t num_attributes; - decode_fail_t failure = DECODE_FAIL_NONE; -@@ -2371,15 +2509,23 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - } - - /* -- * Message-Authenticator is required in Status-Server -- * packets, otherwise they can be trivially forged. -+ * If the caller requires Message-Authenticator, then set -+ * the flag. -+ * -+ * We also require Message-Authenticator if the packet -+ * code is Status-Server. -+ * -+ * If we're receiving packets from a proxy socket, then -+ * require Message-Authenticator for Access-* replies, -+ * and for Protocol-Error. - */ -- if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; -+ require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]); - - /* -- * It's also required if the caller asks for it. -+ * We only limit Proxy-State if we're not requiring -+ * Message-Authenticator. - */ -- if (flags) require_ma = true; -+ limit_proxy_state = ((flags & 0x04) != 0) && !require_ma; - - /* - * Repeat the length checks. This time, instead of -@@ -2534,6 +2680,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - case PW_EAP_MESSAGE: - require_ma = true; - eap = true; -+ packet->eap_message = true; - break; - - case PW_USER_PASSWORD: -@@ -2542,6 +2689,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - non_eap = true; - break; - -+ case PW_PROXY_STATE: -+ seen_proxy_state = true; -+ packet->proxy_state = true; -+ break; -+ - case PW_MESSAGE_AUTHENTICATOR: - if (attr[1] != 2 + AUTH_VECTOR_LEN) { - FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d", -@@ -2553,6 +2705,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - goto finish; - } - seen_ma = true; -+ packet->message_authenticator = true; - break; - } - -@@ -2609,7 +2762,19 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) - * Message-Authenticator attributes. - */ - if (require_ma && !seen_ma) { -- FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute", -+ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.", -+ inet_ntop(packet->src_ipaddr.af, -+ &packet->src_ipaddr.ipaddr, -+ host_ipaddr, sizeof(host_ipaddr))); -+ failure = DECODE_FAIL_MA_MISSING; -+ goto finish; -+ } -+ -+ /* -+ * The client is a NAS which shouldn't send Proxy-State, but it did! -+ */ -+ if (limit_proxy_state && seen_proxy_state && !seen_ma) { -+ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - host_ipaddr, sizeof(host_ipaddr))); -@@ -2896,6 +3061,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre - } - - -+/** Convert one or more NAS-Filter-Rule attributes to one or more -+ * attributes. -+ * -+ */ -+static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx, -+ DICT_ATTR const *da, uint8_t const *start, -+ size_t const packetlen, VALUE_PAIR **pvp) -+{ -+ uint8_t const *p = start; -+ uint8_t const *attr = start; -+ uint8_t const *end = start + packetlen; -+ uint8_t const *attr_end; -+ uint8_t *q; -+ VALUE_PAIR *vp; -+ uint8_t buffer[253]; -+ -+ q = buffer; -+ -+ /* -+ * The packet has already been sanity checked, so we -+ * don't care about walking off of the end of it. -+ */ -+ while (attr < end) { -+ if ((attr + 2) > end) { -+ fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok"); -+ return -1; -+ } -+ -+ if (attr[1] < 2) { -+ fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok"); -+ return -1; -+ } -+ if (attr[0] != PW_NAS_FILTER_RULE) break; -+ -+ /* -+ * Now decode one, or part of one rule. -+ */ -+ p = attr + 2; -+ attr_end = attr + attr[1]; -+ -+ if (attr_end > end) { -+ fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok"); -+ return -1; -+ } -+ -+ /* -+ * Coalesce data until the zero byte. -+ */ -+ while (p < attr_end) { -+ /* -+ * Once we hit the zero byte, create the -+ * VP, skip the zero byte, and reset the -+ * counters. -+ */ -+ if (*p == 0) { -+ /* -+ * Discard consecutive zeroes. -+ */ -+ if (q > buffer) { -+ vp = fr_pair_afrom_da(ctx, da); -+ if (!vp) { -+ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); -+ return -1; -+ } -+ -+ fr_pair_value_bstrncpy(vp, buffer, q - buffer); -+ -+ *pvp = vp; -+ pvp = &(vp->next); -+ q = buffer; -+ } -+ -+ p++; -+ continue; -+ } -+ *(q++) = *(p++); -+ -+ /* -+ * Not much reason to have rules which -+ * are too long. -+ */ -+ if ((size_t) (q - buffer) > sizeof(buffer)) { -+ fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long"); -+ return -1; -+ } -+ } -+ -+ /* -+ * Done this attribute. There MAY be things left -+ * in the buffer. -+ */ -+ attr = attr_end; -+ } -+ -+ if (q == buffer) return attr + attr[2] - start; -+ -+ vp = fr_pair_afrom_da(ctx, da); -+ if (!vp) { -+ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); -+ return -1; -+ } -+ -+ fr_pair_value_bstrncpy(vp, buffer, q - buffer); -+ -+ *pvp = vp; -+ -+ return p - start; -+} -+ - /** Convert a "concatenated" attribute to one long VP - * - */ -@@ -3014,6 +3288,7 @@ ssize_t rad_data2vp_tlvs(TALLOC_CTX *ctx, - tlv_len = data2vp(ctx, packet, original, secret, child, - data + 2, data[1] - 2, data[1] - 2, tail); - if (tlv_len < 0) { -+ dict_attr_free(&child); /* only frees unknowns */ - fr_pair_list_free(&head); - return -1; - } -@@ -3101,7 +3376,10 @@ static ssize_t data2vp_vsa(TALLOC_CTX *ctx, RADIUS_PACKET *packet, - attrlen - (dv->type + dv->length), - attrlen - (dv->type + dv->length), - pvp); -- if (my_len < 0) return my_len; -+ if (my_len < 0) { -+ dict_attr_free(&da); /* only frees unknowns */ -+ return my_len; -+ } - - return attrlen; - } -@@ -3154,7 +3432,10 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, - - rcode = data2vp(ctx, packet, original, secret, child, - data, attrlen, attrlen, pvp); -- if (rcode < 0) return rcode; -+ if (rcode < 0) { -+ dict_attr_free(&child); /* only frees unknowns */ -+ return rcode; -+ } - return attrlen; - } - -@@ -3165,7 +3446,6 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, - rcode = data2vp(ctx, packet, original, secret, da, - data + 2, attrlen - 2, attrlen - 2, - pvp); -- - if ((rcode < 0) || (((size_t) rcode + 2) != attrlen)) goto raw; /* didn't decode all of the data */ - return attrlen; - } -@@ -3307,7 +3587,10 @@ static ssize_t data2vp_wimax(TALLOC_CTX *ctx, - - rcode = data2vp(ctx, packet, original, secret, child, - data, attrlen, attrlen, pvp); -- if (rcode < 0) return rcode; -+ if (rcode < 0) { -+ dict_attr_free(&child); /* only frees unknowns */ -+ return rcode; -+ } - return attrlen; - } - -@@ -3503,7 +3786,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, - /* - * WiMAX craziness - */ -- if ((vendor == VENDORPEC_WIMAX) && dv->flags) { -+ if (dv->flags) { - rcode = data2vp_wimax(ctx, packet, original, secret, vendor, - data, attrlen, packetlen, pvp); - return rcode; -@@ -3610,19 +3893,12 @@ ssize_t data2vp(TALLOC_CTX *ctx, - return 0; - } - --#if !defined(NDEBUG) || defined(__clang_analyzer__) - /* -- * Hacks for Coverity. Editing the dictionary -- * will break assumptions about CUI. We know -- * this, but Coverity doesn't. -+ * Create a zero-length attribute. - */ -- if (da->type != PW_TYPE_OCTETS) return -1; --#endif -- -- data = buffer; -- *buffer = '\0'; -- datalen = 0; -- goto alloc_cui; /* skip everything */ -+ vp = fr_pair_afrom_da(ctx, da); -+ if (!vp) return -1; -+ goto done; - } - - /* -@@ -3874,8 +4150,10 @@ ssize_t data2vp(TALLOC_CTX *ctx, - /* - * This requires a whole lot more work. - */ -- return data2vp_extended(ctx, packet, original, secret, child, -- start, attrlen, packetlen, pvp); -+ rcode = data2vp_extended(ctx, packet, original, secret, child, -+ start, attrlen, packetlen, pvp); -+ if (rcode < 0) dict_attr_free(&child); /* only frees unknowns */ -+ return rcode; - - case PW_TYPE_EVS: - if (datalen < 6) goto raw; /* vid, vtype, value */ -@@ -3926,44 +4204,47 @@ ssize_t data2vp(TALLOC_CTX *ctx, - - default: - raw: -+ /* -+ * If it's already unknown, don't create a new -+ * unknown one. -+ */ -+ if (da->flags.is_unknown) break; -+ - /* - * Re-write the attribute to be "raw". It is - * therefore of type "octets", and will be - * handled below. -+ * -+ * We allocate the VP *first*, and then the da -+ * from it, so that there are no memory leaks. - */ -- da = dict_unknown_afrom_fields(ctx, da->attr, da->vendor); -+ vp = fr_pair_alloc(ctx); -+ if (!vp) return -1; -+ -+ da = dict_unknown_afrom_fields(vp, da->attr, da->vendor); - if (!da) { - fr_strerror_printf("Internal sanity check %d", __LINE__); - return -1; - } - tag = TAG_NONE; --#ifndef NDEBUG -- /* -- * Fix for Coverity. -- */ -- if (da->type != PW_TYPE_OCTETS) { -- dict_attr_free(&da); -- return -1; -- } --#endif -- break; -+ vp->da = da; -+ goto alloc_raw; - } - - /* - * And now that we've verified the basic type - * information, decode the actual data. - */ -- alloc_cui: - vp = fr_pair_afrom_da(ctx, da); -- if (!vp) return -1; -+ if (!vp) { -+ dict_attr_free(&da); /* only frees unknowns */ -+ return -1; -+ } - -+alloc_raw: - vp->vp_length = datalen; - vp->tag = tag; - --#ifdef __clang_analyzer__ -- if (!datalen && da->type != PW_TYPE_OCTETS) return -1; --#endif -- - switch (da->type) { - case PW_TYPE_STRING: - p = talloc_array(vp, char, vp->vp_length + 1); -@@ -4068,10 +4349,13 @@ ssize_t data2vp(TALLOC_CTX *ctx, - fail: - #endif - default: -+ dict_attr_free(&da); /* only frees unknowns */ - fr_pair_list_free(&vp); - fr_strerror_printf("Internal sanity check %d", __LINE__); - return -1; - } -+ -+done: - vp->type = VT_DATA; - *pvp = vp; - -@@ -4112,6 +4396,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, - return data2vp_concat(ctx, da, data, length, pvp); - } - -+ if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) { -+ VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n"); -+ return data2vp_nas_filter_rule(ctx, da, data, length, pvp); -+ } -+ - /* - * Note that we pass the entire length, not just the - * length of this attribute. The Extended or WiMAX -@@ -4120,7 +4409,10 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, - */ - rcode = data2vp(ctx, packet, original, secret, da, - data + 2, data[1] - 2, length - 2, pvp); -- if (rcode < 0) return rcode; -+ if (rcode < 0) { -+ dict_attr_free(&da); /* only frees unknowns */ -+ return rcode; -+ } - - return 2 + rcode; - } -@@ -4261,7 +4553,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, - uint32_t num_attributes; - uint8_t *ptr; - radius_packet_t *hdr; -- VALUE_PAIR *head, **tail, *vp; -+ VALUE_PAIR *head, **tail, *vp = NULL; - - /* - * Extract attribute-value pairs -@@ -4385,8 +4677,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, - secretlen = strlen(secret); - - fr_md5_init(&context); -+ fr_md5_init(&old); - fr_md5_update(&context, (uint8_t const *) secret, secretlen); -- old = context; /* save intermediate work */ -+ fr_md5_copy(old, context); /* save intermediate work */ - - /* - * Encrypt it in place. Don't bother checking -@@ -4397,7 +4690,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, - fr_md5_update(&context, vector, AUTH_PASS_LEN); - fr_md5_final(digest, &context); - } else { -- context = old; -+ fr_md5_copy(context, old); - fr_md5_update(&context, - (uint8_t *) passwd + n - AUTH_PASS_LEN, - AUTH_PASS_LEN); -@@ -4409,6 +4702,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, - } - } - -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); -+ - return 0; - } - -@@ -4441,8 +4737,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, - secretlen = strlen(secret); - - fr_md5_init(&context); -+ fr_md5_init(&old); - fr_md5_update(&context, (uint8_t const *) secret, secretlen); -- old = context; /* save intermediate work */ -+ fr_md5_copy(old, context); /* save intermediate work */ - - /* - * The inverse of the code above. -@@ -4452,7 +4749,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, - fr_md5_update(&context, vector, AUTH_VECTOR_LEN); - fr_md5_final(digest, &context); - -- context = old; -+ fr_md5_copy(context, old); - if (pwlen > AUTH_PASS_LEN) { - fr_md5_update(&context, (uint8_t *) passwd, - AUTH_PASS_LEN); -@@ -4460,7 +4757,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, - } else { - fr_md5_final(digest, &context); - -- context = old; -+ fr_md5_copy(context, old); - if (pwlen > (n + AUTH_PASS_LEN)) { - fr_md5_update(&context, (uint8_t *) passwd + n, - AUTH_PASS_LEN); -@@ -4473,6 +4770,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, - } - - done: -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); -+ - passwd[pwlen] = '\0'; - return strlen(passwd); - } -@@ -4609,8 +4909,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, - secretlen = strlen(secret); - - fr_md5_init(&context); -+ fr_md5_init(&old); - fr_md5_update(&context, (uint8_t const *) secret, secretlen); -- old = context; /* save intermediate work */ -+ fr_md5_copy(old, context); /* save intermediate work */ - - /* - * Set up the initial key: -@@ -4637,7 +4938,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, - - fr_md5_final(digest, &context); - -- context = old; -+ fr_md5_copy(context, old); - - /* - * A quick check: decrypt the first octet -@@ -4647,6 +4948,8 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, - reallen = passwd[2] ^ digest[0]; - if (reallen > encrypted_len) { - fr_strerror_printf("tunnel password is too long for the attribute"); -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); - return -1; - } - -@@ -4657,7 +4960,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, - - fr_md5_final(digest, &context); - -- context = old; -+ fr_md5_copy(context, old); - fr_md5_update(&context, passwd + n + 2, block_len); - } - -@@ -4669,6 +4972,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, - *pwlen = reallen; - passwd[reallen] = 0; - -+ fr_md5_destroy(&old); -+ fr_md5_destroy(&context); -+ - return reallen; - } - -diff --git a/src/lib/token.c b/src/lib/token.c -index 8ae41b392f..a7978622e6 100644 ---- a/src/lib/token.c -+++ b/src/lib/token.c -@@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = { - { "=*", T_OP_CMP_TRUE, }, - { "!*", T_OP_CMP_FALSE, }, - { "==", T_OP_CMP_EQ, }, -+ { "^=", T_OP_PREPEND, }, - { "=", T_OP_EQ, }, - { "!=", T_OP_NE, }, - { ">=", T_OP_GE, }, -@@ -78,9 +79,10 @@ const bool fr_assignment_op[] = { - false, /* =* 20 */ - false, /* !* */ - false, /* == */ -- false, /* # */ -- false, /* bare word */ -- false, /* "foo" 25 */ -+ true, /* ^= */ -+ false, /* # */ -+ false, /* bare word 25 */ -+ false, /* "foo" */ - false, /* 'foo' */ - false, /* `foo` */ - false -@@ -108,12 +110,13 @@ const bool fr_equality_op[] = { - true, /* < */ - true, /* =~ */ - true, /* !~ */ -- true, /* =* 20 */ -+ true, /* =* 20 */ - true, /* !* */ - true, /* == */ -- false, /* # */ -- false, /* bare word */ -- false, /* "foo" 25 */ -+ false, /* ^= */ -+ false, /* # */ -+ false, /* bare word 25 */ -+ false, /* "foo" */ - false, /* 'foo' */ - false, /* `foo` */ - false -@@ -144,9 +147,10 @@ const bool fr_str_tok[] = { - false, /* =* 20 */ - false, /* !* */ - false, /* == */ -- false, /* # */ -- true, /* bare word */ -- true, /* "foo" 25 */ -+ false, /* ^= */ -+ false, /* # */ -+ true, /* bare word 25 */ -+ true, /* "foo" */ - true, /* 'foo' */ - true, /* `foo` */ - false -diff --git a/src/main/cb.c b/src/main/cb.c -index 4ae14e575b..f8b2edbecc 100644 ---- a/src/main/cb.c -+++ b/src/main/cb.c -@@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - #ifdef WITH_TLS - void cbtls_info(SSL const *s, int where, int ret) - { -- char const *str, *state; -+ char const *role, *state; - REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); - - if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { -- str="TLS_connect"; -+ role = "Client "; - } else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) { -- str="TLS_accept"; -+ role = "Server "; - } else { -- str="(other)"; -+ role = ""; - } - - state = SSL_state_string_long(s); - state = state ? state : ""; - - if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) { -- RDEBUG2("%s: %s", str, state); -+ if (RDEBUG_ENABLED3) { -+ char const *abbrv = SSL_state_string(s); -+ size_t len; -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ STACK_OF(SSL_CIPHER) *client_ciphers; -+ STACK_OF(SSL_CIPHER) *server_ciphers; -+#endif -+ -+ /* -+ * Trim crappy OpenSSL state strings... -+ */ -+ len = strlen(abbrv); -+ if ((len > 1) && (abbrv[len - 1] == ' ')) len--; -+ -+ RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", -+ (int)len, abbrv, role, state, SSL_get_state(s)); -+ -+ /* -+ * After a ClientHello, list all the proposed ciphers from the client -+ */ -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) { -+ int i; -+ int num_ciphers; -+ const SSL_CIPHER *this_cipher; -+ -+ server_ciphers = SSL_get_ciphers(s); -+ if (server_ciphers) { -+ RDEBUG3("Server preferred ciphers (by priority)"); -+ num_ciphers = sk_SSL_CIPHER_num(server_ciphers); -+ for (i = 0; i < num_ciphers; i++) { -+ this_cipher = sk_SSL_CIPHER_value(server_ciphers, i); -+ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); -+ } -+ } -+ -+ client_ciphers = SSL_get_client_ciphers(s); -+ if (client_ciphers) { -+ RDEBUG3("Client preferred ciphers (by priority)"); -+ num_ciphers = sk_SSL_CIPHER_num(client_ciphers); -+ for (i = 0; i < num_ciphers; i++) { -+ this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); -+ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); -+ } -+ } -+ } -+#endif -+ } else { -+ RDEBUG2("(TLS) Handshake state - %s%s", role, state); -+ } - return; - } - - if (where & SSL_CB_ALERT) { - if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; - -- RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", -+ RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", - SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); - return; - } - - if (where & SSL_CB_EXIT) { - if (ret == 0) { -- RERROR("%s: Failed in %s", str, state); -+ RERROR("(TLS) %s: Failed in %s", role, state); - return; - } - - if (ret < 0) { - if (SSL_want_read(s)) { -- RDEBUG2("%s: Need to read more data: %s", str, state); -+ RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); - return; - } -- ERROR("tls: %s: Error in %s", str, state); -+ RERROR("(TLS) %s: Error in %s", role, state); - } - } - } -@@ -87,14 +136,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, - * content types. Which breaks our tracking of - * the SSL Session state. - */ -+#if OPENSSL_VERSION_NUMBER < 0x30000000L - if ((msg_version == 0) && (content_type > UINT8_MAX)) { -- DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i", -+#else -+ /* -+ * "...we do not see the need to resolve application breakage -+ * just because the documentation now is incorrect." -+ * -+ * https://github.com/openssl/openssl/issues/17262 -+ */ -+ if ((content_type > UINT8_MAX) && (content_type != SSL3_RT_INNER_CONTENT_TYPE)) { -+#endif -+ DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i", - content_type, msg_version); - return; - } - - if ((write_p != 0) && (write_p != 1)) { -- DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p); -+ DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p); - return; - } - -@@ -104,6 +163,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type, - */ - if (!state) return; - -+ if (rad_debug_lvl > 3) { -+ size_t i, j, data_len = len; -+ char buffer[3*16 + 1]; -+ uint8_t const *in = inbuf; -+ -+ DEBUG("(TLS) Received %zu bytes of TLS data", len); -+ if (data_len > 256) data_len = 256; -+ -+ for (i = 0; i < data_len; i += 16) { -+ for (j = 0; j < 16; j++) { -+ if ((i + j) >= data_len) break; -+ -+ sprintf(buffer + 3 * j, "%02x ", in[i + j]); -+ } -+ -+ DEBUG("(TLS) %s", buffer); -+ } -+ } -+ - /* - * 0 - received (from peer) - * 1 - sending (to peer) -@@ -111,7 +189,6 @@ void cbtls_msg(int write_p, int msg_version, int content_type, - state->info.origin = write_p; - state->info.content_type = content_type; - state->info.record_len = len; -- state->info.version = msg_version; - state->info.initialized = true; - - if (content_type == SSL3_RT_ALERT) { -@@ -124,6 +201,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type, - state->info.alert_level = 0x00; - state->info.alert_description = 0x00; - -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) { -+ /* let tls_ack_handler set application_data */ -+ state->info.content_type = SSL3_RT_HANDSHAKE; -+#endif -+ - #ifdef SSL3_RT_HEARTBEAT - } else if (content_type == TLS1_RT_HEARTBEAT) { - uint8_t *p = buf; -@@ -141,16 +224,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, - } - #endif - } -+ - tls_session_information(state); - } - - int cbtls_password(char *buf, -- int num UNUSED, -+ int num, - int rwflag UNUSED, - void *userdata) - { -- strcpy(buf, (char *)userdata); -- return(strlen((char *)userdata)); -+ size_t len; -+ -+ len = strlcpy(buf, (char *)userdata, num); -+ if (len >= (size_t) num) { -+ ERROR("Password too long. Maximum length is %i bytes", num - 1); -+ return 0; -+ } -+ -+ return len; - } - - #endif -diff --git a/src/main/client.c b/src/main/client.c -index 6228438c47..4d67a27688 100644 ---- a/src/main/client.c -+++ b/src/main/client.c -@@ -41,11 +41,16 @@ RCSID("$Id$") - #endif - - struct radclient_list { -+ char const *name; /* name of this list */ -+ char const *server; /* virtual server associated with this client list */ -+ - /* - * FIXME: One set of trees for IPv4, and another for IPv6? - */ - rbtree_t *trees[129]; /* for 0..128, inclusive. */ - uint32_t min_prefix; -+ -+ bool parsed; - }; - - -@@ -145,6 +150,15 @@ RADCLIENT_LIST *client_list_init(CONF_SECTION *cs) - - clients->min_prefix = 128; - -+ /* -+ * Associate the "clients" list with the virtual server. -+ */ -+ if (cs && (cf_data_add(cs, "clients", clients, NULL) < 0)) { -+ ERROR("Failed to associate client list with section %s\n", cf_section_name1(cs)); -+ client_list_free(clients); -+ return false; -+ } -+ - return clients; - } - -@@ -161,6 +175,18 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) - - if (!client) return false; - -+ /* -+ * Initialize the global list, if not done already. -+ */ -+ if (!root_clients) { -+ root_clients = cf_data_find(main_config.config, "clients"); -+ if (!root_clients) root_clients = client_list_init(main_config.config); -+ if (!root_clients) { -+ ERROR("Cannot add client - failed creating client list"); -+ return false; -+ } -+ } -+ - /* - * Hack to fixup wildcard clients - * -@@ -188,66 +214,85 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) - /* - * If the client also defines a server, do that now. - */ -- if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; -+ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_home_server)) return false; - - /* -- * If "clients" is NULL, it means add to the global list, -- * unless we're trying to add it to a virtual server... -+ * If there's no client list, BUT there's a virtual -+ * server, try to add the client to the appropriate -+ * "clients" section for that virtual server. - */ -- if (!clients) { -- if (client->server != NULL) { -- CONF_SECTION *cs; -- CONF_SECTION *subcs; -- -- cs = cf_section_sub_find_name2(main_config.config, "server", client->server); -- if (!cs) { -- ERROR("Failed to find virtual server %s", client->server); -- return false; -- } -+ if (!clients && client->server) { -+ CONF_SECTION *cs; -+ CONF_SECTION *subcs; -+ CONF_PAIR *cp; -+ char const *section_name; - -- /* -- * If this server has no "listen" section, add the clients -- * to the global client list. -- */ -- subcs = cf_section_sub_find(cs, "listen"); -- if (!subcs) { -- DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", -- client->server); -- goto global_clients; -- } -+ cs = cf_section_sub_find_name2(main_config.config, "server", client->server); -+ if (!cs) { -+ ERROR("Cannot add client - virtual server %s does not exist", client->server); -+ return false; -+ } - -- /* -- * If the client list already exists, use that. -- * Otherwise, create a new client list. -- */ -- clients = cf_data_find(cs, "clients"); -- if (!clients) { -- clients = client_list_init(cs); -- if (!clients) { -- ERROR("Out of memory"); -- return false; -- } -+ /* -+ * If this server has no "listen" section, add the clients -+ * to the global client list. -+ */ -+ subcs = cf_section_sub_find(cs, "listen"); -+ if (!subcs) { -+ DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", -+ client->server); -+ goto check_list; -+ } - -- if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { -- ERROR("Failed to associate clients with virtual server %s", client->server); -- client_list_free(clients); -- return false; -- } -- } -+ cp = cf_pair_find(subcs, "clients"); -+ if (!cp) { -+ DEBUG("No 'clients' configuration item in first listener of virtual server %s. Adding client to global client list", -+ client->server); -+ goto check_list; -+ } - -- } else { -- global_clients: -- /* -- * Initialize the global list, if not done already. -- */ -- if (!root_clients) { -- root_clients = client_list_init(NULL); -- if (!root_clients) return false; -- } -- clients = root_clients; -+ /* -+ * Duplicate the lookup logic in common_socket_parse() -+ * -+ * Explicit list given: use it. -+ */ -+ section_name = cf_pair_value(cp); -+ if (!section_name) goto check_list; -+ -+ subcs = cf_section_sub_find_name2(main_config.config, "clients", section_name); -+ if (!subcs) { -+ subcs = cf_section_find(section_name); -+ } -+ if (!subcs) { -+ cf_log_err_cs(cs, -+ "Failed to find clients %s {...}", -+ section_name); -+ return false; -+ } -+ -+ DEBUG("Adding client to client list %s", section_name); -+ -+ /* -+ * If the client list already exists, use that. -+ * Otherwise, create a new client list. -+ * -+ * @todo - add the client to _all_ listeners? -+ */ -+ clients = cf_data_find(subcs, "clients"); -+ if (clients) goto check_list; -+ -+ clients = client_list_init(subcs); -+ if (!clients) { -+ ERROR("Cannot add client - failed creating client list %s for server %s", section_name, -+ client->server); -+ return false; - } - } - -+check_list: -+ if (!clients) clients = root_clients; -+ client->list = clients; -+ - /* - * Create a tree for it. - */ -@@ -280,10 +325,11 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) - #endif - #ifdef WITH_COA - namecmp(coa_name) && -- (old->coa_server == client->coa_server) && -- (old->coa_pool == client->coa_pool) && -+ (old->coa_home_server == client->coa_home_server) && -+ (old->coa_home_pool == client->coa_home_pool) && - #endif -- (old->message_authenticator == client->message_authenticator)) { -+ (old->require_ma == client->require_ma) && -+ (old->limit_proxy_state == client->limit_proxy_state)) { - WARN("Ignoring duplicate client %s", client->longname); - client_free(client); - return true; -@@ -445,6 +491,8 @@ static fr_ipaddr_t cl_ipaddr; - static uint32_t cl_netmask; - static char const *cl_srcipaddr = NULL; - static char const *hs_proto = NULL; -+static char const *require_message_authenticator = NULL; -+static char const *limit_proxy_state = NULL; - - #ifdef WITH_TCP - static CONF_PARSER limit_config[] = { -@@ -467,7 +515,8 @@ static const CONF_PARSER client_config[] = { - - { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL }, - -- { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" }, -+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, -+ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL }, - - { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL }, - { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL }, -@@ -512,14 +561,34 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls - * it. Otherwise create a new one. - */ - clients = cf_data_find(section, "clients"); -- if (clients) return clients; -- -- clients = client_list_init(section); -- if (!clients) return NULL; -+ if (clients) { -+ /* -+ * Modules are initialized before the listeners. -+ * Which means that we MIGHT have read clients -+ * from SQL before parsing this "clients" -+ * section. So there may already be a clients -+ * list. -+ * -+ * But the list isn't _our_ list that we parsed, -+ * so we still need to parse the clients here. -+ */ -+ if (clients->parsed) return clients; -+ } else { -+ clients = client_list_init(section); -+ if (!clients) return NULL; -+ } - -- if (cf_top_section(section) == section) global = true; -+ if (cf_top_section(section) == section) { -+ global = true; -+ clients->name = "global"; -+ clients->server = NULL; -+ } - -- if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; -+ if (strcmp("server", cf_section_name1(section)) == 0) { -+ clients->name = NULL; -+ clients->server = cf_section_name2(section); -+ in_server = true; -+ } - - for (cs = cf_subsection_find_next(section, NULL, "client"); - cs; -@@ -592,8 +661,8 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls - * Check for valid characters - */ - for (p = dp->d_name; *p != '\0'; p++) { -- if (isalpha((int)*p) || -- isdigit((int)*p) || -+ if (isalpha((uint8_t)*p) || -+ isdigit((uint8_t)*p) || - (*p == ':') || - (*p == '.')) continue; - break; -@@ -632,15 +701,6 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls - - } - -- /* -- * Associate the clients structure with the section. -- */ -- if (cf_data_add(section, "clients", clients, NULL) < 0) { -- cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); -- client_list_free(clients); -- return NULL; -- } -- - /* - * Replace the global list of clients with the new one. - * The old one is still referenced from the original -@@ -648,6 +708,7 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls - */ - if (global) root_clients = clients; - -+ clients->parsed = true; - return clients; - } - -@@ -663,7 +724,7 @@ static const CONF_PARSER dynamic_config[] = { - { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL }, - { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL }, - -- { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL }, -+ { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL }, - - { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" }, - { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" }, -@@ -824,7 +885,7 @@ int client_map_section(CONF_SECTION *out, CONF_SECTION const *map, client_value_ - * @param ctx to allocate new clients in. - * @param cs to process as a client. - * @param in_server Whether the client should belong to a specific virtual server. -- * @param with_coa If true and coa_server or coa_pool aren't specified automatically, -+ * @param with_coa If true and coa_home_server or coa_home_pool aren't specified automatically, - * create a coa home_server section and add it to the client CONF_SECTION. - * @return new RADCLIENT struct. - */ -@@ -845,8 +906,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo - c = talloc_zero(ctx, RADCLIENT); - c->cs = cs; - -+ /* -+ * Set the "require message authenticator" and "limit -+ * proxy state" flags from the global default. If the -+ * configuration item exists, AND is set, it will -+ * over-ride the flag. -+ */ -+ c->require_ma = main_config.require_ma; -+ c->limit_proxy_state = main_config.limit_proxy_state; -+ - memset(&cl_ipaddr, 0, sizeof(cl_ipaddr)); - cl_netmask = 255; -+ require_message_authenticator = NULL; -+ limit_proxy_state = NULL; - - if (cf_section_parse(cs, c, client_config) < 0) { - cf_log_err_cs(cs, "Error parsing client section"); -@@ -856,6 +928,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo - hs_proto = NULL; - cl_srcipaddr = NULL; - #endif -+ require_message_authenticator = NULL; -+ limit_proxy_state = NULL; - - return NULL; - } -@@ -1054,11 +1128,11 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo - cp = cf_pair_find(cs, "coa_server"); - if (cp) { - c->coa_name = cf_pair_value(cp); -- c->coa_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); -- if (!c->coa_pool) { -- c->coa_server = home_server_byname(c->coa_name, HOME_TYPE_COA); -+ c->coa_home_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); -+ if (!c->coa_home_pool) { -+ c->coa_home_server = home_server_byname(c->coa_name, HOME_TYPE_COA); - } -- if (!c->coa_pool && !c->coa_server) { -+ if (!c->coa_home_pool && !c->coa_home_server) { - cf_log_err_cs(cs, "No such home_server or home_server_pool \"%s\"", c->coa_name); - goto error; - } -@@ -1096,7 +1170,7 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo - - rad_assert(home->type == HOME_TYPE_COA); - -- c->coa_server = home; -+ c->coa_home_server = home; - c->defines_coa_server = true; - } - } -@@ -1114,6 +1188,16 @@ done_coa: - } - #endif - -+ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) { -+ goto error; -+ } -+ -+ if (c->require_ma != FR_BOOL_TRUE) { -+ if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) { -+ goto error; -+ } -+ } -+ - return c; - } - -@@ -1158,7 +1242,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons - if (shortname) c->shortname = talloc_typed_strdup(c, shortname); - if (type) c->nas_type = talloc_typed_strdup(c, type); - if (server) c->server = talloc_typed_strdup(c, server); -- c->message_authenticator = require_ma; -+ c->require_ma = require_ma; - - return c; - } -@@ -1175,7 +1259,6 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) - int i, *pi; - char **p; - RADCLIENT *c; -- CONF_PAIR *cp = NULL; - char buffer[128]; - - vp_cursor_t cursor; -@@ -1206,6 +1289,7 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) - for (i = 0; dynamic_config[i].name != NULL; i++) { - DICT_ATTR const *da; - char *strvalue = NULL; -+ CONF_PAIR *cp = NULL; - - da = dict_attrbyname(dynamic_config[i].name); - if (!da) { -@@ -1344,10 +1428,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) - *pi = vp->vp_integer; - - /* -- * Same nastiness as above. -+ * Same nastiness as above, but hard-coded for require Message-Authenticator. - */ - for (parse = client_config; parse->name; parse++) { -- if (parse->offset == dynamic_config[i].offset) break; -+ if (parse->type == PW_TYPE_BOOLEAN) break; - } - if (!parse) break; - -@@ -1377,6 +1461,8 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) - fr_cursor_first(&cursor); - vp = fr_cursor_remove(&cursor); - if (vp) { -+ CONF_PAIR *cp; -+ - do { - char *value; - -@@ -1436,6 +1522,11 @@ validate: - goto error; - } - -+ /* -+ * It can't be set to "auto". Too bad. -+ */ -+ c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma; -+ - if (!client_add_dynamic(clients, request->client, c)) { - return NULL; - } -diff --git a/src/main/command.c b/src/main/command.c -index 844898eed1..706260b150 100644 ---- a/src/main/command.c -+++ b/src/main/command.c -@@ -27,19 +27,14 @@ - #include - #include - #include -+#include -+#include - - #include - #ifdef HAVE_INTTYPES_H - #include - #endif - --#ifdef HAVE_SYS_UN_H --#include --#ifndef SUN_LEN --#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) --#endif --#endif -- - #ifdef HAVE_SYS_STAT_H - #include - #endif -@@ -1009,6 +1004,11 @@ static char const *method_names[MOD_COUNT] = { - "pre-proxy", - "post-proxy", - "post-auth" -+#ifdef WITH_COA -+ , -+ "recv-coa", -+ "send-coa" -+#endif - }; - - -@@ -1138,7 +1138,7 @@ static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED - } - - #ifdef WITH_PROXY --static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) -+static int command_show_home_servers(rad_listen_t *listener, int argc, char *argv[]) - { - int i; - home_server_t *home; -@@ -1146,9 +1146,10 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN - - char buffer[256]; - -- for (i = 0; i < 256; i++) { -- home = home_server_bynumber(i); -- if (!home) break; -+ for (i = 0; i < home_server_max_number; i++) { -+ -+ if ((home = home_server_bynumber(i)) == NULL) -+ continue; - - /* - * Internal "virtual" home server. -@@ -1190,6 +1191,9 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN - } else if (home->state == HOME_STATE_IS_DEAD) { - state = "dead"; - -+ } else if (home->state == HOME_STATE_ADMIN_DOWN) { -+ state = "down"; -+ - } else if (home->state == HOME_STATE_UNKNOWN) { - time_t now = time(NULL); - -@@ -1212,10 +1216,19 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN - - } else continue; - -- cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", -- ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), -- home->port, proto, type, state, -- home->currently_outstanding); -+ if (argc > 0 && !strcmp(argv[0], "all")) { -+ char const *dynamic = home->dynamic ? "yes" : "no"; -+ -+ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\t(name=%s, dynamic=%s)\n", -+ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), -+ home->port, proto, type, state, -+ home->currently_outstanding, home->name, dynamic); -+ } else { -+ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", -+ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), -+ home->port, proto, type, state, -+ home->currently_outstanding); -+ } - } - - return CMD_OK; -@@ -1239,27 +1252,107 @@ static int command_show_client_config(rad_listen_t *listener, int argc, char *ar - return 1; - } - -+/* -+ * @todo - copied from clients.c. Better to re-use, but whatever. -+ */ -+struct radclient_list { -+ char const *name; /* name of this list */ -+ char const *server; /* virtual server associated with this client list */ -+ -+ /* -+ * FIXME: One set of trees for IPv4, and another for IPv6? -+ */ -+ rbtree_t *trees[129]; /* for 0..128, inclusive. */ -+ uint32_t min_prefix; -+}; -+ - --static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) -+static int command_show_clients(rad_listen_t *listener, int argc, char *argv[]) - { - int i; - RADCLIENT *client; - char buffer[256]; - -- for (i = 0; i < 256; i++) { -- client = client_findbynumber(NULL, i); -- if (!client) break; -+ if (argc == 0) { -+ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { -+ ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); -+ -+ if (((client->ipaddr.af == AF_INET) && -+ (client->ipaddr.prefix != 32)) || -+ ((client->ipaddr.af == AF_INET6) && -+ (client->ipaddr.prefix != 128))) { -+ cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); -+ } else { -+ cprintf(listener, "%s\n", buffer); -+ } -+ } - -- ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); -+ return CMD_OK; -+ } - -- if (((client->ipaddr.af == AF_INET) && -- (client->ipaddr.prefix != 32)) || -- ((client->ipaddr.af == AF_INET6) && -- (client->ipaddr.prefix != 128))) { -- cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); -+ if (argc != 1) { -+ cprintf_error(listener, "Unknown command %s %s ...\n", argv[0], argv[1]); -+ return -1; -+ } -+ -+ if (strcmp(argv[0], "verbose") != 0) { -+ cprintf_error(listener, "Unknown command %s\n", argv[0]); -+ return -1; -+ } -+ -+ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { -+ if (client->cs) { -+ cprintf(listener, "client %s {\n", cf_section_name2(client->cs)); - } else { -- cprintf(listener, "%s\n", buffer); -+ cprintf(listener, "client {\n"); -+ } -+ -+ fr_ntop(buffer, sizeof(buffer), &client->ipaddr); -+ cprintf(listener, "\tipaddr = %s\n", buffer); -+ -+ if (client->src_ipaddr.af != AF_UNSPEC) { -+ fr_ntop(buffer, sizeof(buffer), &client->src_ipaddr); -+ cprintf(listener, "\tsrc_ipaddr = %s\n", buffer); -+ } -+ -+ if (client->proto == IPPROTO_UDP) { -+ cprintf(listener, "\tproto = udp\n"); -+ } else if (client->proto == IPPROTO_TCP) { -+ cprintf(listener, "\tproto = tcp\n"); -+ } else { -+ cprintf(listener, "\tproto = *\n"); -+ } -+ -+ cprintf(listener, "\tsecret = %s\n", client->secret); -+ cprintf(listener, "\tlongname = %s\n", client->longname); -+ cprintf(listener, "\tshortname = %s\n", client->shortname); -+ if (client->nas_type) cprintf(listener, "\tnas_type = %s\n", client->nas_type); -+ cprintf(listener, "\tnumber = %d\n", client->number); -+ -+ if (client->server) { -+ cprintf(listener, "\tvirtual_server = %s\n", client->server); -+ } -+ -+#ifdef WITH_DYNAMIC_CLIENTS -+ if (client->dynamic) { -+ cprintf(listener, "\tdynamic = yes\n"); -+ cprintf(listener, "\tlifetime = %u\n", client->lifetime); -+ } -+#endif -+ -+#ifdef WITH_TLS -+ if (client->tls_required) { -+ cprintf(listener, "\ttls = yes\n"); - } -+#endif -+ -+ if (client->list && client->list->server) { -+ cprintf(listener, "\tparent_virtual_server = %s\n", client->list->server); -+ } else { -+ cprintf(listener, "\tglobal = yes\n"); -+ } -+ -+ cprintf(listener, "}\n"); - } - - return CMD_OK; -@@ -1301,7 +1394,7 @@ static int command_debug_file(rad_listen_t *listener, int argc, char *argv[]) - return -1; - } - -- if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) { -+ if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) == argv[0])) { - cprintf_error(listener, "Cannot direct debug logs to absolute path.\n"); - } - -@@ -1646,8 +1739,14 @@ static int command_set_home_server_state(rad_listen_t *listener, int argc, char - } else if (strcmp(argv[last], "dead") == 0) { - struct timeval now; - -- gettimeofday(&now, NULL); /* we do this WAY too ofetn */ -- mark_home_server_dead(home, &now); -+ gettimeofday(&now, NULL); /* we do this WAY too often */ -+ mark_home_server_dead(home, &now, false); -+ -+ } else if (strcmp(argv[last], "down") == 0) { -+ struct timeval now; -+ -+ gettimeofday(&now, NULL); /* we do this WAY too often */ -+ mark_home_server_dead(home, &now, true); - - } else { - cprintf_error(listener, "Unknown state \"%s\"\n", argv[last]); -@@ -1677,6 +1776,10 @@ static int command_show_home_server_state(rad_listen_t *listener, int argc, char - cprintf(listener, "zombie\n"); - break; - -+ case HOME_STATE_ADMIN_DOWN: -+ cprintf(listener, "down\n"); -+ break; -+ - case HOME_STATE_UNKNOWN: - cprintf(listener, "unknown\n"); - break; -@@ -2047,7 +2150,7 @@ static fr_command_table_t command_table_show_client[] = { - "- show configuration for given client", - command_show_client_config, NULL }, - { "list", FR_READ, -- "show client list - shows list of global clients", -+ "show client list [verbose] - shows list of global clients", - command_show_clients, NULL }, - - { NULL, 0, NULL, NULL, NULL } -@@ -2056,7 +2159,7 @@ static fr_command_table_t command_table_show_client[] = { - #ifdef WITH_PROXY - static fr_command_table_t command_table_show_home[] = { - { "list", FR_READ, -- "show home_server list - shows list of home servers", -+ "show home_server list [all] - shows list of home servers", - command_show_home_servers, NULL }, - { "state", FR_READ, - "show home_server state [udp|tcp] [src ] - shows state of given home server", -@@ -2597,6 +2700,36 @@ static int command_del_client(rad_listen_t *listener, int argc, char *argv[]) - } - - -+static int command_del_home_server(rad_listen_t *listener, int argc, char *argv[]) -+{ -+ if (argc < 2) { -+ cprintf_error(listener, " and are required\n"); -+ return 0; -+ } -+ -+ if (home_server_delete(argv[0], argv[1]) < 0) { -+ cprintf_error(listener, "Failed deleted home_server %s - %s\n", argv[1], fr_strerror()); -+ return 0; -+ } -+ -+ return CMD_OK; -+} -+ -+static int command_add_home_server_file(rad_listen_t *listener, int argc, char *argv[]) -+{ -+ if (argc < 1) { -+ cprintf_error(listener, " is required\n"); -+ return 0; -+ } -+ -+ if (home_server_afrom_file(argv[0]) < 0) { -+ cprintf_error(listener, "Unable to add home server - %s\n", fr_strerror()); -+ return 0; -+ } -+ -+ return CMD_OK; -+} -+ - static fr_command_table_t command_table_del_client[] = { - { "ipaddr", FR_WRITE, - "del client ipaddr [udp|tcp] [listen ] - Delete a dynamically created client", -@@ -2605,16 +2738,26 @@ static fr_command_table_t command_table_del_client[] = { - { NULL, 0, NULL, NULL, NULL } - }; - -+static fr_command_table_t command_table_del_home_server[] = { -+ { "file", FR_WRITE, -+ "del home_server file [auth|acct|coa] - Delete a dynamically created home_server", -+ command_del_home_server, NULL }, -+ -+ { NULL, 0, NULL, NULL, NULL } -+}; - - static fr_command_table_t command_table_del[] = { - { "client", FR_WRITE, - "del client - Delete client configuration commands", - NULL, command_table_del_client }, - -+ { "home_server", FR_WRITE, -+ "del home_server - Delete home_server configuration commands", -+ NULL, command_table_del_home_server }, -+ - { NULL, 0, NULL, NULL, NULL } - }; - -- - static fr_command_table_t command_table_add_client[] = { - { "file", FR_WRITE, - "add client file - Add new client definition from ", -@@ -2623,12 +2766,23 @@ static fr_command_table_t command_table_add_client[] = { - { NULL, 0, NULL, NULL, NULL } - }; - -+static fr_command_table_t command_table_add_home_server[] = { -+ { "file", FR_WRITE, -+ "add home_server file - Add new home serverdefinition from ", -+ command_add_home_server_file, NULL }, -+ -+ { NULL, 0, NULL, NULL, NULL } -+}; - - static fr_command_table_t command_table_add[] = { - { "client", FR_WRITE, - "add client - Add client configuration commands", - NULL, command_table_add_client }, - -+ { "home_server", FR_WRITE, -+ "add home_server - Add home server configuration commands", -+ NULL, command_table_add_home_server }, -+ - { NULL, 0, NULL, NULL, NULL } - }; - #endif -@@ -2636,7 +2790,7 @@ static fr_command_table_t command_table_add[] = { - #ifdef WITH_PROXY - static fr_command_table_t command_table_set_home[] = { - { "state", FR_WRITE, -- "set home_server state [udp|tcp] [src ] [alive|dead] - set state for given home server", -+ "set home_server state [udp|tcp] [src ] [alive|dead|down] - set state for given home server", - command_set_home_server_state, NULL }, - - { NULL, 0, NULL, NULL, NULL } -diff --git a/src/main/conffile.c b/src/main/conffile.c -index a8c667bfb5..d458792cd5 100644 ---- a/src/main/conffile.c -+++ b/src/main/conffile.c -@@ -353,6 +353,7 @@ error: - - file->filename = filename; - file->cs = cs; -+ file->from_dir = from_dir; - - if (fstat(fd, &file->buf) == 0) { - #ifdef S_IWOTH -@@ -1174,10 +1175,14 @@ static char const *cf_expand_variables(char const *cf, int *lineno, - return NULL; - } - -+ /* -+ * Might as well make -+ * non-existent string be the -+ * empty string. -+ */ - if (!cp->value) { -- ERROR("%s[%d]: Reference \"%s\" has no value", -- cf, *lineno, input); -- return NULL; -+ *p = '\0'; -+ goto skip_value; - } - - if (p + strlen(cp->value) >= output + outsize) { -@@ -1188,6 +1193,7 @@ static char const *cf_expand_variables(char const *cf, int *lineno, - - strcpy(p, cp->value); - p += strlen(p); -+ skip_value: - ptr = end + 1; - - } else if (ci->type == CONF_ITEM_SECTION) { -@@ -1418,6 +1424,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d - { - int rcode; - bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists; -+ bool ignore_dflt; - char **q; - char const *value; - CONF_PAIR *cp = NULL; -@@ -1441,6 +1448,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d - cant_be_empty = (type & PW_TYPE_NOT_EMPTY); - tmpl = (type & PW_TYPE_TMPL); - multi = (type & PW_TYPE_MULTI); -+ ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT); - - if (attribute) required = true; - if (required) cant_be_empty = true; /* May want to review this in the future... */ -@@ -1464,7 +1472,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d - * section, use the default value. - */ - if (!cp) { -- if (deprecated) return 0; /* Don't set the default value */ -+ if (deprecated || ignore_dflt) return 0; /* Don't set the default value */ - - rcode = 1; - value = dflt; -@@ -1798,6 +1806,7 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, - CONF_PARSER const *variables) - { - int i; -+ void *data; - - for (i = 0; variables[i].name != NULL; i++) { - if (variables[i].type == PW_TYPE_SUBSECTION) { -@@ -1822,9 +1831,13 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, - subcs->item.lineno = cs->item.lineno; - cf_item_add(cs, &(subcs->item)); - } -+ if (base) { -+ data = ((uint8_t *)base) + variables[i].offset; -+ } else { -+ data = NULL; -+ } - -- cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset, -- (CONF_PARSER const *) variables[i].dflt); -+ cf_section_parse_init(subcs, data, (CONF_PARSER const *) variables[i].dflt); - continue; - } - -@@ -1922,8 +1935,13 @@ int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) - goto finish; - } - -- ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset, -- (CONF_PARSER const *) variables[i].dflt); -+ if (base) { -+ data = ((uint8_t *)base) + variables[i].offset; -+ } else { -+ data = NULL; -+ } -+ -+ ret = cf_section_parse(subcs, data, (CONF_PARSER const *) variables[i].dflt); - if (ret < 0) goto finish; - continue; - } /* else it's a CONF_PAIR */ -@@ -2313,7 +2331,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - - if (has_spaces) { - ptr = cbuf; -- while (isspace((int) *ptr)) ptr++; -+ while (isspace((uint8_t) *ptr)) ptr++; - - if (ptr > cbuf) { - memmove(cbuf, ptr, len - (ptr - cbuf)); -@@ -2329,7 +2347,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - if (at_eof) break; - - ptr = buf; -- while (*ptr && isspace((int) *ptr)) ptr++; -+ while (*ptr && isspace((uint8_t) *ptr)) ptr++; - - if (!*ptr || (*ptr == '#')) continue; - -@@ -2511,6 +2529,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - */ - while ((dp = readdir(dir)) != NULL) { - char const *p; -+ int slen; - - if (dp->d_name[0] == '.') continue; - -@@ -2518,8 +2537,8 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - * Check for valid characters - */ - for (p = dp->d_name; *p != '\0'; p++) { -- if (isalpha((int)*p) || -- isdigit((int)*p) || -+ if (isalpha((uint8_t)*p) || -+ isdigit((uint8_t)*p) || - (*p == '-') || - (*p == '_') || - (*p == '.')) continue; -@@ -2527,8 +2546,12 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - } - if (*p != '\0') continue; - -- snprintf(buf2, sizeof(buf2), "%s%s", -- value, dp->d_name); -+ slen = snprintf(buf2, sizeof(buf2), "%s%s", -+ value, dp->d_name); -+ if (slen >= (int) sizeof(buf2) || slen < 0) { -+ ERROR("%s: Full file path is too long.", dp->d_name); -+ return -1; -+ } - if ((stat(buf2, &stat_buf) != 0) || - S_ISDIR(stat_buf.st_mode)) continue; - -@@ -2794,7 +2817,15 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - - case T_OP_EQ: - case T_OP_SET: -- while (isspace((int) *ptr)) ptr++; -+ case T_OP_PREPEND: -+ while (isspace((uint8_t) *ptr)) ptr++; -+ -+ /* -+ * Be a little more forgiving. -+ */ -+ if (*ptr == '#') { -+ t3 = T_HASH; -+ } else - - /* - * New parser: non-quoted strings are -@@ -2809,7 +2840,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - - t3 = T_BARE_WORD; - while (*q && (*q >= ' ') && (*q != ',') && -- !isspace(*q)) q++; -+ !isspace((uint8_t) *q)) q++; - - if ((size_t) (q - ptr) >= sizeof(buf3)) { - ERROR("%s[%d]: Parse error: value too long", -@@ -2891,7 +2922,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - /* - * Require a comma, unless there's a comment. - */ -- while (isspace(*ptr)) ptr++; -+ while (isspace((uint8_t) *ptr)) ptr++; - - if (*ptr == ',') { - ptr++; -@@ -2964,7 +2995,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, - /* - * Done parsing one thing. Skip to EOL if possible. - */ -- while (isspace(*ptr)) ptr++; -+ while (isspace((uint8_t) *ptr)) ptr++; - - if (*ptr == '#') continue; - -@@ -3561,6 +3592,10 @@ bool cf_item_is_pair(CONF_ITEM const *item) - return item->type == CONF_ITEM_PAIR; - } - -+bool cf_item_is_data(CONF_ITEM const *item) -+{ -+ return item->type == CONF_ITEM_DATA; -+} - - static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name, - void *data, void (*data_free)(void *)) -diff --git a/src/main/listen.c b/src/main/listen.c -index ebf7f5221c..6cda245ef0 100644 ---- a/src/main/listen.c -+++ b/src/main/listen.c -@@ -52,6 +52,17 @@ RCSID("$Id$") - #include - #endif - -+#ifdef WITH_TLS -+#include -+ -+# if defined(__APPLE__) || defined(__FreeBSD__) -+# if !defined(SOL_TCP) && defined(IPPROTO_TCP) -+# define SOL_TCP IPPROTO_TCP -+# endif -+# endif -+ -+#endif -+ - #ifdef DEBUG_PRINT_PACKET - static void print_packet(RADIUS_PACKET *packet) - { -@@ -217,7 +228,7 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, - client_delete(clients, client); - - /* -- * Add a timer to free the client in 120s -+ * Add a timer to free the client 20s after it's already timed out. - */ - el = radius_event_list_corral(EVENT_CORRAL_MAIN); - -@@ -349,7 +360,6 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, - - static int listen_bind(rad_listen_t *this); - -- - /* - * Process and reply to a server-status request. - * Like rad_authenticate and rad_accounting this should -@@ -360,6 +370,52 @@ int rad_status_server(REQUEST *request) - int rcode = RLM_MODULE_OK; - DICT_VALUE *dval; - -+#ifdef WITH_TLS -+ if (request->listener->tls) { -+ listen_socket_t *sock = request->listener->data; -+ -+ if (sock->state == LISTEN_TLS_CHECKING) { -+ int autz_type = PW_AUTZ_TYPE; -+ char const *name = "Autz-Type"; -+ -+ if (request->listener->type == RAD_LISTEN_ACCT) { -+ autz_type = PW_ACCT_TYPE; -+ name = "Acct-Type"; -+ } -+ -+ RDEBUG("(TLS) Checking connection to see if it is authorized."); -+ -+ dval = dict_valbyname(autz_type, 0, "New-TLS-Connection"); -+ if (dval) { -+ rcode = process_authorize(dval->value, request); -+ } else { -+ rcode = RLM_MODULE_OK; -+ RWDEBUG("(TLS) Did not find '%s New-TLS-Connection' - defaulting to accept", name); -+ } -+ -+ if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { -+ RDEBUG("(TLS) Connection is authorized"); -+ request->reply->code = PW_CODE_ACCESS_ACCEPT; -+ } else { -+ RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); -+ request->reply->code = PW_CODE_ACCESS_REJECT; -+ } -+ -+ return 0; -+ } -+ } -+#endif -+ -+#ifdef WITH_STATS -+ /* -+ * Full statistics are available only on a statistics -+ * socket. -+ */ -+ if (request->listener->type == RAD_LISTEN_NONE) { -+ request_stats_reply(request); -+ } -+#endif -+ - switch (request->listener->type) { - #ifdef WITH_STATS - case RAD_LISTEN_NONE: -@@ -443,17 +499,124 @@ int rad_status_server(REQUEST *request) - return 0; - } - --#ifdef WITH_STATS -+ return 0; -+} -+ -+static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client) -+{ -+ if (client->require_ma == FR_BOOL_TRUE) return; -+ -+ if (client->require_ma == FR_BOOL_AUTO) { -+ if (!packet->message_authenticator) { -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("BlastRADIUS check: Received packet without Message-Authenticator."); -+ ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ client->require_ma = FR_BOOL_FALSE; -+ -+ /* -+ * And fall through to the -+ * limit_proxy_state checks, which might -+ * complain again. Oh well, maybe that -+ * will make people read the messages. -+ */ -+ -+ } else if (packet->eap_message) { -+ /* -+ * Don't set it to "true" for packets -+ * with EAP-Message. It's already -+ * required there, and we might get a -+ * non-EAP packet with (or without) -+ * Message-Authenticator -+ */ -+ return; -+ } else { -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("BlastRADIUS check: Received packet with Message-Authenticator."); -+ ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack."); -+ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ client->require_ma = FR_BOOL_TRUE; -+ return; -+ } -+ -+ } -+ - /* -- * Full statistics are available only on a statistics -- * socket. -+ * If all of the checks are turned off, then complain for every packet we receive. - */ -- if (request->listener->type == RAD_LISTEN_NONE) { -- request_stats_reply(request); -+ if (client->limit_proxy_state == FR_BOOL_FALSE) { -+ /* -+ * We have a Message-Authenticator, and it's valid. We don't need to compain. -+ */ -+ if (packet->message_authenticator) return; -+ -+ if (!fr_debug_lvl) return; /* easier than checking for each line below */ -+ -+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ DEBUG("BlastRADIUS check: Received packet without Message-Authenticator."); -+ DEBUG("YOU MUST SET \"require_message_authenticator = true\", or"); -+ DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname); -+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ DEBUG("The packet does not contain Message-Authenticator, which is a security issue"); -+ DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); -+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ return; - } --#endif - -- return 0; -+ /* -+ * Don't complain here. rad_packet_ok() will instead -+ * complain about every packet with Proxy-State but which -+ * is missing Message-Authenticator. -+ */ -+ if (client->limit_proxy_state == FR_BOOL_TRUE) { -+ return; -+ } -+ -+ if (packet->proxy_state && !packet->message_authenticator) { -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator."); -+ ERROR("This is either a BlastRADIUS attack, OR"); -+ ERROR("the client is a proxy RADIUS server which has not been upgraded."); -+ ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ client->limit_proxy_state = FR_BOOL_FALSE; -+ -+ } else { -+ client->limit_proxy_state = FR_BOOL_TRUE; -+ -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ if (!packet->proxy_state) { -+ ERROR("BlastRADIUS check: Received packet without Proxy-State."); -+ } else { -+ ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator."); -+ } -+ -+ ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname); -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ if (!packet->message_authenticator) { -+ ERROR("The packet does not contain Message-Authenticator, which is a security issue."); -+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); -+ } else { -+ ERROR("The packet contains Message-Authenticator."); -+ if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack."); -+ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); -+ } -+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ } - } - - #ifdef WITH_TCP -@@ -532,13 +695,37 @@ static int dual_tcp_recv(rad_listen_t *listener) - switch (packet->code) { - case PW_CODE_ACCESS_REQUEST: - if (listener->type != RAD_LISTEN_AUTH) goto bad_packet; -+ -+ /* -+ * Enforce BlastRADIUS checks on TCP, too. -+ */ -+ if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) { -+ FR_STATS_INC(auth, total_malformed_requests); -+ rad_free(&sock->packet); -+ return 0; -+ } -+ -+ /* -+ * Perform BlastRADIUS checks and warnings. -+ */ -+ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); -+ - FR_STATS_INC(auth, total_requests); - fun = rad_authenticate; - break; - - #ifdef WITH_ACCOUNTING - case PW_CODE_ACCOUNTING_REQUEST: -- if (listener->type != RAD_LISTEN_ACCT) goto bad_packet; -+ if (listener->type != RAD_LISTEN_ACCT) { -+ /* -+ * Allow auth + dual. Disallow -+ * everything else. -+ */ -+ if (!((listener->type == RAD_LISTEN_AUTH) && -+ (listener->dual))) { -+ goto bad_packet; -+ } -+ } - FR_STATS_INC(acct, total_requests); - fun = rad_accounting; - break; -@@ -574,6 +761,79 @@ static int dual_tcp_recv(rad_listen_t *listener) - return 1; - } - -+#ifdef WITH_TLS -+typedef struct { -+ char const *name; -+ SSL_CTX *ctx; -+} fr_realm_ctx_t; /* hack from tls. */ -+ -+static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) -+{ -+ fr_tls_server_conf_t *conf = arg; -+ char const *name, *p; -+ int type; -+ fr_realm_ctx_t my_r, *r; -+ REQUEST *request; -+ char buffer[PATH_MAX]; -+ -+ /* -+ * No SNI, that's fine. -+ */ -+ type = SSL_get_servername_type(ssl); -+ if (type < 0) return SSL_TLSEXT_ERR_OK; -+ -+ /* -+ * No realms configured, just use the default context. -+ */ -+ if (!conf->realms) return SSL_TLSEXT_ERR_OK; -+ -+ name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); -+ if (!name) return SSL_TLSEXT_ERR_OK; -+ -+ /* -+ * RFC Section 6066 Section 3 says that the names are -+ * ASCII, without a trailing dot. i.e. punycode. -+ */ -+ for (p = name; *p != '\0'; p++) { -+ if (*p == '-') continue; -+ if (*p == '.') continue; -+ if ((*p >= 'A') && (*p <= 'Z')) continue; -+ if ((*p >= 'a') && (*p <= 'z')) continue; -+ if ((*p >= '0') && (*p <= '9')) continue; -+ -+ /* -+ * Anything else, fail. -+ */ -+ return SSL_TLSEXT_ERR_ALERT_FATAL; -+ } -+ -+ /* -+ * Too long, fail. -+ */ -+ if ((p - name) > 255) return SSL_TLSEXT_ERR_ALERT_FATAL; -+ -+ snprintf(buffer, sizeof(buffer), "%s/%s.pem", conf->realm_dir, name); -+ -+ my_r.name = buffer; -+ r = fr_hash_table_finddata(conf->realms, &my_r); -+ -+ /* -+ * If found, switch certs. Otherwise use the default -+ * one. -+ */ -+ if (r) (void) SSL_set_SSL_CTX(ssl, r->ctx); -+ -+ /* -+ * Set an attribute saying which server has been selected. -+ */ -+ request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); -+ if (request) { -+ (void) pair_make_config("TLS-Server-Name-Indication", name, T_OP_SET); -+ } -+ -+ return SSL_TLSEXT_ERR_OK; -+} -+#endif - - static int dual_tcp_accept(rad_listen_t *listener) - { -@@ -731,6 +991,14 @@ static int dual_tcp_accept(rad_listen_t *listener) - if (this->tls) { - this->recv = dual_tls_recv; - this->send = dual_tls_send; -+ -+ /* -+ * Set up SNI callback. We don't do it -+ * in the main TLS code, because EAP -+ * doesn't need or use SNI. -+ */ -+ SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); -+ SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); - } - #endif - } -@@ -932,7 +1200,6 @@ static CONF_PARSER limit_config[] = { - CONF_PARSER_TERMINATOR - }; - -- - #ifdef WITH_TCP - /* - * TLS requires child threads to handle the listeners. Which -@@ -1018,6 +1285,7 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) - - } else if (strcmp(proto, "tcp") == 0) { - sock->proto = IPPROTO_TCP; -+ - } else { - cf_log_err_cs(cs, - "Unknown proto name \"%s\"", proto); -@@ -1050,6 +1318,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) - return -1; - } - -+ /* -+ * Allow non-blocking for TLS sockets -+ */ -+ rcode = cf_item_parse(cs, "nonblock", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->nonblock), NULL); -+ if (rcode < 0) return -1; -+ - /* - * If unset, set to default. - */ -@@ -1068,6 +1342,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) - } - #endif - -+ rcode = cf_item_parse(cs, "check_client_connections", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->check_client_connections), "no"); -+ if (rcode < 0) return -1; - } - #else /* WITH_TLS */ - /* -@@ -1312,6 +1588,11 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) - */ - this->recv = dual_tcp_accept; - -+ /* -+ * @todo - add a free function? Though this only -+ * matters when we're tearing down the server, so -+ * perhaps it's less relevant. -+ */ - this->children = rbtree_create(this, listener_cmp, NULL, 0); - if (!this->children) { - cf_log_err_cs(cs, "Failed to create child list for TCP socket."); -@@ -1324,12 +1605,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) - } - - /* -- * Send an authentication response packet -+ * Send a response packet - */ --static int auth_socket_send(rad_listen_t *listener, REQUEST *request) -+static int common_socket_send(rad_listen_t *listener, REQUEST *request) - { - rad_assert(request->listener == listener); -- rad_assert(listener->send == auth_socket_send); -+ rad_assert(listener->send == common_socket_send); - - if (request->reply->code == 0) return 0; - -@@ -1355,46 +1636,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) - } - - --#ifdef WITH_ACCOUNTING --/* -- * Send an accounting response packet (or not) -- */ --static int acct_socket_send(rad_listen_t *listener, REQUEST *request) --{ -- rad_assert(request->listener == listener); -- rad_assert(listener->send == acct_socket_send); -- -- /* -- * Accounting reject's are silently dropped. -- * -- * We do it here to avoid polluting the rest of the -- * code with this knowledge -- */ -- if (request->reply->code == 0) return 0; -- --#ifdef WITH_UDPFROMTO -- /* -- * Overwrite the src ip address on the outbound packet -- * with the one specified by the client. -- * This is useful to work around broken DSR implementations -- * and other routing issues. -- */ -- if (request->client->src_ipaddr.af != AF_UNSPEC) { -- request->reply->src_ipaddr = request->client->src_ipaddr; -- } --#endif -- -- if (rad_send(request->reply, request->packet, -- request->client->secret) < 0) { -- RERROR("Failed sending reply: %s", -- fr_strerror()); -- return -1; -- } -- -- return 0; --} --#endif -- - #ifdef WITH_PROXY - /* - * Send a packet to a home server. -@@ -1404,7 +1645,7 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) - static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) - { - rad_assert(request->proxy_listener == listener); -- rad_assert(listener->send == proxy_socket_send); -+ rad_assert(listener->proxy_send == proxy_socket_send); - - if (rad_send(request->proxy, NULL, - request->home_server->secret) < 0) { -@@ -1436,8 +1677,6 @@ static int stats_socket_recv(rad_listen_t *listener) - rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); - if (rcode < 0) return 0; - -- FR_STATS_INC(auth, total_requests); -- - if (rcode < 20) { /* RADIUS_HDR_LEN */ - if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); - FR_STATS_INC(auth, total_malformed_requests); -@@ -1485,7 +1724,6 @@ static int stats_socket_recv(rad_listen_t *listener) - } - #endif - -- - /* - * Check if an incoming request is "ok" - * -@@ -1521,13 +1759,12 @@ static int auth_socket_recv(rad_listen_t *listener) - return 0; - } - -- FR_STATS_TYPE_INC(client->auth.total_requests); -- - /* - * Some sanity checks, based on the packet code. - */ - switch (code) { - case PW_CODE_ACCESS_REQUEST: -+ FR_STATS_TYPE_INC(client->auth.total_requests); - fun = rad_authenticate; - break; - -@@ -1562,7 +1799,7 @@ static int auth_socket_recv(rad_listen_t *listener) - * Now that we've sanity checked everything, receive the - * packet. - */ -- packet = rad_recv(ctx, listener->fd, client->message_authenticator); -+ packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2)); - if (!packet) { - FR_STATS_INC(auth, total_malformed_requests); - if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); -@@ -1570,6 +1807,11 @@ static int auth_socket_recv(rad_listen_t *listener) - return 0; - } - -+ /* -+ * Perform BlastRADIUS checks and warnings. -+ */ -+ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); -+ - #ifdef __APPLE__ - #ifdef WITH_UDPFROMTO - /* -@@ -1637,13 +1879,12 @@ static int acct_socket_recv(rad_listen_t *listener) - return 0; - } - -- FR_STATS_TYPE_INC(client->acct.total_requests); -- - /* - * Some sanity checks, based on the packet code. - */ - switch (code) { - case PW_CODE_ACCOUNTING_REQUEST: -+ FR_STATS_TYPE_INC(client->acct.total_requests); - fun = rad_accounting; - break; - -@@ -1729,7 +1970,6 @@ static int do_proxy(REQUEST *request) - */ - vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY); - if (!vp) vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IPV6_ADDRESS, 0, TAG_ANY); -- - if (!vp) return 0; - - return 1; -@@ -1955,7 +2195,7 @@ static int coa_socket_recv(rad_listen_t *listener) - * Now that we've sanity checked everything, receive the - * packet. - */ -- packet = rad_recv(ctx, listener->fd, client->message_authenticator); -+ packet = rad_recv(ctx, listener->fd, client->require_ma); - if (!packet) { - FR_STATS_INC(coa, total_malformed_requests); - if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); -@@ -2049,19 +2289,67 @@ static int proxy_socket_recv(rad_listen_t *listener) - */ - static int proxy_socket_tcp_recv(rad_listen_t *listener) - { -+ int rcode; - RADIUS_PACKET *packet; - listen_socket_t *sock = listener->data; -- char buffer[128]; -+ char buffer[256]; - - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; - -- packet = fr_tcp_recv(listener->fd, 0); -- if (!packet) { -+ if (!sock->packet) { -+ sock->packet = rad_alloc(sock, false); -+ if (!sock->packet) return 0; -+ -+ sock->packet->sockfd = listener->fd; -+ sock->packet->src_ipaddr = sock->other_ipaddr; -+ sock->packet->src_port = sock->other_port; -+ sock->packet->dst_ipaddr = sock->my_ipaddr; -+ sock->packet->dst_port = sock->my_port; -+ sock->packet->proto = sock->proto; -+ } -+ -+ packet = sock->packet; -+ -+ rcode = fr_tcp_read_packet(packet, 0); -+ -+ /* -+ * Still only a partial packet. Put it back, and return, -+ * so that we'll read more data when it's ready. -+ */ -+ if (rcode == 0) { -+ return 0; -+ } -+ -+ if (rcode == -1) { /* error reading packet */ -+ ERROR("Invalid packet from %s port %d, closing socket: %s", -+ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), -+ packet->src_port, fr_strerror()); -+ } -+ -+ if (rcode < 0) { /* error or connection reset */ - listener->status = RAD_LISTEN_STATUS_EOL; -+ -+ /* -+ * Tell the event handler that an FD has disappeared. -+ */ -+ DEBUG("Home server %s port %d has closed connection", -+ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), -+ packet->src_port); -+ - radius_update_listener(listener); -+ -+ /* -+ * Do NOT free the listener here. It's in use by -+ * a request, and will need to hang around until -+ * all of the requests are done. -+ * -+ * It is instead free'd in remove_from_request_hash() -+ */ - return 0; - } - -+ sock->packet = NULL; /* we have no need for more partial reads */ -+ - /* - * FIXME: Client MIB updates? - */ -@@ -2089,10 +2377,6 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) - return 0; - } - -- packet->src_ipaddr = sock->other_ipaddr; -- packet->src_port = sock->other_port; -- packet->dst_ipaddr = sock->my_ipaddr; -- packet->dst_port = sock->my_port; - - /* - * FIXME: Have it return an indication of packets that -@@ -2113,11 +2397,26 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) - #endif - #endif - -+#ifdef WITH_TLS -+#define TLS_UNUSED -+#else -+#define TLS_UNUSED UNUSED -+#endif - --static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) -+static int client_socket_encode(TLS_UNUSED rad_listen_t *listener, REQUEST *request) - { -+#ifdef WITH_TLS -+ /* -+ * Don't encode fake packets. -+ */ -+ listen_socket_t *sock = listener->data; -+ if (sock->state == LISTEN_TLS_CHECKING) return 0; -+#endif -+ - if (!request->reply->code) return 0; - -+ if (request->reply->data) return 0; /* already encoded */ -+ - if (rad_encode(request->reply, request->packet, request->client->secret) < 0) { - RERROR("Failed encoding packet: %s", fr_strerror()); - -@@ -2142,7 +2441,7 @@ static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) - static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) - { - #ifdef WITH_TLS -- listen_socket_t *sock; -+ listen_socket_t *sock = request->listener->data; - #endif - - if (rad_verify(request->packet, NULL, -@@ -2151,9 +2450,6 @@ static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) - } - - #ifdef WITH_TLS -- sock = request->listener->data; -- rad_assert(sock != NULL); -- - /* - * FIXME: Add the rest of the TLS parameters, too? But - * how do we separate EAP-TLS parameters from RADIUS/TLS -@@ -2218,7 +2514,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { - #ifdef WITH_STATS - { RLM_MODULE_INIT, "status", sizeof(listen_socket_t), NULL, - common_socket_parse, NULL, -- stats_socket_recv, auth_socket_send, -+ stats_socket_recv, common_socket_send, - common_socket_print, client_socket_encode, client_socket_decode }, - #else - /* -@@ -2241,14 +2537,14 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { - /* authentication */ - { RLM_MODULE_INIT, "auth", sizeof(listen_socket_t), NULL, - common_socket_parse, common_socket_free, -- auth_socket_recv, auth_socket_send, -+ auth_socket_recv, common_socket_send, - common_socket_print, client_socket_encode, client_socket_decode }, - - #ifdef WITH_ACCOUNTING - /* accounting */ - { RLM_MODULE_INIT, "acct", sizeof(listen_socket_t), NULL, - common_socket_parse, common_socket_free, -- acct_socket_recv, acct_socket_send, -+ acct_socket_recv, common_socket_send, - common_socket_print, client_socket_encode, client_socket_decode}, - #else - { 0, "acct", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, -@@ -2282,7 +2578,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { - /* Change of Authorization */ - { RLM_MODULE_INIT, "coa", sizeof(listen_socket_t), NULL, - common_socket_parse, NULL, -- coa_socket_recv, auth_socket_send, /* CoA packets are same as auth */ -+ coa_socket_recv, common_socket_send, - common_socket_print, client_socket_encode, client_socket_decode }, - #else - { 0, "coa", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, -@@ -2755,6 +3051,9 @@ static int _listener_free(rad_listen_t *this) - - rad_assert(!sock->ssn || (talloc_parent(sock->ssn) == sock)); - rad_assert(!sock->request || (talloc_parent(sock->request) == sock)); -+ -+ if (sock->home && sock->home->listeners) (void) rbtree_deletebydata(sock->home->listeners, this); -+ - #ifdef HAVE_PTHREAD_H - pthread_mutex_destroy(&(sock->mutex)); - #endif -@@ -2780,8 +3079,16 @@ static rad_listen_t *listen_alloc(TALLOC_CTX *ctx, RAD_LISTEN_TYPE type) - this->recv = master_listen[this->type].recv; - this->send = master_listen[this->type].send; - this->print = master_listen[this->type].print; -- this->encode = master_listen[this->type].encode; -- this->decode = master_listen[this->type].decode; -+ -+ if (type != RAD_LISTEN_PROXY) { -+ this->encode = master_listen[this->type].encode; -+ this->decode = master_listen[this->type].decode; -+ } else { -+ this->send = NULL; /* proxy packets shouldn't call this! */ -+ this->proxy_send = master_listen[this->type].send; -+ this->proxy_encode = master_listen[this->type].encode; -+ this->proxy_decode = master_listen[this->type].decode; -+ } - - talloc_set_destructor(this, _listener_free); - -@@ -2806,7 +3113,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t - - if (!home) return NULL; - -- rad_assert(home->server == NULL); /* we only open real sockets */ -+ rad_assert(home->virtual_server == NULL); /* we only open real sockets */ - - if ((home->limit.max_connections > 0) && - (home->limit.num_connections >= home->limit.max_connections)) { -@@ -2847,11 +3154,20 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t - * FIXME: connect() is blocking! - * We do this with the proxy mutex locked, which may - * cause large delays! -- * -- * http://www.developerweb.net/forum/showthread.php?p=13486 - */ - this->fd = fr_socket_client_tcp(&home->src_ipaddr, -- &home->ipaddr, home->port, false); -+ &home->ipaddr, home->port, -+#ifdef WITH_TLS -+ !this->nonblock -+#else -+ false -+#endif -+ ); -+ -+ /* -+ * Set max_requests, lifetime, and idle_timeout from the home server. -+ */ -+ sock->limit = home->limit; - } else - #endif - this->fd = fr_socket(&home->src_ipaddr, src_port); -@@ -2869,17 +3185,74 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t - #ifdef WITH_TCP - #ifdef WITH_TLS - if ((home->proto == IPPROTO_TCP) && home->tls) { -- DEBUG("Trying SSL to port %d\n", home->port); -+ DEBUG("(TLS) Trying new outgoing proxy connection to %s", buffer); -+ -+ /* -+ * Set SNI, if configured. -+ * -+ * The OpenSSL API says the filename is "char -+ * const *", but some versions have it as "void -+ * *", without the "const". So we un-const it -+ * here through various C magic. -+ */ -+ if (home->tls->client_hostname) { -+ (void) SSL_set_tlsext_host_name(sock->ssn->ssl, (void *) (uintptr_t) home->tls->client_hostname); -+ } -+ -+ this->nonblock |= home->nonblock; -+ -+ /* -+ * Set non-blocking if it's configured. -+ */ -+ if (this->nonblock) { -+ if (fr_nonblock(this->fd) < 0) { -+ ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); -+ goto error; -+ } -+ -+ rad_assert(home->listeners != NULL); -+ -+ if (!rbtree_insert(home->listeners, this)) { -+ ERROR("(TLS) Failed adding tracking informtion for proxy socket '%s'", buffer); -+ goto error; -+ } -+ -+#ifdef TCP_NODELAY -+ /* -+ * Also set TCP_NODELAY, to force the data to be written quickly. -+ */ -+ if (sock->proto == IPPROTO_TCP) { -+ int on = 1; -+ -+ if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { -+ ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); -+ goto error; -+ } -+ } -+#endif -+ } -+ -+ /* -+ * This is blocking. :( -+ */ - sock->ssn = tls_new_client_session(sock, home->tls, this->fd, &sock->certs); - if (!sock->ssn) { -- ERROR("Failed starting SSL to new proxy socket '%s'", buffer); -- home->last_failed_open = now; -- listen_free(&this); -- return NULL; -+ ERROR("(TLS) Failed opening connection on proxy socket '%s'", buffer); -+ goto error; - } - -+ sock->connect_timeout = home->connect_timeout; -+ - this->recv = proxy_tls_recv; -- this->send = proxy_tls_send; -+ this->proxy_send = proxy_tls_send; -+ -+#ifdef HAVE_PTHREAD_H -+ if (pthread_mutex_init(&sock->mutex, NULL) < 0) { -+ rad_assert(0 == 1); -+ listen_free(&this); -+ return 0; -+ } -+#endif - } - #endif - #endif -@@ -2895,6 +3268,8 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t - &sizeof_src) < 0) { - ERROR("Failed getting socket name for '%s': %s", - buffer, fr_syserror(errno)); -+ error: -+ close(this->fd); - home->last_failed_open = now; - listen_free(&this); - return NULL; -@@ -2903,9 +3278,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t - if (!fr_sockaddr2ipaddr(&src, sizeof_src, - &sock->my_ipaddr, &sock->my_port)) { - ERROR("Socket has unsupported address family for '%s'", buffer); -- home->last_failed_open = now; -- listen_free(&this); -- return NULL; -+ goto error; - } - - this->print(this, buffer, sizeof(buffer)); -@@ -2966,6 +3339,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) - char const *value; - fr_dlhandle handle; - CONF_SECTION *server_cs; -+#ifdef WITH_TCP -+ char const *p; -+#endif - char buffer[32]; - - cp = cf_pair_find(cs, "type"); -@@ -3076,10 +3452,13 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) - - #ifdef WITH_TCP - /* -- * Special-case '+' for "auth+acct". -+ * Add special flags '+' for "auth+acct". - */ -- if (strchr(listen_type, '+') != NULL) { -- this->dual = true; -+ p = strchr(listen_type, '+'); -+ if (p) { -+ if (strncmp(p + 1, "acct", 4) == 0) { -+ this->dual = true; -+ } - } - #endif - -@@ -3115,7 +3494,6 @@ static void *recv_thread(void *arg) - - while (1) { - this->recv(this); -- DEBUG("%p", &this); - } - - return NULL; -@@ -3207,7 +3585,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) - if (override) { - cs = cf_section_sub_find_name2(config, "server", - main_config.name); -- if (cs) this->server = main_config.name; -+ if (!cs) cs = cf_section_sub_find_name2(config, "server", -+ "default"); -+ if (cs) this->server = cf_section_name2(cs); - } - - *last = this; -diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c -index e9dd412dee..957bff933f 100644 ---- a/src/main/mainconfig.c -+++ b/src/main/mainconfig.c -@@ -73,6 +73,8 @@ static char const *gid_name = NULL; - static char const *chroot_dir = NULL; - static bool allow_core_dumps = false; - static char const *radlog_dest = NULL; -+static char const *require_message_authenticator = NULL; -+static char const *limit_proxy_state = NULL; - - /* - * These are not used anywhere else.. -@@ -87,6 +89,52 @@ static bool do_colourise = false; - - static char const *radius_dir = NULL; //!< Path to raddb directory - -+static const FR_NAME_NUMBER fr_bool_auto_names[] = { -+ { "false", FR_BOOL_FALSE }, -+ { "no", FR_BOOL_FALSE }, -+ { "0", FR_BOOL_FALSE }, -+ -+ { "true", FR_BOOL_TRUE }, -+ { "yes", FR_BOOL_TRUE }, -+ { "1", FR_BOOL_TRUE }, -+ -+ { "auto", FR_BOOL_AUTO }, -+ -+ { NULL, 0 } -+}; -+ -+/* -+ * Get decent values for false / true / auto -+ */ -+int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str) -+{ -+ int value; -+ -+ /* -+ * Don't change anything. -+ */ -+ if (!str) return 0; -+ -+ value = fr_str2int(fr_bool_auto_names, str, -1); -+ if (value >= 0) { -+ *out = value; -+ return 0; -+ } -+ -+ /* -+ * This should never happen, as the defaults are in the -+ * source code. If there's no CONF_PAIR, and there's a -+ * parse error, then the source code is wrong. -+ */ -+ if (!cp) { -+ fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name); -+ return -1; -+ } -+ -+ cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp)); -+ return -1; -+} -+ - /********************************************************************** - * - * We need to figure out where the logs go, before doing anything -@@ -148,6 +196,7 @@ static const CONF_PARSER log_config[] = { - { "colourise",FR_CONF_POINTER(PW_TYPE_BOOLEAN, &do_colourise), NULL }, - { "use_utc", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &log_dates_utc), NULL }, - { "msg_denied", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.denied_msg), "You are already logged in - access denied" }, -+ { "suppress_secrets", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.suppress_secrets), NULL }, - CONF_PARSER_TERMINATOR - }; - -@@ -159,6 +208,8 @@ static const CONF_PARSER security_config[] = { - { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) }, - { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) }, - { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"}, -+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"}, -+ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"}, - #ifdef ENABLE_OPENSSL_VERSION_CHECK - { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"}, - #endif -@@ -196,6 +247,7 @@ static const CONF_PARSER server_config[] = { - { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, - { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, - { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, -+ { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, - { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, - { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, - -@@ -499,14 +551,31 @@ static ssize_t xlat_listen_common(REQUEST *request, rad_listen_t *listen, - VALUE_PAIR *vp; - listen_socket_t *sock = listen->data; - -+ if (!listen->tls) { -+ RDEBUG("Listener is not using TLS. TLS attributes are not available"); -+ *out = '\0'; -+ return 0; -+ } -+ - for (vp = sock->certs; vp != NULL; vp = vp->next) { - if (strcmp(fmt, vp->da->name) == 0) { - return vp_prints_value(out, outlen, vp, 0); - } - } -+ -+ RDEBUG("Unknown TLS attribute \"%s\"", fmt); -+ *out = '\0'; -+ return 0; -+ } -+#else -+ if (strncmp(fmt, "TLS-", 4) == 0) { -+ RDEBUG("Server is not built with TLS support"); -+ *out = '\0'; -+ return 0; - } - #endif - -+ - cp = cf_pair_find(listen->cs, fmt); - if (!cp || !(value = cf_pair_value(cp))) { - RDEBUG("Listener does not contain config item \"%s\"", fmt); -@@ -560,7 +629,6 @@ static int switch_users(CONF_SECTION *cs) - * initialized. - */ - if (fr_set_dumpable_init() < 0) { -- fr_perror("%s", main_config.name); - return 0; - } - -@@ -571,8 +639,7 @@ static int switch_users(CONF_SECTION *cs) - if (rad_debug_lvl && (getuid() != 0)) return 1; - - if (cf_section_parse(cs, NULL, bootstrap_config) < 0) { -- fr_strerror_printf("%s: Error: Failed to parse user/group information.\n", -- main_config.name); -+ fr_strerror_printf("Failed to parse user/group information."); - return 0; - } - -@@ -587,8 +654,8 @@ static int switch_users(CONF_SECTION *cs) - - gr = getgrnam(gid_name); - if (!gr) { -- fr_strerror_printf("%s: Cannot get ID for group %s: %s\n", -- main_config.name, gid_name, fr_syserror(errno)); -+ fr_strerror_printf("Cannot get ID for group %s: %s", -+ gid_name, fr_syserror(errno)); - return 0; - } - -@@ -608,8 +675,8 @@ static int switch_users(CONF_SECTION *cs) - struct passwd *user; - - if (rad_getpwnam(cs, &user, uid_name) < 0) { -- fr_strerror_printf("%s: Cannot get passwd entry for user %s: %s\n", -- main_config.name, uid_name, fr_strerror()); -+ fr_strerror_printf("Cannot get passwd entry for user %s: %s", -+ uid_name, fr_strerror()); - return 0; - } - -@@ -621,8 +688,8 @@ static int switch_users(CONF_SECTION *cs) - do_suid = true; - #ifdef HAVE_INITGROUPS - if (initgroups(uid_name, server_gid) < 0) { -- fr_strerror_printf("%s: Cannot initialize supplementary group list for user %s: %s\n", -- main_config.name, uid_name, fr_syserror(errno)); -+ fr_strerror_printf("Cannot initialize supplementary group list for user %s: %s", -+ uid_name, fr_syserror(errno)); - talloc_free(user); - return 0; - } -@@ -637,8 +704,8 @@ static int switch_users(CONF_SECTION *cs) - */ - if (chroot_dir) { - if (chroot(chroot_dir) < 0) { -- fr_strerror_printf("%s: Failed to perform chroot %s: %s", -- main_config.name, chroot_dir, fr_syserror(errno)); -+ fr_strerror_printf("Failed to perform chroot to %s: %s", -+ chroot_dir, fr_syserror(errno)); - return 0; - } - -@@ -665,8 +732,8 @@ static int switch_users(CONF_SECTION *cs) - */ - if (do_sgid) { - if (setgid(server_gid) < 0){ -- fr_strerror_printf("%s: Failed setting group to %s: %s", -- main_config.name, gid_name, fr_syserror(errno)); -+ fr_strerror_printf("Failed setting group to %s: %s", -+ gid_name, fr_syserror(errno)); - return 0; - } - } -@@ -713,8 +780,8 @@ static int switch_users(CONF_SECTION *cs) - default_log.fd = open(main_config.log_file, - O_WRONLY | O_APPEND | O_CREAT, 0640); - if (default_log.fd < 0) { -- fr_strerror_printf("%s: Failed to open log file %s: %s\n", -- main_config.name, main_config.log_file, fr_syserror(errno)); -+ fr_strerror_printf("Failed to open log file %s: %s\n", -+ main_config.log_file, fr_syserror(errno)); - return 0; - } - } -@@ -730,8 +797,8 @@ static int switch_users(CONF_SECTION *cs) - if ((do_suid || do_sgid) && - (default_log.dst == L_DST_FILES)) { - if (fchown(default_log.fd, server_uid, server_gid) < 0) { -- fr_strerror_printf("%s: Cannot change ownership of log file %s: %s\n", -- main_config.name, main_config.log_file, fr_syserror(errno)); -+ fr_strerror_printf("Cannot change ownership of log file %s: %s\n", -+ main_config.log_file, fr_syserror(errno)); - return 0; - } - } -@@ -750,7 +817,7 @@ static int switch_users(CONF_SECTION *cs) - * aren't allowed. - */ - if (fr_set_dumpable(allow_core_dumps) < 0) { -- ERROR("%s", fr_strerror()); -+ WARN("Failed to allow core dumps - %s", fr_strerror()); - } - - if (allow_core_dumps) { -@@ -838,6 +905,8 @@ int main_config_init(void) - if (!main_config.dictionary_dir) { - main_config.dictionary_dir = DICTDIR; - } -+ main_config.require_ma = FR_BOOL_AUTO; -+ main_config.limit_proxy_state = FR_BOOL_AUTO; - - /* - * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400 -@@ -941,6 +1010,9 @@ do {\ - for (ci = cf_item_find_next(subcs, NULL); - ci != NULL; - ci = cf_item_find_next(subcs, ci)) { -+ -+ if (cf_item_is_data(ci)) continue; -+ - if (!cf_item_is_pair(ci)) { - cf_log_err(ci, "Unexpected item in ENV section"); - goto failure; -@@ -1066,7 +1138,10 @@ do {\ - /* - * Switch users as early as possible. - */ -- if (!switch_users(cs)) fr_exit(1); -+ if (!switch_users(cs)) { -+ fprintf(stderr, "%s: ERROR - %s\n", main_config.name, fr_strerror()); -+ fr_exit(1); -+ } - #endif - - /* -@@ -1127,6 +1202,23 @@ do {\ - main_config.init_delay.tv_sec = 0; - main_config.init_delay.tv_usec = 2* (1000000 / 3); - -+ { -+ CONF_PAIR *cp = NULL; -+ -+ subcs = cf_section_sub_find(cs, "security"); -+ if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator"); -+ if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) { -+ cf_file_free(cs); -+ return -1; -+ } -+ -+ if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state"); -+ if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) { -+ cf_file_free(cs); -+ return -1; -+ } -+ } -+ - /* - * Free the old configuration items, and replace them - * with the new ones. -diff --git a/src/main/map.c b/src/main/map.c -index 6275ba124d..17988d27f9 100644 ---- a/src/main/map.c -+++ b/src/main/map.c -@@ -245,6 +245,18 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, - } - break; - -+ case T_BARE_WORD: -+ /* -+ * Foo = %{...} -+ * -+ * Not allowed! -+ */ -+ if ((attr[0] == '%') && (attr[1] == '{')) { -+ cf_log_err_cp(cp, "Bare expansions are not permitted. They must be in a double-quoted string."); -+ goto error; -+ } -+ /* FALL-THROUGH */ -+ - default: - slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true); - if (slen <= 0) { -@@ -285,14 +297,20 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, - goto error; - } - -- /* -- * We cannot assign a count to an attribute. That must -- * be done in an xlat. -- */ -- if ((map->rhs->type == TMPL_TYPE_ATTR) && -- (map->rhs->tmpl_num == NUM_COUNT)) { -- cf_log_err_cp(cp, "Cannot assign from a count"); -- goto error; -+ if (map->rhs->type == TMPL_TYPE_ATTR) { -+ /* -+ * We cannot assign a count to an attribute. That must -+ * be done in an xlat. -+ */ -+ if (map->rhs->tmpl_num == NUM_COUNT) { -+ cf_log_err_cp(cp, "Cannot assign from a count"); -+ goto error; -+ } -+ -+ if (map->rhs->tmpl_da->flags.virtual) { -+ cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name); -+ goto error; -+ } - } - - VERIFY_MAP(map); -@@ -1091,6 +1109,12 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t - */ - if (((map->lhs->tmpl_list == PAIR_LIST_COA) || - (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) { -+ if ((request->packet->code == PW_CODE_COA_REQUEST) || -+ (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) { -+ REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request. Use 'update request' instead."); -+ return -2; -+ } -+ - if (!request_alloc_coa(context)) { - REDEBUG("Failed to create a CoA/Disconnect Request message"); - return -2; -@@ -1173,10 +1197,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t - rad_assert(map->rhs->type == TMPL_TYPE_EXEC); - /* FALL-THROUGH */ - case T_OP_ADD: -- fr_pair_list_move(parent, list, &head); -+ fr_pair_list_move(parent, list, &head, map->op); - fr_pair_list_free(&head); - } - goto finish; -+ case T_OP_PREPEND: -+ fr_pair_list_move(parent, list, &head, T_OP_PREPEND); -+ fr_pair_list_free(&head); -+ goto finish; - - default: - fr_pair_list_free(&head); -@@ -1341,6 +1369,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t - fr_pair_list_free(&head); - break; - -+ /* -+ * ^= - Prepend src_list attributes to the destination -+ */ -+ case T_OP_PREPEND: -+ fr_pair_prepend(list, head); -+ head = NULL; -+ break; -+ - /* - * += - Add all src_list attributes to the destination - */ -diff --git a/src/main/process.c b/src/main/process.c -index 1a48517d43..2d20ebf552 100644 ---- a/src/main/process.c -+++ b/src/main/process.c -@@ -74,8 +74,14 @@ static char const *action_codes[] = { - "dup", - "timer", - #ifdef WITH_PROXY -- "proxy-reply" --#endif -+ "proxy-reply", -+#endif -+ "request was cancelled", -+ "conflicting packet was received", -+ "max_time was reached", -+ "internal failure", -+ "cleanup_delay was reached", -+ "CoA packet was cancelled, and not sent", - }; - - #ifdef DEBUG_STATE_MACHINE -@@ -604,6 +610,46 @@ static void request_free(REQUEST *request) - - - #ifdef WITH_PROXY -+#ifdef WITH_TLS -+void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler) -+{ -+ PTHREAD_MUTEX_LOCK(&proxy_mutex); -+ if (!fr_packet_list_socket_freeze(proxy_list, -+ listener->fd)) { -+ ERROR("Fatal error freezing socket: %s", fr_strerror()); -+ fr_exit(1); -+ } -+ -+ listener->blocked = true; -+ -+ if (fr_event_fd_write_handler(el, 0, listener->fd, write_handler, listener) < 0) { -+ ERROR("Fatal error freezing socket: %s", fr_strerror()); -+ fr_exit(1); -+ } -+ -+ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); -+} -+ -+void proxy_listener_thaw(rad_listen_t *listener) -+{ -+ PTHREAD_MUTEX_LOCK(&proxy_mutex); -+ if (!fr_packet_list_socket_thaw(proxy_list, -+ listener->fd)) { -+ ERROR("Fatal error freezing socket: %s", fr_strerror()); -+ fr_exit(1); -+ } -+ -+ listener->blocked = false; -+ -+ if (fr_event_fd_write_handler(el, 0, listener->fd, NULL, listener) < 0) { -+ ERROR("Fatal error freezing socket: %s", fr_strerror()); -+ fr_exit(1); -+ } -+ -+ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); -+} -+#endif /* WITH_TLS */ -+ - static void proxy_reply_too_late(REQUEST *request) - { - char buffer[128]; -@@ -632,9 +678,10 @@ static void proxy_reply_too_late(REQUEST *request) - * } - * \enddot - */ --static void request_done(REQUEST *request, int action) -+static void request_done(REQUEST *request, int original) - { - struct timeval now, when; -+ int action = original; - - VERIFY_REQUEST(request); - -@@ -690,7 +737,7 @@ static void request_done(REQUEST *request, int action) - home_server_t *home = request->home_pool->servers[i]; - - if (home->state == HOME_STATE_CONNECTION_FAIL) { -- mark_home_server_dead(home, &now); -+ mark_home_server_dead(home, &now, false); - } - } - } -@@ -700,7 +747,7 @@ static void request_done(REQUEST *request, int action) - /* - * If it was administratively canceled, then it's done. - */ -- if (action == FR_ACTION_CANCELLED) { -+ if (action >= FR_ACTION_CANCELLED) { - action = FR_ACTION_DONE; - - #ifdef WITH_COA -@@ -887,9 +934,10 @@ static void request_done(REQUEST *request, int action) - #endif - - if (request->packet) { -- RDEBUG2("Cleaning up request packet ID %u with timestamp +%d", -+ RDEBUG2("Cleaning up request packet ID %u with timestamp +%d due to %s", - request->packet->id, -- (unsigned int) (request->timestamp - fr_start_time)); -+ (unsigned int) (request->timestamp - fr_start_time), -+ action_codes[original]); - } /* else don't print anything */ - - ASSERT_MASTER; -@@ -958,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) - #ifdef HAVE_PTHREAD_H - rad_assert(request->child_pid == NO_SUCH_CHILD_PID); - #endif -+ -+ /* -+ * Set the statistics immediately if we can. -+ */ -+ request_stats_final(request); -+ - STATE_MACHINE_TIMER(FR_ACTION_TIMER); - return; - } -@@ -994,8 +1048,7 @@ static bool request_max_time(REQUEST *request) - * stop" macro already took care of it. - */ - if (request->child_state == REQUEST_DONE) { -- done: -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_DONE); - return true; - } - -@@ -1027,7 +1080,8 @@ static bool request_max_time(REQUEST *request) - /* - * Tell the request that it's done. - */ -- goto done; -+ request_done(request, FR_ACTION_MAX_TIME); -+ return true; - } - - /* -@@ -1096,7 +1150,7 @@ static void request_queue_or_run(REQUEST *request, - * Otherwise we're not going to do anything with - * it... - */ -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_INTERNAL_FAILURE); - return; - } - #endif -@@ -1115,6 +1169,11 @@ static void request_queue_or_run(REQUEST *request, - #endif - } - -+void request_inject(REQUEST *request) -+{ -+ request_queue_or_run(request, request_running); -+} -+ - - static void request_dup(REQUEST *request) - { -@@ -1203,11 +1262,14 @@ static void request_cleanup_delay(REQUEST *request, int action) - #ifdef DEBUG_STATE_MACHINE - if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); - #endif -+ -+ request_stats_final(request); -+ - STATE_MACHINE_TIMER(FR_ACTION_TIMER); - return; - } /* else it's time to clean up */ - -- request_done(request, FR_ACTION_DONE); -+ request_done(request, FR_ACTION_CLEANUP_DELAY); - break; - - default: -@@ -1282,6 +1344,7 @@ static void request_response_delay(REQUEST *request, int action) - } /* else it's time to send the reject */ - - RDEBUG2("Sending delayed response"); -+ request->listener->encode(request->listener, request); - debug_packet(request, request->reply, false); - request->listener->send(request->listener, request); - -@@ -1327,7 +1390,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) - /* - * Ignore parse errors. - */ -- if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition)) { -+ if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition) == 1) { - request->log.lvl = L_DBG_LVL_2; - request->log.func = vradlog_request; - } -@@ -1340,7 +1403,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) - } - - if (rcode < 0) { -- RATE_LIMIT(INFO("Dropping packet without response because of error: %s", fr_strerror())); -+ RATE_LIMIT(INFO("Dropping packet without response because of error: %s (from client %s)", fr_strerror(), request->client->shortname)); - request->reply->offset = -2; /* bad authenticator */ - return 0; - } -@@ -1506,7 +1569,8 @@ static void request_finish(REQUEST *request, int action) - /* - * See if we need to delay an Access-Reject packet. - */ -- if ((request->reply->code == PW_CODE_ACCESS_REJECT) && -+ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && -+ (request->reply->code == PW_CODE_ACCESS_REJECT) && - (request->root->reject_delay.tv_sec > 0)) { - request->response_delay = request->root->reject_delay; - -@@ -1534,9 +1598,14 @@ static void request_finish(REQUEST *request, int action) - #ifdef WITH_PROXY - /* - * If we timed out a proxy packet, don't delay -- * the reject any more. -+ * the reject any more. Or, if we proxied it to -+ * a real home server, then don't delay it. -+ * -+ * We don't want to have each proxy in a chain -+ * adding their own reject delay, which would -+ * result in N*reject_delays being applied. - */ -- if (request->proxy && !request->proxy_reply) { -+ if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { - request->response_delay.tv_sec = 0; - request->response_delay.tv_usec = 0; - } -@@ -1560,6 +1629,7 @@ static void request_finish(REQUEST *request, int action) - } - } - -+ request->listener->encode(request->listener, request); - debug_packet(request, request->reply, false); - request->listener->send(request->listener, request); - } -@@ -1598,6 +1668,8 @@ static void request_finish(REQUEST *request, int action) - */ - static void request_running(REQUEST *request, int action) - { -+ int rcode; -+ - VERIFY_REQUEST(request); - - TRACE_STATE_MACHINE; -@@ -1631,7 +1703,8 @@ static void request_running(REQUEST *request, int action) - /* - * We may need to send a proxied request. - */ -- if (request_will_proxy(request)) { -+ rcode = request_will_proxy(request); -+ if (rcode == 1) { - #ifdef DEBUG_STATE_MACHINE - if (rad_debug_lvl) printf("(%u) ********\tWill Proxy\t********\n", request->number); - #endif -@@ -1641,13 +1714,42 @@ static void request_running(REQUEST *request, int action) - * up the post proxy fail - * handler. - */ -+ retry_proxy: - if (request_proxy(request) < 0) { -- if (request->home_server && request->home_server->server) goto req_finished; -+ if (request->home_server && request->home_server->virtual_server) goto req_finished; -+ -+ if (request->home_pool && request->home_server && -+ HOME_SERVER_IS_DEAD(request->home_server)) { -+ VALUE_PAIR *vp; -+ REALM *realm = NULL; -+ home_server_t *home = NULL; -+ -+ vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY); -+ if (vp) realm = realm_find2(vp->vp_strvalue); -+ -+ /* -+ * Since request->home_server is dead, -+ * this function won't pick the same home server as before. -+ */ -+ if (realm) home = home_server_ldb(vp->vp_strvalue, request->home_pool, request); -+ if (home) { -+ home_server_update_request(home, request); -+ goto retry_proxy; -+ } -+ } - - (void) setup_post_proxy_fail(request); - process_proxy_reply(request, NULL); - goto req_finished; - } -+ -+ } else if (rcode < 0) { -+ /* -+ * No live home servers, run Post-Proxy-Type Fail. -+ */ -+ (void) setup_post_proxy_fail(request); -+ process_proxy_reply(request, NULL); -+ goto req_finished; - } else - #endif - { -@@ -1762,7 +1864,7 @@ int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *pack - * the request just as we're logging the - * complaint. - */ -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_CONFLICT); - request = NULL; - - /* -@@ -1851,7 +1953,7 @@ skip_dup: - if (!listener->nodup) { - if (!rbtree_insert(pl, &request->packet)) { - RERROR("Failed to insert request in the list of live requests: discarding it"); -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_INTERNAL_FAILURE); - return 1; - } - -@@ -2016,23 +2118,7 @@ static void tcp_socket_timer(void *ctx) - - fr_event_now(el, &now); - -- switch (listener->type) { --#ifdef WITH_PROXY -- case RAD_LISTEN_PROXY: -- limit = &sock->home->limit; -- break; --#endif -- -- case RAD_LISTEN_AUTH: --#ifdef WITH_ACCOUNTING -- case RAD_LISTEN_ACCT: --#endif -- limit = &sock->limit; -- break; -- -- default: -- return; -- } -+ limit = &sock->limit; - - /* - * If we enforce a lifetime, do it now. -@@ -2069,6 +2155,16 @@ static void tcp_socket_timer(void *ctx) - * Mark the socket as "don't use if at all possible". - */ - listener->status = RAD_LISTEN_STATUS_FROZEN; -+ -+ /* -+ * If it's blocked, then push all of the requests to other sockets. -+ */ -+#ifdef WITH_TLS -+ if (listener->blocked) { -+ listener->status = RAD_LISTEN_STATUS_REMOVE_NOW; -+ } -+#endif -+ - event_new_fd(listener); - return; - } -@@ -2222,11 +2318,9 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) - } - } - --#ifdef WITH_TCP - if (request->proxy_listener) { - request->proxy_listener->count--; - } --#endif - request->proxy_listener = NULL; - - /* -@@ -2278,7 +2372,7 @@ static int insert_into_proxy_hash(REQUEST *request) - - - PTHREAD_MUTEX_LOCK(&proxy_mutex); -- proxy_listener = NULL; -+ proxy_listener = request->proxy_listener; /* may or may not be NULL */ - request->num_proxied_requests = 1; - request->num_proxied_responses = 0; - -@@ -2288,8 +2382,8 @@ static int insert_into_proxy_hash(REQUEST *request) - - RDEBUG3("proxy: Trying to allocate ID (%d/2)", tries); - success = fr_packet_list_id_alloc(proxy_list, -- request->home_server->proto, -- &request->proxy, &proxy_listener); -+ request->home_server->proto, -+ &request->proxy, &proxy_listener); - if (success) break; - - if (tries > 0) continue; /* try opening new socket only once */ -@@ -2298,6 +2392,16 @@ static int insert_into_proxy_hash(REQUEST *request) - if (proxy_no_new_sockets) break; - #endif - -+ /* -+ * Don't try to add a new listener if it's not possible. -+ */ -+ if (fr_event_list_full(el)) { -+ RATE_LIMIT(ERROR("Cannot open a new proxy socket - too many sockets are already open")); -+ request->home_server->state = HOME_STATE_CONNECTION_FAIL; -+ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); -+ goto fail; -+ } -+ - RDEBUG3("proxy: Trying to open a new listener to the home server"); - this = proxy_new_listener(proxy_ctx, request->home_server, 0); - if (!this) { -@@ -2362,9 +2466,7 @@ static int insert_into_proxy_hash(REQUEST *request) - */ - request->home_server->currently_outstanding++; - --#ifdef WITH_TCP - request->proxy_listener->count++; --#endif - - PTHREAD_MUTEX_UNLOCK(&proxy_mutex); - -@@ -2389,7 +2491,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) - /* - * There may be a proxy reply, but it may be too late. - */ -- if ((request->home_server && !request->home_server->server) && !request->proxy_listener) return 0; -+ if ((request->home_server && !request->home_server->virtual_server) && !request->proxy_listener) return 0; - - /* - * Delete any reply we had accumulated until now. -@@ -2453,7 +2555,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) - * Decode the packet if required. - */ - if (request->proxy_listener) { -- rcode = request->proxy_listener->decode(request->proxy_listener, request); -+ rcode = request->proxy_listener->proxy_decode(request->proxy_listener, request); - debug_packet(request, reply, true); - - /* -@@ -2473,19 +2575,18 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) - remove_from_proxy_hash(request); - } - -- old_server = request->server; - - /* -- * If the home server is virtual, just run pre_proxy from -- * that section. -+ * Run the request through the virtual server for the -+ * home server, OR through the virtual server for the -+ * home server pool. - */ -- if (request->home_server && request->home_server->server) { -- request->server = request->home_server->server; -+ old_server = request->server; -+ if (request->home_server && request->home_server->virtual_server) { -+ request->server = request->home_server->virtual_server; - -- } else { -- if (request->home_pool && request->home_pool->virtual_server) { -+ } else if (request->home_pool && request->home_pool->virtual_server) { - request->server = request->home_pool->virtual_server; -- } - } - - /* -@@ -2598,11 +2699,79 @@ int request_proxy_reply(RADIUS_PACKET *packet) - * ignore it. This does the MD5 calculations in the - * server core, but I guess we can fix that later. - */ -- if (!request->proxy_reply && -- (rad_verify(packet, request->proxy, -- request->home_server->secret) != 0)) { -- DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); -- return 0; -+ if (!request->proxy_reply) { -+ decode_fail_t reason; -+ -+ /* -+ * If the home server configuration requires a Message-Authenticator, then set the flag, -+ * but only if the proxied packet is Access-Request or Status-Sercer. -+ * -+ * The realms.c file already clears require_ma for TLS connections. -+ */ -+ bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST); -+ -+ if (!request->home_server) { -+ proxy_reply_too_late(request); -+ return 0; -+ } -+ -+ if (!rad_packet_ok(packet, require_ma, &reason)) { -+ DEBUG("Ignoring invalid packet - %s", fr_strerror()); -+ return 0; -+ } -+ -+ if (rad_verify(packet, request->proxy, -+ request->home_server->secret) != 0) { -+ DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); -+ return 0; -+ } -+ -+ /* -+ * BlastRADIUS checks. We're running in the main -+ * listener thread, so there's no conflict -+ * checking or setting these fields. -+ */ -+ if ((request->proxy->code == PW_CODE_ACCESS_REQUEST) && -+#ifdef WITH_TLS -+ !request->home_server->tls && -+#endif -+ !packet->eap_message) { -+ if (request->home_server->require_ma == FR_BOOL_AUTO) { -+ if (!packet->message_authenticator) { -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator."); -+ RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name); -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name); -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ request->home_server->require_ma = FR_BOOL_FALSE; -+ } else { -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator."); -+ RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name); -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack."); -+ RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); -+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ request->home_server->require_ma = FR_BOOL_TRUE; -+ } -+ -+ } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) { -+ /* -+ * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet. -+ */ -+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name); -+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ RDEBUG("The packet does not contain Message-Authenticator, which is a security issue"); -+ RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); -+ RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); -+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ } -+ } - } - - /* -@@ -2887,8 +3056,9 @@ static void proxy_running(REQUEST *request, int action) - * The key attributes are: - * - PW_PROXY_TO_REALM - Specifies a realm the request should be proxied to. - * - PW_HOME_SERVER_POOL - Specifies a specific home server pool to proxy to. -- * - PW_PACKET_DST_IP_ADDRESS - Specifies a specific IPv4 home server to proxy to. -- * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a specific IPv6 home server to proxy to. -+ * - PW_HOME_SERVER_NAME - Specifies a home server by name -+ * - PW_PACKET_DST_IP_ADDRESS - Specifies a home server by IPv4 address -+ * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a home server by IPv5 address - * - * Certain packet types such as #PW_CODE_STATUS_SERVER will never be proxied. - * -@@ -2918,7 +3088,9 @@ static int request_will_proxy(REQUEST *request) - - VERIFY_REQUEST(request); - -- if (!request->root->proxy_requests) return 0; -+ if (!request->root->proxy_requests) { -+ return 0; -+ } - if (request->packet->dst_port == 0) return 0; - if (request->packet->code == PW_CODE_STATUS_SERVER) return 0; - if (request->in_proxy_hash) return 0; -@@ -3052,7 +3224,55 @@ static int request_will_proxy(REQUEST *request) - * The home server is alive (or may be alive). - * Send the packet to the IP. - */ -- if (home->state < HOME_STATE_IS_DEAD) goto do_home; -+ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; -+ -+ /* -+ * The home server is dead. If you wanted -+ * fail-over, you should have proxied to a pool. -+ * Sucks to be you. -+ */ -+ -+ return 0; -+ -+ } else if ((vp = fr_pair_find_by_num(request->config, PW_HOME_SERVER_NAME, 0, TAG_ANY)) != NULL) { -+ int type; -+ -+ switch (request->packet->code) { -+ case PW_CODE_ACCESS_REQUEST: -+ type = HOME_TYPE_AUTH; -+ break; -+ -+#ifdef WITH_ACCOUNTING -+ case PW_CODE_ACCOUNTING_REQUEST: -+ type = HOME_TYPE_ACCT; -+ break; -+#endif -+ -+#ifdef WITH_COA -+ case PW_CODE_COA_REQUEST: -+ case PW_CODE_DISCONNECT_REQUEST: -+ type = HOME_TYPE_COA; -+ break; -+#endif -+ -+ default: -+ return 0; -+ } -+ -+ /* -+ * Find the home server by name. -+ */ -+ home = home_server_byname(vp->vp_strvalue, type); -+ if (!home) { -+ RWDEBUG("No such home server %s", vp->vp_strvalue); -+ return 0; -+ } -+ -+ /* -+ * The home server is alive (or may be alive). -+ * Send the packet to the IP. -+ */ -+ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; - - /* - * The home server is dead. If you wanted -@@ -3063,6 +3283,7 @@ static int request_will_proxy(REQUEST *request) - return 0; - - } else { -+ - return 0; - } - -@@ -3081,8 +3302,8 @@ static int request_will_proxy(REQUEST *request) - home = home_server_ldb(realmname, pool, request); - - if (!home) { -- REDEBUG2("Failed to find live home server: Cancelling proxy"); -- return 1; -+ REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); -+ return -1; - } - - do_home: -@@ -3093,7 +3314,11 @@ do_home: - * Once we've decided to proxy a request, we cannot send - * a CoA packet. So we free up any CoA packet here. - */ -- if (request->coa) request_done(request->coa, FR_ACTION_CANCELLED); -+ if (request->coa) { -+ RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); -+ request_done(request->coa, FR_ACTION_COA_CANCELLED); -+ request->coa = NULL; -+ } - #endif - - /* -@@ -3179,14 +3404,14 @@ do_home: - pre_proxy_type = vp->vp_integer; - } - -- old_server = request->server; -- - /* -- * If the home server is virtual, just run pre_proxy from -- * that section. -+ * Run the request through the virtual server for the -+ * home server, OR through the virtual server for the -+ * home server pool. - */ -- if (request->home_server && request->home_server->server) { -- request->server = request->home_server->server; -+ old_server = request->server; -+ if (request->home_server && request->home_server->virtual_server) { -+ request->server = request->home_server->virtual_server; - - } else { - char buffer[128]; -@@ -3245,7 +3470,7 @@ static int proxy_to_virtual_server(REQUEST *request) - } - - DEBUG("Proxying to virtual server %s", -- request->home_server->server); -+ request->home_server->virtual_server); - - /* - * Packets to virtual servers don't get -@@ -3260,7 +3485,7 @@ static int proxy_to_virtual_server(REQUEST *request) - fake->packet->vps = fr_pair_list_copy(fake->packet, request->packet->vps); - talloc_free(request->proxy); - -- fake->server = request->home_server->server; -+ fake->server = request->home_server->virtual_server; - fake->handle = request->handle; - fake->process = NULL; /* should never be run for anything */ - -@@ -3309,7 +3534,8 @@ static int request_proxy(REQUEST *request) - #ifdef WITH_COA - if (request->coa) { - RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); -- request_done(request->coa, FR_ACTION_CANCELLED); -+ request_done(request->coa, FR_ACTION_COA_CANCELLED); -+ request->coa = NULL; - } - #endif - -@@ -3329,7 +3555,7 @@ static int request_proxy(REQUEST *request) - * - * So, we have some horrible hacks to get around that. - */ -- if (request->home_server->server) return proxy_to_virtual_server(request); -+ if (request->home_server->virtual_server) return proxy_to_virtual_server(request); - - /* - * We're actually sending a proxied packet. Do that now. -@@ -3372,7 +3598,7 @@ static int request_proxy(REQUEST *request) - /* - * Encode the packet before we do anything else. - */ -- request->proxy_listener->encode(request->proxy_listener, request); -+ request->proxy_listener->proxy_encode(request->proxy_listener, request); - debug_packet(request, request->proxy, false); - - /* -@@ -3392,7 +3618,7 @@ static int request_proxy(REQUEST *request) - /* - * And send the packet. - */ -- request->proxy_listener->send(request->proxy_listener, request); -+ request->proxy_listener->proxy_send(request->proxy_listener, request); - return 1; - } - -@@ -3458,7 +3684,7 @@ static int request_proxy_anew(REQUEST *request) - * If so, run that instead of doing proxying to a real - * server. - */ -- if (home->server) { -+ if (home->virtual_server) { - request->home_server = home; - TALLOC_FREE(request->proxy); - -@@ -3538,7 +3764,7 @@ static void request_ping(REQUEST *request, int action) - * - * If it's zombie, we mark it alive immediately. - */ -- if ((home->state >= HOME_STATE_IS_DEAD) && -+ if (HOME_SERVER_IS_DEAD(home) && - (home->num_received_pings < home->num_pings_to_alive)) { - return; - } -@@ -3610,7 +3836,7 @@ static void ping_home_server(void *ctx) - - if (timercmp(&when, &now, <)) { - DEBUG("PING: Zombie period is over for home server %s", home->log_name); -- mark_home_server_dead(home, &now); -+ mark_home_server_dead(home, &now, false); - } - } - -@@ -3639,6 +3865,7 @@ static void ping_home_server(void *ctx) - NO_CHILD_THREAD; - - request->proxy = rad_alloc(request, true); -+ request->root = &main_config; - rad_assert(request->proxy != NULL); - - if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) { -@@ -3735,9 +3962,10 @@ static void ping_home_server(void *ctx) - home->num_sent_pings++; - - rad_assert(request->proxy_listener != NULL); -+ request->proxy_listener->proxy_encode(request->proxy_listener, request); - debug_packet(request, request->proxy, false); -- request->proxy_listener->send(request->proxy_listener, -- request); -+ request->proxy_listener->proxy_send(request->proxy_listener, -+ request); - - /* - * Add +/- 2s of jitter, as suggested in RFC 3539 -@@ -3840,7 +4068,34 @@ void revive_home_server(void *ctx) - home->port); - } - --void mark_home_server_dead(home_server_t *home, struct timeval *when) -+#ifdef WITH_TLS -+static int eol_home_listener(UNUSED void *ctx, void *data) -+{ -+ rad_listen_t *this = talloc_get_type_abort(data, rad_listen_t); -+ -+ /* -+ * The socket isn't blocked, we can still use it. -+ * -+ * i.e. the home server is dead for a reason OTHER than -+ * "all available sockets are blocked". -+ * -+ * We can still ping the home server via sockets which -+ * are writable. -+ */ -+ if (!this->blocked) return 0; -+ -+ this->status = RAD_LISTEN_STATUS_EOL; -+ -+ FD_MUTEX_LOCK(&fd_mutex); -+ this->next = new_listeners; -+ new_listeners = this; -+ FD_MUTEX_UNLOCK(&fd_mutex); -+ -+ return 1; /* alway delete from this tree */ -+} -+#endif -+ -+void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down) - { - int previous_state = home->state; - char buffer[128]; -@@ -3853,6 +4108,34 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when) - home->state = HOME_STATE_IS_DEAD; - home_trigger(home, "home_server.dead"); - -+#ifdef WITH_TLS -+ /* -+ * If the home server is dead, then close all of the sockets associated with it. -+ * -+ * Note that the "EOL listener" code expects to _also_ -+ * delete the listeners. At which point we end up with a -+ * mutex locked twice, and bad things happen. The -+ * solution is to move the listeners to the global -+ * "waiting for update" list, and then notify ourselves -+ * that there are listeners waiting to be updated. -+ */ -+ if (home->listeners) { -+ ASSERT_MASTER; -+ -+ rbtree_walk(home->listeners, RBTREE_DELETE_ORDER, eol_home_listener, NULL); -+ radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD); -+ } -+#endif -+ -+ /* -+ * Administratively down - don't do anything to bring it -+ * up. -+ */ -+ if (down) { -+ home->state = HOME_STATE_ADMIN_DOWN; -+ return; -+ } -+ - /* - * Ping it if configured, AND we can ping it. - */ -@@ -3925,14 +4208,14 @@ static void proxy_wait_for_reply(REQUEST *request, int action) - * The request was proxied to a virtual server. - * Ignore the retransmit. - */ -- if (request->home_server->server) return; -+ if (request->home_server->virtual_server) return; - - /* - * Failed connections get the home server marked - * as dead. - */ - if (home->state == HOME_STATE_CONNECTION_FAIL) { -- mark_home_server_dead(home, &now); -+ mark_home_server_dead(home, &now, false); - } - - /* -@@ -3948,7 +4231,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) - * If the listener is known or frozen, use it for - * retransmits. - */ -- if ((home->state >= HOME_STATE_IS_DEAD) || -+ if (HOME_SERVER_IS_DEAD(home) || - !request->proxy_listener || - (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { - request_proxy_anew(request); -@@ -3980,7 +4263,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) - when.tv_sec++; - - if (timercmp(&now, &when, <)) { -- DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", -+ DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", - inet_ntop(request->proxy->dst_ipaddr.af, - &request->proxy->dst_ipaddr.ipaddr, - buffer, sizeof(buffer)), -@@ -4014,7 +4297,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) - home->last_packet_sent = now.tv_sec; - request->proxy->timestamp = now; - debug_packet(request, request->proxy, false); -- request->proxy_listener->send(request->proxy_listener, request); -+ request->proxy_listener->proxy_send(request->proxy_listener, request); - break; - - case FR_ACTION_TIMER: -@@ -4023,7 +4306,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) - * as dead. - */ - if (home->state == HOME_STATE_CONNECTION_FAIL) { -- mark_home_server_dead(home, &now); -+ mark_home_server_dead(home, &now, false); - } - - response_window = request_response_window(request); -@@ -4175,6 +4458,7 @@ static void request_coa_originate(REQUEST *request) - VALUE_PAIR *vp; - REQUEST *coa; - fr_ipaddr_t ipaddr; -+ char const *old_server; - char buffer[256]; - - VERIFY_REQUEST(request); -@@ -4202,7 +4486,7 @@ static void request_coa_originate(REQUEST *request) - - if (!main_config.proxy_requests) { - RWDEBUG("Cannot originate CoA packets unless 'proxy_requests = yes'"); -- TALLOC_FREE(request->coa); -+ TALLOC_FREE(request->coa); - return; - } - -@@ -4234,11 +4518,11 @@ static void request_coa_originate(REQUEST *request) - /* - * Prefer the pool to one server - */ -- } else if (request->client->coa_pool) { -- coa->home_pool = request->client->coa_pool; -+ } else if (request->client->coa_home_pool) { -+ coa->home_pool = request->client->coa_home_pool; - -- } else if (request->client->coa_server) { -- coa->home_server = request->client->coa_server; -+ } else if (request->client->coa_home_server) { -+ coa->home_server = request->client->coa_home_server; - - } else { - /* -@@ -4318,19 +4602,26 @@ static void request_coa_originate(REQUEST *request) - pre_proxy_type = vp->vp_integer; - } - -- if (coa->home_pool && coa->home_pool->virtual_server) { -- char const *old_server = coa->server; -+ /* -+ * Run the request through the virtual server for the -+ * home server, OR through the virtual server for the -+ * home server pool. -+ */ -+ old_server = request->server; -+ if (coa->home_server && coa->home_server->virtual_server) { -+ coa->server = coa->home_server->virtual_server; - -+ } else if (coa->home_pool && coa->home_pool->virtual_server) { - coa->server = coa->home_pool->virtual_server; -- RDEBUG2("server %s {", coa->server); -- RINDENT(); -- rcode = process_pre_proxy(pre_proxy_type, coa); -- REXDENT(); -- RDEBUG2("}"); -- coa->server = old_server; -- } else { -- rcode = process_pre_proxy(pre_proxy_type, coa); - } -+ -+ RDEBUG2("server %s {", coa->server); -+ RINDENT(); -+ rcode = process_pre_proxy(pre_proxy_type, coa); -+ REXDENT(); -+ RDEBUG2("}"); -+ coa->server = old_server; -+ - switch (rcode) { - default: - goto fail; -@@ -4379,7 +4670,7 @@ static void request_coa_originate(REQUEST *request) - /* - * Encode the packet before we do anything else. - */ -- coa->proxy_listener->encode(coa->proxy_listener, coa); -+ coa->proxy_listener->proxy_encode(coa->proxy_listener, coa); - debug_packet(coa, coa->proxy, false); - - #ifdef DEBUG_STATE_MACHINE -@@ -4404,7 +4695,7 @@ static void request_coa_originate(REQUEST *request) - /* - * And send the packet. - */ -- coa->proxy_listener->send(coa->proxy_listener, coa); -+ coa->proxy_listener->proxy_send(coa->proxy_listener, coa); - } - - -@@ -4420,11 +4711,11 @@ static void coa_retransmit(REQUEST *request) - * Don't do fail-over. This is a 3.1 feature. - */ - if (!request->home_server || -- (request->home_server->state >= HOME_STATE_IS_DEAD) || -+ HOME_SERVER_IS_DEAD(request->home_server) || - request->proxy_reply || - !request->proxy_listener || - (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_COA_CANCELLED); - return; - } - -@@ -4548,8 +4839,8 @@ static void coa_retransmit(REQUEST *request) - request->proxy->dst_port, - request->proxy->id); - -- request->proxy_listener->send(request->proxy_listener, -- request); -+ request->proxy_listener->proxy_send(request->proxy_listener, -+ request); - } - - -@@ -4579,7 +4870,7 @@ static bool coa_max_time(REQUEST *request) - */ - if (request->child_state == REQUEST_DONE) { - done: -- request_done(request, FR_ACTION_CANCELLED); -+ request_done(request, FR_ACTION_MAX_TIME); - return true; - } - -@@ -4615,7 +4906,8 @@ static bool coa_max_time(REQUEST *request) - buffer, sizeof(buffer)), - request->proxy->dst_port, - mrd); -- goto done; -+ request_done(request, FR_ACTION_DONE); -+ return true; - } - - #ifdef HAVE_PTHREAD_H -@@ -4948,15 +5240,14 @@ static void event_status(struct timeval *wake) - } - } - --#ifdef WITH_TCP - static void listener_free_cb(void *ctx) - { - rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t); -+ listen_socket_t *sock = this->data; - char buffer[1024]; - - if (this->count > 0) { - struct timeval when; -- listen_socket_t *sock = this->data; - - fr_event_now(el, &when); - when.tv_sec += 3; -@@ -4977,9 +5268,13 @@ static void listener_free_cb(void *ctx) - this->print(this, buffer, sizeof(buffer)); - DEBUG("... cleaning up socket %s", buffer); - rad_assert(this->next == NULL); -+#ifdef WITH_TCP -+ fr_event_delete(el, &sock->ev); -+#endif - talloc_free(this); - } - -+#ifdef WITH_TCP - #ifdef WITH_PROXY - static int proxy_eol_cb(void *ctx, void *data) - { -@@ -5024,6 +5319,7 @@ static int proxy_eol_cb(void *ctx, void *data) - static void event_new_fd(rad_listen_t *this) - { - char buffer[1024]; -+ listen_socket_t *sock = NULL; - - ASSERT_MASTER; - -@@ -5031,29 +5327,28 @@ static void event_new_fd(rad_listen_t *this) - - this->print(this, buffer, sizeof(buffer)); - -- if (this->status == RAD_LISTEN_STATUS_INIT) { -- listen_socket_t *sock = this->data; -- -+ if (this->type != RAD_LISTEN_DETAIL) { -+ sock = this->data; - rad_assert(sock != NULL); -+ } -+ -+ if (this->status == RAD_LISTEN_STATUS_INIT) { - if (just_started) { - DEBUG("Listening on %s", buffer); -- } else { -- INFO(" ... adding new socket %s", buffer); -- } - - #ifdef WITH_PROXY -- if (!just_started && (this->type == RAD_LISTEN_PROXY)) { -- home_server_t *home; -- -- home = sock->home; -- if (!home || !home->limit.max_connections) { -- INFO(" ... adding new socket %s", buffer); -- } else { -+ } else if (this->type == RAD_LISTEN_PROXY) { -+ home_server_t *home = sock->home; -+ -+ if (home && home->limit.max_connections) { - INFO(" ... adding new socket %s (%u of %u)", buffer, - home->limit.num_connections, home->limit.max_connections); -+ } else { -+ INFO(" ... adding new socket %s", buffer); - } -- - #endif -+ } else { -+ INFO(" ... adding new socket %s", buffer); - } - - switch (this->type) { -@@ -5084,6 +5379,7 @@ static void event_new_fd(rad_listen_t *this) - */ - case RAD_LISTEN_PROXY: - #ifdef WITH_TCP -+ rad_assert(sock != NULL); - rad_assert((sock->proto == IPPROTO_UDP) || (sock->home != NULL)); - - /* -@@ -5102,6 +5398,19 @@ static void event_new_fd(rad_listen_t *this) - rad_panic("Failed to insert event"); - } - } -+ -+ /* -+ * Run a callback to do any specific -+ * signalling on "connection up". -+ * -+ * For TLS sockets and WITH_COA_TUNNEL, -+ * this function should be similar to -+ * ping_home_server(), except that it -+ * should send a Status-Server packet, -+ * with Originating-Realm-Key as a VSA. -+ */ -+// process_listener_up(this); -+ - #endif /* WITH_TCP */ - break; - #endif /* WITH_PROXY */ -@@ -5136,16 +5445,35 @@ static void event_new_fd(rad_listen_t *this) - /* - * All sockets: add the FD to the event handler. - */ -+ insert_fd: - if (fr_event_fd_insert(el, 0, this->fd, - event_socket_handler, this)) { - this->status = RAD_LISTEN_STATUS_KNOWN; - return; - } - -- ERROR("Failed adding event handler for socket: %s", fr_strerror()); -- this->status = RAD_LISTEN_STATUS_REMOVE_NOW; -+ /* -+ * Print out which socket failed. -+ * -+ * If we're trying to add the socket, then -+ * forcibly remove it immediately, without any -+ * additional cleanups. There cannot, and MUST -+ * NOT be any packets associated with the socket. -+ */ -+ this->print(this, buffer, sizeof(buffer)); -+ ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); -+ -+ this->status = RAD_LISTEN_STATUS_EOL; -+ goto listener_is_eol; - } /* end of INIT */ - -+ if (this->status == RAD_LISTEN_STATUS_PAUSE) { -+ fr_event_fd_delete(el, 0, this->fd); -+ return; -+ } -+ -+ if (this->status == RAD_LISTEN_STATUS_RESUME) goto insert_fd; -+ - #ifdef WITH_TCP - /* - * The socket has reached a timeout. Try to close it. -@@ -5157,7 +5485,6 @@ static void event_new_fd(rad_listen_t *this) - */ - if (this->count > 0) { - struct timeval when; -- listen_socket_t *sock = this->data; - - /* - * Try again to clean up the socket in 30 -@@ -5179,6 +5506,7 @@ static void event_new_fd(rad_listen_t *this) - fr_event_fd_delete(el, 0, this->fd); - this->status = RAD_LISTEN_STATUS_REMOVE_NOW; - } -+#endif /* WITH_TCP */ - - /* - * The socket has had a catastrophic error. Close it. -@@ -5189,6 +5517,7 @@ static void event_new_fd(rad_listen_t *this) - */ - fr_event_fd_delete(el, 0, this->fd); - -+ listener_is_eol: - #ifdef WITH_PROXY - /* - * Tell all requests using this socket that the socket is dead. -@@ -5214,7 +5543,6 @@ static void event_new_fd(rad_listen_t *this) - */ - if (this->count > 0) { - struct timeval when; -- listen_socket_t *sock = this->data; - - /* - * Try again to clean up the socket in 30 -@@ -5238,17 +5566,16 @@ static void event_new_fd(rad_listen_t *this) - */ - this->status = RAD_LISTEN_STATUS_REMOVE_NOW; - } /* socket is at EOL */ --#endif /* WITH_TCP */ -+ -+ if (this->dead) goto wait_some_more; - - /* - * Nuke the socket. - */ - if (this->status == RAD_LISTEN_STATUS_REMOVE_NOW) { - int devnull; --#ifdef WITH_TCP -- listen_socket_t *sock = this->data; -- struct timeval when; --#endif -+ -+ this->dead = true; - - /* - * Re-open the socket, pointing it to /dev/null. -@@ -5290,6 +5617,7 @@ static void event_new_fd(rad_listen_t *this) - */ - if (this->type == RAD_LISTEN_PROXY) { - home_server_t *home; -+ sock = this->data; - - home = sock->home; - if (!home || !home->limit.max_connections) { -@@ -5307,6 +5635,20 @@ static void event_new_fd(rad_listen_t *this) - buffer, fr_strerror()); - fr_exit(1); - } -+ -+#ifdef WITH_TLS -+ /* -+ * Remove this socket from the list of sockets assocated with this home server. -+ * -+ * This MUST be done with the proxy mutex locked! -+ */ -+ if (home && home->tls) { -+ fr_assert(home->listeners); -+ -+ (void) rbtree_deletebydata(home->listeners, this); -+ } -+#endif -+ - PTHREAD_MUTEX_UNLOCK(&proxy_mutex); - } else - #endif /* WITH_PROXY */ -@@ -5324,7 +5666,10 @@ static void event_new_fd(rad_listen_t *this) - */ - if (!spawn_flag) { - ASSERT_MASTER; -- if (sock->ev) fr_event_delete(el, &sock->ev); -+ -+ if (this->type != RAD_LISTEN_DETAIL && sock && sock->ev) { -+ fr_event_delete(el, &sock->ev); -+ } - listen_free(&this); - return; - } -@@ -5332,14 +5677,8 @@ static void event_new_fd(rad_listen_t *this) - /* - * Wait until all requests using this socket are done. - */ -- gettimeofday(&when, NULL); -- when.tv_sec += 3; -- -- ASSERT_MASTER; -- if (!fr_event_insert(el, listener_free_cb, this, &when, -- &(sock->ev))) { -- rad_panic("Failed to insert event"); -- } -+ wait_some_more: -+ listener_free_cb(this); - #endif /* WITH_TCP */ - } - -@@ -5603,7 +5942,10 @@ static void check_proxy(rad_listen_t *head) - rad_listen_t *this; - - if (check_config) return; -- if (!main_config.proxy_requests) return; -+ if (!main_config.proxy_requests) { -+ DEBUG3("Cannot proxy packets unless 'proxy_requests = yes'"); -+ return; -+ } - if (!head) return; - #ifdef WITH_TCP - if (!home_servers_udp) return; -diff --git a/src/main/radclient.c b/src/main/radclient.c -index 52d2872b13..b48d97235e 100644 ---- a/src/main/radclient.c -+++ b/src/main/radclient.c -@@ -28,6 +28,10 @@ RCSID("$Id$") - #include - #include - #include -+#ifdef HAVE_OPENSSL_SSL_H -+#include -+#include -+#endif - #include - - #ifdef HAVE_GETOPT_H -@@ -36,6 +40,8 @@ RCSID("$Id$") - - #include - -+USES_APPLE_DEPRECATED_API -+ - typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */ - - #include "smbdes.h" -@@ -54,6 +60,7 @@ static fr_ipaddr_t server_ipaddr; - static int resend_count = 1; - static bool done = true; - static bool print_filename = false; -+static bool blast_radius = false; - - static fr_ipaddr_t client_ipaddr; - static uint16_t client_port = 0; -@@ -89,6 +96,7 @@ static void NEVER_RETURNS usage(void) - fprintf(stderr, " One of auth, acct, status, coa, disconnect or auto.\n"); - fprintf(stderr, " -4 Use IPv4 address of server\n"); - fprintf(stderr, " -6 Use IPv6 address of server.\n"); -+ fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n"); - fprintf(stderr, " -c Send each packet 'count' times.\n"); - fprintf(stderr, " -d Set user dictionary directory (defaults to " RADDBDIR ").\n"); - fprintf(stderr, " -D Set main dictionary directory (defaults to " DICTDIR ").\n"); -@@ -155,9 +163,60 @@ static int _rc_request_free(rc_request_t *request) - return 0; - } - -+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L -+# include -+ -+static OSSL_PROVIDER *openssl_default_provider = NULL; -+static OSSL_PROVIDER *openssl_legacy_provider = NULL; -+ -+static int openssl3_init(void) -+{ -+ /* -+ * Load the default provider for most algorithms -+ */ -+ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); -+ if (!openssl_default_provider) { -+ ERROR("(TLS) Failed loading default provider"); -+ return -1; -+ } -+ -+ /* -+ * Needed for MD4 -+ * -+ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms -+ */ -+ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); -+ if (!openssl_legacy_provider) { -+ ERROR("(TLS) Failed loading legacy provider"); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+static void openssl3_free(void) -+{ -+ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { -+ ERROR("Failed unloading default provider"); -+ } -+ openssl_default_provider = NULL; -+ -+ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { -+ ERROR("Failed unloading legacy provider"); -+ } -+ openssl_legacy_provider = NULL; -+} -+#else -+#define openssl3_init() -+#define openssl3_free() -+#endif -+ -+ -+ - static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, - char const *password) - { -+ int rcode; - unsigned int i; - uint8_t *p; - VALUE_PAIR *challenge, *reply; -@@ -190,9 +249,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, - - p[1] = 0x01; /* NT hash */ - -- if (mschap_ntpwdhash(nthash, password) < 0) { -- return 0; -- } -+ rcode = mschap_ntpwdhash(nthash, password); -+ if (rcode < 0) return 0; - - smbdes_mschap(nthash, challenge->vp_octets, p + 26); - return 1; -@@ -960,8 +1018,8 @@ static int send_one_packet(rc_request_t *request) - */ - fr_packet_list_yank(pl, request->packet); - -- REDEBUG("No reply from server for ID %d socket %d", -- request->packet->id, request->packet->sockfd); -+ RDEBUG("No reply from server for ID %d socket %d", -+ request->packet->id, request->packet->sockfd); - deallocate_id(request); - - /* -@@ -1000,6 +1058,130 @@ static int send_one_packet(rc_request_t *request) - return 0; - } - -+/* -+ * Do Blast RADIUS checks. -+ * -+ * The request is an Access-Request, and does NOT contain Proxy-State. -+ * -+ * The reply is a raw packet, and is NOT yet decoded. -+ */ -+static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply) -+{ -+ uint8_t *attr, *end; -+ VALUE_PAIR *vp; -+ bool have_message_authenticator = false; -+ -+ /* -+ * We've received a raw packet. Nothing has (as of yet) checked -+ * anything in it other than the length, and that it's a -+ * well-formed RADIUS packet. -+ */ -+ switch (reply->data[0]) { -+ case PW_CODE_ACCESS_ACCEPT: -+ case PW_CODE_ACCESS_REJECT: -+ case PW_CODE_ACCESS_CHALLENGE: -+ if (reply->data[1] != request->packet->id) { -+ ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id); -+ return -1; -+ } -+ break; -+ -+ default: -+ ERROR("Invalid reply code %d to Access-Request", reply->data[0]); -+ return -1; -+ } -+ -+ /* -+ * If the reply has a Message-Authenticator, then it MIGHT be fine. -+ */ -+ attr = reply->data + 20; -+ end = reply->data + reply->data_len; -+ -+ /* -+ * It should be the first attribute, so we warn if it isn't there. -+ * -+ * But it's not a fatal error. -+ */ -+ if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) { -+ RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.", -+ fr_packet_codes[reply->data[0]]); -+ } -+ -+ /* -+ * Set up for Proxy-State checks. -+ * -+ * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack. -+ */ -+ vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY); -+ -+ while (attr < end) { -+ /* -+ * Blast RADIUS work-arounds require that -+ * Message-Authenticator is the first attribute in the -+ * reply. Note that we don't check for it being the -+ * first attribute, but simply that it exists. -+ * -+ * That check is a balance between securing the reply -+ * packet from attacks, and not violating the RFCs which -+ * say that there is no order to attributes in the -+ * packet. -+ * -+ * However, no matter the status of the '-b' flag we -+ * still can check for the signature of the attack, and -+ * discard packets which are suspicious. This behavior -+ * protects radclient from the attack, without mandating -+ * new behavior on the server side. -+ * -+ * Note that we don't set the '-b' flag by default. -+ * radclient is intended for testing / debugging, and is -+ * not intended to be used as part of a secure login / -+ * user checking system. -+ */ -+ if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { -+ have_message_authenticator = true; -+ goto next; -+ } -+ -+ /* -+ * If there are Proxy-State attributes in the reply, they must -+ * match EXACTLY the Proxy-State attributes in the request. -+ * -+ * Note that we don't care if there are more Proxy-States -+ * in the request than in the reply. The Blast RADIUS -+ * issue requires _adding_ Proxy-State attributes, and -+ * cannot work when the server _deletes_ Proxy-State -+ * attributes. -+ */ -+ if (attr[0] == PW_PROXY_STATE) { -+ if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) { -+ ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id); -+ ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent."); -+ return -1; -+ } -+ -+ vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY); -+ } -+ -+ next: -+ attr += attr[1]; -+ } -+ -+ /* -+ * If "-b" is set, then we require Message-Authenticator in the reply. -+ */ -+ if (blast_radius && !have_message_authenticator) { -+ ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.", -+ fr_packet_codes[reply->data[0]]); -+ return -1; -+ } -+ -+ /* -+ * The packet doesn't look like it's a Blast RADIUS attack. The -+ * caller will now verify the packet signature. -+ */ -+ return 0; -+} -+ - /* - * Receive one packet, maybe. - */ -@@ -1051,6 +1233,20 @@ static int recv_one_packet(int wait_time) - } - request = fr_packet2myptr(rc_request_t, packet, packet_p); - -+ /* -+ * We want radclient to be able to send any packet, including -+ * imperfect ones. However, we do NOT want to be vulnerable to -+ * the "Blast RADIUS" issue. Instead of adding command-line -+ * flags to enable/disable similar flags to what the server -+ * sends, we just do a few more smart checks to double-check -+ * things. -+ */ -+ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && -+ blast_radius_check(request, reply) < 0) { -+ rad_free(&reply); -+ return -1; -+ } -+ - /* - * Fails the signature validation: not a real reply. - * FIXME: Silently drop it and listen for another packet. -@@ -1146,6 +1342,7 @@ packet_done: - return 0; - } - -+DIAG_OFF(deprecated-declarations) - int main(int argc, char **argv) - { - int c; -@@ -1183,7 +1380,7 @@ int main(int argc, char **argv) - exit(1); - } - -- while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx" -+ while ((c = getopt(argc, argv, "46bc:d:D:f:Fhn:p:qr:sS:t:vx" - #ifdef WITH_TCP - "P:" - #endif -@@ -1196,10 +1393,16 @@ int main(int argc, char **argv) - force_af = AF_INET6; - break; - -+ case 'b': -+ blast_radius = true; -+ break; -+ - case 'c': -- if (!isdigit((int) *optarg)) -- usage(); -+ if (!isdigit((uint8_t) *optarg)) usage(); -+ - resend_count = atoi(optarg); -+ -+ if (resend_count < 1) usage(); - break; - - case 'D': -@@ -1274,7 +1477,7 @@ int main(int argc, char **argv) - break; - - case 'r': -- if (!isdigit((int) *optarg)) usage(); -+ if (!isdigit((uint8_t) *optarg)) usage(); - retries = atoi(optarg); - if ((retries == 0) || (retries > 1000)) usage(); - break; -@@ -1314,7 +1517,7 @@ int main(int argc, char **argv) - break; - - case 't': -- if (!isdigit((int) *optarg)) -+ if (!isdigit((uint8_t) *optarg)) - usage(); - timeout = atof(optarg); - break; -@@ -1361,7 +1564,7 @@ int main(int argc, char **argv) - /* - * Get the request type - */ -- if (!isdigit((int) argv[2][0])) { -+ if (!isdigit((uint8_t) argv[2][0])) { - packet_code = fr_str2int(request_types, argv[2], -2); - if (packet_code == -2) { - ERROR("Unrecognised request type \"%s\"", argv[2]); -@@ -1421,6 +1624,8 @@ int main(int argc, char **argv) - exit(1); - } - -+ openssl3_init(); -+ - /* - * Bind to the first specified IP address and port. - * This means we ignore later ones. -@@ -1637,5 +1842,9 @@ int main(int argc, char **argv) - if ((stats.lost > 0) || (stats.failed > 0)) { - exit(1); - } -+ -+ openssl3_free(); -+ - exit(0); - } -+DIAG_ON(deprecated-declarations) -diff --git a/src/main/radiusd.c b/src/main/radiusd.c -index 9739514509..6cece08893 100644 ---- a/src/main/radiusd.c -+++ b/src/main/radiusd.c -@@ -710,6 +710,7 @@ cleanup: - if (main_config.memory_report) { - INFO("Allocated memory at time of report:"); - fr_log_talloc_report(NULL); -+ talloc_disable_null_tracking(); - } - - return rcode; -diff --git a/src/main/radtest.in b/src/main/radtest.in -index 38b1ba9a0f..af9df80eec 100644 ---- a/src/main/radtest.in -+++ b/src/main/radtest.in -@@ -19,6 +19,7 @@ usage() { - echo " -x Enable debug output" >&2 - echo " -4 Use IPv4 for the NAS address (default)" >&2 - echo " -6 Use IPv6 for the NAS address" >&2 -+ echo " -6 Mandate checks for Blast RADIUS (this is not set by default)." >&2 - exit 1 - } - -@@ -55,6 +56,10 @@ do - NAS_ADDR_ATTR="NAS-IPv6-Address" - shift - ;; -+ -b) -+ OPTIONS="$OPTIONS -b" -+ shift -+ ;; - -d) - OPTIONS="$OPTIONS -d $2" - shift;shift -@@ -112,7 +117,7 @@ if [ "$7" ] - then - nas=$7 - else -- nas=`hostname` -+ nas=`(hostname || uname -n) 2>/dev/null | sed 1q` - fi - - ( -@@ -120,7 +125,6 @@ fi - echo "$PASSWORD = \"$2\"" - echo "$NAS_ADDR_ATTR = $nas" - echo "NAS-Port = $4" -- echo "Message-Authenticator = 0x00" - if [ "$radclient" = "$radeapclient" ] - then - echo "EAP-Code = Response" -diff --git a/src/main/realms.c b/src/main/realms.c -index eb42598116..3f2c37270c 100644 ---- a/src/main/realms.c -+++ b/src/main/realms.c -@@ -37,6 +37,10 @@ static rbtree_t *realms_byname = NULL; - bool home_servers_udp = false; - #endif - -+#ifdef HAVE_DIRENT_H -+#include -+#endif -+ - #ifdef HAVE_REGEX - typedef struct realm_regex realm_regex_t; - -@@ -53,6 +57,9 @@ static realm_regex_t *realms_regex = NULL; - - struct realm_config { - CONF_SECTION *cs; -+#ifdef HAVE_DIRENT_H -+ char const *directory; -+#endif - uint32_t dead_time; - uint32_t retry_count; - uint32_t retry_delay; -@@ -89,7 +96,7 @@ static realm_config_t *realm_config = NULL; - static rbtree_t *home_servers_byaddr = NULL; - static rbtree_t *home_servers_byname = NULL; - #ifdef WITH_STATS --static int home_server_max_number = 0; -+int home_server_max_number = 0; - static rbtree_t *home_servers_bynumber = NULL; - #endif - -@@ -107,6 +114,10 @@ static const CONF_PARSER proxy_config[] = { - - { "dynamic", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, dynamic), NULL }, - -+#ifdef HAVE_DIRENT_H -+ { "directory", FR_CONF_OFFSET(PW_TYPE_STRING, realm_config_t, directory), NULL }, -+#endif -+ - { "dead_time", FR_CONF_OFFSET(PW_TYPE_INTEGER, realm_config_t, dead_time), STRINGIFY(DEAD_TIME) }, - - { "wake_all_if_all_dead", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, wake_all_if_all_dead), "no" }, -@@ -148,12 +159,12 @@ static int home_server_addr_cmp(void const *one, void const *two) - home_server_t const *a = one; - home_server_t const *b = two; - -- if (a->server && !b->server) return -1; -- if (!a->server && b->server) return +1; -- if (a->server && b->server) { -+ if (a->virtual_server && !b->virtual_server) return -1; -+ if (!a->virtual_server && b->virtual_server) return +1; -+ if (a->virtual_server && b->virtual_server) { - rcode = a->type - b->type; - if (rcode != 0) return rcode; -- return strcmp(a->server, b->server); -+ return strcmp(a->virtual_server, b->virtual_server); - } - - if (a->port < b->port) return -1; -@@ -268,6 +279,10 @@ static ssize_t xlat_home_server(UNUSED void *instance, REQUEST *request, - state = "fail"; - break; - -+ case HOME_STATE_ADMIN_DOWN: -+ state = "down"; -+ break; -+ - default: - state = "unknown"; - break; -@@ -317,6 +332,60 @@ static ssize_t xlat_server_pool(UNUSED void *instance, REQUEST *request, - - return xlat_cs(request->home_pool->cs, fmt, out, outlen); - } -+ -+ -+/* -+ * Xlat for %{home_server_dynamic:foo} -+ */ -+static ssize_t xlat_home_server_dynamic(UNUSED void *instance, REQUEST *request, -+ char const *fmt, char *out, size_t outlen) -+{ -+ int type; -+ char const *p; -+ home_server_t *home; -+ -+ if (outlen < 2) return 0; -+ -+ switch (request->packet->code) { -+ case PW_CODE_ACCESS_REQUEST: -+ type = HOME_TYPE_AUTH; -+ break; -+ -+#ifdef WITH_ACCOUNTING -+ case PW_CODE_ACCOUNTING_REQUEST: -+ type = HOME_TYPE_ACCT; -+ break; -+#endif -+ -+#ifdef WITH_COA -+ case PW_CODE_COA_REQUEST: -+ case PW_CODE_DISCONNECT_REQUEST: -+ type = HOME_TYPE_COA; -+ break; -+#endif -+ -+ default: -+ *out = '\0'; -+ return 0; -+ } -+ -+ p = fmt; -+ while (isspace((uint8_t) *p)) p++; -+ -+ home = home_server_byname(p, type); -+ if (!home) { -+ *out = '\0'; -+ return 0; -+ } -+ -+ /* -+ * 1 for dynamic, 0 for static -+ */ -+ out[0] = '0' + home->dynamic; -+ out[1] = '\0'; -+ -+ return 1; -+} - #endif - - void realms_free(void) -@@ -366,11 +435,15 @@ static CONF_PARSER home_server_coa[] = { - }; - #endif - -+static const char *require_message_authenticator = NULL; -+ - static CONF_PARSER home_server_config[] = { -+ { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, -+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, - { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL }, - { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL }, - { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL }, -- { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, server), NULL }, -+ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, virtual_server), NULL }, - - { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, home_server_t, port), "0" }, - -@@ -519,7 +592,7 @@ static bool home_server_insert(home_server_t *home, CONF_SECTION *cs) - return false; - } - -- if (!home->server && !rbtree_insert(home_servers_byaddr, home)) { -+ if (!home->virtual_server && !rbtree_insert(home_servers_byaddr, home)) { - rbtree_deletebydata(home_servers_byname, home); - cf_log_err_cs(cs, "Internal error %d adding home server %s", __LINE__, home->log_name); - return false; -@@ -561,7 +634,7 @@ bool realm_home_server_add(home_server_t *home) - return false; - } - -- if (!home->server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { -+ if (!home->virtual_server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { - char buffer[INET6_ADDRSTRLEN + 3]; - - inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr, buffer, sizeof(buffer)); -@@ -620,6 +693,19 @@ bool realm_home_server_add(home_server_t *home) - return true; - } - -+#ifdef WITH_TLS -+/* -+ * The listeners are always different. And we always look them up by *known* listener. And not "find me some random thing". -+ */ -+static int listener_cmp(void const *one, void const *two) -+{ -+ if (one < two) return -1; -+ if (one > two) return +1; -+ -+ return 0; -+} -+#endif -+ - /** Alloc a new home server defined by a CONF_SECTION - * - * @param ctx to allocate home_server_t in. -@@ -640,6 +726,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - home->cs = cs; - home->state = HOME_STATE_UNKNOWN; - home->proto = IPPROTO_UDP; -+ home->require_ma = main_config.require_ma; -+ -+ require_message_authenticator = false; - - /* - * Parse the configuration into the home server -@@ -647,6 +736,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - */ - if (cf_section_parse(cs, home, home_server_config) < 0) goto error; - -+ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) { -+ goto error; -+ } -+ - /* - * It has an IP address, it must be a remote server. - */ -@@ -670,23 +763,25 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - } else if (cf_pair_find(cs, "virtual_server") != NULL) { - home->ipaddr.af = AF_UNSPEC; /* mark ipaddr as unused */ - -- if (!home->server) { -+ if (!home->virtual_server) { - cf_log_err_cs(cs, "Invalid value for virtual_server"); - goto error; - } - - /* -- * Try and find a 'server' section off the root of -+ * Try and find a "server" section off the root of - * the config with a name that matches the - * virtual_server. - */ -- if (!cf_section_sub_find_name2(rc->cs, "server", home->server)) { -- cf_log_err_cs(cs, "No such server %s", home->server); -+ if (!rc) goto error; -+ -+ if (!cf_section_sub_find_name2(rc->cs, "server", home->virtual_server)) { -+ cf_log_err_cs(cs, "No such server %s", home->virtual_server); - goto error; - } - - home->secret = ""; -- home->log_name = talloc_typed_strdup(home, home->server); -+ home->log_name = talloc_typed_strdup(home, home->virtual_server); - /* - * Otherwise it's an invalid config section and we - * raise an error. -@@ -717,7 +812,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - - #ifdef WITH_COA - case HOME_TYPE_COA: -- if (home->server != NULL) { -+ if (home->virtual_server != NULL) { - cf_log_err_cs(cs, "Home servers of type \"coa\" cannot point to a virtual server"); - goto error; - } -@@ -781,8 +876,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - cf_log_err_cs(cs, "Server not built with support for RADIUS over TCP"); - goto error; - #endif -- if (home->ping_check != HOME_PING_CHECK_NONE) { -- cf_log_err_cs(cs, "Only 'status_check = none' is allowed for home " -+ if ((home->ping_check != HOME_PING_CHECK_NONE) && -+ (home->ping_check != HOME_PING_CHECK_STATUS_SERVER)) { -+ cf_log_err_cs(cs, "Only 'status_check = status-server' is allowed for home " - "servers with 'proto = tcp'"); - goto error; - } -@@ -796,7 +892,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - home->proto = proto; - } - -- if (!home->server && rbtree_finddata(home_servers_byaddr, home)) { -+ if (!home->virtual_server && rbtree_finddata(home_servers_byaddr, home)) { - cf_log_err_cs(cs, "Duplicate home server"); - goto error; - } -@@ -831,7 +927,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - /* - * Virtual servers have some TLS restrictions. - */ -- if (home->server) { -+ if (home->virtual_server) { - if (tls) { - cf_log_err_cs(cs, "Virtual home_servers cannot have a \"tls\" subsection"); - goto error; -@@ -924,10 +1020,29 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE - * Parse the SSL client configuration. - */ - if (tls) { -+ int rcode; -+ -+ /* -+ * We don't require this for TLS connections. -+ */ -+ home->require_ma = false; -+ - home->tls = tls_client_conf_parse(tls); - if (!home->tls) { - goto error; - } -+ -+ /* -+ * Connection timeouts for outgoing TLS connections. -+ */ -+ -+ rcode = cf_item_parse(tls, "connect_timeout", FR_ITEM_POINTER(PW_TYPE_INTEGER, &home->connect_timeout), NULL); -+ if (rcode < 0) goto error; -+ -+ if (!home->connect_timeout || (home->connect_timeout > 30)) home->connect_timeout = 30; -+ -+ home->listeners = rbtree_create(home, listener_cmp, NULL, RBTREE_FLAG_LOCK); -+ if (!home->listeners) goto error; - } - #endif - } /* end of parse home server */ -@@ -1173,8 +1288,17 @@ void realm_pool_free(home_pool_t *pool) - } - #endif /* HAVE_PTHREAD_H */ - --int realm_pool_add(home_pool_t *pool, UNUSED CONF_SECTION *cs) -+int realm_pool_add(home_pool_t *pool, CONF_SECTION *cs) - { -+ home_pool_t *old; -+ -+ old = rbtree_finddata(home_pools_byname, pool); -+ if (old) { -+ cf_log_err_cs(cs, "Cannot add duplicate home server %s, original is at %s[%d]", pool->name, -+ cf_section_filename(old->cs), cf_section_lineno(old->cs)); -+ return 0; -+ } -+ - /* - * The structs aren't mutex protected. Refuse to destroy - * the server. -@@ -1265,7 +1389,7 @@ static int server_pool_add(realm_config_t *rc, - goto error; - } - -- if (!pool->fallback->server) { -+ if (!pool->fallback->virtual_server) { - cf_log_err_cs(cs, "Fallback home_server %s does NOT contain a virtual_server directive", - pool->fallback->log_name); - goto error; -@@ -1541,7 +1665,7 @@ static int old_server_add(realm_config_t *rc, CONF_SECTION *cs, - home->src_ipaddr.af = home->ipaddr.af; - } else { - home->ipaddr.af = AF_UNSPEC; -- home->server = server; -+ home->virtual_server = server; - } - talloc_free(q); - -@@ -2255,9 +2379,68 @@ int realms_init(CONF_SECTION *config) - #ifdef WITH_PROXY - xlat_register("home_server", xlat_home_server, NULL, NULL); - xlat_register("home_server_pool", xlat_server_pool, NULL, NULL); -+ xlat_register("home_server_dynamic", xlat_home_server_dynamic, NULL, NULL); - #endif - - realm_config = rc; -+ -+#ifdef HAVE_DIRENT_H -+ if (!rc->dynamic) { -+ if (rc->directory) { -+ WARN("Ignoring 'directory' as dynamic home servers were not configured."); -+ } -+ } else { -+ DIR *dir; -+ struct dirent *dp; -+ -+ if (!rc->directory) { -+ WARN("Ignoring \"dynamic = true\" due to not set \"directory\" in proxy.conf"); -+ return 1; -+ } -+ -+ DEBUG2("including files in directory %s", rc->directory); -+ -+ dir = opendir(rc->directory); -+ if (!dir) { -+ cf_log_err_cs(config, "Error reading directory %s: %s", -+ rc->directory, fr_syserror(errno)); -+ goto error; -+ } -+ -+ /* -+ * Read the directory, ignoring "." files. -+ */ -+ while ((dp = readdir(dir)) != NULL) { -+ char const *p; -+ char conf_file[PATH_MAX]; -+ -+ if (dp->d_name[0] == '.') continue; -+ -+ /* -+ * Check for valid characters -+ */ -+ for (p = dp->d_name; *p != '\0'; p++) { -+ if (isalpha((uint8_t)*p) || -+ isdigit((uint8_t)*p) || -+ (*p == '-') || -+ (*p == '_') || -+ (*p == '.')) continue; -+ break; -+ } -+ if (*p != '\0') continue; -+ -+ snprintf(conf_file, sizeof(conf_file), "%s/%s", rc->directory, dp->d_name); -+ if (home_server_afrom_file(conf_file) < 0) { -+ ERROR("Failed reading home_server from %s - %s", -+ conf_file, fr_strerror()); -+ closedir(dir); -+ goto error; -+ } -+ } -+ closedir(dir); -+ } -+#endif -+ - return 1; - } - -@@ -2388,7 +2571,7 @@ void home_server_update_request(home_server_t *home, REQUEST *request) - * the 'hints' file. - */ - request->proxy->vps = fr_pair_list_copy(request->proxy, -- request->packet->vps); -+ request->packet->vps); - } - - /* -@@ -2516,7 +2699,7 @@ home_server_t *home_server_ldb(char const *realmname, - * Home servers that are unknown, alive, or zombie - * are used for proxying. - */ -- if (home->state >= HOME_STATE_IS_DEAD) { -+ if (HOME_SERVER_IS_DEAD(home)) { - continue; - } - -@@ -2533,7 +2716,8 @@ home_server_t *home_server_ldb(char const *realmname, - * came from this server. Don't re-proxy it - * there. - */ -- if ((request->listener->type == RAD_LISTEN_DETAIL) && -+ if (request->listener && -+ (request->listener->type == RAD_LISTEN_DETAIL) && - (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) && - (fr_ipaddr_cmp(&home->ipaddr, &request->packet->src_ipaddr) == 0)) { - continue; -@@ -2641,7 +2825,7 @@ home_server_t *home_server_ldb(char const *realmname, - found = pool->fallback; - - WARN("Home server pool %s failing over to fallback %s", -- pool->name, found->server); -+ pool->name, found->virtual_server); - if (pool->in_fallback) goto update_and_return; - - pool->in_fallback = true; -@@ -2680,7 +2864,7 @@ home_server_t *home_server_ldb(char const *realmname, - - if (!home) continue; - -- if ((home->state >= HOME_STATE_IS_DEAD) && -+ if (HOME_SERVER_IS_DEAD(home) && - (home->ping_check == HOME_PING_CHECK_NONE)) { - home->state = HOME_STATE_ALIVE; - home->response_timeouts = 0; -@@ -2740,7 +2924,7 @@ home_server_t *home_server_find(fr_ipaddr_t *ipaddr, uint16_t port, - #else - myhome.proto = IPPROTO_UDP; - #endif -- myhome.server = NULL; /* we're not called for internal proxying */ -+ myhome.virtual_server = NULL; /* we're not called for internal proxying */ - - return rbtree_finddata(home_servers_byaddr, &myhome); - } -@@ -2764,7 +2948,7 @@ home_server_t *home_server_find_bysrc(fr_ipaddr_t *ipaddr, uint16_t port, - #else - myhome.proto = IPPROTO_UDP; - #endif -- myhome.server = NULL; /* we're not called for internal proxying */ -+ myhome.virtual_server = NULL; /* we're not called for internal proxying */ - - return rbtree_finddata(home_servers_byaddr, &myhome); - } -@@ -2789,7 +2973,7 @@ home_server_t *home_server_bynumber(int number) - - memset(&myhome, 0, sizeof(myhome)); - myhome.number = number; -- myhome.server = NULL; /* we're not called for internal proxying */ -+ myhome.virtual_server = NULL; /* we're not called for internal proxying */ - - return rbtree_finddata(home_servers_bynumber, &myhome); - } -@@ -2805,4 +2989,116 @@ home_pool_t *home_pool_byname(char const *name, int type) - return rbtree_finddata(home_pools_byname, &mypool); - } - -+int home_server_afrom_file(char const *filename) -+{ -+ CONF_SECTION *cs, *subcs; -+ char const *p; -+ home_server_t *home; -+ -+ if (!realm_config->dynamic) { -+ fr_strerror_printf("Must set \"dynamic = true\" in proxy.conf for dynamic home servers to work"); -+ return -1; -+ } -+ -+ cs = cf_section_alloc(NULL, "home", filename); -+ if (!cs) { -+ fr_strerror_printf("Failed allocating memory"); -+ return -1; -+ } -+ -+ if (cf_file_read(cs, filename) < 0) { -+ fr_strerror_printf("Failed reading file %s", filename); -+ error: -+ talloc_free(cs); -+ return -1; -+ } -+ -+ p = strrchr(filename, '/'); -+ if (p) { -+ p++; -+ } else { -+ p = filename; -+ } -+ -+ subcs = cf_section_sub_find_name2(cs, "home_server", p); -+ if (!subcs) { -+ fr_strerror_printf("No 'home_server %s' definition in the file.", p); -+ goto error; -+ } -+ -+ home = home_server_afrom_cs(realm_config, realm_config, subcs); -+ if (!home) { -+ fr_strerror_printf("Failed parsing configuration to a home_server structure"); -+ goto error; -+ } -+ -+ home->dynamic = true; -+ -+ if (home->virtual_server) { -+ fr_strerror_printf("Dynamic home_server '%s' cannot have 'server = %s' configuration item", p, home->virtual_server); -+ talloc_free(home); -+ goto error; -+ } -+ -+ if (home->dual) { -+ fr_strerror_printf("Dynamic home_server '%s' is missing 'type', or it is set to 'auth+acct'. Please specify 'type = auth' or 'type = acct', etc.", p); -+ talloc_free(home); -+ goto error; -+ } -+ -+ if (!realm_home_server_add(home)) { -+ fr_strerror_printf("Failed adding home_server to the internal data structures"); -+ talloc_free(home); -+ goto error; -+ } -+ -+ return 0; -+} -+ -+int home_server_delete(char const *name, char const *type_name) -+{ -+ home_server_t *home; -+ int type; -+ char const *p; -+ -+ if (!realm_config->dynamic) { -+ fr_strerror_printf("Must set 'dynamic' in proxy.conf for dynamic home servers to work"); -+ return -1; -+ } -+ -+ type = fr_str2int(home_server_types, type_name, HOME_TYPE_INVALID); -+ if (type == HOME_TYPE_INVALID) { -+ fr_strerror_printf("Unknown home_server type '%s'", type_name); -+ return -1; -+ } -+ -+ p = strrchr(name, '/'); -+ if (p) { -+ p++; -+ } else { -+ p = name; -+ } -+ -+ home = home_server_byname(p, type); -+ if (!home) { -+ fr_strerror_printf("Failed to find home_server %s", p); -+ return -1; -+ } -+ -+ if (!home->dynamic) { -+ fr_strerror_printf("Cannot delete static home_server %s", p); -+ return -1; -+ } -+ -+ (void) rbtree_deletebydata(home_servers_byname, home); -+ (void) rbtree_deletebydata(home_servers_byaddr, home); -+#ifdef WITH_STATS -+ (void) rbtree_deletebydata(home_servers_bynumber, home); -+#endif -+ -+ /* -+ * Leak home, and home->cs. Oh well. -+ */ -+ return 0; -+} - #endif -diff --git a/src/main/session.c b/src/main/session.c -index e359010a1b..8dbf5a6f14 100644 ---- a/src/main/session.c -+++ b/src/main/session.c -@@ -34,7 +34,7 @@ RCSID("$Id$") - /* - * End a session by faking a Stop packet to all accounting modules. - */ --int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, -+int session_zap(REQUEST *request, fr_ipaddr_t const *nasaddr, uint32_t nas_port, - char const *user, - char const *sessionid, uint32_t cliaddr, char proto, - int session_time) -@@ -64,6 +64,8 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, - - #define IPPAIR(n,v) PAIR(n,v,vp_ipaddr) - -+#define IPV6PAIR(n,v) PAIR(n,v,vp_ipv6addr) -+ - #define STRINGPAIR(n,v) do { \ - if(!(vp = fr_pair_afrom_num(stopreq->packet,n, 0))) { \ - talloc_free(stopreq); \ -@@ -75,7 +77,12 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, - } while(0) - - INTPAIR(PW_ACCT_STATUS_TYPE, PW_STATUS_STOP); -- IPPAIR(PW_NAS_IP_ADDRESS, nasaddr); -+ -+ if (nasaddr->af == AF_INET) { -+ IPPAIR(PW_NAS_IP_ADDRESS, nasaddr->ipaddr.ip4addr.s_addr); -+ } else { -+ IPV6PAIR(PW_NAS_IPV6_ADDRESS, nasaddr->ipaddr.ip6addr); -+ } - - INTPAIR(PW_EVENT_TIMESTAMP, 0); - vp->vp_date = time(NULL); -@@ -127,29 +134,25 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, - * 1 The user is logged in. - * 2 Some error occured. - */ --int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, -+int rad_check_ts(fr_ipaddr_t const *nasaddr, uint32_t nas_port, char const *user, - char const *session_id) - { - pid_t pid, child_pid; - int status; -- char address[16]; -+ char address[64]; - char port[11]; - RADCLIENT *cl; -- fr_ipaddr_t ipaddr; -- -- ipaddr.af = AF_INET; -- ipaddr.ipaddr.ip4addr.s_addr = nasaddr; - - /* - * Find NAS type. - */ -- cl = client_find_old(&ipaddr); -+ cl = client_find_old(nasaddr); - if (!cl) { - /* - * Unknown NAS, so trusting radutmp. - */ - DEBUG2("checkrad: Unknown NAS %s, not checking", -- ip_ntoa(address, nasaddr)); -+ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address))); - return 1; - } - -@@ -202,8 +205,8 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, - */ - closefrom(3); - -- ip_ntoa(address, nasaddr); -- snprintf(port, 11, "%u", nas_port); -+ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address)); -+ snprintf(port, sizeof(port), "%u", nas_port); - - #ifdef __EMX__ - /* OS/2 can't directly execute scripts then we call the command -@@ -223,7 +226,7 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, - exit(2); - } - #else --int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, -+int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, - UNUSED char const *user, UNUSED char const *session_id) - { - ERROR("Simultaneous-Use is not supported"); -@@ -234,7 +237,7 @@ int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, - #else - /* WITH_SESSION_MGMT */ - --int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_t nas_port, -+int session_zap(UNUSED REQUEST *request, fr_ipaddr_t const *nasaddr, UNUSED uint32_t nas_port, - UNUSED char const *user, - UNUSED char const *sessionid, UNUSED uint32_t cliaddr, UNUSED char proto, - UNUSED int session_time) -@@ -242,7 +245,7 @@ int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_ - return RLM_MODULE_FAIL; - } - --int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, -+int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, - UNUSED char const *user, UNUSED char const *session_id) - { - ERROR("Simultaneous-Use is not supported"); -diff --git a/src/main/stats.c b/src/main/stats.c -index 33b5fd238a..2c5df06d8e 100644 ---- a/src/main/stats.c -+++ b/src/main/stats.c -@@ -90,44 +90,66 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, - - void request_stats_final(REQUEST *request) - { -- if (request->master_state == REQUEST_COUNTED) return; -+ rad_listen_t *listener; -+ RADCLIENT *client; - -- if (!request->listener) return; -- if (!request->client) return; -+ if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; - -- if ((request->listener->type != RAD_LISTEN_NONE) && -+ /* -+ * This packet was originated by the server, and not -+ * received from a client. It's a status-server or home -+ * server "ping" packet. So we ignore it for statistics -+ * purposes. -+ */ -+ if (!request->packet) return; -+ -+ /* don't count statistic requests */ -+ if (request->packet->code == PW_CODE_STATUS_SERVER) { -+ return; -+ } -+ -+ listener = request->listener; -+ if (listener) switch (listener->type) { -+ case RAD_LISTEN_NONE: - #ifdef WITH_ACCOUNTING -- (request->listener->type != RAD_LISTEN_ACCT) && -+ case RAD_LISTEN_ACCT: - #endif - #ifdef WITH_COA -- (request->listener->type != RAD_LISTEN_COA) && -+ case RAD_LISTEN_COA: - #endif -- (request->listener->type != RAD_LISTEN_AUTH)) return; -+ case RAD_LISTEN_AUTH: -+ break; - -- /* don't count statistic requests */ -- if (request->packet->code == PW_CODE_STATUS_SERVER) -- return; -+ default: -+ return; -+ } -+ -+ /* -+ * Deal with TCP / TLS issues. The statistics are kept in the parent socket. -+ */ -+ if (listener && listener->parent) listener = listener->parent; -+ client = request->client; - - #undef INC_AUTH --#define INC_AUTH(_x) radius_auth_stats._x++;request->listener->stats._x++;request->client->auth._x++; -+#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; - - #undef INC_ACCT - #ifdef WITH_ACCOUNTING --#define INC_ACCT(_x) radius_acct_stats._x++;request->listener->stats._x++;request->client->acct._x++ -+#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ - #else - #define INC_ACCT(_x) - #endif - - #undef INC_COA - #ifdef WITH_COA --#define INC_COA(_x) radius_coa_stats._x++;request->listener->stats._x++;request->client->coa._x++ -+#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ - #else - #define INC_COA(_x) - #endif - - #undef INC_DSC - #ifdef WITH_DSC --#define INC_DSC(_x) radius_dsc_stats._x++;request->listener->stats._x++;request->client->dsc._x++ -+#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ - #else - #define INC_DSC(_x) - #endif -@@ -140,7 +162,7 @@ void request_stats_final(REQUEST *request) - * deleted, because only the main server thread calls - * this function, which makes it thread-safe. - */ -- if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { -+ if (request->reply) switch (request->reply->code) { - case PW_CODE_ACCESS_ACCEPT: - INC_AUTH(total_access_accepts); - -@@ -268,7 +290,7 @@ void request_stats_final(REQUEST *request) - if (!request->proxy_reply) goto done; /* simplifies formatting */ - - #undef INC --#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; -+#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; - - switch (request->proxy_reply->code) { - case PW_CODE_ACCESS_ACCEPT: -@@ -339,7 +361,7 @@ void request_stats_final(REQUEST *request) - done: - #endif /* WITH_PROXY */ - -- request->master_state = REQUEST_COUNTED; -+ request->options |= RAD_REQUEST_OPTION_STATS; - } - - typedef struct fr_stats2vp { -@@ -582,6 +604,23 @@ void request_stats_reply(REQUEST *request) - */ - if (!cl) return; - } -+#ifdef AF_INET6 -+ } else { -+ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); -+ if (server_ip) { -+ server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); -+ if (server_port) { -+ ipaddr.af = AF_INET6; -+ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; -+ cl = listener_find_client_list(&ipaddr, server_port->vp_integer, IPPROTO_UDP); -+ -+ /* -+ * Not found: don't do anything -+ */ -+ if (!cl) return; -+ } -+ } -+#endif /* AF_INET6 */ - } - - -@@ -597,6 +636,19 @@ void request_stats_reply(REQUEST *request) - } - #endif - -+#ifdef AF_INET6 -+ } else if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { -+ memset(&ipaddr, 0, sizeof(ipaddr)); -+ ipaddr.af = AF_INET6; -+ ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; -+ client = client_find(cl, &ipaddr, IPPROTO_UDP); -+#ifdef WITH_TCP -+ if (!client) { -+ client = client_find(cl, &ipaddr, IPPROTO_TCP); -+ } -+#endif -+#endif /* AF_INET6 */ -+ - /* - * Else look it up by number. - */ -@@ -615,23 +667,44 @@ void request_stats_reply(REQUEST *request) - * When retrieving client by number, also - * echo back it's IP address. - */ -- if ((vp->da->type == PW_TYPE_INTEGER) && -- (client->ipaddr.af == AF_INET)) { -- vp = radius_pair_create(request->reply, -- &request->reply->vps, -- PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); -- if (vp) { -- vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; -+ if (vp->da->type == PW_TYPE_INTEGER) { -+ if (client->ipaddr.af == AF_INET) { -+ vp = radius_pair_create(request->reply, -+ &request->reply->vps, -+ PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); -+ if (vp) { -+ vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; -+ } -+ -+ if (client->ipaddr.prefix != 32) { -+ vp = radius_pair_create(request->reply, -+ &request->reply->vps, -+ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); -+ if (vp) { -+ vp->vp_integer = client->ipaddr.prefix; -+ } -+ } - } - -- if (client->ipaddr.prefix != 32) { -+#ifdef AF_INET6 -+ if (client->ipaddr.af == AF_INET6) { - vp = radius_pair_create(request->reply, -- &request->reply->vps, -- PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); -+ &request->reply->vps, -+ PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS); - if (vp) { -- vp->vp_integer = client->ipaddr.prefix; -+ vp->vp_ipv6addr = client->ipaddr.ipaddr.ip6addr; -+ } -+ -+ if (client->ipaddr.prefix != 128) { -+ vp = radius_pair_create(request->reply, -+ &request->reply->vps, -+ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); -+ if (vp) { -+ vp->vp_integer = client->ipaddr.prefix; -+ } - } - } -+#endif /* AF_INET6 */ - } - - if (server_ip) { -@@ -674,21 +747,26 @@ void request_stats_reply(REQUEST *request) - * See if we need to look up the server by socket - * socket. - */ -- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); -- if (!server_ip) return; -- - server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); - if (!server_port) return; - -- ipaddr.af = AF_INET; -- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; -- this = listener_find_byipaddr(&ipaddr, -- server_port->vp_integer, -- IPPROTO_UDP); -+ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); -+ if (server_ip) { -+ ipaddr.af = AF_INET; -+ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; -+#ifdef AF_INET6 -+ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { -+ ipaddr.af = AF_INET6; -+ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; -+#endif /* AF_INET6 */ -+ } else { -+ stats_error(request, "No listener IP address supplied"); -+ } - - /* - * Not found: don't do anything - */ -+ this = listener_find_byipaddr(&ipaddr, server_port->vp_integer, IPPROTO_UDP); - if (!this) { - stats_error(request, "No such listener"); - return; -@@ -730,16 +808,6 @@ void request_stats_reply(REQUEST *request) - VALUE_PAIR *server_ip, *server_port; - fr_ipaddr_t ipaddr; - -- /* -- * See if we need to look up the server by socket -- * socket. -- */ -- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); -- if (!server_ip) { -- stats_error(request, "No home server IP supplied"); -- return; -- } -- - server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); - if (!server_port) { - stats_error(request, "No home server port supplied"); -@@ -749,15 +817,30 @@ void request_stats_reply(REQUEST *request) - #ifndef NDEBUG - memset(&ipaddr, 0, sizeof(ipaddr)); - #endif -- ipaddr.af = AF_INET; -- ipaddr.prefix = 32; -- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; -- home = home_server_find(&ipaddr, server_port->vp_integer, -- IPPROTO_UDP); -+ -+ /* -+ * See if we need to look up the server by socket -+ * socket. -+ */ -+ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); -+ if (server_ip) { -+ ipaddr.af = AF_INET; -+ ipaddr.prefix = 32; -+ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; -+#ifdef AF_INET6 -+ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { -+ ipaddr.af = AF_INET6; -+ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; -+#endif /* AF_INET6 */ -+ } else { -+ stats_error(request, "No home server IP supplied"); -+ return; -+ } - - /* - * Not found: don't do anything - */ -+ home = home_server_find(&ipaddr, server_port->vp_integer, IPPROTO_UDP); - if (!home) { - stats_error(request, "Failed to find home server IP"); - return; -diff --git a/src/main/tls.c b/src/main/tls.c -index 78c7370a63..338ccd6446 100644 ---- a/src/main/tls.c -+++ b/src/main/tls.c -@@ -27,6 +27,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - - #include - #include -+#include - #include - - #ifdef HAVE_SYS_STAT_H -@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - #include - #endif - -+#ifdef HAVE_DIRENT_H -+#include -+#endif -+ - #ifdef HAVE_UTIME_H - #include - #endif -@@ -56,8 +61,25 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - # endif - # include - -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+# include -+ -+static OSSL_PROVIDER *openssl_default_provider = NULL; -+static OSSL_PROVIDER *openssl_legacy_provider = NULL; -+#endif -+ - #define LOG_PREFIX "tls" - -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+#define ERR_get_error_line(_file, _line) ERR_get_error_all(_file, _line, NULL, NULL, NULL) -+ -+#define FIPS_mode(_x) EVP_default_properties_is_fips_enabled(NULL) -+#define PEM_read_bio_DHparams(_bio, _x, _y, _z) PEM_read_bio_Parameters(_bio, &dh) -+#define SSL_CTX_set0_tmp_dh_pkey(_ctx, _dh) SSL_CTX_set_tmp_dh(_ctx, _dh) -+#define DH EVP_PKEY -+#define DH_free(_dh) -+#endif -+ - #ifdef ENABLE_OPENSSL_VERSION_CHECK - typedef struct libssl_defect { - uint64_t high; -@@ -153,6 +175,11 @@ static unsigned int record_plus(record_t *buf, void const *ptr, - static unsigned int record_minus(record_t *buf, void *ptr, - unsigned int size); - -+typedef struct { -+ char const *name; -+ SSL_CTX *ctx; -+} fr_realm_ctx_t; -+ - DIAG_OFF(format-nonliteral) - /** Print errors in the TLS thread local error stack - * -@@ -191,9 +218,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) - - /* Extra verbose */ - if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { -- ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer); - } else { -- ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer); - } - - talloc_free(p); -@@ -205,7 +232,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) - * Print the error we were given, irrespective - * of whether there were any OpenSSL errors. - */ -- ROPTIONAL(RERROR, ERROR, "%s", p); -+ ROPTIONAL(RERROR, ERROR, "(TLS) %s", p); - talloc_free(p); - } - -@@ -217,9 +244,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) - ERR_error_string_n(error, buffer, sizeof(buffer)); - /* Extra verbose */ - if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { -- ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer); - } else { -- ROPTIONAL(REDEBUG, ERROR, "%s", buffer); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer); - } - in_stack++; - } while ((error = ERR_get_error_line(&file, &line))); -@@ -309,11 +336,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con - * being regarded as "dead". - */ - case SSL_ERROR_SYSCALL: -- ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret); - return 0; - - case SSL_ERROR_SSL: -- ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret); - return 0; - - /* -@@ -323,7 +350,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con - * the code needs updating here. - */ - default: -- ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret); -+ ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret); - return 0; - } - -@@ -338,7 +365,7 @@ static bool identity_is_safe(const char *identity) - if (!identity) return true; - - while ((c = *(identity++)) != '\0') { -- if (isalpha((int) c) || isdigit((int) c) || isspace((int) c) || -+ if (isalpha((uint8_t) c) || isdigit((uint8_t) c) || isspace((uint8_t) c) || - (c == '@') || (c == '-') || (c == '_') || (c == '.')) { - continue; - } -@@ -369,24 +396,32 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, - FR_TLS_EX_INDEX_REQUEST); - if (request && conf->psk_query) { - size_t hex_len; -- VALUE_PAIR *vp; -+ VALUE_PAIR *vp, **certs; -+ TALLOC_CTX *talloc_ctx; - char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */ - - /* - * The passed identity is weird. Deny it. - */ - if (!identity_is_safe(identity)) { -- RWDEBUG("Invalid characters in PSK identity %s", identity); -+ RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity); - return 0; - } - - vp = pair_make_request("TLS-PSK-Identity", identity, T_OP_SET); - if (!vp) return 0; - -+ certs = (VALUE_PAIR **)SSL_get_ex_data(ssl, fr_tls_ex_index_certs); -+ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); -+ fr_assert(certs != NULL); /* pointer to sock->certs */ -+ fr_assert(talloc_ctx != NULL); /* sock */ -+ -+ fr_pair_add(certs, fr_pair_copy(talloc_ctx, vp)); -+ - hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query, - NULL, NULL); - if (!hex_len) { -- RWDEBUG("PSK expansion returned an empty string."); -+ RWDEBUG("(TLS) PSK expansion returned an empty string."); - return 0; - } - -@@ -396,7 +431,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, - * the truncation, and complain about it. - */ - if (hex_len > (2 * max_psk_len)) { -- RWDEBUG("Returned PSK is too long (%u > %u)", -+ RWDEBUG("(TLS) Returned PSK is too long (%u > %u)", - (unsigned int) hex_len, 2 * max_psk_len); - return 0; - } -@@ -419,7 +454,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, - * static identity. - */ - if (strcmp(identity, conf->psk_identity) != 0) { -- ERROR("Supplied PSK identity %s does not match configuration. Rejecting.", -+ ERROR("(TKS) Supplied PSK identity %s does not match configuration. Rejecting.", - identity); - return 0; - } -@@ -475,8 +510,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize) - #endif - } - -- -- - static int _tls_session_free(tls_session_t *ssn) - { - /* -@@ -492,6 +525,52 @@ static int _tls_session_free(tls_session_t *ssn) - return 0; - } - -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) -+/* -+ * By setting the environment variable SSLKEYLOGFILE to a filename keying -+ * material will be exported that you may use with Wireshark to decode any -+ * TLS flows. Please see the following for more details: -+ * -+ * https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption -+ * -+ * An example logging session is (you should delete the file on each run): -+ * -+ * rm -f /tmp/sslkey.log; env SSLKEYLOGFILE=/tmp/sslkey.log freeradius -X | tee /tmp/debug -+ */ -+static void tls_keylog_cb(UNUSED const SSL *ssl, const char *line) -+{ -+ int fd; -+ size_t len; -+ const char *filename; -+ // less than _POSIX_PIPE_BUF (512) guarantees writes are atomic for O_APPEND -+ char buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH]; -+ -+ filename = getenv("SSLKEYLOGFILE"); -+ if (!filename) return; -+ -+ len = strlen(line); -+ if ((len + 1) > sizeof(buffer)) { -+ DEBUG("SSLKEYLOGFILE buffer not large enough, max %lu, required %lu", sizeof(buffer), len + 1); -+ return; -+ } -+ -+ memcpy(buffer, line, len); -+ buffer[len] = '\n'; -+ -+ fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); -+ if (fd < 0) { -+ fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); -+ return; -+ } -+ -+ if (write(fd, buffer, len + 1) == -1) { -+ DEBUG("Failed to write to file %s: %s", filename, strerror(errno)); -+ } -+ -+ close(fd); -+} -+#endif -+ - tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs) - { - int ret; -@@ -506,6 +585,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con - - ssn->ctx = conf->ctx; - ssn->mtu = conf->fragment_size; -+ ssn->conf = conf; - - SSL_CTX_set_mode(ssn->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY); - -@@ -516,8 +596,15 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con - } - - request = request_alloc(ssn); -+ request->packet = rad_alloc(request, false); -+ request->reply = rad_alloc(request, false); -+ - SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); - -+ if (conf->fix_cert_order) { -+ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER, (void *) &conf->fix_cert_order); -+ } -+ - /* - * Add the message callback to identify what type of - * message/handshake is passed -@@ -537,17 +624,19 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con - SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); - SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn); - if (certs) SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)certs); -+ - SSL_set_fd(ssn->ssl, fd); -- ret = SSL_connect(ssn->ssl); - -+ ret = SSL_connect(ssn->ssl); - if (ret < 0) { - switch (SSL_get_error(ssn->ssl, ret)) { -- default: -- break; -- -- -+ default: -+ break; - - case SSL_ERROR_WANT_READ: -+ ssn->connected = false; -+ return ssn; -+ - case SSL_ERROR_WANT_WRITE: - ssn->connected = false; - return ssn; -@@ -555,7 +644,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con - } - - if (ret <= 0) { -- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); -+ tls_error_io_log(NULL, ssn, ret, "Failed in connecting TLS session."); - talloc_free(ssn); - - return NULL; -@@ -575,18 +664,61 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con - * @param conf to use to configure the tls session. - * @param request The current #REQUEST. - * @param client_cert Whether to require a client_cert. -+ * @param allow_tls13 Whether to allow or forbid TLS 1.3. - * @return a new session on success, or NULL on error. - */ --tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert) -+tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, -+#ifndef TLS1_3_VERSION -+ UNUSED -+#endif -+ bool allow_tls13) - { - tls_session_t *state = NULL; - SSL *new_tls = NULL; - int verify_mode = 0; - VALUE_PAIR *vp; -+ X509_STORE *new_cert_store; - - rad_assert(request != NULL); - -- RDEBUG2("Initiating new TLS session"); -+ RDEBUG2("(TLS) Initiating new session"); -+ -+ /* -+ * Replace X509 store if it is time to update CRLs/certs in ca_path -+ */ -+ if (conf->ca_path_reload_interval > 0 && conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { -+ pthread_mutex_lock(&conf->mutex); -+ /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */ -+ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { -+ RDEBUG2("Flushing X509 store to re-read data from ca_path dir"); -+ -+ if ((new_cert_store = fr_init_x509_store(conf)) == NULL) { -+ RERROR("(TLS) Error replacing X509 store, out of memory (?)"); -+ } else { -+ if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store); -+ /* -+ * Swap empty store with the old one. -+ */ -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) -+ conf->old_x509_store = SSL_CTX_get_cert_store(conf->ctx); -+ /* Bump refcnt so the store is kept allocated till next store replacement */ -+ X509_STORE_up_ref(conf->old_x509_store); -+ SSL_CTX_set_cert_store(conf->ctx, new_cert_store); -+#else -+ /* -+ * We do not use SSL_CTX_set_cert_store() call here because -+ * we are not sure that old X509 store is not in the use by some -+ * thread (i.e. cert check in progress). -+ * Keep it allocated till next store replacement. -+ */ -+ conf->old_x509_store = conf->ctx->cert_store; -+ conf->ctx->cert_store = new_cert_store; -+#endif -+ conf->ca_path_last_reload = request->timestamp; -+ } -+ } -+ pthread_mutex_unlock(&conf->mutex); -+ } - - new_tls = SSL_new(conf->ctx); - if (new_tls == NULL) { -@@ -594,11 +726,33 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU - return NULL; - } - -+#ifdef TLS1_3_VERSION -+ /* -+ * Disallow TLS 1.3 for FAST. -+ * -+ * We need another magic configuration option to allow -+ * it. -+ */ -+ if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) { -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ WARN("!! FORCING MAXIMUM TLS VERSION TO TLS 1.2 !!"); -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ WARN("!! There is no standard for using this EAP method with TLS 1.3"); -+ WARN("!! Please set tls_max_version = \"1.2\""); -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ -+ if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) { -+ tls_error_log(request, "Failed limiting maximum version to TLS 1.2"); -+ return NULL; -+ } -+ } -+#endif -+ - /* We use the SSL's "app_data" to indicate a call-back */ - SSL_set_app_data(new_tls, NULL); - - if ((state = talloc_zero(ctx, tls_session_t)) == NULL) { -- RERROR("Error allocating memory for SSL state"); -+ RERROR("(TLS) Error allocating memory for SSL state"); - return NULL; - } - session_init(state); -@@ -606,6 +760,14 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU - - state->ctx = conf->ctx; - state->ssl = new_tls; -+ state->conf = conf; -+ -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) -+ /* -+ * Set the keylog file if the admin requested it. -+ */ -+ if (getenv("SSLKEYLOGFILE") != NULL) SSL_CTX_set_keylog_callback(state->ctx, tls_keylog_cb); -+#endif - - /* - * Initialize callbacks -@@ -637,6 +799,85 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU - SSL_set_msg_callback_arg(new_tls, state); - SSL_set_info_callback(new_tls, cbtls_info); - -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ /* -+ * Allow policies to load context-specific certificate chains. -+ */ -+ vp = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_FILE, 0, TAG_ANY); -+ if (vp) { -+ VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY); -+ if (!key) key = vp; -+ -+ RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue); -+ -+ if (conf->realms) { -+ fr_realm_ctx_t my_r, *r; -+ -+ /* -+ * Use a pre-existing SSL CTX, if -+ * available. Note that due to OpenSSL -+ * issues, this really changes only the -+ * certificate files, and leaves all -+ * other fields alone. e.g. you can't -+ * select a different TLS version. -+ * -+ * This is fine for our purposes in v3. -+ * Due to how we build them, the various -+ * additional SSL_CTXs are identical to -+ * the main one, except for certs. -+ */ -+ my_r.name = vp->vp_strvalue; -+ r = fr_hash_table_finddata(conf->realms, &my_r); -+ if (r) { -+ (void) SSL_set_SSL_CTX(state->ssl, r->ctx); -+ goto after_chain; -+ } -+ -+ /* -+ * Else fall through to trying to dynamically load the certs. -+ */ -+ } -+ -+ if (conf->file_type) { -+ if (SSL_use_certificate_chain_file(state->ssl, vp->vp_strvalue) != 1) { -+ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", -+ vp->vp_strvalue); -+ error: -+ talloc_free(state); -+ return NULL; -+ } -+ } else { -+ if (SSL_use_certificate_file(state->ssl, vp->vp_strvalue, SSL_FILETYPE_ASN1) != 1) { -+ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", -+ vp->vp_strvalue); -+ goto error; -+ } -+ } -+ -+ /* -+ * Note that there is either no password, or it -+ * has to be the same as what's in the -+ * configuration. -+ * -+ * There is just no additional security to -+ * putting a password into the same file system -+ * as the private key. -+ */ -+ if (SSL_use_PrivateKey_file(state->ssl, key->vp_strvalue, SSL_FILETYPE_PEM) != 1) { -+ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", -+ key->vp_strvalue); -+ goto error; -+ } -+ -+ if (SSL_check_private_key(state->ssl) != 1) { -+ tls_error_log(request, "Failed validating TLS session certificate \"%s\"", -+ vp->vp_strvalue); -+ goto error; -+ } -+ } -+after_chain: -+#endif -+ - /* - * In Server mode we only accept. - */ -@@ -646,7 +887,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU - * Verify the peer certificate, if asked. - */ - if (client_cert) { -- RDEBUG2("Setting verify mode to require certificate from client"); -+ RDEBUG2("(TLS) Setting verify mode to require certificate from client"); - verify_mode = SSL_VERIFY_PEER; - verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - verify_mode |= SSL_VERIFY_CLIENT_ONCE; -@@ -670,10 +911,41 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU - * just too much. - */ - state->mtu = conf->fragment_size; -+#define EAP_TLS_MAGIC_OVERHEAD (63) -+ -+ /* -+ * If the packet contains an MTU, then use that. We -+ * trust the admin! -+ */ - vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY); -- if (vp && (vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { -- state->mtu = vp->vp_integer; -+ if (vp) { -+ if ((vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { -+ state->mtu = vp->vp_integer; -+ } -+ -+ } else if (request->parent) { -+ /* -+ * If there's a parent request, we look for what -+ * MTU was set there. Then, we use an MTU which -+ * accounts for the extra overhead of nesting EAP -+ * + TLS inside of EAP + TLS. -+ */ -+ vp = fr_pair_find_by_num(request->parent->state, PW_FRAMED_MTU, 0, TAG_ANY); -+ if (vp && (vp->vp_integer > (100 + EAP_TLS_MAGIC_OVERHEAD)) && (vp->vp_integer <= state->mtu)) { -+ state->mtu = vp->vp_integer - EAP_TLS_MAGIC_OVERHEAD; -+ } -+ } -+ -+ /* -+ * Cache / update the Framed-MTU in the session-state -+ * list. -+ */ -+ vp = fr_pair_find_by_num(request->state, PW_FRAMED_MTU, 0, TAG_ANY); -+ if (!vp) { -+ vp = fr_pair_afrom_num(request->state_ctx, PW_FRAMED_MTU, 0); -+ fr_pair_add(&request->state, vp); - } -+ if (vp) vp->vp_integer = state->mtu; - - if (conf->session_cache_enable) state->allow_session_resumption = true; /* otherwise it's false */ - -@@ -697,12 +969,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) - { - int err; - -- if (ssn->invalid_hb_used) return 0; -+ if (ssn->invalid_hb_used) { -+ REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection"); -+ return 0; -+ } - - if (ssn->dirty_in.used > 0) { - err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used); - if (err != (int) ssn->dirty_in.used) { -- REDEBUG("TLS - Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); -+ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); - record_init(&ssn->dirty_in); - return 0; - } -@@ -716,24 +991,26 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) - return 1; - } - -- if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)")) return 0; -+ if (!tls_error_io_log(request, ssn, err, "Failed reading from OpenSSL")) return 0; - - /* Some Extra STATE information for easy debugging */ - if (!ssn->is_init_finished && SSL_is_init_finished(ssn->ssl)) { - VALUE_PAIR *vp; - char const *str_version; - -- RDEBUG2("TLS - Connection Established"); -+ RDEBUG2("(TLS) Connection Established"); - ssn->is_init_finished = true; - - vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0); - if (vp) { - fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(SSL_get_current_cipher(ssn->ssl))); - fr_pair_add(&request->state, vp); -+ RINDENT(); - rdebug_pair(L_DBG_LVL_2, request, vp, NULL); -+ REXDENT(); - } - -- switch (ssn->info.version) { -+ switch (SSL_version(ssn->ssl)) { - case SSL2_VERSION: - str_version = "SSL 2.0"; - break; -@@ -767,13 +1044,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) - if (vp) { - fr_pair_value_strcpy(vp, str_version); - fr_pair_add(&request->state, vp); -+ RINDENT(); - rdebug_pair(L_DBG_LVL_2, request, vp, NULL); -+ REXDENT(); - } - } -- else if (SSL_in_init(ssn->ssl)) { RDEBUG2("TLS - In Handshake Phase"); } -- else if (SSL_in_before(ssn->ssl)) { RDEBUG2("TLS - Before Handshake Phase"); } -- else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("TLS - In Accept mode"); } -- else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("TLS - In Connect mode"); } -+ else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); } -+ else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); } -+ else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); } -+ else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); } - - #if OPENSSL_VERSION_NUMBER >= 0x10001000L - /* -@@ -791,7 +1070,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) - * to get the session is a hard fail. - */ - if (!ssn->ssl_session && ssn->is_init_finished) { -- RDEBUG("TLS - Failed getting session"); -+ RDEBUG("(TLS) Failed getting session"); - return 0; - } - } -@@ -805,25 +1084,25 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) - err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, - sizeof(ssn->dirty_out.data)); - if (err > 0) { -- RDEBUG2("TLS - got %d bytes of data", err); -+ RDEBUG3("(TLS) got %d bytes of data", err); - ssn->dirty_out.used = err; - - } else if (BIO_should_retry(ssn->from_ssl)) { - record_init(&ssn->dirty_in); -- RDEBUG2("TLS - Asking for more data in tunnel."); -+ RDEBUG2("(TLS) Asking for more data in tunnel."); - return 1; - - } else { -- tls_error_log(NULL, "Error reading from SSL BIO"); -+ tls_error_log(NULL, "Error reading from OpenSSL"); - record_init(&ssn->dirty_in); -- RDEBUG2("TLS - Tunnel data is established."); - return 0; - } - } else { -- -- RDEBUG2("TLS - Application data."); -- /* Its clean application data, do whatever we want */ -+ RDEBUG2("(TLS) Application data."); -+ /* Its clean application data, leave whatever is in the buffer */ -+#if 0 - record_init(&ssn->clean_out); -+#endif - } - - /* We are done with dirty_in, reinitialize it */ -@@ -855,13 +1134,12 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn) - record_minus(&ssn->clean_in, NULL, written); - - /* Get the dirty data from Bio to send it */ -- err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, -- sizeof(ssn->dirty_out.data)); -+ err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used, -+ sizeof(ssn->dirty_out.data) - ssn->dirty_out.used); - if (err > 0) { -- ssn->dirty_out.used = err; -+ ssn->dirty_out.used += err; - } else { -- if (!tls_error_io_log(request, ssn, err, -- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) { -+ if (!tls_error_io_log(request, ssn, err, "Failed writing to OpenSSL")) { - return 0; - } - } -@@ -963,7 +1241,10 @@ void tls_session_information(tls_session_t *tls_session) - { - char const *str_write_p, *str_version, *str_content_type = ""; - char const *str_details1 = "", *str_details2= ""; -+ char const *details = NULL; - REQUEST *request; -+ VALUE_PAIR *vp; -+ char content_type[16], alert_buf[16]; - char buffer[32]; - - /* -@@ -972,9 +1253,20 @@ void tls_session_information(tls_session_t *tls_session) - */ - if (rad_debug_lvl == 0) return; - -- str_write_p = tls_session->info.origin ? ">>> send" : "<<< recv"; -+ /* -+ * OpenSSL calls this function with 'pseudo' content -+ * types. The user doesn't care about them, so suppress them. -+ */ -+ if (tls_session->info.content_type > UINT8_MAX) return; -+ -+ request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); -+ if (!request) return; -+ -+ str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv"; -+ -+#define FROM_CLIENT (tls_session->info.origin == 0) - -- switch (tls_session->info.version) { -+ switch (SSL_version(tls_session->ssl)) { - case SSL2_VERSION: - str_version = "SSL 2.0 "; - break; -@@ -1001,13 +1293,12 @@ void tls_session_information(tls_session_t *tls_session) - #endif - - default: -- sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", tls_session->info.version); -+ sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", SSL_version(tls_session->ssl)); - str_version = buffer; - break; - } - -- if (tls_session->info.version == SSL3_VERSION || -- tls_session->info.version == TLS1_VERSION) { -+ if (1) { - switch (tls_session->info.content_type) { - case SSL3_RT_CHANGE_CIPHER_SPEC: - str_content_type = "ChangeCipherSpec"; -@@ -1026,7 +1317,8 @@ void tls_session_information(tls_session_t *tls_session) - break; - - default: -- str_content_type = "UnknownContentType"; -+ snprintf(content_type, sizeof(content_type), "content=%d", tls_session->info.content_type); -+ str_content_type = content_type; - break; - } - -@@ -1045,9 +1337,12 @@ void tls_session_information(tls_session_t *tls_session) - } - - str_details2 = " ???"; -+ details = "there is a failure inside the TLS protocol exchange"; -+ - switch (tls_session->info.alert_description) { - case SSL3_AD_CLOSE_NOTIFY: - str_details2 = " close_notify"; -+ details = "the connection has been closed, and no further TLS exchanges will take place"; - break; - - case SSL3_AD_UNEXPECTED_MESSAGE: -@@ -1074,24 +1369,34 @@ void tls_session_information(tls_session_t *tls_session) - str_details2 = " handshake_failure"; - break; - -+ case SSL3_AD_NO_CERTIFICATE: -+ str_details2 = " no_certificate"; -+ details = "the server did not present a certificate to the client"; -+ break; -+ - case SSL3_AD_BAD_CERTIFICATE: - str_details2 = " bad_certificate"; -+ details = "it believes the server certificate is invalid or malformed"; - break; - - case SSL3_AD_UNSUPPORTED_CERTIFICATE: - str_details2 = " unsupported_certificate"; -+ details = "it does not understand the certificate presented by the server"; - break; - - case SSL3_AD_CERTIFICATE_REVOKED: - str_details2 = " certificate_revoked"; -+ details = "it believes that the server certificate has been revoked"; - break; - - case SSL3_AD_CERTIFICATE_EXPIRED: - str_details2 = " certificate_expired"; -+ details = "it believes that the server certificate has expired. Either renew the server certificate, or check the time on the client"; - break; - - case SSL3_AD_CERTIFICATE_UNKNOWN: - str_details2 = " certificate_unknown"; -+ details = "it does not recognize the server certificate"; - break; - - case SSL3_AD_ILLEGAL_PARAMETER: -@@ -1100,6 +1405,7 @@ void tls_session_information(tls_session_t *tls_session) - - case TLS1_AD_UNKNOWN_CA: - str_details2 = " unknown_ca"; -+ details = "it does not recognize the CA used to issue the server certificate. Please update the client so that it knows about the CA"; - break; - - case TLS1_AD_ACCESS_DENIED: -@@ -1120,6 +1426,18 @@ void tls_session_information(tls_session_t *tls_session) - - case TLS1_AD_PROTOCOL_VERSION: - str_details2 = " protocol_version"; -+ details = "the client does not accept the version of TLS negotiated by the server"; -+ -+#ifdef TLS1_3_VERSION -+ /* -+ * Complain about OpenSSL bugs. -+ */ -+ if ((SSL_version(tls_session->ssl) > tls_session->conf->max_version) && -+ (rad_debug_lvl > 0)) { -+ WARN("TLS 1.3 has been negotiated even though it was disabled. This is an OpenSSL Bug."); -+ WARN("Please set: cipher_list = \"DEFAULT@SECLEVEL=1\" in the tls {...} section."); -+ } -+#endif - break; - - case TLS1_AD_INSUFFICIENT_SECURITY: -@@ -1137,12 +1455,69 @@ void tls_session_information(tls_session_t *tls_session) - case TLS1_AD_NO_RENEGOTIATION: - str_details2 = " no_renegotiation"; - break; -+ -+#ifdef TLS13_AD_MISSING_EXTENSIONS -+ case TLS13_AD_MISSING_EXTENSIONS: -+ str_details2 = " missing_extensions"; -+ details = "the server did not present a TLS extension which the client expected to be present. Please check the TLS libraries on the client and server for compatibility"; -+ break; -+#endif -+ -+#ifdef TLS13_AD_CERTIFICATE_REQUIRED -+ case TLS13_AD_CERTIFICATE_REQUIRED: -+ str_details2 = " certificate_required"; -+ details = "the server did not present a certificate"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_UNSUPPORTED_EXTENSION -+ case TLS1_AD_UNSUPPORTED_EXTENSION: -+ str_details2 = " unsupported_extension"; -+ details = "the server has sent a TLS message which the client does not recognize. Please check the TLS libraries on the client and server for compatibility"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_CERTIFICATE_UNOBTAINABLE -+ case TLS1_AD_CERTIFICATE_UNOBTAINABLE: -+ str_details2 = " certificate_unobtainable"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_UNRECOGNIZED_NAME -+ case TLS1_AD_UNRECOGNIZED_NAME: -+ str_details2 = " unrecognized_name"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE -+ case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE: -+ str_details2 = " bad_certificate_status_response"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_BAD_CERTIFICATE_HASH_VALUE -+ case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE: -+ str_details2 = " bad_certificate_hash_value"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_UNKNOWN_PSK_IDENTITY -+ case TLS1_AD_UNKNOWN_PSK_IDENTITY: -+ str_details2 = " unknown_psk_identity"; -+ break; -+#endif -+ -+#ifdef TLS1_AD_NO_APPLICATION_PROTOCOL -+ case TLS1_AD_NO_APPLICATION_PROTOCOL: -+ str_details2 = " no_application_protocol"; -+ break; -+#endif - } - } - } - - if (tls_session->info.content_type == SSL3_RT_HANDSHAKE) { -- str_details1 = "???"; -+ str_details1 = ""; - - if (tls_session->info.record_len > 0) switch (tls_session->info.handshake_type) { - case SSL3_MT_HELLO_REQUEST: -@@ -1157,6 +1532,18 @@ void tls_session_information(tls_session_t *tls_session) - str_details1 = ", ServerHello"; - break; - -+#ifdef SSL3_MT_NEWSESSION_TICKET -+ case SSL3_MT_NEWSESSION_TICKET: -+ str_details1 = ", NewSessionTicket"; -+ break; -+#endif -+ -+#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS -+ case SSL3_MT_ENCRYPTED_EXTENSIONS: -+ str_details1 = ", EncryptedExtensions"; -+ break; -+#endif -+ - case SSL3_MT_CERTIFICATE: - str_details1 = ", Certificate"; - break; -@@ -1184,31 +1571,52 @@ void tls_session_information(tls_session_t *tls_session) - case SSL3_MT_FINISHED: - str_details1 = ", Finished"; - break; -+ -+#ifdef SSL3_MT_KEY_UPDATE -+ case SSL3_MT_KEY_UPDATE: -+ str_content_type = "KeyUpdate"; -+ break; -+#endif -+ -+ default: -+ snprintf(alert_buf, sizeof(alert_buf), ", type=%d", tls_session->info.handshake_type); -+ str_details1 = alert_buf; -+ break; - } - } - } - - snprintf(tls_session->info.info_description, - sizeof(tls_session->info.info_description), -- "%s %s%s [length %04lx]%s%s\n", -+ "%s %s%s%s%s", - str_write_p, str_version, str_content_type, -- (unsigned long)tls_session->info.record_len, - str_details1, str_details2); - -- request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); -- if (!request) return; -+ /* -+ * Cache the TLS session information in the session-state -+ * list, so it can be accessed by Post-Auth-Type -+ * Client-Lost { ... } -+ */ -+ vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_INFORMATION, 0); -+ if (vp) { -+ fr_pair_value_strcpy(vp, tls_session->info.info_description); -+ fr_pair_add(&request->state, vp); -+ } - - RDEBUG2("%s", tls_session->info.info_description); -+ -+ if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details); - } - - static CONF_PARSER cache_config[] = { - { "enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, session_cache_enable), "no" }, - -- { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_timeout), "24" }, -+ { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_lifetime), "24" }, - { "name", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_id_name), NULL }, - - { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_cache_size), "255" }, - { "persist_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_path), NULL }, -+ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_server), NULL }, - CONF_PARSER_TERMINATOR - }; - -@@ -1256,6 +1664,7 @@ static CONF_PARSER tls_server_config[] = { - #ifdef X509_V_FLAG_CRL_CHECK_ALL - { "check_all_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, check_all_crl), "no" }, - #endif -+ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, - { "allow_expired_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, allow_expired_crl), NULL }, - { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, - { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, -@@ -1263,6 +1672,14 @@ static CONF_PARSER tls_server_config[] = { - { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, - { "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, require_client_cert), NULL }, - -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ { "sigalgs_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, sigalgs_list), NULL }, -+#endif -+ -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ { "reject_unknown_intermediate_ca", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disallow_untrusted), .dflt = "no", }, -+#endif -+ - #if OPENSSL_VERSION_NUMBER >= 0x0090800fL - #ifndef OPENSSL_NO_ECDH - { "ecdh_curve", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, ecdh_curve), "prime256v1" }, -@@ -1281,9 +1698,23 @@ static CONF_PARSER tls_server_config[] = { - { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, - #endif - -- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, -+ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, -+ -+ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), -+#if defined(TLS1_2_VERSION) -+ "1.2" -+#elif defined(TLS1_1_VERSION) -+ "1.1" -+#else -+ "1.0" -+#endif -+ }, -+ -+#ifdef WITH_RADIUSV11 -+ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL }, -+#endif - -- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, -+ { "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL }, - - { "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config }, - -@@ -1312,6 +1743,9 @@ static CONF_PARSER tls_client_config[] = { - { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, - { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, - { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, -+ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, -+ -+ { "fix_cert_order", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, fix_cert_order), NULL }, - - #if OPENSSL_VERSION_NUMBER >= 0x0090800fL - #ifndef OPENSSL_NO_ECDH -@@ -1331,9 +1765,23 @@ static CONF_PARSER tls_client_config[] = { - { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, - #endif - -- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, -+ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, -+ -+ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), -+#if defined(TLS1_2_VERSION) -+ "1.2" -+#elif defined(TLS1_1_VERSION) -+ "1.1" -+#else -+ "1.0" -+#endif -+ }, -+ -+#ifdef WITH_RADIUSV11 -+ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL }, -+#endif - -- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, -+ { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL }, - - CONF_PARSER_TERMINATOR - }; -@@ -1347,7 +1795,44 @@ static int load_dh_params(SSL_CTX *ctx, char *file) - DH *dh = NULL; - BIO *bio; - -- if (!file) return 0; -+ /* -+ * Prior to trying to load the file, check what OpenSSL will do with it. -+ * -+ * Certain downstreams (such as RHEL) will ignore user-provided dhparams -+ * in FIPS mode, unless the specified parameters are FIPS-approved. -+ * However, since OpenSSL >= 1.1.1 will automatically select parameters -+ * anyways, there's no point in attempting to load them. -+ * -+ * Change suggested by @t8m -+ */ -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ if (FIPS_mode() > 0) { -+ WARN(LOG_PREFIX ": Ignoring user-selected DH parameters in FIPS mode. Using defaults."); -+ file = NULL; -+ } -+ -+ /* -+ * No dh file, set auto context. -+ */ -+ if (!file) { -+ if (!SSL_CTX_set_dh_auto(ctx, 1)) { -+ ERROR(LOG_PREFIX ": Unable to set DH parameters"); -+ return -1; -+ } -+ -+ return 0; -+ } -+ -+ WARN(LOG_PREFIX ": Setting DH parameters from %s - this is no longer necessary.", file); -+ WARN(LOG_PREFIX ": You should comment out the 'dh_file' configuration item."); -+ -+#else -+ if (!file) { -+ WARN(LOG_PREFIX ": Cannot set DH parameters. DH cipher suites may not work."); -+ return 0; -+ } -+#endif -+ - - if ((bio = BIO_new_file(file, "r")) == NULL) { - ERROR(LOG_PREFIX ": Unable to open DH file - %s", file); -@@ -1422,7 +1907,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - - conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); - if (!conf) { -- RWDEBUG("Failed to find TLS configuration in session"); -+ RWDEBUG("(TLS) Failed to find TLS configuration in session"); - return 0; - } - -@@ -1439,7 +1924,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - blob_len = i2d_SSL_SESSION(sess, NULL); - if (blob_len < 1) { - /* something went wrong */ -- if (request) RWDEBUG("Session serialisation failed, couldn't determine required buffer length"); -+ if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length"); - return 0; - } - -@@ -1447,14 +1932,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - /* alloc and convert to ASN.1 */ - sess_blob = malloc(blob_len); - if (!sess_blob) { -- RWDEBUG("Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); -+ RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); - return 0; - } - /* openssl mutates &p */ - p = sess_blob; - rv = i2d_SSL_SESSION(sess, &p); - if (rv != blob_len) { -- if (request) RWDEBUG("Session serialisation failed"); -+ if (request) RWDEBUG("(TLS) Session serialisation failed"); - goto error; - } - -@@ -1463,7 +1948,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - conf->session_cache_path, FR_DIR_SEP, buffer); - fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR); - if (fd < 0) { -- if (request) RERROR("Session serialisation failed, failed opening session file %s: %s", -+ if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s", - filename, fr_syserror(errno)); - goto error; - } -@@ -1486,7 +1971,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - while (todo > 0) { - rv = write(fd, p, todo); - if (rv < 1) { -- if (request) RWDEBUG("Failed writing session: %s", fr_syserror(errno)); -+ if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno)); - close(fd); - goto error; - } -@@ -1494,7 +1979,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) - todo -= rv; - } - close(fd); -- if (request) RWDEBUG("Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); -+ if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); - } - - error: -@@ -1595,7 +2080,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - - conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); - if (!conf) { -- RWDEBUG("Failed to find TLS configuration in session"); -+ RWDEBUG("(TLS) Failed to find TLS configuration in session"); - return NULL; - } - -@@ -1617,20 +2102,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); - fd = open(filename, O_RDONLY); - if (fd < 0) { -- RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno)); -+ RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno)); - goto error; - } - - rv = fstat(fd, &st); - if (rv < 0) { -- RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); -+ RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); - close(fd); - goto error; - } - - sess_data = talloc_array(NULL, unsigned char, st.st_size); - if (!sess_data) { -- RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); -+ RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); - close(fd); - goto error; - } -@@ -1640,7 +2125,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - while (todo > 0) { - rv = read(fd, q, todo); - if (rv < 1) { -- RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno)); -+ RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno)); - close(fd); - goto error; - } -@@ -1664,7 +2149,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - memcpy(&o, &p, sizeof(o)); - sess = d2i_SSL_SESSION(NULL, o, st.st_size); - if (!sess) { -- RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); -+ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); - goto error; - } - -@@ -1674,7 +2159,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - rv = pairlist_read(talloc_ctx, filename, &pairlist, 1); - if (rv < 0) { - /* not safe to un-persist a session w/o VPs */ -- RWDEBUG("Failed loading persisted VPs for session %s", buffer); -+ RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer); - SSL_SESSION_free(sess); - sess = NULL; - goto error; -@@ -1708,12 +2193,27 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l - if (vp) { - if ((request->timestamp + vp->vp_integer) > expires) { - vp->vp_integer = expires - request->timestamp; -- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", -+ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); - } - } - } - -+ /* -+ * Resumption MUST use the same EAP type as from -+ * the original packet. -+ */ -+ vp = fr_pair_find_by_num(pairlist->reply, PW_EAP_TYPE, 0, TAG_ANY); -+ if (vp) { -+ VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); -+ -+ if (type && (type->vp_integer != vp->vp_integer)) { -+ REDEBUG("Resumption has changed EAP types for session %s", buffer); -+ REDEBUG("Rejecting session due to protocol violations"); -+ goto error; -+ } -+ } -+ - /* move the cached VPs into the session */ - fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &pairlist->reply, 0, 0, TAG_ANY); - -@@ -1733,22 +2233,366 @@ error: - return sess; - } - --#ifdef HAVE_OPENSSL_OCSP_H -- --/** Extract components of OCSP responser URL from a certificate -- * -- * @param[in] cert to extract URL from. -- * @param[out] host_out Portion of the URL (must be freed with free()). -- * @param[out] port_out Port portion of the URL (must be freed with free()). -- * @param[out] path_out Path portion of the URL (must be freed with free()). -- * @param[out] is_https Whether the responder should be contacted using https. -- * @return -- * - 0 if no valid URL is contained in the certificate. -- * - 1 if a URL was found and parsed. -- * - -1 if at least one URL was found, but none could be parsed. -- */ --static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, -- char **path_out, int *is_https) -+static size_t tls_session_id_binary(SSL_SESSION *ssn, uint8_t *buffer, size_t bufsize) -+{ -+#if OPENSSL_VERSION_NUMBER < 0x10001000L -+ size_t size; -+ -+ size = ssn->session_id_length; -+ if (size > bufsize) size = bufsize; -+ -+ memcpy(buffer, ssn->session_id, size); -+ return size; -+#else -+ unsigned int size; -+ uint8_t const *p; -+ -+ p = SSL_SESSION_get_id(ssn, &size); -+ if (size > bufsize) size = bufsize; -+ -+ memcpy(buffer, p, size); -+ return size; -+#endif -+} -+ -+/* -+ * From TLS-Cache-Method -+ * -+ * All of the save / clear / load callbacks are done with any -+ * OpenSSL locks *unlocked*. So says the OpenSSL code. -+ */ -+#define CACHE_SAVE (1) -+#define CACHE_LOAD (2) -+#define CACHE_CLEAR (3) -+#define CACHE_REFRESH (4) -+ -+static REQUEST *cache_init_fake_request(fr_tls_server_conf_t const *conf, SSL_SESSION *sess, SSL *ssl, -+ uint8_t const *data, size_t size) -+{ -+ VALUE_PAIR *vp; -+ REQUEST *fake, *request = NULL; -+ uint8_t buffer[MAX_SESSION_SIZE]; -+ -+ if (sess) { -+ size = tls_session_id_binary(sess, buffer, sizeof(buffer)); -+ data = buffer; -+ } -+ -+ /* -+ * We get called essentially at random by OpenSSL, with -+ * no information other than the session ID. As a -+ * result, we have to manually set up our own request. -+ */ -+ if (ssl) request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); -+ -+ if (request) { -+ fake = request_alloc_fake(request); -+ } else { -+ fake = request_alloc(NULL); -+ fake->packet = rad_alloc(fake, false); -+ fake->reply = rad_alloc(fake, false); -+ } -+ -+ vp = fr_pair_afrom_num(fake->packet, PW_TLS_SESSION_ID, 0); -+ if (!vp) { -+ talloc_free(fake); -+ return NULL; -+ } -+ -+ fr_pair_value_memcpy(vp, data, size); -+ fr_pair_add(&fake->packet->vps, vp); -+ -+ fake->server = conf->session_cache_server; -+ -+ return fake; -+} -+ -+/* -+ * Clear cached data -+ */ -+static void cbtls_cache_clear(SSL_CTX *ctx, SSL_SESSION *sess) -+{ -+ fr_tls_server_conf_t *conf; -+ REQUEST *fake; -+ -+ conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx); -+ if (!conf) { -+ DEBUG(LOG_PREFIX ": Failed to find TLS configuration in session"); -+ return; -+ } -+ -+ /* -+ * Find the SSL ID from the session, and delete it. -+ * -+ * Don't bother with any parent request. We're in a -+ * timer callback, and there is no request available. -+ */ -+ fake = cache_init_fake_request(conf, sess, NULL, NULL, 0); -+ if (!fake) return; -+ -+ /* -+ * Use &request:TLS-Session-Id to clear the cache entry. -+ */ -+ (void) process_post_auth(CACHE_CLEAR, fake); -+ talloc_free(fake); -+ return; -+} -+ -+/* -+ * OpenSSL calls this function in order to save the session -+ * BEFORE it has sent the final TLS success. So our process here -+ * is to say "yes, we saved it", and then do the *actual* saving -+ * after the TLS success has been sent. -+ */ -+static int cbtls_cache_save(UNUSED SSL *ssl, UNUSED SSL_SESSION *sess) -+{ -+ return 0; -+} -+ -+static int cbtls_cache_save_vps(SSL *ssl, SSL_SESSION *sess, VALUE_PAIR *vps) -+{ -+ fr_tls_server_conf_t *conf; -+ VALUE_PAIR *vp; -+ REQUEST *fake = NULL; -+ size_t size, rv; -+ uint8_t *p, *sess_blob = NULL; -+ -+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); -+ if (!conf) return 0; -+ -+ /* -+ * Find the SSL ID from the session, and save it. -+ * -+ * Save anything from the parent request. -+ */ -+ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); -+ if (!fake) return 0; -+ -+ /* find out what length data we need */ -+ size = i2d_SSL_SESSION(sess, NULL); -+ if (size < 1) return 0; -+ -+ /* Do not convert to TALLOC - it's passed to OpenSSL */ -+ /* alloc and convert to ASN.1 */ -+ MEM(sess_blob = malloc(size)); -+ -+ /* openssl mutates &p */ -+ p = sess_blob; -+ rv = i2d_SSL_SESSION(sess, &p); -+ if (rv != size) goto error; -+ -+ vp = fr_pair_afrom_num(fake->state_ctx, PW_TLS_SESSION_DATA, 0); -+ if (!vp) goto error; -+ -+ fr_pair_value_memcpy(vp, sess_blob, size); -+ fr_pair_add(&fake->state, vp); -+ -+ if (vps) fr_pair_add(&fake->reply->vps, fr_pair_list_copy(fake->reply, vps)); -+ -+ /* -+ * Use &request:TLS-Session-Id to save the -+ * &session-state:TLS-Session-Data values. -+ * -+ * The current &reply: list is the list of VPs which -+ * should be cached. -+ * -+ * Any other attributes which need to be saved can be -+ * read from the &outer.reply: list. -+ */ -+ (void) process_post_auth(CACHE_SAVE, fake); -+ -+error: -+ if (fake) talloc_free(fake); -+ free(sess_blob); -+ -+ return 0; -+} -+ -+static int cbtls_cache_refresh(SSL *ssl, SSL_SESSION *sess) -+{ -+ fr_tls_server_conf_t *conf; -+ REQUEST *fake = NULL; -+ -+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); -+ if (!conf) return 0; -+ -+ /* -+ * Find the SSL ID from the session, and save it. -+ * -+ * Save anything from the parent request. -+ */ -+ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); -+ if (!fake) return 0; -+ /* -+ * Use &request:TLS-Session-Id to update the cache -+ * entry so that it doesn't not expire. -+ */ -+ (void) process_post_auth(CACHE_REFRESH, fake); -+ -+ talloc_free(fake); -+ -+ return 0; -+} -+ -+#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) -+static SSL_SESSION *cbtls_cache_load(SSL *ssl, unsigned char *data, int len, int *copy) -+#else -+static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int len, int *copy) -+#endif -+{ -+ fr_tls_server_conf_t *conf; -+ size_t size; -+ uint8_t const *p; -+ VALUE_PAIR *vp, *vps; -+ TALLOC_CTX *talloc_ctx; -+ SSL_SESSION *sess = NULL; -+ REQUEST *fake = NULL; -+ REQUEST *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); -+ char buffer[2 * MAX_SESSION_SIZE + 1]; -+ -+ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); -+ if (!conf) return NULL; -+ -+ rad_assert(request); -+ -+ size = len; -+ if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; -+ -+ if (fr_debug_lvl > 1) { -+ fr_bin2hex(buffer, data, size); -+ RDEBUG2("Peer requested cached session: %s", buffer); -+ } -+ -+ *copy = 0; -+ -+ /* -+ * Take the given SSL ID, and create a fake request. -+ * -+ * Don't bother parenting it from another request. We do -+ * this for a number of reasons. -+ * -+ * One is that rest of the code expects that the VPs will -+ * be added to fr_tls_ex_index_vps. So we don't want to -+ * be poking the request directly, as that will result in -+ * a change of behavior. -+ * -+ * The larger reason is that we do _not_ want to actually -+ * update the reply, until such time as we know that the -+ * user has been authenticated. -+ */ -+ fake = cache_init_fake_request(conf, NULL, NULL, data, size); -+ if (!fake) return 0; -+ -+ /* -+ * Use &request:TLS-Session-Id to load the cached -+ * session. -+ * -+ * The "cache load { ...}" section should put the reply -+ * attributes into the &reply: list, and the -+ * &session-state:TLS-Session-Data attribute. -+ * -+ * Why? Because v4 does it that way, and there aren't -+ * really good reasons for doing it differently. -+ */ -+ (void) process_post_auth(CACHE_LOAD, fake); -+ -+ /* -+ * Enforce client certificate expiration. -+ */ -+ vp = fr_pair_find_by_num(fake->reply->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY); -+ if (vp) { -+ time_t expires; -+ -+ if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { -+ RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror()); -+ SSL_SESSION_free(sess); -+ sess = NULL; -+ goto error; -+ } -+ -+ if (expires <= request->timestamp) { -+ RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer); -+ SSL_SESSION_free(sess); -+ sess = NULL; -+ goto error; -+ } -+ -+ /* -+ * Account for Session-Timeout, if it's available. -+ */ -+ vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); -+ if (vp) { -+ if ((request->timestamp + vp->vp_integer) > expires) { -+ vp->vp_integer = expires - request->timestamp; -+ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", -+ vp->vp_integer); -+ } -+ } -+ } -+ -+ /* -+ * Try to de-serialize the session data. -+ */ -+ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY); -+ if (!vp) { -+ RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer); -+ goto error; -+ } -+ -+ /* -+ * OpenSSL mutates what's passed in, so we assign sess_data to q, -+ * so the value of q gets mutated, and not the value of sess_data. -+ * -+ * We then need a pointer to hold &q, but it can't be const, because -+ * clang complains about lack of consting in nested pointer types. -+ * -+ * So we memcpy the value of that pointer, to one that -+ * does have a const, which we then pass into d2i_SSL_SESSION *sigh*. -+ */ -+ p = vp->vp_octets; -+ sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length); -+ if (!sess) { -+ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); -+ goto error; -+ } -+ -+ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); -+ vps = NULL; -+ -+ /* move the cached VPs into the session */ -+ fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &fake->reply->vps, 0, 0, TAG_ANY); -+ -+ SSL_SESSION_set_ex_data(sess, fr_tls_ex_index_vps, vps); -+ RDEBUG("Successfully restored session %s", buffer); -+ rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:"); -+ -+ /* -+ * The "restore VPs from OpenSSL cache" code is -+ * now in eaptls_process() -+ */ -+ -+error: -+ if (fake) talloc_free(fake); -+ -+ return sess; -+} -+ -+#ifdef HAVE_OPENSSL_OCSP_H -+ -+/** Extract components of OCSP responser URL from a certificate -+ * -+ * @param[in] cert to extract URL from. -+ * @param[out] host_out Portion of the URL (must be freed with free()). -+ * @param[out] port_out Port portion of the URL (must be freed with free()). -+ * @param[out] path_out Path portion of the URL (must be freed with free()). -+ * @param[out] is_https Whether the responder should be contacted using https. -+ * @return -+ * - 0 if no valid URL is contained in the certificate. -+ * - 1 if a URL was found and parsed. -+ * - -1 if at least one URL was found, but none could be parsed. -+ */ -+static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, -+ char **path_out, int *is_https) - { - int i; - bool found_uri = false; -@@ -1811,7 +2655,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue - VALUE_PAIR *vp; - - if (issuer_cert == NULL) { -- RWDEBUG("Could not get issuer certificate"); -+ RWDEBUG("(TLS) Could not get issuer certificate"); - goto skipped; - } - -@@ -1836,7 +2680,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue - /* Reading the libssl src, they do a strdup on the URL, so it could of been const *sigh* */ - OCSP_parse_url(url, &host, &port, &path, &use_ssl); - if (!host || !port || !path) { -- RWDEBUG("ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); -+ RWDEBUG("(TLS) ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); - goto skipped; - } - } else { -@@ -1845,15 +2689,15 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue - ret = ocsp_parse_cert_url(client_cert, &host, &port, &path, &use_ssl); - switch (ret) { - case -1: -- RWDEBUG("ocsp: Invalid URL in certificate. Not doing OCSP"); -+ RWDEBUG("(TLS) ocsp: Invalid URL in certificate. Not doing OCSP"); - break; - - case 0: - if (conf->ocsp_url) { -- RWDEBUG("ocsp: No OCSP URL in certificate, falling back to configured URL"); -+ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate, falling back to configured URL"); - goto use_ocsp_url; - } -- RWDEBUG("ocsp: No OCSP URL in certificate. Not doing OCSP"); -+ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate. Not doing OCSP"); - goto skipped; - - case 1: -@@ -1865,7 +2709,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue - - /* Check host and port length are sane, then create Host: HTTP header */ - if ((strlen(host) + strlen(port) + 2) > sizeof(hostheader)) { -- RWDEBUG("ocsp: Host and port too long"); -+ RWDEBUG("(TLS) ocsp: Host and port too long"); - goto skipped; - } - snprintf(hostheader, sizeof(hostheader), "%s:%s", host, port); -@@ -2038,15 +2882,15 @@ ocsp_end: - vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); - vp->vp_integer = 2; /* skipped */ - if (conf->ocsp_softfail) { -- RWDEBUG("ocsp: Unable to check certificate, assuming it's valid"); -- RWDEBUG("ocsp: This may be insecure"); -+ RWDEBUG("(TLS) ocsp: Unable to check certificate, assuming it's valid"); -+ RWDEBUG("(TLS) ocsp: This may be insecure"); - - /* Remove OpenSSL errors from queue or handshake will fail */ - while (ERR_get_error()); - - ocsp_status = OCSP_STATUS_SKIPPED; - } else { -- REDEBUG("ocsp: Unable to check certificate, failing"); -+ REDEBUG("(TLS) ocsp: Unable to check certificate, failing"); - ocsp_status = OCSP_STATUS_FAILED; - } - break; -@@ -2054,7 +2898,7 @@ ocsp_end: - default: - vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); - vp->vp_integer = 0; /* no */ -- REDEBUG("ocsp: Certificate has been expired/revoked"); -+ REDEBUG("(TLS) ocsp: Certificate has been expired/revoked"); - break; - } - -@@ -2087,6 +2931,10 @@ static char const *cert_attr_names[9][2] = { - #define FR_TLS_SAN_UPN (7) - #define FR_TLS_VALID_SINCE (8) - -+static const char *cert_names[2] = { -+ "client", "server", -+}; -+ - /* - * Before trusting a certificate, you must make sure that the - * certificate is 'valid'. There are several steps that your -@@ -2152,12 +3000,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - - lookup = depth; - -- /* -- * Log client/issuing cert. If there's an error, log -- * issuing cert. -- */ -- if ((lookup > 1) && !my_ok) lookup = 1; -- - /* - * Retrieve the pointer to the SSL of the connection currently treated - * and the application specific data stored into the SSL object. -@@ -2177,14 +3019,37 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - - talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); - -+ /* -+ * Log client/issuing cert. If there's an error, log -+ * issuing cert. -+ * -+ * Inbound: 0 = client, 1 = server (intermediate CA), 2 = issuing CA -+ * Outbound: 0 = server, 2 = issuing CA. -+ * -+ * Our array of certificates uses 0 for client, and 1 for server. We -+ * also ignore subsequent certs. -+ */ -+ if (lookup > 1) { -+ if (!my_ok) lookup = 1; -+ -+ } else if (lookup == 0) { -+ /* -+ * This flag is only set for outbound -+ * connections. And then allows us to remap SSL -+ * offset 0 (server) to our offset 1 (also -+ * server). -+ */ -+ lookup = (SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER) != NULL); -+ } -+ - /* - * Get the Serial Number - */ - buf[0] = '\0'; - sn = X509_get_serialNumber(client_cert); - -- RDEBUG2("TLS - Creating attributes from certificate OIDs"); -- RINDENT(); -+ RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup ]); -+ RINDENT(); - - /* - * For this next bit, we create the attributes *only* if -@@ -2328,8 +3193,14 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - - if (!my_ok) { - char const *p = X509_verify_cert_error_string(err); -- RERROR("SSL says error %d : %s", err, p); -+ RERROR("(TLS) OpenSSL says error %d : %s", err, p); - REXDENT(); -+ -+ /* -+ * Copy certs even on failure so that they can be logged. -+ */ -+ if (certs && request) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); -+ - return my_ok; - } - -@@ -2405,7 +3276,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - fr_bin2hex(value + 2, srcp, asn1len); - } - -- - vp = fr_pair_make(talloc_ctx, certs, attribute, value, T_OP_ADD); - if (!vp) { - RDEBUG3("Skipping %s += '%s'. Please check that both the " -@@ -2446,20 +3316,28 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - - switch (X509_STORE_CTX_get_error(ctx)) { - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: -- RERROR("issuer=%s", issuer); -+ RERROR("(TLS) unable to get issuer certificate for issuer=%s", issuer); - break; - - case X509_V_ERR_CERT_NOT_YET_VALID: -+ RERROR("(TLS) Failed with certificate not yet valid."); -+ break; -+ - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: -- RERROR("notBefore="); -+ RERROR("(TLS) Failed with error in certificate 'not before' field."); - #if 0 - ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert)); - #endif - break; - - case X509_V_ERR_CERT_HAS_EXPIRED: -+ RERROR("(TLS) Failed with certificate has expired."); -+ break; -+ - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: -- RERROR("notAfter="); -+ RERROR("(TLS) Failed with err in certificate 'no after' field.."); -+ break; -+ - #if 0 - ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert)); - #endif -@@ -2471,12 +3349,49 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - * checks. - */ - if (depth == 0) { -+ tls_session_t *ssn = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_SSN); -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ STACK_OF(X509)* untrusted = NULL; -+#endif -+ -+ rad_assert(ssn != NULL); -+ -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+ /* -+ * See if there are any untrusted certificates. -+ * If so, complain about them. -+ */ -+ untrusted = X509_STORE_CTX_get0_untrusted(ctx); -+ if (untrusted) { -+ if (conf->disallow_untrusted || RDEBUG_ENABLED2) { -+ int i; -+ -+ WARN("Certificate chain - %i cert(s) untrusted", -+ X509_STORE_CTX_get_num_untrusted(ctx)); -+ for (i = sk_X509_num(untrusted); i > 0 ; i--) { -+ X509 *this_cert = sk_X509_value(untrusted, i - 1); -+ -+ X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject)); -+ subject[sizeof(subject) - 1] = '\0'; -+ -+ WARN("(TLS) untrusted certificate with depth [%i] subject name %s", -+ i - 1, subject); -+ } -+ } -+ -+ if (conf->disallow_untrusted) { -+ AUTH(LOG_PREFIX ": There are untrusted certificates in the certificate chain. Rejecting."); -+ my_ok = 0; -+ } -+ } -+#endif -+ - /* - * If the conf tells us to, check cert issuer - * against the specified value and fail - * verification if they don't match. - */ -- if (conf->check_cert_issuer && -+ if (my_ok && conf->check_cert_issuer && - (strcmp(issuer, conf->check_cert_issuer) != 0)) { - AUTH(LOG_PREFIX ": Certificate issuer (%s) does not match specified value (%s)!", - issuer, conf->check_cert_issuer); -@@ -2595,45 +3510,54 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) - unlink(filename); - break; - } -+ -+ /* -+ * Track that we've verified the client certificate. -+ */ -+ ssn->client_cert_ok = (my_ok == 1); - } /* depth == 0 */ - -+ /* -+ * Copy certs to request even on failure, so that the -+ * user can log them. -+ */ - if (certs && request && !my_ok) { - fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); - } - - if (RDEBUG_ENABLED3) { -- RDEBUG3("chain-depth : %d", depth); -- RDEBUG3("error : %d", err); -+ RDEBUG3("(TLS) chain-depth : %d", depth); -+ RDEBUG3("(TLS) error : %d", err); - - if (identity) RDEBUG3("identity : %s", *identity); -- RDEBUG3("common name : %s", common_name); -- RDEBUG3("subject : %s", subject); -- RDEBUG3("issuer : %s", issuer); -- RDEBUG3("verify return : %d", my_ok); -+ RDEBUG3("(TLS) common name : %s", common_name); -+ RDEBUG3("(TLS) subject : %s", subject); -+ RDEBUG3("(TLS) issuer : %s", issuer); -+ RDEBUG3("(TLS) verify return : %d", my_ok); - } - - return (my_ok != 0); - } - - --#ifdef HAVE_OPENSSL_OCSP_H - /* -- * Create Global X509 revocation store and use it to verify -- * OCSP responses -+ * Configure a X509 CA store to verify OCSP or client repsonses - * - * - Load the trusted CAs - * - Load the trusted issuer certificates -+ * - Configure CRLs check if needed - */ --static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) -+X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf) - { -- X509_STORE *store = NULL; -+ X509_STORE *store = X509_STORE_new(); - -- store = X509_STORE_new(); -+ if (store == NULL) return NULL; - - /* Load the CAs we trust */ - if (conf->ca_file || conf->ca_path) - if (!X509_STORE_load_locations(store, conf->ca_file, conf->ca_path)) { - tls_error_log(NULL, "Error reading Trusted root CA list \"%s\"", conf->ca_file); -+ X509_STORE_free(store); - return NULL; - } - -@@ -2645,38 +3569,65 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) - if (conf->check_all_crl) - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK_ALL); - #endif -+ -+#if defined(X509_V_FLAG_PARTIAL_CHAIN) -+ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); -+#endif -+ - return store; - } --#endif /* HAVE_OPENSSL_OCSP_H */ - - #if OPENSSL_VERSION_NUMBER >= 0x0090800fL - #ifndef OPENSSL_NO_ECDH - static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use) - { -- int nid; -- EC_KEY *ecdh; -+ if (!disable_single_dh_use) { -+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); -+ } - -- if (!ecdh_curve || !*ecdh_curve) return 0; -+ if (!ecdh_curve) return 0; - -- nid = OBJ_sn2nid(ecdh_curve); -- if (!nid) { -- ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); -- return -1; -- } -+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL -+ /* -+ * A colon-separated list of curves. -+ */ -+ if (*ecdh_curve) { -+ char *list; - -- ecdh = EC_KEY_new_by_curve_name(nid); -- if (!ecdh) { -- ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); -- return -1; -+ memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */ -+ -+ if (SSL_CTX_set1_curves_list(ctx, list) == 0) { -+ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); -+ return -1; -+ } - } - -- SSL_CTX_set_tmp_ecdh(ctx, ecdh); -+ (void) SSL_CTX_set_ecdh_auto(ctx, 1); -+#else -+ /* -+ * Use APIs for older versions of OpenSSL. -+ */ -+ { -+ int nid; -+ EC_KEY *ecdh; - -- if (!disable_single_dh_use) { -- SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); -- } -+ nid = OBJ_sn2nid(ecdh_curve); -+ if (!nid) { -+ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); -+ return -1; -+ } - -- EC_KEY_free(ecdh); -+ ecdh = EC_KEY_new_by_curve_name(nid); -+ if (!ecdh) { -+ ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); -+ return -1; -+ } -+ -+ SSL_CTX_set_tmp_ecdh(ctx, ecdh); -+ -+ EC_KEY_free(ecdh); -+ } -+#endif - - return 0; - } -@@ -2708,10 +3659,32 @@ int tls_global_init(bool spawn_flag, bool check) - * and we don't want to have tls.c depend on globals. - */ - if (spawn_flag && !check && (tls_mutexes_init() < 0)) { -- ERROR("FATAL: Failed to set up SSL mutexes"); -+ ERROR("(TLS) FATAL: Failed to set up SSL mutexes"); -+ return -1; -+ } -+ -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ /* -+ * Load the default provider for most algorithms -+ */ -+ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); -+ if (!openssl_default_provider) { -+ ERROR("(TLS) Failed loading default provider"); - return -1; - } - -+ /* -+ * Needed for MD4 -+ * -+ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms -+ */ -+ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); -+ if (!openssl_legacy_provider) { -+ ERROR("(TLS) Failed loading legacy provider"); -+ return -1; -+ } -+#endif -+ - return 0; - } - -@@ -2777,6 +3750,19 @@ void tls_global_cleanup(void) - #ifndef OPENSSL_NO_ENGINE - ENGINE_cleanup(); - #endif -+ -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { -+ ERROR("Failed unloading default provider"); -+ } -+ openssl_default_provider = NULL; -+ -+ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { -+ ERROR("Failed unloading legacy provider"); -+ } -+ openssl_legacy_provider = NULL; -+#endif -+ - CONF_modules_unload(1); - ERR_free_strings(); - EVP_cleanup(); -@@ -2797,9 +3783,6 @@ static const FR_NAME_NUMBER version2int[] = { - #endif - #ifdef TLS1_3_VERSION - { "1.3", TLS1_3_VERSION }, --#endif --#ifdef TLS1_4_VERSION -- { "1.4", TLS1_4_VERSION }, - #endif - { NULL, 0 } - }; -@@ -2816,18 +3799,18 @@ static const FR_NAME_NUMBER version2int[] = { - * - Load the Private key & the certificate - * - Set the Context options & Verify options - */ --SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) -+SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file) - { - SSL_CTX *ctx; - X509_STORE *certstore; - int verify_mode = SSL_VERIFY_NONE; -- int ctx_options = 0; -- int ctx_tls_versions = 0; -+ int ctx_options = 0, ctx_available = 0; - int type; - #ifdef CHECK_FOR_PSK_CERTS - bool psk_and_certs = false; - #endif -- bool insecure_tls_version = false; -+ int min_version; -+ int max_version; - - /* - * SHA256 is in all versions of OpenSSL, but isn't -@@ -2840,7 +3823,7 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) - - ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods". Idiots. */ - if (!ctx) { -- tls_error_log(NULL, "Failed creating TLS context"); -+ tls_error_log(NULL, "Failed creating OpenSSL context"); - return NULL; - } - -@@ -3033,39 +4016,56 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) - * the cert chain needs to be given in PEM from - * openSSL.org - */ -- if (!conf->certificate_file) goto load_ca; -+ if (!chain_file) chain_file = conf->certificate_file; -+ if (!chain_file) goto load_ca; - - if (type == SSL_FILETYPE_PEM) { -- if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) { -+ if (!(SSL_CTX_use_certificate_chain_file(ctx, chain_file))) { - tls_error_log(NULL, "Failed reading certificate file \"%s\"", -- conf->certificate_file); -+ chain_file); - return NULL; - } - -- } else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) { -+ } else if (!(SSL_CTX_use_certificate_file(ctx, chain_file, type))) { - tls_error_log(NULL, "Failed reading certificate file \"%s\"", -- conf->certificate_file); -+ chain_file); - return NULL; - } - -- /* Load the CAs we trust */ - load_ca: -+ /* -+ * Load the CAs we trust and configure CRL checks if needed -+ */ -+ if (conf->ca_file || conf->ca_path) { -+ if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL; -+ SSL_CTX_set_cert_store(ctx, certstore); -+ } else { - #if defined(X509_V_FLAG_PARTIAL_CHAIN) -- X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN); -+ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN); - #endif -- if (conf->ca_file || conf->ca_path) { -- if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) { -- tls_error_log(NULL, "Failed reading Trusted root CA list \"%s\"", -- conf->ca_file); -- return NULL; -- } - } -+ - if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file)); - -- if (conf->private_key_file) { -- if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) { -+ conf->ca_path_last_reload = time(NULL); -+ conf->old_x509_store = NULL; -+ -+ /* -+ * Disable reloading of cert store if we're not using CA path -+ */ -+ if (!conf->ca_path) conf->ca_path_reload_interval = 0; -+ -+ if (conf->ca_path_reload_interval > 0 && conf->ca_path_reload_interval < 300) { -+ DEBUG2("ca_path_reload_interval is set too low, reset it to 300"); -+ conf->ca_path_reload_interval = 300; -+ } -+ -+ /* Load private key */ -+ if (!private_key_file) private_key_file = conf->private_key_file; -+ if (private_key_file) { -+ if (!(SSL_CTX_use_PrivateKey_file(ctx, private_key_file, type))) { - tls_error_log(NULL, "Failed reading private key file \"%s\"", -- conf->private_key_file); -+ private_key_file); - return NULL; - } - -@@ -3088,6 +4088,18 @@ post_ca: - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; - -+ /* -+ * If set then dummy Change Cipher Spec (CCS) messages are sent in -+ * TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2 -+ * so that middleboxes that do not understand TLSv1.3 will not drop -+ * the connection. This isn't needed for EAP-TLS, so we disable it. -+ * -+ * EAP (hopefully) does not have middlebox deployments -+ */ -+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT -+ ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT; -+#endif -+ - /* - * SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0 - * -@@ -3095,168 +4107,291 @@ post_ca: - * below, so we don't need to check for them explicitly. - * - * TLS1_3_VERSION is available in OpenSSL 1.1.1. -- * -- * TLS1_4_VERSION in speculative. - */ -- { -- int min_version = 0; -- int max_version = 0; - -+ /* -+ * Get the max version from the configuration files. -+ */ -+ if (conf->tls_max_version && *conf->tls_max_version) { -+ max_version = fr_str2int(version2int, conf->tls_max_version, 0); -+ if (!max_version) { -+ ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); -+ return NULL; -+ } -+ } else { - /* -- * Get the max version. -+ * Pick the maximum version available at compile -+ * time. - */ -- if (conf->tls_max_version && *conf->tls_max_version) { -- max_version = fr_str2int(version2int, conf->tls_max_version, 0); -- if (!max_version) { -- ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); -- return NULL; -- } -- } else { -- /* -- * Pick the maximum one we know about. -- */ --#ifdef TLS1_4_VERSION -- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */ --#elif defined(TLS1_3_VERSION) -- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */ -+#if defined(TLS1_3_VERSION) -+#ifdef WITH_RADIUSV11 -+ /* -+ * RADIUS 1.1 requires TLS 1.3 or later. -+ */ -+ if (conf->radiusv11) { -+ max_version = TLS1_3_VERSION; -+ } else -+#endif -+ -+ -+ max_version = TLS1_2_VERSION; /* yes, we only use TLS 1.3 if it's EXPLICITELY ENABLED */ - #elif defined(TLS1_2_VERSION) -- max_version = TLS1_2_VERSION; -+ max_version = TLS1_2_VERSION; - #elif defined(TLS1_1_VERSION) -- max_version = TLS1_1_VERSION; -+ max_version = TLS1_1_VERSION; - #else -- max_version = TLS1_VERSION; -+ max_version = TLS1_VERSION; - #endif -- } -+ } - -+ /* -+ * Get the min version from the configuration files. -+ */ -+ if (conf->tls_min_version && *conf->tls_min_version) { -+ min_version = fr_str2int(version2int, conf->tls_min_version, 0); -+ if (!min_version) { -+ ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); -+ return NULL; -+ } -+ } else { -+#ifdef WITH_RADIUSV11 - /* -- * Set these for the rest of the code. -+ * RADIUS 1.1 requires TLS 1.3 or later. - */ --#ifdef TLS1_2_VERSION -- if (max_version < TLS1_2_VERSION) { -- conf->disable_tlsv1_2 = true; -- } --#endif --#ifdef TLS1_1_VERSION -- if (max_version < TLS1_1_VERSION) { -- conf->disable_tlsv1_1 = true; -- } -+ if (conf->radiusv11) { -+ min_version = TLS1_3_VERSION; -+ } else - #endif -- - /* -- * Get the min version. -+ * Allow TLS 1.0. It is horribly insecure, but -+ * some systems still use it. - */ -- if (conf->tls_min_version && *conf->tls_min_version) { -- min_version = fr_str2int(version2int, conf->tls_min_version, 0); -- if (!min_version) { -- ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); -- return NULL; -- } -- } else { -- min_version = TLS1_VERSION; -- } -+ min_version = TLS1_VERSION; -+ } - -- /* -- * Compare the two. -- */ -- if (min_version > max_version) { -- ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", -- conf->tls_min_version, conf->tls_max_version); -- return NULL; -- } -+ /* -+ * Compare the two. -+ */ -+ if ((min_version > max_version) || (max_version < min_version)) { -+ ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", -+ conf->tls_min_version, conf->tls_max_version); -+ return NULL; -+ } - --#if OPENSSL_VERSION_NUMBER >= 0x10100000L - #ifdef CHECK_FOR_PSK_CERTS -- /* -- * Disable TLS 1.3 when using PSKs and certs. -- * This doesn't work. -- * -- * It's best to disable the offending -- * configuration and warn about it. The -- * alternative is to have the admin wonder why it -- * doesn't work. -- * -- * Note that the admin can over-ride this by -- * setting "min_version = max_version = 1.3" -- */ -- if (psk_and_certs && -- (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { -- max_version = TLS1_2_VERSION; -- radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); -- } -+ /* -+ * Disable TLS 1.3 when using PSKs and certs. -+ * This doesn't work. -+ * -+ * It's best to disable the offending -+ * configuration and warn about it. The -+ * alternative is to have the admin wonder why it -+ * doesn't work. -+ * -+ * Note that the admin can over-ride this by -+ * setting "min_version = max_version = 1.3" -+ */ -+ if (psk_and_certs && -+ (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { -+ max_version = TLS1_2_VERSION; -+ radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); -+ } - #endif - -- if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { -- ERROR("Failed setting TLS maximum version"); -- return NULL; -+ /* -+ * No one should be using TLS 1.0 or TLS 1.1 any more -+ * -+ * If TLS1.2 isn't defined by OpenSSL, then we _know_ -+ * it's an insecure version of OpenSSL. -+ */ -+#ifdef TLS1_2_VERSION -+ if (max_version < TLS1_2_VERSION) -+#endif -+ { -+ if (rad_debug_lvl) { -+ WARN(LOG_PREFIX ": The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); -+ WARN(LOG_PREFIX ": Please set: tls_min_version = '1.2'"); - } -+ } - -- if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { -- ERROR("Failed setting TLS minimum version"); -+#ifdef SSL_OP_NO_TLSv1 -+ /* -+ * Check min / max against the old-style "disable" flag. -+ */ -+ if (conf->disable_tlsv1) { -+ if (min_version == TLS1_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'min_version = 1.0'. These cannot both be true."); - return NULL; - } -+ if (max_version == TLS1_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'max_version = 1.0'. These cannot both be true."); -+ return NULL; -+ } -+ ctx_options |= SSL_OP_NO_TLSv1; -+ } - -- /* -- * No one should be using TLS 1.0 or TLS 1.1 any more -- */ -- if (min_version < TLS1_2_VERSION) insecure_tls_version = true; --#else /* OpenSSL version < 1.1.0 */ -+ if (min_version > TLS1_VERSION) ctx_options |= SSL_OP_NO_TLSv1; - --#ifdef SSL_OP_NO_TLSv1 -- insecure_tls_version |= (conf->disable_tlsv1 == false); -+ ctx_available |= SSL_OP_NO_TLSv1; - #endif -+ - #ifdef SSL_OP_NO_TLSv1_1 -- insecure_tls_version |= (conf->disable_tlsv1_1 == false); -+ /* -+ * Check min / max against the old-style "disable" flag. -+ */ -+ if (conf->disable_tlsv1_1) { -+ if (min_version <= TLS1_1_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'min_version <= 1.1'. These cannot both be true."); -+ return NULL; -+ } -+ if (max_version == TLS1_1_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.1'. These cannot both be true."); -+ return NULL; -+ } -+ ctx_options |= SSL_OP_NO_TLSv1_1; -+ } -+ -+ if (min_version > TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; -+ if (max_version < TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; -+ -+ ctx_available |= SSL_OP_NO_TLSv1_1; - #endif --#endif /* OpenSSL version ? 1.1.0 */ - -- if (rad_debug_lvl && insecure_tls_version) { -- WARN("The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); -- WARN("Please set: tls_min_version = \"1.2\""); -+#ifdef SSL_OP_NO_TLSv1_2 -+ /* -+ * Check min / max against the old-style "disable" flag. -+ */ -+ if (conf->disable_tlsv1_2) { -+ if (min_version <= TLS1_2_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'. These cannot both be true."); -+ return NULL; -+ } -+ if (max_version == TLS1_2_VERSION) { -+ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'. These cannot both be true."); -+ return NULL; - } -+ ctx_options |= SSL_OP_NO_TLSv1_2; - } -+ ctx_available |= SSL_OP_NO_TLSv1_2; -+ -+ if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; -+ if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; -+#endif -+ -+#ifdef SSL_OP_NO_TLSv1_3 -+ ctx_available |= SSL_OP_NO_TLSv1_3; -+ if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; -+ if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; -+#endif - -+ -+#ifdef WITH_RADIUSV11 - /* -- * For historical config compatibility, we also allow -- * these, but complain if the admin uses them. -+ * RADIUS 1.1 requires TLS 1.3 or later. - */ --#ifdef SSL_OP_NO_TLSv1 -- if (conf->disable_tlsv1) { -- ctx_options |= SSL_OP_NO_TLSv1; --#if OPENSSL_VERSION_NUMBER >= 0x10100000L -- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1"); -+ if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) { -+ ERROR(LOG_PREFIX ": Please set 'tls_min_version = 1.2' or greater to use 'radiusv1_1 = true'"); -+ return NULL; -+ } - #endif -+ -+ /* -+ * Set the cipher list if we were told to do so. We do -+ * this before setting min/max TLS version. In a sane -+ * world, OpenSSL would error out if we set the max TLS -+ * version to something which was unsupported by the -+ * current security level. However, this is OpenSSL. If -+ * you set conflicting options, it doesn't give an error. -+ * Instead, it just picks something to do. -+ */ -+ if (conf->cipher_list) { -+ if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { -+ tls_error_log(NULL, "Failed setting cipher list"); -+ return NULL; -+ } - } - -- ctx_tls_versions |= SSL_OP_NO_TLSv1; -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ if (conf->sigalgs_list) { -+ char *list; -+ -+ memcpy(&list, &(conf->sigalgs_list), sizeof(list)); /* const issues */ -+ -+ if (SSL_CTX_set1_sigalgs_list(ctx, list) == 0) { -+ tls_error_log(NULL, "Failed setting signature list '%s'", conf->sigalgs_list); -+ return NULL; -+ } -+ } - #endif --#ifdef SSL_OP_NO_TLSv1_1 -- if (conf->disable_tlsv1_1) { -- ctx_options |= SSL_OP_NO_TLSv1_1; --#if OPENSSL_VERSION_NUMBER >= 0x10100000L -- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); -+ -+ /* -+ * Tell OpenSSL PRETTY PLEASE MAY WE USE TLS 1.1. -+ * -+ * Because saying "use TLS 1.1" isn't enough. We have to -+ * send it flowers and cake. -+ */ -+ if (min_version <= TLS1_1_VERSION) { -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ int seclevel = SSL_CTX_get_security_level(ctx); -+ int required;; -+ -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ required = 0; -+#else -+ required = 1; - #endif -- } - -- ctx_tls_versions |= SSL_OP_NO_TLSv1_1; -+ if (seclevel != required) { -+ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=%d\"", required); -+ } -+ -+#else -+ /* -+ * No API to get the security level. Just guess based on the string in the cipher_list. -+ */ -+ if (conf->cipher_list && -+ !strstr(conf->cipher_list, "DEFAULT@SECLEVEL=1")) { -+ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=1\""); -+ } - #endif --#ifdef SSL_OP_NO_TLSv1_2 -+ } - -- if (conf->disable_tlsv1_2) { -- ctx_options |= SSL_OP_NO_TLSv1_2; - #if OPENSSL_VERSION_NUMBER >= 0x10100000L -- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); --#endif -+ if (conf->disable_tlsv1) { -+ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1'"); -+ } -+ if (conf->disable_tlsv1_1) { -+ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_1'"); -+ } -+ if (conf->disable_tlsv1_2) { -+ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_2'"); - } - -- ctx_tls_versions |= SSL_OP_NO_TLSv1_2; -+ ctx_options &= ~(ctx_available); /* clear these flags, as they're not needed. */ - --#endif -+ if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { -+ ERROR("Failed setting TLS maximum version"); -+ return NULL; -+ } -+ if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { -+ ERROR("Failed setting TLS minimum version"); -+ return NULL; -+ } -+#endif /* OpenSSL version < 1.1.0 */ - -- if ((ctx_options & ctx_tls_versions) == ctx_tls_versions) { -+ if ((ctx_options & ctx_available) == ctx_available) { - ERROR(LOG_PREFIX ": You have disabled all available TLS versions. EAP will not work"); - return NULL; - } - -+ /* -+ * Cache min / max TLS version so that we can -+ * programatically disable TLS 1.3 for TTLS, PEAP, and -+ * FAST. -+ */ -+ conf->min_version = min_version; -+ conf->max_version = max_version; -+ - #ifdef SSL_OP_NO_TICKET - ctx_options |= SSL_OP_NO_TICKET; - #endif -@@ -3291,6 +4426,19 @@ post_ca: - - SSL_CTX_set_options(ctx, ctx_options); - -+ /* -+ * TLS 1.3 introduces the concept of early data (also known as zero -+ * round trip data or 0-RTT data). Early data allows a client to send -+ * data to a server in the first round trip of a connection, without -+ * waiting for the TLS handshake to complete if the client has spoken -+ * to the same server recently. This doesn't work for EAP, so we -+ * disable early data. -+ * -+ */ -+#if OPENSSL_VERSION_NUMBER >= 0x10101000L -+ SSL_CTX_set_max_early_data(ctx, 0); -+#endif -+ - /* - * TODO: Set the RSA & DH - * SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa); -@@ -3336,12 +4484,21 @@ post_ca: - /* - * Cache sessions on disk if requested. - */ -- if (conf->session_cache_path) { -+ if (conf->session_cache_path && *conf->session_cache_path) { - SSL_CTX_sess_set_new_cb(ctx, cbtls_new_session); - SSL_CTX_sess_set_get_cb(ctx, cbtls_get_session); - SSL_CTX_sess_set_remove_cb(ctx, cbtls_remove_session); - } - -+ /* -+ * Or run the cache through a virtual server. -+ */ -+ if (conf->session_cache_server && *conf->session_cache_server) { -+ SSL_CTX_sess_set_new_cb(ctx, cbtls_cache_save); -+ SSL_CTX_sess_set_get_cb(ctx, cbtls_cache_load); -+ SSL_CTX_sess_set_remove_cb(ctx, cbtls_cache_clear); -+ } -+ - SSL_CTX_set_quiet_shutdown(ctx, 1); - if (fr_tls_ex_index_vps < 0) - fr_tls_ex_index_vps = SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL); -@@ -3359,6 +4516,17 @@ post_ca: - } - X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK); - -+#ifdef X509_V_FLAG_USE_DELTAS -+ /* -+ * If set, delta CRLs (if present) are used to -+ * determine certificate status. If not set -+ * deltas are ignored. -+ * -+ * So it's safe to always set this flag. -+ */ -+ X509_STORE_set_flags(certstore, X509_V_FLAG_USE_DELTAS); -+#endif -+ - #ifdef X509_V_FLAG_CRL_CHECK_ALL - if (conf->check_all_crl) - X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK_ALL); -@@ -3389,16 +4557,6 @@ post_ca: - } - #endif - -- /* -- * Set the cipher list if we were told to -- */ -- if (conf->cipher_list) { -- if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { -- tls_error_log(NULL, "Failed setting cipher list"); -- return NULL; -- } -- } -- - /* - * Setup session caching - */ -@@ -3424,9 +4582,9 @@ post_ca: - (unsigned int) strlen(conf->session_context_id)); - - /* -- * Our timeout is in hours, this is in seconds. -+ * Our lifetime is in hours, this is in seconds. - */ -- SSL_CTX_set_timeout(ctx, conf->session_timeout * 3600); -+ SSL_CTX_set_timeout(ctx, conf->session_lifetime * 3600); - - /* - * Set the maximum number of entries in the -@@ -3468,11 +4626,15 @@ static int _tls_server_conf_free(fr_tls_server_conf_t *conf) - - if (conf->cache_ht) fr_hash_table_free(conf->cache_ht); - -+ pthread_mutex_destroy(&conf->mutex); -+ - #ifdef HAVE_OPENSSL_OCSP_H - if (conf->ocsp_store) X509_STORE_free(conf->ocsp_store); - conf->ocsp_store = NULL; - #endif - -+ if (conf->realms) fr_hash_table_free(conf->realms); -+ - #ifndef NDEBUG - memset(conf, 0, sizeof(*conf)); - #endif -@@ -3505,9 +4667,109 @@ static int store_cmp(void const *a, void const *b) - DICT_ATTR const *one = a; - DICT_ATTR const *two = b; - -- return one - two; -+ return (one < two) - (one > two); -+} -+ -+static uint32_t realm_hash(void const *data) -+{ -+ fr_realm_ctx_t const *r = data; -+ -+ return fr_hash_string(r->name); -+} -+ -+static int realm_cmp(void const *a, void const *b) -+{ -+ fr_realm_ctx_t const *one = a; -+ fr_realm_ctx_t const *two = b; -+ -+ return strcmp(one->name, two->name); - } - -+static void realm_free(void *data) -+{ -+ fr_realm_ctx_t *r = data; -+ -+ SSL_CTX_free(r->ctx); -+} -+ -+static int tls_realms_load(fr_tls_server_conf_t *conf) -+{ -+ fr_hash_table_t *ht; -+ DIR *dir; -+ struct dirent *dp; -+ char buffer[PATH_MAX]; -+ char buffer2[PATH_MAX]; -+ -+ ht = fr_hash_table_create(realm_hash, realm_cmp, realm_free); -+ if (!ht) return -1; -+ -+ dir = opendir(conf->realm_dir); -+ if (!dir) { -+ ERROR("Error reading directory %s: %s", conf->realm_dir, fr_syserror(errno)); -+ error: -+ if (dir) closedir(dir); -+ fr_hash_table_free(ht); -+ return -1; -+ } -+ -+ /* -+ * Read only the PEM files -+ */ -+ while ((dp = readdir(dir)) != NULL) { -+ char *p; -+ struct stat stat_buf; -+ SSL_CTX *ctx; -+ fr_realm_ctx_t *r; -+ char const *private_key_file = buffer; -+ -+ if (dp->d_name[0] == '.') continue; -+ -+ p = strrchr(dp->d_name, '.'); -+ if (!p) continue; -+ -+ if (memcmp(p, ".pem", 5) != 0) continue; /* must END in .pem */ -+ -+ snprintf(buffer, sizeof(buffer), "%s/%s", conf->realm_dir, dp->d_name); /* ignore directories */ -+ if ((stat(buffer, &stat_buf) != 0) || -+ S_ISDIR(stat_buf.st_mode)) continue; -+ -+ strcpy(buffer2, buffer); -+ p = strchr(buffer2, '.'); /* which must be there... */ -+ if (!p) continue; -+ -+ /* -+ * If there's a key file, then use that. -+ * Otherwise assume that the private key is in -+ * the chain file. -+ */ -+ strcpy(p, ".key"); -+ if (stat(buffer2, &stat_buf) != 0) private_key_file = buffer2; -+ -+ ctx = tls_init_ctx(conf, 1, buffer, private_key_file); -+ if (!ctx) goto error; -+ -+ r = talloc_zero(conf, fr_realm_ctx_t); -+ if (!r) { -+ SSL_CTX_free(ctx); -+ goto error; -+ } -+ -+ r->name = talloc_strdup(r, buffer); -+ r->ctx = ctx; -+ -+ if (fr_hash_table_insert(ht, r) < 0) { -+ ERROR("Failed inserting certificate file %s into hash table", buffer); -+ goto error; -+ } -+ } -+ -+ conf->realms = ht; -+ closedir(dir); -+ -+ return 0; -+} -+ -+ - fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) - { - fr_tls_server_conf_t *conf; -@@ -3535,6 +4797,16 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) - */ - if (conf->fragment_size < 100) conf->fragment_size = 100; - -+ /* -+ * Disallow sessions of more than 7 days, as per RFC -+ * 8446. -+ * -+ * Note that we also enforce this on TLS 1.2, etc. -+ * Because there's just no reason to have month-long TLS -+ * sessions. -+ */ -+ if (conf->session_lifetime > (7 * 24)) conf->session_lifetime = 7 * 24; -+ - /* - * Only check for certificate things if we don't have a - * PSK query. -@@ -3563,10 +4835,15 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) - } - } - -+ /* -+ * Initialize configuration mutex -+ */ -+ pthread_mutex_init(&conf->mutex, NULL); -+ - /* - * Initialize TLS - */ -- conf->ctx = tls_init_ctx(conf, 0); -+ conf->ctx = tls_init_ctx(conf, 0, NULL, NULL); - if (conf->ctx == NULL) { - goto error; - } -@@ -3633,10 +4910,11 @@ skip_list: - * Initialize OCSP Revocation Store - */ - if (conf->ocsp_enable) { -- conf->ocsp_store = init_revocation_store(conf); -+ conf->ocsp_store = fr_init_x509_store(conf); - if (conf->ocsp_store == NULL) goto error; - } - #endif /*HAVE_OPENSSL_OCSP_H*/ -+ - { - char *dh_file; - -@@ -3655,7 +4933,7 @@ skip_list: - } - - if (conf->verify_client_cert_cmd && !conf->verify_tmp_dir) { -- ERROR(LOG_PREFIX ": You MUST set the verify directory in order to use verify_client_cmd"); -+ ERROR(LOG_PREFIX ": You MUST set the 'tmpdir' directory in order to use '%s' cmd", conf->verify_client_cert_cmd); - goto error; - } - -@@ -3663,12 +4941,17 @@ skip_list: - /* - * OpenSSL 1.0.1f and 1.0.1g get the MS-MPPE keys wrong. - */ --#if (OPENSSL_VERSION_NUMBER >= 0x10010060L) && (OPENSSL_VERSION_NUMBER < 0x10010060L) -+#if (OPENSSL_VERSION_NUMBER >= 0x1010106L) && (OPENSSL_VERSION_NUMBER <= 0x1010107L) - conf->disable_tlsv1_2 = true; - WARN(LOG_PREFIX ": Disabling TLSv1.2 due to OpenSSL bugs"); - #endif - #endif - -+ /* -+ * Load certificates and private keys from the realm directory. -+ */ -+ if (conf->realm_dir && (tls_realms_load(conf) < 0)) goto error; -+ - /* - * Cache conf in cs in case we're asked to parse this again. - */ -@@ -3703,7 +4986,7 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs) - /* - * Initialize TLS - */ -- conf->ctx = tls_init_ctx(conf, 1); -+ conf->ctx = tls_init_ctx(conf, 1, NULL, NULL); - if (conf->ctx == NULL) { - goto error; - } -@@ -3755,7 +5038,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - * not allowed, - */ - if (SSL_session_reused(ssn->ssl)) { -- RDEBUG("Forcibly stopping session resumption as it is not allowed"); -+ RDEBUG("(TLS) cache - Forcibly stopping session resumption as it is administratively disabled."); - return -1; - } - -@@ -3763,12 +5046,14 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - * Else resumption IS allowed, so we store the - * user data in the cache. - */ -- } else if (!SSL_session_reused(ssn->ssl)) { -+ } else if ((!SSL_session_reused(ssn->ssl)) || ssn->session_not_resumed) { - VALUE_PAIR **certs; - char buffer[2 * MAX_SESSION_SIZE + 1]; - - tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); - -+ RDEBUG("(TLS) cache - Setting up attributes for session resumption"); -+ - vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_USER_NAME, 0, TAG_ANY); - if (vp) fr_pair_add(&vps, vp); - -@@ -3778,6 +5063,9 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_STRIPPED_USER_DOMAIN, 0, TAG_ANY); - if (vp) fr_pair_add(&vps, vp); - -+ vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); -+ if (vp) fr_pair_add(&vps, vp); -+ - vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_CHARGEABLE_USER_IDENTITY, 0, TAG_ANY); - if (vp) fr_pair_add(&vps, vp); - -@@ -3836,7 +5124,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - if (vp) { - if ((request->timestamp + vp->vp_integer) > expires) { - vp->vp_integer = expires - request->timestamp; -- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", -+ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); - } - } -@@ -3858,7 +5146,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - FR_DIR_SEP, buffer); - vp_file = fopen(filename, "w"); - if (vp_file == NULL) { -- RWDEBUG("Could not write session VPs to persistent cache: %s", -+ RWDEBUG("(TLS) Could not write session VPs to persistent cache: %s", - fr_syserror(errno)); - } else { - VALUE_PAIR *prev = NULL; -@@ -3889,6 +5177,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - fprintf(vp_file, "\n"); - fclose(vp_file); - } -+ -+ } else if (conf->session_cache_server) { -+ cbtls_cache_save_vps(ssn->ssl, ssn->ssl_session, vps); -+ - } else { - RDEBUG("Failed to find 'persist_dir' in TLS configuration. Session will not be cached on disk."); - } -@@ -3901,15 +5193,27 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - * Else the session WAS allowed. Copy the cached reply. - */ - } else { -- char buffer[2 * MAX_SESSION_SIZE + 1]; -- -- tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); -+ RDEBUG("(TLS) cache - Refreshing entry for session resumption"); - - /* - * The "restore VPs from OpenSSL cache" code is - * now in eaptls_process() - */ - if (conf->session_cache_path) { -+ char buffer[2 * MAX_SESSION_SIZE + 1]; -+ -+#if OPENSSL_VERSION_NUMBER >= 0x10001000L -+#ifdef TLS1_3_VERSION -+ /* -+ * OpenSSL frees the underlying session out from -+ * under us in TLS 1.3. -+ */ -+ if (SSL_version(ssn->ssl) == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl); -+#endif -+#endif -+ -+ tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); -+ - /* "touch" the cached session/vp file */ - char filename[3 * MAX_SESSION_SIZE + 1]; - -@@ -3921,6 +5225,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) - utime(filename, NULL); - } - -+ if (conf->session_cache_server) { -+ cbtls_cache_refresh(ssn->ssl, ssn->ssl_session); -+ } -+ - /* - * Mark the request as resumed. - */ -@@ -3953,49 +5261,69 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) - err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, - ssn->dirty_in.used); - if (err != (int) ssn->dirty_in.used) { -+ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); - record_init(&ssn->dirty_in); -- RDEBUG("Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); - return FR_TLS_FAIL; - } -+ -+ record_init(&ssn->dirty_in); - } - - /* -- * Clear the dirty buffer now that we are done with it -- * and init the clean_out buffer to store decrypted data -+ * tls_handshake_recv() may read application data. So -+ * don't touch clean_out. But only if the BIO_write() -+ * above didn't do anything. - */ -- record_init(&ssn->dirty_in); -- record_init(&ssn->clean_out); -+ else if (ssn->clean_out.used > 0) { -+ RDEBUG("(TLS) We already have %zd bytes of application data, processing it.", -+ (ssn->clean_out.used)); -+ goto add_certs; -+ } - - /* - * Read (and decrypt) the tunneled data from the - * SSL session, and put it into the decrypted - * data buffer. - */ -- err = SSL_read(ssn->ssl, ssn->clean_out.data, sizeof(ssn->clean_out.data)); -+ err = SSL_read(ssn->ssl, ssn->clean_out.data + ssn->clean_out.used, -+ sizeof(ssn->clean_out.data) - ssn->clean_out.used); - if (err <= 0) { - int code; - -- RDEBUG("SSL_read Error"); -+ RDEBUG3("(TLS) SSL_read Error"); - - code = SSL_get_error(ssn->ssl, err); - switch (code) { - case SSL_ERROR_WANT_READ: -- RDEBUG("Error in fragmentation logic: SSL_WANT_READ"); -+ if (ssn->clean_out.used > 0) { /* just process what application data we have */ -+ err = 0; -+ break; -+ } -+ -+ RDEBUG("(TLS) OpenSSL says that it needs to read more data."); - return FR_TLS_MORE_FRAGMENTS; - - case SSL_ERROR_WANT_WRITE: -- RDEBUG("Error in fragmentation logic: SSL_WANT_WRITE"); -+ if (ssn->clean_out.used > 0) { /* just process what application data we have */ -+ err = 0; -+ break; -+ } -+ -+ REDEBUG("(TLS) Error in fragmentation logic: SSL_WANT_WRITE"); - return FR_TLS_FAIL; - - case SSL_ERROR_NONE: -- RDEBUG2("No application data received. Assuming handshake is continuing..."); -+ RDEBUG2("(TLS) No application data received. Assuming handshake is continuing..."); - err = 0; - break; - -+ case SSL_ERROR_ZERO_RETURN: -+ RDEBUG2("(TLS) Other end closed the TLS tunnel."); -+ return FR_TLS_FAIL; -+ - default: -- REDEBUG("Error in fragmentation logic"); -- tls_error_io_log(request, ssn, err, -- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)"); -+ REDEBUG("(TLS) Error in fragmentation logic - code %d", code); -+ tls_error_io_log(request, ssn, err, "Failed reading application data from OpenSSL"); - return FR_TLS_FAIL; - } - } -@@ -4003,8 +5331,9 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) - /* - * Passed all checks, successfully decrypted data - */ -- ssn->clean_out.used = err; -+ ssn->clean_out.used += err; - -+add_certs: - /* - * Add the certificates to intermediate packets, so that - * the inner tunnel policies can use them. -@@ -4026,27 +5355,33 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) - fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) - { - if (ssn == NULL){ -- REDEBUG("Unexpected ACK received: No ongoing SSL session"); -+ REDEBUG("(TLS) Unexpected ACK received: No ongoing SSL session"); - return FR_TLS_INVALID; - } - if (!ssn->info.initialized) { -- RDEBUG("No SSL info available. Waiting for more SSL data"); -+ RDEBUG("(TLS) No SSL info available. Waiting for more SSL data"); - return FR_TLS_REQUEST; - } - - if ((ssn->info.content_type == handshake) && (ssn->info.origin == 0)) { -- REDEBUG("Unexpected ACK received: We sent no previous messages"); -+ REDEBUG("(TLS) Unexpected ACK received: We sent no previous messages"); - return FR_TLS_INVALID; - } - - switch (ssn->info.content_type) { - case alert: -- RDEBUG2("Peer ACKed our alert"); -+ RDEBUG2("(TLS) Peer ACKed our alert"); - return FR_TLS_FAIL; - - case handshake: -- if ((ssn->is_init_finished) && (ssn->dirty_out.used == 0)) { -- RDEBUG2("Peer ACKed our handshake fragment. handshake is finished"); -+ if (ssn->dirty_out.used > 0) { -+ RDEBUG2("(TLS) Peer ACKed our handshake fragment"); -+ /* Fragmentation handler, send next fragment */ -+ return FR_TLS_REQUEST; -+ } -+ -+ if (ssn->is_init_finished || SSL_is_init_finished(ssn->ssl)) { -+ RDEBUG2("(TLS) Peer ACKed our handshake fragment. handshake is finished"); - - /* - * From now on all the content is -@@ -4057,12 +5392,11 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) - return FR_TLS_SUCCESS; - } /* else more data to send */ - -- RDEBUG2("Peer ACKed our handshake fragment"); -- /* Fragmentation handler, send next fragment */ -- return FR_TLS_REQUEST; -+ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); -+ return FR_TLS_FAIL; - - case application_data: -- RDEBUG2("Peer ACKed our application data fragment"); -+ RDEBUG2("(TLS) Peer ACKed our application data fragment"); - return FR_TLS_REQUEST; - - /* -@@ -4070,7 +5404,7 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) - * to the default section below. - */ - default: -- REDEBUG("Invalid ACK received: %d", ssn->info.content_type); -+ REDEBUG("(TLS) Invalid ACK received: %d", ssn->info.content_type); - return FR_TLS_INVALID; - } - } -diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c -index 0eed87b64f..6d954d269f 100644 ---- a/src/main/tls_listen.c -+++ b/src/main/tls_listen.c -@@ -81,54 +81,96 @@ static void tls_socket_close(rad_listen_t *listener) - /* - * Tell the event handler that an FD has disappeared. - */ -- DEBUG("Client has closed connection"); -+ DEBUG("(TLS) Closing connection"); - radius_update_listener(listener); - - /* -- * Do NOT free the listener here. It's in use by -+ * Do NOT free the listener here. It may be in use by - * a request, and will need to hang around until - * all of the requests are done. - * -- * It is instead free'd in remove_from_request_hash() -+ * It is instead free'd when all of the requests using it -+ * are done. - */ - } - --static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *request) -+static void tls_write_available(fr_event_list_t *el, int sock, void *ctx); -+ -+static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener) - { -- uint8_t *p; - ssize_t rcode; - listen_socket_t *sock = listener->data; - -- p = sock->ssn->dirty_out.data; -- -- while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { -- RDEBUG3("Writing to socket %d", request->packet->sockfd); -- rcode = write(request->packet->sockfd, p, -- (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); -- if (rcode <= 0) { -- RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno)); -+ /* -+ * It's not writable, so we don't bother writing to it. -+ */ -+ if (listener->blocked) return 0; - -- tls_socket_close(listener); -+ /* -+ * Write as much as possible. -+ */ -+ rcode = write(listener->fd, sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); -+ if (rcode <= 0) { -+#ifdef EWOULDBLOCK -+ /* -+ * Writing to the socket would cause it to block. -+ * As a result, we just mark it as "don't use" -+ * until such time as it becomes writable. -+ */ -+ if (errno == EWOULDBLOCK) { -+ proxy_listener_freeze(listener, tls_write_available); - return 0; - } -- p += rcode; -+#endif -+ -+ -+ ERROR("(TLS) Error writing to socket: %s", fr_syserror(errno)); -+ -+ tls_socket_close(listener); -+ return -1; - } - -- sock->ssn->dirty_out.used = 0; -+ /* -+ * All of the data was written. It's fine. -+ */ -+ if ((size_t) rcode == sock->ssn->dirty_out.used) { -+ sock->ssn->dirty_out.used = 0; -+ return 0; -+ } - -- return 1; -+ /* -+ * Move the data to the start of the buffer. -+ * -+ * Yes, this is horrible. But doing this means that we -+ * don't have to modify the rest of the code which mangles dirty_out, and assumes that the write offset is always &data[used]. -+ */ -+ memmove(&sock->ssn->dirty_out.data[0], &sock->ssn->dirty_out.data[rcode], sock->ssn->dirty_out.used - rcode); -+ sock->ssn->dirty_out.used -= rcode; -+ -+ return 0; -+} -+ -+static void tls_write_available(UNUSED fr_event_list_t *el, UNUSED int fd, void *ctx) -+{ -+ rad_listen_t *listener = ctx; -+ listen_socket_t *sock = listener->data; -+ -+ proxy_listener_thaw(listener); -+ -+ PTHREAD_MUTEX_LOCK(&sock->mutex); -+ (void) tls_socket_write(listener); -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); - } - - - static int tls_socket_recv(rad_listen_t *listener) - { -- bool doing_init = false; -+ bool doing_init = false, already_read = false; - ssize_t rcode; - RADIUS_PACKET *packet; - REQUEST *request; - listen_socket_t *sock = listener->data; - fr_tls_status_t status; -- RADCLIENT *client = sock->client; - - if (!sock->packet) { - sock->packet = rad_alloc(sock, false); -@@ -165,7 +207,7 @@ static int tls_socket_recv(rad_listen_t *listener) - rad_assert(sock->ssn == NULL); - - sock->ssn = tls_new_session(sock, listener->tls, sock->request, -- listener->tls->require_client_cert); -+ listener->tls->require_client_cert, true); - if (!sock->ssn) { - TALLOC_FREE(sock->request); - sock->packet = NULL; -@@ -176,6 +218,8 @@ static int tls_socket_recv(rad_listen_t *listener) - SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs); - SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock); - -+ sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */ -+ - doing_init = true; - } - -@@ -186,7 +230,18 @@ static int tls_socket_recv(rad_listen_t *listener) - - request = sock->request; - -- RDEBUG3("Reading from socket %d", request->packet->sockfd); -+ if (sock->state == LISTEN_TLS_SETUP) { -+ RDEBUG3("(TLS) Setting connection state to RUNNING"); -+ sock->state = LISTEN_TLS_RUNNING; -+ -+ if (sock->ssn->clean_out.used < 20) { -+ goto get_application_data; -+ } -+ -+ goto read_application_data; -+ } -+ -+ RDEBUG3("(TLS) Reading from socket %d", request->packet->sockfd); - PTHREAD_MUTEX_LOCK(&sock->mutex); - - /* -@@ -195,33 +250,38 @@ static int tls_socket_recv(rad_listen_t *listener) - * the socket. - */ - if (SSL_pending(sock->ssn->ssl)) { -- RDEBUG3("Reading pending buffered data"); -+ RDEBUG3("(TLS) Reading pending buffered data"); - sock->ssn->dirty_in.used = 0; -- goto get_application_data; -+ goto check_for_setup; - } - -- rcode = read(request->packet->sockfd, -- sock->ssn->dirty_in.data, -- sizeof(sock->ssn->dirty_in.data)); -- if ((rcode < 0) && (errno == ECONNRESET)) { -- do_close: -- DEBUG("Closing TLS socket from client port %u", sock->other_port); -- tls_socket_close(listener); -- PTHREAD_MUTEX_UNLOCK(&sock->mutex); -- return 0; -- } -+ if (!already_read) { -+ rcode = read(request->packet->sockfd, -+ sock->ssn->dirty_in.data, -+ sizeof(sock->ssn->dirty_in.data)); -+ if ((rcode < 0) && (errno == ECONNRESET)) { -+ do_close: -+ DEBUG("(TLS) Closing socket from client port %u", sock->other_port); -+ tls_socket_close(listener); -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 0; -+ } - -- if (rcode < 0) { -- RDEBUG("Error reading TLS socket: %s", fr_syserror(errno)); -- goto do_close; -- } -+ if (rcode < 0) { -+ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); -+ goto do_close; -+ } - -- /* -- * Normal socket close. -- */ -- if (rcode == 0) goto do_close; -+ /* -+ * Normal socket close. -+ */ -+ if (rcode == 0) { -+ RDEBUG("(TLS) Client has closed the TCP connection"); -+ goto do_close; -+ } - -- sock->ssn->dirty_in.used = rcode; -+ sock->ssn->dirty_in.used = rcode; -+ } - - dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); - -@@ -229,16 +289,17 @@ static int tls_socket_recv(rad_listen_t *listener) - * Catch attempts to use non-SSL. - */ - if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) { -- RDEBUG("Non-TLS data sent to TLS socket: closing"); -+ RDEBUG("(TLS) Non-TLS data sent to TLS socket: closing"); - goto do_close; - } - - /* - * If we need to do more initialization, do that here. - */ -+check_for_setup: - if (!sock->ssn->is_init_finished) { - if (!tls_handshake_recv(request, sock->ssn)) { -- RDEBUG("FAILED in TLS handshake receive"); -+ RDEBUG("(TLS) Failed in TLS handshake receive"); - goto do_close; - } - -@@ -246,24 +307,93 @@ static int tls_socket_recv(rad_listen_t *listener) - * More ACK data to send. Do so. - */ - if (sock->ssn->dirty_out.used > 0) { -- tls_socket_write(listener, request); -+ RDEBUG3("(TLS) Writing to socket %d", listener->fd); -+ tls_socket_write(listener); -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 0; -+ } -+ -+ /* -+ * If SSL handshake still isn't finished, then there -+ * is more data to read. Release the mutex and -+ * return so this function will be called again -+ */ -+ if (!SSL_is_init_finished(sock->ssn->ssl)) { - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - return 0; - } -+ } -+ -+ /* -+ * Run the request through a virtual server in -+ * order to see if we like the certificate -+ * presented by the client. -+ */ -+ if (sock->state == LISTEN_TLS_INIT) { -+ if (!SSL_is_init_finished(sock->ssn->ssl)) { -+ RDEBUG("(TLS) OpenSSL says that the TLS session is still negotiating, but there's no more data to send!"); -+ goto do_close; -+ } -+ -+ sock->ssn->is_init_finished = true; -+ if (!listener->check_client_connections) { -+ sock->state = LISTEN_TLS_RUNNING; -+ goto get_application_data; -+ } -+ -+ request->packet->vps = fr_pair_list_copy(request->packet, sock->certs); -+ -+ /* -+ * Fake out a Status-Server packet, which -+ * does NOT have a Message-Authenticator, -+ * or any other contents. -+ */ -+ request->packet->code = PW_CODE_STATUS_SERVER; -+ request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); -+ request->packet->data[0] = PW_CODE_STATUS_SERVER; -+ request->packet->data[3] = 20; -+ request->listener = listener; -+ sock->state = LISTEN_TLS_CHECKING; -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); - - /* -- * FIXME: Run the request through a virtual -- * server in order to see if we like the -- * certificate presented by the client. -+ * Don't read from the socket until the request -+ * returns. - */ -+ listener->status = RAD_LISTEN_STATUS_PAUSE; -+ radius_update_listener(listener); -+ -+ return 1; - } - - /* - * Try to get application data. - */ - get_application_data: -+ /* -+ * More data to send. Do so. -+ */ -+ if (sock->ssn->dirty_out.used > 0) { -+ RDEBUG3("(TLS) Writing to socket %d", listener->fd); -+ rcode = tls_socket_write(listener); -+ if (rcode < 0) { -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return rcode; -+ } -+ } -+ - status = tls_application_data(sock->ssn, request); -- RDEBUG("Application data status %d", status); -+ RDEBUG3("(TLS) Application data status %d", status); -+ -+ /* -+ * Some kind of failure. Close the socket. -+ */ -+ if (status == FR_TLS_FAIL) { -+ DEBUG("(TLS) Unable to recover from TLS error, closing socket from client port %u", sock->other_port); -+ tls_socket_close(listener); -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 0; -+ } - - if (status == FR_TLS_MORE_FRAGMENTS) { - PTHREAD_MUTEX_UNLOCK(&sock->mutex); -@@ -275,6 +405,16 @@ get_application_data: - return 0; - } - -+ /* -+ * Hold application data if we're not yet in the RUNNING -+ * state. -+ */ -+ if (sock->state != LISTEN_TLS_RUNNING) { -+ RDEBUG3("(TLS) Holding application data until setup is complete"); -+ return 0; -+ } -+ -+read_application_data: - /* - * We now have a bunch of application data. - */ -@@ -286,7 +426,7 @@ get_application_data: - */ - if ((sock->ssn->clean_out.used < 20) || - (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) { -- RDEBUG("Received bad packet: Length %zd contents %d", -+ RDEBUG("(TLS) Received bad packet: Length %zd contents %d", - sock->ssn->clean_out.used, - (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]); - goto do_close; -@@ -299,9 +439,11 @@ get_application_data: - packet->vps = NULL; - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - -+ packet->tls = true; -+ - if (!rad_packet_ok(packet, 0, NULL)) { - if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); -- DEBUG("Closing TLS socket from client"); -+ DEBUG("(TLS) Closing TLS socket from client"); - PTHREAD_MUTEX_LOCK(&sock->mutex); - tls_socket_close(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); -@@ -315,7 +457,7 @@ get_application_data: - char host_ipaddr[128]; - - if (is_radius_code(packet->code)) { -- RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d", -+ RDEBUG("(TLS): %s packet from host %s port %d, id=%d, length=%d", - fr_packet_codes[packet->code], - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, -@@ -323,7 +465,7 @@ get_application_data: - packet->src_port, - packet->id, (int) packet->data_len); - } else { -- RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d", -+ RDEBUG("(TLS): Packet from host %s port %d code=%d, id=%d, length=%d", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - host_ipaddr, sizeof(host_ipaddr)), -@@ -333,8 +475,6 @@ get_application_data: - } - } - -- FR_STATS_INC(auth, total_requests); -- - return 1; - } - -@@ -359,6 +499,7 @@ redo: - rad_assert(client != NULL); - - packet = talloc_steal(NULL, sock->packet); -+ sock->request->packet = NULL; - sock->packet = NULL; - - /* -@@ -391,8 +532,26 @@ redo: - break; - #endif - -+#ifdef WITH_COA -+ case PW_CODE_COA_REQUEST: -+ if (listener->type != RAD_LISTEN_COA) goto bad_packet; -+ FR_STATS_INC(coa, total_requests); -+ fun = rad_coa_recv; -+ break; -+ -+ case PW_CODE_DISCONNECT_REQUEST: -+ if (listener->type != RAD_LISTEN_COA) goto bad_packet; -+ FR_STATS_INC(dsc, total_requests); -+ fun = rad_coa_recv; -+ break; -+#endif -+ - case PW_CODE_STATUS_SERVER: -- if (!main_config.status_server) { -+ if (!main_config.status_server -+#ifdef WITH_TLS -+ && !listener->check_client_connections -+#endif -+ ) { - FR_STATS_INC(auth, total_unknown_types); - WARN("Ignoring Status-Server request due to security configuration"); - rad_free(&packet); -@@ -405,7 +564,7 @@ redo: - bad_packet: - FR_STATS_INC(auth, total_unknown_types); - -- DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED", -+ DEBUG("(TLS) Invalid packet code %d sent from client %s port %d : IGNORED", - packet->code, client->shortname, packet->src_port); - rad_free(&packet); - return 0; -@@ -432,7 +591,7 @@ redo: - int peek = SSL_peek(sock->ssn->ssl, buf, 1); - - if (peek > 0) { -- DEBUG("more TLS records after dual_tls_recv"); -+ DEBUG("(TLS) more TLS records after dual_tls_recv"); - goto redo; - } - } -@@ -455,6 +614,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) - - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; - -+ /* -+ * See if the policies allowed this connection. -+ */ -+ if (sock->state == LISTEN_TLS_CHECKING) { -+ if (request->reply->code != PW_CODE_ACCESS_ACCEPT) { -+ listener->status = RAD_LISTEN_STATUS_EOL; -+ listener->tls = NULL; /* parent owns this! */ -+ -+ /* -+ * Tell the event handler that an FD has disappeared. -+ */ -+ radius_update_listener(listener); -+ return 0; -+ } -+ -+ /* -+ * Resume reading from the listener. -+ */ -+ listener->status = RAD_LISTEN_STATUS_RESUME; -+ radius_update_listener(listener); -+ -+ rad_assert(sock->request->packet != request->packet); -+ -+ sock->state = LISTEN_TLS_SETUP; -+ (void) dual_tls_recv(listener); -+ return 0; -+ } -+ - /* - * Accounting reject's are silently dropped. - * -@@ -507,38 +694,43 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) - if (sock->ssn->dirty_out.used > 0) { - dump_hex("WRITE TO SSL", sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); - -- tls_socket_write(listener, request); -+ RDEBUG3("(TLS) Writing to socket %d", listener->fd); -+ tls_socket_write(listener); - } - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - - return 0; - } - --static int try_connect(tls_session_t *ssn) -+static int try_connect(listen_socket_t *sock) - { - int ret; -- ret = SSL_connect(ssn->ssl); -- if (ret < 0) { -- switch (SSL_get_error(ssn->ssl, ret)) { -- default: -- break; -+ time_t now; - -+ now = time(NULL); -+ if ((sock->opened + sock->connect_timeout) < now) { -+ tls_error_io_log(NULL, sock->ssn, 0, "Timeout in SSL_connect"); -+ return -1; -+ } - -+ ret = SSL_connect(sock->ssn->ssl); -+ if (ret <= 0) { -+ switch (SSL_get_error(sock->ssn->ssl, ret)) { -+ default: -+ tls_error_io_log(NULL, sock->ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); -+ return -1; - - case SSL_ERROR_WANT_READ: -+ DEBUG3("(TLS) SSL_connect() returned WANT_READ"); -+ return 2; -+ - case SSL_ERROR_WANT_WRITE: -- ssn->connected = false; -- return 0; -+ DEBUG3("(TLS) SSL_connect() returned WANT_WRITE"); -+ return 2; - } - } - -- if (ret <= 0) { -- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); -- talloc_free(ssn); -- -- return -1; -- } -- -+ sock->ssn->connected = true; - return 1; - } - -@@ -564,15 +756,27 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) - listen_socket_t *sock = listener->data; - - if (!sock->ssn->connected) { -- rcode = try_connect(sock->ssn); -- if (rcode == 0) return 0; -+ rcode = try_connect(sock); -+ if (rcode <= 0) return rcode; - -- if (rcode < 0) { -- SSL_shutdown(sock->ssn->ssl); -- return -1; -- } -+ if (rcode == 2) return 0; /* more negotiation needed */ -+ } - -- sock->ssn->connected = true; -+ if (sock->ssn->clean_out.used) { -+ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); -+ /* -+ * Write to SSL. -+ */ -+ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); -+ if (rcode > 0) { -+ if ((size_t) rcode < sock->ssn->clean_out.used) { -+ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, -+ sock->ssn->clean_out.used - rcode); -+ sock->ssn->clean_out.used -= rcode; -+ } else { -+ sock->ssn->clean_out.used = 0; -+ } -+ } - } - - /* -@@ -589,9 +793,14 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) - if (rcode <= 0) { - int err = SSL_get_error(sock->ssn->ssl, rcode); - switch (err) { -+ - case SSL_ERROR_WANT_READ: -+ DEBUG3("(TLS) OpenSSL returned WANT_READ"); -+ return 0; -+ - case SSL_ERROR_WANT_WRITE: -- return 0; /* do some more work later */ -+ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); -+ return 0; - - case SSL_ERROR_ZERO_RETURN: - /* remote end sent close_notify, send one back */ -@@ -602,9 +811,12 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) - do_close: - return -1; - -- default: -- tls_error_log(NULL, "Failed in proxy receive"); -+ case SSL_ERROR_SSL: -+ DEBUG("(TLS) Home server has closed the connection"); -+ goto do_close; - -+ default: -+ tls_error_log(NULL, "Failed in proxy receive with OpenSSL error %d", err); - goto do_close; - } - } -@@ -641,16 +853,28 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) - rcode = SSL_read(sock->ssn->ssl, data + sock->partial, - length - sock->partial); - if (rcode <= 0) { -- switch (SSL_get_error(sock->ssn->ssl, rcode)) { -+ int err = SSL_get_error(sock->ssn->ssl, rcode); -+ switch (err) { -+ - case SSL_ERROR_WANT_READ: -+ DEBUG3("(TLS) OpenSSL returned WANT_READ"); -+ return 0; -+ - case SSL_ERROR_WANT_WRITE: -+ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); - return 0; - - case SSL_ERROR_ZERO_RETURN: - /* remote end sent close_notify, send one back */ - SSL_shutdown(sock->ssn->ssl); - goto do_close; -+ -+ case SSL_ERROR_SSL: -+ DEBUG("(TLS) Home server has closed the connection"); -+ goto do_close; -+ - default: -+ DEBUG("(TLS) Unexpected OpenSSL error %d", err); - goto do_close; - } - } -@@ -683,18 +907,18 @@ int proxy_tls_recv(rad_listen_t *listener) - - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; - -+ rad_assert(sock->ssn != NULL); -+ - DEBUG3("Proxy SSL socket has data to read"); - PTHREAD_MUTEX_LOCK(&sock->mutex); - data_len = proxy_tls_read(listener); -- PTHREAD_MUTEX_UNLOCK(&sock->mutex); -- - if (data_len < 0) { -- DEBUG("Closing TLS socket to home server"); -- PTHREAD_MUTEX_LOCK(&sock->mutex); - tls_socket_close(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ DEBUG("Closing TLS socket to home server"); - return 0; - } -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); - - if (data_len == 0) return 0; /* not done yet */ - -@@ -713,6 +937,8 @@ int proxy_tls_recv(rad_listen_t *listener) - memcpy(packet->data, data, packet->data_len); - memcpy(packet->vector, packet->data + 4, 16); - -+ packet->tls = true; -+ - /* - * FIXME: Client MIB updates? - */ -@@ -727,6 +953,14 @@ int proxy_tls_recv(rad_listen_t *listener) - break; - #endif - -+#ifdef WITH_COA -+ case PW_CODE_COA_ACK: -+ case PW_CODE_COA_NAK: -+ case PW_CODE_DISCONNECT_ACK: -+ case PW_CODE_DISCONNECT_NAK: -+ break; -+#endif -+ - default: - /* - * FIXME: Update MIB for packet types? -@@ -765,42 +999,111 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) - * if there's no packet, encode it here. - */ - if (!request->proxy->data) { -- request->proxy_listener->encode(request->proxy_listener, -- request); -+ request->reply->tls = true; -+ request->proxy_listener->proxy_encode(request->proxy_listener, -+ request); - } - -+ rad_assert(sock->ssn != NULL); -+ - if (!sock->ssn->connected) { - PTHREAD_MUTEX_LOCK(&sock->mutex); -- rcode = try_connect(sock->ssn); -+ rcode = try_connect(sock); -+ if (rcode <= 0) { -+ tls_socket_close(listener); -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return rcode; -+ } - PTHREAD_MUTEX_UNLOCK(&sock->mutex); -- if (rcode == 0) return 0; - -- if (rcode < 0) { -- SSL_shutdown(sock->ssn->ssl); -- return -1; -- } -+ /* -+ * More negotiation is needed, but remember to -+ * save this packet to an intermediate buffer. -+ * Once the SSL connection is established, the -+ * later code writes the packet to the -+ * connection. -+ */ -+ if (rcode == 2) { -+ PTHREAD_MUTEX_LOCK(&sock->mutex); -+ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ RERROR("(TLS) Too much data buffered during SSL_connect()"); -+ listener->status = RAD_LISTEN_STATUS_EOL; -+ radius_update_listener(listener); -+ return -1; -+ } - -- sock->ssn->connected = true; -+ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); -+ sock->ssn->clean_out.used += request->proxy->data_len; -+ RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); -+ -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 0; -+ } - } - - DEBUG3("Proxy is writing %u bytes to SSL", - (unsigned int) request->proxy->data_len); - PTHREAD_MUTEX_LOCK(&sock->mutex); -- rcode = SSL_write(sock->ssn->ssl, request->proxy->data, -- request->proxy->data_len); -+ -+ /* -+ * We may have previously cached data on SSL_connect(), which now needs to be written to the home server. -+ */ -+ if (sock->ssn->clean_out.used > 0) { -+ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ RERROR("(TLS) Too much data buffered after SSL_connect()"); -+ listener->status = RAD_LISTEN_STATUS_EOL; -+ radius_update_listener(listener); -+ return -1; -+ } -+ -+ /* -+ * Add in our packet. -+ */ -+ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); -+ sock->ssn->clean_out.used += request->proxy->data_len; -+ -+ /* -+ * Write to SSL. -+ */ -+ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); -+ -+ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); -+ if (rcode > 0) { -+ if ((size_t) rcode < sock->ssn->clean_out.used) { -+ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, -+ sock->ssn->clean_out.used - rcode); -+ sock->ssn->clean_out.used -= rcode; -+ } else { -+ sock->ssn->clean_out.used = 0; -+ } -+ PTHREAD_MUTEX_UNLOCK(&sock->mutex); -+ return 1; -+ } -+ } else { -+ rcode = SSL_write(sock->ssn->ssl, request->proxy->data, -+ request->proxy->data_len); -+ } - if (rcode < 0) { - int err; - - err = ERR_get_error(); - switch (err) { - case SSL_ERROR_NONE: -+ break; -+ - case SSL_ERROR_WANT_READ: -+ DEBUG3("(TLS) OpenSSL returned WANT_READ"); -+ break; -+ - case SSL_ERROR_WANT_WRITE: -- break; /* let someone else retry */ -+ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); -+ break; - - default: -- tls_error_log(NULL, "Failed in proxy send"); -- DEBUG("Closing TLS socket to home server"); -+ tls_error_log(NULL, "Failed in proxy send with OpenSSL error %d", err); -+ DEBUG("(TLS) Closing socket to home server"); - tls_socket_close(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - return 0; -diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c -index 9fed166e62..1cd73ff246 100644 ---- a/src/modules/proto_dhcp/rlm_dhcp.c -+++ b/src/modules/proto_dhcp/rlm_dhcp.c -@@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request, - decoded++; - } - -- fr_pair_list_move(request->packet, &(request->packet->vps), &head); -+ fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD); - - /* Free any unmoved pairs */ - fr_pair_list_free(&head); -diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c -index 83e7252fa8..2f37663df1 100644 ---- a/src/modules/rlm_eap/libeap/eap_tls.c -+++ b/src/modules/rlm_eap/libeap/eap_tls.c -@@ -1,3 +1,4 @@ -+ - /* - * eap_tls.c - * -@@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - * - * Fragment length is Framed-MTU - 4. - */ --tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert) -+tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13) - { - tls_session_t *ssn; - REQUEST *request = handler->request; -@@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ - * in Opaque. So that we can use these data structures - * when we get the response - */ -- ssn = tls_new_session(handler, tls_conf, request, client_cert); -+ ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13); - if (!ssn) { - return NULL; - } -@@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag) - */ - int eaptls_success(eap_handler_t *handler, int peap_flag) - { -- EAPTLS_PACKET reply; -+ EAPTLS_PACKET reply; - REQUEST *request = handler->request; - tls_session_t *tls_session = handler->opaque; - -@@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) - /* - * Automatically generate MPPE keying material. - */ -- if (tls_session->prf_label) { -- eaptls_gen_mppe_keys(handler->request, -- tls_session->ssl, tls_session->prf_label); -+ if (tls_session->label) { -+ uint8_t const *context = NULL; -+ size_t context_size = 0; -+#ifdef TLS1_3_VERSION -+ uint8_t const context_tls13[] = { handler->type }; -+#endif -+ -+ switch (SSL_version(tls_session->ssl)) { -+#ifdef TLS1_3_VERSION -+ case TLS1_3_VERSION: -+ context = context_tls13; -+ context_size = sizeof(context_tls13); -+ tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; -+ break; -+#endif -+ case TLS1_2_VERSION: -+ case TLS1_1_VERSION: -+ case TLS1_VERSION: -+ break; -+ case SSL2_VERSION: -+ case SSL3_VERSION: -+ default: -+ /* Should never happen */ -+ rad_assert(0); -+ return 0; -+ break; -+ } -+ eaptls_gen_mppe_keys(request, -+ tls_session->ssl, tls_session->label, -+ context, context_size); - } else if (handler->type != PW_EAP_FAST) { -- RWDEBUG("Not adding MPPE keys because there is no PRF label"); -+ RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label"); - } - -- eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, -- handler->type); -+ eaptls_gen_eap_key(handler); -+ - return 1; - } - -@@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag) - EAPTLS_PACKET reply; - REQUEST *request = handler->request; - -- RDEBUG2("ACKing Peer's TLS record fragment"); -+ RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data."); - reply.code = FR_TLS_ACK; - reply.length = TLS_HEADER_LEN + 1/*flags*/; - reply.flags = peap_flag; -@@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - /* - * First output the flags (for debugging) - */ -- RDEBUG3("Peer sent flags %c%c%c", -+ RDEBUG3("(TLS) EAP Peer sent flags %c%c%c", - TLS_START(eaptls_packet->flags) ? 'S' : '-', - TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-', - TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-'); -@@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) { - return tls_ack_handler(handler->opaque, request); - } else { -- REDEBUG("Received Invalid TLS ACK"); -+ REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection"); - return FR_TLS_INVALID; - } - } -@@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - * We send TLS_START, but do not receive it. - */ - if (TLS_START(eaptls_packet->flags)) { -- REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)"); -+ REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)"); - return FR_TLS_INVALID; - } - -@@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; - - if (frag_len > total_len) { -- RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len, -+ RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len, - total_len); - } - -- RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len); -+ RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len); - if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { - /* - * The supplicant is free to send fragments of wildly varying -@@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - * as they won't contain the length field. - */ - if (frag_len + 4) { /* check for wrap, else clang scan gets excited */ -- RDEBUG2("Expecting %i TLS record fragments", -+ RDEBUG2("(TLS) EAP Expecting %i fragments", - (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1)); - } - -@@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - */ - if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) || - !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) { -- RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments " -- "to follow", frag_len); -+ RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes). Peer says more fragments " -+ "will follow", frag_len); - tls_session->tls_record_in_total_len = total_len; - tls_session->tls_record_in_recvd_len = frag_len; - - return FR_TLS_FIRST_FRAGMENT; - } - -- RDEBUG2("Got additional TLS record fragment with length (%zu bytes). " -- "Peer indicated more fragments to follow", frag_len); -+ RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). " -+ "Peer says more fragments will follow", frag_len); - - /* - * Check we've not exceeded the originally indicated TLS record size. - */ - tls_session->tls_record_in_recvd_len += frag_len; - if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { -- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " -- "total TLS record length (%zu bytes)", frag_len, total_len); -+ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " -+ "total data length (%zu bytes)", frag_len, total_len); - } - - return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH; -@@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - * value of the four octet TLS length field. - */ - if (total_len != frag_len) { -- RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) " -- "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len); -+ RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) " -+ "does not match expected data length (%zu bytes)", total_len, frag_len); - } - - tls_session->tls_record_in_total_len = total_len; - tls_session->tls_record_in_recvd_len = frag_len; -- RDEBUG2("Got complete TLS record (%zu bytes)", frag_len); -+ RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len); - return FR_TLS_LENGTH_INCLUDED; - } - -@@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) - * this must be the final record fragment - */ - if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { -- RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len); -+ RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len); - tls_session->tls_record_in_recvd_len += frag_len; - if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) { -- RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated " -- "TLS record length (%zu bytes)", -+ RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected " -+ "expected data length (%zu bytes)", - tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); - } - } - - if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { -- RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow", -+ RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes). Peer says more fragments will follow", - frag_len); - tls_session->tls_record_in_recvd_len += frag_len; - if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { -- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " -- "indicated TLS record length (%zu bytes)", -+ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " -+ "expected length (%zu bytes)", - tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); - } - return FR_TLS_MORE_FRAGMENTS; -@@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st - */ - if (TLS_LENGTH_INCLUDED(tlspacket->flags) && - (tlspacket->length < 5)) { /* flags + TLS message length */ -- REDEBUG("Invalid EAP-TLS packet received: Length bit is set, " -+ REDEBUG("(TLS) EAP Invalid packet received: Length bit is set," - "but packet too short to contain length field"); - talloc_free(tlspacket); - return NULL; -@@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st - memcpy(&data_len, &eap_ds->response->type.data[1], 4); - data_len = ntohl(data_len); - if (data_len > MAX_RECORD_SIZE) { -- REDEBUG("Reassembled TLS record will be %u bytes, " -- "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", -+ REDEBUG("(TLS) EAP Reassembled data will be %u bytes, " -+ "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", - data_len); - talloc_free(tlspacket); - return NULL; -@@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st - case FR_TLS_LENGTH_INCLUDED: - case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: - if (tlspacket->length < 5) { /* flags + TLS message length */ -- REDEBUG("Invalid EAP-TLS packet received: Expected length, got none"); -+ REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none"); - talloc_free(tlspacket); - return NULL; - } -@@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st - break; - - default: -- REDEBUG("Invalid EAP-TLS packet received"); -+ REDEBUG("(TLS) EAP Invalid packet received"); - talloc_free(tlspacket); - return NULL; - } -@@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h - * is required then send another request. - */ - if (!tls_handshake_recv(handler->request, tls_session)) { -- REDEBUG("TLS receive handshake failed during operation"); -+ REDEBUG("(TLS) EAP Receive handshake failed during operation"); - tls_fail(tls_session); - return FR_TLS_FAIL; - } - -+#ifdef TLS1_3_VERSION -+ /* -+ * https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5 -+ * -+ * We need to signal the other end that TLS negotiation -+ * is done. We can't send a zero-length application data -+ * message, so we send application data which is one byte -+ * of zero. -+ * -+ * Note this is only done for when there is no application -+ * data to be sent. So this is done always for EAP-TLS but -+ * notibly not for PEAP even on resumption. -+ */ -+ if ((SSL_version(tls_session->ssl) == TLS1_3_VERSION) && -+ (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) { -+ if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) { -+ tls_session->authentication_success = true; -+ -+ RDEBUG("(TLS) EAP Sending final Commitment Message."); -+ tls_session->record_plus(&tls_session->clean_in, "\0", 1); -+ } -+ -+ tls_handshake_send(request, tls_session); -+ } -+#endif -+ - /* - * FIXME: return success/fail. - * -@@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h - /* - * If there is no data to send i.e - * dirty_out.used <=0 and if the SSL -- * handshake is finished, then return a -- * EPTLS_SUCCESS -+ * handshake is finished. - */ -+ if (tls_session->is_init_finished) return FR_TLS_SUCCESS; - -- if (tls_session->is_init_finished) { -+ /* -+ * If session is established, skip round-trip and -+ * try to process any inner tunnel data if present. -+ * -+ * This occurs for EAP-TTLS/PAP with TLSv1.3. -+ */ -+ if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) { - /* -- * Init is finished. The rest is -- * application data. -+ * Don't set is_init_finished, as that causes the -+ * rest of the code to make too many assumptions. - */ -- tls_session->info.content_type = application_data; -- return FR_TLS_SUCCESS; -+ return FR_TLS_OK; - } - - /* - * Who knows what happened... - */ -- REDEBUG("TLS failed during operation"); -+ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); - return FR_TLS_FAIL; - } - -@@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - - if (!request) return FR_TLS_FAIL; - -- RDEBUG2("Continuing EAP-TLS"); -+ RDEBUG3("(TLS) EAP Continuing ..."); - - SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); - -@@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - */ - status = eaptls_verify(handler); - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { -- REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "")); - } - - switch (status) { -@@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - * data" phase. - */ - case FR_TLS_OK: -- RDEBUG2("Done initial handshake"); -+ RDEBUG2("(TLS) EAP Done initial handshake"); - - /* - * Get the rest of the fragments. -@@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - * Extract the TLS packet from the buffer. - */ - if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) { -+ REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message"); - status = FR_TLS_FAIL; - goto done; - } -@@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - if (tlspacket->dlen != - (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) { - talloc_free(tlspacket); -- REDEBUG("Exceeded maximum record size"); -+ REDEBUG("(TLS) EAP Exceeded maximum record size"); - status = FR_TLS_FAIL; - goto done; - } -@@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - * Send the ACK. - */ - eaptls_send_ack(handler, tls_session->peap_flag); -- RDEBUG2("Init is done, but tunneled data is fragmented"); -+ RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented"); - status = FR_TLS_HANDLED; - goto done; - } -@@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - - vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps); - if (!vps) { -- RWDEBUG("No information in cached session %s", buffer); -+ RWDEBUG("(TLS) EAP No information in cached session %s", buffer); - } else { - vp_cursor_t cursor; - VALUE_PAIR *vp; - fr_tls_server_conf_t *conf; - -- RDEBUG("Adding cached attributes from session %s", buffer); -+ RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer); - - conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF); - rad_assert(conf != NULL); -@@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) - rdebug_pair(L_DBG_LVL_2, request, vp, "&request:"); - fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp)); - } -+ -+ } else if ((vp->da->vendor == 0) && -+ (vp->da->attr == PW_EAP_TYPE)) { -+ /* -+ * EAP-Type gets added to -+ * the control list, so -+ * that we can sanity check it. -+ */ -+ rdebug_pair(L_DBG_LVL_2, request, vp, "&control:"); -+ fr_pair_add(&request->config, fr_pair_copy(request, vp)); -+ - } else { -+ - rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:"); - fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); - } -diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h -index 73c7fdd53b..8e5fc773d6 100644 ---- a/src/modules/rlm_eap/libeap/eap_tls.h -+++ b/src/modules/rlm_eap/libeap/eap_tls.h -@@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); - int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); - - --void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); --void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label); -+void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); -+void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); - void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); --void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header); --void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); -+void eaptls_gen_eap_key(eap_handler_t *handler); -+void eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull); - - #define BUFFER_SIZE 1024 - -@@ -100,7 +100,7 @@ typedef struct tls_packet { - /* EAP-TLS framework */ - EAPTLS_PACKET *eaptls_alloc(void); - void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr); --tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert); -+tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13); - int eaptls_start(EAP_DS *eap_ds, int peap); - int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply); - -diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c -index 3a9e864104..385441c62f 100644 ---- a/src/modules/rlm_eap/libeap/mppe_keys.c -+++ b/src/modules/rlm_eap/libeap/mppe_keys.c -@@ -26,11 +26,16 @@ RCSID("$Id$") - USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - - #include "eap_tls.h" -+#include - #include -+#include - -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+#include -+#endif - - /* -- * TLS PRF from RFC 2246 -+ * TLS P_hash from RFC 2246/5246 section 5 - */ - static void P_hash(EVP_MD const *evp_md, - unsigned char const *secret, unsigned int secret_len, -@@ -38,23 +43,18 @@ static void P_hash(EVP_MD const *evp_md, - unsigned char *out, unsigned int out_len) - { - HMAC_CTX *ctx_a, *ctx_out; -- unsigned char a[HMAC_MAX_MD_CBLOCK]; -- unsigned int size; -+ unsigned char a[EVP_MAX_MD_SIZE]; -+ unsigned int size = EVP_MAX_MD_SIZE; -+ unsigned int digest_len; - - ctx_a = HMAC_CTX_new(); - ctx_out = HMAC_CTX_new(); --#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW -- HMAC_CTX_set_flags(ctx_a, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); -- HMAC_CTX_set_flags(ctx_out, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); --#endif - HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL); - HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL); - -- size = HMAC_size(ctx_out); -- - /* Calculate A(1) */ - HMAC_Update(ctx_a, seed, seed_len); -- HMAC_Final(ctx_a, a, NULL); -+ HMAC_Final(ctx_a, a, &size); - - while (1) { - /* Calculate next part of output */ -@@ -63,13 +63,15 @@ static void P_hash(EVP_MD const *evp_md, - - /* Check if last part */ - if (out_len < size) { -- HMAC_Final(ctx_out, a, NULL); -+ digest_len = EVP_MAX_MD_SIZE; -+ HMAC_Final(ctx_out, a, &digest_len); - memcpy(out, a, out_len); - break; - } - - /* Place digest in output buffer */ -- HMAC_Final(ctx_out, out, NULL); -+ digest_len = EVP_MAX_MD_SIZE; -+ HMAC_Final(ctx_out, out, &digest_len); - HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL); - out += size; - out_len -= size; -@@ -77,7 +79,8 @@ static void P_hash(EVP_MD const *evp_md, - /* Calculate next A(i) */ - HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL); - HMAC_Update(ctx_a, a, size); -- HMAC_Final(ctx_a, a, NULL); -+ digest_len = EVP_MAX_MD_SIZE; -+ HMAC_Final(ctx_a, a, &digest_len); - } - - HMAC_CTX_free(ctx_a); -@@ -85,6 +88,82 @@ static void P_hash(EVP_MD const *evp_md, - memset(a, 0, sizeof(a)); - } - -+/* -+ * TLS PRF from RFC 2246 section 5 -+ */ -+static void PRF(unsigned char const *secret, unsigned int secret_len, -+ unsigned char const *seed, unsigned int seed_len, -+ unsigned char *out, unsigned int out_len) -+{ -+ uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)]; -+ unsigned int i; -+ -+ unsigned int len = (secret_len + 1) / 2; -+ uint8_t const *s1 = secret; -+ uint8_t const *s2 = secret + (secret_len - len); -+ -+ EVP_MD const *md5 = NULL; -+ -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ EVP_MD *md5_to_free = NULL; -+ -+ /* -+ * If we are using OpenSSL >= 3.0 and FIPS mode is -+ * enabled, we need to load the default provider in a -+ * standalone context in order to access MD5. -+ */ -+ OSSL_LIB_CTX *libctx = NULL; -+ OSSL_PROVIDER *default_provider = NULL; -+ -+ if (EVP_default_properties_is_fips_enabled(NULL)) { -+ libctx = OSSL_LIB_CTX_new(); -+ default_provider = OSSL_PROVIDER_load(libctx, "default"); -+ -+ if (!default_provider) { -+ ERROR("Failed loading OpenSSL default provider."); -+ return; -+ } -+ -+ md5_to_free = EVP_MD_fetch(libctx, "MD5", NULL); -+ if (!md5_to_free) { -+ ERROR("Failed loading OpenSSL MD5 function."); -+ return; -+ } -+ -+ md5 = md5_to_free; -+ } else { -+ md5 = EVP_md5(); -+ } -+#else -+ md5 = EVP_md5(); -+#endif -+ -+ P_hash(md5, s1, len, seed, seed_len, out, out_len); -+ P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); -+ -+ for (i = 0; i < out_len; i++) { -+ out[i] ^= buf[i]; -+ } -+ -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ if (libctx) { -+ OSSL_PROVIDER_unload(default_provider); -+ OSSL_LIB_CTX_free(libctx); -+ EVP_MD_free(md5_to_free); -+ } -+#endif -+} -+ -+/* -+ * TLS 1.2 PRF from RFC 5246 section 5 -+ */ -+static void PRFv12(unsigned char const *secret, unsigned int secret_len, -+ unsigned char const *seed, unsigned int seed_len, -+ unsigned char *out, unsigned int out_len) -+{ -+ P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len); -+} -+ - /* EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */ - void T_PRF(unsigned char const *secret, unsigned int secret_len, - char const *prf_label, -@@ -128,60 +207,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len, - talloc_free(buf); - } - --static void PRF(unsigned char const *secret, unsigned int secret_len, -- unsigned char const *seed, unsigned int seed_len, -- unsigned char *out, unsigned char *buf, unsigned int out_len) --{ -- unsigned int i; -- unsigned int len = (secret_len + 1) / 2; -- uint8_t const *s1 = secret; -- uint8_t const *s2 = secret + (secret_len - len); -- -- P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len); -- P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); -- -- for (i=0; i < out_len; i++) { -- out[i] ^= buf[i]; -- } --} -- - #define EAPTLS_MPPE_KEY_LEN 32 - - /* - * Generate keys according to RFC 2716 and add to reply - */ --void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) -+void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) - { - uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; - uint8_t *p; -- size_t prf_size; -+ size_t len; - -- prf_size = strlen(prf_label); -+ len = strlen(label); - - #if OPENSSL_VERSION_NUMBER >= 0x10001000L -- if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) { -+ if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { - ERROR("Failed generating keying material"); - return; - } - #else - { -- uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)]; -+ uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; - uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; - - p = seed; - -- memcpy(p, prf_label, prf_size); -- p += prf_size; -+ memcpy(p, label, len); -+ p += len; - - memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); - p += SSL3_RANDOM_SIZE; -- prf_size += SSL3_RANDOM_SIZE; -+ len += SSL3_RANDOM_SIZE; - - memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); -- prf_size += SSL3_RANDOM_SIZE; -+ p += SSL3_RANDOM_SIZE; -+ len += SSL3_RANDOM_SIZE; -+ -+ if (context) { -+ /* cloned and reversed FR_PUT_LE16 */ -+ p[0] = ((uint16_t) (context_size)) >> 8; -+ p[1] = ((uint16_t) (context_size)) & 0xff; -+ p += 2; -+ len += 2; -+ memcpy(p, context, context_size); -+ p += context_size; -+ len += context_size; -+ } - - PRF(s->session->master_key, s->session->master_key_length, -- seed, prf_size, out, buf, sizeof(out)); -+ seed, len, out, buf, sizeof(out)); - } - #endif - -@@ -195,7 +269,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) - } - - --#define FR_TLS_PRF_CHALLENGE "ttls challenge" -+#define FR_TLS_PRF_CHALLENGE "ttls challenge" - - /* - * Generate the TTLS challenge -@@ -206,9 +280,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) - void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) - { - #if OPENSSL_VERSION_NUMBER >= 0x10001000L -- SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, -- sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0); -- -+ if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, -+ sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) { -+ ERROR("Failed generating keying material"); -+ } - #else - uint8_t out[32], buf[32]; - uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; -@@ -226,14 +301,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) - #endif - } - -+#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id" -+ - /* - * Actually generates EAP-Session-Id, which is an internal server - * attribute. Not all systems want to send EAP-Key-Name. - */ --void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) -+void eaptls_gen_eap_key(eap_handler_t *handler) - { -+ RADIUS_PACKET *packet = handler->request->reply; -+ tls_session_t *tls_session = handler->opaque; -+ SSL *s = tls_session->ssl; - VALUE_PAIR *vp; - uint8_t *buff, *p; -+ uint8_t type = handler->type & 0xff; - - vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0); - if (!vp) return; -@@ -241,11 +322,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) - vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE; - buff = p = talloc_array(vp, uint8_t, vp->vp_length); - -- *p++ = header & 0xff; -+ *p++ = type; - -- SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); -- p += SSL3_RANDOM_SIZE; -- SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); -+ switch (SSL_version(tls_session->ssl)) { -+ case TLS1_VERSION: -+ case TLS1_1_VERSION: -+ case TLS1_2_VERSION: -+ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); -+ p += SSL3_RANDOM_SIZE; -+ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); -+ break; -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L -+#ifdef TLS1_3_VERSION -+ case TLS1_3_VERSION: -+#endif -+ default: -+ { -+ uint8_t const context[] = { type }; -+ -+ if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE, -+ FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1, -+ context, sizeof(context), 1) != 1) { -+ ERROR("Failed generating keying material"); -+ return; -+ } -+ } -+#endif -+ } - - vp->vp_octets = buff; - fr_pair_add(&packet->vps, vp); -@@ -254,7 +357,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) - /* - * Same as before, but for EAP-FAST the order of {server,client}_random is flipped - */ --void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) -+void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label) - { - uint8_t *p; - size_t len, master_key_len; -@@ -273,7 +376,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_ - p += SSL3_RANDOM_SIZE; - - master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key)); -- PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size); --} -- - -+ if (version == TLS1_2_VERSION) -+ PRFv12(master_key, master_key_len, seed, p - seed, buffer, size); -+ else -+ PRF(master_key, master_key_len, seed, p - seed, buffer, size); -+} -diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c -index 553a6a6a57..cd504a8363 100644 ---- a/src/modules/rlm_eap/radeapclient.c -+++ b/src/modules/rlm_eap/radeapclient.c -@@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port); - static void rc_evprep_packet_timeout(rc_transaction_t *trans); - static void rc_deallocate_id(rc_transaction_t *trans); - -+/* -+ * For cbtls_cache_*() -+ */ -+rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request) -+{ -+ return RLM_MODULE_FAIL; -+} -+ - - static void NEVER_RETURNS usage(void) - { -diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c -index b0953aa1d4..bbb5a03c95 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c -+++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c -@@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl) - return -1; - - RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d " -- "IV_len=%d", EVP_CIPHER_key_length(c), md_size, -- EVP_CIPHER_iv_length(c)); -+ "IV_len=%d", EVP_CIPHER_key_length(c), md_size, -+ EVP_CIPHER_iv_length(c)); - return 2 * (EVP_CIPHER_key_length(c) + - md_size + - EVP_CIPHER_iv_length(c)); - #else - const SSL_CIPHER *ssl_cipher; - int cipher, digest; -+ int mac_key_len, enc_key_len, fixed_iv_len; - - ssl_cipher = SSL_get_current_cipher(ssl); - if (!ssl_cipher) - return -1; - cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher); - digest = SSL_CIPHER_get_digest_nid(ssl_cipher); -- RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest); -+ RDEBUG3("OpenSSL: cipher nid %d digest nid %d", -+ cipher, digest); - if (cipher < 0 || digest < 0) - return -1; -+ if (cipher == NID_undef) { -+ RDEBUG3("OpenSSL: no cipher in use?!"); -+ return -1; -+ } - c = EVP_get_cipherbynid(cipher); -- h = EVP_get_digestbynid(digest); -- if (!c || !h) -+ if (!c) - return -1; -+ enc_key_len = EVP_CIPHER_key_length(c); -+ if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE || -+ EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE) -+ fixed_iv_len = 4; /* only part of IV from PRF */ -+ else -+ fixed_iv_len = EVP_CIPHER_iv_length(c); -+ if (digest == NID_undef) { -+ RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)"); -+ mac_key_len = 0; -+ } else { -+ h = EVP_get_digestbynid(digest); -+ if (!h) -+ return -1; -+ mac_key_len = EVP_MD_size(h); -+ } - -- RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d", -- EVP_CIPHER_key_length(c), EVP_MD_size(h), -- EVP_CIPHER_iv_length(c)); -- return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) + -- EVP_CIPHER_iv_length(c)); -+ RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d", -+ mac_key_len, enc_key_len, fixed_iv_len); -+ return 2 * (mac_key_len + enc_key_len + fixed_iv_len); - #endif - } - -@@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) - { - eap_fast_tunnel_t *t = tls_session->opaque; - uint8_t *buf; -- uint8_t *scratch; - size_t ksize; - - RDEBUG2("Deriving EAP-FAST keys"); -@@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) - ksize = openssl_get_keyblock_size(request, tls_session->ssl); - rad_assert(ksize > 0); - buf = talloc_size(request, ksize + sizeof(*t->keyblock)); -- scratch = talloc_size(request, ksize + sizeof(*t->keyblock)); - - t->keyblock = talloc(t, eap_fast_keyblock_t); - -- eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion"); -+ eap_fast_tls_gen_challenge(tls_session->ssl, SSL_version(tls_session->ssl), buf, ksize + sizeof(*t->keyblock), "key expansion"); - memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock)); - memset(buf, 0, ksize + sizeof(*t->keyblock)); - -@@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) - t->imckc = 0; - - talloc_free(buf); -- talloc_free(scratch); - } - - /** -@@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio - dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), - t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, - pac.opaque.data, pac.opaque.tag); -+ if (dlen < 0) return; - - pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE); - pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); -@@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session, - switch (reply->code) { - case PW_CODE_ACCESS_ACCEPT: - RDEBUG("Got tunneled Access-Accept"); -+ tls_session->authentication_success = true; - rcode = RLM_MODULE_OK; - - for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { -@@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session) - t->mode = EAP_FAST_PROVISIONING_AUTH; - } - -- if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6) -+ /* -+ * Send a new pac at ~0.6 times the lifetime. -+ */ -+ if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) { - t->pac.send = true; -+ } - } - - eap_fast_init_keys(request, tls_session); -diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c -index 2ce2dd0c3b..093dc868cd 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c -+++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c -@@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) - return -1; - } - -+#ifdef TLS1_3_VERSION -+ if (inst->tls_conf->min_version == TLS1_3_VERSION) { -+ ERROR("There are no standards for using TLS 1.3 with EAP-FAST."); -+ ERROR("You MUST enable TLS 1.2 for EAP-FAST to work."); -+ return -1; -+ } -+ -+ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || -+ (inst->tls_conf->min_version == TLS1_3_VERSION)) { -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ WARN("!! There is no standard for using EAP-FAST with TLS 1.3"); -+ WARN("!! Please set tls_max_version = \"1.2\""); -+ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); -+ WARN("!! This limitation is likely to change in late 2021."); -+ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ } -+#endif -+ - rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH); - FR_MD5_CTX ctx; - fr_md5_init(&ctx); -@@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg) - DICT_ATTR const *fast_da; - char const *errmsg; - int dlen, plen; -- uint16_t length; -+ int length; - eap_fast_attr_pac_opaque_t const *opaque = (eap_fast_attr_pac_opaque_t const *) data; - eap_fast_attr_pac_opaque_t opaque_plaintext; - -@@ -274,7 +293,7 @@ error: - * so we have to use the length in the PAC-Opaque header - */ - length = ntohs(opaque->hdr.length); -- if (len - sizeof(opaque->hdr) < length) { -+ if (len < (int) (length + sizeof(opaque->hdr))) { - errmsg = "PAC has bad length in header"; - goto error; - } -@@ -293,7 +312,7 @@ error: - plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH, - (uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv, - (uint8_t *)&opaque_plaintext); -- if (plen == -1) { -+ if (plen < 0) { - errmsg = "PAC failed to decrypt"; - goto error; - } -@@ -314,8 +333,8 @@ error: - break; - case PAC_INFO_PAC_LIFETIME: - rad_assert(t->pac.expires == 0); -- t->pac.expires = vp->vp_integer; -- t->pac.expired = (vp->vp_integer <= time(NULL)); -+ t->pac.expires = vp->vp_integer + time(NULL); -+ t->pac.expired = false; - break; - case PAC_INFO_PAC_KEY: - rad_assert(t->pac.key == NULL); -@@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - - /* -@@ -553,7 +572,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - } else { - client_cert = inst->req_client_cert; - } -- handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert); -+ -+ /* -+ * Don't allow TLS 1.3 for us, even if it's allowed -+ * elsewhere. We haven't implemented the necessary -+ * changes, so we don't allow it. -+ */ -+ handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false); - - if (!tls_session) return 0; - -@@ -566,16 +591,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - } - } - --#ifdef SSL_OP_NO_TLSv1_2 -- /* -- * Forcibly disable TLSv1.2 -- * -- * @fixme - TLSv1.2 uses a different PRF and -- * SSL_export_keying_material("key expansion") is -- * forbidden -- */ -- SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2); -+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) -+ { -+ int i; -+ for (i = 0; ; i++) { -+ const char *cipher = SSL_get_cipher_list(tls_session->ssl, i); -+ if (!cipher) break; -+ if (!strstr(cipher, "ADH-")) continue; -+ RDEBUG("Setting security level to 0 to allow anonymous cipher suites"); -+ SSL_set_security_level(tls_session->ssl, 0); -+ break; -+ } -+ } - #endif -+ - #ifdef SSL_OP_NO_TLSv1_3 - /* - * Forcibly disable TLSv1.3 -@@ -599,6 +628,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - rcode = eap_fast_tls_start(handler->eap_ds, tls_session); - - if (rcode < 0) { -+ error: - talloc_free(tls_session); - return 0; - } -@@ -607,7 +637,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - - if (!SSL_set_session_ticket_ext_cb(tls_session->ssl, _session_ticket, tls_session)) { - RERROR("Failed setting SSL session ticket callback"); -- return 0; -+ goto error; - } - - handler->stage = PROCESS; -diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c -index deaf702d61..5647f613af 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c -+++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c -@@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se - switch (reply->code) { - case PW_CODE_ACCESS_ACCEPT: - RDEBUG2("Tunneled authentication was successful"); -+ tls_session->authentication_success = true; - t->status = PEAP_STATUS_SENT_TLV_SUCCESS; - eappeap_success(handler, tls_session); - rcode = RLM_MODULE_HANDLED; -@@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, - /* send an identity request */ - t->session_resumption_state = PEAP_RESUMPTION_NO; - t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT; -+ tls_session->session_not_resumed = true; - eappeap_identity(handler, tls_session); - } - return RLM_MODULE_HANDLED; -@@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, - - return RLM_MODULE_REJECT; - -- case PEAP_STATUS_PHASE2_INIT: -- RDEBUG("In state machine in phase2 init?"); -+ case PEAP_STATUS_PHASE2_INIT: -+ RDEBUG("In state machine in phase2 init?"); - -- case PEAP_STATUS_PHASE2: -- break; -+ case PEAP_STATUS_PHASE2: -+ break; - -- default: -- REDEBUG("Unhandled state in peap"); -- return RLM_MODULE_REJECT; -+ default: -+ REDEBUG("Unhandled state in peap"); -+ return RLM_MODULE_REJECT; - } - - fake = request_alloc_fake(request); -@@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, - - if (vp) { - eap_tunnel_data_t *tunnel; -+ bool proxy_as_eap = t->proxy_tunneled_request_as_eap; -+ VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY); -+ -+ if (flag) proxy_as_eap = flag->vp_integer; - - /* - * The tunneled request was NOT handled, -@@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, - * Once the tunneled EAP session is ALMOST - * done, THEN we proxy it... - */ -- if (!t->proxy_tunneled_request_as_eap) { -+ if (!proxy_as_eap) { - fake->options |= RAD_REQUEST_OPTION_PROXY_EAP; - - /* -diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c -index 3d23322663..d9f850cef2 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c -+++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c -@@ -192,7 +192,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - client_cert = inst->req_client_cert; - } - -- ssn = eaptls_session(handler, inst->tls_conf, client_cert); -+ /* -+ * Allow TLS 1.3, it works. -+ */ -+ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); - if (!ssn) { - return 0; - } -@@ -200,9 +203,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - handler->opaque = ((void *)ssn); - - /* -- * Set up type-specific information. -+ * Set the label to a fixed string. For TLS 1.3, the -+ * label is the same for all TLS-based EAP methods. If -+ * the client is using TLS 1.3, then eaptls_success() -+ * will over-ride this label with the correct label for -+ * TLS 1.3. - */ -- ssn->prf_label = "client EAP encryption"; -+ ssn->label = "client EAP encryption"; - - /* - * As it is a poorly designed protocol, PEAP uses -@@ -230,7 +237,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - if (status == 0) return 0; - -@@ -274,7 +281,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - - /* -@@ -311,6 +318,10 @@ static int mod_process(void *arg, eap_handler_t *handler) - * data. - */ - case FR_TLS_OK: -+ /* -+ * TLSv1.3 makes application data immediately avaliable -+ */ -+ if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED; - break; - - /* -diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h -new file mode 100644 -index 0000000000..b717dd51b3 ---- /dev/null -+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h -@@ -0,0 +1,190 @@ -+/* -+ * Helper functions for constant time operations -+ * Copyright (c) 2019, The Linux Foundation -+ * -+ * This software may be distributed under the terms of the BSD license. -+ * See README for more details. -+ * -+ * These helper functions can be used to implement logic that needs to minimize -+ * externally visible differences in execution path by avoiding use of branches, -+ * avoiding early termination or other time differences, and forcing same memory -+ * access pattern regardless of values. -+ */ -+ -+#ifndef CONST_TIME_H -+#define CONST_TIME_H -+ -+ -+#if defined(__clang__) -+#define NO_UBSAN_UINT_OVERFLOW \ -+ __attribute__((no_sanitize("unsigned-integer-overflow"))) -+#else -+#define NO_UBSAN_UINT_OVERFLOW -+#endif -+ -+/** -+ * const_time_fill_msb - Fill all bits with MSB value -+ * @param val Input value -+ * @return Value with all the bits set to the MSB of the input val -+ */ -+static inline unsigned int const_time_fill_msb(unsigned int val) -+{ -+ /* Move the MSB to LSB and multiple by -1 to fill in all bits. */ -+ return (val >> (sizeof(val) * 8 - 1)) * ~0U; -+} -+ -+ -+/* @return -1 if val is zero; 0 if val is not zero */ -+static inline unsigned int const_time_is_zero(unsigned int val) -+ NO_UBSAN_UINT_OVERFLOW -+{ -+ /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */ -+ return const_time_fill_msb(~val & (val - 1)); -+} -+ -+ -+/* @return -1 if a == b; 0 if a != b */ -+static inline unsigned int const_time_eq(unsigned int a, unsigned int b) -+{ -+ return const_time_is_zero(a ^ b); -+} -+ -+ -+/* @return -1 if a == b; 0 if a != b */ -+static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b) -+{ -+ return (unsigned char) const_time_eq(a, b); -+} -+ -+ -+/** -+ * const_time_eq_bin - Constant time memory comparison -+ * @param a First buffer to compare -+ * @param b Second buffer to compare -+ * @param len Number of octets to compare -+ * @return -1 if buffers are equal, 0 if not -+ * -+ * This function is meant for comparing passwords or hash values where -+ * difference in execution time or memory access pattern could provide external -+ * observer information about the location of the difference in the memory -+ * buffers. The return value does not behave like memcmp(), i.e., -+ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike -+ * memcmp(), the execution time of const_time_eq_bin() does not depend on the -+ * contents of the compared memory buffers, but only on the total compared -+ * length. -+ */ -+static inline unsigned int const_time_eq_bin(const void *a, const void *b, -+ size_t len) -+{ -+ const unsigned char *aa = a; -+ const unsigned char *bb = b; -+ size_t i; -+ unsigned char res = 0; -+ -+ for (i = 0; i < len; i++) -+ res |= aa[i] ^ bb[i]; -+ -+ return const_time_is_zero(res); -+} -+ -+ -+/** -+ * const_time_select - Constant time unsigned int selection -+ * @param mask 0 (false) or -1 (true) to identify which value to select -+ * @param true_val Value to select for the true case -+ * @param false_val Value to select for the false case -+ * @return true_val if mask == -1, false_val if mask == 0 -+ */ -+static inline unsigned int const_time_select(unsigned int mask, -+ unsigned int true_val, -+ unsigned int false_val) -+{ -+ return (mask & true_val) | (~mask & false_val); -+} -+ -+ -+/** -+ * const_time_select_int - Constant time int selection -+ * @param mask 0 (false) or -1 (true) to identify which value to select -+ * @param true_val Value to select for the true case -+ * @param false_val Value to select for the false case -+ * @return true_val if mask == -1, false_val if mask == 0 -+ */ -+static inline int const_time_select_int(unsigned int mask, int true_val, -+ int false_val) -+{ -+ return (int) const_time_select(mask, (unsigned int) true_val, -+ (unsigned int) false_val); -+} -+ -+ -+/** -+ * const_time_select_u8 - Constant time u8 selection -+ * @param mask 0 (false) or -1 (true) to identify which value to select -+ * @param true_val Value to select for the true case -+ * @param false_val Value to select for the false case -+ * @return true_val if mask == -1, false_val if mask == 0 -+ */ -+static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val) -+{ -+ return (unsigned char) const_time_select(mask, true_val, false_val); -+} -+ -+ -+/** -+ * const_time_select_s8 - Constant time s8 selection -+ * @param mask 0 (false) or -1 (true) to identify which value to select -+ * @param true_val Value to select for the true case -+ * @param false_val Value to select for the false case -+ * @return true_val if mask == -1, false_val if mask == 0 -+ */ -+static inline char const_time_select_s8(char mask, char true_val, char false_val) -+{ -+ return (char) const_time_select(mask, (unsigned int) true_val, -+ (unsigned int) false_val); -+} -+ -+ -+/** -+ * const_time_select_bin - Constant time binary buffer selection copy -+ * @param mask 0 (false) or -1 (true) to identify which value to copy -+ * @param true_val Buffer to copy for the true case -+ * @param false_val Buffer to copy for the false case -+ * @param len Number of octets to copy -+ * @param dst Destination buffer for the copy -+ * -+ * This function copies the specified buffer into the destination buffer using -+ * operations with identical memory access pattern regardless of which buffer -+ * is being copied. -+ */ -+static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val, -+ const unsigned char *false_val, size_t len, -+ unsigned char *dst) -+{ -+ size_t i; -+ -+ for (i = 0; i < len; i++) -+ dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]); -+} -+ -+ -+static inline int const_time_memcmp(const void *a, const void *b, size_t len) -+{ -+ const unsigned char *aa = a; -+ const unsigned char *bb = b; -+ int diff, res = 0; -+ unsigned int mask; -+ -+ if (len == 0) -+ return 0; -+ do { -+ len--; -+ diff = (int) aa[len] - (int) bb[len]; -+ mask = const_time_is_zero((unsigned int) diff); -+ res = const_time_select_int(mask, res, diff); -+ } while (len); -+ -+ return res; -+} -+ -+#endif /* CONST_TIME_H */ -diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c -index d94851c3aa..26260527a5 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c -+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c -@@ -1,7 +1,5 @@ --/* -- * Copyright (c) Dan Harkins, 2012 -- * -- * Copyright holder grants permission for redistribution and use in source -+/** -+ * copyright holder grants permission for redistribution and use in source - * and binary forms, with or without modification, provided that the - * following conditions are met: - * 1. Redistribution of source code must retain the above copyright -@@ -29,100 +27,237 @@ - * This license and distribution terms cannot be changed. In other words, - * this code cannot simply be copied and put under a different distribution - * license (including the GNU public license). -+ * -+ * @copyright (c) Dan Harkins, 2012 - */ - - RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $") - USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - - #include "eap_pwd.h" -+#include "const_time.h" -+#include - --#include --#include -+static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 }; - - /* The random function H(x) = HMAC-SHA256(0^32, x) */ --static void H_Init(HMAC_CTX *ctx) -+static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest) - { -- uint8_t allzero[SHA256_DIGEST_LENGTH]; -+ unsigned int mdlen = SHA256_DIGEST_LENGTH; -+ HMAC_Final(hmac_ctx, digest, &mdlen); -+// HMAC_CTX_reset(hmac_ctx); -+} - -- memset(allzero, 0, SHA256_DIGEST_LENGTH); -+/* a counter-based KDF based on NIST SP800-108 */ -+static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label, -+ int label_len, uint8_t *result, int result_bit_len) -+{ -+ HMAC_CTX *hmac_ctx; -+ uint8_t digest[SHA256_DIGEST_LENGTH]; -+ uint16_t i, ctr, L; -+ int result_byte_len, len = 0; -+ unsigned int mdlen = SHA256_DIGEST_LENGTH; -+ uint8_t mask = 0xff; -+ -+ MEM(hmac_ctx = HMAC_CTX_new()); -+ result_byte_len = (result_bit_len + 7) / 8; -+ -+ ctr = 0; -+ L = htons(result_bit_len); -+ while (len < result_byte_len) { -+ ctr++; i = htons(ctr); - -- HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); -+ HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL); -+ if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen); -+ HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t)); -+ HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len); -+ HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t)); -+ HMAC_Final(hmac_ctx, digest, &mdlen); -+ if ((len + (int) mdlen) > result_byte_len) { -+ memcpy(result + len, digest, result_byte_len - len); -+ } else { -+ memcpy(result + len, digest, mdlen); -+ } -+ len += mdlen; -+// HMAC_CTX_reset(hmac_ctx); -+ } -+ -+ /* since we're expanding to a bit length, mask off the excess */ -+ if (result_bit_len % 8) { -+ mask <<= (8 - (result_bit_len % 8)); -+ result[result_byte_len - 1] &= mask; -+ } -+ -+ HMAC_CTX_free(hmac_ctx); - } - --static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len) -+static BIGNUM *consttime_BN (void) - { -- HMAC_Update(ctx, data, len); -+ BIGNUM *bn; -+ -+ bn = BN_new(); -+ if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME); -+ return bn; - } - --static void H_Final(HMAC_CTX *ctx, uint8_t *digest) -+/* -+ * compute the legendre symbol in constant time -+ */ -+static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx) - { -- unsigned int mdlen = SHA256_DIGEST_LENGTH; -+ int symbol; -+ unsigned int mask; -+ BIGNUM *res, *pm1over2; -+ -+ pm1over2 = consttime_BN(); -+ res = consttime_BN(); -+ -+ if (!BN_sub(pm1over2, p, BN_value_one()) || -+ !BN_rshift1(pm1over2, pm1over2) || -+ !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) { -+ BN_free(pm1over2); -+ BN_free(res); -+ return -2; -+ } -+ -+ symbol = -1; -+ mask = const_time_eq(BN_is_word(res, 1), 1); -+ symbol = const_time_select_int(mask, 1, symbol); -+ mask = const_time_eq(BN_is_zero(res), 1); -+ symbol = const_time_select_int(mask, -1, symbol); -+ -+ BN_free(pm1over2); -+ BN_free(res); - -- HMAC_Final(ctx, digest, &mdlen); -+ return symbol; - } - --/* a counter-based KDF based on NIST SP800-108 */ --static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen) -+static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx) - { -- HMAC_CTX *hctx = NULL; -- uint8_t digest[SHA256_DIGEST_LENGTH]; -- uint16_t i, ctr, L; -- int resultbytelen, len = 0; -- unsigned int mdlen = SHA256_DIGEST_LENGTH; -- uint8_t mask = 0xff; -+ BIGNUM *p, *a, *b, *tmp1, *pm1; - -- hctx = HMAC_CTX_new(); -- if (hctx == NULL) { -- DEBUG("failed allocating HMAC context"); -- return -1; -+ tmp1 = BN_new(); -+ pm1 = BN_new(); -+ p = BN_new(); -+ a = BN_new(); -+ b = BN_new(); -+ EC_GROUP_get_curve(group, p, a, b, bnctx); -+ -+ BN_sub(pm1, p, BN_value_one()); -+ -+ /* -+ * y2 = x^3 + ax + b -+ */ -+ BN_mod_sqr(tmp1, x, p, bnctx); -+ BN_mod_mul(y2, tmp1, x, p, bnctx); -+ BN_mod_mul(tmp1, a, x, p, bnctx); -+ BN_mod_add_quick(y2, y2, tmp1, p); -+ BN_mod_add_quick(y2, y2, b, p); -+ -+ BN_free(tmp1); -+ BN_free(pm1); -+ BN_free(p); -+ BN_free(a); -+ BN_free(b); -+ -+ return; -+} -+ -+static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx) -+{ -+ int offset, check, ret = 0; -+ BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL; -+ unsigned int mask; -+ unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL; -+ -+ if (((r = consttime_BN()) == NULL) || -+ ((res = consttime_BN()) == NULL) || -+ ((qr_or_qnr = consttime_BN()) == NULL) || -+ ((pm1 = consttime_BN()) == NULL)) { -+ ret = -2; -+ goto fail; - } -- resultbytelen = (resultbitlen + 7)/8; -- ctr = 0; -- L = htons(resultbitlen); -- while (len < resultbytelen) { -- ctr++; i = htons(ctr); -- HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL); -- if (ctr > 1) { -- HMAC_Update(hctx, digest, mdlen); -- } -- HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t)); -- HMAC_Update(hctx, (uint8_t const *)label, labellen); -- HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t)); -- HMAC_Final(hctx, digest, &mdlen); -- if ((len + (int) mdlen) > resultbytelen) { -- memcpy(result + len, digest, resultbytelen - len); -- } else { -- memcpy(result + len, digest, mdlen); -- } -- len += mdlen; -+ -+ if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || -+ ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || -+ ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) { -+ ret = -2; -+ goto fail; - } -- HMAC_CTX_free(hctx); - -- /* since we're expanding to a bit length, mask off the excess */ -- if (resultbitlen % 8) { -- mask <<= (8 - (resultbitlen % 8)); -- result[resultbytelen - 1] &= mask; -+ /* -+ * we select binary in constant time so make them binary -+ */ -+ memset(qr_bin, 0, BN_num_bytes(p)); -+ memset(qnr_bin, 0, BN_num_bytes(p)); -+ memset(qr_or_qnr_bin, 0, BN_num_bytes(p)); -+ -+ offset = BN_num_bytes(p) - BN_num_bytes(qr); -+ BN_bn2bin(qr, qr_bin + offset); -+ -+ offset = BN_num_bytes(p) - BN_num_bytes(qnr); -+ BN_bn2bin(qnr, qnr_bin + offset); -+ -+ /* -+ * r = (random() mod p-1) + 1 -+ */ -+ BN_sub(pm1, p, BN_value_one()); -+ BN_rand_range(r, pm1); -+ BN_add(r, r, BN_value_one()); -+ -+ BN_copy(res, val); -+ -+ /* -+ * res = val * r * r which ensures res != val but has same quadratic residocity -+ */ -+ BN_mod_mul(res, res, r, p, bnctx); -+ BN_mod_mul(res, res, r, p, bnctx); -+ -+ /* -+ * if r is even (mask is -1) then multiply by qnr and our check is qnr -+ * otherwise multiply by qr and our check is qr -+ */ -+ mask = const_time_is_zero(BN_is_odd(r)); -+ const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin); -+ BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr); -+ BN_mod_mul(res, res, qr_or_qnr, p, bnctx); -+ check = const_time_select_int(mask, -1, 1); -+ -+ if ((ret = legendre(res, p, bnctx)) == -2) { -+ ret = -1; /* just say no it's not */ -+ goto fail; - } -+ mask = const_time_eq(ret, check); -+ ret = const_time_select_int(mask, 1, 0); - -- return 0; -+fail: -+ if (qr_bin != NULL) free(qr_bin); -+ if (qnr_bin != NULL) free(qnr_bin); -+ if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin); -+ BN_free(r); -+ BN_free(res); -+ BN_free(qr_or_qnr); -+ BN_free(pm1); -+ -+ return ret; - } - --int compute_password_element (pwd_session_t *session, uint16_t grp_num, -+int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num, - char const *password, int password_len, - char const *id_server, int id_server_len, - char const *id_peer, int id_peer_len, - uint32_t *token) - { -- BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL; -- HMAC_CTX *ctx = NULL; -- uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr; -- int nid, is_odd, primebitlen, primebytelen, ret = 0; -- -- ctx = HMAC_CTX_new(); -- if (ctx == NULL) { -- DEBUG("failed allocating HMAC context"); -- goto fail; -- } -+ BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL; -+ EVP_MD_CTX *hmac_ctx; -+ EVP_PKEY *hmac_pkey; -+ uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = NULL, ctr; -+ int nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask; -+ int save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp; -+ unsigned int skip; -+ -+ MEM(hmac_ctx = EVP_MD_CTX_new()); -+ MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero))); - - switch (grp_num) { /* from IANA registry for IKE D-H groups */ - case 19: -@@ -159,17 +294,23 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, - goto fail; - } - -- if (((rnd = BN_new()) == NULL) || -- ((cofactor = BN_new()) == NULL) || -+ if (((rnd = consttime_BN()) == NULL) || - ((session->pwe = EC_POINT_new(session->group)) == NULL) || -- ((session->order = BN_new()) == NULL) || -- ((session->prime = BN_new()) == NULL) || -- ((x_candidate = BN_new()) == NULL)) { -+ ((session->order = consttime_BN()) == NULL) || -+ ((session->prime = consttime_BN()) == NULL) || -+ ((qr = consttime_BN()) == NULL) || -+ ((qnr = consttime_BN()) == NULL) || -+ ((x_candidate = consttime_BN()) == NULL) || -+ ((y_sqrd = consttime_BN()) == NULL) || -+ ((y1 = consttime_BN()) == NULL) || -+ ((y2 = consttime_BN()) == NULL) || -+ ((y = consttime_BN()) == NULL) || -+ ((exp = consttime_BN()) == NULL)) { - DEBUG("unable to create bignums"); - goto fail; - } - -- if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) { -+ if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) { - DEBUG("unable to get prime for GFp curve"); - goto fail; - } -@@ -179,46 +320,80 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, - goto fail; - } - -- if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { -- DEBUG("unable to get cofactor for curve"); -- goto fail; -- } -- - primebitlen = BN_num_bits(session->prime); - primebytelen = BN_num_bytes(session->prime); - if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) { - DEBUG("unable to alloc space for prf buffer"); - goto fail; - } -+ if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { -+ DEBUG("unable to alloc space for x buffer"); -+ goto fail; -+ } -+ if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { -+ DEBUG("unable to alloc space for pm1 buffer"); -+ goto fail; -+ } -+ if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { -+ DEBUG("unable to alloc space for y1 buffer"); -+ goto fail; -+ } -+ if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { -+ DEBUG("unable to alloc space for y2 buffer"); -+ goto fail; -+ } -+ if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { -+ DEBUG("unable to alloc space for y buffer"); -+ goto fail; -+ } -+ -+ -+ /* -+ * derive random quadradic residue and quadratic non-residue -+ */ -+ do { -+ BN_rand_range(qr, session->prime); -+ } while (legendre(qr, session->prime, session->bnctx) != 1); -+ -+ do { -+ BN_rand_range(qnr, session->prime); -+ } while (legendre(qnr, session->prime, session->bnctx) != -1); -+ -+ if (!BN_sub(rnd, session->prime, BN_value_one())) { -+ goto fail; -+ } -+ BN_bn2bin(rnd, pm1buf); -+ -+ save_is_odd = 0; -+ found = 0; -+ memset(xbuf, 0, primebytelen); - ctr = 0; -- while (1) { -- if (ctr > 100) { -- DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num); -- goto fail; -- } -+ while (ctr < 40) { - ctr++; - - /* - * compute counter-mode password value and stretch to prime -- * pwd-seed = H(token | peer-id | server-id | password | -- * counter) -+ * pwd-seed = H(token | peer-id | server-id | password | -+ * counter) - */ -- H_Init(ctx); -- H_Update(ctx, (uint8_t *)token, sizeof(*token)); -- H_Update(ctx, (uint8_t const *)id_peer, id_peer_len); -- H_Update(ctx, (uint8_t const *)id_server, id_server_len); -- H_Update(ctx, (uint8_t const *)password, password_len); -- H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr)); -- H_Final(ctx, pwe_digest); -+ EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey); -+ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token)); -+ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len); -+ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len); -+ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len); -+ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr)); -+ -+ { -+ size_t mdlen = SHA256_DIGEST_LENGTH; -+ -+ EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen); -+ EVP_MD_CTX_reset(hmac_ctx); -+ } - - BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd); -- if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", -- strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) { -- DEBUG("key derivation function failed"); -- goto fail; -- } -+ eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", -+ strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen); - -- BN_bin2bn(prfbuf, primebytelen, x_candidate); - /* - * eap_pwd_kdf() returns a string of bits 0..primebitlen but - * BN_bin2bn will treat that string of bits as a big endian -@@ -226,49 +401,86 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, - * then excessive bits-- those _after_ primebitlen-- so now - * we have to shift right the amount we masked off. - */ -- if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8))); -- if (BN_ucmp(x_candidate, session->prime) >= 0) continue; -+ if (primebitlen % 8) { -+ rbits = 8 - (primebitlen % 8); -+ for (i = primebytelen - 1; i > 0; i--) { -+ prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits); -+ } -+ prfbuf[0] >>= rbits; -+ } -+ BN_bin2bn(prfbuf, primebytelen, x_candidate); - - /* -- * need to unambiguously identify the solution, if there is -- * one... -- */ -- is_odd = BN_is_odd(rnd) ? 1 : 0; -+ * it would've been better if the spec reduced the candidate -+ * modulo the prime but it didn't. So if the candidate >= prime -+ * we need to skip it but still run through the operations below -+ */ -+ cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen); -+ skip = const_time_fill_msb((unsigned int)cmp); - - /* -- * solve the quadratic equation, if it's not solvable then we -- * don't have a point -- */ -- if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) { -- continue; -- } -+ * need to unambiguously identify the solution, if there is -+ * one.. -+ */ -+ is_odd = BN_is_odd(rnd); - - /* -- * If there's a solution to the equation then the point must be -- * on the curve so why check again explicitly? OpenSSL code -- * says this is required by X9.62. We're not X9.62 but it can't -- * hurt just to be sure. -- */ -- if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) { -- DEBUG("EAP-pwd: point is not on curve"); -- continue; -- } -+ * check whether x^3 + a*x + b is a quadratic residue -+ * -+ * save the first quadratic residue we find in the loop but do -+ * it in constant time. -+ */ -+ do_equation(session->group, y_sqrd, x_candidate, session->bnctx); -+ qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx); - -- if (BN_cmp(cofactor, BN_value_one())) { -- /* make sure the point is not in a small sub-group */ -- if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe, -- cofactor, NULL)) { -- DEBUG("EAP-pwd: cannot multiply generator by order"); -- continue; -- } -+ /* -+ * if the candidate >= prime then we want to skip it -+ */ -+ qr_or_qnr = const_time_select(skip, 0, qr_or_qnr); - -- if (EC_POINT_is_at_infinity(session->group, session->pwe)) { -- DEBUG("EAP-pwd: point is at infinity"); -- continue; -- } -- } -- /* if we got here then we have a new generator. */ -- break; -+ /* -+ * if we haven't found PWE yet (found = 0) then mask will be true, -+ * if we have found PWE then mask will be false -+ */ -+ mask = const_time_select(found, 0, -1); -+ -+ /* -+ * save will be 1 if we want to save this value-- i.e. we haven't -+ * found PWE yet and this is a quadratic residue-- and 0 otherwise -+ */ -+ save = const_time_select(mask, qr_or_qnr, 0); -+ -+ /* -+ * mask will be true (-1) if we want to save this and false (0) -+ * otherwise -+ */ -+ mask = const_time_eq(save, 1); -+ -+ const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf); -+ save_is_odd = const_time_select(mask, is_odd, save_is_odd); -+ found = const_time_select(mask, -1, found); -+ } -+ -+ /* -+ * now we can savely construct PWE -+ */ -+ BN_bin2bn(xbuf, primebytelen, x_candidate); -+ do_equation(session->group, y_sqrd, x_candidate, session->bnctx); -+ if ( !BN_add(exp, session->prime, BN_value_one()) || -+ !BN_rshift(exp, exp, 2) || -+ !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) || -+ !BN_sub(y2, session->prime, y1) || -+ !BN_bn2bin(y1, y1buf) || -+ !BN_bn2bin(y2, y2buf)) { -+ DEBUG("unable to compute y"); -+ goto fail; -+ } -+ mask = const_time_eq(save_is_odd, BN_is_odd(y1)); -+ const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf); -+ if (BN_bin2bn(ybuf, primebytelen, y) == NULL || -+ !EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) { -+ DEBUG("unable to set point coordinate"); -+ goto fail; - } - - session->group_num = grp_num; -@@ -278,78 +490,89 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, - } - - /* cleanliness and order.... */ -- BN_clear_free(cofactor); - BN_clear_free(x_candidate); -+ BN_clear_free(y_sqrd); -+ BN_clear_free(qr); -+ BN_clear_free(qnr); - BN_clear_free(rnd); -- talloc_free(prfbuf); -- HMAC_CTX_free(ctx); -+ BN_clear_free(y1); -+ BN_clear_free(y2); -+ BN_clear_free(y); -+ BN_clear_free(exp); -+ -+ if (prfbuf) talloc_free(prfbuf); -+ if (xbuf) talloc_free(xbuf); -+ if (pm1buf) talloc_free(pm1buf); -+ if (y1buf) talloc_free(y1buf); -+ if (y2buf) talloc_free(y2buf); -+ if (ybuf) talloc_free(ybuf); -+ -+ EVP_MD_CTX_free(hmac_ctx); -+ EVP_PKEY_free(hmac_pkey); - - return ret; - } - --int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) { -+int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx) -+{ - BIGNUM *mask = NULL; - int ret = -1; - -- if (((session->private_value = BN_new()) == NULL) || -- ((session->my_element = EC_POINT_new(session->group)) == NULL) || -- ((session->my_scalar = BN_new()) == NULL) || -- ((mask = BN_new()) == NULL)) { -- DEBUG2("server scalar allocation failed"); -- goto fail; -- } -+ MEM(session->private_value = BN_new()); -+ MEM(session->my_element = EC_POINT_new(session->group)); -+ MEM(session->my_scalar = BN_new()); -+ -+ MEM(mask = BN_new()); - - if (BN_rand_range(session->private_value, session->order) != 1) { -- DEBUG2("Unable to get randomness for private_value"); -- goto fail; -+ REDEBUG("Unable to get randomness for private_value"); -+ goto error; - } - if (BN_rand_range(mask, session->order) != 1) { -- DEBUG2("Unable to get randomness for mask"); -- goto fail; -+ REDEBUG("Unable to get randomness for mask"); -+ goto error; - } - BN_add(session->my_scalar, session->private_value, mask); -- BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx); -+ BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx); - -- if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) { -- DEBUG2("server element allocation failed"); -- goto fail; -+ if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) { -+ REDEBUG("Server element allocation failed"); -+ goto error; - } - -- if (!EC_POINT_invert(session->group, session->my_element, bnctx)) { -- DEBUG2("server element inversion failed"); -- goto fail; -+ if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) { -+ REDEBUG("Server element inversion failed"); -+ goto error; - } - - ret = 0; - --fail: -+error: - BN_clear_free(mask); - - return ret; - } - --int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx) -+int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx) - { -- uint8_t *ptr; -- size_t data_len; -- BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; -- EC_POINT *K = NULL, *point = NULL; -- int res = 1; -- -- if (((session->peer_scalar = BN_new()) == NULL) || -- ((session->k = BN_new()) == NULL) || -- ((cofactor = BN_new()) == NULL) || -- ((x = BN_new()) == NULL) || -- ((y = BN_new()) == NULL) || -- ((point = EC_POINT_new(session->group)) == NULL) || -- ((K = EC_POINT_new(session->group)) == NULL) || -- ((session->peer_element = EC_POINT_new(session->group)) == NULL)) { -- DEBUG2("pwd: failed to allocate room to process peer's commit"); -- goto finish; -- } -+ uint8_t *ptr; -+ size_t data_len; -+ BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; -+ EC_POINT *K = NULL, *point = NULL; -+ int ret = 1; -+ -+ MEM(session->peer_scalar = BN_new()); -+ MEM(session->k = BN_new()); -+ MEM(session->peer_element = EC_POINT_new(session->group)); -+ MEM(point = EC_POINT_new(session->group)); -+ MEM(K = EC_POINT_new(session->group)); -+ -+ MEM(cofactor = BN_new()); -+ MEM(x = BN_new()); -+ MEM(y = BN_new()); - - if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { -- DEBUG2("pwd: unable to get group co-factor"); -+ REDEBUG("Unable to get group co-factor"); - goto finish; - } - -@@ -361,7 +584,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ - * Did the peer send enough data? - */ - if (in_len < (2 * data_len + BN_num_bytes(session->order))) { -- DEBUG("pwd: Invalid commit packet"); -+ REDEBUG("Invalid commit packet"); - goto finish; - } - -@@ -377,54 +600,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ - if (BN_is_zero(session->peer_scalar) || - BN_is_one(session->peer_scalar) || - BN_cmp(session->peer_scalar, session->order) >= 0) { -- ERROR("Peer's scalar is not within the allowed range"); -+ REDEBUG("Peer's scalar is not within the allowed range"); - goto finish; - } - -- if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { -- DEBUG2("pwd: unable to get coordinates of peer's element"); -+ if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { -+ REDEBUG("Unable to get coordinates of peer's element"); - goto finish; - } - - /* validate received element */ -- if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) || -+ if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) || - EC_POINT_is_at_infinity(session->group, session->peer_element)) { -- ERROR("Peer's element is not a point on the elliptic curve"); -+ REDEBUG("Peer's element is not a point on the elliptic curve"); - goto finish; - } - - /* check to ensure peer's element is not in a small sub-group */ - if (BN_cmp(cofactor, BN_value_one())) { - if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) { -- DEBUG2("pwd: unable to multiply element by co-factor"); -+ REDEBUG("Unable to multiply element by co-factor"); - goto finish; - } - - if (EC_POINT_is_at_infinity(session->group, point)) { -- DEBUG2("pwd: peer's element is in small sub-group"); -+ REDEBUG("Peer's element is in small sub-group"); - goto finish; - } - } - - /* detect reflection attacks */ - if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 || -- EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) { -- ERROR("Reflection attack detected"); -+ EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) { -+ REDEBUG("Reflection attack detected"); - goto finish; - } - - /* compute the shared key, k */ -- if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) || -- (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) || -- (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) { -- DEBUG2("pwd: unable to compute shared key, k"); -+ if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) || -+ (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) || -+ (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) { -+ REDEBUG("Unable to compute shared key, k"); - goto finish; - } - - /* ensure that the shared key isn't in a small sub-group */ - if (BN_cmp(cofactor, BN_value_one())) { - if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) { -- DEBUG2("pwd: unable to multiply k by co-factor"); -+ REDEBUG("Unable to multiply k by co-factor"); - goto finish; - } - } -@@ -436,15 +659,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ - * sure" so let's be safe. - */ - if (EC_POINT_is_at_infinity(session->group, K)) { -- DEBUG2("pwd: k is point-at-infinity!"); -+ REDEBUG("K is point-at-infinity"); - goto finish; - } - -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) { -- DEBUG2("pwd: unable to get shared secret from K"); -+ if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) { -+ REDEBUG("Unable to get shared secret from K"); - goto finish; - } -- res = 0; -+ ret = 0; - - finish: - EC_POINT_clear_free(K); -@@ -453,36 +676,29 @@ finish: - BN_clear_free(x); - BN_clear_free(y); - -- return res; -+ return ret; - } - --int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) -+int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) - { -- BIGNUM *x = NULL, *y = NULL; -- HMAC_CTX *ctx = NULL; -- uint8_t *cruft = NULL; -- int offset, req = -1; -- -- ctx = HMAC_CTX_new(); -- if (ctx == NULL) { -- DEBUG2("pwd: unable to allocate HMAC context!"); -- goto finish; -- } -+ BIGNUM *x = NULL, *y = NULL; -+ HMAC_CTX *hmac_ctx = NULL; -+ uint8_t *cruft = NULL; -+ int offset, req = -1; - - /* - * Each component of the cruft will be at most as big as the prime - */ -- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || -- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { -- DEBUG2("pwd: unable to allocate space to compute confirm!"); -- goto finish; -- } -+ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); -+ MEM(x = BN_new()); -+ MEM(y = BN_new()); - - /* - * commit is H(k | server_element | server_scalar | peer_element | - * peer_scalar | ciphersuite) - */ -- H_Init(ctx); -+ MEM(hmac_ctx = HMAC_CTX_new()); -+ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); - - /* - * Zero the memory each time because this is mod prime math and some -@@ -492,24 +708,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - */ - offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); - BN_bn2bin(session->k, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * next is server element: x, y - */ -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { -- DEBUG2("pwd: unable to get coordinates of server element"); -+ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { -+ REDEBUG("Unable to get coordinates of server element"); - goto finish; - } - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(x); - BN_bn2bin(x, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(y); - BN_bn2bin(y, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * and server scalar -@@ -517,25 +733,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); - BN_bn2bin(session->my_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); - - /* - * next is peer element: x, y - */ -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { -- DEBUG2("pwd: unable to get coordinates of peer's element"); -+ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { -+ REDEBUG("Unable to get coordinates of peer's element"); - goto finish; - } - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(x); - BN_bn2bin(x, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(y); - BN_bn2bin(y, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * and peer scalar -@@ -543,52 +759,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); - BN_bn2bin(session->peer_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); - - /* - * finally, ciphersuite - */ -- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); -+ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); - -- H_Final(ctx, out); -+ pwd_hmac_final(hmac_ctx, out); - - req = 0; -+ - finish: -+ HMAC_CTX_free(hmac_ctx); - talloc_free(cruft); - BN_free(x); - BN_free(y); -- HMAC_CTX_free(ctx); - - return req; - } - --int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) -+int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) - { -- BIGNUM *x = NULL, *y = NULL; -- HMAC_CTX *ctx = NULL; -- uint8_t *cruft = NULL; -- int offset, req = -1; -- -- ctx = HMAC_CTX_new(); -- if (ctx == NULL) { -- DEBUG2("pwd: unable to allocate HMAC context!"); -- goto finish; -- } -+ BIGNUM *x = NULL, *y = NULL; -+ HMAC_CTX *hmac_ctx = NULL; -+ uint8_t *cruft = NULL; -+ int offset, req = -1; - - /* - * Each component of the cruft will be at most as big as the prime - */ -- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || -- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { -- DEBUG2("pwd: unable to allocate space to compute confirm!"); -- goto finish; -- } -+ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); -+ MEM(x = BN_new()); -+ MEM(y = BN_new()); - - /* - * commit is H(k | server_element | server_scalar | peer_element | - * peer_scalar | ciphersuite) - */ -- H_Init(ctx); -+ MEM(hmac_ctx = HMAC_CTX_new()); -+ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); - - /* - * Zero the memory each time because this is mod prime math and some -@@ -598,25 +808,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - */ - offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); - BN_bn2bin(session->k, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * then peer element: x, y - */ -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { -- DEBUG2("pwd: unable to get coordinates of peer's element"); -+ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { -+ REDEBUG("Unable to get coordinates of peer's element"); - goto finish; - } - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(x); - BN_bn2bin(x, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(y); - BN_bn2bin(y, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * and peer scalar -@@ -624,24 +834,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); - BN_bn2bin(session->peer_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); - - /* - * then server element: x, y - */ -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { -- DEBUG2("pwd: unable to get coordinates of server element"); -+ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { -+ REDEBUG("Unable to get coordinates of server element"); - goto finish; - } - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(x); - BN_bn2bin(x, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(y); - BN_bn2bin(y, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - - /* - * and server scalar -@@ -649,94 +859,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); - BN_bn2bin(session->my_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); - - /* - * finally, ciphersuite - */ -- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); -+ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); - -- H_Final(ctx, out); -+ pwd_hmac_final(hmac_ctx, out); - - req = 0; - finish: -+ HMAC_CTX_free(hmac_ctx); - talloc_free(cruft); - BN_free(x); - BN_free(y); -- HMAC_CTX_free(ctx); - - return req; - } - --int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) -+int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) - { -- HMAC_CTX *ctx = NULL; -- uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL; -- uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; -- uint8_t msk_emsk[128]; /* 64 each */ -- int offset, ret = -1; -- -- ctx = HMAC_CTX_new(); -- if (ctx == NULL) { -- DEBUG2("pwd: unable to allocate HMAC context!"); -- goto finish; -- } -+ HMAC_CTX *hmac_ctx; -+ uint8_t mk[SHA256_DIGEST_LENGTH], *cruft; -+ uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; -+ uint8_t msk_emsk[128]; /* 64 each */ -+ int offset; - -- if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) { -- DEBUG2("pwd: unable to allocate space to compute keys"); -- goto finish; -- } -+ MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))); -+ MEM(hmac_ctx = HMAC_CTX_new()); - - /* - * first compute the session-id = TypeCode | H(ciphersuite | scal_p | - * scal_s) - */ - session_id[0] = PW_EAP_PWD; -- H_Init(ctx); -- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); -+ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); -+ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); - memset(cruft, 0, BN_num_bytes(session->prime)); - BN_bn2bin(session->peer_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); - offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); - memset(cruft, 0, BN_num_bytes(session->prime)); - BN_bn2bin(session->my_scalar, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->order)); -- H_Final(ctx, (uint8_t *)&session_id[1]); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); -+ pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]); - - /* then compute MK = H(k | commit-peer | commit-server) */ -- H_Init(ctx); -+ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); - - memset(cruft, 0, BN_num_bytes(session->prime)); - offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); - BN_bn2bin(session->k, cruft + offset); -- H_Update(ctx, cruft, BN_num_bytes(session->prime)); -+ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - -- H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH); -+ HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH); - -- H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH); -+ HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH); - -- H_Final(ctx, mk); -+ pwd_hmac_final(hmac_ctx, mk); - - /* stretch the mk with the session-id to get MSK | EMSK */ -- if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, -- SHA256_DIGEST_LENGTH + 1, msk_emsk, -- /* it's bits, ((64 + 64) * 8) */ -- 1024) != 0) { -- DEBUG("key derivation function failed"); -- goto finish; -- } -+ eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, -+ SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024); /* it's bits, ((64 + 64) * 8) */ - - memcpy(msk, msk_emsk, 64); - memcpy(emsk, msk_emsk + 64, 64); - -- ret = 0; --finish: -+ HMAC_CTX_free(hmac_ctx); - talloc_free(cruft); -- HMAC_CTX_free(ctx); -- return ret; -+ return 0; - } -- -- -- -- -diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h -index ca12778f61..a40a346069 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h -+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h -@@ -102,18 +102,22 @@ typedef struct _pwd_session_t { - EC_POINT *my_element; - EC_POINT *peer_element; - uint8_t my_confirm[SHA256_DIGEST_LENGTH]; -+ uint8_t prep; -+ uint8_t salt_present; -+ uint8_t salt_len; -+ uint8_t salt[255]; - } pwd_session_t; - --int compute_password_element(pwd_session_t *sess, uint16_t grp_num, -+int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num, - char const *password, int password_len, - char const *id_server, int id_server_len, - char const *id_peer, int id_peer_len, - uint32_t *token); --int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx); --int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); --int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); --int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); --int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm, -+int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx); -+int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); -+int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); -+int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); -+int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm, - uint8_t *msk, uint8_t *emsk); - #ifdef PRINTBUF - void print_buf(char *str, uint8_t *buf, int len); -diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c -index 18ab97f148..4992a2aeef 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c -+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c -@@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - #define MPPE_KEY_LEN 32 - #define MSK_EMSK_LEN (2*MPPE_KEY_LEN) - -+/* EAP-PWD can use different preprocessing (prep) modes to mangle the password -+ * before proving to both parties that they both know the same (mangled) password. -+ * -+ * The server advertises a preprocessing mode to the client. Only "none" is -+ * mandatory to implement. -+ * -+ * What is a good selection on the preprocessing mode? -+ * -+ * a) the server uses a hashed password -+ * b) the client uses a hashed password -+ * -+ * a | b | result -+ * --+---+--------------------------------------- -+ * n | n | none -+ * n | y | hint needed (cannot know automatically) -+ * y | n | select by hash given -+ * y | y | only works if both have the same hash; select by hash given -+ * -+ * Which hash functions does the server or client need to implement? -+ * -+ * a | b | server | client -+ * --+---+------------------------+---------------------- -+ * n | n | none | none -+ * n | y | as configured | none -+ * y | n | none | as selected by server -+ * y | y | none | none -+ * -+ * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement. -+ * Can we avoid implementing them all? Only if they are provided as hash by some -+ * other module, e.g. in SQL or statically in password database. -+ * -+ * Therefore we select the preprocessing mode by the type of password given if -+ * in automatic mode: -+ * a) Cleartext-Password or User-Password: None. -+ * If the client only supports a hash (e.g. on Windows it might only have an -+ * NT-Password), do not provide a Cleartext-Password attribute but instead -+ * preprocess the password externally (e.g. hash the Cleartext-Password -+ * into an NT-Password and drop the Cleartext-Password). -+ * b) NT-Password: rfc2759 (prep=MS). -+ * The NT-Password Hash is hashed into a HashNTPasswordHash hash. -+ * c) EAP-Pwd-Password-Hash - provides hash as binary -+ * EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client -+ * (RFC 8146) -+ * EAP-Pwd-Password-Prep - constant to transmit to client in prep field -+ * -+ * Though, there is one issue left. The method needs to be selected in -+ * EAP-PWD-ID/Request, that is the first message from server and thus before -+ * the client sent its peer-id. This is feasable using the EAP-Identity frame -+ * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway. -+ * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use -+ * only the peer_id (inner identity). This toogle is an integer to also support -+ * setting currently unknown nor not implemented preprocessing methods. -+ * -+ * The toogle is named "prep", is a module configuration item, and accepts the -+ * following values: -+ * prep | meaning -+ * -------+-------------------------------------------------------------------- -+ * -1 | [automatic] discover using method described above from EAP-Identity -+ * | as User-Name before EAP-PWD-Id/Request -+ * 0..255 | [static] Fixed password preprocessing method. Expects virtual -+ * | server to provide matching password given EAP-PWD -+ * | peer-id as User-Name. The virtual server is provided -+ * | with EAP-Pwd-Password-Prep containing the configured -+ * | prep value. -+ * else | reserved/invalid -+ * -+ * Attributes to provide Password/Password-Hash and possibly salt. -+ * prep | accepted attributes -+ * -------+-------------------------------------------------------------------- -+ * -1 | see above for automatic discovery -+ * 0 | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash -+ * 1 | Use NT-Password, Cleartext-Password, User-Password or -+ * | give hashed NT-Password hash in EAP-Pwd-Password-Hash -+ * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt. -+ * -+ * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex -+ * string, they are decoded as hex if module config option unhex=1 (default). -+ * Set it to zero if you provide binary input. -+ */ -+ - static CONF_PARSER pwd_module_config[] = { - { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" }, - { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" }, - { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL }, - { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL }, -+ { "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" }, -+ { "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" }, - CONF_PARSER_TERMINATOR - }; - -@@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance) - return -1; - } - -+ if (inst->prep < -1 || inst->prep > 255) { -+ cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep); -+ return -1; -+ } -+ - return 0; - } - -@@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds) - return 1; - } - -+static void normify(REQUEST *request, VALUE_PAIR *vp) -+{ -+ size_t decoded; -+ size_t expected_len; -+ uint8_t *buffer; -+ -+ rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING)); -+ -+ if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return; -+ -+ expected_len = vp->vp_length / 2; -+ buffer = talloc_zero_array(request, uint8_t, expected_len); -+ rad_assert(buffer); -+ -+ decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length); -+ if (decoded == expected_len) { -+ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes", -+ vp->da->name, vp->vp_length, decoded); -+ fr_pair_value_memcpy(vp, buffer, decoded); -+ } else { -+ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes", -+ vp->da->name, vp->vp_length, expected_len, decoded); -+ } -+ -+ talloc_free(buffer); -+} -+ -+static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) { -+ REQUEST *fake; -+ VALUE_PAIR *vp, *pw; -+ const char *pwbuf; -+ int pw_len; -+ uint8_t nthash[MD4_DIGEST_LENGTH]; -+ uint8_t nthashash[MD4_DIGEST_LENGTH]; -+ int ret = -1; -+ eap_type_t old_eap_type = 0; -+ -+ if ((fake = request_alloc_fake(request)) == NULL) { -+ RDEBUG("pwd unable to create fake request!"); -+ return ret; -+ } -+ fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); -+ if (!fake->username) { -+ RDEBUG("Failed creating pair for peer id"); -+ goto out; -+ } -+ fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); -+ fr_pair_add(&fake->packet->vps, fake->username); -+ -+ if (inst->prep >= 0) { -+ vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0); -+ rad_assert(vp != NULL); -+ vp->vp_byte = inst->prep; -+ fr_pair_add(&fake->packet->vps, vp); -+ } -+ -+ if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { -+ fake->server = vp->vp_strvalue; -+ } else if (inst->virtual_server) { -+ fake->server = inst->virtual_server; -+ } /* else fake->server == request->server */ -+ -+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { -+ /* EAP-Type = NAK here if inst->prep == -1. -+ * But this does not help the virtual server to differentiate -+ * based on which EAP method was selected, that is to properly -+ * prepare session-state: for PWD. -+ * So fake EAP-Type = PWD here for the time of the inner request. -+ */ -+ old_eap_type = vp->vp_integer; -+ vp->vp_integer = PW_EAP_PWD; -+ } -+ RDEBUG("Sending tunneled request"); -+ rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); -+ -+ if (fake->server) { -+ RDEBUG("server %s {", fake->server); -+ } else { -+ RDEBUG("server {"); -+ } -+ -+ /* -+ * Call authorization recursively, which will -+ * get the password. -+ */ -+ RINDENT(); -+ process_authorize(0, fake); -+ REXDENT(); -+ -+ /* -+ * Note that we don't do *anything* with the reply -+ * attributes. -+ */ -+ if (fake->server) { -+ RDEBUG("} # server %s", fake->server); -+ } else { -+ RDEBUG("}"); -+ } -+ -+ RDEBUG("Got tunneled reply code %d", fake->reply->code); -+ rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); -+ -+ if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { -+ vp->vp_integer = old_eap_type; -+ } -+ -+ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); -+ if (!pw) { -+ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); -+ } -+ -+ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) { -+ VERIFY_VP(pw); -+ session->prep = EAP_PWD_PREP_NONE; -+ -+ RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication", -+ session->peer_id); -+ -+ pwbuf = pw->vp_strvalue; -+ pw_len = pw->vp_length; -+ -+ goto success; -+ } -+ -+ pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY); -+ -+ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) { -+ VERIFY_VP(pw); -+ session->prep = EAP_PWD_PREP_MS; -+ -+ RDEBUG("Use NT-Password for %s to do pwd authentication", -+ session->peer_id); -+ -+ if (pw->vp_length != MD4_DIGEST_LENGTH) { -+ RDEBUG("NT-Password invalid length"); -+ goto out; -+ } -+ -+ fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length); -+ pwbuf = (const char*) nthashash; -+ pw_len = MD4_DIGEST_LENGTH; -+ -+ goto success; -+ } -+ -+ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); -+ if (!pw) { -+ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); -+ } -+ -+ if (pw && inst->prep == EAP_PWD_PREP_MS) { -+ VERIFY_VP(pw); -+ session->prep = EAP_PWD_PREP_NONE; -+ -+ RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication", -+ session->peer_id); -+ -+ // compute NT-Hash from Cleartext-Password -+ ssize_t len; -+ uint8_t ucs2_password[512]; -+ len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length); -+ if (len < 0) { -+ ERROR("rlm_eap_pwd: Error converting password to UCS2"); -+ goto out; -+ } -+ fr_md4_calc(nthash, ucs2_password, len); -+ -+ fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH); -+ pwbuf = (const char*) nthashash; -+ pw_len = MD4_DIGEST_LENGTH; -+ -+ goto success; -+ } -+ -+ vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY); -+ if (vp) { -+ VERIFY_VP(vp); -+ } -+ if (vp && inst->prep < 0) { -+ RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication", -+ vp->vp_byte, session->peer_id); -+ session->prep = vp->vp_byte; -+ } else if (vp && inst->prep != vp->vp_byte) { -+ RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s", -+ session->peer_id); -+ goto out; -+ } else if (inst->prep < 0) { -+ RDEBUG2("Missing EAP-Pwd-Password-Prep for %s", -+ session->peer_id); -+ goto out; -+ } -+ -+ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY); -+ if (pw) { -+ VERIFY_VP(pw); -+ -+ RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication", -+ session->peer_id); -+ -+ if (inst->unhex) normify(request, pw); -+ -+ if (pw->vp_length > 255) { -+ /* salt len is 1 byte */ -+ RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)"); -+ goto out; -+ } -+ rad_assert(pw->vp_length <= sizeof(session->salt)); -+ -+ session->salt_present = 1; -+ session->salt_len = pw->vp_length; -+ memcpy(session->salt, pw->vp_octets, pw->vp_length); -+ } -+ -+ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY); -+ if (pw) { -+ VERIFY_VP(pw); -+ -+ RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication", -+ session->peer_id); -+ -+ if (inst->unhex) normify(request, pw); -+ -+ pwbuf = (const char*) pw->vp_octets; -+ pw_len = pw->vp_length; -+ -+ goto success; -+ } -+ -+ RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s", -+ session->peer_id); -+ goto out; -+ -+success: -+ if (RDEBUG_ENABLED4) { -+ char outbuf[1024]; -+ char *p = outbuf; -+ for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) { -+ p += sprintf(p, "%02hhX", pwbuf[i]); -+ } -+ RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len); -+ } -+ -+ if (compute_password_element(request, session, session->group_num, -+ pwbuf, pw_len, -+ inst->server_id, strlen(inst->server_id), -+ session->peer_id, strlen(session->peer_id), -+ &session->token)) { -+ RDEBUG("failed to obtain password element"); -+ goto out; -+ } -+ -+ ret = 0; -+out: -+ talloc_free(fake); -+ return ret; -+} -+ - static int mod_session_init (void *instance, eap_handler_t *handler) - { - pwd_session_t *session; - eap_pwd_t *inst = (eap_pwd_t *)instance; - VALUE_PAIR *vp; - pwd_id_packet_t *packet; -+ REQUEST *request; - - if (!inst || !handler) { - ERROR("rlm_eap_pwd: Initiate, NULL data provided"); - return 0; - } - -+ request = handler->request; -+ if (!request) { -+ ERROR("rlm_eap_pwd: NULL request provided"); -+ return 0; -+ } -+ - /* - * make sure the server's been configured properly - */ -@@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler) - session->out_pos = 0; - handler->opaque = session; - -+ session->token = fr_rand(); -+ if (inst->prep < 0) { -+ RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity); -+ session->peer_id_len = strlen(handler->identity); -+ if (session->peer_id_len >= sizeof(session->peer_id)) { -+ RDEBUG("identity is malformed"); -+ return 0; -+ } -+ memcpy(session->peer_id, handler->identity, session->peer_id_len); -+ session->peer_id[session->peer_id_len] = '\0'; -+ -+ /* -+ * make fake request to get the password for the usable ID -+ * in order to identity prep -+ */ -+ if (fetch_and_process_password(session, handler->request, inst) < 0) { -+ RDEBUG("failed to find password for %s to do pwd authentication (init)", -+ session->peer_id); -+ return 0; -+ } -+ } else { -+ session->prep = inst->prep; -+ } -+ - /* - * construct an EAP-pwd-ID/Request - */ -@@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler) - packet->group_num = htons(session->group_num); - packet->random_function = EAP_PWD_DEF_RAND_FUN; - packet->prf = EAP_PWD_DEF_PRF; -- session->token = fr_rand(); - memcpy(packet->token, (char *)&session->token, 4); -- packet->prep = EAP_PWD_PREP_NONE; -+ packet->prep = session->prep; - memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) ); - - handler->stage = PROCESS; -@@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler) - pwd_session_t *session; - pwd_hdr *hdr; - pwd_id_packet_t *packet; -+ REQUEST *request; - eap_packet_t *response; -- REQUEST *request, *fake; -- VALUE_PAIR *pw, *vp; - EAP_DS *eap_ds; -- size_t in_len; -+ size_t in_len, peer_id_len; - int ret = 0; - eap_pwd_t *inst = (eap_pwd_t *)arg; - uint16_t offset; - uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; - uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; -+ char *peer_id; - - if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0; - -@@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - - if ((packet->prf != EAP_PWD_DEF_PRF) || - (packet->random_function != EAP_PWD_DEF_RAND_FUN) || -- (packet->prep != EAP_PWD_PREP_NONE) || -+ (packet->prep != session->prep) || - (CRYPTO_memcmp(packet->token, &session->token, 4)) || - (packet->group_num != ntohs(session->group_num))) { - RDEBUG2("pwd id response is invalid"); -@@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler) - ptr += sizeof(uint8_t); - *ptr = EAP_PWD_DEF_PRF; - -- session->peer_id_len = in_len - sizeof(pwd_id_packet_t); -- if (session->peer_id_len >= sizeof(session->peer_id)) { -+ peer_id_len = in_len - sizeof(pwd_id_packet_t); -+ if (peer_id_len >= sizeof(session->peer_id)) { - RDEBUG2("pwd id response is malformed"); - return 0; - } -+ peer_id = packet->identity; - -- memcpy(session->peer_id, packet->identity, session->peer_id_len); -- session->peer_id[session->peer_id_len] = '\0'; -- -- /* -- * make fake request to get the password for the usable ID -- */ -- if ((fake = request_alloc_fake(handler->request)) == NULL) { -- RDEBUG("pwd unable to create fake request!"); -- return 0; -- } -- fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); -- if (!fake->username) { -- RDEBUG("Failed creating pair for peer id"); -- talloc_free(fake); -- return 0; -- } -- fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); -- fr_pair_add(&fake->packet->vps, fake->username); -- -- if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { -- fake->server = vp->vp_strvalue; -- } else if (inst->virtual_server) { -- fake->server = inst->virtual_server; -- } /* else fake->server == request->server */ -- -- RDEBUG("Sending tunneled request"); -- rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); -- -- if (fake->server) { -- RDEBUG("server %s {", fake->server); -- } else { -- RDEBUG("server {"); -- } -+ if (inst->prep >= 0) { -+ /* -+ * make fake request to get the password for the usable ID -+ */ - -- /* -- * Call authorization recursively, which will -- * get the password. -- */ -- RINDENT(); -- process_authorize(0, fake); -- REXDENT(); -+ session->peer_id_len = peer_id_len; -+ memcpy(session->peer_id, peer_id, peer_id_len); -+ session->peer_id[peer_id_len] = '\0'; - -- /* -- * Note that we don't do *anything* with the reply -- * attributes. -- */ -- if (fake->server) { -- RDEBUG("} # server %s", fake->server); -+ if (fetch_and_process_password(session, request, inst) < 0) { -+ RDEBUG2("failed to find password for %s to do pwd authentication", -+ session->peer_id); -+ return 0; -+ } - } else { -- RDEBUG("}"); -+ /* verify inner identity == outer identity */ -+ if (session->peer_id_len != peer_id_len || -+ memcmp(session->peer_id, peer_id, peer_id_len) != 0) { -+ char buf[sizeof(session->peer_id)]; -+ memcpy(buf, peer_id, peer_id_len); -+ buf[peer_id_len] = '\0'; -+ -+ RDEBUG2("inner identity(peer_id) %s does not match outer identity %s", -+ buf, session->peer_id); -+ return 0; -+ } -+ RDEBUG2("inner identity matched for %s", session->peer_id); - } - -- RDEBUG("Got tunneled reply code %d", fake->reply->code); -- rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); -- -- if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) { -- DEBUG2("failed to find password for %s to do pwd authentication", -- session->peer_id); -- talloc_free(fake); -- return 0; -- } -- -- if (compute_password_element(session, session->group_num, -- pw->data.strvalue, strlen(pw->data.strvalue), -- inst->server_id, strlen(inst->server_id), -- session->peer_id, strlen(session->peer_id), -- &session->token)) { -- DEBUG2("failed to obtain password element"); -- talloc_free(fake); -- return 0; -- } -- TALLOC_FREE(fake); -- - /* - * compute our scalar and element - */ -- if (compute_scalar_element(session, session->bnctx)) { -+ if (compute_scalar_element(request, session, session->bnctx)) { - DEBUG2("failed to compute server's scalar and element"); - return 0; - } -@@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - /* - * element is a point, get both coordinates: x and y - */ -- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, -+ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, - session->bnctx)) { - DEBUG2("server point assignment failed"); - BN_clear_free(x); -@@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler) - * construct request - */ - session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); -+ if (session->salt_present) -+ session->out_len += 1 + session->salt_len; -+ - if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { - return 0; - } - memset(session->out, 0, session->out_len); - - ptr = session->out; -+ if (session->salt_present) { -+ *ptr = session->salt_len; -+ ptr++; -+ -+ memcpy(ptr, session->salt, session->salt_len); -+ ptr += session->salt_len; -+ } -+ - offset = BN_num_bytes(session->prime) - BN_num_bytes(x); - BN_bn2bin(x, ptr + offset); - BN_clear_free(x); -@@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - } - break; - -- case PWD_STATE_COMMIT: -+ case PWD_STATE_COMMIT: - if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { - RDEBUG2("pwd exchange is incorrect: not commit!"); - return 0; -@@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - /* - * process the peer's commit and generate the shared key, k - */ -- if (process_peer_commit(session, in, in_len, session->bnctx)) { -+ if (process_peer_commit(request, session, in, in_len, session->bnctx)) { - RDEBUG2("failed to process peer's commit"); - return 0; - } -@@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - /* - * compute our confirm blob - */ -- if (compute_server_confirm(session, session->my_confirm, session->bnctx)) { -+ if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) { - ERROR("rlm_eap_pwd: failed to compute confirm!"); - return 0; - } -@@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - RDEBUG2("pwd exchange is incorrect: not commit!"); - return 0; - } -- if (compute_peer_confirm(session, peer_confirm, session->bnctx)) { -+ if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) { - RDEBUG2("pwd exchange cannot compute peer's confirm"); - return 0; - } -@@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); - return 0; - } -- if (compute_keys(session, peer_confirm, msk, emsk)) { -+ if (compute_keys(request, session, peer_confirm, msk, emsk)) { - RDEBUG2("pwd exchange cannot generate (E)MSK!"); - return 0; - } -diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h -index 2264566bb6..966646c360 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h -+++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h -@@ -44,6 +44,8 @@ typedef struct _eap_pwd_t { - uint32_t fragment_size; - char const *server_id; - char const *virtual_server; -+ int32_t prep; -+ int32_t unhex; - } eap_pwd_t; - - #endif /* _RLM_EAP_PWD_H */ -diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c -index 4d41cd42e6..d327c575fc 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c -+++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c -@@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - static CONF_PARSER module_config[] = { - { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL }, - { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL }, -+ { "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL }, - CONF_PARSER_TERMINATOR - }; - -@@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) - return -1; - } - -+#ifdef TLS1_3_VERSION -+ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || -+ (inst->tls_conf->min_version == TLS1_3_VERSION)) { -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3"); -+ WARN("!! Please set tls_max_version = \"1.2\""); -+ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); -+ WARN("!! This limitation is likely to change in late 2021."); -+ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); -+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -+ } -+#endif -+ - return 0; - } - -@@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - tls_session_t *ssn; - rlm_eap_tls_t *inst; - REQUEST *request = handler->request; -+ bool require_client_cert = true; - - inst = type_arg; - - handler->tls = true; - - /* -- * EAP-TLS always requires a client certificate. -+ * Respect EAP-TLS-Require-Client-Cert, but only if -+ * enabled in the module configuration. -+ * -+ * We can't change behavior of existing systems, so this -+ * change has to be enabled via a new configuration -+ * option. - */ -- ssn = eaptls_session(handler, inst->tls_conf, true); -+ if (inst->configurable_client_cert) { -+ VALUE_PAIR *vp; -+ -+ vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY); -+ if (vp && !vp->vp_integer) require_client_cert = false; -+ } -+ -+ /* -+ * EAP-TLS always requires a client certificate, and -+ * allows for TLS 1.3 if permitted. -+ */ -+ ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true); - if (!ssn) { - return 0; - } - - handler->opaque = ((void *)ssn); -- -- /* -- * Set up type-specific information. -- */ -- ssn->prf_label = "client EAP encryption"; -+ ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */ - - /* - * TLS session initialization is over. Now handle TLS -@@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - 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, "")); - } else { -- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - - -@@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) - /* success */ - } - -+ /* -+ * Set the label to a fixed string. For TLS 1.3, -+ * the label is the same for all TLS-based EAP -+ * methods. -+ */ -+ tls_session->label = "client EAP encryption"; -+ - /* - * Success: Automatically return MPPE keys. - */ -diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h -index cebcb92f57..550cbbdce3 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h -+++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h -@@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t { - * Virtual server for checking certificates - */ - char const *virtual_server; -+ -+ /* -+ * Configurable EAP-TLS-Require-Client-Cert -+ */ -+ bool configurable_client_cert; - } rlm_eap_tls_t; - - #endif /* _RLM_EAP_TLS_H */ -diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c -index a3c575bceb..4e53c92244 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c -+++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c -@@ -181,7 +181,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - client_cert = inst->req_client_cert; - } - -- ssn = eaptls_session(handler, inst->tls_conf, client_cert); -+ /* -+ * Allow TLS 1.3, it works. -+ */ -+ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); - if (!ssn) { - return 0; - } -@@ -189,9 +192,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - handler->opaque = ((void *)ssn); - - /* -- * Set up type-specific information. -+ * Set the label to a fixed string. For TLS 1.3, the -+ * label is the same for all TLS-based EAP methods. If -+ * the client is using TLS 1.3, then eaptls_success() -+ * will over-ride this label with the correct label for -+ * TLS 1.3. - */ -- ssn->prf_label = "ttls keying material"; -+ ssn->label = "ttls keying material"; - - /* - * TLS session initialization is over. Now handle TLS -@@ -201,7 +208,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - if (status == 0) return 0; - -@@ -238,7 +245,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } else { -- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); -+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); - } - - /* -@@ -342,8 +349,7 @@ static int mod_process(void *arg, eap_handler_t *handler) - * Success: Automatically return MPPE keys. - */ - case PW_CODE_ACCESS_ACCEPT: -- ret = eaptls_success(handler, 0); -- goto done; -+ goto do_keys; - - /* - * No response packet, MUST be proxying it. -diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c -index 627d722ed7..cbe423951a 100644 ---- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c -+++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c -@@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - size_t size; - size_t data_left = data_len; - VALUE_PAIR *first = NULL; -- VALUE_PAIR *vp; -+ VALUE_PAIR *vp = NULL; - RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ - vp_cursor_t out; -+ DICT_ATTR const *da; - - fr_cursor_init(&out, &first); - -@@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - if (decoded < 0) { - REDEBUG2("diameter2vp failed decoding attr: %s", - fr_strerror()); -- goto do_octets; -+ goto raw; - } - - if ((size_t) decoded != size + 2) { - REDEBUG2("diameter2vp failed to entirely decode VSA"); - fr_pair_list_free(&vp); -- goto do_octets; -+ goto raw; - } - - fr_cursor_merge(&out, vp); -@@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - /* - * Create it. If this fails, it's because we're OOM. - */ -- do_octets: -- vp = fr_pair_afrom_num(packet, attr, vendor); -+ da = dict_attrbyvalue(attr, vendor); -+ if (!da) goto raw; -+ -+ vp = fr_pair_afrom_da(packet, da); - if (!vp) { - RDEBUG2("Failure in creating VP"); - fr_pair_list_free(&first); -@@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - case PW_TYPE_INTEGER: - case PW_TYPE_DATE: - if (size != vp->vp_length) { -- DICT_ATTR const *da; -- - /* - * Bad format. Create a "raw" - * attribute. -@@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - */ - if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || - ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { -- uint8_t challenge[16]; -+ uint8_t challenge[17]; - - if ((vp->vp_length < 8) || - (vp->vp_length > 16)) { -@@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, - return NULL; - } - -- eapttls_gen_challenge(ssl, challenge, -- sizeof(challenge)); -+ /* -+ * TLSv1.3 exports a different key depending on the length -+ * requested so ask for *exactly* what the spec requires -+ */ -+ eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1); - - if (memcmp(challenge, vp->vp_octets, - vp->vp_length) != 0) { -@@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se - */ - switch (reply->code) { - case PW_CODE_ACCESS_ACCEPT: -+ tls_session->authentication_success = true; - RDEBUG("Got tunneled Access-Accept"); - - rcode = RLM_MODULE_OK; -diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c -index e7dbfa685e..f7e23628e6 100644 ---- a/src/modules/rlm_exec/rlm_exec.c -+++ b/src/modules/rlm_exec/rlm_exec.c -@@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r - * If we're not waiting, then there are no output pairs. - */ - if (inst->output) { -- fr_pair_list_move(ctx, output_pairs, &answer); -+ fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD); - } - fr_pair_list_free(&answer); - -@@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - /* - * Always add the value-pairs to the reply. - */ -- fr_pair_list_move(request->reply, &request->reply->vps, &tmp); -+ fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD); - fr_pair_list_free(&tmp); - - finish: -diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c -index c449d7776b..f835800376 100644 ---- a/src/modules/rlm_expr/rlm_expr.c -+++ b/src/modules/rlm_expr/rlm_expr.c -@@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request, - return strlen(out); - } - -+/** Calculate the MD4 hash of a string or attribute. -+ * -+ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2" -+ */ -+static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request, -+ char const *fmt, char *out, size_t outlen) -+{ -+ uint8_t digest[16]; -+ ssize_t i, len, inlen; -+ uint8_t const *p; -+ FR_MD4_CTX ctx; -+ -+ /* -+ * We need room for at least one octet of output. -+ */ -+ if (outlen < 3) { -+ *out = '\0'; -+ return 0; -+ } -+ -+ inlen = xlat_fmt_to_ref(&p, request, fmt); -+ if (inlen < 0) { -+ return -1; -+ } -+ -+ fr_md4_init(&ctx); -+ fr_md4_update(&ctx, p, inlen); -+ fr_md4_final(digest, &ctx); -+ -+ /* -+ * Each digest octet takes two hex digits, plus one for -+ * the terminating NUL. -+ */ -+ len = (outlen / 2) - 1; -+ if (len > 16) len = 16; -+ -+ for (i = 0; i < len; i++) { -+ snprintf(out + i * 2, 3, "%02x", digest[i]); -+ } -+ -+ return strlen(out); -+} -+ - /** Calculate the SHA1 hash of a string or attribute. - * - * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" -@@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request, - return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now)); - } - -+/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s). -+ * -+ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520. -+ */ -+static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request, -+ char const *fmt, char *out, size_t outlen) -+{ -+ long num; -+ -+ char const *p; -+ char *q; -+ time_t now; -+ struct tm *local, local_buff; -+ -+ now = time(NULL); -+ local = localtime_r(&now, &local_buff); -+ -+ p = fmt; -+ -+ num = strtoul(p, &q, 10); -+ if (!q || *q == '\0') { -+ REDEBUG("nexttime: must be followed by period specifier (h|d|w|m|y)"); -+ return -1; -+ } -+ -+ if (p == q) { -+ num = 1; -+ } else { -+ p += q - p; -+ } -+ -+ local->tm_sec = 0; -+ local->tm_min = 0; -+ -+ switch (*p) { -+ case 'h': -+ local->tm_hour -= num; -+ break; -+ -+ case 'd': -+ local->tm_hour = 0; -+ local->tm_mday -= num; -+ break; -+ -+ case 'w': -+ local->tm_hour = 0; -+ local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1)); -+ break; -+ -+ case 'm': -+ local->tm_hour = 0; -+ local->tm_mday = 1; -+ local->tm_mon -= num; -+ break; -+ -+ case 'y': -+ local->tm_hour = 0; -+ local->tm_mday = 1; -+ local->tm_mon = 0; -+ local->tm_year -= num; -+ break; -+ -+ default: -+ REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p); -+ return -1; -+ } -+ -+ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local))); -+} -+ - - /* - * Parse the 3 arguments to lpad / rpad. -@@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) - xlat_register("unescape", unescape_xlat, NULL, inst); - xlat_register("tolower", tolower_xlat, NULL, inst); - xlat_register("toupper", toupper_xlat, NULL, inst); -+ xlat_register("md4", md4_xlat, NULL, inst); - xlat_register("md5", md5_xlat, NULL, inst); - xlat_register("sha1", sha1_xlat, NULL, inst); - #ifdef HAVE_OPENSSL_EVP_H -@@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) - xlat_register("explode", explode_xlat, NULL, inst); - - xlat_register("nexttime", next_time_xlat, NULL, inst); -+ xlat_register("lasttime", last_time_xlat, NULL, inst); - xlat_register("lpad", lpad_xlat, NULL, inst); - xlat_register("rpad", rpad_xlat, NULL, inst); - -diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c -index c825a9230b..9e77cd7ff1 100644 ---- a/src/modules/rlm_files/rlm_files.c -+++ b/src/modules/rlm_files/rlm_files.c -@@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * - /* ctx may be reply or proxy */ - reply_tmp = fr_pair_list_copy(reply_packet, pl->reply); - radius_pairmove(request, &reply_packet->vps, reply_tmp, true); -- fr_pair_list_move(request, &request->config, &check_tmp); -+ fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD); - fr_pair_list_free(&check_tmp); - - /* -diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h -index 7dc874d35e..29e7cfd757 100644 ---- a/src/modules/rlm_ldap/ldap.h -+++ b/src/modules/rlm_ldap/ldap.h -@@ -20,6 +20,7 @@ - * always need to support that. - */ - #define LDAP_DEPRECATED 1 -+USES_APPLE_DEPRECATED_API /* Apple wants us to use OpenDirectory Framework, we don't want that */ - #include - #include - #include "config.h" -@@ -265,6 +266,9 @@ typedef struct ldap_instance { - - int tls_require_cert; //!< OpenLDAP constant representing the require cert string. - -+ char const *tls_min_version_str; //!< Minimum TLS version -+ int tls_min_version; -+ - /* - * Options - */ -diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c -index 83c84a63e2..25bcd9c44e 100644 ---- a/src/modules/rlm_mschap/rlm_mschap.c -+++ b/src/modules/rlm_mschap/rlm_mschap.c -@@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request, - data = response->vp_octets + 2; - data_len = 24; - -+ /* -+ * Pull the domain name out of the User-Name, if it exists. -+ * -+ * This is the full domain name, not just the name after host/ -+ */ -+ } else if (strncasecmp(fmt, "Domain-Name", 11) == 0) { -+ char *p; -+ -+ user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); -+ if (!user_name) { -+ REDEBUG("No User-Name was found in the request"); -+ return -1; -+ } -+ -+ /* -+ * First check to see if this is a host/ style User-Name -+ * (a la Kerberos host principal) -+ */ -+ if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) { -+ /* -+ * If we're getting a User-Name formatted in this way, -+ * it's likely due to PEAP. The Windows Domain will be -+ * the first domain component following the hostname, -+ * or the machine name itself if only a hostname is supplied -+ */ -+ p = strchr(user_name->vp_strvalue, '.'); -+ if (!p) { -+ RDEBUG2("setting NT-Domain to same as machine name"); -+ strlcpy(out, user_name->vp_strvalue + 5, outlen); -+ } else { -+ p++; /* skip the period */ -+ strlcpy(out, p, outlen); -+ } -+ } else { -+ p = strchr(user_name->vp_strvalue, '\\'); -+ if (!p) { -+ REDEBUG("No NT-Domain was found in the User-Name"); -+ return -1; -+ } -+ -+ /* -+ * Hack. This is simpler than the alternatives. -+ */ -+ *p = '\0'; -+ strlcpy(out, user_name->vp_strvalue, outlen); -+ *p = '\\'; -+ } -+ -+ return strlen(out); -+ - /* - * Pull the NT-Domain out of the User-Name, if it exists. - */ -@@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) - return -1; - } - #else -- cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time"); -+ cf_log_err_cs(conf, "'winbind' is not enabled in this build."); - return -1; - #endif - } -@@ -942,7 +992,6 @@ ntlm_auth_err: - ssize_t result_len; - char result[253]; - uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH]; -- RC4_KEY key; - - if (!nt_password) { - RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute"); -@@ -951,11 +1000,47 @@ ntlm_auth_err: - RDEBUG("Doing MS-CHAPv2 password change locally"); - } - -- /* -- * Decrypt the blob -- */ -- RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); -- RC4(&key, 516, new_nt_password, nt_pass_decrypted); -+#if OPENSSL_VERSION_NUMBER >= 0x30000000L -+ { -+ EVP_CIPHER_CTX *ctx; -+ int ntlen = sizeof(nt_pass_decrypted); -+ -+ ctx = EVP_CIPHER_CTX_new(); -+ if (!ctx) { -+ REDEBUG("Failed getting RC4 from OpenSSL"); -+ error: -+ if (ctx) EVP_CIPHER_CTX_free(ctx); -+ return -1; -+ } -+ -+ if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { -+ REDEBUG("Failed setting key length"); -+ goto error; -+ } -+ -+ if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { -+ REDEBUG("Failed setting key value"); -+ goto error;; -+ } -+ -+ if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { -+ REDEBUG("Failed getting output"); -+ goto error; -+ } -+ -+ EVP_CIPHER_CTX_free(ctx); -+ } -+#else -+ { -+ RC4_KEY key; -+ -+ /* -+ * Decrypt the blob -+ */ -+ RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); -+ RC4(&key, 516, new_nt_password, nt_pass_decrypted); -+ } -+#endif - - /* - * pwblock is -diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c -index 399689abf3..932e44abe9 100644 ---- a/src/modules/rlm_otp/otp_mppe.c -+++ b/src/modules/rlm_otp/otp_mppe.c -@@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ - - #include - -+#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) -+#define UNUSED3 -+#else -+#define UNUSED3 UNUSED -+#endif -+ - /* - * Add MPPE attributes to a request, if required. - */ --void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode) -+void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode) - { - VALUE_PAIR *cvp, *rvp; - -@@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const - case PWE_CHAP: - return; - -+#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) - case PWE_MSCHAP: - /* First, set some related attributes. */ - pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ); -@@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const - - break; /* PWE_MSCHAP2 */ - } /* PWE_MSCHAP2 */ -- -+#else -+ case PWE_MSCHAP: -+ case PWE_MSCHAP2: -+ REDEBUG("MS-CHAP is unsupported for OpenSSL 3."); -+ break; -+#endif - } /* switch (pwe) */ - - return; -diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c -index 66fd8b4987..256437a552 100644 ---- a/src/modules/rlm_otp/otp_radstate.c -+++ b/src/modules/rlm_otp/otp_radstate.c -@@ -113,6 +113,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], - HMAC_CTX *hmac_ctx; - uint8_t hmac[MD5_DIGEST_LENGTH]; - char *p; -+ unsigned int len = sizeof(hmac); - - /* - * Generate the hmac. We already have a dependency on openssl for -@@ -125,7 +126,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], - HMAC_Update(hmac_ctx, (uint8_t const *) challenge, clen); - HMAC_Update(hmac_ctx, (uint8_t *) &flags, 4); - HMAC_Update(hmac_ctx, (uint8_t *) &when, 4); -- HMAC_Final(hmac_ctx, hmac, NULL); -+ HMAC_Final(hmac_ctx, hmac, &len); - HMAC_CTX_free(hmac_ctx); - - /* -diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c -index 8b85df0662..fcb3fd11fc 100644 ---- a/src/modules/rlm_rest/rest.c -+++ b/src/modules/rlm_rest/rest.c -@@ -115,6 +115,15 @@ do {\ - }\ - } while (0) - -+/* -+ * that macro is originally declared in include/curl/curlver.h -+ * We have to use this as curl uses lots of enums -+ */ -+#ifndef CURL_AT_LEAST_VERSION -+# define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z)) -+# define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) -+#endif -+ - const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = { - 0, // HTTP_AUTH_UNKNOWN - 0, // HTTP_AUTH_NONE -@@ -221,6 +230,40 @@ const FR_NAME_NUMBER http_content_type_table[] = { - { NULL , -1 } - }; - -+/** Conversion table for "HTTP" protocol version to use. -+ * -+ * Used by rlm_rest_t for specify the http client version. -+ * -+ * Values we expect to use in curl_easy_setopt() -+ * -+ * @see fr_str2int -+ * @see fr_int2str -+ */ -+const FR_NAME_NUMBER http_negotiation_table[] = { -+ -+ { "1.0", CURL_HTTP_VERSION_1_0 }, //!< Enforce HTTP 1.0 requests. -+ { "1.1", CURL_HTTP_VERSION_1_1 }, //!< Enforce HTTP 1.1 requests. -+/* -+ * These are all enum values -+ */ -+#if CURL_AT_LEAST_VERSION(7,49,0) -+ { "2.0", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, //!< Enforce HTTP 2.0 requests. -+#endif -+#if CURL_AT_LEAST_VERSION(7,33,0) -+ { "2.0+auto", CURL_HTTP_VERSION_2_0 }, //!< Attempt HTTP 2 requests. libcurl will fall back -+ ///< to HTTP 1.1 if HTTP 2 can't be negotiated with the -+ ///< server. (Added in 7.33.0) -+#endif -+#if CURL_AT_LEAST_VERSION(7,47,0) -+ { "2.0+tls", CURL_HTTP_VERSION_2TLS }, //!< Attempt HTTP 2 over TLS (HTTPS) only. -+ ///< libcurl will fall back to HTTP 1.1 if HTTP 2 -+ ///< can't be negotiated with the HTTPS server. -+ ///< For clear text HTTP servers, libcurl will use 1.1. -+#endif -+ { "default", CURL_HTTP_VERSION_NONE } //!< We don't care about what version the library uses. -+ ///< libcurl will use whatever it thinks fit. -+}; -+ - /* - * Encoder specific structures. - * @todo split encoders/decoders into submodules. -@@ -603,19 +646,30 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd - } - - /* -- * there are more attributes, insert a separator -+ * there are no more attributes, stop - */ -- if (fr_cursor_next(&ctx->cursor)) { -- if (freespace < 1) goto no_space; -- *p++ = '&'; -- freespace--; -+ if (!fr_cursor_next_peek(&ctx->cursor)) { -+ ctx->state = READ_STATE_END; -+ break; - } - -+ if (freespace < 1) goto no_space; -+ *p++ = '&'; -+ freespace--; -+ /* -+ * Only advance once we have a separator -+ * really we should have an additional -+ * state for encoding the separator, -+ * but, we don't, and v3.0.x is stable -+ * so let's do the easiest fix with the -+ * lowest risk. -+ */ -+ fr_cursor_next(&ctx->cursor); -+ - /* - * We wrote one full attribute value pair, record progress. - */ - encoded = p; -- - ctx->state = READ_STATE_ATTR_BEGIN; - } - -@@ -747,7 +801,13 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd - - type = fr_int2str(dict_attr_types, vp->da->type, ""); - -- 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/ [ ]\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 - -+#ifdef HAVE_WDOCUMENTATION -+DIAG_OFF(documentation) -+#endif -+ - #ifdef HAVE_JSON - # if defined(HAVE_JSONMC_JSON_H) - # include -@@ -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 -+#include -+ -+#include -+#include -+#include -+#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 -+ -+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 -+ -+/* -+ * 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 - #include -+#include "milenage.h" - - #ifdef HAVE_OPENSSL_HMAC_H - #include - #endif - -+#include -+ -+#define WIMAX_EPSAKA_RAND_SIZE 16 -+#define WIMAX_EPSAKA_KI_SIZE 16 -+#define WIMAX_EPSAKA_OPC_SIZE 16 -+#define WIMAX_EPSAKA_AMF_SIZE 2 -+#define WIMAX_EPSAKA_SQN_SIZE 6 -+#define WIMAX_EPSAKA_MAC_A_SIZE 8 -+#define WIMAX_EPSAKA_MAC_S_SIZE 8 -+#define WIMAX_EPSAKA_XRES_SIZE 8 -+#define WIMAX_EPSAKA_CK_SIZE 16 -+#define WIMAX_EPSAKA_IK_SIZE 16 -+#define WIMAX_EPSAKA_AK_SIZE 6 -+#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 -+#define WIMAX_EPSAKA_KK_SIZE 32 -+#define WIMAX_EPSAKA_KS_SIZE 14 -+#define WIMAX_EPSAKA_PLMN_SIZE 3 -+#define WIMAX_EPSAKA_KASME_SIZE 32 -+#define WIMAX_EPSAKA_AUTN_SIZE 16 -+#define WIMAX_EPSAKA_AUTS_SIZE 14 -+ - /* - * FIXME: Fix the build system to create definitions from names. - */ - typedef struct rlm_wimax_t { - bool delete_mppe_keys; -+ -+ DICT_ATTR const *resync_info; -+ DICT_ATTR const *xres; -+ DICT_ATTR const *autn; -+ DICT_ATTR const *kasme; - } rlm_wimax_t; - - /* -@@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = { - CONF_PARSER_TERMINATOR - }; - -+/* -+ * Print hex values in a readable format for debugging -+ * Example: -+ * FOO: 00 11 AA 22 00 FF -+ */ -+static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) -+{ -+ int i; -+ char buffer[256]; /* large enough for largest len */ -+ -+ /* -+ * Leave a trailing space, we don't really care about that. -+ */ -+ for (i = 0; i < len; i++) { -+ snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]); -+ } -+ -+ RDEBUG("%s %s", prefix, buffer); -+} -+#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex -+ - /* - * Find the named user in this modules database. Create the set - * of attribute-value pairs to check and reply with for this user - * from the database. The authentication code only needs to check - * the password, the rest is done here. - */ --static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request) -+static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) - { - VALUE_PAIR *vp; -+ rlm_wimax_t *inst = instance; - - /* - * Fix Calling-Station-Id. Damn you, WiMAX! -@@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST - return RLM_MODULE_OK; - } - -+ /* -+ * Check for attr WiMAX-Re-synchronization-Info -+ * which contains the concatenation of RAND and AUTS -+ * -+ * If it is present then we proceed to verify the SIM and -+ * extract the new value of SQN -+ */ -+ VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand; -+ int m_ret; -+ -+ /* Look for the Re-synchronization-Info attribute in the request */ -+ resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY); -+ if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) { -+ RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it"); -+ resync_info = NULL; -+ } -+ -+ /* -+ * These are the private keys which should be added to the control -+ * list after looking them up in a database by IMSI -+ * -+ * We grab them from the control list here -+ */ -+ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); -+ if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it"); -+ ki = NULL; -+ } -+ -+ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); -+ if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it"); -+ opc = NULL; -+ } -+ -+ /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */ -+ if (resync_info && ki && opc) { -+ uint64_t sqn_bin; -+ uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE]; -+ uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE]; -+ -+ RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync"); -+ -+ /* Split Re-synchronization-Info into seperate RAND and AUTS */ -+ -+ memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE); -+ memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE); -+ -+ RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE); -+ RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE); -+ -+ /* -+ * This procedure uses the secret keys Ki and OPc to authenticate -+ * the SIM and extract the SQN -+ */ -+ m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin); -+ -+ /* -+ * If the SIM verification fails then we can't go any further as -+ * we don't have the keys. And that probably means something bad -+ * is happening so we bail out now -+ */ -+ if (m_ret < 0) { -+ RDEBUG("SIM verification failed"); -+ return RLM_MODULE_REJECT; -+ } -+ -+ /* -+ * If we got this far it means have got a new SQN and RAND -+ * so we store them in: -+ * control:WiMAX-SIM-SQN -+ * control:WiMAX-SIM-RAND -+ * -+ * From there they can be grabbed by unlang and used later -+ */ -+ -+ /* SQN is six bytes so we extract what we need from the 64 bit variable */ -+ uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = { -+ (sqn_bin & 0x0000FF0000000000ull) >> 40, -+ (sqn_bin & 0x000000FF00000000ull) >> 32, -+ (sqn_bin & 0x00000000FF000000ull) >> 24, -+ (sqn_bin & 0x0000000000FF0000ull) >> 16, -+ (sqn_bin & 0x000000000000FF00ull) >> 8, -+ (sqn_bin & 0x00000000000000FFull) >> 0 -+ }; -+ -+ /* Add SQN to control:WiMAX-SIM-SQN */ -+ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); -+ if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it"); -+ sqn = NULL; -+ } -+ -+ if (!sqn) { -+ MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET)); -+ fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE); -+ } -+ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE); -+ -+ /* Add RAND to control:WiMAX-SIM-RAND */ -+ rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); -+ if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it"); -+ rand = NULL; -+ } -+ -+ if (!rand) { -+ MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET)); -+ fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE); -+ } -+ RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE); -+ -+ return RLM_MODULE_UPDATED; -+ } -+ - return RLM_MODULE_NOOP; - } - -- - /* - * Massage the request before recording it or proxying it - */ -@@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request - return mod_authorize(instance, request); - } - --/* -- * Write accounting information to this modules database. -- */ --static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request) --{ -- return RLM_MODULE_OK; --} - - /* -- * Generate the keys after the user has been authenticated. -+ * This function generates the keys for old style WiMAX (v1 to v2.0) - */ --static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) -+static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk) - { - rlm_wimax_t *inst = instance; -- VALUE_PAIR *msk, *emsk, *vp; -+ VALUE_PAIR *vp; - VALUE_PAIR *mn_nai, *ip, *fa_rk; - HMAC_CTX *hmac; - unsigned int rk1_len, rk2_len, rk_len; -@@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; - uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; - -- msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); -- emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); -- if (!msk || !emsk) { -- RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys"); -- return RLM_MODULE_NOOP; -- } -- - /* - * If we delete the MS-MPPE-*-Key attributes, then add in - * the WiMAX-MSK so that the client has a key available. -@@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); - fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); - -- vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ); -- if (vp) { -- fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); -- } -+ MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ)); -+ fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); - } - - /* -@@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - */ - hmac = HMAC_CTX_new(); - HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); -+ rk1_len = SHA256_DIGEST_LENGTH; - - HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); -@@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - - HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len); - HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); -+ rk2_len = SHA256_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_2[0], &rk2_len); - - memcpy(mip_rk, mip_rk_1, rk1_len); -@@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL); - - HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12); -+ rk1_len = SHA256_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); - - /* -@@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - (mip_rk_1[2] << 8) | mip_rk_1[3]); - if (mip_spi < 256) mip_spi += 256; - -- if (rad_debug_lvl) { -- int len = rk_len; -- char buffer[512]; -- -- if (len > 128) len = 128; /* buffer size */ -- -- fr_bin2hex(buffer, mip_rk, len); -- RDEBUG("MIP-RK = 0x%s", buffer); -- RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); -- } -+ RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len); -+ RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); - - /* - * FIXME: Perform SPI collision prevention -@@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11); - HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); - HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); -+ rk1_len = SHA1_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); - - /* -@@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11); - HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); - HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); -+ rk1_len = SHA1_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); - - /* -@@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11); - HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16); - HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); -+ rk1_len = SHA1_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); - - /* -@@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - - HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5); - -+ rk1_len = SHA1_DIGEST_LENGTH; - HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); - - fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len); -@@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque - return RLM_MODULE_UPDATED; - } - -+/* -+ * Generate the EPS-AKA authentication vector -+ * -+ * These are the keys needed for new style WiMAX (LTE / 3gpp authentication), -+ for WiMAX v2.1 -+ */ -+static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc, -+ VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn) -+{ -+ size_t i; -+ VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme; -+ -+ /* -+ * For most authentication requests we need to generate a fresh RAND -+ * -+ * The exception is after SQN re-syncronisation - in this case we -+ * get RAND in the request, and this module if called in authorize should -+ * have put it in control:WiMAX-SIM-RAND so we can grab it from there) -+ */ -+ rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); -+ if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it."); -+ rand_previous = NULL; -+ } -+ -+ MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET)); -+ if (!rand_previous) { -+ uint32_t lvalue; -+ uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE]; -+ -+ for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) { -+ lvalue = fr_rand(); -+ memcpy(buffer + i * 4, &lvalue, sizeof(lvalue)); -+ } -+ -+ fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE); -+ -+ } else { -+ fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE); -+ } -+ -+ /* -+ * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5) -+ * which returns AUTN, AK, CK, IK, XRES. -+ */ -+ uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE]; -+ uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE]; -+ uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE]; -+ uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE]; -+ uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE]; -+ -+ /* But first convert uint8 SQN to uint64 */ -+ uint64_t sqn_bin = 0x0000000000000000; -+ for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i]; -+ -+ if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it"); -+ return RLM_MODULE_NOOP; -+ } -+ if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it"); -+ return RLM_MODULE_NOOP; -+ } -+ if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) { -+ RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it"); -+ return RLM_MODULE_NOOP; -+ } -+ -+ /* Call milenage */ -+ milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets, -+ amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets); -+ -+ /* -+ * Now we genertate KASME -+ * -+ * Officially described in 33401-g30.doc section A.2 -+ * But an easier to read explanation can be found at: -+ * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec -+ * -+ */ -+ -+ /* k = CK || IK */ -+ uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE]; -+ memcpy(kk_bin, ck_bin, sizeof(ck_bin)); -+ memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin)); -+ -+ /* Initialize a 14 byte buffer s */ -+ uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE]; -+ -+ /* Assign the first byte of s as 0x10 */ -+ ks_bin[0] = 0x10; -+ -+ /* Copy the 3 bytes of PLMN into s */ -+ memcpy(ks_bin + 1, plmn->vp_octets, 3); -+ -+ /* Assign 5th and 6th byte as 0x00 and 0x03 */ -+ ks_bin[4] = 0x00; -+ ks_bin[5] = 0x03; -+ -+ /* Assign the next 6 bytes as SQN XOR AK */ -+ for (i = 0; i < 6; i++) { -+ ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i]; -+ } -+ -+ /* Assign the last two bytes as 0x00 and 0x06 */ -+ ks_bin[12] = 0x00; -+ ks_bin[13] = 0x06; -+ -+ /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */ -+ uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE]; -+ HMAC_CTX *hmac; -+ unsigned int kasme_len = sizeof(kasme_bin); -+ -+ hmac = HMAC_CTX_new(); -+ HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL); -+ HMAC_Update(hmac, ks_bin, sizeof(ks_bin)); -+ kasme_len = SHA256_DIGEST_LENGTH; -+ HMAC_Final(hmac, &kasme_bin[0], &kasme_len); -+ HMAC_CTX_free(hmac); -+ -+ /* -+ * Add reply attributes XRES, AUTN and KASME (RAND we added earlier) -+ * -+ * Note that we can't call fr_pair_find_by_num(), as -+ * these attributes are buried deep inside of the WiMAX -+ * hierarchy. -+ */ -+ xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY); -+ if (!xres) { -+ MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET)); -+ fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE); -+ } -+ -+ autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY); -+ if (!autn) { -+ MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET)); -+ fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE); -+ } -+ -+ kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY); -+ if (!kasme) { -+ MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET)); -+ fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE); -+ } -+ -+ /* Print keys to log for debugging */ -+ if (rad_debug_lvl) { -+ RDEBUG("-------- Milenage in --------"); -+ RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length); -+ RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length); -+ RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length); -+ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length); -+ RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length); -+ RDEBUG("-------- Milenage out -------"); -+ RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length); -+ RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin)); -+ RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin)); -+ RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin)); -+ RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length); -+ RDEBUG("-----------------------------"); -+ RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin)); -+ RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin)); -+ RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length); -+ } -+ -+ return RLM_MODULE_UPDATED; -+} -+ -+/* -+ * Generate the keys after the user has been authenticated. -+ */ -+static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) -+{ -+ VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn; -+ -+ /* -+ * If we have MSK and EMSK then assume we want MIP keys -+ * Else if we have the SIM keys then we want the EPS-AKA vector -+ */ -+ -+ msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); -+ emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); -+ -+ if (msk && emsk) { -+ RDEBUG("MSK and EMSK found. Generating MIP keys"); -+ return mip_keys_generate(instance, request, msk, emsk); -+ } -+ -+ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); -+ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); -+ amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY); -+ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); -+ plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY); -+ -+ if (ki && opc && amf && sqn && plmn) { -+ RDEBUG("AKA attributes found. Generating AKA keys."); -+ return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn); -+ } -+ -+ RDEBUG("Input keys not found. Cannot create WiMAX keys"); -+ return RLM_MODULE_NOOP; -+} -+ -+ -+static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) -+{ -+ rlm_wimax_t *inst = instance; -+ -+ inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info"); -+ inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES"); -+ inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN"); -+ inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME"); -+ -+ return 0; -+} - - /* - * The module name should be the only globally exported symbol. -@@ -472,10 +833,10 @@ module_t rlm_wimax = { - .type = RLM_TYPE_THREAD_SAFE, - .inst_size = sizeof(rlm_wimax_t), - .config = module_config, -+ .instantiate = mod_instantiate, - .methods = { - [MOD_AUTHORIZE] = mod_authorize, - [MOD_PREACCT] = mod_preacct, -- [MOD_ACCOUNTING] = mod_accounting, - [MOD_POST_AUTH] = mod_post_auth - }, - }; -diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4 -new file mode 100644 -index 0000000000..7e9b1ffcdf ---- /dev/null -+++ b/src/tests/keywords/md4 -@@ -0,0 +1,58 @@ -+# -+# PRE: update if -+# -+update reply { -+ Filter-Id := "filter" -+} -+ -+update { -+ control:Cleartext-Password := 'hello' -+ request:Tmp-String-0 := "This is a string\n" -+ request:Tmp-Octets-0 := 0x000504030201 -+ request:Tmp-String-1 := "what do ya want for nothing?" -+ request:Tmp-String-2 := "Jefe" -+} -+ -+# -+# Put "This is a string" into a file and call "md5sum" on it. -+# You should get this string. -+# -+if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') { -+ update reply { -+ Filter-Id += 'fail 1' -+ } -+} -+ -+if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { -+ update reply { -+ Filter-Id += 'fail 2' -+ } -+} -+ -+if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { -+ update reply { -+ Filter-Id += 'fail 3' -+ } -+} -+ -+if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') { -+ update reply { -+ Filter-Id += 'fail 4' -+ } -+} -+ -+# -+# MD4 should also be able to cope with references to octet attributes -+# -+if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') { -+ update reply { -+ Filter-Id += 'fail 5' -+ } -+} -+ -+if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') { -+ update reply { -+ Filter-Id += 'fail 6' -+ } -+} -+ diff --git a/SOURCES/freeradius-Fix-resource-hard-limit-error.patch b/SOURCES/freeradius-Fix-resource-hard-limit-error.patch deleted file mode 100644 index 800c06c..0000000 --- a/SOURCES/freeradius-Fix-resource-hard-limit-error.patch +++ /dev/null @@ -1,32 +0,0 @@ -commit 1ce4508c92493cf03ea1b3c42e83540b387884fa -Author: Antonio Torres -Date: Fri Jul 2 07:12:48 2021 -0400 -Subject: [PATCH] debug: don't set resource hard limit to zero - - Setting the resource hard limit to zero is irreversible, meaning if it - is set to zero then there is no way to set it higher. This means - enabling core dump is not possible, since setting a new resource limit - for RLIMIT_CORE would fail. By only setting the soft limit to zero, we - can disable and enable core dumps without failures. - - This fix is present in both main and 3.0.x upstream branches. - - Ticket in RHEL Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1977572 - Signed-off-by: Antonio Torres antorres@redhat.com ---- - src/lib/debug.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/lib/debug.c b/src/lib/debug.c -index 576bcb2a65..6330c9cb66 100644 ---- a/src/lib/debug.c -+++ b/src/lib/debug.c -@@ -599,7 +599,7 @@ int fr_set_dumpable(bool allow_core_dumps) - struct rlimit no_core; - - no_core.rlim_cur = 0; -- no_core.rlim_max = 0; -+ no_core.rlim_max = core_limits.rlim_max; - - if (setrlimit(RLIMIT_CORE, &no_core) < 0) { - fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno)); diff --git a/SOURCES/freeradius-Use-system-crypto-policy-by-default.patch b/SOURCES/freeradius-Use-system-crypto-policy-by-default.patch index 199e583..9e513b5 100644 --- a/SOURCES/freeradius-Use-system-crypto-policy-by-default.patch +++ b/SOURCES/freeradius-Use-system-crypto-policy-by-default.patch @@ -4,6 +4,8 @@ Date: Wed, 8 May 2019 10:16:31 -0400 Subject: [PATCH] Use system-provided crypto-policies by default Signed-off-by: Alexander Scheel + +[antorres@redhat.com]: updated to work with FreeRADIUS 3.0.27 --- raddb/mods-available/eap | 4 ++-- raddb/mods-available/inner-eap | 2 +- @@ -12,21 +14,21 @@ Signed-off-by: Alexander Scheel 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap -index 36849e10f2..b28c0f19c6 100644 +index d6780c3892..25f4687cb6 100644 --- a/raddb/mods-available/eap +++ b/raddb/mods-available/eap -@@ -368,7 +368,7 @@ eap { - # - # For EAP-FAST, use "ALL:!EXPORT:!eNULL:!SSLv2" +@@ -405,7 +405,7 @@ eap { + # TLS cipher suites. The format is listed + # in "man 1 ciphers". # - cipher_list = "DEFAULT" + cipher_list = "PROFILE=SYSTEM" # If enabled, OpenSSL will use server cipher list # (possibly defined by cipher_list option above) -@@ -912,7 +912,7 @@ eap { - # Note - for OpenSSL 1.1.0 and above you may need - # to add ":@SECLEVEL=0" +@@ -1080,7 +1080,7 @@ eap { + # "DEFAULT" as "DEFAULT" contains "!aNULL" so instead it is + # recommended "ALL:!EXPORT:!eNULL:!SSLv2" is used # - # cipher_list = "ALL:!EXPORT:!eNULL:!SSLv2" + # cipher_list = "PROFILE=SYSTEM" @@ -47,23 +49,23 @@ index 576eb7739e..ffa07188e2 100644 # You may want to set a very small fragment size. # The TLS data here needs to go inside of the diff --git a/raddb/sites-available/abfab-tls b/raddb/sites-available/abfab-tls -index 92f1d6330e..cd69b3905a 100644 +index b8d0626bbe..073b2933c2 100644 --- a/raddb/sites-available/abfab-tls +++ b/raddb/sites-available/abfab-tls -@@ -19,7 +19,7 @@ listen { +@@ -20,7 +20,7 @@ listen { dh_file = ${certdir}/dh fragment_size = 8192 ca_path = ${cadir} - cipher_list = "DEFAULT" + cipher_list = "PROFILE=SYSTEM" - cache { enable = no + lifetime = 24 # hours diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls -index bbc761b1c5..83cd35b851 100644 +index 257770777d..a24e433122 100644 --- a/raddb/sites-available/tls +++ b/raddb/sites-available/tls -@@ -215,7 +215,7 @@ listen { +@@ -288,7 +288,7 @@ listen { # Set this option to specify the allowed # TLS cipher suites. The format is listed # in "man 1 ciphers". @@ -72,7 +74,7 @@ index bbc761b1c5..83cd35b851 100644 # If enabled, OpenSSL will use server cipher list # (possibly defined by cipher_list option above) -@@ -517,7 +517,7 @@ home_server tls { +@@ -679,7 +679,7 @@ home_server tls { # Set this option to specify the allowed # TLS cipher suites. The format is listed # in "man 1 ciphers". @@ -81,6 +83,3 @@ index bbc761b1c5..83cd35b851 100644 } } --- -2.21.0 - diff --git a/SOURCES/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch b/SOURCES/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch index b3dc68e..70b19e2 100644 --- a/SOURCES/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch +++ b/SOURCES/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch @@ -13,13 +13,14 @@ Signed-off-by: Antonio Torres [antorres@redhat.com]: patch adapted to work together with freeradius-bootstrap-create-only.patch. In bootstrap diff, -f is changed to -e in conditionals. +[antorres@redhat.com]: updated to work with FreeRADIUS 3.0.27 --- raddb/certs/Makefile | 20 ++++++++++++++++---- raddb/certs/bootstrap | 6 +++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/raddb/certs/Makefile b/raddb/certs/Makefile -index 5cbfd467ce..cb10394ec3 100644 +index c9fbc9e864..33eeef6640 100644 --- a/raddb/certs/Makefile +++ b/raddb/certs/Makefile @@ -60,6 +60,8 @@ passwords.mk: server.cnf ca.cnf client.cnf inner-server.cnf @@ -60,7 +61,7 @@ index 5cbfd467ce..cb10394ec3 100644 + $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf -noenc chmod g+r server.key - server.crt: server.csr ca.key ca.pem + server.crt: ca.key ca.pem server.csr @@ -101,6 +107,8 @@ server.p12: server.crt server.pem: server.p12 $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) @@ -78,8 +79,8 @@ index 5cbfd467ce..cb10394ec3 100644 + $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf -noenc chmod g+r client.key - client.crt: client.csr ca.pem ca.key -@@ -127,6 +135,8 @@ client.pem: client.p12 + client.crt: ca.key ca.pem client.csr +@@ -128,6 +136,8 @@ client.pem: client.p12 $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) chmod g+r client.pem cp client.pem $(USER_NAME).pem @@ -88,7 +89,7 @@ index 5cbfd467ce..cb10394ec3 100644 .PHONY: client.vrfy client.vrfy: ca.pem client.pem -@@ -139,7 +149,7 @@ client.vrfy: ca.pem client.pem +@@ -140,7 +150,7 @@ client.vrfy: ca.pem client.pem # ###################################################################### inner-server.csr inner-server.key: inner-server.cnf @@ -96,8 +97,8 @@ index 5cbfd467ce..cb10394ec3 100644 + $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf -noenc chmod g+r inner-server.key - inner-server.crt: inner-server.csr ca.key ca.pem -@@ -152,6 +162,8 @@ inner-server.p12: inner-server.crt + inner-server.crt: ca.key ca.pem inner-server.csr +@@ -153,6 +163,8 @@ inner-server.p12: inner-server.crt inner-server.pem: inner-server.p12 $(OPENSSL) pkcs12 -in inner-server.p12 -out inner-server.pem -passin pass:$(PASSWORD_INNER) -passout pass:$(PASSWORD_INNER) chmod g+r inner-server.pem diff --git a/SOURCES/freeradius-fix-crash-on-invalid-abinary-data.patch b/SOURCES/freeradius-fix-crash-on-invalid-abinary-data.patch deleted file mode 100644 index 862c6b5..0000000 --- a/SOURCES/freeradius-fix-crash-on-invalid-abinary-data.patch +++ /dev/null @@ -1,47 +0,0 @@ -From: Antonio Torres -Date: Fri, 09 Dec 2022 -Subject: Fix crash on invalid abinary data - -A malicious RADIUS client or home server can send a malformed abinary -attribute which can cause the server to crash. - -Backport of https://github.com/FreeRADIUS/freeradius-server/commit/0ec2b39d260e - -Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151707 -Signed-off-by: Antonio Torres ---- -diff --git a/src/lib/filters.c b/src/lib/filters.c -index 4868cd385d9f..3f3b63daeef3 100644 ---- a/src/lib/filters.c -+++ b/src/lib/filters.c -@@ -1205,13 +1205,19 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in - } - } - } else if (filter->type == RAD_FILTER_GENERIC) { -- int count; -+ size_t count, masklen; -+ -+ masklen = ntohs(filter->u.generic.len); -+ if (masklen >= sizeof(filter->u.generic.mask)) { -+ *p = '\0'; -+ return; -+ } - - i = snprintf(p, outlen, " %u ", (unsigned int) ntohs(filter->u.generic.offset)); - p += i; - - /* show the mask */ -- for (count = 0; count < ntohs(filter->u.generic.len); count++) { -+ for (count = 0; count < masklen; count++) { - i = snprintf(p, outlen, "%02x", filter->u.generic.mask[count]); - p += i; - outlen -= i; -@@ -1222,7 +1228,7 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in - outlen--; - - /* show the value */ -- for (count = 0; count < ntohs(filter->u.generic.len); count++) { -+ for (count = 0; count < masklen; count++) { - i = snprintf(p, outlen, "%02x", filter->u.generic.value[count]); - p += i; - outlen -= i; diff --git a/SOURCES/freeradius-fix-crash-unknown-eap-sim.patch b/SOURCES/freeradius-fix-crash-unknown-eap-sim.patch deleted file mode 100644 index d2b7956..0000000 --- a/SOURCES/freeradius-fix-crash-unknown-eap-sim.patch +++ /dev/null @@ -1,115 +0,0 @@ -From: Antonio Torres -Date: Fri, 09 Dec 2022 -Subject: Fix crash on unknown option in EAP-SIM - -When an EAP-SIM supplicant sends an unknown SIM option, the server will try to -look that option up in the internal dictionaries. This lookup will fail, but the -SIM code will not check for that failure. Instead, it will dereference a NULL -pointer, and cause the server to crash. - -Backport of: -https://github.com/FreeRADIUS/freeradius-server/commit/f1cdbb33ec61c4a64a -https://github.com/FreeRADIUS/freeradius-server/commit/71128cac3ee236a88a05cc7bddd43e43a88a3089 - -Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151705 -Signed-off-by: Antonio Torres ---- -diff --git a/src/modules/rlm_eap/libeap/eapsimlib.c b/src/modules/rlm_eap/libeap/eapsimlib.c -index cf1e8a7dd92..e438a844eab 100644 ---- a/src/modules/rlm_eap/libeap/eapsimlib.c -+++ b/src/modules/rlm_eap/libeap/eapsimlib.c -@@ -307,42 +307,77 @@ int unmap_eapsim_basictypes(RADIUS_PACKET *r, - newvp->vp_length = 1; - fr_pair_add(&(r->vps), newvp); - -+ /* -+ * EAP-SIM has a 1 octet of subtype, and 2 octets -+ * reserved. -+ */ - attr += 3; - attrlen -= 3; - -- /* now, loop processing each attribute that we find */ -- while(attrlen > 0) { -+ /* -+ * Loop over each attribute. The format is: -+ * -+ * 1 octet of type -+ * 1 octet of length (value 1..255) -+ * ((4 * length) - 2) octets of data. -+ */ -+ while (attrlen > 0) { - uint8_t *p; - -- if(attrlen < 2) { -+ if (attrlen < 2) { - fr_strerror_printf("EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen); - return 0; - } - -+ if (!attr[1]) { -+ fr_strerror_printf("EAP-Sim attribute %d (no.%d) has no data", attr[0], -+ es_attribute_count); -+ return 0; -+ } -+ - eapsim_attribute = attr[0]; - eapsim_len = attr[1] * 4; - -+ /* -+ * The length includes the 2-byte header. -+ */ - if (eapsim_len > attrlen) { - fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)", - eapsim_attribute, es_attribute_count, eapsim_len, attrlen); - return 0; - } - -- if(eapsim_len > MAX_STRING_LEN) { -- eapsim_len = MAX_STRING_LEN; -- } -- if (eapsim_len < 2) { -- fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length too small", eapsim_attribute, -- es_attribute_count); -- return 0; -- } -+ newvp = fr_pair_afrom_num(r, eapsim_attribute + PW_EAP_SIM_BASE, 0); -+ if (!newvp) { -+ /* -+ * RFC 4186 Section 8.1 says 0..127 are -+ * "non-skippable". If one such -+ * attribute is found and we don't -+ * understand it, the server has to send: -+ * -+ * EAP-Request/SIM/Notification packet with an -+ * (AT_NOTIFICATION code, which implies general failure ("General -+ * failure after authentication" (0), or "General failure" (16384), -+ * depending on the phase of the exchange), which terminates the -+ * authentication exchange. -+ */ -+ if (eapsim_attribute <= 127) { -+ fr_strerror_printf("Unknown mandatory attribute %d, failing", -+ eapsim_attribute); -+ return 0; -+ } - -- newvp = fr_pair_afrom_num(r, eapsim_attribute+PW_EAP_SIM_BASE, 0); -- newvp->vp_length = eapsim_len-2; -- newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length); -- memcpy(p, &attr[2], eapsim_len-2); -- fr_pair_add(&(r->vps), newvp); -- newvp = NULL; -+ } else { -+ /* -+ * It's known, ccount for header, and -+ * copy the value over. -+ */ -+ newvp->vp_length = eapsim_len - 2; -+ -+ newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length); -+ memcpy(p, &attr[2], newvp->vp_length); -+ fr_pair_add(&(r->vps), newvp); -+ } - - /* advance pointers, decrement length */ - attr += eapsim_len; diff --git a/SOURCES/freeradius-fix-python3-library-suffix.patch b/SOURCES/freeradius-fix-python3-library-suffix.patch deleted file mode 100644 index b6d6ab3..0000000 --- a/SOURCES/freeradius-fix-python3-library-suffix.patch +++ /dev/null @@ -1,635 +0,0 @@ -From: Antonio Torres -Date: Mon, 06 Nov 2023 -Subject: Fix Python3.8+ library name suffix - -Python 3.8 has removed the "m" suffix in the library name, add a check for it. - -Backport of https://github.com/FreeRADIUS/freeradius-server/commit/fa837465493158257e600f28bca009ba890db863 - -Resolves: https://issues.redhat.com/browse/RHEL-15503 -Signed-off-by: Antonio Torres ---- -diff --git a/src/modules/rlm_python3/configure b/src/modules/rlm_python3/configure -index f421558ac0c0..05907f12c359 100755 ---- a/src/modules/rlm_python3/configure -+++ b/src/modules/rlm_python3/configure -@@ -588,7 +588,17 @@ LIBOBJS - targetname - mod_cflags - mod_ldflags -+AWK - PYTHON3_CONFIG_BIN -+pkgpyexecdir -+pyexecdir -+pkgpythondir -+pythondir -+PYTHON_PLATFORM -+PYTHON_EXEC_PREFIX -+PYTHON_PREFIX -+PYTHON_VERSION -+PYTHON - CPP - OBJEXT - EXEEXT -@@ -648,7 +658,8 @@ CFLAGS - LDFLAGS - LIBS - CPPFLAGS --CPP' -+CPP -+PYTHON' - - - # Initialize some variables set by options. -@@ -1266,6 +1277,7 @@ Some influential environment variables: - CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if - you have headers in a nonstandard directory - CPP C preprocessor -+ PYTHON the Python interpreter - - Use these variables to override the choices made by `configure' or to help - it to find libraries and programs with nonstandard names/locations. -@@ -1421,6 +1433,119 @@ fi - as_fn_set_status $ac_retval - - } # ac_fn_c_try_cpp -+ -+# ac_fn_c_try_link LINENO -+# ----------------------- -+# Try to link conftest.$ac_ext, and return whether this succeeded. -+ac_fn_c_try_link () -+{ -+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack -+ rm -f conftest.$ac_objext conftest$ac_exeext -+ if { { ac_try="$ac_link" -+case "(($ac_try" in -+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; -+ *) ac_try_echo=$ac_try;; -+esac -+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -+$as_echo "$ac_try_echo"; } >&5 -+ (eval "$ac_link") 2>conftest.err -+ ac_status=$? -+ if test -s conftest.err; then -+ grep -v '^ *+' conftest.err >conftest.er1 -+ cat conftest.er1 >&5 -+ mv -f conftest.er1 conftest.err -+ fi -+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 -+ test $ac_status = 0; } && { -+ test -z "$ac_c_werror_flag" || -+ test ! -s conftest.err -+ } && test -s conftest$ac_exeext && { -+ test "$cross_compiling" = yes || -+ test -x conftest$ac_exeext -+ }; then : -+ ac_retval=0 -+else -+ $as_echo "$as_me: failed program was:" >&5 -+sed 's/^/| /' conftest.$ac_ext >&5 -+ -+ ac_retval=1 -+fi -+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information -+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would -+ # interfere with the next link command; also delete a directory that is -+ # left behind by Apple's compiler. We do this before executing the actions. -+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo -+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -+ as_fn_set_status $ac_retval -+ -+} # ac_fn_c_try_link -+ -+# ac_fn_c_check_func LINENO FUNC VAR -+# ---------------------------------- -+# Tests whether FUNC exists, setting the cache variable VAR accordingly -+ac_fn_c_check_func () -+{ -+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -+$as_echo_n "checking for $2... " >&6; } -+if eval \${$3+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext -+/* end confdefs.h. */ -+/* Define $2 to an innocuous variant, in case declares $2. -+ For example, HP-UX 11i declares gettimeofday. */ -+#define $2 innocuous_$2 -+ -+/* System header to define __stub macros and hopefully few prototypes, -+ which can conflict with char $2 (); below. -+ Prefer to if __STDC__ is defined, since -+ exists even on freestanding compilers. */ -+ -+#ifdef __STDC__ -+# include -+#else -+# include -+#endif -+ -+#undef $2 -+ -+/* Override any GCC internal prototype to avoid an error. -+ Use char because int might match the return type of a GCC -+ builtin and then its argument prototype would still apply. */ -+#ifdef __cplusplus -+extern "C" -+#endif -+char $2 (); -+/* The GNU C library defines this for functions which it implements -+ to always fail with ENOSYS. Some functions are actually named -+ something starting with __ and the normal name is an alias. */ -+#if defined __stub_$2 || defined __stub___$2 -+choke me -+#endif -+ -+int -+main () -+{ -+return $2 (); -+ ; -+ return 0; -+} -+_ACEOF -+if ac_fn_c_try_link "$LINENO"; then : -+ eval "$3=yes" -+else -+ eval "$3=no" -+fi -+rm -f core conftest.err conftest.$ac_objext \ -+ conftest$ac_exeext conftest.$ac_ext -+fi -+eval ac_res=\$$3 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -+$as_echo "$ac_res" >&6; } -+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -+ -+} # ac_fn_c_check_func - cat >config.log <<_ACEOF - This file contains any messages produced by compilers while - running configure, to aid debugging if configure makes a mistake. -@@ -2705,6 +2830,267 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $ - ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -+ -+ -+ -+ -+ -+ if test -n "$PYTHON"; then -+ # If the user set $PYTHON, use it and don't search something else. -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 3.0" >&5 -+$as_echo_n "checking whether $PYTHON version is >= 3.0... " >&6; } -+ prog="import sys -+# split strings by '.' and convert to numeric. Append some zeros -+# because we need at least 4 digits for the hex conversion. -+# map returns an iterator in Python 3.0 and a list in 2.x -+minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0] -+minverhex = 0 -+# xrange is not present in Python 3.0 and range returns an iterator -+for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] -+sys.exit(sys.hexversion < minverhex)" -+ if { echo "$as_me:$LINENO: $PYTHON -c "$prog"" >&5 -+ ($PYTHON -c "$prog") >&5 2>&5 -+ ac_status=$? -+ echo "$as_me:$LINENO: \$? = $ac_status" >&5 -+ (exit $ac_status); }; then : -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -+$as_echo "yes" >&6; } -+else -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+$as_echo "no" >&6; } -+ as_fn_error $? "Python interpreter is too old" "$LINENO" 5 -+fi -+ am_display_PYTHON=$PYTHON -+ else -+ # Otherwise, try each interpreter until we find one that satisfies -+ # VERSION. -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 3.0" >&5 -+$as_echo_n "checking for a Python interpreter with version >= 3.0... " >&6; } -+if ${am_cv_pathless_PYTHON+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ -+ for am_cv_pathless_PYTHON in python python2 python3 python3.9 python3.8 python3.7 python3.6 python3.5 python3.4 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do -+ test "$am_cv_pathless_PYTHON" = none && break -+ prog="import sys -+# split strings by '.' and convert to numeric. Append some zeros -+# because we need at least 4 digits for the hex conversion. -+# map returns an iterator in Python 3.0 and a list in 2.x -+minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0] -+minverhex = 0 -+# xrange is not present in Python 3.0 and range returns an iterator -+for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] -+sys.exit(sys.hexversion < minverhex)" -+ if { echo "$as_me:$LINENO: $am_cv_pathless_PYTHON -c "$prog"" >&5 -+ ($am_cv_pathless_PYTHON -c "$prog") >&5 2>&5 -+ ac_status=$? -+ echo "$as_me:$LINENO: \$? = $ac_status" >&5 -+ (exit $ac_status); }; then : -+ break -+fi -+ done -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_pathless_PYTHON" >&5 -+$as_echo "$am_cv_pathless_PYTHON" >&6; } -+ # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. -+ if test "$am_cv_pathless_PYTHON" = none; then -+ PYTHON=: -+ else -+ # Extract the first word of "$am_cv_pathless_PYTHON", so it can be a program name with args. -+set dummy $am_cv_pathless_PYTHON; ac_word=$2 -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -+$as_echo_n "checking for $ac_word... " >&6; } -+if ${ac_cv_path_PYTHON+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ case $PYTHON in -+ [\\/]* | ?:[\\/]*) -+ ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. -+ ;; -+ *) -+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -+for as_dir in $PATH -+do -+ IFS=$as_save_IFS -+ test -z "$as_dir" && as_dir=. -+ for ac_exec_ext in '' $ac_executable_extensions; do -+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then -+ ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" -+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 -+ break 2 -+ fi -+done -+ done -+IFS=$as_save_IFS -+ -+ ;; -+esac -+fi -+PYTHON=$ac_cv_path_PYTHON -+if test -n "$PYTHON"; then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 -+$as_echo "$PYTHON" >&6; } -+else -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+$as_echo "no" >&6; } -+fi -+ -+ -+ fi -+ am_display_PYTHON=$am_cv_pathless_PYTHON -+ fi -+ -+ -+ if test "$PYTHON" = :; then -+ : -+ else -+ -+ -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON version" >&5 -+$as_echo_n "checking for $am_display_PYTHON version... " >&6; } -+if ${am_cv_python_version+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[:3])"` -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_version" >&5 -+$as_echo "$am_cv_python_version" >&6; } -+ PYTHON_VERSION=$am_cv_python_version -+ -+ -+ -+ PYTHON_PREFIX='${prefix}' -+ -+ PYTHON_EXEC_PREFIX='${exec_prefix}' -+ -+ -+ -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON platform" >&5 -+$as_echo_n "checking for $am_display_PYTHON platform... " >&6; } -+if ${am_cv_python_platform+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"` -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_platform" >&5 -+$as_echo "$am_cv_python_platform" >&6; } -+ PYTHON_PLATFORM=$am_cv_python_platform -+ -+ -+ # Just factor out some code duplication. -+ am_python_setup_sysconfig="\ -+import sys -+# Prefer sysconfig over distutils.sysconfig, for better compatibility -+# with python 3.x. See automake bug#10227. -+try: -+ import sysconfig -+except ImportError: -+ can_use_sysconfig = 0 -+else: -+ can_use_sysconfig = 1 -+# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: -+# -+try: -+ from platform import python_implementation -+ if python_implementation() == 'CPython' and sys.version[:3] == '2.7': -+ can_use_sysconfig = 0 -+except ImportError: -+ pass" -+ -+ -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 -+$as_echo_n "checking for $am_display_PYTHON script directory... " >&6; } -+if ${am_cv_python_pythondir+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ if test "x$prefix" = xNONE -+ then -+ am_py_prefix=$ac_default_prefix -+ else -+ am_py_prefix=$prefix -+ fi -+ am_cv_python_pythondir=`$PYTHON -c " -+$am_python_setup_sysconfig -+if can_use_sysconfig: -+ sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) -+else: -+ from distutils import sysconfig -+ sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') -+sys.stdout.write(sitedir)"` -+ case $am_cv_python_pythondir in -+ $am_py_prefix*) -+ am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` -+ am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` -+ ;; -+ *) -+ case $am_py_prefix in -+ /usr|/System*) ;; -+ *) -+ am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages -+ ;; -+ esac -+ ;; -+ esac -+ -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pythondir" >&5 -+$as_echo "$am_cv_python_pythondir" >&6; } -+ pythondir=$am_cv_python_pythondir -+ -+ -+ -+ pkgpythondir=\${pythondir}/$PACKAGE -+ -+ -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON extension module directory" >&5 -+$as_echo_n "checking for $am_display_PYTHON extension module directory... " >&6; } -+if ${am_cv_python_pyexecdir+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ if test "x$exec_prefix" = xNONE -+ then -+ am_py_exec_prefix=$am_py_prefix -+ else -+ am_py_exec_prefix=$exec_prefix -+ fi -+ am_cv_python_pyexecdir=`$PYTHON -c " -+$am_python_setup_sysconfig -+if can_use_sysconfig: -+ sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) -+else: -+ from distutils import sysconfig -+ sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') -+sys.stdout.write(sitedir)"` -+ case $am_cv_python_pyexecdir in -+ $am_py_exec_prefix*) -+ am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` -+ am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` -+ ;; -+ *) -+ case $am_py_exec_prefix in -+ /usr|/System*) ;; -+ *) -+ am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages -+ ;; -+ esac -+ ;; -+ esac -+ -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pyexecdir" >&5 -+$as_echo "$am_cv_python_pyexecdir" >&6; } -+ pyexecdir=$am_cv_python_pyexecdir -+ -+ -+ -+ pkgpyexecdir=\${pyexecdir}/$PACKAGE -+ -+ -+ -+ fi -+ -+ -+ - PYTHON3_CONFIG_BIN= - - # Check whether --with-rlm-python3-config-bin was given. -@@ -2771,8 +3157,6 @@ test -n "$PYTHON3_CONFIG_BIN" || PYTHON3_CONFIG_BIN="not-found" - fi - - if test "x$PYTHON3_CONFIG_BIN" = xnot-found; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python3-config not found" >&5 --$as_echo "$as_me: WARNING: python3-config not found" >&2;} - fail="$fail python3-config" - else - old_CFLAGS="$CFLAGS" -@@ -2794,7 +3178,85 @@ $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s cflags were \"${python3_cflags}\"" >&6 - { $as_echo "$as_me:${as_lineno-$LINENO}: Sanitized cflags were \"${mod_cflags}\"" >&5 - $as_echo "$as_me: Sanitized cflags were \"${mod_cflags}\"" >&6;} - -- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags` -+ for ac_prog in gawk mawk nawk awk -+do -+ # Extract the first word of "$ac_prog", so it can be a program name with args. -+set dummy $ac_prog; ac_word=$2 -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -+$as_echo_n "checking for $ac_word... " >&6; } -+if ${ac_cv_prog_AWK+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ if test -n "$AWK"; then -+ ac_cv_prog_AWK="$AWK" # Let the user override the test. -+else -+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -+for as_dir in $PATH -+do -+ IFS=$as_save_IFS -+ test -z "$as_dir" && as_dir=. -+ for ac_exec_ext in '' $ac_executable_extensions; do -+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then -+ ac_cv_prog_AWK="$ac_prog" -+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 -+ break 2 -+ fi -+done -+ done -+IFS=$as_save_IFS -+ -+fi -+fi -+AWK=$ac_cv_prog_AWK -+if test -n "$AWK"; then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 -+$as_echo "$AWK" >&6; } -+else -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+$as_echo "no" >&6; } -+fi -+ -+ -+ test -n "$AWK" && break -+done -+ -+ -+ -+ -+ # Used to indicate true or false condition -+ ax_compare_version=false -+ -+ # Convert the two version strings to be compared into a format that -+ # allows a simple string comparison. The end result is that a version -+ # string of the form 1.12.5-r617 will be converted to the form -+ # 0001001200050617. In other words, each number is zero padded to four -+ # digits, and non digits are removed. -+ -+ ax_compare_version_A=`echo "${PYTHON_VERSION}" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -+ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -+ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -+ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -+ -e 's/[^0-9]//g'` -+ -+ -+ ax_compare_version_B=`echo "3.8" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -+ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -+ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -+ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -+ -e 's/[^0-9]//g'` -+ -+ -+ ax_compare_version=`echo "x$ax_compare_version_A -+x$ax_compare_version_B" | sed 's/^ *//' | sort -r | sed "s/x${ax_compare_version_A}/true/;s/x${ax_compare_version_B}/false/;1q"` -+ -+ -+ -+ if test "$ax_compare_version" = "true" ; then -+ EMBED="--embed" -+ fi -+ -+ -+ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED` - { $as_echo "$as_me:${as_lineno-$LINENO}: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&5 - $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&6;} - -@@ -2811,6 +3273,18 @@ $as_echo "$as_me: Sanitized ldflags were \"${mod_ldflags}\"" >&6;} - - targetname="rlm_python3" - fi -+ -+for ac_func in dl_iterate_phdr -+do : -+ ac_fn_c_check_func "$LINENO" "dl_iterate_phdr" "ac_cv_func_dl_iterate_phdr" -+if test "x$ac_cv_func_dl_iterate_phdr" = xyes; then : -+ cat >>confdefs.h <<_ACEOF -+#define HAVE_DL_ITERATE_PHDR 1 -+_ACEOF -+ -+fi -+done -+ - else - targetname= - echo \*\*\* module rlm_python3 is disabled. -@@ -2833,11 +3307,7 @@ ac_config_headers="$ac_config_headers config.h" - - - -- -- unset ac_cv_env_LIBS_set -- unset ac_cv_env_LIBS_value -- -- ac_config_files="$ac_config_files all.mk" -+ac_config_files="$ac_config_files all.mk" - - cat >confcache <<\_ACEOF - # This file is a shell script that caches the results of configure -@@ -3417,6 +3887,7 @@ gives unlimited permission to copy, distribute and modify it." - - ac_pwd='$ac_pwd' - srcdir='$srcdir' -+AWK='$AWK' - test -n "\$AWK" || AWK=awk - _ACEOF - -@@ -4111,4 +4582,3 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} - fi - -- -diff --git a/src/modules/rlm_python3/configure.ac b/src/modules/rlm_python3/configure.ac -index 295a2486d2ac..698a8c1d1874 100644 ---- a/src/modules/rlm_python3/configure.ac -+++ b/src/modules/rlm_python3/configure.ac -@@ -7,6 +7,7 @@ if test x$with_[]modname != xno; then - - AC_PROG_CC - AC_PROG_CPP -+ AM_PATH_PYTHON([3.0],, [:]) - - dnl extra argument: --with-rlm-python3-config-bin - PYTHON3_CONFIG_BIN= -@@ -58,7 +59,11 @@ if test x$with_[]modname != xno; then - '` - AC_MSG_NOTICE([Sanitized cflags were \"${mod_cflags}\"]) - -- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags` -+ dnl # From python 3.8, --embed is required -+ dnl # https://bugs.python.org/issue36721 -+ AX_COMPARE_VERSION(${PYTHON_VERSION}, [ge], [3.8], [EMBED="--embed"], []) -+ -+ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED` - AC_MSG_NOTICE([${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"]) - - dnl # Strip -Wl,-O1... Is -O even a valid linker flag?? -@@ -77,6 +82,7 @@ if test x$with_[]modname != xno; then - - targetname="rlm_python3" - fi -+ AC_CHECK_FUNCS([dl_iterate_phdr]) - else - targetname= - echo \*\*\* module modname is disabled. -diff --git a/src/modules/rlm_python3/rlm_python3.c b/src/modules/rlm_python3/rlm_python3.c -index df223f0f401b..5da23f4d7116 100644 ---- a/src/modules/rlm_python3/rlm_python3.c -+++ b/src/modules/rlm_python3/rlm_python3.c -@@ -41,8 +41,17 @@ RCSID("$Id$") - #include - #endif - -+/* -+ * Since version 3.8, the "m" suffix is no longer available. -+ * https://bugs.python.org/issue36707 -+ */ -+#if PY_MINOR_VERSION >= 8 -+#define LIBPYTHON_LINKER_NAME \ -+ "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) LT_SHREXT -+#else - #define LIBPYTHON_LINKER_NAME \ - "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) "m" LT_SHREXT -+#endif - - static uint32_t python_instances = 0; - static void *python_dlhandle; diff --git a/SOURCES/freeradius-no-buildtime-cert-gen.patch b/SOURCES/freeradius-no-buildtime-cert-gen.patch index aa3be66..df2c316 100644 --- a/SOURCES/freeradius-no-buildtime-cert-gen.patch +++ b/SOURCES/freeradius-no-buildtime-cert-gen.patch @@ -4,34 +4,43 @@ Date: Wed, 8 May 2019 12:58:02 -0400 Subject: [PATCH] Don't generate certificates in reproducible builds Signed-off-by: Alexander Scheel +[antorres@redhat.com]: updated to work with FreeRADIUS 3.0.27 --- Make.inc.in | 5 +++++ configure | 4 ++++ configure.ac | 3 +++ raddb/all.mk | 4 ++++ 4 files changed, 16 insertions(+) - diff --git a/Make.inc.in b/Make.inc.in -index 0b2cd74de8..8c623cf95c 100644 +index 05f82776ff..37626b4ccd 100644 --- a/Make.inc.in +++ b/Make.inc.in -@@ -173,3 +173,8 @@ else +@@ -107,6 +107,7 @@ USE_SHARED_LIBS = @USE_SHARED_LIBS@ + bm_shared_libs = @USE_SHARED_LIBS@ + USE_STATIC_LIBS = @USE_STATIC_LIBS@ + bm_static_libs = @USE_STATIC_LIBS@ ++ENABLE_REPRODUCIBLE_BUILDS = @ENABLE_REPRODUCIBLE_BUILDS@ + + STATIC_MODULES = @STATIC_MODULES@ + LIBREADLINE = @LIBREADLINE@ +@@ -173,8 +174,6 @@ else TESTBINDIR = ./$(BUILD_DIR)/bin TESTBIN = ./$(BUILD_DIR)/bin endif -+ -+# -+# With reproducible builds, do not generate certificates during installation -+# -+ENABLE_REPRODUCIBLE_BUILDS = @ENABLE_REPRODUCIBLE_BUILDS@ +- +- + # + # For creating documentation via doc/all.mk + # + diff --git a/configure b/configure -index c2c599c92b..3d4403a844 100755 +index f77471c768..3e6a078a09 100755 --- a/configure +++ b/configure -@@ -655,6 +655,7 @@ RUSERS +@@ -653,6 +653,7 @@ ACLOCAL + RUSERS SNMPWALK SNMPGET - PERL +ENABLE_REPRODUCIBLE_BUILDS openssl_version_check_config WITH_DHCP @@ -61,10 +70,10 @@ index c2c599c92b..3d4403a844 100755 # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 diff --git a/configure.ac b/configure.ac -index a7abf0025a..35b013f4af 100644 +index ad8bc8cdda..d093b1d13e 100644 --- a/configure.ac +++ b/configure.ac -@@ -619,6 +619,7 @@ AC_SUBST([openssl_version_check_config]) +@@ -725,6 +725,7 @@ AC_SUBST([openssl_version_check_config]) dnl # dnl # extra argument: --enable-reproducible-builds dnl # @@ -72,7 +81,7 @@ index a7abf0025a..35b013f4af 100644 AC_ARG_ENABLE(reproducible-builds, [AS_HELP_STRING([--enable-reproducible-builds], [ensure the build does not change each time])], -@@ -630,8 +631,10 @@ AC_ARG_ENABLE(reproducible-builds, +@@ -736,8 +737,10 @@ AC_ARG_ENABLE(reproducible-builds, ;; *) reproducible_builds=no @@ -81,10 +90,8 @@ index a7abf0025a..35b013f4af 100644 ) +AC_SUBST(ENABLE_REPRODUCIBLE_BUILDS) - - dnl ############################################################# -diff --git a/raddb/all.mk b/raddb/all.mk -index c966edd657..c8e976a499 100644 + dnl # + dnl # Enable the -fsanitize=fuzzer and link in the address sanitizer --- a/raddb/all.mk +++ b/raddb/all.mk @@ -124,7 +124,11 @@ $(R)$(raddbdir)/users: $(R)$(modconfdir)/files/authorize diff --git a/SOURCES/freeradius-no-sqlippool-tool.patch b/SOURCES/freeradius-no-sqlippool-tool.patch new file mode 100644 index 0000000..58d3282 --- /dev/null +++ b/SOURCES/freeradius-no-sqlippool-tool.patch @@ -0,0 +1,28 @@ +From: Antonio Torres +Date: Wed, 5 Mar 2025 +Subject: Remove sqlippool tool + +This script relies on a Perl package, perl-Net-IP, that won't be available. +Remove it from build script and let the user pull it manually instead, as it's +just a helper script for SQL module users. + +--- +diff --git a/scripts/all.mk b/scripts/all.mk +index a6e90aa3eb..517adb8590 100644 +--- a/scripts/all.mk ++++ b/scripts/all.mk +@@ -1,5 +1,5 @@ + install: $(R)$(sbindir)/rc.radiusd $(R)$(sbindir)/raddebug \ +- $(R)$(bindir)/radsqlrelay $(R)$(bindir)/radcrypt $(R)$(bindir)/rlm_sqlippool_tool ++ $(R)$(bindir)/radsqlrelay $(R)$(bindir)/radcrypt + + $(R)$(sbindir)/rc.radiusd: scripts/rc.radiusd + @mkdir -p $(dir $@) +@@ -16,7 +16,3 @@ $(R)$(bindir)/radsqlrelay: scripts/sql/radsqlrelay + $(R)$(bindir)/radcrypt: scripts/cryptpasswd + @mkdir -p $(dir $@) + @$(INSTALL) -m 755 $< $@ +- +-$(R)$(bindir)/rlm_sqlippool_tool: scripts/sql/rlm_sqlippool_tool +- @mkdir -p $(dir $@) +- @$(INSTALL) -m 755 $< $@ diff --git a/SPECS/freeradius.spec b/SPECS/freeradius.spec index 3c4c916..6ac84d2 100644 --- a/SPECS/freeradius.spec +++ b/SPECS/freeradius.spec @@ -1,7 +1,7 @@ Summary: High-performance and highly configurable free RADIUS server Name: freeradius -Version: 3.0.21 -Release: 44%{?dist} +Version: 3.0.27 +Release: 1%{?dist} License: GPLv2+ and LGPLv2+ URL: http://www.freeradius.org/ @@ -26,13 +26,9 @@ Patch2: freeradius-Use-system-crypto-policy-by-default.patch Patch3: freeradius-bootstrap-create-only.patch Patch4: freeradius-no-buildtime-cert-gen.patch Patch5: freeradius-bootstrap-make-permissions.patch -Patch6: freeradius-Fix-resource-hard-limit-error.patch -Patch7: freeradius-ldap-infinite-timeout-on-starttls.patch -Patch8: freeradius-Backport-OpenSSL3-fixes.patch -Patch9: freeradius-bootstrap-pass-noenc-to-certificate-generation.patch -Patch10: freeradius-fix-crash-unknown-eap-sim.patch -Patch11: freeradius-fix-crash-on-invalid-abinary-data.patch -Patch12: freeradius-fix-python3-library-suffix.patch +Patch6: freeradius-ldap-infinite-timeout-on-starttls.patch +Patch7: freeradius-bootstrap-pass-noenc-to-certificate-generation.patch +Patch8: freeradius-no-sqlippool-tool.patch %global docdir %{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}} @@ -220,10 +216,6 @@ This plugin provides the REST support for the FreeRADIUS server project. %patch6 -p1 %patch7 -p1 %patch8 -p1 -%patch9 -p1 -%patch10 -p1 -%patch11 -p1 -%patch12 -p1 %build # Force compile/link options, extra security for network facing daemon @@ -435,7 +427,7 @@ EOF %dir %attr(770,root,radiusd) /etc/raddb/certs %config(noreplace) /etc/raddb/certs/Makefile %config(noreplace) /etc/raddb/certs/passwords.mk -/etc/raddb/certs/README +/etc/raddb/certs/README.md %config(noreplace) /etc/raddb/certs/xpextensions %attr(640,root,radiusd) %config(noreplace) /etc/raddb/certs/*.cnf %attr(750,root,radiusd) /etc/raddb/certs/bootstrap @@ -483,6 +475,9 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/tls %attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/channel_bindings %attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/challenge +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/google-ldap-auth +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/resource-check +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/totp # sites-enabled # symlink: /etc/raddb/sites-enabled/xxx -> ../sites-available/xxx @@ -496,7 +491,6 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/always %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/attr_filter %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache -%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache_eap %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/chap %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/counter %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cui @@ -550,13 +544,19 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/utf8 %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/wimax %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/yubikey +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp_files +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp_passwd +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp_sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ldap_google +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sql_map +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/totp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache_auth # mods-enabled # symlink: /etc/raddb/mods-enabled/xxx -> ../mods-available/xxx %dir %attr(750,root,radiusd) /etc/raddb/mods-enabled %config(missingok) /etc/raddb/mods-enabled/always %config(missingok) /etc/raddb/mods-enabled/attr_filter -%config(missingok) /etc/raddb/mods-enabled/cache_eap %config(missingok) /etc/raddb/mods-enabled/chap %config(missingok) /etc/raddb/mods-enabled/date %config(missingok) /etc/raddb/mods-enabled/detail @@ -584,6 +584,7 @@ EOF %config(missingok) /etc/raddb/mods-enabled/unix %config(missingok) /etc/raddb/mods-enabled/unpack %config(missingok) /etc/raddb/mods-enabled/utf8 +%config(missingok) /etc/raddb/mods-enabled/totp # policy %dir %attr(750,root,radiusd) /etc/raddb/policy.d @@ -638,7 +639,6 @@ EOF %{_libdir}/freeradius/rlm_eap.so %{_libdir}/freeradius/rlm_eap_fast.so %{_libdir}/freeradius/rlm_eap_gtc.so -%{_libdir}/freeradius/rlm_eap_leap.so %{_libdir}/freeradius/rlm_eap_md5.so %{_libdir}/freeradius/rlm_eap_mschapv2.so %{_libdir}/freeradius/rlm_eap_peap.so @@ -675,6 +675,8 @@ EOF %{_libdir}/freeradius/rlm_utf8.so %{_libdir}/freeradius/rlm_wimax.so %{_libdir}/freeradius/rlm_yubikey.so +%{_libdir}/freeradius/rlm_sql_map.so +%{_libdir}/freeradius/rlm_totp.so # main man pages %doc %{_mandir}/man5/clients.conf.5.gz @@ -729,6 +731,7 @@ EOF %doc %{_mandir}/man8/radsniff.8.gz %doc %{_mandir}/man8/radsqlrelay.8.gz %doc %{_mandir}/man8/rlm_ippool_tool.8.gz +%doc %{_mandir}/man8/rlm_sqlippool_tool.8.gz %files devel /usr/include/freeradius @@ -763,6 +766,7 @@ EOF %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/mysql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/dailycounter.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/expire_on_login.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/weeklycounter.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/monthlycounter.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/noresetcounter.conf @@ -770,14 +774,49 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/queries.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/schema.sql +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mssql +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mssql/queries.conf +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mssql/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mysql +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mysql/queries.conf +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mysql/schema.sql +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/mysql/setup.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/dhcp/oracle +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/oracle/queries.conf +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/oracle/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/dhcp/postgresql +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/postgresql/queries.conf +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/postgresql/schema.sql +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/postgresql/setup.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/dhcp/sqlite +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/sqlite/queries.conf +%attr(640,root,radiusd) /etc/raddb/mods-config/sql/dhcp/sqlite/schema.sql + %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/mysql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/queries.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/schema.sql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/procedure.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/procedure-no-skip-locked.sql %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/mysql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/mssql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/postgresql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/setup.sql @@ -803,6 +842,7 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/weeklycounter.conf %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/postgresql %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/postgresql/queries.conf @@ -831,6 +871,7 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/weeklycounter.conf %dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/sqlite %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/sqlite/queries.conf @@ -864,9 +905,17 @@ EOF %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest %changelog -* Tue May 27 2025 Antonio Torres - 3.0.21-44 +* Tue Jul 08 2025 Antonio Torres - 3.0.27-1 +- Rebase to upstream 3.0.27 + Resolves: RHEL-102101 + +* Mon May 26 2025 Antonio Torres - 3.0.21-45 - Expose ca_path_reload_interval in configuration files - Resolves: RHEL-93721 + Resolves: RHEL-93552 + +* Tue Apr 22 2025 Antonio Torres - 3.0.21-44 +- Rebuild for OpenSSL rebase to 3.5 + Resolves: RHEL-88052 * Tue Oct 01 2024 Antonio Torres - 3.0.21-43 - Ignore home server ping packets