diff options
author | CoprDistGit <infra@openeuler.org> | 2024-08-01 14:06:58 +0000 |
---|---|---|
committer | CoprDistGit <infra@openeuler.org> | 2024-08-01 14:06:58 +0000 |
commit | fa0594f4021dbf53966e167cf44c1bb84df5bb23 (patch) | |
tree | 3c991fcabf18a0e314a10edf337db266e504af11 | |
parent | 72d830c7e64b038eb96c0f36e0c1a0ab225238e3 (diff) |
automatic import of freeradiusopeneuler24.03_LTS
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch | 60 | ||||
-rw-r--r-- | freeradius-Backport-OpenSSL3-fixes.patch | 18790 | ||||
-rw-r--r-- | freeradius-Fix-resource-hard-limit-error.patch | 32 | ||||
-rw-r--r-- | freeradius-Use-system-crypto-policy-by-default.patch | 86 | ||||
-rw-r--r-- | freeradius-bootstrap-create-only.patch | 89 | ||||
-rw-r--r-- | freeradius-bootstrap-make-permissions.patch | 29 | ||||
-rw-r--r-- | freeradius-bootstrap-pass-noenc-to-certificate-generation.patch | 136 | ||||
-rw-r--r-- | freeradius-fix-crash-on-invalid-abinary-data.patch | 47 | ||||
-rw-r--r-- | freeradius-fix-crash-unknown-eap-sim.patch | 115 | ||||
-rw-r--r-- | freeradius-fix-python3-library-suffix.patch | 635 | ||||
-rw-r--r-- | freeradius-ldap-infinite-timeout-on-starttls.patch | 31 | ||||
-rw-r--r-- | freeradius-logrotate | 56 | ||||
-rw-r--r-- | freeradius-no-buildtime-cert-gen.patch | 104 | ||||
-rw-r--r-- | freeradius-pam-conf | 6 | ||||
-rw-r--r-- | freeradius-tmpfiles.conf | 2 | ||||
-rw-r--r-- | freeradius.spec | 2617 | ||||
-rw-r--r-- | freeradius.sysusers | 3 | ||||
-rw-r--r-- | radiusd.service | 15 | ||||
-rw-r--r-- | sources | 1 |
20 files changed, 22855 insertions, 0 deletions
@@ -0,0 +1 @@ +/freeradius-server-3.0.21.tar.bz2 diff --git a/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch b/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch new file mode 100644 index 0000000..6b2329b --- /dev/null +++ b/freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch @@ -0,0 +1,60 @@ +From 958f470cda2ba8943f02f13d1b46f357f92d9639 Mon Sep 17 00:00:00 2001 +From: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +Date: Mon, 8 Sep 2014 12:32:13 +0300 +Subject: [PATCH] Adjust configuration to fit Red Hat specifics + +--- + raddb/mods-available/eap | 4 ++-- + raddb/radiusd.conf.in | 7 +++---- + 2 files changed, 5 insertions(+), 6 deletions(-) + +diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap +index 2621e183c..94494b2c6 100644 +--- a/raddb/mods-available/eap ++++ b/raddb/mods-available/eap +@@ -533,7 +533,7 @@ + # You should also delete all of the files + # in the directory when the server starts. + # +- # tmpdir = /tmp/radiusd ++ # tmpdir = /var/run/radiusd/tmp + + # The command used to verify the client cert. + # We recommend using the OpenSSL command-line +@@ -548,7 +548,7 @@ + # deleted by the server when the command + # returns. + # +- # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" ++ # client = "/usr/bin/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" + } + + # OCSP Configuration +diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in +index a83c1f687..e500cf97b 100644 +--- a/raddb/radiusd.conf.in ++++ b/raddb/radiusd.conf.in +@@ -70,8 +70,7 @@ certdir = ${confdir}/certs + cadir = ${confdir}/certs + run_dir = ${localstatedir}/run/${name} + +-# Should likely be ${localstatedir}/lib/radiusd +-db_dir = ${raddbdir} ++db_dir = ${localstatedir}/lib/radiusd + + # + # libdir: Where to find the rlm_* modules. +@@ -398,8 +397,8 @@ security { + # member. This can allow for some finer-grained access + # controls. + # +-# user = radius +-# group = radius ++ user = radiusd ++ group = radiusd + + # Core dumps are a bad thing. This should only be set to + # 'yes' if you're debugging a problem with the server. +-- +2.13.2 + diff --git a/freeradius-Backport-OpenSSL3-fixes.patch b/freeradius-Backport-OpenSSL3-fixes.patch new file mode 100644 index 0000000..92d22c1 --- /dev/null +++ b/freeradius-Backport-OpenSSL3-fixes.patch @@ -0,0 +1,18790 @@ +Date: Tue, 11 Jan 2022 +Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.x + +Backport TLS and OpenSSL3 fixes from the 3.0.x branch as of May 24th, 2022. + +Related: rhbz#1978216 +Related: rhbz#2083699 +Related: rhbz#2263240 +Signed-off-by: Antonio Torres <antorres@redhat.com> + +[antorres@redhat.com]: these changes include the macro WITH_FIPS, which allows FreeRADIUS +to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this macro on the specfile. +[antorres@redhat.com]: backported tls.c, tls-h changes from 3.2.x branch. +[antorres@redhat.com]: the sites-available/tls file has been modified to add the fix_cert_order option. +[antorres@redhat.com]: mods-available/eap has been modified to comment out 'disable_tlsv1' and 'dh_file' options. +[antorres@redhat.com]: add fix for BlastRADIUS CVE, commit range backported: 3a00a6ecc188629b0441fd45ad61ca8986de156e^..da643f1edc267ce95260dc36069e6f1a7a4d66f8, +this backport includes changes from other files not included in the commit range, to ensure correct compilation. +--- + man/man1/radclient.1 | 10 +- + man/man1/radtest.1 | 13 +- + raddb/clients.conf | 98 +- + raddb/mods-available/eap | 6 +- + raddb/proxy.conf | 83 +- + raddb/radiusd.conf.in | 280 ++- + raddb/sites-available/tls | 8 + + share/dictionary.freeradius | 5 + + share/dictionary.freeradius.internal | 54 +- + src/include/build.h | 29 +- + src/include/clients.h | 16 +- + src/include/conffile.h | 2 + + src/include/event.h | 3 + + src/include/libradius.h | 43 +- + src/include/listen.h | 30 +- + src/include/md4.h | 50 +- + src/include/md5.h | 33 +- + src/include/missing-h | 4 + + src/include/openssl3.h | 109 ++ + src/include/process.h | 9 + + src/include/radius.h | 1 + + src/include/radiusd.h | 27 +- + src/include/realms.h | 23 +- + src/include/socket.h | 53 + + src/include/tls-h | 45 +- + src/include/token.h | 7 +- + src/lib/dict.c | 150 +- + src/lib/event.c | 153 +- + src/lib/hmacmd5.c | 6 +- + src/lib/hmacsha1.c | 5 +- + src/lib/md4.c | 1 + + src/lib/md5.c | 1 + + src/lib/pair.c | 97 +- + src/lib/print.c | 19 +- + src/lib/radius.c | 428 ++++- + src/lib/token.c | 24 +- + src/main/cb.c | 121 +- + src/main/client.c | 255 ++- + src/main/command.c | 220 ++- + src/main/conffile.c | 71 +- + src/main/listen.c | 582 +++++- + src/main/mainconfig.c | 130 +- + src/main/map.c | 54 +- + src/main/process.c | 640 +++++-- + src/main/radclient.c | 231 ++- + src/main/radiusd.c | 1 + + src/main/radtest.in | 8 +- + src/main/realms.c | 354 +++- + src/main/session.c | 33 +- + src/main/stats.c | 177 +- + src/main/tls.c | 2012 ++++++++++++++++---- + src/main/tls_listen.c | 509 ++++- + src/modules/proto_dhcp/rlm_dhcp.c | 2 +- + src/modules/rlm_eap/libeap/eap_tls.c | 178 +- + src/modules/rlm_eap/libeap/eap_tls.h | 10 +- + src/modules/rlm_eap/libeap/mppe_keys.c | 211 +- + src/modules/rlm_eap/radeapclient.c | 8 + + src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c | 51 +- + .../rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c | 64 +- + src/modules/rlm_eap/types/rlm_eap_peap/peap.c | 22 +- + .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c | 21 +- + src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h | 190 ++ + src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c | 779 +++++--- + src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h | 16 +- + .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c | 508 ++++- + .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h | 2 + + .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c | 52 +- + .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h | 5 + + .../rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c | 20 +- + src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c | 25 +- + src/modules/rlm_exec/rlm_exec.c | 4 +- + src/modules/rlm_expr/rlm_expr.c | 115 ++ + src/modules/rlm_files/rlm_files.c | 2 +- + src/modules/rlm_ldap/ldap.h | 4 + + src/modules/rlm_mschap/rlm_mschap.c | 99 +- + src/modules/rlm_otp/otp_mppe.c | 16 +- + src/modules/rlm_otp/otp_radstate.c | 3 +- + src/modules/rlm_rest/rest.c | 107 +- + src/modules/rlm_rest/rest.h | 18 + + src/modules/rlm_rest/rlm_rest.c | 12 + + src/modules/rlm_wimax/milenage.c | 642 +++++++ + src/modules/rlm_wimax/milenage.h | 128 ++ + src/modules/rlm_wimax/rlm_wimax.c | 429 ++++- + src/tests/keywords/md4 | 58 + + 84 files changed, 9222 insertions(+), 1902 deletions(-) + +diff --git a/man/man1/radclient.1 b/man/man1/radclient.1 +index 229dcae0c7..b83bee931a 100644 +--- a/man/man1/radclient.1 ++++ b/man/man1/radclient.1 +@@ -1,10 +1,11 @@ +-.TH RADCLIENT 1 "22 March 2019" "" "FreeRADIUS Daemon" ++.TH RADCLIENT 1 "21 May 2024" "" "FreeRADIUS Daemon" + .SH NAME + radclient - send packets to a RADIUS server, show reply + .SH SYNOPSIS + .B radclient + .RB [ \-4 ] + .RB [ \-6 ] ++.RB [ \-b ] + .RB [ \-c + .IR count ] + .RB [ \-d +@@ -52,6 +53,13 @@ automatically encrypted before the packet is sent to the server. + Use IPv4 (default) + .IP \-6 + Use IPv6 ++.IP \-b ++Enforce the Blast RADIUS checks. All replies to an Access-Request packet ++must contain a Message-Authenticator as the first attribute. ++ ++For compatibility with old servers, this flag is not set by default. ++However, radclient still checks for the Blast RADIUS signature, and ++discards packets which match the attack. + .IP \-c\ \fIcount\fP + Send each packet \fIcount\fP times. + .IP \-d\ \fIraddb_directory\fP +diff --git a/man/man1/radtest.1 b/man/man1/radtest.1 +index b3184779c0..d90651aba8 100644 +--- a/man/man1/radtest.1 ++++ b/man/man1/radtest.1 +@@ -1,4 +1,4 @@ +-.TH RADTEST 1 "5 April 2010" "" "FreeRADIUS Daemon" ++.TH RADTEST 1 "21 May 2024" "" "FreeRADIUS Daemon" + .SH NAME + radtest - send packets to a RADIUS server, show reply + .SH SYNOPSIS +@@ -15,6 +15,8 @@ radtest - send packets to a RADIUS server, show reply + .IR ] + .RB [ \-6 + .IR ] ++.RB [ \-b ++.IR ] + .I user password radius-server nas-port-number secret + .RB [ ppphint ] + .RB [ nasname ] +@@ -26,6 +28,13 @@ way to test a radius server. + + .SH OPTIONS + ++.IP \-b ++Enforce the Blast RADIUS checks. All replies to an Access-Request packet ++must contain a Message-Authenticator as the first attribute. ++ ++For compatibility with old servers, this flag is not set by default. ++However, radclient still checks for the Blast RADIUS signature, and ++discards packets which match the attack. + .IP "\-d \fIraddb_directory\fP" + The directory that contains the RADIUS dictionary files. Defaults to + \fI/etc/raddb\fP. +@@ -35,7 +44,7 @@ Use \fIproto\fP transport protocol ("tcp" or "udp"). + Only available if FreeRADIUS is compiled with TCP transport support. + + .IP "\-t \fIpap/chap/mschap/eap-md5\fP" +-Choose the authentiction method to use. e.g. "-t pap", "-t chap", "-t ++Choose the authentication method to use. e.g. "-t pap", "-t chap", "-t + mschap", or "-t eap-md5",. Defaults to "pap". Using EAP-MD5 requires + that the "radeapclient" program is installed. + +diff --git a/raddb/clients.conf b/raddb/clients.conf +index 76b300d3c5..28bd6863b5 100644 +--- a/raddb/clients.conf ++++ b/raddb/clients.conf +@@ -82,17 +82,33 @@ client localhost { + # Quotation marks can be entered by escaping them, + # e.g. "foo\"bar" + # +- # A note on security: The security of the RADIUS protocol ++ # A note on security: The security of the RADIUS protocol + # depends COMPLETELY on this secret! We recommend using a +- # shared secret that is composed of: ++ # shared secret that at LEAST 16 characters long. It should ++ # preferably be 32 characters in length. The secret MUST be ++ # random, and should not be words, phrase, or anything else ++ # that is recognisable. + # +- # upper case letters +- # lower case letters +- # numbers ++ # Computing power has increased enormously since RADIUS was ++ # first defined. A hobbyist with a high-end GPU can try ALL ++ # of the 8-character shared secrets in about a day. The ++ # security of shared secrets increases MUCH more with the ++ # length of the shared secret, than with number of different ++ # characters used in it. So don't bother trying to use ++ # "special characters" or anything else in an attempt to get ++ # un-guessable secrets. Instead, just get data from a secure ++ # random number generator, and use that. + # +- # And is at LEAST 8 characters long, preferably 16 characters in +- # length. The secret MUST be random, and should not be words, +- # phrase, or anything else that is recognisable. ++ # You should create shared secrets using a method like this: ++ # ++ # dd if=/dev/random bs=1 count=24 | base64 ++ # ++ # This process will give output which takes 24 random bytes, ++ # and converts them to 32 characters of ASCII. The output ++ # should be accepted by all RADIUS clients. ++ # ++ # You should NOT create shared secrets by hand. They will ++ # not be random. They will will be trivial to crack. + # + # The default secret below is only for testing, and should + # not be used in any real environment. +@@ -100,15 +116,45 @@ client localhost { + secret = testing123 + + # +- # Old-style clients do not send a Message-Authenticator +- # in an Access-Request. RFC 5080 suggests that all clients +- # SHOULD include it in an Access-Request. The configuration +- # item below allows the server to require it. If a client +- # is required to include a Message-Authenticator and it does +- # not, then the packet will be silently discarded. ++ # The global configuration "security.require_message_authenticator" ++ # flag sets the default for all clients. That default can be ++ # over-ridden here, by setting it to a value. If no value is set, ++ # then the default from the "radiusd.conf" file is used. ++ # ++ # See that file for full documentation on the flag, along ++ # with allowed values and meanings. ++ # ++ # This flag exists solely for legacy clients which do not send ++ # Message-Authenticator in all Access-Request packets. We do not ++ # recommend setting it to "no". + # +- # allowed values: yes, no +- require_message_authenticator = no ++ # The number one way to protect yourself from the BlastRADIUS ++ # attack is to update all RADIUS servers, and then set this ++ # flag to "yes". If all RADIUS servers are updated, and if ++ # all of them have this flag set to "yes" for all clients, ++ # then your network is safe. You can then upgrade the ++ # clients when it is convenient, instead of rushing the ++ # upgrades. ++ # ++ # allowed values: yes, no, auto ++ # ++# require_message_authenticator = no ++ ++ # ++ # The global configuration "security.limit_proxy_state" ++ # flag sets the default for all clients. That default can be ++ # over-ridden here, by setting it to "no". ++ # ++ # See that file for full documentation on the flag, along ++ # with allowed values,and meanings. ++ # ++ # This flag exists solely for legacy clients which do not send ++ # Message-Authenticator in all Access-Request packets. We do not ++ # recommend setting it to "no". ++ # ++ # allowed values: yes, no, auto ++ # ++# limit_proxy_state = yes + + # + # The short name is used as an alias for the fully qualified +@@ -260,6 +306,26 @@ client localhost_ipv6 { + # "clients = per_socket_clients". That IP address/port combination + # will then accept ONLY the clients listed in this section. + # ++# There are additional considerations when using clients from SQL. ++# ++# A client can be link to a virtual server via modules such as SQL. ++# This link is done via the following process: ++# ++# If there is no listener in a virtual server, SQL clients are added ++# to the global list for that virtual server. ++# ++# If there is a listener, and the first listener does not have a ++# "clients=..." configuration item, SQL clients are added to the ++# global list. ++# ++# If there is a listener, and the first one does have a "clients=..." ++# configuration item, SQL clients are added to that list. The client ++# { ...} ` configured in that list are also added for that listener. ++# ++# The only issue is if you have multiple listeners in a virtual ++# server, each with a different client list, then the SQL clients are ++# added only to the first listener. ++# + #clients per_socket_clients { + # client socket_client { + # ipaddr = 192.0.2.4 +diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap +index a89a783663..bf73485e3c 100644 +--- a/raddb/mods-available/eap ++++ b/raddb/mods-available/eap +@@ -281,7 +281,7 @@ eap { + # + # openssl dhparam -out certs/dh 2048 + # +- dh_file = ${certdir}/dh ++ # dh_file = ${certdir}/dh + + # If your system doesn't have /dev/urandom, + # you will need to create this file, and +@@ -392,8 +392,8 @@ eap { + # tls_max_version. + # + # disable_tlsv1_2 = no +- disable_tlsv1_1 = yes +- disable_tlsv1 = yes ++ # disable_tlsv1_1 = yes ++ # disable_tlsv1 = yes + + # Set min / max TLS version. Mainly for Debian + # "trusty", which disables older versions of TLS, and +diff --git a/raddb/proxy.conf b/raddb/proxy.conf +index 91b4b37930..7295538d5e 100644 +--- a/raddb/proxy.conf ++++ b/raddb/proxy.conf +@@ -66,6 +66,46 @@ proxy server { + # + default_fallback = no + ++ # ++ # Whether or not we allow dynamic home servers. ++ # ++ # This setting should be "no" by default. If set to "yes", ++ # it can slow the server down, due to mutex locking across ++ # multiple threads. ++ # ++ # Dynamic servers will work ONLY with the "directory" ++ # configuration below. ++ # ++# dynamic = yes ++ ++ # ++ # The directory which contains dynamic home servers. Each ++ # file in the directory should be a normal "home_server" ++ # definitions. This directory does not exist by default. ++ # ++ # e.g: The content of home_servers/example.com should be ++ # a home server definition. ++ # ++ # The name of the home server MUST be the same as the ++ # filename. ++ # ++ # Each home server must be set to only one type. e.g. ++ # "type = auth", and not "type = auth+acct" ++ # ++ # For example: ++ # ++ # home_server example.com { ++ # type = auth ++ # ipaddr = ... ++ # ... ++ # } ++ # ++ # For complete documentation, please see ++ # ++ # doc/configuration/dynamic_home_servers.md ++ # ++# directory = ${confdir}/home_servers ++ + } + + ####################################################################### +@@ -111,6 +151,12 @@ proxy server { + # which was awkward. In 2.0, they have been made independent + # from realms, which is better for a number of reasons. + # ++# You can proxy to a specific home server by doing: ++# ++# update control { ++# Home-Server-Name = "name of home server" ++# } ++# + home_server localhost { + # + # Home servers can be sent Access-Request packets +@@ -204,6 +250,24 @@ home_server localhost { + # + secret = testing123 + ++ # ++ # The global configuration "security.require_message_authenticator" ++ # flag sets the default for all home servers. That default can be ++ # over-ridden here, by setting it to a value. If no value is set, ++ # then the default from the "radiusd.conf" file is used. ++ # ++ # See that file for full documentation on the flag, along ++ # with allowed values and meanings. ++ # ++ # This flag exists solely for legacy home servers which do ++ # not send Message-Authenticator in all Access-Accept, ++ # Access-Reject, or Access-Challenge packets. We do not ++ # recommend setting it to "no". ++ # ++ # allowed values: yes, no, auto ++ # ++# require_message_authenticator = no ++ + ############################################################ + # + # The rest of the configuration items listed here are optional, +@@ -510,6 +574,12 @@ home_server localhost { + # 10 'realm" sections, and one "home_server_pool" section to tie the + # two together. + # ++# You can proxy to a specific home server pool by doing: ++# ++# update control { ++# Home-Server-Pool = "name of pool" ++# } ++# + home_server_pool my_auth_failover { + # + # The type of this pool controls how home servers are chosen. +@@ -664,10 +734,9 @@ realm example.com { + auth_pool = my_auth_failover + # acct_pool = acct + +- # As of Version 3.0, the server can proxy CoA packets +- # based on the Operator-Name attribute. This requires +- # that the "suffix" module be listed in the "recv-coa" +- # section. ++ # The server can proxy CoA packets based on the Operator-Name ++ # attribute. This requires that the "suffix" module be ++ # listed in the "recv-coa" section. + # + # See raddb/sites-available/coa + # +@@ -689,6 +758,9 @@ realm example.com { + # + # If you do not want this to happen, uncomment "nostrip" below. + # ++ # Note that if the system is doing EAP, you MUST set the "nostrip" ++ # option for realms used in EAP. Otherwise EAP will fail. ++ # + # nostrip + + # There are no more configuration entries for a realm. +@@ -790,9 +862,6 @@ realm LOCAL { + # Yes, this rule is different than the normal "unlang" rules for + # regular expressions. That may be fixed in a future release. + # +-# - for version 3.0.4 and following, with "correct_escapes = true", +-# use normal regex backslash rules. Just one. Not two. +-# + # - If you are matching domain names, put a '$' at the end of the regex + # that matches the domain name. This tells the regex matching code + # that the realm ENDS with the domain name, so it does not match +diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in +index e8aee3c001..4909c1b901 100644 +--- a/raddb/radiusd.conf.in ++++ b/raddb/radiusd.conf.in +@@ -25,7 +25,7 @@ + # The "sites-available" directory contains many + # worked examples of common configurations. + # +-# raddb/certs/README ++# raddb/certs/README.md + # How to create certificates for EAP or RadSec. + # + # Every configuration item in the server is documented +@@ -151,13 +151,21 @@ pidfile = ${run_dir}/${name}.pid + # + # correct_escapes: use correct backslash escaping + # ++# This setting is for compatibility with 3.0.4 and earlier. If ++# you're running a copy of the configuration from 3.0.4, you can ++# change this setting to "no" in order to run a new binary using the ++# old configuration files. ++# ++# If you've created the configuration after 2014, this should be set ++# to "true", and you can ignore it. ++# + # Prior to version 3.0.5, the handling of backslashes was a little + # awkward, i.e. "wrong". In some cases, to get one backslash into + # a regex, you had to put 4 in the config files. + # +-# Version 3.0.5 fixes that. However, for backwards compatibility, ++# Version 3.0.5 fixed that. However, for backwards compatibility, + # the new method of escaping is DISABLED BY DEFAULT. This means +-# that upgrading to 3.0.5 won't break your configuration. ++# that upgrading from a 3.0.4 or below won't break your configuration. + # + # If you don't have double backslashes (i.e. \\) in your configuration, + # this won't matter to you. If you do have them, fix that to use only +@@ -207,8 +215,8 @@ correct_escapes = true + + # max_request_time: The maximum time (in seconds) to handle a request. + # +-# Requests which take more time than this to process may be killed, and +-# a REJECT message is returned. ++# Requests which take more time than this to process are discarded, ++# and no reply is returned. + # + # WARNING: If you notice that requests take a long time to be handled, + # then this MAY INDICATE a bug in the server, in one of the modules +@@ -219,6 +227,12 @@ correct_escapes = true + # then it probably means that you haven't indexed the database. See your + # SQL server documentation for more information. + # ++# In general, values larger than 30 or so are useless. If the server ++# replies to the NAS after 60 seconds, the NAS will almost always ++# have given up on the request, and gone to another one. You can see ++# this happening when the logs have messages like "received conflicting ++# packet", and "discarding old request". ++# + # Useful range of values: 5 to 120 + # + max_request_time = 30 +@@ -279,6 +293,17 @@ max_requests = 16384 + # + hostname_lookups = no + ++# ++# Run a "Post-Auth-Type Client-Lost" section. This ONLY happens when ++# the server sends an Access-Challenge, and then client does not ++# respond to it. The goal is to allow administrators to log ++# something when the client does not respond. ++# ++# See sites-available/default, "Post-Auth-Type Client-Lost" for more ++# information. ++# ++#postauth_client_lost = no ++ + # + # Logging section. The various "log_*" configuration items + # will eventually be moved here. +@@ -377,6 +402,25 @@ log { + # The message when the user exceeds the Simultaneous-Use limit. + # + msg_denied = "You are already logged in - access denied" ++ ++ # Suppress "secret" attributes when printing them in debug mode. ++ # ++ # Secrets are NOT tracked across xlat expansions. If your ++ # configuration puts secrets into other strings, they will ++ # still get printed. ++ # ++ # Setting this to "yes" means that the server prints ++ # ++ # <<< secret >>> ++ # ++ # instead of the value, for attriburtes which contain secret ++ # information. e.g. User-Name, Tunnel-Password, etc. ++ # ++ # This configuration is disabled by default. It is extremely ++ # important for administrators to be able to debug user logins ++ # by seeing what is actually being sent. ++ # ++# suppress_secrets = no + } + + # The program to execute to do concurrency checks. +@@ -436,6 +480,24 @@ ENV { + # LD_PRELOAD = /path/to/library2.so + } + ++# ++# TEMPLATES ++# ++# Template files hold common definitions that can be used in other ++# server sections. When a template is referenced, the configuration ++# items within the referenced template are copied to the referencing ++# section. ++# ++# Using templates reduces repetition of common configuration items, ++# which in turn makes the server configuration easier to maintain. ++# ++# See templates.conf for examples of using templates, and the ++# referencing syntax. ++# ++ ++# $INCLUDE templates.conf ++ ++ + # SECURITY CONFIGURATION + # + # There may be multiple methods of attacking on the server. This +@@ -462,7 +524,7 @@ security { + # + # If you are worried about security issues related to this + # use of chdir, then simply ensure that the "raddb" directory +- # is inside of the chroot, end be sure to do "cd raddb" ++ # is inside of the chroot, and be sure to do "cd raddb" + # BEFORE starting the server. + # + # If the server is statically linked, then the only files +@@ -538,8 +600,7 @@ security { + # rejects will be sent at 'cleanup_delay' time, when the request + # is deleted from the internal cache of requests. + # +- # As of Version 3.0.5, "reject_delay" has sub-second resolution. +- # e.g. "reject_delay = 1.4" seconds is possible. ++ # This number can be a decimal, e.g. 3.4 + # + # Useful ranges: 1 to 5 + reject_delay = 1 +@@ -564,6 +625,191 @@ security { + # + status_server = yes + ++ # ++ # Global configuration for requiring Message-Authenticator in ++ # all Access-* packets sent over UDP or TCP. This flag is ++ # ignored for TLS. ++ # ++ # The number one way to protect yourself from the BlastRADIUS ++ # attack is to update all RADIUS servers, and then set this ++ # flag to "yes". If all RADIUS servers are updated, and if ++ # all of them have this flag set to "yes" for all clients, ++ # then your network is safe. You can then upgrade the ++ # clients when it is convenient, instead of rushing the ++ # upgrades. ++ # ++ # This flag sets the global default for all clients and home ++ # servers. It can be over-ridden in an individual client or ++ # home_server definition by adding the same flag to that ++ # section with an appropriate value. ++ # ++ # All upgraded RADIUS implementations should send ++ # Message-Authenticator in all Access-Request, Access-Accept, ++ # Access-Reject, and Access-Challenge packets. Once all ++ # systems are upgraded, setting this flag to "yes" is the ++ # best protection from the attack. ++ # ++ # The possible values and meanings for ++ # "require_message_authenticator" are; ++ # ++ # * "no" - allow Access-* packet which do not contain ++ # Message-Authenticator ++ # ++ # For a client, if this flag is set to "no", then the ++ # "limit_proxy_state" flag, below, is also checked. ++ # ++ # For a home_server, if this flag is set to "no", then the ++ # Access-Accept, Access-Reject, and Access-Challenge ++ # packets do not need to contain Message-Authenticator. ++ # ++ # The only reason to set this flag to "no" is when the ++ # RADIUS client or home server has not been updated. It is ++ # always safer to set this flag "no" in the individual ++ # client or home_server definition. The global flag SHOULD ++ # still be set to a safe value: "yes". ++ # ++ # WARNING: Setting this flag and the "limit_proxy_state" ++ # flag to "no" will allow MITM attackers to create fake ++ # Access-Accept packets to the NAS! At least one of them ++ # MUST be set to "yes" for the system to have any ++ # protection against the attack. ++ # ++ # * "yes" - Require that all Access-* packets (client and ++ # home_server) contain Message-Authenticator. If a packet ++ # does not contain Message-Authenticator, then it is ++ # discarded. ++ # ++ # * "auto" - Automatically determine the value of the flag, ++ # based on the first packet received from that client or ++ # home_server. ++ # ++ # If the packet does not contain Message-Authenticator, ++ # then the value of the flag is automatically switched to ++ # "no". ++ # ++ # If the packet contains Message-Authenticator but not ++ # EAP-Message, then the value of the flag is automatically ++ # switched to "yes". The server has to check for ++ # EAP-Message, because the previous RFCs require that the ++ # packet contains Message-Authenticator when it also ++ # contains EAP-Message. So having a Message-Authenticator ++ # in those packets doesn't give the server enough ++ # information to determined if the client or home_server ++ # has been updated. ++ # ++ # If the packet contains Message-Authenticator and ++ # EAP-Message, then the flag is left at the "auto" value. ++ # ++ # WARNING: This switch is done for the first packet ++ # received from that client or home server. The change ++ # does NOT persist across server restarts. You MUST change ++ # the to "yes" manually, in order to make a permanent ++ # change to the configuration. ++ # ++ # WARNING: If there are multiple NASes with the same source ++ # IP and client definitions, BUT the NASes have different ++ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. ++ # ++ # That is, when there are multiple different RADIUS clients ++ # behind one NATed IP address, then these security settings ++ # have to be set to allow the MOST INSECURE packets to be ++ # processed. This is a terrible idea, and will leave your ++ # network vulnerable to the attack. Please upgrade all ++ # clients immediately. ++ # ++ # The only solution to that rare configuration is to set ++ # this flag to "no", in which case the network will work, ++ # but will be vulnerable to the attack. ++ # ++ require_message_authenticator = auto ++ ++ # ++ # Global configuration for limiting the combination of ++ # Proxy-State and Message-Authenticator. This flag only ++ # applies to packets sent over UDP or TCP. This flag is ++ # ignored for TLS. ++ # ++ # This flag sets the global default for all clients. It can ++ # be over-ridden in an individual client definition by adding ++ # the same flag to that section with an appropriate value. ++ # ++ # If "require_message_authenticator" is set to "yes", this ++ # configuration item is ignored. ++ # ++ # If "require_message_authenticator" is set to "no", this ++ # configuration item is checked. ++ # ++ # The possible values and meanings for "limit_proxy_state" are; ++ # ++ # * "no" - allow any packets from the client, even packets ++ # which contain the BlastRADIUS attack. Please be aware ++ # that in this configuration the server will complain for ++ # EVERY packet which it receives. ++ # ++ # The only reason to set this flag to "no" is when the ++ # client is a proxy, AND the proxy does not send ++ # Message-Authenticator in Access-Request packets. Even ++ # then, the best approach to fix the issue is to (1) update ++ # the proxy to send Message-Authenticator, and if that ++ # can't be done, then (2) set this flag to "no", but ONLY ++ # for that one client. The global flag SHOULD still be set ++ # to a safe value: "yes". ++ # ++ # WARNING: Setting both this flag and the ++ # "require_message_authenticator" flag to "no" will allow ++ # MITM attackers to create fake Access-Accept packets to the ++ # NAS! At least one of them MUST be set to "yes" for the ++ # system to have any protection against the attack. ++ # ++ # * "yes" - Allow packets without Message-Authenticator, ++ # but only when they do not contain Proxy-State. ++ # packets which contain Proxy-State MUST also contain ++ # Message-Authenticator, otherwise they are discarded. ++ # ++ # This setting is safe for most NASes, GGSNs, BRAS, etc. ++ # Most regular RADIUS clients do not send Proxy-State ++ # attributes for Access-Request packets that they originate. ++ # However some aggregators (e.g. Wireless LAN Controllers) ++ # may act as a RADIUS proxy for requests from their cohort ++ # of managed devices, and in such cases will provide a ++ # Proxy-State attribute. For those systems, you _must_ look ++ # at the actual packets to determine what to do. It may be ++ # that the only way to fix the vulnerability is to upgrade ++ # the WLC, and set "require_message_authenticator" to "yes". ++ # ++ # * "auto" - Automatically determine the value of the flag, ++ # based on the first packet received from that client. ++ # ++ # If the packet contains Proxy-State but no ++ # Message-Authenticator, then the value of the flag is ++ # automatically switched to "no". ++ # ++ # For all other situations, the value of the flag is ++ # automatically switched to "yes". ++ # ++ # WARNING: This switch is done for the first packet ++ # received from that client. The change does NOT persist ++ # across server restarts. You MUST change the to "yes" ++ # manually, in order to make a permanent change to the ++ # configuration. ++ # ++ # WARNING: If there are multiple NASes with the same source ++ # IP and client definitions, BUT the NASes have different ++ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. ++ # ++ # That is, when there are multiple different RADIUS clients ++ # behind one NATed IP address, then these security settings ++ # have to be set to allow the MOST INSECURE packets to be ++ # processed. This is a terrible idea, and will leave your ++ # network vulnerable to the attack. Please upgrade all ++ # clients immediately. ++ # ++ # The only solution to that rare configuration is to set ++ # this flag to "no", in which case the network will work, ++ # but will be vulnerable to the attack. ++ # ++ limit_proxy_state = auto ++ + @openssl_version_check_config@ + } + +@@ -754,19 +1000,19 @@ modules { + # directory from start to finish. Which means that the + # modules are read off of disk randomly. + # +- # As of 3.0.18, you can list individual modules *before* the +- # directory inclusion. Those modules will be loaded first. +- # Then, when the directory is read, those modules will be +- # skipped and not read twice. ++ # You can list individual modules *before* the directory ++ # inclusion. Those modules will be loaded first. Then, when ++ # the directory is read, those modules will be skipped and ++ # not read twice. + # + # $INCLUDE mods-enabled/sql + + # +- # As of 3.0, modules are in mods-enabled/. Files matching +- # the regex /[a-zA-Z0-9_.]+/ are loaded. The modules are +- # initialized ONLY if they are referenced in a processing +- # section, such as authorize, authenticate, accounting, +- # pre/post-proxy, etc. ++ # All modules are in ther mods-enabled/ directory. Files ++ # matching the regex /[a-zA-Z0-9_.]+/ are read. The ++ # modules are initialized ONLY if they are referenced in a ++ # processing section, such as authorize, authenticate, ++ # accounting, pre/post-proxy, etc. + # + $INCLUDE mods-enabled/ + } +diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls +index e2a3b080ca..25a10b6364 100644 +--- a/raddb/sites-available/tls ++++ b/raddb/sites-available/tls +@@ -468,6 +468,14 @@ home_server tls { + # this configuration item. + ca_file = ${cadir}/ca.pem + ++ # In previous versions, outbound RadSec connections ++ # would put the home server certificate into the ++ # TLS-Client-Cert* attributes. Set this configuration ++ # item to "yes" in order to have the home server ++ # certificates placed into the "TLS-Cert-*" attributes. ++ # ++# fix_cert_order = yes ++ + # + # For TLS-PSK, the key should be specified + # dynamically, instead of using a hard-coded +diff --git a/share/dictionary.freeradius b/share/dictionary.freeradius +index c92c5214ad..bfab9296c4 100644 +--- a/share/dictionary.freeradius ++++ b/share/dictionary.freeradius +@@ -107,6 +107,8 @@ VALUE FreeRADIUS-Stats-Server-State Alive 0 + VALUE FreeRADIUS-Stats-Server-State Zombie 1 + VALUE FreeRADIUS-Stats-Server-State Dead 2 + VALUE FreeRADIUS-Stats-Server-State Idle 3 ++VALUE FreeRADIUS-Stats-Server-State Admin-Down 4 ++VALUE FreeRADIUS-Stats-Server-State Connection-Fail 5 + + # + # When a home server is marked "dead" or "alive" +@@ -182,4 +184,7 @@ ATTRIBUTE FreeRADIUS-EAP-FAST-PKCS 186.20 octets + + ATTRIBUTE FreeRADIUS-Stats-Error 187 string + ++ATTRIBUTE FreeRADIUS-Stats-Client-IPv6-Address 188 ipv6addr ++ATTRIBUTE FreeRADIUS-Stats-Server-IPv6-Address 189 ipv6addr ++ + END-VENDOR FreeRADIUS +diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal +index 724e1f7ff6..347e3e59f3 100644 +--- a/share/dictionary.freeradius.internal ++++ b/share/dictionary.freeradius.internal +@@ -148,7 +148,7 @@ VALUE EAP-IKEv2-IDType DER_ASN1_GN 10 + VALUE EAP-IKEv2-IDType KEY_ID 11 + + ATTRIBUTE EAP-IKEv2-ID 1104 string +-ATTRIBUTE EAP-IKEv2-Secret 1105 string ++ATTRIBUTE EAP-IKEv2-Secret 1105 string secret + ATTRIBUTE EAP-IKEv2-AuthType 1106 integer + + VALUE EAP-IKEv2-AuthType none 0 +@@ -196,7 +196,7 @@ ATTRIBUTE FreeRADIUS-Client-Require-MA 1122 integer + VALUE FreeRADIUS-Client-Require-MA no 0 + VALUE FreeRADIUS-Client-Require-MA yes 1 + +-ATTRIBUTE FreeRADIUS-Client-Secret 1123 string ++ATTRIBUTE FreeRADIUS-Client-Secret 1123 string secret + ATTRIBUTE FreeRADIUS-Client-Shortname 1124 string + ATTRIBUTE FreeRADIUS-Client-NAS-Type 1125 string + ATTRIBUTE FreeRADIUS-Client-Virtual-Server 1126 string +@@ -254,6 +254,7 @@ ATTRIBUTE FreeRADIUS-Response-Delay-USec 1155 integer + + ATTRIBUTE REST-HTTP-Header 1160 string + ATTRIBUTE REST-HTTP-Body 1161 string ++ATTRIBUTE REST-HTTP-Status-Code 1162 integer + + ATTRIBUTE Cache-Expires 1170 date + ATTRIBUTE Cache-Created 1171 date +@@ -277,7 +278,24 @@ ATTRIBUTE SSHA2-256-Password 1178 octets + ATTRIBUTE SSHA2-384-Password 1179 octets + ATTRIBUTE SSHA2-512-Password 1180 octets + ++ATTRIBUTE PBKDF2-Password 1181 octets ++ATTRIBUTE SSHA3-224-Password 1182 octets ++ATTRIBUTE SSHA3-256-Password 1183 octets ++ATTRIBUTE SSHA3-384-Password 1184 octets ++ATTRIBUTE SSHA3-512-Password 1185 octets ++ + ATTRIBUTE MS-CHAP-Peer-Challenge 1192 octets ++ATTRIBUTE Home-Server-Name 1193 string ++ATTRIBUTE Originating-Realm-Key 1194 string ++ATTRIBUTE Proxy-To-Originating-Realm 1195 string ++ ++ATTRIBUTE TOTP-Secret 1194 string # base32 encoded ++ATTRIBUTE TOTP-Key 1195 octets # raw key ++ATTRIBUTE TOTP-Password 1196 string ++ ++ATTRIBUTE Proxy-Tunneled-Request-As-EAP 1197 integer ++VALUE Proxy-Tunneled-Request-As-EAP No 0 ++VALUE Proxy-Tunneled-Request-As-EAP Yes 1 + + # + # Range: 1200-1279 +@@ -318,6 +336,10 @@ ATTRIBUTE EAP-Sim-Algo-Version 1216 integer + ATTRIBUTE Outer-Realm-Name 1218 string + ATTRIBUTE Inner-Realm-Name 1219 string + ++ATTRIBUTE EAP-Pwd-Password-Hash 1220 octets ++ATTRIBUTE EAP-Pwd-Password-Salt 1221 octets ++ATTRIBUTE EAP-Pwd-Password-Prep 1222 byte ++ + # + # Range: 1280 - 1535 + # EAP-type specific attributes +@@ -516,6 +538,11 @@ ATTRIBUTE Tmp-Cast-IPv4Prefix 1870 ipv4prefix + # these attributes. + # + ATTRIBUTE WiMAX-MN-NAI 1900 string ++ATTRIBUTE WiMAX-SIM-Ki 1901 octets ++ATTRIBUTE WiMAX-SIM-OPc 1902 octets ++ATTRIBUTE WiMAX-SIM-AMF 1903 octets ++ATTRIBUTE WiMAX-SIM-SQN 1904 octets ++ATTRIBUTE WiMAX-SIM-RAND 1905 octets + + ATTRIBUTE TLS-Cert-Serial 1910 string + ATTRIBUTE TLS-Cert-Expiration 1911 string +@@ -526,7 +553,7 @@ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Email 1915 string + ATTRIBUTE TLS-Cert-Subject-Alt-Name-Dns 1916 string + ATTRIBUTE TLS-Cert-Subject-Alt-Name-Upn 1917 string + ATTRIBUTE TLS-Cert-Valid-Since 1918 string +-# 1919: reserved for future cert attribute ++ATTRIBUTE TLS-Session-Information 1919 string + ATTRIBUTE TLS-Client-Cert-Serial 1920 string + ATTRIBUTE TLS-Client-Cert-Expiration 1921 string + ATTRIBUTE TLS-Client-Cert-Issuer 1922 string +@@ -543,10 +570,19 @@ ATTRIBUTE TLS-Client-Cert-Subject-Alt-Name-Upn 1932 string + ATTRIBUTE TLS-PSK-Identity 1933 string + ATTRIBUTE TLS-Client-Cert-X509v3-Extended-Key-Usage-OID 1936 string + ATTRIBUTE TLS-Client-Cert-Valid-Since 1937 string ++ATTRIBUTE TLS-Cache-Method 1938 integer ++VALUE TLS-Cache-Method save 1 ++VALUE TLS-Cache-Method load 2 ++VALUE TLS-Cache-Method clear 3 ++VALUE TLS-Cache-Method refresh 4 + +-# 1938 - 1939: reserved for future cert attributes + +-# 1940 - 1949: reserved for TLS session caching, mostly in 4.0 ++# 1939: reserved for future cert attributes ++ ++# 1940 - 1959: reserved for TLS session caching, mostly in 4.0 ++ ++ATTRIBUTE TLS-Session-ID 1940 octets ++ATTRIBUTE TLS-Session-Data 1942 octets + + # Set by EAP-TLS code + ATTRIBUTE TLS-OCSP-Cert-Valid 1943 integer +@@ -560,8 +596,13 @@ ATTRIBUTE TLS-Cache-Filename 1946 string + ATTRIBUTE TLS-Session-Version 1947 string + ATTRIBUTE TLS-Session-Cipher-Suite 1948 string + ++ATTRIBUTE TLS-Session-Cert-File 1949 string ++ATTRIBUTE TLS-Session-Cert-Private-Key-File 1950 string ++ ++ATTRIBUTE TLS-Server-Name-Indication 1951 string ++ + # +-# Range: 1950-2099 ++# Range: 1960-2099 + # Free + # + # Range: 2100-2199 +@@ -650,6 +691,7 @@ VALUE Session-Type Local 1 + VALUE Post-Auth-Type Local 1 + VALUE Post-Auth-Type Reject 2 + VALUE Post-Auth-Type Challenge 3 ++VALUE Post-Auth-Type Client-Lost 4 + + # + # And Post-Proxy +diff --git a/src/include/build.h b/src/include/build.h +index 5da940c2b7..c5eaa45457 100644 +--- a/src/include/build.h ++++ b/src/include/build.h +@@ -46,9 +46,13 @@ extern "C" { + * compiler. + */ + #ifdef __GNUC__ +-# define CC_HINT(_x) __attribute__ ((_x)) ++# define CC_HINT(...) __attribute__ ((__VA_ARGS__)) ++# define likely(_x) __builtin_expect((_x), 1) ++# define unlikely(_x) __builtin_expect((_x), 0) + #else +-# define CC_HINT(_x) ++# define CC_HINT(...) ++# define likely(_x) _x ++# define unlikely(_x) _x + #endif + + #ifdef HAVE_ATTRIBUTE_BOUNDED +@@ -57,6 +61,18 @@ extern "C" { + # define CC_BOUNDED(...) + #endif + ++/* ++ * GCC uses __SANITIZE_ADDRESS__, clang uses __has_feature, which ++ * GCC complains about. ++ */ ++#ifndef __SANITIZE_ADDRESS__ ++#ifdef __has_feature ++#if __has_feature(address_sanitizer) ++#define __SANITIZE_ADDRESS__ (1) ++#endif ++#endif ++#endif ++ + /* + * Macros to add pragmas + */ +@@ -66,6 +82,7 @@ extern "C" { + * Macros for controlling warnings in GCC >= 4.2 and clang >= 2.8 + */ + #if defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 ++# define DIAG_UNKNOWN_PRAGMAS pragmas + # define DIAG_PRAGMA(_x) PRAGMA(GCC diagnostic _x) + # if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 + # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) +@@ -75,10 +92,12 @@ extern "C" { + # define DIAG_ON(_x) DIAG_PRAGMA(warning JOINSTR(-W,_x)) + # endif + #elif defined(__clang__) && ((__clang_major__ * 100) + __clang_minor__ >= 208) ++# define DIAG_UNKNOWN_PRAGMAS unknown-pragmas + # define DIAG_PRAGMA(_x) PRAGMA(clang diagnostic _x) + # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) + # define DIAG_ON(_x) DIAG_PRAGMA(pop) + #else ++# define DIAG_UNKNOWN_PRAGMAS + # define DIAG_OFF(_x) + # define DIAG_ON(_x) + #endif +@@ -137,6 +156,12 @@ extern "C" { + # endif + #endif + ++#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) ++#define NEVER_RETURNS CC_HINT(noreturn) ++#define HIDDEN CC_HINT(visibility("hidden")) ++#define UNUSED CC_HINT(unused) ++#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ ++ + #ifdef __cplusplus + } + #endif +diff --git a/src/include/clients.h b/src/include/clients.h +index 560211557f..7d3288a489 100644 +--- a/src/include/clients.h ++++ b/src/include/clients.h +@@ -26,10 +26,14 @@ + * @copyright 2015 The FreeRADIUS server project + */ + ++typedef struct radclient_list RADCLIENT_LIST; ++ ++ + /** Describes a host allowed to send packets to the server + * + */ + typedef struct radclient { ++ RADCLIENT_LIST *list; //!< parent list + fr_ipaddr_t ipaddr; //!< IPv4/IPv6 address of the host. + fr_ipaddr_t src_ipaddr; //!< IPv4/IPv6 address to send responses + //!< from (family must match ipaddr). +@@ -39,7 +43,11 @@ typedef struct radclient { + + char const *secret; //!< Secret PSK. + +- bool message_authenticator; //!< Require RADIUS message authenticator in requests. ++ fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests. ++ ++ bool dynamic_require_ma; //!< for dynamic clients ++ ++ fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests + + char const *nas_type; //!< Type of client (arbitrary). + +@@ -91,16 +99,14 @@ typedef struct radclient { + + #ifdef WITH_COA + char const *coa_name; //!< Name of the CoA home server or pool. +- home_server_t *coa_server; //!< The CoA home_server_t the client is associated with. ++ home_server_t *coa_home_server; //!< The CoA home_server_t the client is associated with. + //!< Must be used exclusively from coa_pool. +- home_pool_t *coa_pool; //!< The CoA home_pool_t the client is associated with. ++ home_pool_t *coa_home_pool; //!< The CoA home_pool_t the client is associated with. + //!< Must be used exclusively from coa_server. + bool defines_coa_server; //!< Client also defines a home_server. + #endif + } RADCLIENT; + +-typedef struct radclient_list RADCLIENT_LIST; +- + /** Callback for retrieving values when building client sections + * + * Example: +diff --git a/src/include/conffile.h b/src/include/conffile.h +index 8cb045c946..237469c880 100644 +--- a/src/include/conffile.h ++++ b/src/include/conffile.h +@@ -140,6 +140,7 @@ typedef struct timeval _timeval_t; + #define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies. + #define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value. + #define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist ++#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing + /* @} **/ + + #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\ +@@ -273,6 +274,7 @@ int cf_pair_count(CONF_SECTION const *cs); + CONF_SECTION *cf_item_parent(CONF_ITEM const *ci); + bool cf_item_is_section(CONF_ITEM const *item); + bool cf_item_is_pair(CONF_ITEM const *item); ++bool cf_item_is_data(CONF_ITEM const *item); + CONF_PAIR *cf_item_to_pair(CONF_ITEM const *item); + CONF_SECTION *cf_item_to_section(CONF_ITEM const *item); + CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp); +diff --git a/src/include/event.h b/src/include/event.h +index a29c9562a9..884edec526 100644 +--- a/src/include/event.h ++++ b/src/include/event.h +@@ -40,6 +40,7 @@ typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); + fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status); + + int fr_event_list_num_fds(fr_event_list_t *el); ++int fr_event_list_full(fr_event_list_t *el); + int fr_event_list_num_elements(fr_event_list_t *el); + + int fr_event_insert(fr_event_list_t *el, +@@ -53,6 +54,8 @@ int fr_event_now(fr_event_list_t *el, struct timeval *when); + + int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t handler, void *ctx); ++int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, ++ fr_event_fd_handler_t write_handler, void *ctx); + int fr_event_fd_delete(fr_event_list_t *el, int type, int fd); + int fr_event_loop(fr_event_list_t *el); + void fr_event_loop_exit(fr_event_list_t *el, int code); +diff --git a/src/include/libradius.h b/src/include/libradius.h +index ce2f713de1..1b975517b5 100644 +--- a/src/include/libradius.h ++++ b/src/include/libradius.h +@@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$") + * Talloc memory allocation is used in preference to malloc throughout + * the libraries and server. + */ ++#ifdef HAVE_WDOCUMENTATION ++DIAG_OFF(documentation) ++#endif + #include <talloc.h> ++#ifdef HAVE_WDOCUMENTATION ++DIAG_ON(documentation) ++#endif + + /* + * Defines signatures for any missing functions. +@@ -162,11 +168,6 @@ typedef void (*sig_t)(int); + + #define PAD(_x, _y) (_y - ((_x) % _y)) + +-#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) +-#define NEVER_RETURNS CC_HINT(noreturn) +-#define UNUSED CC_HINT(unused) +-#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ +- + typedef struct attr_flags { + unsigned int is_unknown : 1; //!< Attribute number or vendor is unknown. + unsigned int is_tlv : 1; //!< Is a sub attribute. +@@ -189,6 +190,10 @@ typedef struct attr_flags { + + unsigned int compare : 1; //!< has a paircompare registered + ++ unsigned int is_dup : 1; //!< is a duplicate of another attribute ++ ++ unsigned int secret : 1; //!< is a secret thingy ++ + uint8_t encrypt; //!< Ecryption method. + uint8_t length; + } ATTR_FLAGS; +@@ -402,6 +407,11 @@ typedef struct radius_packet { + size_t partial; + int proto; + #endif ++ bool tls; //!< uses secure transport ++ ++ bool message_authenticator; ++ bool proxy_state; ++ bool eap_message; + } RADIUS_PACKET; + + typedef enum { +@@ -443,7 +453,7 @@ size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q + + char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote); + +-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp); ++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value); + size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp); + void vp_print(FILE *, VALUE_PAIR const *); + void vp_printlist(FILE *, VALUE_PAIR const *); +@@ -472,6 +482,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value); + int dict_init(char const *dir, char const *fn); + void dict_free(void); + int dict_read(char const *dir, char const *filename); ++size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da); ++int dict_walk(fr_hash_table_walk_t callback, void *context); + + void dict_attr_free(DICT_ATTR const **da); + int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor); +@@ -507,6 +519,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor); + /* radius.c */ + int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret); + bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason); ++ ++/* ++ * 1 == require_ma ++ * 2 == msg_peek ++ * 4 == limit_proxy_state ++ * 8 == require_ma for Access-* replies and Protocol-Error ++ */ + RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags); + ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code); + void rad_recv_discard(int sockfd); +@@ -586,6 +605,7 @@ int rad_vp2attr(RADIUS_PACKET const *packet, + VALUE_PAIR const **pvp, uint8_t *ptr, size_t room); + + /* pair.c */ ++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx); + VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da); + VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor); + int fr_pair_to_unknown(VALUE_PAIR *vp); +@@ -611,6 +631,7 @@ VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor); + VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new); + void fr_pair_delete_by_num(VALUE_PAIR **, unsigned int attr, unsigned int vendor, int8_t tag); + void fr_pair_add(VALUE_PAIR **, VALUE_PAIR *); ++void fr_pair_prepend(VALUE_PAIR **, VALUE_PAIR *); + void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *add); + int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b); + int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b); +@@ -632,7 +653,7 @@ void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src); + void fr_pair_value_strcpy(VALUE_PAIR *vp, char const * src); + void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const * src, size_t len); + void fr_pair_value_sprintf(VALUE_PAIR *vp, char const * fmt, ...) CC_HINT(format (printf, 2, 3)); +-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from); ++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op); + void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, + unsigned int attr, unsigned int vendor, int8_t tag); + void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, +@@ -694,7 +715,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */ + extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */ + extern int fr_debug_lvl; /* 0 = no debugging information */ + extern uint32_t fr_max_attributes; /* per incoming packet */ +-#define FR_MAX_PACKET_CODE (52) ++#define FR_MAX_PACKET_CODE (53) + extern char const *fr_packet_codes[FR_MAX_PACKET_CODE]; + #define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE)) + extern FILE *fr_log_fp; +@@ -932,6 +953,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout); + } + #endif + ++typedef enum { ++ FR_BOOL_FALSE = 0, ++ FR_BOOL_TRUE, ++ FR_BOOL_AUTO, ++} fr_bool_auto_t; ++ + #include <freeradius-devel/packet.h> + + #ifdef WITH_TCP +diff --git a/src/include/listen.h b/src/include/listen.h +index 4f50bbf808..2b4e02bd54 100644 +--- a/src/include/listen.h ++++ b/src/include/listen.h +@@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE { + typedef enum RAD_LISTEN_STATUS { + RAD_LISTEN_STATUS_INIT = 0, + RAD_LISTEN_STATUS_KNOWN, ++ RAD_LISTEN_STATUS_PAUSE, ++ RAD_LISTEN_STATUS_RESUME, + RAD_LISTEN_STATUS_FROZEN, + RAD_LISTEN_STATUS_EOL, + RAD_LISTEN_STATUS_REMOVE_NOW +@@ -68,24 +70,41 @@ struct rad_listen { + int fd; + char const *server; + int status; +-#ifdef WITH_TCP + int count; +- bool dual; ++#ifdef WITH_TCP + rbtree_t *children; + rad_listen_t *parent; ++ ++ bool dual; + #endif + bool nodup; + bool synchronous; ++ bool dead; + uint32_t workers; + + #ifdef WITH_TLS + fr_tls_server_conf_t *tls; ++ bool check_client_connections; ++ bool nonblock; ++ bool blocked; + #endif + + rad_listen_recv_t recv; + rad_listen_send_t send; ++ ++ /* ++ * We don't need a proxy_recv, because the main loop in ++ * process.c calls listener->recv(), and we don't know ++ * what kind of packet we're receiving until we receive ++ * it. ++ */ ++ rad_listen_send_t proxy_send; ++ ++ + rad_listen_encode_t encode; + rad_listen_decode_t decode; ++ rad_listen_encode_t proxy_encode; ++ rad_listen_decode_t proxy_decode; + rad_listen_print_t print; + + CONF_SECTION const *cs; +@@ -143,9 +162,16 @@ typedef struct listen_socket_t { + tls_session_t *ssn; + REQUEST *request; /* horrible hacks */ + VALUE_PAIR *certs; ++ uint32_t connect_timeout; + pthread_mutex_t mutex; + uint8_t *data; + size_t partial; ++ enum { ++ LISTEN_TLS_INIT = 0, ++ LISTEN_TLS_CHECKING, ++ LISTEN_TLS_SETUP, ++ LISTEN_TLS_RUNNING, ++ } state; + #endif + + RADCLIENT_LIST *clients; +diff --git a/src/include/md4.h b/src/include/md4.h +index b7bdd6a15e..1492bd4a5c 100644 +--- a/src/include/md4.h ++++ b/src/include/md4.h +@@ -26,6 +26,10 @@ RCSIDH(md4_h, "$Id$") + + #include <string.h> + ++#ifdef WITH_FIPS ++#undef HAVE_OPENSSL_MD4_H ++#endif ++ + #ifdef HAVE_OPENSSL_MD4_H + # include <openssl/md4.h> + #endif +@@ -71,14 +75,58 @@ void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) + void fr_md4_transform(uint32_t buf[4], uint8_t const inc[MD4_BLOCK_LENGTH]) + CC_BOUNDED(__size__, 1, 4, 4) + CC_BOUNDED(__minbytes__, 2, MD4_BLOCK_LENGTH); ++# define fr_md4_destroy(_x) + #else /* HAVE_OPENSSL_MD4_H */ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + USES_APPLE_DEPRECATED_API + # define FR_MD4_CTX MD4_CTX + # define fr_md4_init MD4_Init + # define fr_md4_update MD4_Update + # define fr_md4_final MD4_Final + # define fr_md4_transform MD4_Transform +-#endif ++# define fr_md4_destroy(_x) ++#else ++#include <openssl/evp.h> ++ ++/* ++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of ++ * the code too much. ++ */ ++typedef struct FR_MD4_CTX { ++ EVP_MD_CTX *ctx; ++ EVP_MD const *md; ++ unsigned int len; ++} FR_MD4_CTX; ++ ++static inline void fr_md4_init(FR_MD4_CTX *ctx) ++{ ++ ctx->ctx = EVP_MD_CTX_new(); ++// ctx->md = EVP_MD_fetch(NULL, "MD4", "provider=legacy"); ++ ctx->md = EVP_md4(); ++ ctx->len = MD4_DIGEST_LENGTH; ++ ++ EVP_MD_CTX_set_flags(ctx->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); ++ EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL); ++} ++ ++static inline void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen) ++{ ++ EVP_DigestUpdate(ctx->ctx, in, inlen); ++} ++ ++static inline void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) ++{ ++ EVP_DigestFinal_ex(ctx->ctx, out, &(ctx->len)); ++} ++ ++static inline void fr_md4_destroy(FR_MD4_CTX *ctx) ++{ ++ EVP_MD_CTX_destroy(ctx->ctx); ++// EVP_MD_free(ctx->md); ++} ++ ++#endif /* OPENSSL3 */ ++#endif /* HAVE_OPENSSL_MD4_H */ + + /* md4.c */ + void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen); +diff --git a/src/include/md5.h b/src/include/md5.h +index a44584564f..b7d571ac7b 100644 +--- a/src/include/md5.h ++++ b/src/include/md5.h +@@ -26,6 +26,10 @@ RCSIDH(md5_h, "$Id$") + + # include <string.h> + ++#ifdef WITH_FIPS ++#undef HAVE_OPENSSL_MD5_H ++#endif ++ + #ifdef HAVE_OPENSSL_MD5_H + # include <openssl/md5.h> + #endif +@@ -68,14 +72,41 @@ void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx) + void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) + CC_BOUNDED(__size__, 1, 4, 4) + CC_BOUNDED(__minbytes__, 2, MD5_BLOCK_LENGTH); ++# define fr_md5_destroy(_x) ++# define fr_md5_copy(_dst, _src) _dst = _src + #else /* HAVE_OPENSSL_MD5_H */ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + USES_APPLE_DEPRECATED_API + # define FR_MD5_CTX MD5_CTX + # define fr_md5_init MD5_Init + # define fr_md5_update MD5_Update + # define fr_md5_final MD5_Final + # define fr_md5_transform MD5_Transform +-#endif ++# define fr_md5_copy(_dst, _src) _dst = _src ++# define fr_md5_destroy(_x) ++#else ++#include <openssl/evp.h> ++ ++/* ++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of ++ * the code too much. ++ */ ++typedef EVP_MD_CTX* FR_MD5_CTX; ++ ++# define fr_md5_init(_ctx) \ ++ do { \ ++ *_ctx = EVP_MD_CTX_new(); \ ++ EVP_MD_CTX_set_flags(*_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); \ ++ EVP_DigestInit_ex(*_ctx, EVP_md5(), NULL); \ ++ } while (0) ++# define fr_md5_update(_ctx, _str, _len) \ ++ EVP_DigestUpdate(*_ctx, _str, _len) ++# define fr_md5_final(_out, _ctx) \ ++ EVP_DigestFinal_ex(*_ctx, _out, NULL) ++# define fr_md5_destroy(_ctx) EVP_MD_CTX_destroy(*_ctx) ++# define fr_md5_copy(_dst, _src) EVP_MD_CTX_copy_ex(_dst, _src) ++#endif /* OPENSSL3 */ ++#endif /* HAVE_OPENSSL_MD5_H */ + + /* hmac.c */ + void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, +diff --git a/src/include/missing-h b/src/include/missing-h +index bb74e060b9..5698797b0e 100644 +--- a/src/include/missing-h ++++ b/src/include/missing-h +@@ -516,6 +516,10 @@ size_t SSL_SESSION_get_master_key(const SSL_SESSION *s, + #define O_DIRECTORY 0 + #endif + ++#ifndef O_NOFOLLOW ++#define O_NOFOLLOW 0 ++#endif ++ + /* + * Not really missing, but may be submitted as patches + * to the talloc project at some point in the future. +diff --git a/src/include/openssl3.h b/src/include/openssl3.h +new file mode 100644 +index 0000000000..4423ee538a +--- /dev/null ++++ b/src/include/openssl3.h +@@ -0,0 +1,109 @@ ++/* ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++#ifndef FR_OPENSSL3_H ++#define FR_OPENSSL3_H ++/** ++ * $Id$ ++ * ++ * @file openssl3.h ++ * @brief Wrappers to shut up OpenSSL3 ++ * ++ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) ++ */ ++ ++RCSIDH(openssl3_h, "$Id$") ++ ++/* ++ * The HMAC APIs are deprecated in OpenSSL3. We don't want to ++ * fill the code with ifdef's, so we define some horrific ++ * wrappers here. ++ * ++ * This file should be included AFTER all OpenSSL header files. ++ */ ++#ifdef HAVE_OPENSSL_SSL_H ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#include <string.h> ++#include <openssl/evp.h> ++#include <openssl/core_names.h> ++ ++typedef struct { ++ EVP_MAC *mac; ++ EVP_MAC_CTX *ctx; ++} HMAC3_CTX; ++#define HMAC_CTX HMAC3_CTX ++ ++#define HMAC_CTX_new HMAC3_CTX_new ++static inline HMAC3_CTX *HMAC3_CTX_new(void) ++{ ++ HMAC3_CTX *h = calloc(1, sizeof(*h)); ++ ++ return h; ++} ++ ++#define HMAC_Init_ex(_ctx, _key, _keylen, _md, _engine) HMAC3_Init_ex(_ctx, _key, _keylen, _md, _engine) ++static inline int HMAC3_Init_ex(HMAC3_CTX *ctx, const unsigned char *key, unsigned int keylen, const EVP_MD *md, UNUSED void *engine) ++{ ++ OSSL_PARAM params[2], *p = params; ++ char const *name; ++ char *unconst; ++ ++ ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); ++ if (!ctx->mac) return 0; ++ ++ ctx->ctx = EVP_MAC_CTX_new(ctx->mac); ++ if (!ctx->ctx) return 0; ++ ++ name = EVP_MD_get0_name(md); ++ memcpy(&unconst, &name, sizeof(name)); /* const issues */ ++ ++ p[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, unconst, 0); ++ p[1] = OSSL_PARAM_construct_end(); ++ ++ return EVP_MAC_init(ctx->ctx, key, keylen, params); ++} ++ ++#define HMAC_Update HMAC3_Update ++static inline int HMAC3_Update(HMAC3_CTX *ctx, const unsigned char *data, unsigned int datalen) ++{ ++ return EVP_MAC_update(ctx->ctx, data, datalen); ++} ++ ++#define HMAC_Final HMAC3_Final ++static inline int HMAC3_Final(HMAC3_CTX *ctx, unsigned char *out, unsigned int *len) ++{ ++ size_t mylen = *len; ++ ++ if (!EVP_MAC_final(ctx->ctx, out, &mylen, mylen)) return 0; ++ ++ *len = mylen; ++ return 1; ++} ++ ++#define HMAC_CTX_free HMAC3_CTX_free ++static inline void HMAC3_CTX_free(HMAC3_CTX *ctx) ++{ ++ if (!ctx) return; ++ ++ EVP_MAC_free(ctx->mac); ++ EVP_MAC_CTX_free(ctx->ctx); ++ free(ctx); ++} ++ ++#define HMAC_CTX_set_flags(_ctx, _flags) ++ ++#endif /* OPENSSL_VERSION_NUMBER */ ++#endif ++#endif /* FR_OPENSSL3_H */ +diff --git a/src/include/process.h b/src/include/process.h +index 8c3c7275e0..35a91bfa55 100644 +--- a/src/include/process.h ++++ b/src/include/process.h +@@ -47,6 +47,11 @@ typedef enum fr_state_action_t { /* server action */ + FR_ACTION_PROXY_REPLY, + #endif + FR_ACTION_CANCELLED, ++ FR_ACTION_CONFLICT, ++ FR_ACTION_MAX_TIME, ++ FR_ACTION_INTERNAL_FAILURE, ++ FR_ACTION_CLEANUP_DELAY, ++ FR_ACTION_COA_CANCELLED, + } fr_state_action_t; + + /* +@@ -66,9 +71,13 @@ int request_enqueue(REQUEST *request); + + int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *packet, + RADCLIENT *client, RAD_REQUEST_FUNP fun); ++void request_inject(REQUEST *request); + + #ifdef WITH_PROXY + int request_proxy_reply(RADIUS_PACKET *packet); ++ ++void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler); ++void proxy_listener_thaw(rad_listen_t *listener); + #endif + + #ifdef __cplusplus +diff --git a/src/include/radius.h b/src/include/radius.h +index 473528d65d..147d674eed 100644 +--- a/src/include/radius.h ++++ b/src/include/radius.h +@@ -61,6 +61,7 @@ typedef enum { + PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request + PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive) + PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform) ++ PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue + PW_CODE_MAX = 255, //!< Maximum possible code + } PW_CODE; + +diff --git a/src/include/radiusd.h b/src/include/radiusd.h +index b2a0a0f642..2b9bc19f15 100644 +--- a/src/include/radiusd.h ++++ b/src/include/radiusd.h +@@ -114,6 +114,7 @@ typedef struct main_config { + fr_ipaddr_t myip; //!< IP to bind to. Set on command line. + uint16_t port; //!< Port to bind to. Set on command line. + ++ bool suppress_secrets; //!< for debug levels < 3 + bool log_auth; //!< Log all authentication attempts. + bool log_accept; //!< Log Access-Accept + bool log_reject; //!< Log Access-Reject +@@ -140,6 +141,8 @@ typedef struct main_config { + uint32_t cleanup_delay; //!< How long before cleaning up cached responses. + uint32_t max_requests; + ++ bool postauth_client_lost; //!< Whether to run Post-Auth-Type Client-Lost section ++ + uint32_t debug_level; + char const *log_file; + int syslog_facility; +@@ -171,6 +174,9 @@ typedef struct main_config { + + bool exiting; //!< are we exiting? + ++ fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers ++ ++ fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients + + #ifdef ENABLE_OPENSSL_VERSION_CHECK + char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged. +@@ -191,9 +197,8 @@ typedef struct main_config { + typedef enum { + REQUEST_ACTIVE = 1, + REQUEST_STOP_PROCESSING, +- REQUEST_COUNTED + } rad_master_state_t; +-#define REQUEST_MASTER_NUM_STATES (REQUEST_COUNTED + 1) ++#define REQUEST_MASTER_NUM_STATES (REQUEST_STOP_PROCESSING + 1) + + typedef enum { + REQUEST_QUEUED = 1, +@@ -312,8 +317,10 @@ struct rad_request { + #define RAD_REQUEST_LVL_DEBUG3 (3) + #define RAD_REQUEST_LVL_DEBUG4 (4) + +-#define RAD_REQUEST_OPTION_COA (1 << 0) +-#define RAD_REQUEST_OPTION_CTX (1 << 1) ++#define RAD_REQUEST_OPTION_COA (1 << 0) ++#define RAD_REQUEST_OPTION_CTX (1 << 1) ++#define RAD_REQUEST_OPTION_CANCELLED (1 << 2) ++#define RAD_REQUEST_OPTION_STATS (1 << 3) + + #define SECONDS_PER_DAY 86400 + #define MAX_REQUEST_TIME 30 +@@ -351,8 +358,8 @@ extern char const *radacct_dir; + extern char const *radlog_dir; + extern char const *radlib_dir; + extern bool log_stripped_names; +-extern char const *radiusd_version; +-extern char const *radiusd_version_short; ++extern HIDDEN char const *radiusd_version; ++extern HIDDEN char const *radiusd_version_short; + void radius_signal_self(int flag); + + typedef enum { +@@ -374,8 +381,8 @@ int rad_accounting(REQUEST *); + int rad_coa_recv(REQUEST *request); + + /* session.c */ +-int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, char const *sessionid); +-int session_zap(REQUEST *request, uint32_t nasaddr, ++int rad_check_ts(fr_ipaddr_t const *nas_addr, uint32_t nas_port, char const *user, char const *sessionid); ++int session_zap(REQUEST *request, fr_ipaddr_t const *nas_addr, + uint32_t nas_port, char const *user, + char const *sessionid, uint32_t cliaddr, + char proto, int session_time); +@@ -558,6 +565,8 @@ int main_config_free(void); + void main_config_hup(void); + void hup_logfile(void); + ++int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str); ++ + /* listen.c */ + void listen_free(rad_listen_t **head); + int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag); +@@ -583,7 +592,7 @@ void radius_event_free(void); + int radius_event_process(void); + void radius_update_listener(rad_listen_t *listener); + void revive_home_server(void *ctx); +-void mark_home_server_dead(home_server_t *home, struct timeval *when); ++void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down); + + /* evaluate.c */ + typedef struct fr_cond_t fr_cond_t; +diff --git a/src/include/realms.h b/src/include/realms.h +index 6dae8b4f85..171a449112 100644 +--- a/src/include/realms.h ++++ b/src/include/realms.h +@@ -21,10 +21,10 @@ typedef enum { + HOME_TYPE_INVALID = 0, + HOME_TYPE_AUTH, //!< Authentication server + HOME_TYPE_ACCT, //!< Accounting server +- HOME_TYPE_AUTH_ACCT //!< Authentication and accounting server ++ HOME_TYPE_AUTH_ACCT, //!< Authentication and accounting server + + #ifdef WITH_COA +- ,HOME_TYPE_COA //!< CoA destination (NAS or Proxy) ++ HOME_TYPE_COA, //!< CoA destination (NAS or Proxy) + #endif + } home_type_t; + +@@ -36,13 +36,16 @@ typedef enum { + } home_ping_check_t; + + typedef enum { +- HOME_STATE_UNKNOWN = 0, +- HOME_STATE_ALIVE, ++ HOME_STATE_ALIVE = 0, + HOME_STATE_ZOMBIE, + HOME_STATE_IS_DEAD, ++ HOME_STATE_UNKNOWN, ++ HOME_STATE_ADMIN_DOWN, + HOME_STATE_CONNECTION_FAIL, + } home_state_t; + ++#define HOME_SERVER_IS_DEAD(_x) (((_x)->state == HOME_STATE_IS_DEAD) || ((_x)->state == HOME_STATE_ADMIN_DOWN) || ((_x)->state == HOME_STATE_CONNECTION_FAIL)) ++ + typedef struct fr_socket_limit_t { + uint32_t max_connections; + uint32_t num_connections; +@@ -59,7 +62,11 @@ typedef struct home_server { + //!< stats or when specifying home servers for a pool. + + bool dual; //!< One of a pair of homeservers on consecutive ports. +- char const *server; //!< For internal proxying ++ bool dynamic; //!< is this a dynamically added home server? ++ bool nonblock; //!< Enable a socket non-blocking to the home server. ++ fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server ++ ++ char const *virtual_server; //!< For internal proxying + char const *parent_server; + + fr_ipaddr_t ipaddr; //!< IP address of home server. +@@ -122,6 +129,8 @@ typedef struct home_server { + #endif + #ifdef WITH_TLS + fr_tls_server_conf_t *tls; ++ uint32_t connect_timeout; ++ rbtree_t *listeners; + #endif + + #ifdef WITH_STATS +@@ -202,10 +211,14 @@ CONF_SECTION *home_server_cs_afrom_client(CONF_SECTION *client); + home_server_t *home_server_byname(char const *name, int type); + #endif + #ifdef WITH_STATS ++extern int home_server_max_number; + home_server_t *home_server_bynumber(int number); + #endif + home_pool_t *home_pool_byname(char const *name, int type); + ++int home_server_afrom_file(char const *filename); ++int home_server_delete(char const *name, char const *type); ++ + #ifdef __cplusplus + } + #endif +diff --git a/src/include/socket.h b/src/include/socket.h +new file mode 100644 +index 0000000000..821c2a0b7a +--- /dev/null ++++ b/src/include/socket.h +@@ -0,0 +1,53 @@ ++#pragma once ++ ++/* ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or (at ++ * your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++ ++/** Functions for establishing and managing low level sockets ++ * ++ * @file src/include/socket.h ++ * ++ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) ++ * @author Alan DeKok (aland@freeradius.org) ++ * ++ * @copyright 2015 The FreeRADIUS project ++ */ ++RCSIDH(socket_h, "$Id$") ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#include <freeradius-devel/build.h> ++#include <freeradius-devel/missing.h> ++ ++#ifdef HAVE_SYS_UN_H ++# include <sys/un.h> ++/* ++ * The linux headers define the macro as: ++ * ++ * # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ ++ * + strlen ((ptr)->sun_path)) ++ * ++ * Which trips UBSAN, because it sees an operation on a NULL pointer. ++ */ ++# undef SUN_LEN ++# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) ++#endif ++ ++#ifdef __cplusplus ++} ++#endif +diff --git a/src/include/tls-h b/src/include/tls-h +index 62f57c4715..4bf1665483 100644 +--- a/src/include/tls-h ++++ b/src/include/tls-h +@@ -67,7 +67,7 @@ typedef enum { + } fr_tls_status_t; + extern FR_NAME_NUMBER const fr_tls_status_table[]; + +-#define MAX_RECORD_SIZE 16384 ++#define MAX_RECORD_SIZE 65536 + + /* + * A single TLS record may be up to 16384 octets in length, but a +@@ -89,12 +89,12 @@ extern FR_NAME_NUMBER const fr_tls_status_table[]; + * or configure TLS not to exceed MAX_RECORD_SIZE. + */ + typedef struct _record_t { +- uint8_t data[MAX_RECORD_SIZE]; + size_t used; ++ uint8_t data[MAX_RECORD_SIZE]; + } record_t; + + typedef struct _tls_info_t { +- int origin; ++ int origin; // 0 - received (from peer), 1 - sending (to peer) + int content_type; + uint8_t handshake_type; + uint8_t alert_level; +@@ -103,7 +103,6 @@ typedef struct _tls_info_t { + + char info_description[256]; + size_t record_len; +- int version; + } tls_info_t; + + #if OPENSSL_VERSION_NUMBER < 0x10001000L +@@ -139,6 +138,9 @@ typedef struct _tls_session_t { + bool invalid_hb_used; //!< Whether heartbleed attack was detected. + bool connected; //!< whether the outgoing socket is connected + bool is_init_finished; //!< whether or not init is finished ++ bool client_cert_ok; //!< whether or not we validated the client certificate ++ bool authentication_success; //!< whether or not the user was authenticated (cert or PW) ++ bool quick_session_tickets; //!< for EAP-TLS. + + /* + * Framed-MTU attribute in RADIUS, if present, can also be used to set this +@@ -160,8 +162,11 @@ typedef struct _tls_session_t { + void *opaque; + void (*free_opaque)(void *opaque); + +- char const *prf_label; ++ char const *label; + bool allow_session_resumption; //!< Whether session resumption is allowed. ++ bool session_not_resumed; //!< Whether our session was not resumed. ++ ++ fr_tls_server_conf_t const *conf; //! for better complaints + } tls_session_t; + + /* +@@ -309,16 +314,17 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char co + CC_HINT(format (printf, 4, 5)); + + void tls_global_cleanup(void); +-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert); ++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, bool allow_tls13); + tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs); + fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs); + fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs); + fr_tls_server_conf_t *tls_server_conf_alloc(TALLOC_CTX *ctx); +-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client); ++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file); + int tls_handshake_recv(REQUEST *, tls_session_t *ssn); + int tls_handshake_send(REQUEST *, tls_session_t *ssn); + void tls_session_information(tls_session_t *ssn); + void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize); ++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf); + + /* + * Low-level TLS stuff +@@ -335,6 +341,7 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request); + #define FR_TLS_EX_INDEX_STORE (14) + #define FR_TLS_EX_INDEX_SSN (15) + #define FR_TLS_EX_INDEX_TALLOC (16) ++#define FR_TLS_EX_INDEX_FIX_CERT_ORDER (17) + + extern int fr_tls_ex_index_certs; + extern int fr_tls_ex_index_vps; +@@ -360,6 +367,10 @@ struct fr_tls_server_conf_t { + bool disable_tlsv1; + bool disable_tlsv1_1; + bool disable_tlsv1_2; ++ bool disallow_untrusted; //!< allow untrusted CAs to issue client certificates ++ ++ int min_version; ++ int max_version; + + char const *tls_min_version; + char const *tls_max_version; +@@ -371,16 +382,21 @@ struct fr_tls_server_conf_t { + bool check_crl; + bool check_all_crl; + bool allow_expired_crl; ++ uint32_t ca_path_reload_interval; ++ uint32_t ca_path_last_reload; ++ X509_STORE *old_x509_store; + char const *check_cert_cn; + char const *cipher_list; + bool cipher_server_preference; + char const *check_cert_issuer; ++ char const *sigalgs_list; + + bool session_cache_enable; +- uint32_t session_timeout; ++ uint32_t session_lifetime; + uint32_t session_cache_size; + char const *session_id_name; + char const *session_cache_path; ++ char const *session_cache_server; + fr_hash_table_t *cache_ht; + char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH]; + +@@ -389,6 +405,10 @@ struct fr_tls_server_conf_t { + char const *verify_client_cert_cmd; + bool require_client_cert; + ++ bool fix_cert_order; ++ ++ pthread_mutex_t mutex; ++ + #ifdef HAVE_OPENSSL_OCSP_H + /* + * OCSP Configuration +@@ -414,6 +434,15 @@ struct fr_tls_server_conf_t { + char const *psk_query; + #endif + ++ char const *realm_dir; ++ fr_hash_table_t *realms; ++ ++ char const *client_hostname; ++ ++#ifdef WITH_RADIUSV11 ++ char const *radiusv11_name; ++ fr_radiusv11_t radiusv11; ++#endif + }; + + #ifdef __cplusplus +diff --git a/src/include/token.h b/src/include/token.h +index 6cbd05217a..c8bb748702 100644 +--- a/src/include/token.h ++++ b/src/include/token.h +@@ -56,16 +56,17 @@ typedef enum fr_token_t { + T_OP_CMP_TRUE, /* =* 20 */ + T_OP_CMP_FALSE, /* !* */ + T_OP_CMP_EQ, /* == */ ++ T_OP_PREPEND, /* ^= */ + T_HASH, /* # */ +- T_BARE_WORD, /* bare word */ +- T_DOUBLE_QUOTED_STRING, /* "foo" 25 */ ++ T_BARE_WORD, /* bare word 25 */ ++ T_DOUBLE_QUOTED_STRING, /* "foo" */ + T_SINGLE_QUOTED_STRING, /* 'foo' */ + T_BACK_QUOTED_STRING, /* `foo` */ + T_TOKEN_LAST + } FR_TOKEN; + + #define T_EQSTART T_OP_ADD +-#define T_EQEND (T_OP_CMP_EQ + 1) ++#define T_EQEND (T_OP_PREPEND + 1) + + typedef struct FR_NAME_NUMBER { + char const *name; +diff --git a/src/lib/dict.c b/src/lib/dict.c +index 96e06b5287..479bf1104e 100644 +--- a/src/lib/dict.c ++++ b/src/lib/dict.c +@@ -725,7 +725,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, + { + size_t namelen; + DICT_ATTR const *parent; +- DICT_ATTR *n; ++ DICT_ATTR *n; ++ DICT_ATTR const *old; + static int max_attr = 0; + + namelen = strlen(name); +@@ -882,6 +883,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, + return -1; + } + ++ if (flags.encrypt) flags.secret = 1; ++ + if (flags.length && (type != PW_TYPE_OCTETS)) { + fr_strerror_printf("The \"length\" flag can only be set for attributes of type \"octets\""); + return -1; +@@ -1080,6 +1083,18 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, + n->type = type; + n->flags = flags; + ++ /* ++ * Allow old-style names, but they always end up as ++ * new-style names. ++ */ ++ old = dict_attrbyvalue(n->attr, n->vendor); ++ if (old && (old->type == n->type)) { ++ DICT_ATTR *mutable; ++ ++ memcpy(&mutable, &old, sizeof(old)); /* const issues */ ++ mutable->flags.is_dup = true; ++ } ++ + /* + * Insert the attribute, only if it's not a duplicate. + */ +@@ -1258,6 +1273,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value) + fr_int2str(dict_attr_types, da->type, "?Unknown?")); + return -1; + } ++ /* in v4 this is done with the UNCONST #define */ ++ ((DICT_ATTR *)((uintptr_t)(da)))->flags.has_value = 1; + } else { + value_fixup_t *fixup; + +@@ -1742,6 +1759,10 @@ static int process_attribute(char const* fn, int const line, + "\"encrypt=3\" flag set", fn, line); + return -1; + } ++ flags.secret = 1; ++ ++ } else if (strncmp(key, "secret", 6) == 0) { ++ flags.secret = 1; + + } else if (strncmp(key, "array", 6) == 0) { + flags.array = 1; +@@ -2022,7 +2043,7 @@ static int process_value_alias(char const* fn, int const line, char **argv, + } + + +-static int parse_format(char const *fn, int line, char const *format, int *pvalue, int *ptype, int *plength, bool *pcontinuation) ++static int parse_format(char const *fn, int line, char const *format, int *ptype, int *plength, bool *pcontinuation) + { + char const *p; + int type, length; +@@ -2075,9 +2096,8 @@ static int parse_format(char const *fn, int line, char const *format, int *pvalu + } + continuation = true; + +- if ((*pvalue != VENDORPEC_WIMAX) || +- (type != 1) || (length != 1)) { +- fr_strerror_printf("dict_init: %s[%d]: Only WiMAX VSAs can have continuations", ++ if ((type != 1) || (length != 1)) { ++ fr_strerror_printf("dict_init: %s[%d]: Only 'format=1,1' VSAs can have continuations", + fn, line); + return -1; + } +@@ -2132,7 +2152,7 @@ static int process_vendor(char const* fn, int const line, char **argv, + * Look for a format statement. Allow it to over-ride the hard-coded formats below. + */ + if (argc == 3) { +- if (parse_format(fn, line, argv[2], &value, &type, &length, &continuation) < 0) { ++ if (parse_format(fn, line, argv[2], &type, &length, &continuation) < 0) { + return -1; + } + +@@ -2409,7 +2429,8 @@ static int my_dict_init(char const *parent, char const *filename, + /* + * Optionally include a dictionary + */ +- if (strcasecmp(argv[0], "$INCLUDE-") == 0) { ++ if ((strcasecmp(argv[0], "$INCLUDE-") == 0) || ++ (strcasecmp(argv[0], "$-INCLUDE") == 0)) { + int rcode = my_dict_init(dir, argv[1], fn, line); + + if (rcode == -2) continue; +@@ -2783,45 +2804,72 @@ int dict_init(char const *dir, char const *fn) + return 0; + } + +-static size_t print_attr_oid(char *buffer, size_t size, unsigned int attr, +- int dv_type) ++static size_t print_attr_oid(char *buffer, size_t bufsize, unsigned int attr, unsigned int vendor) + { +- int nest; +- size_t outlen; ++ int nest, dv_type = 1; + size_t len; ++ char *p = buffer; ++ ++ if (vendor > FR_MAX_VENDOR) { ++ len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); ++ p += len; ++ bufsize -= len; ++ vendor &= (FR_MAX_VENDOR) - 1; ++ } ++ ++ if (vendor) { ++ DICT_VENDOR *dv; ++ ++ /* ++ * dv_type is the length of the vendor's type field ++ * RFC 2865 never defined a mandatory length, so ++ * different vendors have different length type fields. ++ */ ++ dv = dict_vendorbyvalue(vendor); ++ if (dv) dv_type = dv->type; ++ ++ len = snprintf(p, bufsize, "26.%u.", vendor); ++ ++ p += len; ++ bufsize -= len; ++ } ++ + + switch (dv_type) { + default: + case 1: +- len = snprintf(buffer, size, "%u", attr & 0xff); ++ len = snprintf(p, bufsize, "%u", attr & 0xff); ++ p += len; ++ bufsize -= len; ++ if ((attr >> 8) == 0) return p - buffer; + break; + +- case 4: +- return snprintf(buffer, size, "%u", attr); +- + case 2: +- return snprintf(buffer, size, "%u", attr & 0xffff); +- +- } ++ len = snprintf(p, bufsize, "%u", attr & 0xffff); ++ p += len; ++ return p - buffer; + +- if ((attr >> 8) == 0) return len; ++ case 4: ++ len = snprintf(p, bufsize, "%u", attr); ++ p += len; ++ return p - buffer; + +- outlen = len; +- buffer += len; +- size -= len; ++ } + ++ /* ++ * "attr" is a sequence of packed numbers. Unpack them. ++ */ + for (nest = 1; nest <= fr_attr_max_tlv; nest++) { + if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break; + +- len = snprintf(buffer, size, ".%u", ++ len = snprintf(p, bufsize, ".%u", + (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]); + +- outlen = len; +- buffer += len; +- size -= len; ++ p += len; ++ bufsize -= len; + } + +- return outlen; ++ return p - buffer; + } + + /** Free dynamically allocated (unknown attributes) +@@ -2862,7 +2910,6 @@ void dict_attr_free(DICT_ATTR const **da) + int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor) + { + char *p; +- int dv_type = 1; + size_t len = 0; + size_t bufsize = DICT_ATTR_MAX_NAME_LEN; + +@@ -2888,32 +2935,7 @@ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vend + p += len; + bufsize -= len; + +- if (vendor > FR_MAX_VENDOR) { +- len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); +- p += len; +- bufsize -= len; +- vendor &= (FR_MAX_VENDOR) - 1; +- } +- +- if (vendor) { +- DICT_VENDOR *dv; +- +- /* +- * dv_type is the length of the vendor's type field +- * RFC 2865 never defined a mandatory length, so +- * different vendors have different length type fields. +- */ +- dv = dict_vendorbyvalue(vendor); +- if (dv) { +- dv_type = dv->type; +- } +- len = snprintf(p, bufsize, "26.%u.", vendor); +- +- p += len; +- bufsize -= len; +- } +- +- print_attr_oid(p, bufsize , attr, dv_type); ++ print_attr_oid(p, bufsize , attr, vendor); + + return 0; + } +@@ -3270,7 +3292,15 @@ DICT_ATTR const *dict_attrbyname(char const *name) + da = (DICT_ATTR *) buffer; + strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1); + +- return fr_hash_table_finddata(attributes_byname, da); ++ da = fr_hash_table_finddata(attributes_byname, da); ++ if (!da) return NULL; ++ ++ if (!da->flags.is_dup) return da; ++ ++ /* ++ * This MUST exist if the dup flag is set. ++ */ ++ return dict_attrbyvalue(da->attr, da->vendor); + } + + /** Look up a dictionary attribute by name embedded in another string +@@ -3464,3 +3494,13 @@ DICT_ATTR const *dict_unknown_add(DICT_ATTR const *old) + da = dict_attrbyvalue(old->attr, old->vendor); + return da; + } ++ ++size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da) ++{ ++ return print_attr_oid(buffer, buflen, da->attr, da->vendor); ++} ++ ++int dict_walk(fr_hash_table_walk_t callback, void *context) ++{ ++ return fr_hash_table_walk(attributes_byname, callback, context); ++} +diff --git a/src/lib/event.c b/src/lib/event.c +index 0c2976b3c9..d912955fe8 100644 +--- a/src/lib/event.c ++++ b/src/lib/event.c +@@ -28,6 +28,8 @@ RCSID("$Id$") + #include <freeradius-devel/heap.h> + #include <freeradius-devel/event.h> + ++#include <pthread.h> ++ + #ifdef HAVE_KQUEUE + #ifndef HAVE_SYS_EVENT_H + #error kqueue requires <sys/event.h> +@@ -39,7 +41,9 @@ RCSID("$Id$") + + typedef struct fr_event_fd_t { + int fd; ++ + fr_event_fd_handler_t handler; ++ fr_event_fd_handler_t write_handler; + void *ctx; + } fr_event_fd_t; + +@@ -61,13 +65,15 @@ struct fr_event_list_t { + int num_readers; + #ifndef HAVE_KQUEUE + int max_readers; ++ int max_fd; + +- bool changed; +- ++ fd_set read_fds; ++ fd_set write_fds; + #else + int kq; + struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ + #endif ++ + fr_event_fd_t readers[FR_EV_MAX_FDS]; + }; + +@@ -139,8 +145,9 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) + } + + #ifndef HAVE_KQUEUE +- el->changed = true; /* force re-set of fds's */ +- ++ el->max_fd = 0; ++ FD_ZERO(&el->read_fds); ++ FD_ZERO(&el->write_fds); + #else + el->kq = kqueue(); + if (el->kq < 0) { +@@ -161,6 +168,11 @@ int fr_event_list_num_fds(fr_event_list_t *el) + return el->num_readers; + } + ++int fr_event_list_full(fr_event_list_t *el) ++{ ++ return (el->num_readers >= FR_EV_MAX_FDS); ++} ++ + int fr_event_list_num_elements(fr_event_list_t *el) + { + if (!el) return 0; +@@ -432,6 +444,9 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + el->num_readers++; + + if (i == el->max_readers) el->max_readers = i + 1; ++ ++ FD_SET(fd, &el->read_fds); ++ if (el->max_fd <= fd) el->max_fd = fd; + break; + } + } +@@ -446,13 +461,69 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + ef->handler = handler; + ef->ctx = ctx; + +-#ifndef HAVE_KQUEUE +- el->changed = true; +-#endif +- + return 1; + } + ++int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, ++ fr_event_fd_handler_t write_handler, void *ctx) ++{ ++ int i; ++ ++ if (!el || (fd < 0)) return 0; ++ ++ if (type != 0) return 0; ++ ++#ifdef HAVE_KQUEUE ++ for (i = 0; i < FR_EV_MAX_FDS; i++) { ++ int j; ++ struct kevent evset; ++ ++ j = (i + fd) & (FR_EV_MAX_FDS - 1); ++ ++ if (el->readers[j].fd != fd) continue; ++ ++ fr_assert(ctx = el->readers[j].ctx); ++ ++ /* ++ * Tell us when the socket is ready for writing ++ */ ++ if (write_handler) { ++ fr_assert(!el->readers[j].write_handler); ++ ++ el->readers[j].write_handler = write_handler; ++ ++ EV_SET(&evset, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); ++ } else { ++ fr_assert(el->readers[j].write_handler); ++ ++ el->readers[j].write_handler = NULL; ++ ++ EV_SET(&evset, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); ++ } ++ if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { ++ fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); ++ return 0; ++ } ++ ++ return 1; ++ } ++ ++#else ++ ++ for (i = 0; i < el->max_readers; i++) { ++ if (el->readers[i].fd != fd) continue; ++ ++ fr_assert(ctx = el->readers[i].ctx); ++ el->readers[i].write_handler = write_handler; ++ ++ FD_SET(fd, &el->write_fds); /* fd MUST already be in the set of readers! */ ++ return 1; ++ } ++#endif /* HAVE_KQUEUE */ ++ ++ return 0; ++} ++ + int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + { + int i; +@@ -480,6 +551,14 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + ++ /* ++ * Delete the write handler if it exits. ++ */ ++ if (el->readers[j].write_handler) { ++ EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); ++ (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); ++ } ++ + el->readers[j].fd = -1; + el->num_readers--; + +@@ -487,14 +566,18 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) + } + + #else +- + for (i = 0; i < el->max_readers; i++) { + if (el->readers[i].fd == fd) { + el->readers[i].fd = -1; + el->num_readers--; + + if ((i + 1) == el->max_readers) el->max_readers = i; +- el->changed = true; ++ FD_CLR(fd, &el->read_fds); ++ FD_CLR(fd, &el->write_fds); ++ ++ /* ++ * @todo - update el->max_fd, too. ++ */ + return 1; + } + } +@@ -523,39 +606,13 @@ int fr_event_loop(fr_event_list_t *el) + #ifdef HAVE_KQUEUE + struct timespec ts_when, *ts_wake; + #else +- int maxfd = 0; +- fd_set read_fds, master_fds; +- +- el->changed = true; ++ fd_set read_fds, write_fds; + #endif + + el->exit = 0; + el->dispatch = true; + + while (!el->exit) { +-#ifndef HAVE_KQUEUE +- /* +- * Cache the list of FD's to watch. +- */ +- if (el->changed) { +-#ifdef __clang_analyzer__ +- memset(&master_fds, 0, sizeof(master_fds)); +-#else +- FD_ZERO(&master_fds); +-#endif +- for (i = 0; i < el->max_readers; i++) { +- if (el->readers[i].fd < 0) continue; +- +- if (el->readers[i].fd > maxfd) { +- maxfd = el->readers[i].fd; +- } +- FD_SET(el->readers[i].fd, &master_fds); +- } +- +- el->changed = false; +- } +-#endif /* HAVE_KQUEUE */ +- + /* + * Find the first event. If there's none, we wait + * on the socket forever. +@@ -604,8 +661,9 @@ int fr_event_loop(fr_event_list_t *el) + if (el->status) el->status(wake); + + #ifndef HAVE_KQUEUE +- read_fds = master_fds; +- rcode = select(maxfd + 1, &read_fds, NULL, NULL, wake); ++ read_fds = el->read_fds; ++ write_fds = el->write_fds; ++ rcode = select(el->max_fd + 1, &read_fds, &write_fds, NULL, wake); + if ((rcode < 0) && (errno != EINTR)) { + fr_strerror_printf("Failed in select: %s", fr_syserror(errno)); + el->dispatch = false; +@@ -618,6 +676,7 @@ int fr_event_loop(fr_event_list_t *el) + ts_wake = &ts_when; + ts_when.tv_sec = when.tv_sec; + ts_when.tv_nsec = when.tv_usec * 1000; ++ + } else { + ts_wake = NULL; + } +@@ -644,11 +703,16 @@ int fr_event_loop(fr_event_list_t *el) + + if (ef->fd < 0) continue; + ++ /* ++ * Check if the socket is available for writing. ++ */ ++ if (ef->write_handler && FD_ISSET(ef->fd, &write_fds)) { ++ ef->write_handler(el, ef->fd, ef->ctx); ++ } ++ + if (!FD_ISSET(ef->fd, &read_fds)) continue; + + ef->handler(el, ef->fd, ef->ctx); +- +- if (el->changed) break; + } + + #else /* HAVE_KQUEUE */ +@@ -673,6 +737,11 @@ int fr_event_loop(fr_event_list_t *el) + continue; + } + ++ if (el->events[i].filter == EVFILT_WRITE) { ++ ef->write_handler(el, ef->fd, ef->ctx); ++ continue; ++ } ++ + /* + * Else it's our event. We only set + * EVFILT_READ, so it must be a read +@@ -719,7 +788,7 @@ static uint32_t event_rand(void) + { + uint32_t num; + +- num = rand_pool.randrsl[rand_pool.randcnt++]; ++ num = rand_pool.randrsl[rand_pool.randcnt++ & 0xff]; + if (rand_pool.randcnt == 256) { + fr_isaac(&rand_pool); + rand_pool.randcnt = 0; +diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c +index 1cca00fa2a..2aad490e30 100644 +--- a/src/lib/hmacmd5.c ++++ b/src/lib/hmacmd5.c +@@ -34,8 +34,9 @@ RCSID("$Id$") + + #include <freeradius-devel/libradius.h> + #include <freeradius-devel/md5.h> ++#include <freeradius-devel/openssl3.h> + +-#ifdef HAVE_OPENSSL_EVP_H ++#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS) + /** Calculate HMAC using OpenSSL's MD5 implementation + * + * @param digest Caller digest to be filled in. +@@ -49,6 +50,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t + uint8_t const *key, size_t key_len) + { + HMAC_CTX *ctx = HMAC_CTX_new(); ++ unsigned int len = MD5_DIGEST_LENGTH; + + #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW + /* Since MD5 is not allowed by FIPS, explicitly allow it. */ +@@ -57,7 +59,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t + + HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL); + HMAC_Update(ctx, text, text_len); +- HMAC_Final(ctx, digest, NULL); ++ HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); + } + #else +diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c +index 211470ea35..8711f983b7 100644 +--- a/src/lib/hmacsha1.c ++++ b/src/lib/hmacsha1.c +@@ -13,6 +13,7 @@ RCSID("$Id$") + #ifdef HAVE_OPENSSL_EVP_H + #include <openssl/hmac.h> + #include <openssl/evp.h> ++#include <freeradius-devel/openssl3.h> + #endif + + #include <freeradius-devel/libradius.h> +@@ -35,9 +36,11 @@ void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_ + uint8_t const *key, size_t key_len) + { + HMAC_CTX *ctx = HMAC_CTX_new(); ++ unsigned int len = SHA1_DIGEST_LENGTH; ++ + HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL); + HMAC_Update(ctx, text, text_len); +- HMAC_Final(ctx, digest, NULL); ++ HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); + } + +diff --git a/src/lib/md4.c b/src/lib/md4.c +index 0515fca1ae..7169000381 100644 +--- a/src/lib/md4.c ++++ b/src/lib/md4.c +@@ -28,6 +28,7 @@ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen + fr_md4_init(&ctx); + fr_md4_update(&ctx, in, inlen); + fr_md4_final(out, &ctx); ++ fr_md4_destroy(&ctx); + } + + #ifndef HAVE_OPENSSL_MD4_H +diff --git a/src/lib/md5.c b/src/lib/md5.c +index 9858175bd4..b5c1729148 100644 +--- a/src/lib/md5.c ++++ b/src/lib/md5.c +@@ -30,6 +30,7 @@ void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen) + fr_md5_init(&ctx); + fr_md5_update(&ctx, in, inlen); + fr_md5_final(out, &ctx); ++ fr_md5_destroy(&ctx); + } + + #ifndef HAVE_OPENSSL_MD5_H +diff --git a/src/lib/pair.c b/src/lib/pair.c +index d711f90c5d..146c82f95b 100644 +--- a/src/lib/pair.c ++++ b/src/lib/pair.c +@@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) { + return 0; + } + +-static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) ++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) + { + VALUE_PAIR *vp; + +@@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v + DICT_ATTR const *da; + + da = dict_attrbyvalue(attr, vendor); +- if (!da) { +- VALUE_PAIR *vp; +- +- vp = fr_pair_alloc(ctx); +- if (!vp) return NULL; +- +- /* +- * Ensure that the DA is parented by the VP. +- */ +- da = dict_unknown_afrom_fields(vp, attr, vendor); +- if (!da) { +- talloc_free(vp); +- return NULL; +- } +- +- vp->da = da; +- return vp; +- } ++ if (!da) return NULL; + + return fr_pair_afrom_da(ctx, da); + } +@@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add) + i->next = add; + } + ++/** Add a VP to the start of the list. ++ * ++ * Links the new VP to 'first', then points 'first' at the new VP. ++ * ++ * @param[in] first VP in linked list. Will add new VP to the start of this list. ++ * @param[in] add VP to add to list. ++ */ ++void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add) ++{ ++ VALUE_PAIR *i; ++ ++ if (!add) return; ++ ++ VERIFY_VP(add); ++ ++ if (*first == NULL) { ++ *first = add; ++ return; ++ } ++ ++ /* ++ * Find the end of the list to be prepended ++ */ ++ for (i = add; i->next; i = i->next) { ++#ifdef WITH_VERIFY_PTR ++ VERIFY_VP(i); ++ /* ++ * The same VP should never by added multiple times ++ * to the same list. ++ */ ++ fr_assert(*first != i); ++#endif ++ } ++ i->next = *first; ++ *first = add; ++} ++ + /** Replace all matching VPs + * + * Walks over 'first', and replaces the first VP that matches 'replace'. +@@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp) + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. ++ * @param[in] op operator for move. + * + * @see radius_pairmove + */ +-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) ++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op) + { + VALUE_PAIR *i, *found; + VALUE_PAIR *head_new, **tail_new; ++ VALUE_PAIR *head_prepend; + VALUE_PAIR **tail_from; + + if (!to || !from || !*from) return; +@@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) + head_new = NULL; + tail_new = &head_new; + ++ /* ++ * Any attributes that are requested to be prepended ++ * are added to a temporary list here ++ */ ++ head_prepend = NULL; ++ + /* + * We're looping over the "from" list, moving some + * attributes out, but leaving others in place. +@@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) + fr_pair_steal(ctx, i); + tail_new = &(i->next); + continue; ++ case T_OP_PREPEND: ++ i->next = head_prepend; ++ head_prepend = i; ++ fr_pair_steal(ctx, i); ++ continue; + } + } /* loop over the "from" list. */ + + /* +- * Take the "new" list, and append it to the "to" list. ++ * If the op parameter was prepend, add the "new" list ++ * attributes first as those whose individual operator ++ * is prepend should be prepended to the resulting list + */ +- fr_pair_add(to, head_new); ++ if (op == T_OP_PREPEND) { ++ fr_pair_prepend(to, head_new); ++ } ++ ++ /* ++ * If there are any items in the prepend list prepend ++ * it to the "to" list ++ */ ++ fr_pair_prepend(to, head_prepend); ++ ++ /* ++ * If the op parameter was not prepend, take the "new" ++ * list, and append it to the "to" list. ++ */ ++ if (op != T_OP_PREPEND) { ++ fr_pair_add(to, head_new); ++ } + } + + /** Move matching pairs between VALUE_PAIR lists +@@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw) + break; + + default: +- fr_strerror_printf("Failed to find expected value on right hand side"); ++ fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand); + return T_INVALID; + } + +diff --git a/src/lib/print.c b/src/lib/print.c +index 9ac927358b..57455b6f30 100644 +--- a/src/lib/print.c ++++ b/src/lib/print.c +@@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. ++ * @param raw_value if true, the raw value is printed and not the enumerated attribute value + * @return the length of data written to out, or a value >= outlen on truncation. + */ +-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp) ++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) + { + char const *q; + size_t len, freespace = outlen; ++ /* attempt to print raw_value when has_value is false, or raw_value is false, but only ++ if has_tag is also false */ ++ bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; + +- if (!vp->da->flags.has_tag) { ++ if (raw) { + switch (vp->da->type) { + case PW_TYPE_INTEGER: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", vp->vp_integer); + + case PW_TYPE_SHORT: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); + + case PW_TYPE_BYTE: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); + +- case PW_TYPE_SIGNED: +- return snprintf(out, freespace, "%d", vp->vp_signed); +- + default: + break; + } +@@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) + + value = vp_aprints_value(ctx, vp, quote); + +- if (vp->da->flags.has_tag) { ++ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); + } else { +diff --git a/src/lib/radius.c b/src/lib/radius.c +index 3881111f7d..6dcee141e2 100644 +--- a/src/lib/radius.c ++++ b/src/lib/radius.c +@@ -28,6 +28,7 @@ RCSID("$Id$") + #include <freeradius-devel/libradius.h> + + #include <freeradius-devel/md5.h> ++#include <freeradius-devel/rfc4849.h> + + #include <fcntl.h> + #include <ctype.h> +@@ -142,8 +143,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = { + "47", + "48", + "49", +- "IP-Address-Allocate", +- "IP-Address-Release", //!< 50 ++ "IP-Address-Allocate", //!< 50 ++ "IP-Address-Release", ++ "Protocol-Error", + }; + + +@@ -528,6 +530,8 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, + for ( i = 0; i < length; i++ ) { + digest[i] ^= value[i]; + } ++ ++ fr_md5_destroy(&context); + } + + #define MAX_PASS_LEN (128) +@@ -562,8 +566,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + *outlen = len; + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); +- old = context; ++ fr_md5_copy(old, context); + + /* + * Do first pass. +@@ -572,7 +577,7 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + + for (n = 0; n < len; n += AUTH_PASS_LEN) { + if (n > 0) { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -585,6 +590,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + } + + memcpy(output, passwd, len); ++ ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + } + + +@@ -653,8 +661,9 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + output[2] = inlen; /* length of the password string */ + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); +- old = context; ++ fr_md5_copy(old, context); + + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_update(&context, &output[0], 2); +@@ -663,7 +672,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + size_t block_len; + + if (n > 0) { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + output + 2 + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -681,6 +690,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + output[i + 2 + n] ^= digest[i]; + } + } ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + } + + static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest) +@@ -1552,6 +1563,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, + + VERIFY_VP(vp); + ++ if (room < 2) return -1; ++ + if (vp->da->vendor != 0) { + fr_strerror_printf("rad_vp2rfc called with VSA"); + return -1; +@@ -1594,6 +1607,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, + return 18; + } + ++ /* ++ * Hacks for NAS-Filter-Rule. They all get concatenated ++ * with 0x00 bytes in between the values. We rely on the ++ * decoder to do the opposite transformation on incoming ++ * packets. ++ */ ++ if (vp->da->attr == PW_NAS_FILTER_RULE) { ++ uint8_t const *end = ptr + room; ++ uint8_t *p, *attr = ptr; ++ bool zero = false; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = ptr + 2; ++ ++ while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) { ++ if ((p + zero + vp->vp_length) > end) { ++ break; ++ } ++ ++ if (zero) { ++ if (attr[1] == 255) { ++ attr = p; ++ if ((attr + 3) >= end) break; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = attr + 2; ++ } ++ ++ *(p++) = 0; ++ attr[1]++; ++ } ++ ++ /* ++ * Check for overflow ++ */ ++ if ((attr[1] + vp->vp_length) < 255) { ++ memcpy(p, vp->vp_strvalue, vp->vp_length); ++ attr[1] += vp->vp_length; ++ p += vp->vp_length; ++ ++ } else if (attr + (attr[1] + 2 + vp->vp_length) > end) { ++ break; ++ ++ } else if (vp->vp_length > 253) { ++ /* ++ * Drop VPs which are too long. ++ * We don't (yet) split one VP ++ * across multiple attributes. ++ */ ++ vp = vp->next; ++ continue; ++ ++ } else { ++ size_t first, second; ++ ++ first = 255 - attr[1]; ++ second = vp->vp_length - first; ++ ++ memcpy(p, vp->vp_strvalue, first); ++ p += first; ++ attr[1] = 255; ++ attr = p; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = attr + 2; ++ ++ memcpy(p, vp->vp_strvalue + first, second); ++ attr[1] += second; ++ p += second; ++ } ++ ++ vp = vp->next; ++ zero = true; ++ } ++ ++ *pvp = vp; ++ return p - ptr; ++ } ++ + /* + * EAP-Message is special. + */ +@@ -1700,6 +1795,14 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, + return rad_vp2vsa(packet, original, secret, pvp, start, room); + } + ++static const bool code2ma[FR_MAX_PACKET_CODE] = { ++ [ PW_CODE_ACCESS_REQUEST ] = true, ++ [ PW_CODE_ACCESS_ACCEPT ] = true, ++ [ PW_CODE_ACCESS_REJECT ] = true, ++ [ PW_CODE_ACCESS_CHALLENGE ] = true, ++ [ PW_CODE_STATUS_SERVER ] = true, ++ [ PW_CODE_PROTOCOL_ERROR ] = true, ++}; + + /** Encode a packet + * +@@ -1712,6 +1815,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + uint16_t total_length; + int len; + VALUE_PAIR const *reply; ++ bool seen_ma = false; + + /* + * A 4K packet, aligned on 64-bits. +@@ -1775,6 +1879,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + * memcpy. + */ + ++ /* ++ * Always add Message-Authenticator for replies to ++ * Access-Request packets, and for all Access-Accept, ++ * Access-Reject, Access-Challenge. ++ * ++ * It must be the FIRST attribute in the packet. ++ */ ++ if (!packet->tls && ++ ((code2ma[packet->code]) || (original && code2ma[original->code]))) { ++ seen_ma = true; ++ ++ packet->offset = RADIUS_HDR_LEN; ++ ++ ptr[0] = PW_MESSAGE_AUTHENTICATOR; ++ ptr[1] = 18; ++ memset(ptr + 2, 0, 16); ++ ++ ptr += 18; ++ total_length += 18; ++ } ++ + /* + * Loop over the reply attributes for the packet. + */ +@@ -1832,6 +1957,14 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + * length and initial value. + */ + if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { ++ /* ++ * We have already encoded the Message-Authenticator, don't do it again. ++ */ ++ if (seen_ma) { ++ reply = reply->next; ++ continue; ++ } ++ + if (room < 18) break; + + /* +@@ -1973,11 +2106,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: +- case PW_CODE_DISCONNECT_ACK: +- case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_REQUEST: +- case PW_CODE_COA_ACK: +- case PW_CODE_COA_NAK: + memset(hdr->vector, 0, AUTH_VECTOR_LEN); + break; + +@@ -1985,6 +2114,10 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: ++ case PW_CODE_DISCONNECT_ACK: ++ case PW_CODE_DISCONNECT_NAK: ++ case PW_CODE_COA_ACK: ++ case PW_CODE_COA_NAK: + memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); + break; + +@@ -2036,6 +2169,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + fr_md5_update(&context, (uint8_t const *) secret, + strlen(secret)); + fr_md5_final(digest, &context); ++ fr_md5_destroy(&context); + + memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); + memcpy(packet->vector, digest, AUTH_VECTOR_LEN); +@@ -2159,6 +2293,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(digest, &context); ++ fr_md5_destroy(&context); + + /* + * Return 0 if OK, 2 if not OK. +@@ -2198,6 +2333,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(calc_digest, &context); ++ fr_md5_destroy(&context); + + /* + * Copy the packet's vector back to the packet. +@@ -2323,6 +2459,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + radius_packet_t *hdr; + char host_ipaddr[128]; + bool require_ma = false; ++ bool limit_proxy_state = false; ++ bool seen_proxy_state = false; + bool seen_ma = false; + uint32_t num_attributes; + decode_fail_t failure = DECODE_FAIL_NONE; +@@ -2371,15 +2509,23 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + } + + /* +- * Message-Authenticator is required in Status-Server +- * packets, otherwise they can be trivially forged. ++ * If the caller requires Message-Authenticator, then set ++ * the flag. ++ * ++ * We also require Message-Authenticator if the packet ++ * code is Status-Server. ++ * ++ * If we're receiving packets from a proxy socket, then ++ * require Message-Authenticator for Access-* replies, ++ * and for Protocol-Error. + */ +- if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; ++ require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]); + + /* +- * It's also required if the caller asks for it. ++ * We only limit Proxy-State if we're not requiring ++ * Message-Authenticator. + */ +- if (flags) require_ma = true; ++ limit_proxy_state = ((flags & 0x04) != 0) && !require_ma; + + /* + * Repeat the length checks. This time, instead of +@@ -2534,6 +2680,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + case PW_EAP_MESSAGE: + require_ma = true; + eap = true; ++ packet->eap_message = true; + break; + + case PW_USER_PASSWORD: +@@ -2542,6 +2689,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + non_eap = true; + break; + ++ case PW_PROXY_STATE: ++ seen_proxy_state = true; ++ packet->proxy_state = true; ++ break; ++ + case PW_MESSAGE_AUTHENTICATOR: + if (attr[1] != 2 + AUTH_VECTOR_LEN) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d", +@@ -2553,6 +2705,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + goto finish; + } + seen_ma = true; ++ packet->message_authenticator = true; + break; + } + +@@ -2609,7 +2762,19 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) + * Message-Authenticator attributes. + */ + if (require_ma && !seen_ma) { +- FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute", ++ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.", ++ inet_ntop(packet->src_ipaddr.af, ++ &packet->src_ipaddr.ipaddr, ++ host_ipaddr, sizeof(host_ipaddr))); ++ failure = DECODE_FAIL_MA_MISSING; ++ goto finish; ++ } ++ ++ /* ++ * The client is a NAS which shouldn't send Proxy-State, but it did! ++ */ ++ if (limit_proxy_state && seen_proxy_state && !seen_ma) { ++ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); +@@ -2896,6 +3061,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre + } + + ++/** Convert one or more NAS-Filter-Rule attributes to one or more ++ * attributes. ++ * ++ */ ++static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx, ++ DICT_ATTR const *da, uint8_t const *start, ++ size_t const packetlen, VALUE_PAIR **pvp) ++{ ++ uint8_t const *p = start; ++ uint8_t const *attr = start; ++ uint8_t const *end = start + packetlen; ++ uint8_t const *attr_end; ++ uint8_t *q; ++ VALUE_PAIR *vp; ++ uint8_t buffer[253]; ++ ++ q = buffer; ++ ++ /* ++ * The packet has already been sanity checked, so we ++ * don't care about walking off of the end of it. ++ */ ++ while (attr < end) { ++ if ((attr + 2) > end) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok"); ++ return -1; ++ } ++ ++ if (attr[1] < 2) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok"); ++ return -1; ++ } ++ if (attr[0] != PW_NAS_FILTER_RULE) break; ++ ++ /* ++ * Now decode one, or part of one rule. ++ */ ++ p = attr + 2; ++ attr_end = attr + attr[1]; ++ ++ if (attr_end > end) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok"); ++ return -1; ++ } ++ ++ /* ++ * Coalesce data until the zero byte. ++ */ ++ while (p < attr_end) { ++ /* ++ * Once we hit the zero byte, create the ++ * VP, skip the zero byte, and reset the ++ * counters. ++ */ ++ if (*p == 0) { ++ /* ++ * Discard consecutive zeroes. ++ */ ++ if (q > buffer) { ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); ++ return -1; ++ } ++ ++ fr_pair_value_bstrncpy(vp, buffer, q - buffer); ++ ++ *pvp = vp; ++ pvp = &(vp->next); ++ q = buffer; ++ } ++ ++ p++; ++ continue; ++ } ++ *(q++) = *(p++); ++ ++ /* ++ * Not much reason to have rules which ++ * are too long. ++ */ ++ if ((size_t) (q - buffer) > sizeof(buffer)) { ++ fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long"); ++ return -1; ++ } ++ } ++ ++ /* ++ * Done this attribute. There MAY be things left ++ * in the buffer. ++ */ ++ attr = attr_end; ++ } ++ ++ if (q == buffer) return attr + attr[2] - start; ++ ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); ++ return -1; ++ } ++ ++ fr_pair_value_bstrncpy(vp, buffer, q - buffer); ++ ++ *pvp = vp; ++ ++ return p - start; ++} ++ + /** Convert a "concatenated" attribute to one long VP + * + */ +@@ -3014,6 +3288,7 @@ ssize_t rad_data2vp_tlvs(TALLOC_CTX *ctx, + tlv_len = data2vp(ctx, packet, original, secret, child, + data + 2, data[1] - 2, data[1] - 2, tail); + if (tlv_len < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ + fr_pair_list_free(&head); + return -1; + } +@@ -3101,7 +3376,10 @@ static ssize_t data2vp_vsa(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + attrlen - (dv->type + dv->length), + attrlen - (dv->type + dv->length), + pvp); +- if (my_len < 0) return my_len; ++ if (my_len < 0) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return my_len; ++ } + + return attrlen; + } +@@ -3154,7 +3432,10 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; ++ } + return attrlen; + } + +@@ -3165,7 +3446,6 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, attrlen - 2, attrlen - 2, + pvp); +- + if ((rcode < 0) || (((size_t) rcode + 2) != attrlen)) goto raw; /* didn't decode all of the data */ + return attrlen; + } +@@ -3307,7 +3587,10 @@ static ssize_t data2vp_wimax(TALLOC_CTX *ctx, + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; ++ } + return attrlen; + } + +@@ -3503,7 +3786,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + /* + * WiMAX craziness + */ +- if ((vendor == VENDORPEC_WIMAX) && dv->flags) { ++ if (dv->flags) { + rcode = data2vp_wimax(ctx, packet, original, secret, vendor, + data, attrlen, packetlen, pvp); + return rcode; +@@ -3610,19 +3893,12 @@ ssize_t data2vp(TALLOC_CTX *ctx, + return 0; + } + +-#if !defined(NDEBUG) || defined(__clang_analyzer__) + /* +- * Hacks for Coverity. Editing the dictionary +- * will break assumptions about CUI. We know +- * this, but Coverity doesn't. ++ * Create a zero-length attribute. + */ +- if (da->type != PW_TYPE_OCTETS) return -1; +-#endif +- +- data = buffer; +- *buffer = '\0'; +- datalen = 0; +- goto alloc_cui; /* skip everything */ ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) return -1; ++ goto done; + } + + /* +@@ -3874,8 +4150,10 @@ ssize_t data2vp(TALLOC_CTX *ctx, + /* + * This requires a whole lot more work. + */ +- return data2vp_extended(ctx, packet, original, secret, child, +- start, attrlen, packetlen, pvp); ++ rcode = data2vp_extended(ctx, packet, original, secret, child, ++ start, attrlen, packetlen, pvp); ++ if (rcode < 0) dict_attr_free(&child); /* only frees unknowns */ ++ return rcode; + + case PW_TYPE_EVS: + if (datalen < 6) goto raw; /* vid, vtype, value */ +@@ -3926,44 +4204,47 @@ ssize_t data2vp(TALLOC_CTX *ctx, + + default: + raw: ++ /* ++ * If it's already unknown, don't create a new ++ * unknown one. ++ */ ++ if (da->flags.is_unknown) break; ++ + /* + * Re-write the attribute to be "raw". It is + * therefore of type "octets", and will be + * handled below. ++ * ++ * We allocate the VP *first*, and then the da ++ * from it, so that there are no memory leaks. + */ +- da = dict_unknown_afrom_fields(ctx, da->attr, da->vendor); ++ vp = fr_pair_alloc(ctx); ++ if (!vp) return -1; ++ ++ da = dict_unknown_afrom_fields(vp, da->attr, da->vendor); + if (!da) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + tag = TAG_NONE; +-#ifndef NDEBUG +- /* +- * Fix for Coverity. +- */ +- if (da->type != PW_TYPE_OCTETS) { +- dict_attr_free(&da); +- return -1; +- } +-#endif +- break; ++ vp->da = da; ++ goto alloc_raw; + } + + /* + * And now that we've verified the basic type + * information, decode the actual data. + */ +- alloc_cui: + vp = fr_pair_afrom_da(ctx, da); +- if (!vp) return -1; ++ if (!vp) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return -1; ++ } + ++alloc_raw: + vp->vp_length = datalen; + vp->tag = tag; + +-#ifdef __clang_analyzer__ +- if (!datalen && da->type != PW_TYPE_OCTETS) return -1; +-#endif +- + switch (da->type) { + case PW_TYPE_STRING: + p = talloc_array(vp, char, vp->vp_length + 1); +@@ -4068,10 +4349,13 @@ ssize_t data2vp(TALLOC_CTX *ctx, + fail: + #endif + default: ++ dict_attr_free(&da); /* only frees unknowns */ + fr_pair_list_free(&vp); + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } ++ ++done: + vp->type = VT_DATA; + *pvp = vp; + +@@ -4112,6 +4396,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, + return data2vp_concat(ctx, da, data, length, pvp); + } + ++ if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) { ++ VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n"); ++ return data2vp_nas_filter_rule(ctx, da, data, length, pvp); ++ } ++ + /* + * Note that we pass the entire length, not just the + * length of this attribute. The Extended or WiMAX +@@ -4120,7 +4409,10 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, + */ + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, data[1] - 2, length - 2, pvp); +- if (rcode < 0) return rcode; ++ if (rcode < 0) { ++ dict_attr_free(&da); /* only frees unknowns */ ++ return rcode; ++ } + + return 2 + rcode; + } +@@ -4261,7 +4553,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, + uint32_t num_attributes; + uint8_t *ptr; + radius_packet_t *hdr; +- VALUE_PAIR *head, **tail, *vp; ++ VALUE_PAIR *head, **tail, *vp = NULL; + + /* + * Extract attribute-value pairs +@@ -4385,8 +4677,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Encrypt it in place. Don't bother checking +@@ -4397,7 +4690,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + fr_md5_update(&context, vector, AUTH_PASS_LEN); + fr_md5_final(digest, &context); + } else { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + (uint8_t *) passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -4409,6 +4702,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + } + } + ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + return 0; + } + +@@ -4441,8 +4737,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * The inverse of the code above. +@@ -4452,7 +4749,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + if (pwlen > AUTH_PASS_LEN) { + fr_md5_update(&context, (uint8_t *) passwd, + AUTH_PASS_LEN); +@@ -4460,7 +4757,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + } else { + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + if (pwlen > (n + AUTH_PASS_LEN)) { + fr_md5_update(&context, (uint8_t *) passwd + n, + AUTH_PASS_LEN); +@@ -4473,6 +4770,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + } + + done: ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + passwd[pwlen] = '\0'; + return strlen(passwd); + } +@@ -4609,8 +4909,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Set up the initial key: +@@ -4637,7 +4938,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + + /* + * A quick check: decrypt the first octet +@@ -4647,6 +4948,8 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + reallen = passwd[2] ^ digest[0]; + if (reallen > encrypted_len) { + fr_strerror_printf("tunnel password is too long for the attribute"); ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + return -1; + } + +@@ -4657,7 +4960,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, passwd + n + 2, block_len); + } + +@@ -4669,6 +4972,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + *pwlen = reallen; + passwd[reallen] = 0; + ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + return reallen; + } + +diff --git a/src/lib/token.c b/src/lib/token.c +index 8ae41b392f..a7978622e6 100644 +--- a/src/lib/token.c ++++ b/src/lib/token.c +@@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = { + { "=*", T_OP_CMP_TRUE, }, + { "!*", T_OP_CMP_FALSE, }, + { "==", T_OP_CMP_EQ, }, ++ { "^=", T_OP_PREPEND, }, + { "=", T_OP_EQ, }, + { "!=", T_OP_NE, }, + { ">=", T_OP_GE, }, +@@ -78,9 +79,10 @@ const bool fr_assignment_op[] = { + false, /* =* 20 */ + false, /* !* */ + false, /* == */ +- false, /* # */ +- false, /* bare word */ +- false, /* "foo" 25 */ ++ true, /* ^= */ ++ false, /* # */ ++ false, /* bare word 25 */ ++ false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +@@ -108,12 +110,13 @@ const bool fr_equality_op[] = { + true, /* < */ + true, /* =~ */ + true, /* !~ */ +- true, /* =* 20 */ ++ true, /* =* 20 */ + true, /* !* */ + true, /* == */ +- false, /* # */ +- false, /* bare word */ +- false, /* "foo" 25 */ ++ false, /* ^= */ ++ false, /* # */ ++ false, /* bare word 25 */ ++ false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +@@ -144,9 +147,10 @@ const bool fr_str_tok[] = { + false, /* =* 20 */ + false, /* !* */ + false, /* == */ +- false, /* # */ +- true, /* bare word */ +- true, /* "foo" 25 */ ++ false, /* ^= */ ++ false, /* # */ ++ true, /* bare word 25 */ ++ true, /* "foo" */ + true, /* 'foo' */ + true, /* `foo` */ + false +diff --git a/src/main/cb.c b/src/main/cb.c +index 4ae14e575b..f8b2edbecc 100644 +--- a/src/main/cb.c ++++ b/src/main/cb.c +@@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #ifdef WITH_TLS + void cbtls_info(SSL const *s, int where, int ret) + { +- char const *str, *state; ++ char const *role, *state; + REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); + + if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { +- str="TLS_connect"; ++ role = "Client "; + } else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) { +- str="TLS_accept"; ++ role = "Server "; + } else { +- str="(other)"; ++ role = ""; + } + + state = SSL_state_string_long(s); + state = state ? state : "<none>"; + + if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) { +- RDEBUG2("%s: %s", str, state); ++ if (RDEBUG_ENABLED3) { ++ char const *abbrv = SSL_state_string(s); ++ size_t len; ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ STACK_OF(SSL_CIPHER) *client_ciphers; ++ STACK_OF(SSL_CIPHER) *server_ciphers; ++#endif ++ ++ /* ++ * Trim crappy OpenSSL state strings... ++ */ ++ len = strlen(abbrv); ++ if ((len > 1) && (abbrv[len - 1] == ' ')) len--; ++ ++ RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", ++ (int)len, abbrv, role, state, SSL_get_state(s)); ++ ++ /* ++ * After a ClientHello, list all the proposed ciphers from the client ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) { ++ int i; ++ int num_ciphers; ++ const SSL_CIPHER *this_cipher; ++ ++ server_ciphers = SSL_get_ciphers(s); ++ if (server_ciphers) { ++ RDEBUG3("Server preferred ciphers (by priority)"); ++ num_ciphers = sk_SSL_CIPHER_num(server_ciphers); ++ for (i = 0; i < num_ciphers; i++) { ++ this_cipher = sk_SSL_CIPHER_value(server_ciphers, i); ++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); ++ } ++ } ++ ++ client_ciphers = SSL_get_client_ciphers(s); ++ if (client_ciphers) { ++ RDEBUG3("Client preferred ciphers (by priority)"); ++ num_ciphers = sk_SSL_CIPHER_num(client_ciphers); ++ for (i = 0; i < num_ciphers; i++) { ++ this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); ++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); ++ } ++ } ++ } ++#endif ++ } else { ++ RDEBUG2("(TLS) Handshake state - %s%s", role, state); ++ } + return; + } + + if (where & SSL_CB_ALERT) { + if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; + +- RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", ++ RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + return; + } + + if (where & SSL_CB_EXIT) { + if (ret == 0) { +- RERROR("%s: Failed in %s", str, state); ++ RERROR("(TLS) %s: Failed in %s", role, state); + return; + } + + if (ret < 0) { + if (SSL_want_read(s)) { +- RDEBUG2("%s: Need to read more data: %s", str, state); ++ RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); + return; + } +- ERROR("tls: %s: Error in %s", str, state); ++ RERROR("(TLS) %s: Error in %s", role, state); + } + } + } +@@ -87,14 +136,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + * content types. Which breaks our tracking of + * the SSL Session state. + */ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + if ((msg_version == 0) && (content_type > UINT8_MAX)) { +- DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i", ++#else ++ /* ++ * "...we do not see the need to resolve application breakage ++ * just because the documentation now is incorrect." ++ * ++ * https://github.com/openssl/openssl/issues/17262 ++ */ ++ if ((content_type > UINT8_MAX) && (content_type != SSL3_RT_INNER_CONTENT_TYPE)) { ++#endif ++ DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i", + content_type, msg_version); + return; + } + + if ((write_p != 0) && (write_p != 1)) { +- DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p); ++ DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p); + return; + } + +@@ -104,6 +163,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + */ + if (!state) return; + ++ if (rad_debug_lvl > 3) { ++ size_t i, j, data_len = len; ++ char buffer[3*16 + 1]; ++ uint8_t const *in = inbuf; ++ ++ DEBUG("(TLS) Received %zu bytes of TLS data", len); ++ if (data_len > 256) data_len = 256; ++ ++ for (i = 0; i < data_len; i += 16) { ++ for (j = 0; j < 16; j++) { ++ if ((i + j) >= data_len) break; ++ ++ sprintf(buffer + 3 * j, "%02x ", in[i + j]); ++ } ++ ++ DEBUG("(TLS) %s", buffer); ++ } ++ } ++ + /* + * 0 - received (from peer) + * 1 - sending (to peer) +@@ -111,7 +189,6 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + state->info.origin = write_p; + state->info.content_type = content_type; + state->info.record_len = len; +- state->info.version = msg_version; + state->info.initialized = true; + + if (content_type == SSL3_RT_ALERT) { +@@ -124,6 +201,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + state->info.alert_level = 0x00; + state->info.alert_description = 0x00; + ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) { ++ /* let tls_ack_handler set application_data */ ++ state->info.content_type = SSL3_RT_HANDSHAKE; ++#endif ++ + #ifdef SSL3_RT_HEARTBEAT + } else if (content_type == TLS1_RT_HEARTBEAT) { + uint8_t *p = buf; +@@ -141,16 +224,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + } + #endif + } ++ + tls_session_information(state); + } + + int cbtls_password(char *buf, +- int num UNUSED, ++ int num, + int rwflag UNUSED, + void *userdata) + { +- strcpy(buf, (char *)userdata); +- return(strlen((char *)userdata)); ++ size_t len; ++ ++ len = strlcpy(buf, (char *)userdata, num); ++ if (len >= (size_t) num) { ++ ERROR("Password too long. Maximum length is %i bytes", num - 1); ++ return 0; ++ } ++ ++ return len; + } + + #endif +diff --git a/src/main/client.c b/src/main/client.c +index 6228438c47..4d67a27688 100644 +--- a/src/main/client.c ++++ b/src/main/client.c +@@ -41,11 +41,16 @@ RCSID("$Id$") + #endif + + struct radclient_list { ++ char const *name; /* name of this list */ ++ char const *server; /* virtual server associated with this client list */ ++ + /* + * FIXME: One set of trees for IPv4, and another for IPv6? + */ + rbtree_t *trees[129]; /* for 0..128, inclusive. */ + uint32_t min_prefix; ++ ++ bool parsed; + }; + + +@@ -145,6 +150,15 @@ RADCLIENT_LIST *client_list_init(CONF_SECTION *cs) + + clients->min_prefix = 128; + ++ /* ++ * Associate the "clients" list with the virtual server. ++ */ ++ if (cs && (cf_data_add(cs, "clients", clients, NULL) < 0)) { ++ ERROR("Failed to associate client list with section %s\n", cf_section_name1(cs)); ++ client_list_free(clients); ++ return false; ++ } ++ + return clients; + } + +@@ -161,6 +175,18 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + + if (!client) return false; + ++ /* ++ * Initialize the global list, if not done already. ++ */ ++ if (!root_clients) { ++ root_clients = cf_data_find(main_config.config, "clients"); ++ if (!root_clients) root_clients = client_list_init(main_config.config); ++ if (!root_clients) { ++ ERROR("Cannot add client - failed creating client list"); ++ return false; ++ } ++ } ++ + /* + * Hack to fixup wildcard clients + * +@@ -188,66 +214,85 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + /* + * If the client also defines a server, do that now. + */ +- if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; ++ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_home_server)) return false; + + /* +- * If "clients" is NULL, it means add to the global list, +- * unless we're trying to add it to a virtual server... ++ * If there's no client list, BUT there's a virtual ++ * server, try to add the client to the appropriate ++ * "clients" section for that virtual server. + */ +- if (!clients) { +- if (client->server != NULL) { +- CONF_SECTION *cs; +- CONF_SECTION *subcs; +- +- cs = cf_section_sub_find_name2(main_config.config, "server", client->server); +- if (!cs) { +- ERROR("Failed to find virtual server %s", client->server); +- return false; +- } ++ if (!clients && client->server) { ++ CONF_SECTION *cs; ++ CONF_SECTION *subcs; ++ CONF_PAIR *cp; ++ char const *section_name; + +- /* +- * If this server has no "listen" section, add the clients +- * to the global client list. +- */ +- subcs = cf_section_sub_find(cs, "listen"); +- if (!subcs) { +- DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", +- client->server); +- goto global_clients; +- } ++ cs = cf_section_sub_find_name2(main_config.config, "server", client->server); ++ if (!cs) { ++ ERROR("Cannot add client - virtual server %s does not exist", client->server); ++ return false; ++ } + +- /* +- * If the client list already exists, use that. +- * Otherwise, create a new client list. +- */ +- clients = cf_data_find(cs, "clients"); +- if (!clients) { +- clients = client_list_init(cs); +- if (!clients) { +- ERROR("Out of memory"); +- return false; +- } ++ /* ++ * If this server has no "listen" section, add the clients ++ * to the global client list. ++ */ ++ subcs = cf_section_sub_find(cs, "listen"); ++ if (!subcs) { ++ DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", ++ client->server); ++ goto check_list; ++ } + +- if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { +- ERROR("Failed to associate clients with virtual server %s", client->server); +- client_list_free(clients); +- return false; +- } +- } ++ cp = cf_pair_find(subcs, "clients"); ++ if (!cp) { ++ DEBUG("No 'clients' configuration item in first listener of virtual server %s. Adding client to global client list", ++ client->server); ++ goto check_list; ++ } + +- } else { +- global_clients: +- /* +- * Initialize the global list, if not done already. +- */ +- if (!root_clients) { +- root_clients = client_list_init(NULL); +- if (!root_clients) return false; +- } +- clients = root_clients; ++ /* ++ * Duplicate the lookup logic in common_socket_parse() ++ * ++ * Explicit list given: use it. ++ */ ++ section_name = cf_pair_value(cp); ++ if (!section_name) goto check_list; ++ ++ subcs = cf_section_sub_find_name2(main_config.config, "clients", section_name); ++ if (!subcs) { ++ subcs = cf_section_find(section_name); ++ } ++ if (!subcs) { ++ cf_log_err_cs(cs, ++ "Failed to find clients %s {...}", ++ section_name); ++ return false; ++ } ++ ++ DEBUG("Adding client to client list %s", section_name); ++ ++ /* ++ * If the client list already exists, use that. ++ * Otherwise, create a new client list. ++ * ++ * @todo - add the client to _all_ listeners? ++ */ ++ clients = cf_data_find(subcs, "clients"); ++ if (clients) goto check_list; ++ ++ clients = client_list_init(subcs); ++ if (!clients) { ++ ERROR("Cannot add client - failed creating client list %s for server %s", section_name, ++ client->server); ++ return false; + } + } + ++check_list: ++ if (!clients) clients = root_clients; ++ client->list = clients; ++ + /* + * Create a tree for it. + */ +@@ -280,10 +325,11 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) + #endif + #ifdef WITH_COA + namecmp(coa_name) && +- (old->coa_server == client->coa_server) && +- (old->coa_pool == client->coa_pool) && ++ (old->coa_home_server == client->coa_home_server) && ++ (old->coa_home_pool == client->coa_home_pool) && + #endif +- (old->message_authenticator == client->message_authenticator)) { ++ (old->require_ma == client->require_ma) && ++ (old->limit_proxy_state == client->limit_proxy_state)) { + WARN("Ignoring duplicate client %s", client->longname); + client_free(client); + return true; +@@ -445,6 +491,8 @@ static fr_ipaddr_t cl_ipaddr; + static uint32_t cl_netmask; + static char const *cl_srcipaddr = NULL; + static char const *hs_proto = NULL; ++static char const *require_message_authenticator = NULL; ++static char const *limit_proxy_state = NULL; + + #ifdef WITH_TCP + static CONF_PARSER limit_config[] = { +@@ -467,7 +515,8 @@ static const CONF_PARSER client_config[] = { + + { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL }, + +- { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" }, ++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, ++ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL }, + + { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL }, + { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL }, +@@ -512,14 +561,34 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + * it. Otherwise create a new one. + */ + clients = cf_data_find(section, "clients"); +- if (clients) return clients; +- +- clients = client_list_init(section); +- if (!clients) return NULL; ++ if (clients) { ++ /* ++ * Modules are initialized before the listeners. ++ * Which means that we MIGHT have read clients ++ * from SQL before parsing this "clients" ++ * section. So there may already be a clients ++ * list. ++ * ++ * But the list isn't _our_ list that we parsed, ++ * so we still need to parse the clients here. ++ */ ++ if (clients->parsed) return clients; ++ } else { ++ clients = client_list_init(section); ++ if (!clients) return NULL; ++ } + +- if (cf_top_section(section) == section) global = true; ++ if (cf_top_section(section) == section) { ++ global = true; ++ clients->name = "global"; ++ clients->server = NULL; ++ } + +- if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; ++ if (strcmp("server", cf_section_name1(section)) == 0) { ++ clients->name = NULL; ++ clients->server = cf_section_name2(section); ++ in_server = true; ++ } + + for (cs = cf_subsection_find_next(section, NULL, "client"); + cs; +@@ -592,8 +661,8 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { +- if (isalpha((int)*p) || +- isdigit((int)*p) || ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || + (*p == ':') || + (*p == '.')) continue; + break; +@@ -632,15 +701,6 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + + } + +- /* +- * Associate the clients structure with the section. +- */ +- if (cf_data_add(section, "clients", clients, NULL) < 0) { +- cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); +- client_list_free(clients); +- return NULL; +- } +- + /* + * Replace the global list of clients with the new one. + * The old one is still referenced from the original +@@ -648,6 +708,7 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls + */ + if (global) root_clients = clients; + ++ clients->parsed = true; + return clients; + } + +@@ -663,7 +724,7 @@ static const CONF_PARSER dynamic_config[] = { + { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL }, + { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL }, + +- { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL }, ++ { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL }, + + { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" }, + { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" }, +@@ -824,7 +885,7 @@ int client_map_section(CONF_SECTION *out, CONF_SECTION const *map, client_value_ + * @param ctx to allocate new clients in. + * @param cs to process as a client. + * @param in_server Whether the client should belong to a specific virtual server. +- * @param with_coa If true and coa_server or coa_pool aren't specified automatically, ++ * @param with_coa If true and coa_home_server or coa_home_pool aren't specified automatically, + * create a coa home_server section and add it to the client CONF_SECTION. + * @return new RADCLIENT struct. + */ +@@ -845,8 +906,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + c = talloc_zero(ctx, RADCLIENT); + c->cs = cs; + ++ /* ++ * Set the "require message authenticator" and "limit ++ * proxy state" flags from the global default. If the ++ * configuration item exists, AND is set, it will ++ * over-ride the flag. ++ */ ++ c->require_ma = main_config.require_ma; ++ c->limit_proxy_state = main_config.limit_proxy_state; ++ + memset(&cl_ipaddr, 0, sizeof(cl_ipaddr)); + cl_netmask = 255; ++ require_message_authenticator = NULL; ++ limit_proxy_state = NULL; + + if (cf_section_parse(cs, c, client_config) < 0) { + cf_log_err_cs(cs, "Error parsing client section"); +@@ -856,6 +928,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + hs_proto = NULL; + cl_srcipaddr = NULL; + #endif ++ require_message_authenticator = NULL; ++ limit_proxy_state = NULL; + + return NULL; + } +@@ -1054,11 +1128,11 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + cp = cf_pair_find(cs, "coa_server"); + if (cp) { + c->coa_name = cf_pair_value(cp); +- c->coa_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); +- if (!c->coa_pool) { +- c->coa_server = home_server_byname(c->coa_name, HOME_TYPE_COA); ++ c->coa_home_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); ++ if (!c->coa_home_pool) { ++ c->coa_home_server = home_server_byname(c->coa_name, HOME_TYPE_COA); + } +- if (!c->coa_pool && !c->coa_server) { ++ if (!c->coa_home_pool && !c->coa_home_server) { + cf_log_err_cs(cs, "No such home_server or home_server_pool \"%s\"", c->coa_name); + goto error; + } +@@ -1096,7 +1170,7 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo + + rad_assert(home->type == HOME_TYPE_COA); + +- c->coa_server = home; ++ c->coa_home_server = home; + c->defines_coa_server = true; + } + } +@@ -1114,6 +1188,16 @@ done_coa: + } + #endif + ++ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) { ++ goto error; ++ } ++ ++ if (c->require_ma != FR_BOOL_TRUE) { ++ if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) { ++ goto error; ++ } ++ } ++ + return c; + } + +@@ -1158,7 +1242,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons + if (shortname) c->shortname = talloc_typed_strdup(c, shortname); + if (type) c->nas_type = talloc_typed_strdup(c, type); + if (server) c->server = talloc_typed_strdup(c, server); +- c->message_authenticator = require_ma; ++ c->require_ma = require_ma; + + return c; + } +@@ -1175,7 +1259,6 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + int i, *pi; + char **p; + RADCLIENT *c; +- CONF_PAIR *cp = NULL; + char buffer[128]; + + vp_cursor_t cursor; +@@ -1206,6 +1289,7 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + for (i = 0; dynamic_config[i].name != NULL; i++) { + DICT_ATTR const *da; + char *strvalue = NULL; ++ CONF_PAIR *cp = NULL; + + da = dict_attrbyname(dynamic_config[i].name); + if (!da) { +@@ -1344,10 +1428,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + *pi = vp->vp_integer; + + /* +- * Same nastiness as above. ++ * Same nastiness as above, but hard-coded for require Message-Authenticator. + */ + for (parse = client_config; parse->name; parse++) { +- if (parse->offset == dynamic_config[i].offset) break; ++ if (parse->type == PW_TYPE_BOOLEAN) break; + } + if (!parse) break; + +@@ -1377,6 +1461,8 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) + fr_cursor_first(&cursor); + vp = fr_cursor_remove(&cursor); + if (vp) { ++ CONF_PAIR *cp; ++ + do { + char *value; + +@@ -1436,6 +1522,11 @@ validate: + goto error; + } + ++ /* ++ * It can't be set to "auto". Too bad. ++ */ ++ c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma; ++ + if (!client_add_dynamic(clients, request->client, c)) { + return NULL; + } +diff --git a/src/main/command.c b/src/main/command.c +index 844898eed1..706260b150 100644 +--- a/src/main/command.c ++++ b/src/main/command.c +@@ -27,19 +27,14 @@ + #include <freeradius-devel/modcall.h> + #include <freeradius-devel/md5.h> + #include <freeradius-devel/channel.h> ++#include <freeradius-devel/connection.h> ++#include <freeradius-devel/socket.h> + + #include <libgen.h> + #ifdef HAVE_INTTYPES_H + #include <inttypes.h> + #endif + +-#ifdef HAVE_SYS_UN_H +-#include <sys/un.h> +-#ifndef SUN_LEN +-#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +-#endif +-#endif +- + #ifdef HAVE_SYS_STAT_H + #include <sys/stat.h> + #endif +@@ -1009,6 +1004,11 @@ static char const *method_names[MOD_COUNT] = { + "pre-proxy", + "post-proxy", + "post-auth" ++#ifdef WITH_COA ++ , ++ "recv-coa", ++ "send-coa" ++#endif + }; + + +@@ -1138,7 +1138,7 @@ static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED + } + + #ifdef WITH_PROXY +-static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) ++static int command_show_home_servers(rad_listen_t *listener, int argc, char *argv[]) + { + int i; + home_server_t *home; +@@ -1146,9 +1146,10 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + + char buffer[256]; + +- for (i = 0; i < 256; i++) { +- home = home_server_bynumber(i); +- if (!home) break; ++ for (i = 0; i < home_server_max_number; i++) { ++ ++ if ((home = home_server_bynumber(i)) == NULL) ++ continue; + + /* + * Internal "virtual" home server. +@@ -1190,6 +1191,9 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + } else if (home->state == HOME_STATE_IS_DEAD) { + state = "dead"; + ++ } else if (home->state == HOME_STATE_ADMIN_DOWN) { ++ state = "down"; ++ + } else if (home->state == HOME_STATE_UNKNOWN) { + time_t now = time(NULL); + +@@ -1212,10 +1216,19 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN + + } else continue; + +- cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", +- ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), +- home->port, proto, type, state, +- home->currently_outstanding); ++ if (argc > 0 && !strcmp(argv[0], "all")) { ++ char const *dynamic = home->dynamic ? "yes" : "no"; ++ ++ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\t(name=%s, dynamic=%s)\n", ++ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), ++ home->port, proto, type, state, ++ home->currently_outstanding, home->name, dynamic); ++ } else { ++ cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", ++ ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), ++ home->port, proto, type, state, ++ home->currently_outstanding); ++ } + } + + return CMD_OK; +@@ -1239,27 +1252,107 @@ static int command_show_client_config(rad_listen_t *listener, int argc, char *ar + return 1; + } + ++/* ++ * @todo - copied from clients.c. Better to re-use, but whatever. ++ */ ++struct radclient_list { ++ char const *name; /* name of this list */ ++ char const *server; /* virtual server associated with this client list */ ++ ++ /* ++ * FIXME: One set of trees for IPv4, and another for IPv6? ++ */ ++ rbtree_t *trees[129]; /* for 0..128, inclusive. */ ++ uint32_t min_prefix; ++}; ++ + +-static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) ++static int command_show_clients(rad_listen_t *listener, int argc, char *argv[]) + { + int i; + RADCLIENT *client; + char buffer[256]; + +- for (i = 0; i < 256; i++) { +- client = client_findbynumber(NULL, i); +- if (!client) break; ++ if (argc == 0) { ++ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { ++ ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); ++ ++ if (((client->ipaddr.af == AF_INET) && ++ (client->ipaddr.prefix != 32)) || ++ ((client->ipaddr.af == AF_INET6) && ++ (client->ipaddr.prefix != 128))) { ++ cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); ++ } else { ++ cprintf(listener, "%s\n", buffer); ++ } ++ } + +- ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); ++ return CMD_OK; ++ } + +- if (((client->ipaddr.af == AF_INET) && +- (client->ipaddr.prefix != 32)) || +- ((client->ipaddr.af == AF_INET6) && +- (client->ipaddr.prefix != 128))) { +- cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); ++ if (argc != 1) { ++ cprintf_error(listener, "Unknown command %s %s ...\n", argv[0], argv[1]); ++ return -1; ++ } ++ ++ if (strcmp(argv[0], "verbose") != 0) { ++ cprintf_error(listener, "Unknown command %s\n", argv[0]); ++ return -1; ++ } ++ ++ for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { ++ if (client->cs) { ++ cprintf(listener, "client %s {\n", cf_section_name2(client->cs)); + } else { +- cprintf(listener, "%s\n", buffer); ++ cprintf(listener, "client {\n"); ++ } ++ ++ fr_ntop(buffer, sizeof(buffer), &client->ipaddr); ++ cprintf(listener, "\tipaddr = %s\n", buffer); ++ ++ if (client->src_ipaddr.af != AF_UNSPEC) { ++ fr_ntop(buffer, sizeof(buffer), &client->src_ipaddr); ++ cprintf(listener, "\tsrc_ipaddr = %s\n", buffer); ++ } ++ ++ if (client->proto == IPPROTO_UDP) { ++ cprintf(listener, "\tproto = udp\n"); ++ } else if (client->proto == IPPROTO_TCP) { ++ cprintf(listener, "\tproto = tcp\n"); ++ } else { ++ cprintf(listener, "\tproto = *\n"); ++ } ++ ++ cprintf(listener, "\tsecret = %s\n", client->secret); ++ cprintf(listener, "\tlongname = %s\n", client->longname); ++ cprintf(listener, "\tshortname = %s\n", client->shortname); ++ if (client->nas_type) cprintf(listener, "\tnas_type = %s\n", client->nas_type); ++ cprintf(listener, "\tnumber = %d\n", client->number); ++ ++ if (client->server) { ++ cprintf(listener, "\tvirtual_server = %s\n", client->server); ++ } ++ ++#ifdef WITH_DYNAMIC_CLIENTS ++ if (client->dynamic) { ++ cprintf(listener, "\tdynamic = yes\n"); ++ cprintf(listener, "\tlifetime = %u\n", client->lifetime); ++ } ++#endif ++ ++#ifdef WITH_TLS ++ if (client->tls_required) { ++ cprintf(listener, "\ttls = yes\n"); + } ++#endif ++ ++ if (client->list && client->list->server) { ++ cprintf(listener, "\tparent_virtual_server = %s\n", client->list->server); ++ } else { ++ cprintf(listener, "\tglobal = yes\n"); ++ } ++ ++ cprintf(listener, "}\n"); + } + + return CMD_OK; +@@ -1301,7 +1394,7 @@ static int command_debug_file(rad_listen_t *listener, int argc, char *argv[]) + return -1; + } + +- if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) { ++ if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) == argv[0])) { + cprintf_error(listener, "Cannot direct debug logs to absolute path.\n"); + } + +@@ -1646,8 +1739,14 @@ static int command_set_home_server_state(rad_listen_t *listener, int argc, char + } else if (strcmp(argv[last], "dead") == 0) { + struct timeval now; + +- gettimeofday(&now, NULL); /* we do this WAY too ofetn */ +- mark_home_server_dead(home, &now); ++ gettimeofday(&now, NULL); /* we do this WAY too often */ ++ mark_home_server_dead(home, &now, false); ++ ++ } else if (strcmp(argv[last], "down") == 0) { ++ struct timeval now; ++ ++ gettimeofday(&now, NULL); /* we do this WAY too often */ ++ mark_home_server_dead(home, &now, true); + + } else { + cprintf_error(listener, "Unknown state \"%s\"\n", argv[last]); +@@ -1677,6 +1776,10 @@ static int command_show_home_server_state(rad_listen_t *listener, int argc, char + cprintf(listener, "zombie\n"); + break; + ++ case HOME_STATE_ADMIN_DOWN: ++ cprintf(listener, "down\n"); ++ break; ++ + case HOME_STATE_UNKNOWN: + cprintf(listener, "unknown\n"); + break; +@@ -2047,7 +2150,7 @@ static fr_command_table_t command_table_show_client[] = { + "- show configuration for given client", + command_show_client_config, NULL }, + { "list", FR_READ, +- "show client list - shows list of global clients", ++ "show client list [verbose] - shows list of global clients", + command_show_clients, NULL }, + + { NULL, 0, NULL, NULL, NULL } +@@ -2056,7 +2159,7 @@ static fr_command_table_t command_table_show_client[] = { + #ifdef WITH_PROXY + static fr_command_table_t command_table_show_home[] = { + { "list", FR_READ, +- "show home_server list - shows list of home servers", ++ "show home_server list [all] - shows list of home servers", + command_show_home_servers, NULL }, + { "state", FR_READ, + "show home_server state <ipaddr> <port> [udp|tcp] [src <ipaddr>] - shows state of given home server", +@@ -2597,6 +2700,36 @@ static int command_del_client(rad_listen_t *listener, int argc, char *argv[]) + } + + ++static int command_del_home_server(rad_listen_t *listener, int argc, char *argv[]) ++{ ++ if (argc < 2) { ++ cprintf_error(listener, "<name> and <type> are required\n"); ++ return 0; ++ } ++ ++ if (home_server_delete(argv[0], argv[1]) < 0) { ++ cprintf_error(listener, "Failed deleted home_server %s - %s\n", argv[1], fr_strerror()); ++ return 0; ++ } ++ ++ return CMD_OK; ++} ++ ++static int command_add_home_server_file(rad_listen_t *listener, int argc, char *argv[]) ++{ ++ if (argc < 1) { ++ cprintf_error(listener, "<file> is required\n"); ++ return 0; ++ } ++ ++ if (home_server_afrom_file(argv[0]) < 0) { ++ cprintf_error(listener, "Unable to add home server - %s\n", fr_strerror()); ++ return 0; ++ } ++ ++ return CMD_OK; ++} ++ + static fr_command_table_t command_table_del_client[] = { + { "ipaddr", FR_WRITE, + "del client ipaddr <ipaddr> [udp|tcp] [listen <ipaddr> <port>] - Delete a dynamically created client", +@@ -2605,16 +2738,26 @@ static fr_command_table_t command_table_del_client[] = { + { NULL, 0, NULL, NULL, NULL } + }; + ++static fr_command_table_t command_table_del_home_server[] = { ++ { "file", FR_WRITE, ++ "del home_server file <name> [auth|acct|coa] - Delete a dynamically created home_server", ++ command_del_home_server, NULL }, ++ ++ { NULL, 0, NULL, NULL, NULL } ++}; + + static fr_command_table_t command_table_del[] = { + { "client", FR_WRITE, + "del client <command> - Delete client configuration commands", + NULL, command_table_del_client }, + ++ { "home_server", FR_WRITE, ++ "del home_server <command> - Delete home_server configuration commands", ++ NULL, command_table_del_home_server }, ++ + { NULL, 0, NULL, NULL, NULL } + }; + +- + static fr_command_table_t command_table_add_client[] = { + { "file", FR_WRITE, + "add client file <filename> - Add new client definition from <filename>", +@@ -2623,12 +2766,23 @@ static fr_command_table_t command_table_add_client[] = { + { NULL, 0, NULL, NULL, NULL } + }; + ++static fr_command_table_t command_table_add_home_server[] = { ++ { "file", FR_WRITE, ++ "add home_server file <filename> - Add new home serverdefinition from <filename>", ++ command_add_home_server_file, NULL }, ++ ++ { NULL, 0, NULL, NULL, NULL } ++}; + + static fr_command_table_t command_table_add[] = { + { "client", FR_WRITE, + "add client <command> - Add client configuration commands", + NULL, command_table_add_client }, + ++ { "home_server", FR_WRITE, ++ "add home_server <command> - Add home server configuration commands", ++ NULL, command_table_add_home_server }, ++ + { NULL, 0, NULL, NULL, NULL } + }; + #endif +@@ -2636,7 +2790,7 @@ static fr_command_table_t command_table_add[] = { + #ifdef WITH_PROXY + static fr_command_table_t command_table_set_home[] = { + { "state", FR_WRITE, +- "set home_server state <ipaddr> <port> [udp|tcp] [src <ipaddr>] [alive|dead] - set state for given home server", ++ "set home_server state <ipaddr> <port> [udp|tcp] [src <ipaddr>] [alive|dead|down] - set state for given home server", + command_set_home_server_state, NULL }, + + { NULL, 0, NULL, NULL, NULL } +diff --git a/src/main/conffile.c b/src/main/conffile.c +index a8c667bfb5..d458792cd5 100644 +--- a/src/main/conffile.c ++++ b/src/main/conffile.c +@@ -353,6 +353,7 @@ error: + + file->filename = filename; + file->cs = cs; ++ file->from_dir = from_dir; + + if (fstat(fd, &file->buf) == 0) { + #ifdef S_IWOTH +@@ -1174,10 +1175,14 @@ static char const *cf_expand_variables(char const *cf, int *lineno, + return NULL; + } + ++ /* ++ * Might as well make ++ * non-existent string be the ++ * empty string. ++ */ + if (!cp->value) { +- ERROR("%s[%d]: Reference \"%s\" has no value", +- cf, *lineno, input); +- return NULL; ++ *p = '\0'; ++ goto skip_value; + } + + if (p + strlen(cp->value) >= output + outsize) { +@@ -1188,6 +1193,7 @@ static char const *cf_expand_variables(char const *cf, int *lineno, + + strcpy(p, cp->value); + p += strlen(p); ++ skip_value: + ptr = end + 1; + + } else if (ci->type == CONF_ITEM_SECTION) { +@@ -1418,6 +1424,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d + { + int rcode; + bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists; ++ bool ignore_dflt; + char **q; + char const *value; + CONF_PAIR *cp = NULL; +@@ -1441,6 +1448,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d + cant_be_empty = (type & PW_TYPE_NOT_EMPTY); + tmpl = (type & PW_TYPE_TMPL); + multi = (type & PW_TYPE_MULTI); ++ ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT); + + if (attribute) required = true; + if (required) cant_be_empty = true; /* May want to review this in the future... */ +@@ -1464,7 +1472,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d + * section, use the default value. + */ + if (!cp) { +- if (deprecated) return 0; /* Don't set the default value */ ++ if (deprecated || ignore_dflt) return 0; /* Don't set the default value */ + + rcode = 1; + value = dflt; +@@ -1798,6 +1806,7 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, + CONF_PARSER const *variables) + { + int i; ++ void *data; + + for (i = 0; variables[i].name != NULL; i++) { + if (variables[i].type == PW_TYPE_SUBSECTION) { +@@ -1822,9 +1831,13 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, + subcs->item.lineno = cs->item.lineno; + cf_item_add(cs, &(subcs->item)); + } ++ if (base) { ++ data = ((uint8_t *)base) + variables[i].offset; ++ } else { ++ data = NULL; ++ } + +- cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset, +- (CONF_PARSER const *) variables[i].dflt); ++ cf_section_parse_init(subcs, data, (CONF_PARSER const *) variables[i].dflt); + continue; + } + +@@ -1922,8 +1935,13 @@ int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) + goto finish; + } + +- ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset, +- (CONF_PARSER const *) variables[i].dflt); ++ if (base) { ++ data = ((uint8_t *)base) + variables[i].offset; ++ } else { ++ data = NULL; ++ } ++ ++ ret = cf_section_parse(subcs, data, (CONF_PARSER const *) variables[i].dflt); + if (ret < 0) goto finish; + continue; + } /* else it's a CONF_PAIR */ +@@ -2313,7 +2331,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + if (has_spaces) { + ptr = cbuf; +- while (isspace((int) *ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (ptr > cbuf) { + memmove(cbuf, ptr, len - (ptr - cbuf)); +@@ -2329,7 +2347,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + if (at_eof) break; + + ptr = buf; +- while (*ptr && isspace((int) *ptr)) ptr++; ++ while (*ptr && isspace((uint8_t) *ptr)) ptr++; + + if (!*ptr || (*ptr == '#')) continue; + +@@ -2511,6 +2529,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + */ + while ((dp = readdir(dir)) != NULL) { + char const *p; ++ int slen; + + if (dp->d_name[0] == '.') continue; + +@@ -2518,8 +2537,8 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { +- if (isalpha((int)*p) || +- isdigit((int)*p) || ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || + (*p == '-') || + (*p == '_') || + (*p == '.')) continue; +@@ -2527,8 +2546,12 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + } + if (*p != '\0') continue; + +- snprintf(buf2, sizeof(buf2), "%s%s", +- value, dp->d_name); ++ slen = snprintf(buf2, sizeof(buf2), "%s%s", ++ value, dp->d_name); ++ if (slen >= (int) sizeof(buf2) || slen < 0) { ++ ERROR("%s: Full file path is too long.", dp->d_name); ++ return -1; ++ } + if ((stat(buf2, &stat_buf) != 0) || + S_ISDIR(stat_buf.st_mode)) continue; + +@@ -2794,7 +2817,15 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + case T_OP_EQ: + case T_OP_SET: +- while (isspace((int) *ptr)) ptr++; ++ case T_OP_PREPEND: ++ while (isspace((uint8_t) *ptr)) ptr++; ++ ++ /* ++ * Be a little more forgiving. ++ */ ++ if (*ptr == '#') { ++ t3 = T_HASH; ++ } else + + /* + * New parser: non-quoted strings are +@@ -2809,7 +2840,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + + t3 = T_BARE_WORD; + while (*q && (*q >= ' ') && (*q != ',') && +- !isspace(*q)) q++; ++ !isspace((uint8_t) *q)) q++; + + if ((size_t) (q - ptr) >= sizeof(buf3)) { + ERROR("%s[%d]: Parse error: value too long", +@@ -2891,7 +2922,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + /* + * Require a comma, unless there's a comment. + */ +- while (isspace(*ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (*ptr == ',') { + ptr++; +@@ -2964,7 +2995,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, + /* + * Done parsing one thing. Skip to EOL if possible. + */ +- while (isspace(*ptr)) ptr++; ++ while (isspace((uint8_t) *ptr)) ptr++; + + if (*ptr == '#') continue; + +@@ -3561,6 +3592,10 @@ bool cf_item_is_pair(CONF_ITEM const *item) + return item->type == CONF_ITEM_PAIR; + } + ++bool cf_item_is_data(CONF_ITEM const *item) ++{ ++ return item->type == CONF_ITEM_DATA; ++} + + static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name, + void *data, void (*data_free)(void *)) +diff --git a/src/main/listen.c b/src/main/listen.c +index ebf7f5221c..6cda245ef0 100644 +--- a/src/main/listen.c ++++ b/src/main/listen.c +@@ -52,6 +52,17 @@ RCSID("$Id$") + #include <sys/stat.h> + #endif + ++#ifdef WITH_TLS ++#include <netinet/tcp.h> ++ ++# if defined(__APPLE__) || defined(__FreeBSD__) ++# if !defined(SOL_TCP) && defined(IPPROTO_TCP) ++# define SOL_TCP IPPROTO_TCP ++# endif ++# endif ++ ++#endif ++ + #ifdef DEBUG_PRINT_PACKET + static void print_packet(RADIUS_PACKET *packet) + { +@@ -217,7 +228,7 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, + client_delete(clients, client); + + /* +- * Add a timer to free the client in 120s ++ * Add a timer to free the client 20s after it's already timed out. + */ + el = radius_event_list_corral(EVENT_CORRAL_MAIN); + +@@ -349,7 +360,6 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, + + static int listen_bind(rad_listen_t *this); + +- + /* + * Process and reply to a server-status request. + * Like rad_authenticate and rad_accounting this should +@@ -360,6 +370,52 @@ int rad_status_server(REQUEST *request) + int rcode = RLM_MODULE_OK; + DICT_VALUE *dval; + ++#ifdef WITH_TLS ++ if (request->listener->tls) { ++ listen_socket_t *sock = request->listener->data; ++ ++ if (sock->state == LISTEN_TLS_CHECKING) { ++ int autz_type = PW_AUTZ_TYPE; ++ char const *name = "Autz-Type"; ++ ++ if (request->listener->type == RAD_LISTEN_ACCT) { ++ autz_type = PW_ACCT_TYPE; ++ name = "Acct-Type"; ++ } ++ ++ RDEBUG("(TLS) Checking connection to see if it is authorized."); ++ ++ dval = dict_valbyname(autz_type, 0, "New-TLS-Connection"); ++ if (dval) { ++ rcode = process_authorize(dval->value, request); ++ } else { ++ rcode = RLM_MODULE_OK; ++ RWDEBUG("(TLS) Did not find '%s New-TLS-Connection' - defaulting to accept", name); ++ } ++ ++ if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { ++ RDEBUG("(TLS) Connection is authorized"); ++ request->reply->code = PW_CODE_ACCESS_ACCEPT; ++ } else { ++ RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); ++ request->reply->code = PW_CODE_ACCESS_REJECT; ++ } ++ ++ return 0; ++ } ++ } ++#endif ++ ++#ifdef WITH_STATS ++ /* ++ * Full statistics are available only on a statistics ++ * socket. ++ */ ++ if (request->listener->type == RAD_LISTEN_NONE) { ++ request_stats_reply(request); ++ } ++#endif ++ + switch (request->listener->type) { + #ifdef WITH_STATS + case RAD_LISTEN_NONE: +@@ -443,17 +499,124 @@ int rad_status_server(REQUEST *request) + return 0; + } + +-#ifdef WITH_STATS ++ return 0; ++} ++ ++static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client) ++{ ++ if (client->require_ma == FR_BOOL_TRUE) return; ++ ++ if (client->require_ma == FR_BOOL_AUTO) { ++ if (!packet->message_authenticator) { ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("BlastRADIUS check: Received packet without Message-Authenticator."); ++ ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ client->require_ma = FR_BOOL_FALSE; ++ ++ /* ++ * And fall through to the ++ * limit_proxy_state checks, which might ++ * complain again. Oh well, maybe that ++ * will make people read the messages. ++ */ ++ ++ } else if (packet->eap_message) { ++ /* ++ * Don't set it to "true" for packets ++ * with EAP-Message. It's already ++ * required there, and we might get a ++ * non-EAP packet with (or without) ++ * Message-Authenticator ++ */ ++ return; ++ } else { ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("BlastRADIUS check: Received packet with Message-Authenticator."); ++ ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack."); ++ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ client->require_ma = FR_BOOL_TRUE; ++ return; ++ } ++ ++ } ++ + /* +- * Full statistics are available only on a statistics +- * socket. ++ * If all of the checks are turned off, then complain for every packet we receive. + */ +- if (request->listener->type == RAD_LISTEN_NONE) { +- request_stats_reply(request); ++ if (client->limit_proxy_state == FR_BOOL_FALSE) { ++ /* ++ * We have a Message-Authenticator, and it's valid. We don't need to compain. ++ */ ++ if (packet->message_authenticator) return; ++ ++ if (!fr_debug_lvl) return; /* easier than checking for each line below */ ++ ++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ DEBUG("BlastRADIUS check: Received packet without Message-Authenticator."); ++ DEBUG("YOU MUST SET \"require_message_authenticator = true\", or"); ++ DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname); ++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ DEBUG("The packet does not contain Message-Authenticator, which is a security issue"); ++ DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); ++ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ return; + } +-#endif + +- return 0; ++ /* ++ * Don't complain here. rad_packet_ok() will instead ++ * complain about every packet with Proxy-State but which ++ * is missing Message-Authenticator. ++ */ ++ if (client->limit_proxy_state == FR_BOOL_TRUE) { ++ return; ++ } ++ ++ if (packet->proxy_state && !packet->message_authenticator) { ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator."); ++ ERROR("This is either a BlastRADIUS attack, OR"); ++ ERROR("the client is a proxy RADIUS server which has not been upgraded."); ++ ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ client->limit_proxy_state = FR_BOOL_FALSE; ++ ++ } else { ++ client->limit_proxy_state = FR_BOOL_TRUE; ++ ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ if (!packet->proxy_state) { ++ ERROR("BlastRADIUS check: Received packet without Proxy-State."); ++ } else { ++ ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator."); ++ } ++ ++ ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname); ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ if (!packet->message_authenticator) { ++ ERROR("The packet does not contain Message-Authenticator, which is a security issue."); ++ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); ++ } else { ++ ERROR("The packet contains Message-Authenticator."); ++ if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack."); ++ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); ++ } ++ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } + } + + #ifdef WITH_TCP +@@ -532,13 +695,37 @@ static int dual_tcp_recv(rad_listen_t *listener) + switch (packet->code) { + case PW_CODE_ACCESS_REQUEST: + if (listener->type != RAD_LISTEN_AUTH) goto bad_packet; ++ ++ /* ++ * Enforce BlastRADIUS checks on TCP, too. ++ */ ++ if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) { ++ FR_STATS_INC(auth, total_malformed_requests); ++ rad_free(&sock->packet); ++ return 0; ++ } ++ ++ /* ++ * Perform BlastRADIUS checks and warnings. ++ */ ++ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); ++ + FR_STATS_INC(auth, total_requests); + fun = rad_authenticate; + break; + + #ifdef WITH_ACCOUNTING + case PW_CODE_ACCOUNTING_REQUEST: +- if (listener->type != RAD_LISTEN_ACCT) goto bad_packet; ++ if (listener->type != RAD_LISTEN_ACCT) { ++ /* ++ * Allow auth + dual. Disallow ++ * everything else. ++ */ ++ if (!((listener->type == RAD_LISTEN_AUTH) && ++ (listener->dual))) { ++ goto bad_packet; ++ } ++ } + FR_STATS_INC(acct, total_requests); + fun = rad_accounting; + break; +@@ -574,6 +761,79 @@ static int dual_tcp_recv(rad_listen_t *listener) + return 1; + } + ++#ifdef WITH_TLS ++typedef struct { ++ char const *name; ++ SSL_CTX *ctx; ++} fr_realm_ctx_t; /* hack from tls. */ ++ ++static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) ++{ ++ fr_tls_server_conf_t *conf = arg; ++ char const *name, *p; ++ int type; ++ fr_realm_ctx_t my_r, *r; ++ REQUEST *request; ++ char buffer[PATH_MAX]; ++ ++ /* ++ * No SNI, that's fine. ++ */ ++ type = SSL_get_servername_type(ssl); ++ if (type < 0) return SSL_TLSEXT_ERR_OK; ++ ++ /* ++ * No realms configured, just use the default context. ++ */ ++ if (!conf->realms) return SSL_TLSEXT_ERR_OK; ++ ++ name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); ++ if (!name) return SSL_TLSEXT_ERR_OK; ++ ++ /* ++ * RFC Section 6066 Section 3 says that the names are ++ * ASCII, without a trailing dot. i.e. punycode. ++ */ ++ for (p = name; *p != '\0'; p++) { ++ if (*p == '-') continue; ++ if (*p == '.') continue; ++ if ((*p >= 'A') && (*p <= 'Z')) continue; ++ if ((*p >= 'a') && (*p <= 'z')) continue; ++ if ((*p >= '0') && (*p <= '9')) continue; ++ ++ /* ++ * Anything else, fail. ++ */ ++ return SSL_TLSEXT_ERR_ALERT_FATAL; ++ } ++ ++ /* ++ * Too long, fail. ++ */ ++ if ((p - name) > 255) return SSL_TLSEXT_ERR_ALERT_FATAL; ++ ++ snprintf(buffer, sizeof(buffer), "%s/%s.pem", conf->realm_dir, name); ++ ++ my_r.name = buffer; ++ r = fr_hash_table_finddata(conf->realms, &my_r); ++ ++ /* ++ * If found, switch certs. Otherwise use the default ++ * one. ++ */ ++ if (r) (void) SSL_set_SSL_CTX(ssl, r->ctx); ++ ++ /* ++ * Set an attribute saying which server has been selected. ++ */ ++ request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ if (request) { ++ (void) pair_make_config("TLS-Server-Name-Indication", name, T_OP_SET); ++ } ++ ++ return SSL_TLSEXT_ERR_OK; ++} ++#endif + + static int dual_tcp_accept(rad_listen_t *listener) + { +@@ -731,6 +991,14 @@ static int dual_tcp_accept(rad_listen_t *listener) + if (this->tls) { + this->recv = dual_tls_recv; + this->send = dual_tls_send; ++ ++ /* ++ * Set up SNI callback. We don't do it ++ * in the main TLS code, because EAP ++ * doesn't need or use SNI. ++ */ ++ SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); ++ SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); + } + #endif + } +@@ -932,7 +1200,6 @@ static CONF_PARSER limit_config[] = { + CONF_PARSER_TERMINATOR + }; + +- + #ifdef WITH_TCP + /* + * TLS requires child threads to handle the listeners. Which +@@ -1018,6 +1285,7 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + + } else if (strcmp(proto, "tcp") == 0) { + sock->proto = IPPROTO_TCP; ++ + } else { + cf_log_err_cs(cs, + "Unknown proto name \"%s\"", proto); +@@ -1050,6 +1318,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + return -1; + } + ++ /* ++ * Allow non-blocking for TLS sockets ++ */ ++ rcode = cf_item_parse(cs, "nonblock", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->nonblock), NULL); ++ if (rcode < 0) return -1; ++ + /* + * If unset, set to default. + */ +@@ -1068,6 +1342,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + } + #endif + ++ rcode = cf_item_parse(cs, "check_client_connections", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->check_client_connections), "no"); ++ if (rcode < 0) return -1; + } + #else /* WITH_TLS */ + /* +@@ -1312,6 +1588,11 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + */ + this->recv = dual_tcp_accept; + ++ /* ++ * @todo - add a free function? Though this only ++ * matters when we're tearing down the server, so ++ * perhaps it's less relevant. ++ */ + this->children = rbtree_create(this, listener_cmp, NULL, 0); + if (!this->children) { + cf_log_err_cs(cs, "Failed to create child list for TCP socket."); +@@ -1324,12 +1605,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) + } + + /* +- * Send an authentication response packet ++ * Send a response packet + */ +-static int auth_socket_send(rad_listen_t *listener, REQUEST *request) ++static int common_socket_send(rad_listen_t *listener, REQUEST *request) + { + rad_assert(request->listener == listener); +- rad_assert(listener->send == auth_socket_send); ++ rad_assert(listener->send == common_socket_send); + + if (request->reply->code == 0) return 0; + +@@ -1355,46 +1636,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) + } + + +-#ifdef WITH_ACCOUNTING +-/* +- * Send an accounting response packet (or not) +- */ +-static int acct_socket_send(rad_listen_t *listener, REQUEST *request) +-{ +- rad_assert(request->listener == listener); +- rad_assert(listener->send == acct_socket_send); +- +- /* +- * Accounting reject's are silently dropped. +- * +- * We do it here to avoid polluting the rest of the +- * code with this knowledge +- */ +- if (request->reply->code == 0) return 0; +- +-#ifdef WITH_UDPFROMTO +- /* +- * Overwrite the src ip address on the outbound packet +- * with the one specified by the client. +- * This is useful to work around broken DSR implementations +- * and other routing issues. +- */ +- if (request->client->src_ipaddr.af != AF_UNSPEC) { +- request->reply->src_ipaddr = request->client->src_ipaddr; +- } +-#endif +- +- if (rad_send(request->reply, request->packet, +- request->client->secret) < 0) { +- RERROR("Failed sending reply: %s", +- fr_strerror()); +- return -1; +- } +- +- return 0; +-} +-#endif +- + #ifdef WITH_PROXY + /* + * Send a packet to a home server. +@@ -1404,7 +1645,7 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) + static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) + { + rad_assert(request->proxy_listener == listener); +- rad_assert(listener->send == proxy_socket_send); ++ rad_assert(listener->proxy_send == proxy_socket_send); + + if (rad_send(request->proxy, NULL, + request->home_server->secret) < 0) { +@@ -1436,8 +1677,6 @@ static int stats_socket_recv(rad_listen_t *listener) + rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); + if (rcode < 0) return 0; + +- FR_STATS_INC(auth, total_requests); +- + if (rcode < 20) { /* RADIUS_HDR_LEN */ + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); + FR_STATS_INC(auth, total_malformed_requests); +@@ -1485,7 +1724,6 @@ static int stats_socket_recv(rad_listen_t *listener) + } + #endif + +- + /* + * Check if an incoming request is "ok" + * +@@ -1521,13 +1759,12 @@ static int auth_socket_recv(rad_listen_t *listener) + return 0; + } + +- FR_STATS_TYPE_INC(client->auth.total_requests); +- + /* + * Some sanity checks, based on the packet code. + */ + switch (code) { + case PW_CODE_ACCESS_REQUEST: ++ FR_STATS_TYPE_INC(client->auth.total_requests); + fun = rad_authenticate; + break; + +@@ -1562,7 +1799,7 @@ static int auth_socket_recv(rad_listen_t *listener) + * Now that we've sanity checked everything, receive the + * packet. + */ +- packet = rad_recv(ctx, listener->fd, client->message_authenticator); ++ packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2)); + if (!packet) { + FR_STATS_INC(auth, total_malformed_requests); + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); +@@ -1570,6 +1807,11 @@ static int auth_socket_recv(rad_listen_t *listener) + return 0; + } + ++ /* ++ * Perform BlastRADIUS checks and warnings. ++ */ ++ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); ++ + #ifdef __APPLE__ + #ifdef WITH_UDPFROMTO + /* +@@ -1637,13 +1879,12 @@ static int acct_socket_recv(rad_listen_t *listener) + return 0; + } + +- FR_STATS_TYPE_INC(client->acct.total_requests); +- + /* + * Some sanity checks, based on the packet code. + */ + switch (code) { + case PW_CODE_ACCOUNTING_REQUEST: ++ FR_STATS_TYPE_INC(client->acct.total_requests); + fun = rad_accounting; + break; + +@@ -1729,7 +1970,6 @@ static int do_proxy(REQUEST *request) + */ + vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY); + if (!vp) vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IPV6_ADDRESS, 0, TAG_ANY); +- + if (!vp) return 0; + + return 1; +@@ -1955,7 +2195,7 @@ static int coa_socket_recv(rad_listen_t *listener) + * Now that we've sanity checked everything, receive the + * packet. + */ +- packet = rad_recv(ctx, listener->fd, client->message_authenticator); ++ packet = rad_recv(ctx, listener->fd, client->require_ma); + if (!packet) { + FR_STATS_INC(coa, total_malformed_requests); + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); +@@ -2049,19 +2289,67 @@ static int proxy_socket_recv(rad_listen_t *listener) + */ + static int proxy_socket_tcp_recv(rad_listen_t *listener) + { ++ int rcode; + RADIUS_PACKET *packet; + listen_socket_t *sock = listener->data; +- char buffer[128]; ++ char buffer[256]; + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + +- packet = fr_tcp_recv(listener->fd, 0); +- if (!packet) { ++ if (!sock->packet) { ++ sock->packet = rad_alloc(sock, false); ++ if (!sock->packet) return 0; ++ ++ sock->packet->sockfd = listener->fd; ++ sock->packet->src_ipaddr = sock->other_ipaddr; ++ sock->packet->src_port = sock->other_port; ++ sock->packet->dst_ipaddr = sock->my_ipaddr; ++ sock->packet->dst_port = sock->my_port; ++ sock->packet->proto = sock->proto; ++ } ++ ++ packet = sock->packet; ++ ++ rcode = fr_tcp_read_packet(packet, 0); ++ ++ /* ++ * Still only a partial packet. Put it back, and return, ++ * so that we'll read more data when it's ready. ++ */ ++ if (rcode == 0) { ++ return 0; ++ } ++ ++ if (rcode == -1) { /* error reading packet */ ++ ERROR("Invalid packet from %s port %d, closing socket: %s", ++ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), ++ packet->src_port, fr_strerror()); ++ } ++ ++ if (rcode < 0) { /* error or connection reset */ + listener->status = RAD_LISTEN_STATUS_EOL; ++ ++ /* ++ * Tell the event handler that an FD has disappeared. ++ */ ++ DEBUG("Home server %s port %d has closed connection", ++ ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), ++ packet->src_port); ++ + radius_update_listener(listener); ++ ++ /* ++ * Do NOT free the listener here. It's in use by ++ * a request, and will need to hang around until ++ * all of the requests are done. ++ * ++ * It is instead free'd in remove_from_request_hash() ++ */ + return 0; + } + ++ sock->packet = NULL; /* we have no need for more partial reads */ ++ + /* + * FIXME: Client MIB updates? + */ +@@ -2089,10 +2377,6 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) + return 0; + } + +- packet->src_ipaddr = sock->other_ipaddr; +- packet->src_port = sock->other_port; +- packet->dst_ipaddr = sock->my_ipaddr; +- packet->dst_port = sock->my_port; + + /* + * FIXME: Have it return an indication of packets that +@@ -2113,11 +2397,26 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) + #endif + #endif + ++#ifdef WITH_TLS ++#define TLS_UNUSED ++#else ++#define TLS_UNUSED UNUSED ++#endif + +-static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) ++static int client_socket_encode(TLS_UNUSED rad_listen_t *listener, REQUEST *request) + { ++#ifdef WITH_TLS ++ /* ++ * Don't encode fake packets. ++ */ ++ listen_socket_t *sock = listener->data; ++ if (sock->state == LISTEN_TLS_CHECKING) return 0; ++#endif ++ + if (!request->reply->code) return 0; + ++ if (request->reply->data) return 0; /* already encoded */ ++ + if (rad_encode(request->reply, request->packet, request->client->secret) < 0) { + RERROR("Failed encoding packet: %s", fr_strerror()); + +@@ -2142,7 +2441,7 @@ static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) + static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) + { + #ifdef WITH_TLS +- listen_socket_t *sock; ++ listen_socket_t *sock = request->listener->data; + #endif + + if (rad_verify(request->packet, NULL, +@@ -2151,9 +2450,6 @@ static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) + } + + #ifdef WITH_TLS +- sock = request->listener->data; +- rad_assert(sock != NULL); +- + /* + * FIXME: Add the rest of the TLS parameters, too? But + * how do we separate EAP-TLS parameters from RADIUS/TLS +@@ -2218,7 +2514,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + #ifdef WITH_STATS + { RLM_MODULE_INIT, "status", sizeof(listen_socket_t), NULL, + common_socket_parse, NULL, +- stats_socket_recv, auth_socket_send, ++ stats_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + #else + /* +@@ -2241,14 +2537,14 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + /* authentication */ + { RLM_MODULE_INIT, "auth", sizeof(listen_socket_t), NULL, + common_socket_parse, common_socket_free, +- auth_socket_recv, auth_socket_send, ++ auth_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + + #ifdef WITH_ACCOUNTING + /* accounting */ + { RLM_MODULE_INIT, "acct", sizeof(listen_socket_t), NULL, + common_socket_parse, common_socket_free, +- acct_socket_recv, acct_socket_send, ++ acct_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode}, + #else + { 0, "acct", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +@@ -2282,7 +2578,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { + /* Change of Authorization */ + { RLM_MODULE_INIT, "coa", sizeof(listen_socket_t), NULL, + common_socket_parse, NULL, +- coa_socket_recv, auth_socket_send, /* CoA packets are same as auth */ ++ coa_socket_recv, common_socket_send, + common_socket_print, client_socket_encode, client_socket_decode }, + #else + { 0, "coa", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +@@ -2755,6 +3051,9 @@ static int _listener_free(rad_listen_t *this) + + rad_assert(!sock->ssn || (talloc_parent(sock->ssn) == sock)); + rad_assert(!sock->request || (talloc_parent(sock->request) == sock)); ++ ++ if (sock->home && sock->home->listeners) (void) rbtree_deletebydata(sock->home->listeners, this); ++ + #ifdef HAVE_PTHREAD_H + pthread_mutex_destroy(&(sock->mutex)); + #endif +@@ -2780,8 +3079,16 @@ static rad_listen_t *listen_alloc(TALLOC_CTX *ctx, RAD_LISTEN_TYPE type) + this->recv = master_listen[this->type].recv; + this->send = master_listen[this->type].send; + this->print = master_listen[this->type].print; +- this->encode = master_listen[this->type].encode; +- this->decode = master_listen[this->type].decode; ++ ++ if (type != RAD_LISTEN_PROXY) { ++ this->encode = master_listen[this->type].encode; ++ this->decode = master_listen[this->type].decode; ++ } else { ++ this->send = NULL; /* proxy packets shouldn't call this! */ ++ this->proxy_send = master_listen[this->type].send; ++ this->proxy_encode = master_listen[this->type].encode; ++ this->proxy_decode = master_listen[this->type].decode; ++ } + + talloc_set_destructor(this, _listener_free); + +@@ -2806,7 +3113,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + + if (!home) return NULL; + +- rad_assert(home->server == NULL); /* we only open real sockets */ ++ rad_assert(home->virtual_server == NULL); /* we only open real sockets */ + + if ((home->limit.max_connections > 0) && + (home->limit.num_connections >= home->limit.max_connections)) { +@@ -2847,11 +3154,20 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + * FIXME: connect() is blocking! + * We do this with the proxy mutex locked, which may + * cause large delays! +- * +- * http://www.developerweb.net/forum/showthread.php?p=13486 + */ + this->fd = fr_socket_client_tcp(&home->src_ipaddr, +- &home->ipaddr, home->port, false); ++ &home->ipaddr, home->port, ++#ifdef WITH_TLS ++ !this->nonblock ++#else ++ false ++#endif ++ ); ++ ++ /* ++ * Set max_requests, lifetime, and idle_timeout from the home server. ++ */ ++ sock->limit = home->limit; + } else + #endif + this->fd = fr_socket(&home->src_ipaddr, src_port); +@@ -2869,17 +3185,74 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + #ifdef WITH_TCP + #ifdef WITH_TLS + if ((home->proto == IPPROTO_TCP) && home->tls) { +- DEBUG("Trying SSL to port %d\n", home->port); ++ DEBUG("(TLS) Trying new outgoing proxy connection to %s", buffer); ++ ++ /* ++ * Set SNI, if configured. ++ * ++ * The OpenSSL API says the filename is "char ++ * const *", but some versions have it as "void ++ * *", without the "const". So we un-const it ++ * here through various C magic. ++ */ ++ if (home->tls->client_hostname) { ++ (void) SSL_set_tlsext_host_name(sock->ssn->ssl, (void *) (uintptr_t) home->tls->client_hostname); ++ } ++ ++ this->nonblock |= home->nonblock; ++ ++ /* ++ * Set non-blocking if it's configured. ++ */ ++ if (this->nonblock) { ++ if (fr_nonblock(this->fd) < 0) { ++ ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); ++ goto error; ++ } ++ ++ rad_assert(home->listeners != NULL); ++ ++ if (!rbtree_insert(home->listeners, this)) { ++ ERROR("(TLS) Failed adding tracking informtion for proxy socket '%s'", buffer); ++ goto error; ++ } ++ ++#ifdef TCP_NODELAY ++ /* ++ * Also set TCP_NODELAY, to force the data to be written quickly. ++ */ ++ if (sock->proto == IPPROTO_TCP) { ++ int on = 1; ++ ++ if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { ++ ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); ++ goto error; ++ } ++ } ++#endif ++ } ++ ++ /* ++ * This is blocking. :( ++ */ + sock->ssn = tls_new_client_session(sock, home->tls, this->fd, &sock->certs); + if (!sock->ssn) { +- ERROR("Failed starting SSL to new proxy socket '%s'", buffer); +- home->last_failed_open = now; +- listen_free(&this); +- return NULL; ++ ERROR("(TLS) Failed opening connection on proxy socket '%s'", buffer); ++ goto error; + } + ++ sock->connect_timeout = home->connect_timeout; ++ + this->recv = proxy_tls_recv; +- this->send = proxy_tls_send; ++ this->proxy_send = proxy_tls_send; ++ ++#ifdef HAVE_PTHREAD_H ++ if (pthread_mutex_init(&sock->mutex, NULL) < 0) { ++ rad_assert(0 == 1); ++ listen_free(&this); ++ return 0; ++ } ++#endif + } + #endif + #endif +@@ -2895,6 +3268,8 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + &sizeof_src) < 0) { + ERROR("Failed getting socket name for '%s': %s", + buffer, fr_syserror(errno)); ++ error: ++ close(this->fd); + home->last_failed_open = now; + listen_free(&this); + return NULL; +@@ -2903,9 +3278,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t + if (!fr_sockaddr2ipaddr(&src, sizeof_src, + &sock->my_ipaddr, &sock->my_port)) { + ERROR("Socket has unsupported address family for '%s'", buffer); +- home->last_failed_open = now; +- listen_free(&this); +- return NULL; ++ goto error; + } + + this->print(this, buffer, sizeof(buffer)); +@@ -2966,6 +3339,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) + char const *value; + fr_dlhandle handle; + CONF_SECTION *server_cs; ++#ifdef WITH_TCP ++ char const *p; ++#endif + char buffer[32]; + + cp = cf_pair_find(cs, "type"); +@@ -3076,10 +3452,13 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) + + #ifdef WITH_TCP + /* +- * Special-case '+' for "auth+acct". ++ * Add special flags '+' for "auth+acct". + */ +- if (strchr(listen_type, '+') != NULL) { +- this->dual = true; ++ p = strchr(listen_type, '+'); ++ if (p) { ++ if (strncmp(p + 1, "acct", 4) == 0) { ++ this->dual = true; ++ } + } + #endif + +@@ -3115,7 +3494,6 @@ static void *recv_thread(void *arg) + + while (1) { + this->recv(this); +- DEBUG("%p", &this); + } + + return NULL; +@@ -3207,7 +3585,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) + if (override) { + cs = cf_section_sub_find_name2(config, "server", + main_config.name); +- if (cs) this->server = main_config.name; ++ if (!cs) cs = cf_section_sub_find_name2(config, "server", ++ "default"); ++ if (cs) this->server = cf_section_name2(cs); + } + + *last = this; +diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c +index e9dd412dee..957bff933f 100644 +--- a/src/main/mainconfig.c ++++ b/src/main/mainconfig.c +@@ -73,6 +73,8 @@ static char const *gid_name = NULL; + static char const *chroot_dir = NULL; + static bool allow_core_dumps = false; + static char const *radlog_dest = NULL; ++static char const *require_message_authenticator = NULL; ++static char const *limit_proxy_state = NULL; + + /* + * These are not used anywhere else.. +@@ -87,6 +89,52 @@ static bool do_colourise = false; + + static char const *radius_dir = NULL; //!< Path to raddb directory + ++static const FR_NAME_NUMBER fr_bool_auto_names[] = { ++ { "false", FR_BOOL_FALSE }, ++ { "no", FR_BOOL_FALSE }, ++ { "0", FR_BOOL_FALSE }, ++ ++ { "true", FR_BOOL_TRUE }, ++ { "yes", FR_BOOL_TRUE }, ++ { "1", FR_BOOL_TRUE }, ++ ++ { "auto", FR_BOOL_AUTO }, ++ ++ { NULL, 0 } ++}; ++ ++/* ++ * Get decent values for false / true / auto ++ */ ++int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str) ++{ ++ int value; ++ ++ /* ++ * Don't change anything. ++ */ ++ if (!str) return 0; ++ ++ value = fr_str2int(fr_bool_auto_names, str, -1); ++ if (value >= 0) { ++ *out = value; ++ return 0; ++ } ++ ++ /* ++ * This should never happen, as the defaults are in the ++ * source code. If there's no CONF_PAIR, and there's a ++ * parse error, then the source code is wrong. ++ */ ++ if (!cp) { ++ fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name); ++ return -1; ++ } ++ ++ cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp)); ++ return -1; ++} ++ + /********************************************************************** + * + * We need to figure out where the logs go, before doing anything +@@ -148,6 +196,7 @@ static const CONF_PARSER log_config[] = { + { "colourise",FR_CONF_POINTER(PW_TYPE_BOOLEAN, &do_colourise), NULL }, + { "use_utc", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &log_dates_utc), NULL }, + { "msg_denied", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.denied_msg), "You are already logged in - access denied" }, ++ { "suppress_secrets", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.suppress_secrets), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -159,6 +208,8 @@ static const CONF_PARSER security_config[] = { + { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) }, + { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) }, + { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"}, ++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"}, ++ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"}, + #ifdef ENABLE_OPENSSL_VERSION_CHECK + { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"}, + #endif +@@ -196,6 +247,7 @@ static const CONF_PARSER server_config[] = { + { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, + { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, + { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, ++ { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, + { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, + { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, + +@@ -499,14 +551,31 @@ static ssize_t xlat_listen_common(REQUEST *request, rad_listen_t *listen, + VALUE_PAIR *vp; + listen_socket_t *sock = listen->data; + ++ if (!listen->tls) { ++ RDEBUG("Listener is not using TLS. TLS attributes are not available"); ++ *out = '\0'; ++ return 0; ++ } ++ + for (vp = sock->certs; vp != NULL; vp = vp->next) { + if (strcmp(fmt, vp->da->name) == 0) { + return vp_prints_value(out, outlen, vp, 0); + } + } ++ ++ RDEBUG("Unknown TLS attribute \"%s\"", fmt); ++ *out = '\0'; ++ return 0; ++ } ++#else ++ if (strncmp(fmt, "TLS-", 4) == 0) { ++ RDEBUG("Server is not built with TLS support"); ++ *out = '\0'; ++ return 0; + } + #endif + ++ + cp = cf_pair_find(listen->cs, fmt); + if (!cp || !(value = cf_pair_value(cp))) { + RDEBUG("Listener does not contain config item \"%s\"", fmt); +@@ -560,7 +629,6 @@ static int switch_users(CONF_SECTION *cs) + * initialized. + */ + if (fr_set_dumpable_init() < 0) { +- fr_perror("%s", main_config.name); + return 0; + } + +@@ -571,8 +639,7 @@ static int switch_users(CONF_SECTION *cs) + if (rad_debug_lvl && (getuid() != 0)) return 1; + + if (cf_section_parse(cs, NULL, bootstrap_config) < 0) { +- fr_strerror_printf("%s: Error: Failed to parse user/group information.\n", +- main_config.name); ++ fr_strerror_printf("Failed to parse user/group information."); + return 0; + } + +@@ -587,8 +654,8 @@ static int switch_users(CONF_SECTION *cs) + + gr = getgrnam(gid_name); + if (!gr) { +- fr_strerror_printf("%s: Cannot get ID for group %s: %s\n", +- main_config.name, gid_name, fr_syserror(errno)); ++ fr_strerror_printf("Cannot get ID for group %s: %s", ++ gid_name, fr_syserror(errno)); + return 0; + } + +@@ -608,8 +675,8 @@ static int switch_users(CONF_SECTION *cs) + struct passwd *user; + + if (rad_getpwnam(cs, &user, uid_name) < 0) { +- fr_strerror_printf("%s: Cannot get passwd entry for user %s: %s\n", +- main_config.name, uid_name, fr_strerror()); ++ fr_strerror_printf("Cannot get passwd entry for user %s: %s", ++ uid_name, fr_strerror()); + return 0; + } + +@@ -621,8 +688,8 @@ static int switch_users(CONF_SECTION *cs) + do_suid = true; + #ifdef HAVE_INITGROUPS + if (initgroups(uid_name, server_gid) < 0) { +- fr_strerror_printf("%s: Cannot initialize supplementary group list for user %s: %s\n", +- main_config.name, uid_name, fr_syserror(errno)); ++ fr_strerror_printf("Cannot initialize supplementary group list for user %s: %s", ++ uid_name, fr_syserror(errno)); + talloc_free(user); + return 0; + } +@@ -637,8 +704,8 @@ static int switch_users(CONF_SECTION *cs) + */ + if (chroot_dir) { + if (chroot(chroot_dir) < 0) { +- fr_strerror_printf("%s: Failed to perform chroot %s: %s", +- main_config.name, chroot_dir, fr_syserror(errno)); ++ fr_strerror_printf("Failed to perform chroot to %s: %s", ++ chroot_dir, fr_syserror(errno)); + return 0; + } + +@@ -665,8 +732,8 @@ static int switch_users(CONF_SECTION *cs) + */ + if (do_sgid) { + if (setgid(server_gid) < 0){ +- fr_strerror_printf("%s: Failed setting group to %s: %s", +- main_config.name, gid_name, fr_syserror(errno)); ++ fr_strerror_printf("Failed setting group to %s: %s", ++ gid_name, fr_syserror(errno)); + return 0; + } + } +@@ -713,8 +780,8 @@ static int switch_users(CONF_SECTION *cs) + default_log.fd = open(main_config.log_file, + O_WRONLY | O_APPEND | O_CREAT, 0640); + if (default_log.fd < 0) { +- fr_strerror_printf("%s: Failed to open log file %s: %s\n", +- main_config.name, main_config.log_file, fr_syserror(errno)); ++ fr_strerror_printf("Failed to open log file %s: %s\n", ++ main_config.log_file, fr_syserror(errno)); + return 0; + } + } +@@ -730,8 +797,8 @@ static int switch_users(CONF_SECTION *cs) + if ((do_suid || do_sgid) && + (default_log.dst == L_DST_FILES)) { + if (fchown(default_log.fd, server_uid, server_gid) < 0) { +- fr_strerror_printf("%s: Cannot change ownership of log file %s: %s\n", +- main_config.name, main_config.log_file, fr_syserror(errno)); ++ fr_strerror_printf("Cannot change ownership of log file %s: %s\n", ++ main_config.log_file, fr_syserror(errno)); + return 0; + } + } +@@ -750,7 +817,7 @@ static int switch_users(CONF_SECTION *cs) + * aren't allowed. + */ + if (fr_set_dumpable(allow_core_dumps) < 0) { +- ERROR("%s", fr_strerror()); ++ WARN("Failed to allow core dumps - %s", fr_strerror()); + } + + if (allow_core_dumps) { +@@ -838,6 +905,8 @@ int main_config_init(void) + if (!main_config.dictionary_dir) { + main_config.dictionary_dir = DICTDIR; + } ++ main_config.require_ma = FR_BOOL_AUTO; ++ main_config.limit_proxy_state = FR_BOOL_AUTO; + + /* + * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400 +@@ -941,6 +1010,9 @@ do {\ + for (ci = cf_item_find_next(subcs, NULL); + ci != NULL; + ci = cf_item_find_next(subcs, ci)) { ++ ++ if (cf_item_is_data(ci)) continue; ++ + if (!cf_item_is_pair(ci)) { + cf_log_err(ci, "Unexpected item in ENV section"); + goto failure; +@@ -1066,7 +1138,10 @@ do {\ + /* + * Switch users as early as possible. + */ +- if (!switch_users(cs)) fr_exit(1); ++ if (!switch_users(cs)) { ++ fprintf(stderr, "%s: ERROR - %s\n", main_config.name, fr_strerror()); ++ fr_exit(1); ++ } + #endif + + /* +@@ -1127,6 +1202,23 @@ do {\ + main_config.init_delay.tv_sec = 0; + main_config.init_delay.tv_usec = 2* (1000000 / 3); + ++ { ++ CONF_PAIR *cp = NULL; ++ ++ subcs = cf_section_sub_find(cs, "security"); ++ if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator"); ++ if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) { ++ cf_file_free(cs); ++ return -1; ++ } ++ ++ if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state"); ++ if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) { ++ cf_file_free(cs); ++ return -1; ++ } ++ } ++ + /* + * Free the old configuration items, and replace them + * with the new ones. +diff --git a/src/main/map.c b/src/main/map.c +index 6275ba124d..17988d27f9 100644 +--- a/src/main/map.c ++++ b/src/main/map.c +@@ -245,6 +245,18 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, + } + break; + ++ case T_BARE_WORD: ++ /* ++ * Foo = %{...} ++ * ++ * Not allowed! ++ */ ++ if ((attr[0] == '%') && (attr[1] == '{')) { ++ cf_log_err_cp(cp, "Bare expansions are not permitted. They must be in a double-quoted string."); ++ goto error; ++ } ++ /* FALL-THROUGH */ ++ + default: + slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true); + if (slen <= 0) { +@@ -285,14 +297,20 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, + goto error; + } + +- /* +- * We cannot assign a count to an attribute. That must +- * be done in an xlat. +- */ +- if ((map->rhs->type == TMPL_TYPE_ATTR) && +- (map->rhs->tmpl_num == NUM_COUNT)) { +- cf_log_err_cp(cp, "Cannot assign from a count"); +- goto error; ++ if (map->rhs->type == TMPL_TYPE_ATTR) { ++ /* ++ * We cannot assign a count to an attribute. That must ++ * be done in an xlat. ++ */ ++ if (map->rhs->tmpl_num == NUM_COUNT) { ++ cf_log_err_cp(cp, "Cannot assign from a count"); ++ goto error; ++ } ++ ++ if (map->rhs->tmpl_da->flags.virtual) { ++ cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name); ++ goto error; ++ } + } + + VERIFY_MAP(map); +@@ -1091,6 +1109,12 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + */ + if (((map->lhs->tmpl_list == PAIR_LIST_COA) || + (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) { ++ if ((request->packet->code == PW_CODE_COA_REQUEST) || ++ (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) { ++ REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request. Use 'update request' instead."); ++ return -2; ++ } ++ + if (!request_alloc_coa(context)) { + REDEBUG("Failed to create a CoA/Disconnect Request message"); + return -2; +@@ -1173,10 +1197,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + rad_assert(map->rhs->type == TMPL_TYPE_EXEC); + /* FALL-THROUGH */ + case T_OP_ADD: +- fr_pair_list_move(parent, list, &head); ++ fr_pair_list_move(parent, list, &head, map->op); + fr_pair_list_free(&head); + } + goto finish; ++ case T_OP_PREPEND: ++ fr_pair_list_move(parent, list, &head, T_OP_PREPEND); ++ fr_pair_list_free(&head); ++ goto finish; + + default: + fr_pair_list_free(&head); +@@ -1341,6 +1369,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + fr_pair_list_free(&head); + break; + ++ /* ++ * ^= - Prepend src_list attributes to the destination ++ */ ++ case T_OP_PREPEND: ++ fr_pair_prepend(list, head); ++ head = NULL; ++ break; ++ + /* + * += - Add all src_list attributes to the destination + */ +diff --git a/src/main/process.c b/src/main/process.c +index 1a48517d43..2d20ebf552 100644 +--- a/src/main/process.c ++++ b/src/main/process.c +@@ -74,8 +74,14 @@ static char const *action_codes[] = { + "dup", + "timer", + #ifdef WITH_PROXY +- "proxy-reply" +-#endif ++ "proxy-reply", ++#endif ++ "request was cancelled", ++ "conflicting packet was received", ++ "max_time was reached", ++ "internal failure", ++ "cleanup_delay was reached", ++ "CoA packet was cancelled, and not sent", + }; + + #ifdef DEBUG_STATE_MACHINE +@@ -604,6 +610,46 @@ static void request_free(REQUEST *request) + + + #ifdef WITH_PROXY ++#ifdef WITH_TLS ++void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler) ++{ ++ PTHREAD_MUTEX_LOCK(&proxy_mutex); ++ if (!fr_packet_list_socket_freeze(proxy_list, ++ listener->fd)) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ listener->blocked = true; ++ ++ if (fr_event_fd_write_handler(el, 0, listener->fd, write_handler, listener) < 0) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++} ++ ++void proxy_listener_thaw(rad_listen_t *listener) ++{ ++ PTHREAD_MUTEX_LOCK(&proxy_mutex); ++ if (!fr_packet_list_socket_thaw(proxy_list, ++ listener->fd)) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ listener->blocked = false; ++ ++ if (fr_event_fd_write_handler(el, 0, listener->fd, NULL, listener) < 0) { ++ ERROR("Fatal error freezing socket: %s", fr_strerror()); ++ fr_exit(1); ++ } ++ ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++} ++#endif /* WITH_TLS */ ++ + static void proxy_reply_too_late(REQUEST *request) + { + char buffer[128]; +@@ -632,9 +678,10 @@ static void proxy_reply_too_late(REQUEST *request) + * } + * \enddot + */ +-static void request_done(REQUEST *request, int action) ++static void request_done(REQUEST *request, int original) + { + struct timeval now, when; ++ int action = original; + + VERIFY_REQUEST(request); + +@@ -690,7 +737,7 @@ static void request_done(REQUEST *request, int action) + home_server_t *home = request->home_pool->servers[i]; + + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + } + } +@@ -700,7 +747,7 @@ static void request_done(REQUEST *request, int action) + /* + * If it was administratively canceled, then it's done. + */ +- if (action == FR_ACTION_CANCELLED) { ++ if (action >= FR_ACTION_CANCELLED) { + action = FR_ACTION_DONE; + + #ifdef WITH_COA +@@ -887,9 +934,10 @@ static void request_done(REQUEST *request, int action) + #endif + + if (request->packet) { +- RDEBUG2("Cleaning up request packet ID %u with timestamp +%d", ++ RDEBUG2("Cleaning up request packet ID %u with timestamp +%d due to %s", + request->packet->id, +- (unsigned int) (request->timestamp - fr_start_time)); ++ (unsigned int) (request->timestamp - fr_start_time), ++ action_codes[original]); + } /* else don't print anything */ + + ASSERT_MASTER; +@@ -958,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) + #ifdef HAVE_PTHREAD_H + rad_assert(request->child_pid == NO_SUCH_CHILD_PID); + #endif ++ ++ /* ++ * Set the statistics immediately if we can. ++ */ ++ request_stats_final(request); ++ + STATE_MACHINE_TIMER(FR_ACTION_TIMER); + return; + } +@@ -994,8 +1048,7 @@ static bool request_max_time(REQUEST *request) + * stop" macro already took care of it. + */ + if (request->child_state == REQUEST_DONE) { +- done: +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_DONE); + return true; + } + +@@ -1027,7 +1080,8 @@ static bool request_max_time(REQUEST *request) + /* + * Tell the request that it's done. + */ +- goto done; ++ request_done(request, FR_ACTION_MAX_TIME); ++ return true; + } + + /* +@@ -1096,7 +1150,7 @@ static void request_queue_or_run(REQUEST *request, + * Otherwise we're not going to do anything with + * it... + */ +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_INTERNAL_FAILURE); + return; + } + #endif +@@ -1115,6 +1169,11 @@ static void request_queue_or_run(REQUEST *request, + #endif + } + ++void request_inject(REQUEST *request) ++{ ++ request_queue_or_run(request, request_running); ++} ++ + + static void request_dup(REQUEST *request) + { +@@ -1203,11 +1262,14 @@ static void request_cleanup_delay(REQUEST *request, int action) + #ifdef DEBUG_STATE_MACHINE + if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); + #endif ++ ++ request_stats_final(request); ++ + STATE_MACHINE_TIMER(FR_ACTION_TIMER); + return; + } /* else it's time to clean up */ + +- request_done(request, FR_ACTION_DONE); ++ request_done(request, FR_ACTION_CLEANUP_DELAY); + break; + + default: +@@ -1282,6 +1344,7 @@ static void request_response_delay(REQUEST *request, int action) + } /* else it's time to send the reject */ + + RDEBUG2("Sending delayed response"); ++ request->listener->encode(request->listener, request); + debug_packet(request, request->reply, false); + request->listener->send(request->listener, request); + +@@ -1327,7 +1390,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) + /* + * Ignore parse errors. + */ +- if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition)) { ++ if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition) == 1) { + request->log.lvl = L_DBG_LVL_2; + request->log.func = vradlog_request; + } +@@ -1340,7 +1403,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) + } + + if (rcode < 0) { +- RATE_LIMIT(INFO("Dropping packet without response because of error: %s", fr_strerror())); ++ RATE_LIMIT(INFO("Dropping packet without response because of error: %s (from client %s)", fr_strerror(), request->client->shortname)); + request->reply->offset = -2; /* bad authenticator */ + return 0; + } +@@ -1506,7 +1569,8 @@ static void request_finish(REQUEST *request, int action) + /* + * See if we need to delay an Access-Reject packet. + */ +- if ((request->reply->code == PW_CODE_ACCESS_REJECT) && ++ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && ++ (request->reply->code == PW_CODE_ACCESS_REJECT) && + (request->root->reject_delay.tv_sec > 0)) { + request->response_delay = request->root->reject_delay; + +@@ -1534,9 +1598,14 @@ static void request_finish(REQUEST *request, int action) + #ifdef WITH_PROXY + /* + * If we timed out a proxy packet, don't delay +- * the reject any more. ++ * the reject any more. Or, if we proxied it to ++ * a real home server, then don't delay it. ++ * ++ * We don't want to have each proxy in a chain ++ * adding their own reject delay, which would ++ * result in N*reject_delays being applied. + */ +- if (request->proxy && !request->proxy_reply) { ++ if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { + request->response_delay.tv_sec = 0; + request->response_delay.tv_usec = 0; + } +@@ -1560,6 +1629,7 @@ static void request_finish(REQUEST *request, int action) + } + } + ++ request->listener->encode(request->listener, request); + debug_packet(request, request->reply, false); + request->listener->send(request->listener, request); + } +@@ -1598,6 +1668,8 @@ static void request_finish(REQUEST *request, int action) + */ + static void request_running(REQUEST *request, int action) + { ++ int rcode; ++ + VERIFY_REQUEST(request); + + TRACE_STATE_MACHINE; +@@ -1631,7 +1703,8 @@ static void request_running(REQUEST *request, int action) + /* + * We may need to send a proxied request. + */ +- if (request_will_proxy(request)) { ++ rcode = request_will_proxy(request); ++ if (rcode == 1) { + #ifdef DEBUG_STATE_MACHINE + if (rad_debug_lvl) printf("(%u) ********\tWill Proxy\t********\n", request->number); + #endif +@@ -1641,13 +1714,42 @@ static void request_running(REQUEST *request, int action) + * up the post proxy fail + * handler. + */ ++ retry_proxy: + if (request_proxy(request) < 0) { +- if (request->home_server && request->home_server->server) goto req_finished; ++ if (request->home_server && request->home_server->virtual_server) goto req_finished; ++ ++ if (request->home_pool && request->home_server && ++ HOME_SERVER_IS_DEAD(request->home_server)) { ++ VALUE_PAIR *vp; ++ REALM *realm = NULL; ++ home_server_t *home = NULL; ++ ++ vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY); ++ if (vp) realm = realm_find2(vp->vp_strvalue); ++ ++ /* ++ * Since request->home_server is dead, ++ * this function won't pick the same home server as before. ++ */ ++ if (realm) home = home_server_ldb(vp->vp_strvalue, request->home_pool, request); ++ if (home) { ++ home_server_update_request(home, request); ++ goto retry_proxy; ++ } ++ } + + (void) setup_post_proxy_fail(request); + process_proxy_reply(request, NULL); + goto req_finished; + } ++ ++ } else if (rcode < 0) { ++ /* ++ * No live home servers, run Post-Proxy-Type Fail. ++ */ ++ (void) setup_post_proxy_fail(request); ++ process_proxy_reply(request, NULL); ++ goto req_finished; + } else + #endif + { +@@ -1762,7 +1864,7 @@ int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *pack + * the request just as we're logging the + * complaint. + */ +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_CONFLICT); + request = NULL; + + /* +@@ -1851,7 +1953,7 @@ skip_dup: + if (!listener->nodup) { + if (!rbtree_insert(pl, &request->packet)) { + RERROR("Failed to insert request in the list of live requests: discarding it"); +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_INTERNAL_FAILURE); + return 1; + } + +@@ -2016,23 +2118,7 @@ static void tcp_socket_timer(void *ctx) + + fr_event_now(el, &now); + +- switch (listener->type) { +-#ifdef WITH_PROXY +- case RAD_LISTEN_PROXY: +- limit = &sock->home->limit; +- break; +-#endif +- +- case RAD_LISTEN_AUTH: +-#ifdef WITH_ACCOUNTING +- case RAD_LISTEN_ACCT: +-#endif +- limit = &sock->limit; +- break; +- +- default: +- return; +- } ++ limit = &sock->limit; + + /* + * If we enforce a lifetime, do it now. +@@ -2069,6 +2155,16 @@ static void tcp_socket_timer(void *ctx) + * Mark the socket as "don't use if at all possible". + */ + listener->status = RAD_LISTEN_STATUS_FROZEN; ++ ++ /* ++ * If it's blocked, then push all of the requests to other sockets. ++ */ ++#ifdef WITH_TLS ++ if (listener->blocked) { ++ listener->status = RAD_LISTEN_STATUS_REMOVE_NOW; ++ } ++#endif ++ + event_new_fd(listener); + return; + } +@@ -2222,11 +2318,9 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) + } + } + +-#ifdef WITH_TCP + if (request->proxy_listener) { + request->proxy_listener->count--; + } +-#endif + request->proxy_listener = NULL; + + /* +@@ -2278,7 +2372,7 @@ static int insert_into_proxy_hash(REQUEST *request) + + + PTHREAD_MUTEX_LOCK(&proxy_mutex); +- proxy_listener = NULL; ++ proxy_listener = request->proxy_listener; /* may or may not be NULL */ + request->num_proxied_requests = 1; + request->num_proxied_responses = 0; + +@@ -2288,8 +2382,8 @@ static int insert_into_proxy_hash(REQUEST *request) + + RDEBUG3("proxy: Trying to allocate ID (%d/2)", tries); + success = fr_packet_list_id_alloc(proxy_list, +- request->home_server->proto, +- &request->proxy, &proxy_listener); ++ request->home_server->proto, ++ &request->proxy, &proxy_listener); + if (success) break; + + if (tries > 0) continue; /* try opening new socket only once */ +@@ -2298,6 +2392,16 @@ static int insert_into_proxy_hash(REQUEST *request) + if (proxy_no_new_sockets) break; + #endif + ++ /* ++ * Don't try to add a new listener if it's not possible. ++ */ ++ if (fr_event_list_full(el)) { ++ RATE_LIMIT(ERROR("Cannot open a new proxy socket - too many sockets are already open")); ++ request->home_server->state = HOME_STATE_CONNECTION_FAIL; ++ PTHREAD_MUTEX_UNLOCK(&proxy_mutex); ++ goto fail; ++ } ++ + RDEBUG3("proxy: Trying to open a new listener to the home server"); + this = proxy_new_listener(proxy_ctx, request->home_server, 0); + if (!this) { +@@ -2362,9 +2466,7 @@ static int insert_into_proxy_hash(REQUEST *request) + */ + request->home_server->currently_outstanding++; + +-#ifdef WITH_TCP + request->proxy_listener->count++; +-#endif + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + +@@ -2389,7 +2491,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + /* + * There may be a proxy reply, but it may be too late. + */ +- if ((request->home_server && !request->home_server->server) && !request->proxy_listener) return 0; ++ if ((request->home_server && !request->home_server->virtual_server) && !request->proxy_listener) return 0; + + /* + * Delete any reply we had accumulated until now. +@@ -2453,7 +2555,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + * Decode the packet if required. + */ + if (request->proxy_listener) { +- rcode = request->proxy_listener->decode(request->proxy_listener, request); ++ rcode = request->proxy_listener->proxy_decode(request->proxy_listener, request); + debug_packet(request, reply, true); + + /* +@@ -2473,19 +2575,18 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) + remove_from_proxy_hash(request); + } + +- old_server = request->server; + + /* +- * If the home server is virtual, just run pre_proxy from +- * that section. ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. + */ +- if (request->home_server && request->home_server->server) { +- request->server = request->home_server->server; ++ old_server = request->server; ++ if (request->home_server && request->home_server->virtual_server) { ++ request->server = request->home_server->virtual_server; + +- } else { +- if (request->home_pool && request->home_pool->virtual_server) { ++ } else if (request->home_pool && request->home_pool->virtual_server) { + request->server = request->home_pool->virtual_server; +- } + } + + /* +@@ -2598,11 +2699,79 @@ int request_proxy_reply(RADIUS_PACKET *packet) + * ignore it. This does the MD5 calculations in the + * server core, but I guess we can fix that later. + */ +- if (!request->proxy_reply && +- (rad_verify(packet, request->proxy, +- request->home_server->secret) != 0)) { +- DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); +- return 0; ++ if (!request->proxy_reply) { ++ decode_fail_t reason; ++ ++ /* ++ * If the home server configuration requires a Message-Authenticator, then set the flag, ++ * but only if the proxied packet is Access-Request or Status-Sercer. ++ * ++ * The realms.c file already clears require_ma for TLS connections. ++ */ ++ bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST); ++ ++ if (!request->home_server) { ++ proxy_reply_too_late(request); ++ return 0; ++ } ++ ++ if (!rad_packet_ok(packet, require_ma, &reason)) { ++ DEBUG("Ignoring invalid packet - %s", fr_strerror()); ++ return 0; ++ } ++ ++ if (rad_verify(packet, request->proxy, ++ request->home_server->secret) != 0) { ++ DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); ++ return 0; ++ } ++ ++ /* ++ * BlastRADIUS checks. We're running in the main ++ * listener thread, so there's no conflict ++ * checking or setting these fields. ++ */ ++ if ((request->proxy->code == PW_CODE_ACCESS_REQUEST) && ++#ifdef WITH_TLS ++ !request->home_server->tls && ++#endif ++ !packet->eap_message) { ++ if (request->home_server->require_ma == FR_BOOL_AUTO) { ++ if (!packet->message_authenticator) { ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator."); ++ RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name); ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name); ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ request->home_server->require_ma = FR_BOOL_FALSE; ++ } else { ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator."); ++ RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name); ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack."); ++ RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); ++ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ request->home_server->require_ma = FR_BOOL_TRUE; ++ } ++ ++ } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) { ++ /* ++ * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet. ++ */ ++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name); ++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ RDEBUG("The packet does not contain Message-Authenticator, which is a security issue"); ++ RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); ++ RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); ++ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++ } + } + + /* +@@ -2887,8 +3056,9 @@ static void proxy_running(REQUEST *request, int action) + * The key attributes are: + * - PW_PROXY_TO_REALM - Specifies a realm the request should be proxied to. + * - PW_HOME_SERVER_POOL - Specifies a specific home server pool to proxy to. +- * - PW_PACKET_DST_IP_ADDRESS - Specifies a specific IPv4 home server to proxy to. +- * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a specific IPv6 home server to proxy to. ++ * - PW_HOME_SERVER_NAME - Specifies a home server by name ++ * - PW_PACKET_DST_IP_ADDRESS - Specifies a home server by IPv4 address ++ * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a home server by IPv5 address + * + * Certain packet types such as #PW_CODE_STATUS_SERVER will never be proxied. + * +@@ -2918,7 +3088,9 @@ static int request_will_proxy(REQUEST *request) + + VERIFY_REQUEST(request); + +- if (!request->root->proxy_requests) return 0; ++ if (!request->root->proxy_requests) { ++ return 0; ++ } + if (request->packet->dst_port == 0) return 0; + if (request->packet->code == PW_CODE_STATUS_SERVER) return 0; + if (request->in_proxy_hash) return 0; +@@ -3052,7 +3224,55 @@ static int request_will_proxy(REQUEST *request) + * The home server is alive (or may be alive). + * Send the packet to the IP. + */ +- if (home->state < HOME_STATE_IS_DEAD) goto do_home; ++ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; ++ ++ /* ++ * The home server is dead. If you wanted ++ * fail-over, you should have proxied to a pool. ++ * Sucks to be you. ++ */ ++ ++ return 0; ++ ++ } else if ((vp = fr_pair_find_by_num(request->config, PW_HOME_SERVER_NAME, 0, TAG_ANY)) != NULL) { ++ int type; ++ ++ switch (request->packet->code) { ++ case PW_CODE_ACCESS_REQUEST: ++ type = HOME_TYPE_AUTH; ++ break; ++ ++#ifdef WITH_ACCOUNTING ++ case PW_CODE_ACCOUNTING_REQUEST: ++ type = HOME_TYPE_ACCT; ++ break; ++#endif ++ ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ case PW_CODE_DISCONNECT_REQUEST: ++ type = HOME_TYPE_COA; ++ break; ++#endif ++ ++ default: ++ return 0; ++ } ++ ++ /* ++ * Find the home server by name. ++ */ ++ home = home_server_byname(vp->vp_strvalue, type); ++ if (!home) { ++ RWDEBUG("No such home server %s", vp->vp_strvalue); ++ return 0; ++ } ++ ++ /* ++ * The home server is alive (or may be alive). ++ * Send the packet to the IP. ++ */ ++ if (!HOME_SERVER_IS_DEAD(home)) goto do_home; + + /* + * The home server is dead. If you wanted +@@ -3063,6 +3283,7 @@ static int request_will_proxy(REQUEST *request) + return 0; + + } else { ++ + return 0; + } + +@@ -3081,8 +3302,8 @@ static int request_will_proxy(REQUEST *request) + home = home_server_ldb(realmname, pool, request); + + if (!home) { +- REDEBUG2("Failed to find live home server: Cancelling proxy"); +- return 1; ++ REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); ++ return -1; + } + + do_home: +@@ -3093,7 +3314,11 @@ do_home: + * Once we've decided to proxy a request, we cannot send + * a CoA packet. So we free up any CoA packet here. + */ +- if (request->coa) request_done(request->coa, FR_ACTION_CANCELLED); ++ if (request->coa) { ++ RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); ++ request_done(request->coa, FR_ACTION_COA_CANCELLED); ++ request->coa = NULL; ++ } + #endif + + /* +@@ -3179,14 +3404,14 @@ do_home: + pre_proxy_type = vp->vp_integer; + } + +- old_server = request->server; +- + /* +- * If the home server is virtual, just run pre_proxy from +- * that section. ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. + */ +- if (request->home_server && request->home_server->server) { +- request->server = request->home_server->server; ++ old_server = request->server; ++ if (request->home_server && request->home_server->virtual_server) { ++ request->server = request->home_server->virtual_server; + + } else { + char buffer[128]; +@@ -3245,7 +3470,7 @@ static int proxy_to_virtual_server(REQUEST *request) + } + + DEBUG("Proxying to virtual server %s", +- request->home_server->server); ++ request->home_server->virtual_server); + + /* + * Packets to virtual servers don't get +@@ -3260,7 +3485,7 @@ static int proxy_to_virtual_server(REQUEST *request) + fake->packet->vps = fr_pair_list_copy(fake->packet, request->packet->vps); + talloc_free(request->proxy); + +- fake->server = request->home_server->server; ++ fake->server = request->home_server->virtual_server; + fake->handle = request->handle; + fake->process = NULL; /* should never be run for anything */ + +@@ -3309,7 +3534,8 @@ static int request_proxy(REQUEST *request) + #ifdef WITH_COA + if (request->coa) { + RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); +- request_done(request->coa, FR_ACTION_CANCELLED); ++ request_done(request->coa, FR_ACTION_COA_CANCELLED); ++ request->coa = NULL; + } + #endif + +@@ -3329,7 +3555,7 @@ static int request_proxy(REQUEST *request) + * + * So, we have some horrible hacks to get around that. + */ +- if (request->home_server->server) return proxy_to_virtual_server(request); ++ if (request->home_server->virtual_server) return proxy_to_virtual_server(request); + + /* + * We're actually sending a proxied packet. Do that now. +@@ -3372,7 +3598,7 @@ static int request_proxy(REQUEST *request) + /* + * Encode the packet before we do anything else. + */ +- request->proxy_listener->encode(request->proxy_listener, request); ++ request->proxy_listener->proxy_encode(request->proxy_listener, request); + debug_packet(request, request->proxy, false); + + /* +@@ -3392,7 +3618,7 @@ static int request_proxy(REQUEST *request) + /* + * And send the packet. + */ +- request->proxy_listener->send(request->proxy_listener, request); ++ request->proxy_listener->proxy_send(request->proxy_listener, request); + return 1; + } + +@@ -3458,7 +3684,7 @@ static int request_proxy_anew(REQUEST *request) + * If so, run that instead of doing proxying to a real + * server. + */ +- if (home->server) { ++ if (home->virtual_server) { + request->home_server = home; + TALLOC_FREE(request->proxy); + +@@ -3538,7 +3764,7 @@ static void request_ping(REQUEST *request, int action) + * + * If it's zombie, we mark it alive immediately. + */ +- if ((home->state >= HOME_STATE_IS_DEAD) && ++ if (HOME_SERVER_IS_DEAD(home) && + (home->num_received_pings < home->num_pings_to_alive)) { + return; + } +@@ -3610,7 +3836,7 @@ static void ping_home_server(void *ctx) + + if (timercmp(&when, &now, <)) { + DEBUG("PING: Zombie period is over for home server %s", home->log_name); +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + } + +@@ -3639,6 +3865,7 @@ static void ping_home_server(void *ctx) + NO_CHILD_THREAD; + + request->proxy = rad_alloc(request, true); ++ request->root = &main_config; + rad_assert(request->proxy != NULL); + + if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) { +@@ -3735,9 +3962,10 @@ static void ping_home_server(void *ctx) + home->num_sent_pings++; + + rad_assert(request->proxy_listener != NULL); ++ request->proxy_listener->proxy_encode(request->proxy_listener, request); + debug_packet(request, request->proxy, false); +- request->proxy_listener->send(request->proxy_listener, +- request); ++ request->proxy_listener->proxy_send(request->proxy_listener, ++ request); + + /* + * Add +/- 2s of jitter, as suggested in RFC 3539 +@@ -3840,7 +4068,34 @@ void revive_home_server(void *ctx) + home->port); + } + +-void mark_home_server_dead(home_server_t *home, struct timeval *when) ++#ifdef WITH_TLS ++static int eol_home_listener(UNUSED void *ctx, void *data) ++{ ++ rad_listen_t *this = talloc_get_type_abort(data, rad_listen_t); ++ ++ /* ++ * The socket isn't blocked, we can still use it. ++ * ++ * i.e. the home server is dead for a reason OTHER than ++ * "all available sockets are blocked". ++ * ++ * We can still ping the home server via sockets which ++ * are writable. ++ */ ++ if (!this->blocked) return 0; ++ ++ this->status = RAD_LISTEN_STATUS_EOL; ++ ++ FD_MUTEX_LOCK(&fd_mutex); ++ this->next = new_listeners; ++ new_listeners = this; ++ FD_MUTEX_UNLOCK(&fd_mutex); ++ ++ return 1; /* alway delete from this tree */ ++} ++#endif ++ ++void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down) + { + int previous_state = home->state; + char buffer[128]; +@@ -3853,6 +4108,34 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when) + home->state = HOME_STATE_IS_DEAD; + home_trigger(home, "home_server.dead"); + ++#ifdef WITH_TLS ++ /* ++ * If the home server is dead, then close all of the sockets associated with it. ++ * ++ * Note that the "EOL listener" code expects to _also_ ++ * delete the listeners. At which point we end up with a ++ * mutex locked twice, and bad things happen. The ++ * solution is to move the listeners to the global ++ * "waiting for update" list, and then notify ourselves ++ * that there are listeners waiting to be updated. ++ */ ++ if (home->listeners) { ++ ASSERT_MASTER; ++ ++ rbtree_walk(home->listeners, RBTREE_DELETE_ORDER, eol_home_listener, NULL); ++ radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD); ++ } ++#endif ++ ++ /* ++ * Administratively down - don't do anything to bring it ++ * up. ++ */ ++ if (down) { ++ home->state = HOME_STATE_ADMIN_DOWN; ++ return; ++ } ++ + /* + * Ping it if configured, AND we can ping it. + */ +@@ -3925,14 +4208,14 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * The request was proxied to a virtual server. + * Ignore the retransmit. + */ +- if (request->home_server->server) return; ++ if (request->home_server->virtual_server) return; + + /* + * Failed connections get the home server marked + * as dead. + */ + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + + /* +@@ -3948,7 +4231,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * If the listener is known or frozen, use it for + * retransmits. + */ +- if ((home->state >= HOME_STATE_IS_DEAD) || ++ if (HOME_SERVER_IS_DEAD(home) || + !request->proxy_listener || + (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { + request_proxy_anew(request); +@@ -3980,7 +4263,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + when.tv_sec++; + + if (timercmp(&now, &when, <)) { +- DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", ++ DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), +@@ -4014,7 +4297,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + home->last_packet_sent = now.tv_sec; + request->proxy->timestamp = now; + debug_packet(request, request->proxy, false); +- request->proxy_listener->send(request->proxy_listener, request); ++ request->proxy_listener->proxy_send(request->proxy_listener, request); + break; + + case FR_ACTION_TIMER: +@@ -4023,7 +4306,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) + * as dead. + */ + if (home->state == HOME_STATE_CONNECTION_FAIL) { +- mark_home_server_dead(home, &now); ++ mark_home_server_dead(home, &now, false); + } + + response_window = request_response_window(request); +@@ -4175,6 +4458,7 @@ static void request_coa_originate(REQUEST *request) + VALUE_PAIR *vp; + REQUEST *coa; + fr_ipaddr_t ipaddr; ++ char const *old_server; + char buffer[256]; + + VERIFY_REQUEST(request); +@@ -4202,7 +4486,7 @@ static void request_coa_originate(REQUEST *request) + + if (!main_config.proxy_requests) { + RWDEBUG("Cannot originate CoA packets unless 'proxy_requests = yes'"); +- TALLOC_FREE(request->coa); ++ TALLOC_FREE(request->coa); + return; + } + +@@ -4234,11 +4518,11 @@ static void request_coa_originate(REQUEST *request) + /* + * Prefer the pool to one server + */ +- } else if (request->client->coa_pool) { +- coa->home_pool = request->client->coa_pool; ++ } else if (request->client->coa_home_pool) { ++ coa->home_pool = request->client->coa_home_pool; + +- } else if (request->client->coa_server) { +- coa->home_server = request->client->coa_server; ++ } else if (request->client->coa_home_server) { ++ coa->home_server = request->client->coa_home_server; + + } else { + /* +@@ -4318,19 +4602,26 @@ static void request_coa_originate(REQUEST *request) + pre_proxy_type = vp->vp_integer; + } + +- if (coa->home_pool && coa->home_pool->virtual_server) { +- char const *old_server = coa->server; ++ /* ++ * Run the request through the virtual server for the ++ * home server, OR through the virtual server for the ++ * home server pool. ++ */ ++ old_server = request->server; ++ if (coa->home_server && coa->home_server->virtual_server) { ++ coa->server = coa->home_server->virtual_server; + ++ } else if (coa->home_pool && coa->home_pool->virtual_server) { + coa->server = coa->home_pool->virtual_server; +- RDEBUG2("server %s {", coa->server); +- RINDENT(); +- rcode = process_pre_proxy(pre_proxy_type, coa); +- REXDENT(); +- RDEBUG2("}"); +- coa->server = old_server; +- } else { +- rcode = process_pre_proxy(pre_proxy_type, coa); + } ++ ++ RDEBUG2("server %s {", coa->server); ++ RINDENT(); ++ rcode = process_pre_proxy(pre_proxy_type, coa); ++ REXDENT(); ++ RDEBUG2("}"); ++ coa->server = old_server; ++ + switch (rcode) { + default: + goto fail; +@@ -4379,7 +4670,7 @@ static void request_coa_originate(REQUEST *request) + /* + * Encode the packet before we do anything else. + */ +- coa->proxy_listener->encode(coa->proxy_listener, coa); ++ coa->proxy_listener->proxy_encode(coa->proxy_listener, coa); + debug_packet(coa, coa->proxy, false); + + #ifdef DEBUG_STATE_MACHINE +@@ -4404,7 +4695,7 @@ static void request_coa_originate(REQUEST *request) + /* + * And send the packet. + */ +- coa->proxy_listener->send(coa->proxy_listener, coa); ++ coa->proxy_listener->proxy_send(coa->proxy_listener, coa); + } + + +@@ -4420,11 +4711,11 @@ static void coa_retransmit(REQUEST *request) + * Don't do fail-over. This is a 3.1 feature. + */ + if (!request->home_server || +- (request->home_server->state >= HOME_STATE_IS_DEAD) || ++ HOME_SERVER_IS_DEAD(request->home_server) || + request->proxy_reply || + !request->proxy_listener || + (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_COA_CANCELLED); + return; + } + +@@ -4548,8 +4839,8 @@ static void coa_retransmit(REQUEST *request) + request->proxy->dst_port, + request->proxy->id); + +- request->proxy_listener->send(request->proxy_listener, +- request); ++ request->proxy_listener->proxy_send(request->proxy_listener, ++ request); + } + + +@@ -4579,7 +4870,7 @@ static bool coa_max_time(REQUEST *request) + */ + if (request->child_state == REQUEST_DONE) { + done: +- request_done(request, FR_ACTION_CANCELLED); ++ request_done(request, FR_ACTION_MAX_TIME); + return true; + } + +@@ -4615,7 +4906,8 @@ static bool coa_max_time(REQUEST *request) + buffer, sizeof(buffer)), + request->proxy->dst_port, + mrd); +- goto done; ++ request_done(request, FR_ACTION_DONE); ++ return true; + } + + #ifdef HAVE_PTHREAD_H +@@ -4948,15 +5240,14 @@ static void event_status(struct timeval *wake) + } + } + +-#ifdef WITH_TCP + static void listener_free_cb(void *ctx) + { + rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t); ++ listen_socket_t *sock = this->data; + char buffer[1024]; + + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + fr_event_now(el, &when); + when.tv_sec += 3; +@@ -4977,9 +5268,13 @@ static void listener_free_cb(void *ctx) + this->print(this, buffer, sizeof(buffer)); + DEBUG("... cleaning up socket %s", buffer); + rad_assert(this->next == NULL); ++#ifdef WITH_TCP ++ fr_event_delete(el, &sock->ev); ++#endif + talloc_free(this); + } + ++#ifdef WITH_TCP + #ifdef WITH_PROXY + static int proxy_eol_cb(void *ctx, void *data) + { +@@ -5024,6 +5319,7 @@ static int proxy_eol_cb(void *ctx, void *data) + static void event_new_fd(rad_listen_t *this) + { + char buffer[1024]; ++ listen_socket_t *sock = NULL; + + ASSERT_MASTER; + +@@ -5031,29 +5327,28 @@ static void event_new_fd(rad_listen_t *this) + + this->print(this, buffer, sizeof(buffer)); + +- if (this->status == RAD_LISTEN_STATUS_INIT) { +- listen_socket_t *sock = this->data; +- ++ if (this->type != RAD_LISTEN_DETAIL) { ++ sock = this->data; + rad_assert(sock != NULL); ++ } ++ ++ if (this->status == RAD_LISTEN_STATUS_INIT) { + if (just_started) { + DEBUG("Listening on %s", buffer); +- } else { +- INFO(" ... adding new socket %s", buffer); +- } + + #ifdef WITH_PROXY +- if (!just_started && (this->type == RAD_LISTEN_PROXY)) { +- home_server_t *home; +- +- home = sock->home; +- if (!home || !home->limit.max_connections) { +- INFO(" ... adding new socket %s", buffer); +- } else { ++ } else if (this->type == RAD_LISTEN_PROXY) { ++ home_server_t *home = sock->home; ++ ++ if (home && home->limit.max_connections) { + INFO(" ... adding new socket %s (%u of %u)", buffer, + home->limit.num_connections, home->limit.max_connections); ++ } else { ++ INFO(" ... adding new socket %s", buffer); + } +- + #endif ++ } else { ++ INFO(" ... adding new socket %s", buffer); + } + + switch (this->type) { +@@ -5084,6 +5379,7 @@ static void event_new_fd(rad_listen_t *this) + */ + case RAD_LISTEN_PROXY: + #ifdef WITH_TCP ++ rad_assert(sock != NULL); + rad_assert((sock->proto == IPPROTO_UDP) || (sock->home != NULL)); + + /* +@@ -5102,6 +5398,19 @@ static void event_new_fd(rad_listen_t *this) + rad_panic("Failed to insert event"); + } + } ++ ++ /* ++ * Run a callback to do any specific ++ * signalling on "connection up". ++ * ++ * For TLS sockets and WITH_COA_TUNNEL, ++ * this function should be similar to ++ * ping_home_server(), except that it ++ * should send a Status-Server packet, ++ * with Originating-Realm-Key as a VSA. ++ */ ++// process_listener_up(this); ++ + #endif /* WITH_TCP */ + break; + #endif /* WITH_PROXY */ +@@ -5136,16 +5445,35 @@ static void event_new_fd(rad_listen_t *this) + /* + * All sockets: add the FD to the event handler. + */ ++ insert_fd: + if (fr_event_fd_insert(el, 0, this->fd, + event_socket_handler, this)) { + this->status = RAD_LISTEN_STATUS_KNOWN; + return; + } + +- ERROR("Failed adding event handler for socket: %s", fr_strerror()); +- this->status = RAD_LISTEN_STATUS_REMOVE_NOW; ++ /* ++ * Print out which socket failed. ++ * ++ * If we're trying to add the socket, then ++ * forcibly remove it immediately, without any ++ * additional cleanups. There cannot, and MUST ++ * NOT be any packets associated with the socket. ++ */ ++ this->print(this, buffer, sizeof(buffer)); ++ ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); ++ ++ this->status = RAD_LISTEN_STATUS_EOL; ++ goto listener_is_eol; + } /* end of INIT */ + ++ if (this->status == RAD_LISTEN_STATUS_PAUSE) { ++ fr_event_fd_delete(el, 0, this->fd); ++ return; ++ } ++ ++ if (this->status == RAD_LISTEN_STATUS_RESUME) goto insert_fd; ++ + #ifdef WITH_TCP + /* + * The socket has reached a timeout. Try to close it. +@@ -5157,7 +5485,6 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + /* + * Try again to clean up the socket in 30 +@@ -5179,6 +5506,7 @@ static void event_new_fd(rad_listen_t *this) + fr_event_fd_delete(el, 0, this->fd); + this->status = RAD_LISTEN_STATUS_REMOVE_NOW; + } ++#endif /* WITH_TCP */ + + /* + * The socket has had a catastrophic error. Close it. +@@ -5189,6 +5517,7 @@ static void event_new_fd(rad_listen_t *this) + */ + fr_event_fd_delete(el, 0, this->fd); + ++ listener_is_eol: + #ifdef WITH_PROXY + /* + * Tell all requests using this socket that the socket is dead. +@@ -5214,7 +5543,6 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->count > 0) { + struct timeval when; +- listen_socket_t *sock = this->data; + + /* + * Try again to clean up the socket in 30 +@@ -5238,17 +5566,16 @@ static void event_new_fd(rad_listen_t *this) + */ + this->status = RAD_LISTEN_STATUS_REMOVE_NOW; + } /* socket is at EOL */ +-#endif /* WITH_TCP */ ++ ++ if (this->dead) goto wait_some_more; + + /* + * Nuke the socket. + */ + if (this->status == RAD_LISTEN_STATUS_REMOVE_NOW) { + int devnull; +-#ifdef WITH_TCP +- listen_socket_t *sock = this->data; +- struct timeval when; +-#endif ++ ++ this->dead = true; + + /* + * Re-open the socket, pointing it to /dev/null. +@@ -5290,6 +5617,7 @@ static void event_new_fd(rad_listen_t *this) + */ + if (this->type == RAD_LISTEN_PROXY) { + home_server_t *home; ++ sock = this->data; + + home = sock->home; + if (!home || !home->limit.max_connections) { +@@ -5307,6 +5635,20 @@ static void event_new_fd(rad_listen_t *this) + buffer, fr_strerror()); + fr_exit(1); + } ++ ++#ifdef WITH_TLS ++ /* ++ * Remove this socket from the list of sockets assocated with this home server. ++ * ++ * This MUST be done with the proxy mutex locked! ++ */ ++ if (home && home->tls) { ++ fr_assert(home->listeners); ++ ++ (void) rbtree_deletebydata(home->listeners, this); ++ } ++#endif ++ + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + } else + #endif /* WITH_PROXY */ +@@ -5324,7 +5666,10 @@ static void event_new_fd(rad_listen_t *this) + */ + if (!spawn_flag) { + ASSERT_MASTER; +- if (sock->ev) fr_event_delete(el, &sock->ev); ++ ++ if (this->type != RAD_LISTEN_DETAIL && sock && sock->ev) { ++ fr_event_delete(el, &sock->ev); ++ } + listen_free(&this); + return; + } +@@ -5332,14 +5677,8 @@ static void event_new_fd(rad_listen_t *this) + /* + * Wait until all requests using this socket are done. + */ +- gettimeofday(&when, NULL); +- when.tv_sec += 3; +- +- ASSERT_MASTER; +- if (!fr_event_insert(el, listener_free_cb, this, &when, +- &(sock->ev))) { +- rad_panic("Failed to insert event"); +- } ++ wait_some_more: ++ listener_free_cb(this); + #endif /* WITH_TCP */ + } + +@@ -5603,7 +5942,10 @@ static void check_proxy(rad_listen_t *head) + rad_listen_t *this; + + if (check_config) return; +- if (!main_config.proxy_requests) return; ++ if (!main_config.proxy_requests) { ++ DEBUG3("Cannot proxy packets unless 'proxy_requests = yes'"); ++ return; ++ } + if (!head) return; + #ifdef WITH_TCP + if (!home_servers_udp) return; +diff --git a/src/main/radclient.c b/src/main/radclient.c +index 52d2872b13..b48d97235e 100644 +--- a/src/main/radclient.c ++++ b/src/main/radclient.c +@@ -28,6 +28,10 @@ RCSID("$Id$") + #include <freeradius-devel/radpaths.h> + #include <freeradius-devel/udpfromto.h> + #include <freeradius-devel/conf.h> ++#ifdef HAVE_OPENSSL_SSL_H ++#include <openssl/ssl.h> ++#include <freeradius-devel/openssl3.h> ++#endif + #include <ctype.h> + + #ifdef HAVE_GETOPT_H +@@ -36,6 +40,8 @@ RCSID("$Id$") + + #include <assert.h> + ++USES_APPLE_DEPRECATED_API ++ + typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */ + + #include "smbdes.h" +@@ -54,6 +60,7 @@ static fr_ipaddr_t server_ipaddr; + static int resend_count = 1; + static bool done = true; + static bool print_filename = false; ++static bool blast_radius = false; + + static fr_ipaddr_t client_ipaddr; + static uint16_t client_port = 0; +@@ -89,6 +96,7 @@ static void NEVER_RETURNS usage(void) + fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n"); + fprintf(stderr, " -4 Use IPv4 address of server\n"); + fprintf(stderr, " -6 Use IPv6 address of server.\n"); ++ fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n"); + fprintf(stderr, " -c <count> Send each packet 'count' times.\n"); + fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n"); + fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n"); +@@ -155,9 +163,60 @@ static int _rc_request_free(rc_request_t *request) + return 0; + } + ++#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L ++# include <openssl/provider.h> ++ ++static OSSL_PROVIDER *openssl_default_provider = NULL; ++static OSSL_PROVIDER *openssl_legacy_provider = NULL; ++ ++static int openssl3_init(void) ++{ ++ /* ++ * Load the default provider for most algorithms ++ */ ++ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); ++ if (!openssl_default_provider) { ++ ERROR("(TLS) Failed loading default provider"); ++ return -1; ++ } ++ ++ /* ++ * Needed for MD4 ++ * ++ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms ++ */ ++ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); ++ if (!openssl_legacy_provider) { ++ ERROR("(TLS) Failed loading legacy provider"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static void openssl3_free(void) ++{ ++ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { ++ ERROR("Failed unloading default provider"); ++ } ++ openssl_default_provider = NULL; ++ ++ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { ++ ERROR("Failed unloading legacy provider"); ++ } ++ openssl_legacy_provider = NULL; ++} ++#else ++#define openssl3_init() ++#define openssl3_free() ++#endif ++ ++ ++ + static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, + char const *password) + { ++ int rcode; + unsigned int i; + uint8_t *p; + VALUE_PAIR *challenge, *reply; +@@ -190,9 +249,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, + + p[1] = 0x01; /* NT hash */ + +- if (mschap_ntpwdhash(nthash, password) < 0) { +- return 0; +- } ++ rcode = mschap_ntpwdhash(nthash, password); ++ if (rcode < 0) return 0; + + smbdes_mschap(nthash, challenge->vp_octets, p + 26); + return 1; +@@ -960,8 +1018,8 @@ static int send_one_packet(rc_request_t *request) + */ + fr_packet_list_yank(pl, request->packet); + +- REDEBUG("No reply from server for ID %d socket %d", +- request->packet->id, request->packet->sockfd); ++ RDEBUG("No reply from server for ID %d socket %d", ++ request->packet->id, request->packet->sockfd); + deallocate_id(request); + + /* +@@ -1000,6 +1058,130 @@ static int send_one_packet(rc_request_t *request) + return 0; + } + ++/* ++ * Do Blast RADIUS checks. ++ * ++ * The request is an Access-Request, and does NOT contain Proxy-State. ++ * ++ * The reply is a raw packet, and is NOT yet decoded. ++ */ ++static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply) ++{ ++ uint8_t *attr, *end; ++ VALUE_PAIR *vp; ++ bool have_message_authenticator = false; ++ ++ /* ++ * We've received a raw packet. Nothing has (as of yet) checked ++ * anything in it other than the length, and that it's a ++ * well-formed RADIUS packet. ++ */ ++ switch (reply->data[0]) { ++ case PW_CODE_ACCESS_ACCEPT: ++ case PW_CODE_ACCESS_REJECT: ++ case PW_CODE_ACCESS_CHALLENGE: ++ if (reply->data[1] != request->packet->id) { ++ ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id); ++ return -1; ++ } ++ break; ++ ++ default: ++ ERROR("Invalid reply code %d to Access-Request", reply->data[0]); ++ return -1; ++ } ++ ++ /* ++ * If the reply has a Message-Authenticator, then it MIGHT be fine. ++ */ ++ attr = reply->data + 20; ++ end = reply->data + reply->data_len; ++ ++ /* ++ * It should be the first attribute, so we warn if it isn't there. ++ * ++ * But it's not a fatal error. ++ */ ++ if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) { ++ RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.", ++ fr_packet_codes[reply->data[0]]); ++ } ++ ++ /* ++ * Set up for Proxy-State checks. ++ * ++ * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack. ++ */ ++ vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY); ++ ++ while (attr < end) { ++ /* ++ * Blast RADIUS work-arounds require that ++ * Message-Authenticator is the first attribute in the ++ * reply. Note that we don't check for it being the ++ * first attribute, but simply that it exists. ++ * ++ * That check is a balance between securing the reply ++ * packet from attacks, and not violating the RFCs which ++ * say that there is no order to attributes in the ++ * packet. ++ * ++ * However, no matter the status of the '-b' flag we ++ * still can check for the signature of the attack, and ++ * discard packets which are suspicious. This behavior ++ * protects radclient from the attack, without mandating ++ * new behavior on the server side. ++ * ++ * Note that we don't set the '-b' flag by default. ++ * radclient is intended for testing / debugging, and is ++ * not intended to be used as part of a secure login / ++ * user checking system. ++ */ ++ if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { ++ have_message_authenticator = true; ++ goto next; ++ } ++ ++ /* ++ * If there are Proxy-State attributes in the reply, they must ++ * match EXACTLY the Proxy-State attributes in the request. ++ * ++ * Note that we don't care if there are more Proxy-States ++ * in the request than in the reply. The Blast RADIUS ++ * issue requires _adding_ Proxy-State attributes, and ++ * cannot work when the server _deletes_ Proxy-State ++ * attributes. ++ */ ++ if (attr[0] == PW_PROXY_STATE) { ++ if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) { ++ ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id); ++ ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent."); ++ return -1; ++ } ++ ++ vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY); ++ } ++ ++ next: ++ attr += attr[1]; ++ } ++ ++ /* ++ * If "-b" is set, then we require Message-Authenticator in the reply. ++ */ ++ if (blast_radius && !have_message_authenticator) { ++ ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.", ++ fr_packet_codes[reply->data[0]]); ++ return -1; ++ } ++ ++ /* ++ * The packet doesn't look like it's a Blast RADIUS attack. The ++ * caller will now verify the packet signature. ++ */ ++ return 0; ++} ++ + /* + * Receive one packet, maybe. + */ +@@ -1051,6 +1233,20 @@ static int recv_one_packet(int wait_time) + } + request = fr_packet2myptr(rc_request_t, packet, packet_p); + ++ /* ++ * We want radclient to be able to send any packet, including ++ * imperfect ones. However, we do NOT want to be vulnerable to ++ * the "Blast RADIUS" issue. Instead of adding command-line ++ * flags to enable/disable similar flags to what the server ++ * sends, we just do a few more smart checks to double-check ++ * things. ++ */ ++ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && ++ blast_radius_check(request, reply) < 0) { ++ rad_free(&reply); ++ return -1; ++ } ++ + /* + * Fails the signature validation: not a real reply. + * FIXME: Silently drop it and listen for another packet. +@@ -1146,6 +1342,7 @@ packet_done: + return 0; + } + ++DIAG_OFF(deprecated-declarations) + int main(int argc, char **argv) + { + int c; +@@ -1183,7 +1380,7 @@ int main(int argc, char **argv) + exit(1); + } + +- while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx" ++ while ((c = getopt(argc, argv, "46bc:d:D:f:Fhn:p:qr:sS:t:vx" + #ifdef WITH_TCP + "P:" + #endif +@@ -1196,10 +1393,16 @@ int main(int argc, char **argv) + force_af = AF_INET6; + break; + ++ case 'b': ++ blast_radius = true; ++ break; ++ + case 'c': +- if (!isdigit((int) *optarg)) +- usage(); ++ if (!isdigit((uint8_t) *optarg)) usage(); ++ + resend_count = atoi(optarg); ++ ++ if (resend_count < 1) usage(); + break; + + case 'D': +@@ -1274,7 +1477,7 @@ int main(int argc, char **argv) + break; + + case 'r': +- if (!isdigit((int) *optarg)) usage(); ++ if (!isdigit((uint8_t) *optarg)) usage(); + retries = atoi(optarg); + if ((retries == 0) || (retries > 1000)) usage(); + break; +@@ -1314,7 +1517,7 @@ int main(int argc, char **argv) + break; + + case 't': +- if (!isdigit((int) *optarg)) ++ if (!isdigit((uint8_t) *optarg)) + usage(); + timeout = atof(optarg); + break; +@@ -1361,7 +1564,7 @@ int main(int argc, char **argv) + /* + * Get the request type + */ +- if (!isdigit((int) argv[2][0])) { ++ if (!isdigit((uint8_t) argv[2][0])) { + packet_code = fr_str2int(request_types, argv[2], -2); + if (packet_code == -2) { + ERROR("Unrecognised request type \"%s\"", argv[2]); +@@ -1421,6 +1624,8 @@ int main(int argc, char **argv) + exit(1); + } + ++ openssl3_init(); ++ + /* + * Bind to the first specified IP address and port. + * This means we ignore later ones. +@@ -1637,5 +1842,9 @@ int main(int argc, char **argv) + if ((stats.lost > 0) || (stats.failed > 0)) { + exit(1); + } ++ ++ openssl3_free(); ++ + exit(0); + } ++DIAG_ON(deprecated-declarations) +diff --git a/src/main/radiusd.c b/src/main/radiusd.c +index 9739514509..6cece08893 100644 +--- a/src/main/radiusd.c ++++ b/src/main/radiusd.c +@@ -710,6 +710,7 @@ cleanup: + if (main_config.memory_report) { + INFO("Allocated memory at time of report:"); + fr_log_talloc_report(NULL); ++ talloc_disable_null_tracking(); + } + + return rcode; +diff --git a/src/main/radtest.in b/src/main/radtest.in +index 38b1ba9a0f..af9df80eec 100644 +--- a/src/main/radtest.in ++++ b/src/main/radtest.in +@@ -19,6 +19,7 @@ usage() { + echo " -x Enable debug output" >&2 + echo " -4 Use IPv4 for the NAS address (default)" >&2 + echo " -6 Use IPv6 for the NAS address" >&2 ++ echo " -6 Mandate checks for Blast RADIUS (this is not set by default)." >&2 + exit 1 + } + +@@ -55,6 +56,10 @@ do + NAS_ADDR_ATTR="NAS-IPv6-Address" + shift + ;; ++ -b) ++ OPTIONS="$OPTIONS -b" ++ shift ++ ;; + -d) + OPTIONS="$OPTIONS -d $2" + shift;shift +@@ -112,7 +117,7 @@ if [ "$7" ] + then + nas=$7 + else +- nas=`hostname` ++ nas=`(hostname || uname -n) 2>/dev/null | sed 1q` + fi + + ( +@@ -120,7 +125,6 @@ fi + echo "$PASSWORD = \"$2\"" + echo "$NAS_ADDR_ATTR = $nas" + echo "NAS-Port = $4" +- echo "Message-Authenticator = 0x00" + if [ "$radclient" = "$radeapclient" ] + then + echo "EAP-Code = Response" +diff --git a/src/main/realms.c b/src/main/realms.c +index eb42598116..3f2c37270c 100644 +--- a/src/main/realms.c ++++ b/src/main/realms.c +@@ -37,6 +37,10 @@ static rbtree_t *realms_byname = NULL; + bool home_servers_udp = false; + #endif + ++#ifdef HAVE_DIRENT_H ++#include <dirent.h> ++#endif ++ + #ifdef HAVE_REGEX + typedef struct realm_regex realm_regex_t; + +@@ -53,6 +57,9 @@ static realm_regex_t *realms_regex = NULL; + + struct realm_config { + CONF_SECTION *cs; ++#ifdef HAVE_DIRENT_H ++ char const *directory; ++#endif + uint32_t dead_time; + uint32_t retry_count; + uint32_t retry_delay; +@@ -89,7 +96,7 @@ static realm_config_t *realm_config = NULL; + static rbtree_t *home_servers_byaddr = NULL; + static rbtree_t *home_servers_byname = NULL; + #ifdef WITH_STATS +-static int home_server_max_number = 0; ++int home_server_max_number = 0; + static rbtree_t *home_servers_bynumber = NULL; + #endif + +@@ -107,6 +114,10 @@ static const CONF_PARSER proxy_config[] = { + + { "dynamic", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, dynamic), NULL }, + ++#ifdef HAVE_DIRENT_H ++ { "directory", FR_CONF_OFFSET(PW_TYPE_STRING, realm_config_t, directory), NULL }, ++#endif ++ + { "dead_time", FR_CONF_OFFSET(PW_TYPE_INTEGER, realm_config_t, dead_time), STRINGIFY(DEAD_TIME) }, + + { "wake_all_if_all_dead", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, wake_all_if_all_dead), "no" }, +@@ -148,12 +159,12 @@ static int home_server_addr_cmp(void const *one, void const *two) + home_server_t const *a = one; + home_server_t const *b = two; + +- if (a->server && !b->server) return -1; +- if (!a->server && b->server) return +1; +- if (a->server && b->server) { ++ if (a->virtual_server && !b->virtual_server) return -1; ++ if (!a->virtual_server && b->virtual_server) return +1; ++ if (a->virtual_server && b->virtual_server) { + rcode = a->type - b->type; + if (rcode != 0) return rcode; +- return strcmp(a->server, b->server); ++ return strcmp(a->virtual_server, b->virtual_server); + } + + if (a->port < b->port) return -1; +@@ -268,6 +279,10 @@ static ssize_t xlat_home_server(UNUSED void *instance, REQUEST *request, + state = "fail"; + break; + ++ case HOME_STATE_ADMIN_DOWN: ++ state = "down"; ++ break; ++ + default: + state = "unknown"; + break; +@@ -317,6 +332,60 @@ static ssize_t xlat_server_pool(UNUSED void *instance, REQUEST *request, + + return xlat_cs(request->home_pool->cs, fmt, out, outlen); + } ++ ++ ++/* ++ * Xlat for %{home_server_dynamic:foo} ++ */ ++static ssize_t xlat_home_server_dynamic(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ int type; ++ char const *p; ++ home_server_t *home; ++ ++ if (outlen < 2) return 0; ++ ++ switch (request->packet->code) { ++ case PW_CODE_ACCESS_REQUEST: ++ type = HOME_TYPE_AUTH; ++ break; ++ ++#ifdef WITH_ACCOUNTING ++ case PW_CODE_ACCOUNTING_REQUEST: ++ type = HOME_TYPE_ACCT; ++ break; ++#endif ++ ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ case PW_CODE_DISCONNECT_REQUEST: ++ type = HOME_TYPE_COA; ++ break; ++#endif ++ ++ default: ++ *out = '\0'; ++ return 0; ++ } ++ ++ p = fmt; ++ while (isspace((uint8_t) *p)) p++; ++ ++ home = home_server_byname(p, type); ++ if (!home) { ++ *out = '\0'; ++ return 0; ++ } ++ ++ /* ++ * 1 for dynamic, 0 for static ++ */ ++ out[0] = '0' + home->dynamic; ++ out[1] = '\0'; ++ ++ return 1; ++} + #endif + + void realms_free(void) +@@ -366,11 +435,15 @@ static CONF_PARSER home_server_coa[] = { + }; + #endif + ++static const char *require_message_authenticator = NULL; ++ + static CONF_PARSER home_server_config[] = { ++ { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, ++ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, + { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL }, + { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL }, + { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL }, +- { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, server), NULL }, ++ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, virtual_server), NULL }, + + { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, home_server_t, port), "0" }, + +@@ -519,7 +592,7 @@ static bool home_server_insert(home_server_t *home, CONF_SECTION *cs) + return false; + } + +- if (!home->server && !rbtree_insert(home_servers_byaddr, home)) { ++ if (!home->virtual_server && !rbtree_insert(home_servers_byaddr, home)) { + rbtree_deletebydata(home_servers_byname, home); + cf_log_err_cs(cs, "Internal error %d adding home server %s", __LINE__, home->log_name); + return false; +@@ -561,7 +634,7 @@ bool realm_home_server_add(home_server_t *home) + return false; + } + +- if (!home->server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { ++ if (!home->virtual_server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { + char buffer[INET6_ADDRSTRLEN + 3]; + + inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr, buffer, sizeof(buffer)); +@@ -620,6 +693,19 @@ bool realm_home_server_add(home_server_t *home) + return true; + } + ++#ifdef WITH_TLS ++/* ++ * The listeners are always different. And we always look them up by *known* listener. And not "find me some random thing". ++ */ ++static int listener_cmp(void const *one, void const *two) ++{ ++ if (one < two) return -1; ++ if (one > two) return +1; ++ ++ return 0; ++} ++#endif ++ + /** Alloc a new home server defined by a CONF_SECTION + * + * @param ctx to allocate home_server_t in. +@@ -640,6 +726,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + home->cs = cs; + home->state = HOME_STATE_UNKNOWN; + home->proto = IPPROTO_UDP; ++ home->require_ma = main_config.require_ma; ++ ++ require_message_authenticator = false; + + /* + * Parse the configuration into the home server +@@ -647,6 +736,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + */ + if (cf_section_parse(cs, home, home_server_config) < 0) goto error; + ++ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) { ++ goto error; ++ } ++ + /* + * It has an IP address, it must be a remote server. + */ +@@ -670,23 +763,25 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + } else if (cf_pair_find(cs, "virtual_server") != NULL) { + home->ipaddr.af = AF_UNSPEC; /* mark ipaddr as unused */ + +- if (!home->server) { ++ if (!home->virtual_server) { + cf_log_err_cs(cs, "Invalid value for virtual_server"); + goto error; + } + + /* +- * Try and find a 'server' section off the root of ++ * Try and find a "server" section off the root of + * the config with a name that matches the + * virtual_server. + */ +- if (!cf_section_sub_find_name2(rc->cs, "server", home->server)) { +- cf_log_err_cs(cs, "No such server %s", home->server); ++ if (!rc) goto error; ++ ++ if (!cf_section_sub_find_name2(rc->cs, "server", home->virtual_server)) { ++ cf_log_err_cs(cs, "No such server %s", home->virtual_server); + goto error; + } + + home->secret = ""; +- home->log_name = talloc_typed_strdup(home, home->server); ++ home->log_name = talloc_typed_strdup(home, home->virtual_server); + /* + * Otherwise it's an invalid config section and we + * raise an error. +@@ -717,7 +812,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + + #ifdef WITH_COA + case HOME_TYPE_COA: +- if (home->server != NULL) { ++ if (home->virtual_server != NULL) { + cf_log_err_cs(cs, "Home servers of type \"coa\" cannot point to a virtual server"); + goto error; + } +@@ -781,8 +876,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + cf_log_err_cs(cs, "Server not built with support for RADIUS over TCP"); + goto error; + #endif +- if (home->ping_check != HOME_PING_CHECK_NONE) { +- cf_log_err_cs(cs, "Only 'status_check = none' is allowed for home " ++ if ((home->ping_check != HOME_PING_CHECK_NONE) && ++ (home->ping_check != HOME_PING_CHECK_STATUS_SERVER)) { ++ cf_log_err_cs(cs, "Only 'status_check = status-server' is allowed for home " + "servers with 'proto = tcp'"); + goto error; + } +@@ -796,7 +892,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + home->proto = proto; + } + +- if (!home->server && rbtree_finddata(home_servers_byaddr, home)) { ++ if (!home->virtual_server && rbtree_finddata(home_servers_byaddr, home)) { + cf_log_err_cs(cs, "Duplicate home server"); + goto error; + } +@@ -831,7 +927,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + /* + * Virtual servers have some TLS restrictions. + */ +- if (home->server) { ++ if (home->virtual_server) { + if (tls) { + cf_log_err_cs(cs, "Virtual home_servers cannot have a \"tls\" subsection"); + goto error; +@@ -924,10 +1020,29 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE + * Parse the SSL client configuration. + */ + if (tls) { ++ int rcode; ++ ++ /* ++ * We don't require this for TLS connections. ++ */ ++ home->require_ma = false; ++ + home->tls = tls_client_conf_parse(tls); + if (!home->tls) { + goto error; + } ++ ++ /* ++ * Connection timeouts for outgoing TLS connections. ++ */ ++ ++ rcode = cf_item_parse(tls, "connect_timeout", FR_ITEM_POINTER(PW_TYPE_INTEGER, &home->connect_timeout), NULL); ++ if (rcode < 0) goto error; ++ ++ if (!home->connect_timeout || (home->connect_timeout > 30)) home->connect_timeout = 30; ++ ++ home->listeners = rbtree_create(home, listener_cmp, NULL, RBTREE_FLAG_LOCK); ++ if (!home->listeners) goto error; + } + #endif + } /* end of parse home server */ +@@ -1173,8 +1288,17 @@ void realm_pool_free(home_pool_t *pool) + } + #endif /* HAVE_PTHREAD_H */ + +-int realm_pool_add(home_pool_t *pool, UNUSED CONF_SECTION *cs) ++int realm_pool_add(home_pool_t *pool, CONF_SECTION *cs) + { ++ home_pool_t *old; ++ ++ old = rbtree_finddata(home_pools_byname, pool); ++ if (old) { ++ cf_log_err_cs(cs, "Cannot add duplicate home server %s, original is at %s[%d]", pool->name, ++ cf_section_filename(old->cs), cf_section_lineno(old->cs)); ++ return 0; ++ } ++ + /* + * The structs aren't mutex protected. Refuse to destroy + * the server. +@@ -1265,7 +1389,7 @@ static int server_pool_add(realm_config_t *rc, + goto error; + } + +- if (!pool->fallback->server) { ++ if (!pool->fallback->virtual_server) { + cf_log_err_cs(cs, "Fallback home_server %s does NOT contain a virtual_server directive", + pool->fallback->log_name); + goto error; +@@ -1541,7 +1665,7 @@ static int old_server_add(realm_config_t *rc, CONF_SECTION *cs, + home->src_ipaddr.af = home->ipaddr.af; + } else { + home->ipaddr.af = AF_UNSPEC; +- home->server = server; ++ home->virtual_server = server; + } + talloc_free(q); + +@@ -2255,9 +2379,68 @@ int realms_init(CONF_SECTION *config) + #ifdef WITH_PROXY + xlat_register("home_server", xlat_home_server, NULL, NULL); + xlat_register("home_server_pool", xlat_server_pool, NULL, NULL); ++ xlat_register("home_server_dynamic", xlat_home_server_dynamic, NULL, NULL); + #endif + + realm_config = rc; ++ ++#ifdef HAVE_DIRENT_H ++ if (!rc->dynamic) { ++ if (rc->directory) { ++ WARN("Ignoring 'directory' as dynamic home servers were not configured."); ++ } ++ } else { ++ DIR *dir; ++ struct dirent *dp; ++ ++ if (!rc->directory) { ++ WARN("Ignoring \"dynamic = true\" due to not set \"directory\" in proxy.conf"); ++ return 1; ++ } ++ ++ DEBUG2("including files in directory %s", rc->directory); ++ ++ dir = opendir(rc->directory); ++ if (!dir) { ++ cf_log_err_cs(config, "Error reading directory %s: %s", ++ rc->directory, fr_syserror(errno)); ++ goto error; ++ } ++ ++ /* ++ * Read the directory, ignoring "." files. ++ */ ++ while ((dp = readdir(dir)) != NULL) { ++ char const *p; ++ char conf_file[PATH_MAX]; ++ ++ if (dp->d_name[0] == '.') continue; ++ ++ /* ++ * Check for valid characters ++ */ ++ for (p = dp->d_name; *p != '\0'; p++) { ++ if (isalpha((uint8_t)*p) || ++ isdigit((uint8_t)*p) || ++ (*p == '-') || ++ (*p == '_') || ++ (*p == '.')) continue; ++ break; ++ } ++ if (*p != '\0') continue; ++ ++ snprintf(conf_file, sizeof(conf_file), "%s/%s", rc->directory, dp->d_name); ++ if (home_server_afrom_file(conf_file) < 0) { ++ ERROR("Failed reading home_server from %s - %s", ++ conf_file, fr_strerror()); ++ closedir(dir); ++ goto error; ++ } ++ } ++ closedir(dir); ++ } ++#endif ++ + return 1; + } + +@@ -2388,7 +2571,7 @@ void home_server_update_request(home_server_t *home, REQUEST *request) + * the 'hints' file. + */ + request->proxy->vps = fr_pair_list_copy(request->proxy, +- request->packet->vps); ++ request->packet->vps); + } + + /* +@@ -2516,7 +2699,7 @@ home_server_t *home_server_ldb(char const *realmname, + * Home servers that are unknown, alive, or zombie + * are used for proxying. + */ +- if (home->state >= HOME_STATE_IS_DEAD) { ++ if (HOME_SERVER_IS_DEAD(home)) { + continue; + } + +@@ -2533,7 +2716,8 @@ home_server_t *home_server_ldb(char const *realmname, + * came from this server. Don't re-proxy it + * there. + */ +- if ((request->listener->type == RAD_LISTEN_DETAIL) && ++ if (request->listener && ++ (request->listener->type == RAD_LISTEN_DETAIL) && + (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) && + (fr_ipaddr_cmp(&home->ipaddr, &request->packet->src_ipaddr) == 0)) { + continue; +@@ -2641,7 +2825,7 @@ home_server_t *home_server_ldb(char const *realmname, + found = pool->fallback; + + WARN("Home server pool %s failing over to fallback %s", +- pool->name, found->server); ++ pool->name, found->virtual_server); + if (pool->in_fallback) goto update_and_return; + + pool->in_fallback = true; +@@ -2680,7 +2864,7 @@ home_server_t *home_server_ldb(char const *realmname, + + if (!home) continue; + +- if ((home->state >= HOME_STATE_IS_DEAD) && ++ if (HOME_SERVER_IS_DEAD(home) && + (home->ping_check == HOME_PING_CHECK_NONE)) { + home->state = HOME_STATE_ALIVE; + home->response_timeouts = 0; +@@ -2740,7 +2924,7 @@ home_server_t *home_server_find(fr_ipaddr_t *ipaddr, uint16_t port, + #else + myhome.proto = IPPROTO_UDP; + #endif +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_byaddr, &myhome); + } +@@ -2764,7 +2948,7 @@ home_server_t *home_server_find_bysrc(fr_ipaddr_t *ipaddr, uint16_t port, + #else + myhome.proto = IPPROTO_UDP; + #endif +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_byaddr, &myhome); + } +@@ -2789,7 +2973,7 @@ home_server_t *home_server_bynumber(int number) + + memset(&myhome, 0, sizeof(myhome)); + myhome.number = number; +- myhome.server = NULL; /* we're not called for internal proxying */ ++ myhome.virtual_server = NULL; /* we're not called for internal proxying */ + + return rbtree_finddata(home_servers_bynumber, &myhome); + } +@@ -2805,4 +2989,116 @@ home_pool_t *home_pool_byname(char const *name, int type) + return rbtree_finddata(home_pools_byname, &mypool); + } + ++int home_server_afrom_file(char const *filename) ++{ ++ CONF_SECTION *cs, *subcs; ++ char const *p; ++ home_server_t *home; ++ ++ if (!realm_config->dynamic) { ++ fr_strerror_printf("Must set \"dynamic = true\" in proxy.conf for dynamic home servers to work"); ++ return -1; ++ } ++ ++ cs = cf_section_alloc(NULL, "home", filename); ++ if (!cs) { ++ fr_strerror_printf("Failed allocating memory"); ++ return -1; ++ } ++ ++ if (cf_file_read(cs, filename) < 0) { ++ fr_strerror_printf("Failed reading file %s", filename); ++ error: ++ talloc_free(cs); ++ return -1; ++ } ++ ++ p = strrchr(filename, '/'); ++ if (p) { ++ p++; ++ } else { ++ p = filename; ++ } ++ ++ subcs = cf_section_sub_find_name2(cs, "home_server", p); ++ if (!subcs) { ++ fr_strerror_printf("No 'home_server %s' definition in the file.", p); ++ goto error; ++ } ++ ++ home = home_server_afrom_cs(realm_config, realm_config, subcs); ++ if (!home) { ++ fr_strerror_printf("Failed parsing configuration to a home_server structure"); ++ goto error; ++ } ++ ++ home->dynamic = true; ++ ++ if (home->virtual_server) { ++ fr_strerror_printf("Dynamic home_server '%s' cannot have 'server = %s' configuration item", p, home->virtual_server); ++ talloc_free(home); ++ goto error; ++ } ++ ++ if (home->dual) { ++ fr_strerror_printf("Dynamic home_server '%s' is missing 'type', or it is set to 'auth+acct'. Please specify 'type = auth' or 'type = acct', etc.", p); ++ talloc_free(home); ++ goto error; ++ } ++ ++ if (!realm_home_server_add(home)) { ++ fr_strerror_printf("Failed adding home_server to the internal data structures"); ++ talloc_free(home); ++ goto error; ++ } ++ ++ return 0; ++} ++ ++int home_server_delete(char const *name, char const *type_name) ++{ ++ home_server_t *home; ++ int type; ++ char const *p; ++ ++ if (!realm_config->dynamic) { ++ fr_strerror_printf("Must set 'dynamic' in proxy.conf for dynamic home servers to work"); ++ return -1; ++ } ++ ++ type = fr_str2int(home_server_types, type_name, HOME_TYPE_INVALID); ++ if (type == HOME_TYPE_INVALID) { ++ fr_strerror_printf("Unknown home_server type '%s'", type_name); ++ return -1; ++ } ++ ++ p = strrchr(name, '/'); ++ if (p) { ++ p++; ++ } else { ++ p = name; ++ } ++ ++ home = home_server_byname(p, type); ++ if (!home) { ++ fr_strerror_printf("Failed to find home_server %s", p); ++ return -1; ++ } ++ ++ if (!home->dynamic) { ++ fr_strerror_printf("Cannot delete static home_server %s", p); ++ return -1; ++ } ++ ++ (void) rbtree_deletebydata(home_servers_byname, home); ++ (void) rbtree_deletebydata(home_servers_byaddr, home); ++#ifdef WITH_STATS ++ (void) rbtree_deletebydata(home_servers_bynumber, home); ++#endif ++ ++ /* ++ * Leak home, and home->cs. Oh well. ++ */ ++ return 0; ++} + #endif +diff --git a/src/main/session.c b/src/main/session.c +index e359010a1b..8dbf5a6f14 100644 +--- a/src/main/session.c ++++ b/src/main/session.c +@@ -34,7 +34,7 @@ RCSID("$Id$") + /* + * End a session by faking a Stop packet to all accounting modules. + */ +-int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, ++int session_zap(REQUEST *request, fr_ipaddr_t const *nasaddr, uint32_t nas_port, + char const *user, + char const *sessionid, uint32_t cliaddr, char proto, + int session_time) +@@ -64,6 +64,8 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + + #define IPPAIR(n,v) PAIR(n,v,vp_ipaddr) + ++#define IPV6PAIR(n,v) PAIR(n,v,vp_ipv6addr) ++ + #define STRINGPAIR(n,v) do { \ + if(!(vp = fr_pair_afrom_num(stopreq->packet,n, 0))) { \ + talloc_free(stopreq); \ +@@ -75,7 +77,12 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + } while(0) + + INTPAIR(PW_ACCT_STATUS_TYPE, PW_STATUS_STOP); +- IPPAIR(PW_NAS_IP_ADDRESS, nasaddr); ++ ++ if (nasaddr->af == AF_INET) { ++ IPPAIR(PW_NAS_IP_ADDRESS, nasaddr->ipaddr.ip4addr.s_addr); ++ } else { ++ IPV6PAIR(PW_NAS_IPV6_ADDRESS, nasaddr->ipaddr.ip6addr); ++ } + + INTPAIR(PW_EVENT_TIMESTAMP, 0); + vp->vp_date = time(NULL); +@@ -127,29 +134,25 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, + * 1 The user is logged in. + * 2 Some error occured. + */ +-int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, uint32_t nas_port, char const *user, + char const *session_id) + { + pid_t pid, child_pid; + int status; +- char address[16]; ++ char address[64]; + char port[11]; + RADCLIENT *cl; +- fr_ipaddr_t ipaddr; +- +- ipaddr.af = AF_INET; +- ipaddr.ipaddr.ip4addr.s_addr = nasaddr; + + /* + * Find NAS type. + */ +- cl = client_find_old(&ipaddr); ++ cl = client_find_old(nasaddr); + if (!cl) { + /* + * Unknown NAS, so trusting radutmp. + */ + DEBUG2("checkrad: Unknown NAS %s, not checking", +- ip_ntoa(address, nasaddr)); ++ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address))); + return 1; + } + +@@ -202,8 +205,8 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, + */ + closefrom(3); + +- ip_ntoa(address, nasaddr); +- snprintf(port, 11, "%u", nas_port); ++ inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address)); ++ snprintf(port, sizeof(port), "%u", nas_port); + + #ifdef __EMX__ + /* OS/2 can't directly execute scripts then we call the command +@@ -223,7 +226,7 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, + exit(2); + } + #else +-int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, + UNUSED char const *user, UNUSED char const *session_id) + { + ERROR("Simultaneous-Use is not supported"); +@@ -234,7 +237,7 @@ int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, + #else + /* WITH_SESSION_MGMT */ + +-int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_t nas_port, ++int session_zap(UNUSED REQUEST *request, fr_ipaddr_t const *nasaddr, UNUSED uint32_t nas_port, + UNUSED char const *user, + UNUSED char const *sessionid, UNUSED uint32_t cliaddr, UNUSED char proto, + UNUSED int session_time) +@@ -242,7 +245,7 @@ int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_ + return RLM_MODULE_FAIL; + } + +-int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, ++int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, + UNUSED char const *user, UNUSED char const *session_id) + { + ERROR("Simultaneous-Use is not supported"); +diff --git a/src/main/stats.c b/src/main/stats.c +index 33b5fd238a..6aa908bfea 100644 +--- a/src/main/stats.c ++++ b/src/main/stats.c +@@ -90,44 +90,58 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, + + void request_stats_final(REQUEST *request) + { +- if (request->master_state == REQUEST_COUNTED) return; ++ rad_listen_t *listener; ++ RADCLIENT *client; + +- if (!request->listener) return; +- if (!request->client) return; ++ if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; + +- if ((request->listener->type != RAD_LISTEN_NONE) && ++ /* don't count statistic requests */ ++ if (request->packet->code == PW_CODE_STATUS_SERVER) { ++ return; ++ } ++ ++ listener = request->listener; ++ if (listener) switch (listener->type) { ++ case RAD_LISTEN_NONE: + #ifdef WITH_ACCOUNTING +- (request->listener->type != RAD_LISTEN_ACCT) && ++ case RAD_LISTEN_ACCT: + #endif + #ifdef WITH_COA +- (request->listener->type != RAD_LISTEN_COA) && ++ case RAD_LISTEN_COA: + #endif +- (request->listener->type != RAD_LISTEN_AUTH)) return; ++ case RAD_LISTEN_AUTH: ++ break; + +- /* don't count statistic requests */ +- if (request->packet->code == PW_CODE_STATUS_SERVER) +- return; ++ default: ++ return; ++ } ++ ++ /* ++ * Deal with TCP / TLS issues. The statistics are kept in the parent socket. ++ */ ++ if (listener && listener->parent) listener = listener->parent; ++ client = request->client; + + #undef INC_AUTH +-#define INC_AUTH(_x) radius_auth_stats._x++;request->listener->stats._x++;request->client->auth._x++; ++#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; + + #undef INC_ACCT + #ifdef WITH_ACCOUNTING +-#define INC_ACCT(_x) radius_acct_stats._x++;request->listener->stats._x++;request->client->acct._x++ ++#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ + #else + #define INC_ACCT(_x) + #endif + + #undef INC_COA + #ifdef WITH_COA +-#define INC_COA(_x) radius_coa_stats._x++;request->listener->stats._x++;request->client->coa._x++ ++#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ + #else + #define INC_COA(_x) + #endif + + #undef INC_DSC + #ifdef WITH_DSC +-#define INC_DSC(_x) radius_dsc_stats._x++;request->listener->stats._x++;request->client->dsc._x++ ++#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ + #else + #define INC_DSC(_x) + #endif +@@ -140,7 +154,7 @@ void request_stats_final(REQUEST *request) + * deleted, because only the main server thread calls + * this function, which makes it thread-safe. + */ +- if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { ++ if (request->reply) switch (request->reply->code) { + case PW_CODE_ACCESS_ACCEPT: + INC_AUTH(total_access_accepts); + +@@ -268,7 +282,7 @@ void request_stats_final(REQUEST *request) + if (!request->proxy_reply) goto done; /* simplifies formatting */ + + #undef INC +-#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; ++#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; + + switch (request->proxy_reply->code) { + case PW_CODE_ACCESS_ACCEPT: +@@ -339,7 +353,7 @@ void request_stats_final(REQUEST *request) + done: + #endif /* WITH_PROXY */ + +- request->master_state = REQUEST_COUNTED; ++ request->options |= RAD_REQUEST_OPTION_STATS; + } + + typedef struct fr_stats2vp { +@@ -582,6 +596,23 @@ void request_stats_reply(REQUEST *request) + */ + if (!cl) return; + } ++#ifdef AF_INET6 ++ } else { ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_port) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++ cl = listener_find_client_list(&ipaddr, server_port->vp_integer, IPPROTO_UDP); ++ ++ /* ++ * Not found: don't do anything ++ */ ++ if (!cl) return; ++ } ++ } ++#endif /* AF_INET6 */ + } + + +@@ -597,6 +628,19 @@ void request_stats_reply(REQUEST *request) + } + #endif + ++#ifdef AF_INET6 ++ } else if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ memset(&ipaddr, 0, sizeof(ipaddr)); ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; ++ client = client_find(cl, &ipaddr, IPPROTO_UDP); ++#ifdef WITH_TCP ++ if (!client) { ++ client = client_find(cl, &ipaddr, IPPROTO_TCP); ++ } ++#endif ++#endif /* AF_INET6 */ ++ + /* + * Else look it up by number. + */ +@@ -615,23 +659,44 @@ void request_stats_reply(REQUEST *request) + * When retrieving client by number, also + * echo back it's IP address. + */ +- if ((vp->da->type == PW_TYPE_INTEGER) && +- (client->ipaddr.af == AF_INET)) { +- vp = radius_pair_create(request->reply, +- &request->reply->vps, +- PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); +- if (vp) { +- vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; ++ if (vp->da->type == PW_TYPE_INTEGER) { ++ if (client->ipaddr.af == AF_INET) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; ++ } ++ ++ if (client->ipaddr.prefix != 32) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_integer = client->ipaddr.prefix; ++ } ++ } + } + +- if (client->ipaddr.prefix != 32) { ++#ifdef AF_INET6 ++ if (client->ipaddr.af == AF_INET6) { + vp = radius_pair_create(request->reply, +- &request->reply->vps, +- PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS); + if (vp) { +- vp->vp_integer = client->ipaddr.prefix; ++ vp->vp_ipv6addr = client->ipaddr.ipaddr.ip6addr; ++ } ++ ++ if (client->ipaddr.prefix != 128) { ++ vp = radius_pair_create(request->reply, ++ &request->reply->vps, ++ PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); ++ if (vp) { ++ vp->vp_integer = client->ipaddr.prefix; ++ } + } + } ++#endif /* AF_INET6 */ + } + + if (server_ip) { +@@ -674,21 +739,26 @@ void request_stats_reply(REQUEST *request) + * See if we need to look up the server by socket + * socket. + */ +- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); +- if (!server_ip) return; +- + server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); + if (!server_port) return; + +- ipaddr.af = AF_INET; +- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +- this = listener_find_byipaddr(&ipaddr, +- server_port->vp_integer, +- IPPROTO_UDP); ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ ipaddr.af = AF_INET; ++ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; ++#ifdef AF_INET6 ++ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++#endif /* AF_INET6 */ ++ } else { ++ stats_error(request, "No listener IP address supplied"); ++ } + + /* + * Not found: don't do anything + */ ++ this = listener_find_byipaddr(&ipaddr, server_port->vp_integer, IPPROTO_UDP); + if (!this) { + stats_error(request, "No such listener"); + return; +@@ -730,16 +800,6 @@ void request_stats_reply(REQUEST *request) + VALUE_PAIR *server_ip, *server_port; + fr_ipaddr_t ipaddr; + +- /* +- * See if we need to look up the server by socket +- * socket. +- */ +- server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); +- if (!server_ip) { +- stats_error(request, "No home server IP supplied"); +- return; +- } +- + server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); + if (!server_port) { + stats_error(request, "No home server port supplied"); +@@ -749,15 +809,30 @@ void request_stats_reply(REQUEST *request) + #ifndef NDEBUG + memset(&ipaddr, 0, sizeof(ipaddr)); + #endif +- ipaddr.af = AF_INET; +- ipaddr.prefix = 32; +- ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +- home = home_server_find(&ipaddr, server_port->vp_integer, +- IPPROTO_UDP); ++ ++ /* ++ * See if we need to look up the server by socket ++ * socket. ++ */ ++ server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); ++ if (server_ip) { ++ ipaddr.af = AF_INET; ++ ipaddr.prefix = 32; ++ ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; ++#ifdef AF_INET6 ++ } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { ++ ipaddr.af = AF_INET6; ++ ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; ++#endif /* AF_INET6 */ ++ } else { ++ stats_error(request, "No home server IP supplied"); ++ return; ++ } + + /* + * Not found: don't do anything + */ ++ home = home_server_find(&ipaddr, server_port->vp_integer, IPPROTO_UDP); + if (!home) { + stats_error(request, "Failed to find home server IP"); + return; +diff --git a/src/main/tls.c b/src/main/tls.c +index 78c7370a63..338ccd6446 100644 +--- a/src/main/tls.c ++++ b/src/main/tls.c +@@ -27,6 +27,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include <freeradius-devel/radiusd.h> + #include <freeradius-devel/process.h> ++#include <freeradius-devel/modules.h> + #include <freeradius-devel/rad_assert.h> + + #ifdef HAVE_SYS_STAT_H +@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #include <fcntl.h> + #endif + ++#ifdef HAVE_DIRENT_H ++#include <dirent.h> ++#endif ++ + #ifdef HAVE_UTIME_H + #include <utime.h> + #endif +@@ -56,8 +61,25 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + # endif + # include <openssl/ssl.h> + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++# include <openssl/provider.h> ++ ++static OSSL_PROVIDER *openssl_default_provider = NULL; ++static OSSL_PROVIDER *openssl_legacy_provider = NULL; ++#endif ++ + #define LOG_PREFIX "tls" + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#define ERR_get_error_line(_file, _line) ERR_get_error_all(_file, _line, NULL, NULL, NULL) ++ ++#define FIPS_mode(_x) EVP_default_properties_is_fips_enabled(NULL) ++#define PEM_read_bio_DHparams(_bio, _x, _y, _z) PEM_read_bio_Parameters(_bio, &dh) ++#define SSL_CTX_set0_tmp_dh_pkey(_ctx, _dh) SSL_CTX_set_tmp_dh(_ctx, _dh) ++#define DH EVP_PKEY ++#define DH_free(_dh) ++#endif ++ + #ifdef ENABLE_OPENSSL_VERSION_CHECK + typedef struct libssl_defect { + uint64_t high; +@@ -153,6 +175,11 @@ static unsigned int record_plus(record_t *buf, void const *ptr, + static unsigned int record_minus(record_t *buf, void *ptr, + unsigned int size); + ++typedef struct { ++ char const *name; ++ SSL_CTX *ctx; ++} fr_realm_ctx_t; ++ + DIAG_OFF(format-nonliteral) + /** Print errors in the TLS thread local error stack + * +@@ -191,9 +218,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + + /* Extra verbose */ + if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { +- ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer); + } else { +- ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer); + } + + talloc_free(p); +@@ -205,7 +232,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + * Print the error we were given, irrespective + * of whether there were any OpenSSL errors. + */ +- ROPTIONAL(RERROR, ERROR, "%s", p); ++ ROPTIONAL(RERROR, ERROR, "(TLS) %s", p); + talloc_free(p); + } + +@@ -217,9 +244,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + ERR_error_string_n(error, buffer, sizeof(buffer)); + /* Extra verbose */ + if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { +- ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer); + } else { +- ROPTIONAL(REDEBUG, ERROR, "%s", buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer); + } + in_stack++; + } while ((error = ERR_get_error_line(&file, &line))); +@@ -309,11 +336,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con + * being regarded as "dead". + */ + case SSL_ERROR_SYSCALL: +- ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret); + return 0; + + case SSL_ERROR_SSL: +- ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret); + return 0; + + /* +@@ -323,7 +350,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con + * the code needs updating here. + */ + default: +- ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret); + return 0; + } + +@@ -338,7 +365,7 @@ static bool identity_is_safe(const char *identity) + if (!identity) return true; + + while ((c = *(identity++)) != '\0') { +- if (isalpha((int) c) || isdigit((int) c) || isspace((int) c) || ++ if (isalpha((uint8_t) c) || isdigit((uint8_t) c) || isspace((uint8_t) c) || + (c == '@') || (c == '-') || (c == '_') || (c == '.')) { + continue; + } +@@ -369,24 +396,32 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + FR_TLS_EX_INDEX_REQUEST); + if (request && conf->psk_query) { + size_t hex_len; +- VALUE_PAIR *vp; ++ VALUE_PAIR *vp, **certs; ++ TALLOC_CTX *talloc_ctx; + char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */ + + /* + * The passed identity is weird. Deny it. + */ + if (!identity_is_safe(identity)) { +- RWDEBUG("Invalid characters in PSK identity %s", identity); ++ RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity); + return 0; + } + + vp = pair_make_request("TLS-PSK-Identity", identity, T_OP_SET); + if (!vp) return 0; + ++ certs = (VALUE_PAIR **)SSL_get_ex_data(ssl, fr_tls_ex_index_certs); ++ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); ++ fr_assert(certs != NULL); /* pointer to sock->certs */ ++ fr_assert(talloc_ctx != NULL); /* sock */ ++ ++ fr_pair_add(certs, fr_pair_copy(talloc_ctx, vp)); ++ + hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query, + NULL, NULL); + if (!hex_len) { +- RWDEBUG("PSK expansion returned an empty string."); ++ RWDEBUG("(TLS) PSK expansion returned an empty string."); + return 0; + } + +@@ -396,7 +431,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + * the truncation, and complain about it. + */ + if (hex_len > (2 * max_psk_len)) { +- RWDEBUG("Returned PSK is too long (%u > %u)", ++ RWDEBUG("(TLS) Returned PSK is too long (%u > %u)", + (unsigned int) hex_len, 2 * max_psk_len); + return 0; + } +@@ -419,7 +454,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + * static identity. + */ + if (strcmp(identity, conf->psk_identity) != 0) { +- ERROR("Supplied PSK identity %s does not match configuration. Rejecting.", ++ ERROR("(TKS) Supplied PSK identity %s does not match configuration. Rejecting.", + identity); + return 0; + } +@@ -475,8 +510,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize) + #endif + } + +- +- + static int _tls_session_free(tls_session_t *ssn) + { + /* +@@ -492,6 +525,52 @@ static int _tls_session_free(tls_session_t *ssn) + return 0; + } + ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) ++/* ++ * By setting the environment variable SSLKEYLOGFILE to a filename keying ++ * material will be exported that you may use with Wireshark to decode any ++ * TLS flows. Please see the following for more details: ++ * ++ * https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption ++ * ++ * An example logging session is (you should delete the file on each run): ++ * ++ * rm -f /tmp/sslkey.log; env SSLKEYLOGFILE=/tmp/sslkey.log freeradius -X | tee /tmp/debug ++ */ ++static void tls_keylog_cb(UNUSED const SSL *ssl, const char *line) ++{ ++ int fd; ++ size_t len; ++ const char *filename; ++ // less than _POSIX_PIPE_BUF (512) guarantees writes are atomic for O_APPEND ++ char buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH]; ++ ++ filename = getenv("SSLKEYLOGFILE"); ++ if (!filename) return; ++ ++ len = strlen(line); ++ if ((len + 1) > sizeof(buffer)) { ++ DEBUG("SSLKEYLOGFILE buffer not large enough, max %lu, required %lu", sizeof(buffer), len + 1); ++ return; ++ } ++ ++ memcpy(buffer, line, len); ++ buffer[len] = '\n'; ++ ++ fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); ++ if (fd < 0) { ++ fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); ++ return; ++ } ++ ++ if (write(fd, buffer, len + 1) == -1) { ++ DEBUG("Failed to write to file %s: %s", filename, strerror(errno)); ++ } ++ ++ close(fd); ++} ++#endif ++ + tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs) + { + int ret; +@@ -506,6 +585,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + + ssn->ctx = conf->ctx; + ssn->mtu = conf->fragment_size; ++ ssn->conf = conf; + + SSL_CTX_set_mode(ssn->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY); + +@@ -516,8 +596,15 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + } + + request = request_alloc(ssn); ++ request->packet = rad_alloc(request, false); ++ request->reply = rad_alloc(request, false); ++ + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); + ++ if (conf->fix_cert_order) { ++ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER, (void *) &conf->fix_cert_order); ++ } ++ + /* + * Add the message callback to identify what type of + * message/handshake is passed +@@ -537,17 +624,19 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn); + if (certs) SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)certs); ++ + SSL_set_fd(ssn->ssl, fd); +- ret = SSL_connect(ssn->ssl); + ++ ret = SSL_connect(ssn->ssl); + if (ret < 0) { + switch (SSL_get_error(ssn->ssl, ret)) { +- default: +- break; +- +- ++ default: ++ break; + + case SSL_ERROR_WANT_READ: ++ ssn->connected = false; ++ return ssn; ++ + case SSL_ERROR_WANT_WRITE: + ssn->connected = false; + return ssn; +@@ -555,7 +644,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + } + + if (ret <= 0) { +- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); ++ tls_error_io_log(NULL, ssn, ret, "Failed in connecting TLS session."); + talloc_free(ssn); + + return NULL; +@@ -575,18 +664,61 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + * @param conf to use to configure the tls session. + * @param request The current #REQUEST. + * @param client_cert Whether to require a client_cert. ++ * @param allow_tls13 Whether to allow or forbid TLS 1.3. + * @return a new session on success, or NULL on error. + */ +-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert) ++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, ++#ifndef TLS1_3_VERSION ++ UNUSED ++#endif ++ bool allow_tls13) + { + tls_session_t *state = NULL; + SSL *new_tls = NULL; + int verify_mode = 0; + VALUE_PAIR *vp; ++ X509_STORE *new_cert_store; + + rad_assert(request != NULL); + +- RDEBUG2("Initiating new TLS session"); ++ RDEBUG2("(TLS) Initiating new session"); ++ ++ /* ++ * Replace X509 store if it is time to update CRLs/certs in ca_path ++ */ ++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { ++ pthread_mutex_lock(&conf->mutex); ++ /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */ ++ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { ++ RDEBUG2("Flushing X509 store to re-read data from ca_path dir"); ++ ++ if ((new_cert_store = fr_init_x509_store(conf)) == NULL) { ++ RERROR("(TLS) Error replacing X509 store, out of memory (?)"); ++ } else { ++ if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store); ++ /* ++ * Swap empty store with the old one. ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++ conf->old_x509_store = SSL_CTX_get_cert_store(conf->ctx); ++ /* Bump refcnt so the store is kept allocated till next store replacement */ ++ X509_STORE_up_ref(conf->old_x509_store); ++ SSL_CTX_set_cert_store(conf->ctx, new_cert_store); ++#else ++ /* ++ * We do not use SSL_CTX_set_cert_store() call here because ++ * we are not sure that old X509 store is not in the use by some ++ * thread (i.e. cert check in progress). ++ * Keep it allocated till next store replacement. ++ */ ++ conf->old_x509_store = conf->ctx->cert_store; ++ conf->ctx->cert_store = new_cert_store; ++#endif ++ conf->ca_path_last_reload = request->timestamp; ++ } ++ } ++ pthread_mutex_unlock(&conf->mutex); ++ } + + new_tls = SSL_new(conf->ctx); + if (new_tls == NULL) { +@@ -594,11 +726,33 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + return NULL; + } + ++#ifdef TLS1_3_VERSION ++ /* ++ * Disallow TLS 1.3 for FAST. ++ * ++ * We need another magic configuration option to allow ++ * it. ++ */ ++ if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! FORCING MAXIMUM TLS VERSION TO TLS 1.2 !!"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using this EAP method with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) { ++ tls_error_log(request, "Failed limiting maximum version to TLS 1.2"); ++ return NULL; ++ } ++ } ++#endif ++ + /* We use the SSL's "app_data" to indicate a call-back */ + SSL_set_app_data(new_tls, NULL); + + if ((state = talloc_zero(ctx, tls_session_t)) == NULL) { +- RERROR("Error allocating memory for SSL state"); ++ RERROR("(TLS) Error allocating memory for SSL state"); + return NULL; + } + session_init(state); +@@ -606,6 +760,14 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + + state->ctx = conf->ctx; + state->ssl = new_tls; ++ state->conf = conf; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) ++ /* ++ * Set the keylog file if the admin requested it. ++ */ ++ if (getenv("SSLKEYLOGFILE") != NULL) SSL_CTX_set_keylog_callback(state->ctx, tls_keylog_cb); ++#endif + + /* + * Initialize callbacks +@@ -637,6 +799,85 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + SSL_set_msg_callback_arg(new_tls, state); + SSL_set_info_callback(new_tls, cbtls_info); + ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ /* ++ * Allow policies to load context-specific certificate chains. ++ */ ++ vp = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_FILE, 0, TAG_ANY); ++ if (vp) { ++ VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY); ++ if (!key) key = vp; ++ ++ RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue); ++ ++ if (conf->realms) { ++ fr_realm_ctx_t my_r, *r; ++ ++ /* ++ * Use a pre-existing SSL CTX, if ++ * available. Note that due to OpenSSL ++ * issues, this really changes only the ++ * certificate files, and leaves all ++ * other fields alone. e.g. you can't ++ * select a different TLS version. ++ * ++ * This is fine for our purposes in v3. ++ * Due to how we build them, the various ++ * additional SSL_CTXs are identical to ++ * the main one, except for certs. ++ */ ++ my_r.name = vp->vp_strvalue; ++ r = fr_hash_table_finddata(conf->realms, &my_r); ++ if (r) { ++ (void) SSL_set_SSL_CTX(state->ssl, r->ctx); ++ goto after_chain; ++ } ++ ++ /* ++ * Else fall through to trying to dynamically load the certs. ++ */ ++ } ++ ++ if (conf->file_type) { ++ if (SSL_use_certificate_chain_file(state->ssl, vp->vp_strvalue) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ error: ++ talloc_free(state); ++ return NULL; ++ } ++ } else { ++ if (SSL_use_certificate_file(state->ssl, vp->vp_strvalue, SSL_FILETYPE_ASN1) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ goto error; ++ } ++ } ++ ++ /* ++ * Note that there is either no password, or it ++ * has to be the same as what's in the ++ * configuration. ++ * ++ * There is just no additional security to ++ * putting a password into the same file system ++ * as the private key. ++ */ ++ if (SSL_use_PrivateKey_file(state->ssl, key->vp_strvalue, SSL_FILETYPE_PEM) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ key->vp_strvalue); ++ goto error; ++ } ++ ++ if (SSL_check_private_key(state->ssl) != 1) { ++ tls_error_log(request, "Failed validating TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ goto error; ++ } ++ } ++after_chain: ++#endif ++ + /* + * In Server mode we only accept. + */ +@@ -646,7 +887,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + * Verify the peer certificate, if asked. + */ + if (client_cert) { +- RDEBUG2("Setting verify mode to require certificate from client"); ++ RDEBUG2("(TLS) Setting verify mode to require certificate from client"); + verify_mode = SSL_VERIFY_PEER; + verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + verify_mode |= SSL_VERIFY_CLIENT_ONCE; +@@ -670,10 +911,41 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + * just too much. + */ + state->mtu = conf->fragment_size; ++#define EAP_TLS_MAGIC_OVERHEAD (63) ++ ++ /* ++ * If the packet contains an MTU, then use that. We ++ * trust the admin! ++ */ + vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY); +- if (vp && (vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { +- state->mtu = vp->vp_integer; ++ if (vp) { ++ if ((vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { ++ state->mtu = vp->vp_integer; ++ } ++ ++ } else if (request->parent) { ++ /* ++ * If there's a parent request, we look for what ++ * MTU was set there. Then, we use an MTU which ++ * accounts for the extra overhead of nesting EAP ++ * + TLS inside of EAP + TLS. ++ */ ++ vp = fr_pair_find_by_num(request->parent->state, PW_FRAMED_MTU, 0, TAG_ANY); ++ if (vp && (vp->vp_integer > (100 + EAP_TLS_MAGIC_OVERHEAD)) && (vp->vp_integer <= state->mtu)) { ++ state->mtu = vp->vp_integer - EAP_TLS_MAGIC_OVERHEAD; ++ } ++ } ++ ++ /* ++ * Cache / update the Framed-MTU in the session-state ++ * list. ++ */ ++ vp = fr_pair_find_by_num(request->state, PW_FRAMED_MTU, 0, TAG_ANY); ++ if (!vp) { ++ vp = fr_pair_afrom_num(request->state_ctx, PW_FRAMED_MTU, 0); ++ fr_pair_add(&request->state, vp); + } ++ if (vp) vp->vp_integer = state->mtu; + + if (conf->session_cache_enable) state->allow_session_resumption = true; /* otherwise it's false */ + +@@ -697,12 +969,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + { + int err; + +- if (ssn->invalid_hb_used) return 0; ++ if (ssn->invalid_hb_used) { ++ REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection"); ++ return 0; ++ } + + if (ssn->dirty_in.used > 0) { + err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used); + if (err != (int) ssn->dirty_in.used) { +- REDEBUG("TLS - Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); ++ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + record_init(&ssn->dirty_in); + return 0; + } +@@ -716,24 +991,26 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + return 1; + } + +- if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)")) return 0; ++ if (!tls_error_io_log(request, ssn, err, "Failed reading from OpenSSL")) return 0; + + /* Some Extra STATE information for easy debugging */ + if (!ssn->is_init_finished && SSL_is_init_finished(ssn->ssl)) { + VALUE_PAIR *vp; + char const *str_version; + +- RDEBUG2("TLS - Connection Established"); ++ RDEBUG2("(TLS) Connection Established"); + ssn->is_init_finished = true; + + vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0); + if (vp) { + fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(SSL_get_current_cipher(ssn->ssl))); + fr_pair_add(&request->state, vp); ++ RINDENT(); + rdebug_pair(L_DBG_LVL_2, request, vp, NULL); ++ REXDENT(); + } + +- switch (ssn->info.version) { ++ switch (SSL_version(ssn->ssl)) { + case SSL2_VERSION: + str_version = "SSL 2.0"; + break; +@@ -767,13 +1044,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + if (vp) { + fr_pair_value_strcpy(vp, str_version); + fr_pair_add(&request->state, vp); ++ RINDENT(); + rdebug_pair(L_DBG_LVL_2, request, vp, NULL); ++ REXDENT(); + } + } +- else if (SSL_in_init(ssn->ssl)) { RDEBUG2("TLS - In Handshake Phase"); } +- else if (SSL_in_before(ssn->ssl)) { RDEBUG2("TLS - Before Handshake Phase"); } +- else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("TLS - In Accept mode"); } +- else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("TLS - In Connect mode"); } ++ else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); } ++ else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); } ++ else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); } ++ else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); } + + #if OPENSSL_VERSION_NUMBER >= 0x10001000L + /* +@@ -791,7 +1070,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + * to get the session is a hard fail. + */ + if (!ssn->ssl_session && ssn->is_init_finished) { +- RDEBUG("TLS - Failed getting session"); ++ RDEBUG("(TLS) Failed getting session"); + return 0; + } + } +@@ -805,25 +1084,25 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, + sizeof(ssn->dirty_out.data)); + if (err > 0) { +- RDEBUG2("TLS - got %d bytes of data", err); ++ RDEBUG3("(TLS) got %d bytes of data", err); + ssn->dirty_out.used = err; + + } else if (BIO_should_retry(ssn->from_ssl)) { + record_init(&ssn->dirty_in); +- RDEBUG2("TLS - Asking for more data in tunnel."); ++ RDEBUG2("(TLS) Asking for more data in tunnel."); + return 1; + + } else { +- tls_error_log(NULL, "Error reading from SSL BIO"); ++ tls_error_log(NULL, "Error reading from OpenSSL"); + record_init(&ssn->dirty_in); +- RDEBUG2("TLS - Tunnel data is established."); + return 0; + } + } else { +- +- RDEBUG2("TLS - Application data."); +- /* Its clean application data, do whatever we want */ ++ RDEBUG2("(TLS) Application data."); ++ /* Its clean application data, leave whatever is in the buffer */ ++#if 0 + record_init(&ssn->clean_out); ++#endif + } + + /* We are done with dirty_in, reinitialize it */ +@@ -855,13 +1134,12 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn) + record_minus(&ssn->clean_in, NULL, written); + + /* Get the dirty data from Bio to send it */ +- err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, +- sizeof(ssn->dirty_out.data)); ++ err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used, ++ sizeof(ssn->dirty_out.data) - ssn->dirty_out.used); + if (err > 0) { +- ssn->dirty_out.used = err; ++ ssn->dirty_out.used += err; + } else { +- if (!tls_error_io_log(request, ssn, err, +- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) { ++ if (!tls_error_io_log(request, ssn, err, "Failed writing to OpenSSL")) { + return 0; + } + } +@@ -963,7 +1241,10 @@ void tls_session_information(tls_session_t *tls_session) + { + char const *str_write_p, *str_version, *str_content_type = ""; + char const *str_details1 = "", *str_details2= ""; ++ char const *details = NULL; + REQUEST *request; ++ VALUE_PAIR *vp; ++ char content_type[16], alert_buf[16]; + char buffer[32]; + + /* +@@ -972,9 +1253,20 @@ void tls_session_information(tls_session_t *tls_session) + */ + if (rad_debug_lvl == 0) return; + +- str_write_p = tls_session->info.origin ? ">>> send" : "<<< recv"; ++ /* ++ * OpenSSL calls this function with 'pseudo' content ++ * types. The user doesn't care about them, so suppress them. ++ */ ++ if (tls_session->info.content_type > UINT8_MAX) return; ++ ++ request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); ++ if (!request) return; ++ ++ str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv"; ++ ++#define FROM_CLIENT (tls_session->info.origin == 0) + +- switch (tls_session->info.version) { ++ switch (SSL_version(tls_session->ssl)) { + case SSL2_VERSION: + str_version = "SSL 2.0 "; + break; +@@ -1001,13 +1293,12 @@ void tls_session_information(tls_session_t *tls_session) + #endif + + default: +- sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", tls_session->info.version); ++ sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", SSL_version(tls_session->ssl)); + str_version = buffer; + break; + } + +- if (tls_session->info.version == SSL3_VERSION || +- tls_session->info.version == TLS1_VERSION) { ++ if (1) { + switch (tls_session->info.content_type) { + case SSL3_RT_CHANGE_CIPHER_SPEC: + str_content_type = "ChangeCipherSpec"; +@@ -1026,7 +1317,8 @@ void tls_session_information(tls_session_t *tls_session) + break; + + default: +- str_content_type = "UnknownContentType"; ++ snprintf(content_type, sizeof(content_type), "content=%d", tls_session->info.content_type); ++ str_content_type = content_type; + break; + } + +@@ -1045,9 +1337,12 @@ void tls_session_information(tls_session_t *tls_session) + } + + str_details2 = " ???"; ++ details = "there is a failure inside the TLS protocol exchange"; ++ + switch (tls_session->info.alert_description) { + case SSL3_AD_CLOSE_NOTIFY: + str_details2 = " close_notify"; ++ details = "the connection has been closed, and no further TLS exchanges will take place"; + break; + + case SSL3_AD_UNEXPECTED_MESSAGE: +@@ -1074,24 +1369,34 @@ void tls_session_information(tls_session_t *tls_session) + str_details2 = " handshake_failure"; + break; + ++ case SSL3_AD_NO_CERTIFICATE: ++ str_details2 = " no_certificate"; ++ details = "the server did not present a certificate to the client"; ++ break; ++ + case SSL3_AD_BAD_CERTIFICATE: + str_details2 = " bad_certificate"; ++ details = "it believes the server certificate is invalid or malformed"; + break; + + case SSL3_AD_UNSUPPORTED_CERTIFICATE: + str_details2 = " unsupported_certificate"; ++ details = "it does not understand the certificate presented by the server"; + break; + + case SSL3_AD_CERTIFICATE_REVOKED: + str_details2 = " certificate_revoked"; ++ details = "it believes that the server certificate has been revoked"; + break; + + case SSL3_AD_CERTIFICATE_EXPIRED: + str_details2 = " certificate_expired"; ++ details = "it believes that the server certificate has expired. Either renew the server certificate, or check the time on the client"; + break; + + case SSL3_AD_CERTIFICATE_UNKNOWN: + str_details2 = " certificate_unknown"; ++ details = "it does not recognize the server certificate"; + break; + + case SSL3_AD_ILLEGAL_PARAMETER: +@@ -1100,6 +1405,7 @@ void tls_session_information(tls_session_t *tls_session) + + case TLS1_AD_UNKNOWN_CA: + str_details2 = " unknown_ca"; ++ details = "it does not recognize the CA used to issue the server certificate. Please update the client so that it knows about the CA"; + break; + + case TLS1_AD_ACCESS_DENIED: +@@ -1120,6 +1426,18 @@ void tls_session_information(tls_session_t *tls_session) + + case TLS1_AD_PROTOCOL_VERSION: + str_details2 = " protocol_version"; ++ details = "the client does not accept the version of TLS negotiated by the server"; ++ ++#ifdef TLS1_3_VERSION ++ /* ++ * Complain about OpenSSL bugs. ++ */ ++ if ((SSL_version(tls_session->ssl) > tls_session->conf->max_version) && ++ (rad_debug_lvl > 0)) { ++ WARN("TLS 1.3 has been negotiated even though it was disabled. This is an OpenSSL Bug."); ++ WARN("Please set: cipher_list = \"DEFAULT@SECLEVEL=1\" in the tls {...} section."); ++ } ++#endif + break; + + case TLS1_AD_INSUFFICIENT_SECURITY: +@@ -1137,12 +1455,69 @@ void tls_session_information(tls_session_t *tls_session) + case TLS1_AD_NO_RENEGOTIATION: + str_details2 = " no_renegotiation"; + break; ++ ++#ifdef TLS13_AD_MISSING_EXTENSIONS ++ case TLS13_AD_MISSING_EXTENSIONS: ++ str_details2 = " missing_extensions"; ++ details = "the server did not present a TLS extension which the client expected to be present. Please check the TLS libraries on the client and server for compatibility"; ++ break; ++#endif ++ ++#ifdef TLS13_AD_CERTIFICATE_REQUIRED ++ case TLS13_AD_CERTIFICATE_REQUIRED: ++ str_details2 = " certificate_required"; ++ details = "the server did not present a certificate"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNSUPPORTED_EXTENSION ++ case TLS1_AD_UNSUPPORTED_EXTENSION: ++ str_details2 = " unsupported_extension"; ++ details = "the server has sent a TLS message which the client does not recognize. Please check the TLS libraries on the client and server for compatibility"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_CERTIFICATE_UNOBTAINABLE ++ case TLS1_AD_CERTIFICATE_UNOBTAINABLE: ++ str_details2 = " certificate_unobtainable"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNRECOGNIZED_NAME ++ case TLS1_AD_UNRECOGNIZED_NAME: ++ str_details2 = " unrecognized_name"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE ++ case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE: ++ str_details2 = " bad_certificate_status_response"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_BAD_CERTIFICATE_HASH_VALUE ++ case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE: ++ str_details2 = " bad_certificate_hash_value"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNKNOWN_PSK_IDENTITY ++ case TLS1_AD_UNKNOWN_PSK_IDENTITY: ++ str_details2 = " unknown_psk_identity"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_NO_APPLICATION_PROTOCOL ++ case TLS1_AD_NO_APPLICATION_PROTOCOL: ++ str_details2 = " no_application_protocol"; ++ break; ++#endif + } + } + } + + if (tls_session->info.content_type == SSL3_RT_HANDSHAKE) { +- str_details1 = "???"; ++ str_details1 = ""; + + if (tls_session->info.record_len > 0) switch (tls_session->info.handshake_type) { + case SSL3_MT_HELLO_REQUEST: +@@ -1157,6 +1532,18 @@ void tls_session_information(tls_session_t *tls_session) + str_details1 = ", ServerHello"; + break; + ++#ifdef SSL3_MT_NEWSESSION_TICKET ++ case SSL3_MT_NEWSESSION_TICKET: ++ str_details1 = ", NewSessionTicket"; ++ break; ++#endif ++ ++#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS ++ case SSL3_MT_ENCRYPTED_EXTENSIONS: ++ str_details1 = ", EncryptedExtensions"; ++ break; ++#endif ++ + case SSL3_MT_CERTIFICATE: + str_details1 = ", Certificate"; + break; +@@ -1184,31 +1571,52 @@ void tls_session_information(tls_session_t *tls_session) + case SSL3_MT_FINISHED: + str_details1 = ", Finished"; + break; ++ ++#ifdef SSL3_MT_KEY_UPDATE ++ case SSL3_MT_KEY_UPDATE: ++ str_content_type = "KeyUpdate"; ++ break; ++#endif ++ ++ default: ++ snprintf(alert_buf, sizeof(alert_buf), ", type=%d", tls_session->info.handshake_type); ++ str_details1 = alert_buf; ++ break; + } + } + } + + snprintf(tls_session->info.info_description, + sizeof(tls_session->info.info_description), +- "%s %s%s [length %04lx]%s%s\n", ++ "%s %s%s%s%s", + str_write_p, str_version, str_content_type, +- (unsigned long)tls_session->info.record_len, + str_details1, str_details2); + +- request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); +- if (!request) return; ++ /* ++ * Cache the TLS session information in the session-state ++ * list, so it can be accessed by Post-Auth-Type ++ * Client-Lost { ... } ++ */ ++ vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_INFORMATION, 0); ++ if (vp) { ++ fr_pair_value_strcpy(vp, tls_session->info.info_description); ++ fr_pair_add(&request->state, vp); ++ } + + RDEBUG2("%s", tls_session->info.info_description); ++ ++ if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details); + } + + static CONF_PARSER cache_config[] = { + { "enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, session_cache_enable), "no" }, + +- { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_timeout), "24" }, ++ { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_lifetime), "24" }, + { "name", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_id_name), NULL }, + + { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_cache_size), "255" }, + { "persist_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_path), NULL }, ++ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_server), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -1256,6 +1664,7 @@ static CONF_PARSER tls_server_config[] = { + #ifdef X509_V_FLAG_CRL_CHECK_ALL + { "check_all_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, check_all_crl), "no" }, + #endif ++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, + { "allow_expired_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, allow_expired_crl), NULL }, + { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, + { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, +@@ -1263,6 +1672,14 @@ static CONF_PARSER tls_server_config[] = { + { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, + { "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, require_client_cert), NULL }, + ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ { "sigalgs_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, sigalgs_list), NULL }, ++#endif ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ { "reject_unknown_intermediate_ca", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disallow_untrusted), .dflt = "no", }, ++#endif ++ + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH + { "ecdh_curve", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, ecdh_curve), "prime256v1" }, +@@ -1281,9 +1698,23 @@ static CONF_PARSER tls_server_config[] = { + { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, + #endif + +- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, ++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, ++ ++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), ++#if defined(TLS1_2_VERSION) ++ "1.2" ++#elif defined(TLS1_1_VERSION) ++ "1.1" ++#else ++ "1.0" ++#endif ++ }, ++ ++#ifdef WITH_RADIUSV11 ++ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL }, ++#endif + +- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, ++ { "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL }, + + { "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config }, + +@@ -1312,6 +1743,9 @@ static CONF_PARSER tls_client_config[] = { + { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, + { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, + { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, ++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, ++ ++ { "fix_cert_order", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, fix_cert_order), NULL }, + + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH +@@ -1331,9 +1765,23 @@ static CONF_PARSER tls_client_config[] = { + { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, + #endif + +- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, ++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, ++ ++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), ++#if defined(TLS1_2_VERSION) ++ "1.2" ++#elif defined(TLS1_1_VERSION) ++ "1.1" ++#else ++ "1.0" ++#endif ++ }, ++ ++#ifdef WITH_RADIUSV11 ++ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL }, ++#endif + +- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, ++ { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL }, + + CONF_PARSER_TERMINATOR + }; +@@ -1347,7 +1795,44 @@ static int load_dh_params(SSL_CTX *ctx, char *file) + DH *dh = NULL; + BIO *bio; + +- if (!file) return 0; ++ /* ++ * Prior to trying to load the file, check what OpenSSL will do with it. ++ * ++ * Certain downstreams (such as RHEL) will ignore user-provided dhparams ++ * in FIPS mode, unless the specified parameters are FIPS-approved. ++ * However, since OpenSSL >= 1.1.1 will automatically select parameters ++ * anyways, there's no point in attempting to load them. ++ * ++ * Change suggested by @t8m ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ if (FIPS_mode() > 0) { ++ WARN(LOG_PREFIX ": Ignoring user-selected DH parameters in FIPS mode. Using defaults."); ++ file = NULL; ++ } ++ ++ /* ++ * No dh file, set auto context. ++ */ ++ if (!file) { ++ if (!SSL_CTX_set_dh_auto(ctx, 1)) { ++ ERROR(LOG_PREFIX ": Unable to set DH parameters"); ++ return -1; ++ } ++ ++ return 0; ++ } ++ ++ WARN(LOG_PREFIX ": Setting DH parameters from %s - this is no longer necessary.", file); ++ WARN(LOG_PREFIX ": You should comment out the 'dh_file' configuration item."); ++ ++#else ++ if (!file) { ++ WARN(LOG_PREFIX ": Cannot set DH parameters. DH cipher suites may not work."); ++ return 0; ++ } ++#endif ++ + + if ((bio = BIO_new_file(file, "r")) == NULL) { + ERROR(LOG_PREFIX ": Unable to open DH file - %s", file); +@@ -1422,7 +1907,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (!conf) { +- RWDEBUG("Failed to find TLS configuration in session"); ++ RWDEBUG("(TLS) Failed to find TLS configuration in session"); + return 0; + } + +@@ -1439,7 +1924,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + blob_len = i2d_SSL_SESSION(sess, NULL); + if (blob_len < 1) { + /* something went wrong */ +- if (request) RWDEBUG("Session serialisation failed, couldn't determine required buffer length"); ++ if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length"); + return 0; + } + +@@ -1447,14 +1932,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + /* alloc and convert to ASN.1 */ + sess_blob = malloc(blob_len); + if (!sess_blob) { +- RWDEBUG("Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); ++ RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); + return 0; + } + /* openssl mutates &p */ + p = sess_blob; + rv = i2d_SSL_SESSION(sess, &p); + if (rv != blob_len) { +- if (request) RWDEBUG("Session serialisation failed"); ++ if (request) RWDEBUG("(TLS) Session serialisation failed"); + goto error; + } + +@@ -1463,7 +1948,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + conf->session_cache_path, FR_DIR_SEP, buffer); + fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR); + if (fd < 0) { +- if (request) RERROR("Session serialisation failed, failed opening session file %s: %s", ++ if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s", + filename, fr_syserror(errno)); + goto error; + } +@@ -1486,7 +1971,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + while (todo > 0) { + rv = write(fd, p, todo); + if (rv < 1) { +- if (request) RWDEBUG("Failed writing session: %s", fr_syserror(errno)); ++ if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno)); + close(fd); + goto error; + } +@@ -1494,7 +1979,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + todo -= rv; + } + close(fd); +- if (request) RWDEBUG("Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); ++ if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); + } + + error: +@@ -1595,7 +2080,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (!conf) { +- RWDEBUG("Failed to find TLS configuration in session"); ++ RWDEBUG("(TLS) Failed to find TLS configuration in session"); + return NULL; + } + +@@ -1617,20 +2102,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); + fd = open(filename, O_RDONLY); + if (fd < 0) { +- RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno)); ++ RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno)); + goto error; + } + + rv = fstat(fd, &st); + if (rv < 0) { +- RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); ++ RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); + close(fd); + goto error; + } + + sess_data = talloc_array(NULL, unsigned char, st.st_size); + if (!sess_data) { +- RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); ++ RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); + close(fd); + goto error; + } +@@ -1640,7 +2125,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + while (todo > 0) { + rv = read(fd, q, todo); + if (rv < 1) { +- RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno)); ++ RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno)); + close(fd); + goto error; + } +@@ -1664,7 +2149,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + memcpy(&o, &p, sizeof(o)); + sess = d2i_SSL_SESSION(NULL, o, st.st_size); + if (!sess) { +- RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); ++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + +@@ -1674,7 +2159,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + rv = pairlist_read(talloc_ctx, filename, &pairlist, 1); + if (rv < 0) { + /* not safe to un-persist a session w/o VPs */ +- RWDEBUG("Failed loading persisted VPs for session %s", buffer); ++ RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer); + SSL_SESSION_free(sess); + sess = NULL; + goto error; +@@ -1708,12 +2193,27 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + if (vp) { + if ((request->timestamp + vp->vp_integer) > expires) { + vp->vp_integer = expires - request->timestamp; +- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", + vp->vp_integer); + } + } + } + ++ /* ++ * Resumption MUST use the same EAP type as from ++ * the original packet. ++ */ ++ vp = fr_pair_find_by_num(pairlist->reply, PW_EAP_TYPE, 0, TAG_ANY); ++ if (vp) { ++ VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); ++ ++ if (type && (type->vp_integer != vp->vp_integer)) { ++ REDEBUG("Resumption has changed EAP types for session %s", buffer); ++ REDEBUG("Rejecting session due to protocol violations"); ++ goto error; ++ } ++ } ++ + /* move the cached VPs into the session */ + fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &pairlist->reply, 0, 0, TAG_ANY); + +@@ -1733,22 +2233,366 @@ error: + return sess; + } + +-#ifdef HAVE_OPENSSL_OCSP_H +- +-/** Extract components of OCSP responser URL from a certificate +- * +- * @param[in] cert to extract URL from. +- * @param[out] host_out Portion of the URL (must be freed with free()). +- * @param[out] port_out Port portion of the URL (must be freed with free()). +- * @param[out] path_out Path portion of the URL (must be freed with free()). +- * @param[out] is_https Whether the responder should be contacted using https. +- * @return +- * - 0 if no valid URL is contained in the certificate. +- * - 1 if a URL was found and parsed. +- * - -1 if at least one URL was found, but none could be parsed. +- */ +-static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, +- char **path_out, int *is_https) ++static size_t tls_session_id_binary(SSL_SESSION *ssn, uint8_t *buffer, size_t bufsize) ++{ ++#if OPENSSL_VERSION_NUMBER < 0x10001000L ++ size_t size; ++ ++ size = ssn->session_id_length; ++ if (size > bufsize) size = bufsize; ++ ++ memcpy(buffer, ssn->session_id, size); ++ return size; ++#else ++ unsigned int size; ++ uint8_t const *p; ++ ++ p = SSL_SESSION_get_id(ssn, &size); ++ if (size > bufsize) size = bufsize; ++ ++ memcpy(buffer, p, size); ++ return size; ++#endif ++} ++ ++/* ++ * From TLS-Cache-Method ++ * ++ * All of the save / clear / load callbacks are done with any ++ * OpenSSL locks *unlocked*. So says the OpenSSL code. ++ */ ++#define CACHE_SAVE (1) ++#define CACHE_LOAD (2) ++#define CACHE_CLEAR (3) ++#define CACHE_REFRESH (4) ++ ++static REQUEST *cache_init_fake_request(fr_tls_server_conf_t const *conf, SSL_SESSION *sess, SSL *ssl, ++ uint8_t const *data, size_t size) ++{ ++ VALUE_PAIR *vp; ++ REQUEST *fake, *request = NULL; ++ uint8_t buffer[MAX_SESSION_SIZE]; ++ ++ if (sess) { ++ size = tls_session_id_binary(sess, buffer, sizeof(buffer)); ++ data = buffer; ++ } ++ ++ /* ++ * We get called essentially at random by OpenSSL, with ++ * no information other than the session ID. As a ++ * result, we have to manually set up our own request. ++ */ ++ if (ssl) request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ ++ if (request) { ++ fake = request_alloc_fake(request); ++ } else { ++ fake = request_alloc(NULL); ++ fake->packet = rad_alloc(fake, false); ++ fake->reply = rad_alloc(fake, false); ++ } ++ ++ vp = fr_pair_afrom_num(fake->packet, PW_TLS_SESSION_ID, 0); ++ if (!vp) { ++ talloc_free(fake); ++ return NULL; ++ } ++ ++ fr_pair_value_memcpy(vp, data, size); ++ fr_pair_add(&fake->packet->vps, vp); ++ ++ fake->server = conf->session_cache_server; ++ ++ return fake; ++} ++ ++/* ++ * Clear cached data ++ */ ++static void cbtls_cache_clear(SSL_CTX *ctx, SSL_SESSION *sess) ++{ ++ fr_tls_server_conf_t *conf; ++ REQUEST *fake; ++ ++ conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx); ++ if (!conf) { ++ DEBUG(LOG_PREFIX ": Failed to find TLS configuration in session"); ++ return; ++ } ++ ++ /* ++ * Find the SSL ID from the session, and delete it. ++ * ++ * Don't bother with any parent request. We're in a ++ * timer callback, and there is no request available. ++ */ ++ fake = cache_init_fake_request(conf, sess, NULL, NULL, 0); ++ if (!fake) return; ++ ++ /* ++ * Use &request:TLS-Session-Id to clear the cache entry. ++ */ ++ (void) process_post_auth(CACHE_CLEAR, fake); ++ talloc_free(fake); ++ return; ++} ++ ++/* ++ * OpenSSL calls this function in order to save the session ++ * BEFORE it has sent the final TLS success. So our process here ++ * is to say "yes, we saved it", and then do the *actual* saving ++ * after the TLS success has been sent. ++ */ ++static int cbtls_cache_save(UNUSED SSL *ssl, UNUSED SSL_SESSION *sess) ++{ ++ return 0; ++} ++ ++static int cbtls_cache_save_vps(SSL *ssl, SSL_SESSION *sess, VALUE_PAIR *vps) ++{ ++ fr_tls_server_conf_t *conf; ++ VALUE_PAIR *vp; ++ REQUEST *fake = NULL; ++ size_t size, rv; ++ uint8_t *p, *sess_blob = NULL; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return 0; ++ ++ /* ++ * Find the SSL ID from the session, and save it. ++ * ++ * Save anything from the parent request. ++ */ ++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); ++ if (!fake) return 0; ++ ++ /* find out what length data we need */ ++ size = i2d_SSL_SESSION(sess, NULL); ++ if (size < 1) return 0; ++ ++ /* Do not convert to TALLOC - it's passed to OpenSSL */ ++ /* alloc and convert to ASN.1 */ ++ MEM(sess_blob = malloc(size)); ++ ++ /* openssl mutates &p */ ++ p = sess_blob; ++ rv = i2d_SSL_SESSION(sess, &p); ++ if (rv != size) goto error; ++ ++ vp = fr_pair_afrom_num(fake->state_ctx, PW_TLS_SESSION_DATA, 0); ++ if (!vp) goto error; ++ ++ fr_pair_value_memcpy(vp, sess_blob, size); ++ fr_pair_add(&fake->state, vp); ++ ++ if (vps) fr_pair_add(&fake->reply->vps, fr_pair_list_copy(fake->reply, vps)); ++ ++ /* ++ * Use &request:TLS-Session-Id to save the ++ * &session-state:TLS-Session-Data values. ++ * ++ * The current &reply: list is the list of VPs which ++ * should be cached. ++ * ++ * Any other attributes which need to be saved can be ++ * read from the &outer.reply: list. ++ */ ++ (void) process_post_auth(CACHE_SAVE, fake); ++ ++error: ++ if (fake) talloc_free(fake); ++ free(sess_blob); ++ ++ return 0; ++} ++ ++static int cbtls_cache_refresh(SSL *ssl, SSL_SESSION *sess) ++{ ++ fr_tls_server_conf_t *conf; ++ REQUEST *fake = NULL; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return 0; ++ ++ /* ++ * Find the SSL ID from the session, and save it. ++ * ++ * Save anything from the parent request. ++ */ ++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); ++ if (!fake) return 0; ++ /* ++ * Use &request:TLS-Session-Id to update the cache ++ * entry so that it doesn't not expire. ++ */ ++ (void) process_post_auth(CACHE_REFRESH, fake); ++ ++ talloc_free(fake); ++ ++ return 0; ++} ++ ++#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++static SSL_SESSION *cbtls_cache_load(SSL *ssl, unsigned char *data, int len, int *copy) ++#else ++static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int len, int *copy) ++#endif ++{ ++ fr_tls_server_conf_t *conf; ++ size_t size; ++ uint8_t const *p; ++ VALUE_PAIR *vp, *vps; ++ TALLOC_CTX *talloc_ctx; ++ SSL_SESSION *sess = NULL; ++ REQUEST *fake = NULL; ++ REQUEST *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ char buffer[2 * MAX_SESSION_SIZE + 1]; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return NULL; ++ ++ rad_assert(request); ++ ++ size = len; ++ if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; ++ ++ if (fr_debug_lvl > 1) { ++ fr_bin2hex(buffer, data, size); ++ RDEBUG2("Peer requested cached session: %s", buffer); ++ } ++ ++ *copy = 0; ++ ++ /* ++ * Take the given SSL ID, and create a fake request. ++ * ++ * Don't bother parenting it from another request. We do ++ * this for a number of reasons. ++ * ++ * One is that rest of the code expects that the VPs will ++ * be added to fr_tls_ex_index_vps. So we don't want to ++ * be poking the request directly, as that will result in ++ * a change of behavior. ++ * ++ * The larger reason is that we do _not_ want to actually ++ * update the reply, until such time as we know that the ++ * user has been authenticated. ++ */ ++ fake = cache_init_fake_request(conf, NULL, NULL, data, size); ++ if (!fake) return 0; ++ ++ /* ++ * Use &request:TLS-Session-Id to load the cached ++ * session. ++ * ++ * The "cache load { ...}" section should put the reply ++ * attributes into the &reply: list, and the ++ * &session-state:TLS-Session-Data attribute. ++ * ++ * Why? Because v4 does it that way, and there aren't ++ * really good reasons for doing it differently. ++ */ ++ (void) process_post_auth(CACHE_LOAD, fake); ++ ++ /* ++ * Enforce client certificate expiration. ++ */ ++ vp = fr_pair_find_by_num(fake->reply->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY); ++ if (vp) { ++ time_t expires; ++ ++ if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { ++ RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror()); ++ SSL_SESSION_free(sess); ++ sess = NULL; ++ goto error; ++ } ++ ++ if (expires <= request->timestamp) { ++ RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer); ++ SSL_SESSION_free(sess); ++ sess = NULL; ++ goto error; ++ } ++ ++ /* ++ * Account for Session-Timeout, if it's available. ++ */ ++ vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); ++ if (vp) { ++ if ((request->timestamp + vp->vp_integer) > expires) { ++ vp->vp_integer = expires - request->timestamp; ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", ++ vp->vp_integer); ++ } ++ } ++ } ++ ++ /* ++ * Try to de-serialize the session data. ++ */ ++ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY); ++ if (!vp) { ++ RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer); ++ goto error; ++ } ++ ++ /* ++ * OpenSSL mutates what's passed in, so we assign sess_data to q, ++ * so the value of q gets mutated, and not the value of sess_data. ++ * ++ * We then need a pointer to hold &q, but it can't be const, because ++ * clang complains about lack of consting in nested pointer types. ++ * ++ * So we memcpy the value of that pointer, to one that ++ * does have a const, which we then pass into d2i_SSL_SESSION *sigh*. ++ */ ++ p = vp->vp_octets; ++ sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length); ++ if (!sess) { ++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); ++ goto error; ++ } ++ ++ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); ++ vps = NULL; ++ ++ /* move the cached VPs into the session */ ++ fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &fake->reply->vps, 0, 0, TAG_ANY); ++ ++ SSL_SESSION_set_ex_data(sess, fr_tls_ex_index_vps, vps); ++ RDEBUG("Successfully restored session %s", buffer); ++ rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:"); ++ ++ /* ++ * The "restore VPs from OpenSSL cache" code is ++ * now in eaptls_process() ++ */ ++ ++error: ++ if (fake) talloc_free(fake); ++ ++ return sess; ++} ++ ++#ifdef HAVE_OPENSSL_OCSP_H ++ ++/** Extract components of OCSP responser URL from a certificate ++ * ++ * @param[in] cert to extract URL from. ++ * @param[out] host_out Portion of the URL (must be freed with free()). ++ * @param[out] port_out Port portion of the URL (must be freed with free()). ++ * @param[out] path_out Path portion of the URL (must be freed with free()). ++ * @param[out] is_https Whether the responder should be contacted using https. ++ * @return ++ * - 0 if no valid URL is contained in the certificate. ++ * - 1 if a URL was found and parsed. ++ * - -1 if at least one URL was found, but none could be parsed. ++ */ ++static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, ++ char **path_out, int *is_https) + { + int i; + bool found_uri = false; +@@ -1811,7 +2655,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + VALUE_PAIR *vp; + + if (issuer_cert == NULL) { +- RWDEBUG("Could not get issuer certificate"); ++ RWDEBUG("(TLS) Could not get issuer certificate"); + goto skipped; + } + +@@ -1836,7 +2680,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + /* Reading the libssl src, they do a strdup on the URL, so it could of been const *sigh* */ + OCSP_parse_url(url, &host, &port, &path, &use_ssl); + if (!host || !port || !path) { +- RWDEBUG("ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); ++ RWDEBUG("(TLS) ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); + goto skipped; + } + } else { +@@ -1845,15 +2689,15 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + ret = ocsp_parse_cert_url(client_cert, &host, &port, &path, &use_ssl); + switch (ret) { + case -1: +- RWDEBUG("ocsp: Invalid URL in certificate. Not doing OCSP"); ++ RWDEBUG("(TLS) ocsp: Invalid URL in certificate. Not doing OCSP"); + break; + + case 0: + if (conf->ocsp_url) { +- RWDEBUG("ocsp: No OCSP URL in certificate, falling back to configured URL"); ++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate, falling back to configured URL"); + goto use_ocsp_url; + } +- RWDEBUG("ocsp: No OCSP URL in certificate. Not doing OCSP"); ++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate. Not doing OCSP"); + goto skipped; + + case 1: +@@ -1865,7 +2709,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + + /* Check host and port length are sane, then create Host: HTTP header */ + if ((strlen(host) + strlen(port) + 2) > sizeof(hostheader)) { +- RWDEBUG("ocsp: Host and port too long"); ++ RWDEBUG("(TLS) ocsp: Host and port too long"); + goto skipped; + } + snprintf(hostheader, sizeof(hostheader), "%s:%s", host, port); +@@ -2038,15 +2882,15 @@ ocsp_end: + vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); + vp->vp_integer = 2; /* skipped */ + if (conf->ocsp_softfail) { +- RWDEBUG("ocsp: Unable to check certificate, assuming it's valid"); +- RWDEBUG("ocsp: This may be insecure"); ++ RWDEBUG("(TLS) ocsp: Unable to check certificate, assuming it's valid"); ++ RWDEBUG("(TLS) ocsp: This may be insecure"); + + /* Remove OpenSSL errors from queue or handshake will fail */ + while (ERR_get_error()); + + ocsp_status = OCSP_STATUS_SKIPPED; + } else { +- REDEBUG("ocsp: Unable to check certificate, failing"); ++ REDEBUG("(TLS) ocsp: Unable to check certificate, failing"); + ocsp_status = OCSP_STATUS_FAILED; + } + break; +@@ -2054,7 +2898,7 @@ ocsp_end: + default: + vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); + vp->vp_integer = 0; /* no */ +- REDEBUG("ocsp: Certificate has been expired/revoked"); ++ REDEBUG("(TLS) ocsp: Certificate has been expired/revoked"); + break; + } + +@@ -2087,6 +2931,10 @@ static char const *cert_attr_names[9][2] = { + #define FR_TLS_SAN_UPN (7) + #define FR_TLS_VALID_SINCE (8) + ++static const char *cert_names[2] = { ++ "client", "server", ++}; ++ + /* + * Before trusting a certificate, you must make sure that the + * certificate is 'valid'. There are several steps that your +@@ -2152,12 +3000,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + lookup = depth; + +- /* +- * Log client/issuing cert. If there's an error, log +- * issuing cert. +- */ +- if ((lookup > 1) && !my_ok) lookup = 1; +- + /* + * Retrieve the pointer to the SSL of the connection currently treated + * and the application specific data stored into the SSL object. +@@ -2177,14 +3019,37 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); + ++ /* ++ * Log client/issuing cert. If there's an error, log ++ * issuing cert. ++ * ++ * Inbound: 0 = client, 1 = server (intermediate CA), 2 = issuing CA ++ * Outbound: 0 = server, 2 = issuing CA. ++ * ++ * Our array of certificates uses 0 for client, and 1 for server. We ++ * also ignore subsequent certs. ++ */ ++ if (lookup > 1) { ++ if (!my_ok) lookup = 1; ++ ++ } else if (lookup == 0) { ++ /* ++ * This flag is only set for outbound ++ * connections. And then allows us to remap SSL ++ * offset 0 (server) to our offset 1 (also ++ * server). ++ */ ++ lookup = (SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER) != NULL); ++ } ++ + /* + * Get the Serial Number + */ + buf[0] = '\0'; + sn = X509_get_serialNumber(client_cert); + +- RDEBUG2("TLS - Creating attributes from certificate OIDs"); +- RINDENT(); ++ RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup ]); ++ RINDENT(); + + /* + * For this next bit, we create the attributes *only* if +@@ -2328,8 +3193,14 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + if (!my_ok) { + char const *p = X509_verify_cert_error_string(err); +- RERROR("SSL says error %d : %s", err, p); ++ RERROR("(TLS) OpenSSL says error %d : %s", err, p); + REXDENT(); ++ ++ /* ++ * Copy certs even on failure so that they can be logged. ++ */ ++ if (certs && request) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); ++ + return my_ok; + } + +@@ -2405,7 +3276,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + fr_bin2hex(value + 2, srcp, asn1len); + } + +- + vp = fr_pair_make(talloc_ctx, certs, attribute, value, T_OP_ADD); + if (!vp) { + RDEBUG3("Skipping %s += '%s'. Please check that both the " +@@ -2446,20 +3316,28 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + switch (X509_STORE_CTX_get_error(ctx)) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: +- RERROR("issuer=%s", issuer); ++ RERROR("(TLS) unable to get issuer certificate for issuer=%s", issuer); + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: ++ RERROR("(TLS) Failed with certificate not yet valid."); ++ break; ++ + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: +- RERROR("notBefore="); ++ RERROR("(TLS) Failed with error in certificate 'not before' field."); + #if 0 + ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert)); + #endif + break; + + case X509_V_ERR_CERT_HAS_EXPIRED: ++ RERROR("(TLS) Failed with certificate has expired."); ++ break; ++ + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: +- RERROR("notAfter="); ++ RERROR("(TLS) Failed with err in certificate 'no after' field.."); ++ break; ++ + #if 0 + ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert)); + #endif +@@ -2471,12 +3349,49 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + * checks. + */ + if (depth == 0) { ++ tls_session_t *ssn = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_SSN); ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ STACK_OF(X509)* untrusted = NULL; ++#endif ++ ++ rad_assert(ssn != NULL); ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ /* ++ * See if there are any untrusted certificates. ++ * If so, complain about them. ++ */ ++ untrusted = X509_STORE_CTX_get0_untrusted(ctx); ++ if (untrusted) { ++ if (conf->disallow_untrusted || RDEBUG_ENABLED2) { ++ int i; ++ ++ WARN("Certificate chain - %i cert(s) untrusted", ++ X509_STORE_CTX_get_num_untrusted(ctx)); ++ for (i = sk_X509_num(untrusted); i > 0 ; i--) { ++ X509 *this_cert = sk_X509_value(untrusted, i - 1); ++ ++ X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject)); ++ subject[sizeof(subject) - 1] = '\0'; ++ ++ WARN("(TLS) untrusted certificate with depth [%i] subject name %s", ++ i - 1, subject); ++ } ++ } ++ ++ if (conf->disallow_untrusted) { ++ AUTH(LOG_PREFIX ": There are untrusted certificates in the certificate chain. Rejecting."); ++ my_ok = 0; ++ } ++ } ++#endif ++ + /* + * If the conf tells us to, check cert issuer + * against the specified value and fail + * verification if they don't match. + */ +- if (conf->check_cert_issuer && ++ if (my_ok && conf->check_cert_issuer && + (strcmp(issuer, conf->check_cert_issuer) != 0)) { + AUTH(LOG_PREFIX ": Certificate issuer (%s) does not match specified value (%s)!", + issuer, conf->check_cert_issuer); +@@ -2595,45 +3510,54 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + unlink(filename); + break; + } ++ ++ /* ++ * Track that we've verified the client certificate. ++ */ ++ ssn->client_cert_ok = (my_ok == 1); + } /* depth == 0 */ + ++ /* ++ * Copy certs to request even on failure, so that the ++ * user can log them. ++ */ + if (certs && request && !my_ok) { + fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); + } + + if (RDEBUG_ENABLED3) { +- RDEBUG3("chain-depth : %d", depth); +- RDEBUG3("error : %d", err); ++ RDEBUG3("(TLS) chain-depth : %d", depth); ++ RDEBUG3("(TLS) error : %d", err); + + if (identity) RDEBUG3("identity : %s", *identity); +- RDEBUG3("common name : %s", common_name); +- RDEBUG3("subject : %s", subject); +- RDEBUG3("issuer : %s", issuer); +- RDEBUG3("verify return : %d", my_ok); ++ RDEBUG3("(TLS) common name : %s", common_name); ++ RDEBUG3("(TLS) subject : %s", subject); ++ RDEBUG3("(TLS) issuer : %s", issuer); ++ RDEBUG3("(TLS) verify return : %d", my_ok); + } + + return (my_ok != 0); + } + + +-#ifdef HAVE_OPENSSL_OCSP_H + /* +- * Create Global X509 revocation store and use it to verify +- * OCSP responses ++ * Configure a X509 CA store to verify OCSP or client repsonses + * + * - Load the trusted CAs + * - Load the trusted issuer certificates ++ * - Configure CRLs check if needed + */ +-static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) ++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf) + { +- X509_STORE *store = NULL; ++ X509_STORE *store = X509_STORE_new(); + +- store = X509_STORE_new(); ++ if (store == NULL) return NULL; + + /* Load the CAs we trust */ + if (conf->ca_file || conf->ca_path) + if (!X509_STORE_load_locations(store, conf->ca_file, conf->ca_path)) { + tls_error_log(NULL, "Error reading Trusted root CA list \"%s\"", conf->ca_file); ++ X509_STORE_free(store); + return NULL; + } + +@@ -2645,38 +3569,65 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) + if (conf->check_all_crl) + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK_ALL); + #endif ++ ++#if defined(X509_V_FLAG_PARTIAL_CHAIN) ++ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); ++#endif ++ + return store; + } +-#endif /* HAVE_OPENSSL_OCSP_H */ + + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH + static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use) + { +- int nid; +- EC_KEY *ecdh; ++ if (!disable_single_dh_use) { ++ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); ++ } + +- if (!ecdh_curve || !*ecdh_curve) return 0; ++ if (!ecdh_curve) return 0; + +- nid = OBJ_sn2nid(ecdh_curve); +- if (!nid) { +- ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); +- return -1; +- } ++#if OPENSSL_VERSION_NUMBER >= 0x1000200fL ++ /* ++ * A colon-separated list of curves. ++ */ ++ if (*ecdh_curve) { ++ char *list; + +- ecdh = EC_KEY_new_by_curve_name(nid); +- if (!ecdh) { +- ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); +- return -1; ++ memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */ ++ ++ if (SSL_CTX_set1_curves_list(ctx, list) == 0) { ++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); ++ return -1; ++ } + } + +- SSL_CTX_set_tmp_ecdh(ctx, ecdh); ++ (void) SSL_CTX_set_ecdh_auto(ctx, 1); ++#else ++ /* ++ * Use APIs for older versions of OpenSSL. ++ */ ++ { ++ int nid; ++ EC_KEY *ecdh; + +- if (!disable_single_dh_use) { +- SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); +- } ++ nid = OBJ_sn2nid(ecdh_curve); ++ if (!nid) { ++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); ++ return -1; ++ } + +- EC_KEY_free(ecdh); ++ ecdh = EC_KEY_new_by_curve_name(nid); ++ if (!ecdh) { ++ ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); ++ return -1; ++ } ++ ++ SSL_CTX_set_tmp_ecdh(ctx, ecdh); ++ ++ EC_KEY_free(ecdh); ++ } ++#endif + + return 0; + } +@@ -2708,10 +3659,32 @@ int tls_global_init(bool spawn_flag, bool check) + * and we don't want to have tls.c depend on globals. + */ + if (spawn_flag && !check && (tls_mutexes_init() < 0)) { +- ERROR("FATAL: Failed to set up SSL mutexes"); ++ ERROR("(TLS) FATAL: Failed to set up SSL mutexes"); ++ return -1; ++ } ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ /* ++ * Load the default provider for most algorithms ++ */ ++ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); ++ if (!openssl_default_provider) { ++ ERROR("(TLS) Failed loading default provider"); + return -1; + } + ++ /* ++ * Needed for MD4 ++ * ++ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms ++ */ ++ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); ++ if (!openssl_legacy_provider) { ++ ERROR("(TLS) Failed loading legacy provider"); ++ return -1; ++ } ++#endif ++ + return 0; + } + +@@ -2777,6 +3750,19 @@ void tls_global_cleanup(void) + #ifndef OPENSSL_NO_ENGINE + ENGINE_cleanup(); + #endif ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { ++ ERROR("Failed unloading default provider"); ++ } ++ openssl_default_provider = NULL; ++ ++ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { ++ ERROR("Failed unloading legacy provider"); ++ } ++ openssl_legacy_provider = NULL; ++#endif ++ + CONF_modules_unload(1); + ERR_free_strings(); + EVP_cleanup(); +@@ -2797,9 +3783,6 @@ static const FR_NAME_NUMBER version2int[] = { + #endif + #ifdef TLS1_3_VERSION + { "1.3", TLS1_3_VERSION }, +-#endif +-#ifdef TLS1_4_VERSION +- { "1.4", TLS1_4_VERSION }, + #endif + { NULL, 0 } + }; +@@ -2816,18 +3799,18 @@ static const FR_NAME_NUMBER version2int[] = { + * - Load the Private key & the certificate + * - Set the Context options & Verify options + */ +-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) ++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file) + { + SSL_CTX *ctx; + X509_STORE *certstore; + int verify_mode = SSL_VERIFY_NONE; +- int ctx_options = 0; +- int ctx_tls_versions = 0; ++ int ctx_options = 0, ctx_available = 0; + int type; + #ifdef CHECK_FOR_PSK_CERTS + bool psk_and_certs = false; + #endif +- bool insecure_tls_version = false; ++ int min_version; ++ int max_version; + + /* + * SHA256 is in all versions of OpenSSL, but isn't +@@ -2840,7 +3823,7 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) + + ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods". Idiots. */ + if (!ctx) { +- tls_error_log(NULL, "Failed creating TLS context"); ++ tls_error_log(NULL, "Failed creating OpenSSL context"); + return NULL; + } + +@@ -3033,39 +4016,56 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) + * the cert chain needs to be given in PEM from + * openSSL.org + */ +- if (!conf->certificate_file) goto load_ca; ++ if (!chain_file) chain_file = conf->certificate_file; ++ if (!chain_file) goto load_ca; + + if (type == SSL_FILETYPE_PEM) { +- if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) { ++ if (!(SSL_CTX_use_certificate_chain_file(ctx, chain_file))) { + tls_error_log(NULL, "Failed reading certificate file \"%s\"", +- conf->certificate_file); ++ chain_file); + return NULL; + } + +- } else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) { ++ } else if (!(SSL_CTX_use_certificate_file(ctx, chain_file, type))) { + tls_error_log(NULL, "Failed reading certificate file \"%s\"", +- conf->certificate_file); ++ chain_file); + return NULL; + } + +- /* Load the CAs we trust */ + load_ca: ++ /* ++ * Load the CAs we trust and configure CRL checks if needed ++ */ ++ if (conf->ca_file || conf->ca_path) { ++ if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL; ++ SSL_CTX_set_cert_store(ctx, certstore); ++ } else { + #if defined(X509_V_FLAG_PARTIAL_CHAIN) +- X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN); ++ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN); + #endif +- if (conf->ca_file || conf->ca_path) { +- if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) { +- tls_error_log(NULL, "Failed reading Trusted root CA list \"%s\"", +- conf->ca_file); +- return NULL; +- } + } ++ + if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file)); + +- if (conf->private_key_file) { +- if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) { ++ conf->ca_path_last_reload = time(NULL); ++ conf->old_x509_store = NULL; ++ ++ /* ++ * Disable reloading of cert store if we're not using CA path ++ */ ++ if (!conf->ca_path) conf->ca_path_reload_interval = 0; ++ ++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_reload_interval < 300) { ++ DEBUG2("ca_path_reload_interval is set too low, reset it to 300"); ++ conf->ca_path_reload_interval = 300; ++ } ++ ++ /* Load private key */ ++ if (!private_key_file) private_key_file = conf->private_key_file; ++ if (private_key_file) { ++ if (!(SSL_CTX_use_PrivateKey_file(ctx, private_key_file, type))) { + tls_error_log(NULL, "Failed reading private key file \"%s\"", +- conf->private_key_file); ++ private_key_file); + return NULL; + } + +@@ -3088,6 +4088,18 @@ post_ca: + ctx_options |= SSL_OP_NO_SSLv2; + ctx_options |= SSL_OP_NO_SSLv3; + ++ /* ++ * If set then dummy Change Cipher Spec (CCS) messages are sent in ++ * TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2 ++ * so that middleboxes that do not understand TLSv1.3 will not drop ++ * the connection. This isn't needed for EAP-TLS, so we disable it. ++ * ++ * EAP (hopefully) does not have middlebox deployments ++ */ ++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT ++ ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT; ++#endif ++ + /* + * SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0 + * +@@ -3095,168 +4107,291 @@ post_ca: + * below, so we don't need to check for them explicitly. + * + * TLS1_3_VERSION is available in OpenSSL 1.1.1. +- * +- * TLS1_4_VERSION in speculative. + */ +- { +- int min_version = 0; +- int max_version = 0; + ++ /* ++ * Get the max version from the configuration files. ++ */ ++ if (conf->tls_max_version && *conf->tls_max_version) { ++ max_version = fr_str2int(version2int, conf->tls_max_version, 0); ++ if (!max_version) { ++ ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); ++ return NULL; ++ } ++ } else { + /* +- * Get the max version. ++ * Pick the maximum version available at compile ++ * time. + */ +- if (conf->tls_max_version && *conf->tls_max_version) { +- max_version = fr_str2int(version2int, conf->tls_max_version, 0); +- if (!max_version) { +- ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); +- return NULL; +- } +- } else { +- /* +- * Pick the maximum one we know about. +- */ +-#ifdef TLS1_4_VERSION +- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */ +-#elif defined(TLS1_3_VERSION) +- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */ ++#if defined(TLS1_3_VERSION) ++#ifdef WITH_RADIUSV11 ++ /* ++ * RADIUS 1.1 requires TLS 1.3 or later. ++ */ ++ if (conf->radiusv11) { ++ max_version = TLS1_3_VERSION; ++ } else ++#endif ++ ++ ++ max_version = TLS1_2_VERSION; /* yes, we only use TLS 1.3 if it's EXPLICITELY ENABLED */ + #elif defined(TLS1_2_VERSION) +- max_version = TLS1_2_VERSION; ++ max_version = TLS1_2_VERSION; + #elif defined(TLS1_1_VERSION) +- max_version = TLS1_1_VERSION; ++ max_version = TLS1_1_VERSION; + #else +- max_version = TLS1_VERSION; ++ max_version = TLS1_VERSION; + #endif +- } ++ } + ++ /* ++ * Get the min version from the configuration files. ++ */ ++ if (conf->tls_min_version && *conf->tls_min_version) { ++ min_version = fr_str2int(version2int, conf->tls_min_version, 0); ++ if (!min_version) { ++ ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); ++ return NULL; ++ } ++ } else { ++#ifdef WITH_RADIUSV11 + /* +- * Set these for the rest of the code. ++ * RADIUS 1.1 requires TLS 1.3 or later. + */ +-#ifdef TLS1_2_VERSION +- if (max_version < TLS1_2_VERSION) { +- conf->disable_tlsv1_2 = true; +- } +-#endif +-#ifdef TLS1_1_VERSION +- if (max_version < TLS1_1_VERSION) { +- conf->disable_tlsv1_1 = true; +- } ++ if (conf->radiusv11) { ++ min_version = TLS1_3_VERSION; ++ } else + #endif +- + /* +- * Get the min version. ++ * Allow TLS 1.0. It is horribly insecure, but ++ * some systems still use it. + */ +- if (conf->tls_min_version && *conf->tls_min_version) { +- min_version = fr_str2int(version2int, conf->tls_min_version, 0); +- if (!min_version) { +- ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); +- return NULL; +- } +- } else { +- min_version = TLS1_VERSION; +- } ++ min_version = TLS1_VERSION; ++ } + +- /* +- * Compare the two. +- */ +- if (min_version > max_version) { +- ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", +- conf->tls_min_version, conf->tls_max_version); +- return NULL; +- } ++ /* ++ * Compare the two. ++ */ ++ if ((min_version > max_version) || (max_version < min_version)) { ++ ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", ++ conf->tls_min_version, conf->tls_max_version); ++ return NULL; ++ } + +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L + #ifdef CHECK_FOR_PSK_CERTS +- /* +- * Disable TLS 1.3 when using PSKs and certs. +- * This doesn't work. +- * +- * It's best to disable the offending +- * configuration and warn about it. The +- * alternative is to have the admin wonder why it +- * doesn't work. +- * +- * Note that the admin can over-ride this by +- * setting "min_version = max_version = 1.3" +- */ +- if (psk_and_certs && +- (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { +- max_version = TLS1_2_VERSION; +- radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); +- } ++ /* ++ * Disable TLS 1.3 when using PSKs and certs. ++ * This doesn't work. ++ * ++ * It's best to disable the offending ++ * configuration and warn about it. The ++ * alternative is to have the admin wonder why it ++ * doesn't work. ++ * ++ * Note that the admin can over-ride this by ++ * setting "min_version = max_version = 1.3" ++ */ ++ if (psk_and_certs && ++ (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { ++ max_version = TLS1_2_VERSION; ++ radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); ++ } + #endif + +- if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { +- ERROR("Failed setting TLS maximum version"); +- return NULL; ++ /* ++ * No one should be using TLS 1.0 or TLS 1.1 any more ++ * ++ * If TLS1.2 isn't defined by OpenSSL, then we _know_ ++ * it's an insecure version of OpenSSL. ++ */ ++#ifdef TLS1_2_VERSION ++ if (max_version < TLS1_2_VERSION) ++#endif ++ { ++ if (rad_debug_lvl) { ++ WARN(LOG_PREFIX ": The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); ++ WARN(LOG_PREFIX ": Please set: tls_min_version = '1.2'"); + } ++ } + +- if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { +- ERROR("Failed setting TLS minimum version"); ++#ifdef SSL_OP_NO_TLSv1 ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1) { ++ if (min_version == TLS1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'min_version = 1.0'. These cannot both be true."); + return NULL; + } ++ if (max_version == TLS1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'max_version = 1.0'. These cannot both be true."); ++ return NULL; ++ } ++ ctx_options |= SSL_OP_NO_TLSv1; ++ } + +- /* +- * No one should be using TLS 1.0 or TLS 1.1 any more +- */ +- if (min_version < TLS1_2_VERSION) insecure_tls_version = true; +-#else /* OpenSSL version < 1.1.0 */ ++ if (min_version > TLS1_VERSION) ctx_options |= SSL_OP_NO_TLSv1; + +-#ifdef SSL_OP_NO_TLSv1 +- insecure_tls_version |= (conf->disable_tlsv1 == false); ++ ctx_available |= SSL_OP_NO_TLSv1; + #endif ++ + #ifdef SSL_OP_NO_TLSv1_1 +- insecure_tls_version |= (conf->disable_tlsv1_1 == false); ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1_1) { ++ if (min_version <= TLS1_1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'min_version <= 1.1'. These cannot both be true."); ++ return NULL; ++ } ++ if (max_version == TLS1_1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.1'. These cannot both be true."); ++ return NULL; ++ } ++ ctx_options |= SSL_OP_NO_TLSv1_1; ++ } ++ ++ if (min_version > TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; ++ if (max_version < TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; ++ ++ ctx_available |= SSL_OP_NO_TLSv1_1; + #endif +-#endif /* OpenSSL version ? 1.1.0 */ + +- if (rad_debug_lvl && insecure_tls_version) { +- WARN("The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); +- WARN("Please set: tls_min_version = \"1.2\""); ++#ifdef SSL_OP_NO_TLSv1_2 ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1_2) { ++ if (min_version <= TLS1_2_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'. These cannot both be true."); ++ return NULL; ++ } ++ if (max_version == TLS1_2_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'. These cannot both be true."); ++ return NULL; + } ++ ctx_options |= SSL_OP_NO_TLSv1_2; + } ++ ctx_available |= SSL_OP_NO_TLSv1_2; ++ ++ if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; ++ if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; ++#endif ++ ++#ifdef SSL_OP_NO_TLSv1_3 ++ ctx_available |= SSL_OP_NO_TLSv1_3; ++ if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; ++ if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; ++#endif + ++ ++#ifdef WITH_RADIUSV11 + /* +- * For historical config compatibility, we also allow +- * these, but complain if the admin uses them. ++ * RADIUS 1.1 requires TLS 1.3 or later. + */ +-#ifdef SSL_OP_NO_TLSv1 +- if (conf->disable_tlsv1) { +- ctx_options |= SSL_OP_NO_TLSv1; +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1"); ++ if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) { ++ ERROR(LOG_PREFIX ": Please set 'tls_min_version = 1.2' or greater to use 'radiusv1_1 = true'"); ++ return NULL; ++ } + #endif ++ ++ /* ++ * Set the cipher list if we were told to do so. We do ++ * this before setting min/max TLS version. In a sane ++ * world, OpenSSL would error out if we set the max TLS ++ * version to something which was unsupported by the ++ * current security level. However, this is OpenSSL. If ++ * you set conflicting options, it doesn't give an error. ++ * Instead, it just picks something to do. ++ */ ++ if (conf->cipher_list) { ++ if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { ++ tls_error_log(NULL, "Failed setting cipher list"); ++ return NULL; ++ } + } + +- ctx_tls_versions |= SSL_OP_NO_TLSv1; ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ if (conf->sigalgs_list) { ++ char *list; ++ ++ memcpy(&list, &(conf->sigalgs_list), sizeof(list)); /* const issues */ ++ ++ if (SSL_CTX_set1_sigalgs_list(ctx, list) == 0) { ++ tls_error_log(NULL, "Failed setting signature list '%s'", conf->sigalgs_list); ++ return NULL; ++ } ++ } + #endif +-#ifdef SSL_OP_NO_TLSv1_1 +- if (conf->disable_tlsv1_1) { +- ctx_options |= SSL_OP_NO_TLSv1_1; +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); ++ ++ /* ++ * Tell OpenSSL PRETTY PLEASE MAY WE USE TLS 1.1. ++ * ++ * Because saying "use TLS 1.1" isn't enough. We have to ++ * send it flowers and cake. ++ */ ++ if (min_version <= TLS1_1_VERSION) { ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ int seclevel = SSL_CTX_get_security_level(ctx); ++ int required;; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ required = 0; ++#else ++ required = 1; + #endif +- } + +- ctx_tls_versions |= SSL_OP_NO_TLSv1_1; ++ if (seclevel != required) { ++ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=%d\"", required); ++ } ++ ++#else ++ /* ++ * No API to get the security level. Just guess based on the string in the cipher_list. ++ */ ++ if (conf->cipher_list && ++ !strstr(conf->cipher_list, "DEFAULT@SECLEVEL=1")) { ++ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=1\""); ++ } + #endif +-#ifdef SSL_OP_NO_TLSv1_2 ++ } + +- if (conf->disable_tlsv1_2) { +- ctx_options |= SSL_OP_NO_TLSv1_2; + #if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); +-#endif ++ if (conf->disable_tlsv1) { ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1'"); ++ } ++ if (conf->disable_tlsv1_1) { ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_1'"); ++ } ++ if (conf->disable_tlsv1_2) { ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_2'"); + } + +- ctx_tls_versions |= SSL_OP_NO_TLSv1_2; ++ ctx_options &= ~(ctx_available); /* clear these flags, as they're not needed. */ + +-#endif ++ if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { ++ ERROR("Failed setting TLS maximum version"); ++ return NULL; ++ } ++ if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { ++ ERROR("Failed setting TLS minimum version"); ++ return NULL; ++ } ++#endif /* OpenSSL version < 1.1.0 */ + +- if ((ctx_options & ctx_tls_versions) == ctx_tls_versions) { ++ if ((ctx_options & ctx_available) == ctx_available) { + ERROR(LOG_PREFIX ": You have disabled all available TLS versions. EAP will not work"); + return NULL; + } + ++ /* ++ * Cache min / max TLS version so that we can ++ * programatically disable TLS 1.3 for TTLS, PEAP, and ++ * FAST. ++ */ ++ conf->min_version = min_version; ++ conf->max_version = max_version; ++ + #ifdef SSL_OP_NO_TICKET + ctx_options |= SSL_OP_NO_TICKET; + #endif +@@ -3291,6 +4426,19 @@ post_ca: + + SSL_CTX_set_options(ctx, ctx_options); + ++ /* ++ * TLS 1.3 introduces the concept of early data (also known as zero ++ * round trip data or 0-RTT data). Early data allows a client to send ++ * data to a server in the first round trip of a connection, without ++ * waiting for the TLS handshake to complete if the client has spoken ++ * to the same server recently. This doesn't work for EAP, so we ++ * disable early data. ++ * ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ SSL_CTX_set_max_early_data(ctx, 0); ++#endif ++ + /* + * TODO: Set the RSA & DH + * SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa); +@@ -3336,12 +4484,21 @@ post_ca: + /* + * Cache sessions on disk if requested. + */ +- if (conf->session_cache_path) { ++ if (conf->session_cache_path && *conf->session_cache_path) { + SSL_CTX_sess_set_new_cb(ctx, cbtls_new_session); + SSL_CTX_sess_set_get_cb(ctx, cbtls_get_session); + SSL_CTX_sess_set_remove_cb(ctx, cbtls_remove_session); + } + ++ /* ++ * Or run the cache through a virtual server. ++ */ ++ if (conf->session_cache_server && *conf->session_cache_server) { ++ SSL_CTX_sess_set_new_cb(ctx, cbtls_cache_save); ++ SSL_CTX_sess_set_get_cb(ctx, cbtls_cache_load); ++ SSL_CTX_sess_set_remove_cb(ctx, cbtls_cache_clear); ++ } ++ + SSL_CTX_set_quiet_shutdown(ctx, 1); + if (fr_tls_ex_index_vps < 0) + fr_tls_ex_index_vps = SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL); +@@ -3359,6 +4516,17 @@ post_ca: + } + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK); + ++#ifdef X509_V_FLAG_USE_DELTAS ++ /* ++ * If set, delta CRLs (if present) are used to ++ * determine certificate status. If not set ++ * deltas are ignored. ++ * ++ * So it's safe to always set this flag. ++ */ ++ X509_STORE_set_flags(certstore, X509_V_FLAG_USE_DELTAS); ++#endif ++ + #ifdef X509_V_FLAG_CRL_CHECK_ALL + if (conf->check_all_crl) + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK_ALL); +@@ -3389,16 +4557,6 @@ post_ca: + } + #endif + +- /* +- * Set the cipher list if we were told to +- */ +- if (conf->cipher_list) { +- if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { +- tls_error_log(NULL, "Failed setting cipher list"); +- return NULL; +- } +- } +- + /* + * Setup session caching + */ +@@ -3424,9 +4582,9 @@ post_ca: + (unsigned int) strlen(conf->session_context_id)); + + /* +- * Our timeout is in hours, this is in seconds. ++ * Our lifetime is in hours, this is in seconds. + */ +- SSL_CTX_set_timeout(ctx, conf->session_timeout * 3600); ++ SSL_CTX_set_timeout(ctx, conf->session_lifetime * 3600); + + /* + * Set the maximum number of entries in the +@@ -3468,11 +4626,15 @@ static int _tls_server_conf_free(fr_tls_server_conf_t *conf) + + if (conf->cache_ht) fr_hash_table_free(conf->cache_ht); + ++ pthread_mutex_destroy(&conf->mutex); ++ + #ifdef HAVE_OPENSSL_OCSP_H + if (conf->ocsp_store) X509_STORE_free(conf->ocsp_store); + conf->ocsp_store = NULL; + #endif + ++ if (conf->realms) fr_hash_table_free(conf->realms); ++ + #ifndef NDEBUG + memset(conf, 0, sizeof(*conf)); + #endif +@@ -3505,9 +4667,109 @@ static int store_cmp(void const *a, void const *b) + DICT_ATTR const *one = a; + DICT_ATTR const *two = b; + +- return one - two; ++ return (one < two) - (one > two); ++} ++ ++static uint32_t realm_hash(void const *data) ++{ ++ fr_realm_ctx_t const *r = data; ++ ++ return fr_hash_string(r->name); ++} ++ ++static int realm_cmp(void const *a, void const *b) ++{ ++ fr_realm_ctx_t const *one = a; ++ fr_realm_ctx_t const *two = b; ++ ++ return strcmp(one->name, two->name); + } + ++static void realm_free(void *data) ++{ ++ fr_realm_ctx_t *r = data; ++ ++ SSL_CTX_free(r->ctx); ++} ++ ++static int tls_realms_load(fr_tls_server_conf_t *conf) ++{ ++ fr_hash_table_t *ht; ++ DIR *dir; ++ struct dirent *dp; ++ char buffer[PATH_MAX]; ++ char buffer2[PATH_MAX]; ++ ++ ht = fr_hash_table_create(realm_hash, realm_cmp, realm_free); ++ if (!ht) return -1; ++ ++ dir = opendir(conf->realm_dir); ++ if (!dir) { ++ ERROR("Error reading directory %s: %s", conf->realm_dir, fr_syserror(errno)); ++ error: ++ if (dir) closedir(dir); ++ fr_hash_table_free(ht); ++ return -1; ++ } ++ ++ /* ++ * Read only the PEM files ++ */ ++ while ((dp = readdir(dir)) != NULL) { ++ char *p; ++ struct stat stat_buf; ++ SSL_CTX *ctx; ++ fr_realm_ctx_t *r; ++ char const *private_key_file = buffer; ++ ++ if (dp->d_name[0] == '.') continue; ++ ++ p = strrchr(dp->d_name, '.'); ++ if (!p) continue; ++ ++ if (memcmp(p, ".pem", 5) != 0) continue; /* must END in .pem */ ++ ++ snprintf(buffer, sizeof(buffer), "%s/%s", conf->realm_dir, dp->d_name); /* ignore directories */ ++ if ((stat(buffer, &stat_buf) != 0) || ++ S_ISDIR(stat_buf.st_mode)) continue; ++ ++ strcpy(buffer2, buffer); ++ p = strchr(buffer2, '.'); /* which must be there... */ ++ if (!p) continue; ++ ++ /* ++ * If there's a key file, then use that. ++ * Otherwise assume that the private key is in ++ * the chain file. ++ */ ++ strcpy(p, ".key"); ++ if (stat(buffer2, &stat_buf) != 0) private_key_file = buffer2; ++ ++ ctx = tls_init_ctx(conf, 1, buffer, private_key_file); ++ if (!ctx) goto error; ++ ++ r = talloc_zero(conf, fr_realm_ctx_t); ++ if (!r) { ++ SSL_CTX_free(ctx); ++ goto error; ++ } ++ ++ r->name = talloc_strdup(r, buffer); ++ r->ctx = ctx; ++ ++ if (fr_hash_table_insert(ht, r) < 0) { ++ ERROR("Failed inserting certificate file %s into hash table", buffer); ++ goto error; ++ } ++ } ++ ++ conf->realms = ht; ++ closedir(dir); ++ ++ return 0; ++} ++ ++ + fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + { + fr_tls_server_conf_t *conf; +@@ -3535,6 +4797,16 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + */ + if (conf->fragment_size < 100) conf->fragment_size = 100; + ++ /* ++ * Disallow sessions of more than 7 days, as per RFC ++ * 8446. ++ * ++ * Note that we also enforce this on TLS 1.2, etc. ++ * Because there's just no reason to have month-long TLS ++ * sessions. ++ */ ++ if (conf->session_lifetime > (7 * 24)) conf->session_lifetime = 7 * 24; ++ + /* + * Only check for certificate things if we don't have a + * PSK query. +@@ -3563,10 +4835,15 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + } + } + ++ /* ++ * Initialize configuration mutex ++ */ ++ pthread_mutex_init(&conf->mutex, NULL); ++ + /* + * Initialize TLS + */ +- conf->ctx = tls_init_ctx(conf, 0); ++ conf->ctx = tls_init_ctx(conf, 0, NULL, NULL); + if (conf->ctx == NULL) { + goto error; + } +@@ -3633,10 +4910,11 @@ skip_list: + * Initialize OCSP Revocation Store + */ + if (conf->ocsp_enable) { +- conf->ocsp_store = init_revocation_store(conf); ++ conf->ocsp_store = fr_init_x509_store(conf); + if (conf->ocsp_store == NULL) goto error; + } + #endif /*HAVE_OPENSSL_OCSP_H*/ ++ + { + char *dh_file; + +@@ -3655,7 +4933,7 @@ skip_list: + } + + if (conf->verify_client_cert_cmd && !conf->verify_tmp_dir) { +- ERROR(LOG_PREFIX ": You MUST set the verify directory in order to use verify_client_cmd"); ++ ERROR(LOG_PREFIX ": You MUST set the 'tmpdir' directory in order to use '%s' cmd", conf->verify_client_cert_cmd); + goto error; + } + +@@ -3663,12 +4941,17 @@ skip_list: + /* + * OpenSSL 1.0.1f and 1.0.1g get the MS-MPPE keys wrong. + */ +-#if (OPENSSL_VERSION_NUMBER >= 0x10010060L) && (OPENSSL_VERSION_NUMBER < 0x10010060L) ++#if (OPENSSL_VERSION_NUMBER >= 0x1010106L) && (OPENSSL_VERSION_NUMBER <= 0x1010107L) + conf->disable_tlsv1_2 = true; + WARN(LOG_PREFIX ": Disabling TLSv1.2 due to OpenSSL bugs"); + #endif + #endif + ++ /* ++ * Load certificates and private keys from the realm directory. ++ */ ++ if (conf->realm_dir && (tls_realms_load(conf) < 0)) goto error; ++ + /* + * Cache conf in cs in case we're asked to parse this again. + */ +@@ -3703,7 +4986,7 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs) + /* + * Initialize TLS + */ +- conf->ctx = tls_init_ctx(conf, 1); ++ conf->ctx = tls_init_ctx(conf, 1, NULL, NULL); + if (conf->ctx == NULL) { + goto error; + } +@@ -3755,7 +5038,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * not allowed, + */ + if (SSL_session_reused(ssn->ssl)) { +- RDEBUG("Forcibly stopping session resumption as it is not allowed"); ++ RDEBUG("(TLS) cache - Forcibly stopping session resumption as it is administratively disabled."); + return -1; + } + +@@ -3763,12 +5046,14 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * Else resumption IS allowed, so we store the + * user data in the cache. + */ +- } else if (!SSL_session_reused(ssn->ssl)) { ++ } else if ((!SSL_session_reused(ssn->ssl)) || ssn->session_not_resumed) { + VALUE_PAIR **certs; + char buffer[2 * MAX_SESSION_SIZE + 1]; + + tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); + ++ RDEBUG("(TLS) cache - Setting up attributes for session resumption"); ++ + vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_USER_NAME, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + +@@ -3778,6 +5063,9 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_STRIPPED_USER_DOMAIN, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + ++ vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); ++ if (vp) fr_pair_add(&vps, vp); ++ + vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_CHARGEABLE_USER_IDENTITY, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + +@@ -3836,7 +5124,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + if (vp) { + if ((request->timestamp + vp->vp_integer) > expires) { + vp->vp_integer = expires - request->timestamp; +- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", + vp->vp_integer); + } + } +@@ -3858,7 +5146,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + FR_DIR_SEP, buffer); + vp_file = fopen(filename, "w"); + if (vp_file == NULL) { +- RWDEBUG("Could not write session VPs to persistent cache: %s", ++ RWDEBUG("(TLS) Could not write session VPs to persistent cache: %s", + fr_syserror(errno)); + } else { + VALUE_PAIR *prev = NULL; +@@ -3889,6 +5177,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + fprintf(vp_file, "\n"); + fclose(vp_file); + } ++ ++ } else if (conf->session_cache_server) { ++ cbtls_cache_save_vps(ssn->ssl, ssn->ssl_session, vps); ++ + } else { + RDEBUG("Failed to find 'persist_dir' in TLS configuration. Session will not be cached on disk."); + } +@@ -3901,15 +5193,27 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * Else the session WAS allowed. Copy the cached reply. + */ + } else { +- char buffer[2 * MAX_SESSION_SIZE + 1]; +- +- tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); ++ RDEBUG("(TLS) cache - Refreshing entry for session resumption"); + + /* + * The "restore VPs from OpenSSL cache" code is + * now in eaptls_process() + */ + if (conf->session_cache_path) { ++ char buffer[2 * MAX_SESSION_SIZE + 1]; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10001000L ++#ifdef TLS1_3_VERSION ++ /* ++ * OpenSSL frees the underlying session out from ++ * under us in TLS 1.3. ++ */ ++ if (SSL_version(ssn->ssl) == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl); ++#endif ++#endif ++ ++ tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); ++ + /* "touch" the cached session/vp file */ + char filename[3 * MAX_SESSION_SIZE + 1]; + +@@ -3921,6 +5225,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + utime(filename, NULL); + } + ++ if (conf->session_cache_server) { ++ cbtls_cache_refresh(ssn->ssl, ssn->ssl_session); ++ } ++ + /* + * Mark the request as resumed. + */ +@@ -3953,49 +5261,69 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) + err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, + ssn->dirty_in.used); + if (err != (int) ssn->dirty_in.used) { ++ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + record_init(&ssn->dirty_in); +- RDEBUG("Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + return FR_TLS_FAIL; + } ++ ++ record_init(&ssn->dirty_in); + } + + /* +- * Clear the dirty buffer now that we are done with it +- * and init the clean_out buffer to store decrypted data ++ * tls_handshake_recv() may read application data. So ++ * don't touch clean_out. But only if the BIO_write() ++ * above didn't do anything. + */ +- record_init(&ssn->dirty_in); +- record_init(&ssn->clean_out); ++ else if (ssn->clean_out.used > 0) { ++ RDEBUG("(TLS) We already have %zd bytes of application data, processing it.", ++ (ssn->clean_out.used)); ++ goto add_certs; ++ } + + /* + * Read (and decrypt) the tunneled data from the + * SSL session, and put it into the decrypted + * data buffer. + */ +- err = SSL_read(ssn->ssl, ssn->clean_out.data, sizeof(ssn->clean_out.data)); ++ err = SSL_read(ssn->ssl, ssn->clean_out.data + ssn->clean_out.used, ++ sizeof(ssn->clean_out.data) - ssn->clean_out.used); + if (err <= 0) { + int code; + +- RDEBUG("SSL_read Error"); ++ RDEBUG3("(TLS) SSL_read Error"); + + code = SSL_get_error(ssn->ssl, err); + switch (code) { + case SSL_ERROR_WANT_READ: +- RDEBUG("Error in fragmentation logic: SSL_WANT_READ"); ++ if (ssn->clean_out.used > 0) { /* just process what application data we have */ ++ err = 0; ++ break; ++ } ++ ++ RDEBUG("(TLS) OpenSSL says that it needs to read more data."); + return FR_TLS_MORE_FRAGMENTS; + + case SSL_ERROR_WANT_WRITE: +- RDEBUG("Error in fragmentation logic: SSL_WANT_WRITE"); ++ if (ssn->clean_out.used > 0) { /* just process what application data we have */ ++ err = 0; ++ break; ++ } ++ ++ REDEBUG("(TLS) Error in fragmentation logic: SSL_WANT_WRITE"); + return FR_TLS_FAIL; + + case SSL_ERROR_NONE: +- RDEBUG2("No application data received. Assuming handshake is continuing..."); ++ RDEBUG2("(TLS) No application data received. Assuming handshake is continuing..."); + err = 0; + break; + ++ case SSL_ERROR_ZERO_RETURN: ++ RDEBUG2("(TLS) Other end closed the TLS tunnel."); ++ return FR_TLS_FAIL; ++ + default: +- REDEBUG("Error in fragmentation logic"); +- tls_error_io_log(request, ssn, err, +- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)"); ++ REDEBUG("(TLS) Error in fragmentation logic - code %d", code); ++ tls_error_io_log(request, ssn, err, "Failed reading application data from OpenSSL"); + return FR_TLS_FAIL; + } + } +@@ -4003,8 +5331,9 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) + /* + * Passed all checks, successfully decrypted data + */ +- ssn->clean_out.used = err; ++ ssn->clean_out.used += err; + ++add_certs: + /* + * Add the certificates to intermediate packets, so that + * the inner tunnel policies can use them. +@@ -4026,27 +5355,33 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) + fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + { + if (ssn == NULL){ +- REDEBUG("Unexpected ACK received: No ongoing SSL session"); ++ REDEBUG("(TLS) Unexpected ACK received: No ongoing SSL session"); + return FR_TLS_INVALID; + } + if (!ssn->info.initialized) { +- RDEBUG("No SSL info available. Waiting for more SSL data"); ++ RDEBUG("(TLS) No SSL info available. Waiting for more SSL data"); + return FR_TLS_REQUEST; + } + + if ((ssn->info.content_type == handshake) && (ssn->info.origin == 0)) { +- REDEBUG("Unexpected ACK received: We sent no previous messages"); ++ REDEBUG("(TLS) Unexpected ACK received: We sent no previous messages"); + return FR_TLS_INVALID; + } + + switch (ssn->info.content_type) { + case alert: +- RDEBUG2("Peer ACKed our alert"); ++ RDEBUG2("(TLS) Peer ACKed our alert"); + return FR_TLS_FAIL; + + case handshake: +- if ((ssn->is_init_finished) && (ssn->dirty_out.used == 0)) { +- RDEBUG2("Peer ACKed our handshake fragment. handshake is finished"); ++ if (ssn->dirty_out.used > 0) { ++ RDEBUG2("(TLS) Peer ACKed our handshake fragment"); ++ /* Fragmentation handler, send next fragment */ ++ return FR_TLS_REQUEST; ++ } ++ ++ if (ssn->is_init_finished || SSL_is_init_finished(ssn->ssl)) { ++ RDEBUG2("(TLS) Peer ACKed our handshake fragment. handshake is finished"); + + /* + * From now on all the content is +@@ -4057,12 +5392,11 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + return FR_TLS_SUCCESS; + } /* else more data to send */ + +- RDEBUG2("Peer ACKed our handshake fragment"); +- /* Fragmentation handler, send next fragment */ +- return FR_TLS_REQUEST; ++ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); ++ return FR_TLS_FAIL; + + case application_data: +- RDEBUG2("Peer ACKed our application data fragment"); ++ RDEBUG2("(TLS) Peer ACKed our application data fragment"); + return FR_TLS_REQUEST; + + /* +@@ -4070,7 +5404,7 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + * to the default section below. + */ + default: +- REDEBUG("Invalid ACK received: %d", ssn->info.content_type); ++ REDEBUG("(TLS) Invalid ACK received: %d", ssn->info.content_type); + return FR_TLS_INVALID; + } + } +diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c +index 0eed87b64f..6d954d269f 100644 +--- a/src/main/tls_listen.c ++++ b/src/main/tls_listen.c +@@ -81,54 +81,96 @@ static void tls_socket_close(rad_listen_t *listener) + /* + * Tell the event handler that an FD has disappeared. + */ +- DEBUG("Client has closed connection"); ++ DEBUG("(TLS) Closing connection"); + radius_update_listener(listener); + + /* +- * Do NOT free the listener here. It's in use by ++ * Do NOT free the listener here. It may be in use by + * a request, and will need to hang around until + * all of the requests are done. + * +- * It is instead free'd in remove_from_request_hash() ++ * It is instead free'd when all of the requests using it ++ * are done. + */ + } + +-static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *request) ++static void tls_write_available(fr_event_list_t *el, int sock, void *ctx); ++ ++static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener) + { +- uint8_t *p; + ssize_t rcode; + listen_socket_t *sock = listener->data; + +- p = sock->ssn->dirty_out.data; +- +- while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { +- RDEBUG3("Writing to socket %d", request->packet->sockfd); +- rcode = write(request->packet->sockfd, p, +- (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); +- if (rcode <= 0) { +- RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno)); ++ /* ++ * It's not writable, so we don't bother writing to it. ++ */ ++ if (listener->blocked) return 0; + +- tls_socket_close(listener); ++ /* ++ * Write as much as possible. ++ */ ++ rcode = write(listener->fd, sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); ++ if (rcode <= 0) { ++#ifdef EWOULDBLOCK ++ /* ++ * Writing to the socket would cause it to block. ++ * As a result, we just mark it as "don't use" ++ * until such time as it becomes writable. ++ */ ++ if (errno == EWOULDBLOCK) { ++ proxy_listener_freeze(listener, tls_write_available); + return 0; + } +- p += rcode; ++#endif ++ ++ ++ ERROR("(TLS) Error writing to socket: %s", fr_syserror(errno)); ++ ++ tls_socket_close(listener); ++ return -1; + } + +- sock->ssn->dirty_out.used = 0; ++ /* ++ * All of the data was written. It's fine. ++ */ ++ if ((size_t) rcode == sock->ssn->dirty_out.used) { ++ sock->ssn->dirty_out.used = 0; ++ return 0; ++ } + +- return 1; ++ /* ++ * Move the data to the start of the buffer. ++ * ++ * Yes, this is horrible. But doing this means that we ++ * don't have to modify the rest of the code which mangles dirty_out, and assumes that the write offset is always &data[used]. ++ */ ++ memmove(&sock->ssn->dirty_out.data[0], &sock->ssn->dirty_out.data[rcode], sock->ssn->dirty_out.used - rcode); ++ sock->ssn->dirty_out.used -= rcode; ++ ++ return 0; ++} ++ ++static void tls_write_available(UNUSED fr_event_list_t *el, UNUSED int fd, void *ctx) ++{ ++ rad_listen_t *listener = ctx; ++ listen_socket_t *sock = listener->data; ++ ++ proxy_listener_thaw(listener); ++ ++ PTHREAD_MUTEX_LOCK(&sock->mutex); ++ (void) tls_socket_write(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); + } + + + static int tls_socket_recv(rad_listen_t *listener) + { +- bool doing_init = false; ++ bool doing_init = false, already_read = false; + ssize_t rcode; + RADIUS_PACKET *packet; + REQUEST *request; + listen_socket_t *sock = listener->data; + fr_tls_status_t status; +- RADCLIENT *client = sock->client; + + if (!sock->packet) { + sock->packet = rad_alloc(sock, false); +@@ -165,7 +207,7 @@ static int tls_socket_recv(rad_listen_t *listener) + rad_assert(sock->ssn == NULL); + + sock->ssn = tls_new_session(sock, listener->tls, sock->request, +- listener->tls->require_client_cert); ++ listener->tls->require_client_cert, true); + if (!sock->ssn) { + TALLOC_FREE(sock->request); + sock->packet = NULL; +@@ -176,6 +218,8 @@ static int tls_socket_recv(rad_listen_t *listener) + SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs); + SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock); + ++ sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */ ++ + doing_init = true; + } + +@@ -186,7 +230,18 @@ static int tls_socket_recv(rad_listen_t *listener) + + request = sock->request; + +- RDEBUG3("Reading from socket %d", request->packet->sockfd); ++ if (sock->state == LISTEN_TLS_SETUP) { ++ RDEBUG3("(TLS) Setting connection state to RUNNING"); ++ sock->state = LISTEN_TLS_RUNNING; ++ ++ if (sock->ssn->clean_out.used < 20) { ++ goto get_application_data; ++ } ++ ++ goto read_application_data; ++ } ++ ++ RDEBUG3("(TLS) Reading from socket %d", request->packet->sockfd); + PTHREAD_MUTEX_LOCK(&sock->mutex); + + /* +@@ -195,33 +250,38 @@ static int tls_socket_recv(rad_listen_t *listener) + * the socket. + */ + if (SSL_pending(sock->ssn->ssl)) { +- RDEBUG3("Reading pending buffered data"); ++ RDEBUG3("(TLS) Reading pending buffered data"); + sock->ssn->dirty_in.used = 0; +- goto get_application_data; ++ goto check_for_setup; + } + +- rcode = read(request->packet->sockfd, +- sock->ssn->dirty_in.data, +- sizeof(sock->ssn->dirty_in.data)); +- if ((rcode < 0) && (errno == ECONNRESET)) { +- do_close: +- DEBUG("Closing TLS socket from client port %u", sock->other_port); +- tls_socket_close(listener); +- PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- return 0; +- } ++ if (!already_read) { ++ rcode = read(request->packet->sockfd, ++ sock->ssn->dirty_in.data, ++ sizeof(sock->ssn->dirty_in.data)); ++ if ((rcode < 0) && (errno == ECONNRESET)) { ++ do_close: ++ DEBUG("(TLS) Closing socket from client port %u", sock->other_port); ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } + +- if (rcode < 0) { +- RDEBUG("Error reading TLS socket: %s", fr_syserror(errno)); +- goto do_close; +- } ++ if (rcode < 0) { ++ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); ++ goto do_close; ++ } + +- /* +- * Normal socket close. +- */ +- if (rcode == 0) goto do_close; ++ /* ++ * Normal socket close. ++ */ ++ if (rcode == 0) { ++ RDEBUG("(TLS) Client has closed the TCP connection"); ++ goto do_close; ++ } + +- sock->ssn->dirty_in.used = rcode; ++ sock->ssn->dirty_in.used = rcode; ++ } + + dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); + +@@ -229,16 +289,17 @@ static int tls_socket_recv(rad_listen_t *listener) + * Catch attempts to use non-SSL. + */ + if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) { +- RDEBUG("Non-TLS data sent to TLS socket: closing"); ++ RDEBUG("(TLS) Non-TLS data sent to TLS socket: closing"); + goto do_close; + } + + /* + * If we need to do more initialization, do that here. + */ ++check_for_setup: + if (!sock->ssn->is_init_finished) { + if (!tls_handshake_recv(request, sock->ssn)) { +- RDEBUG("FAILED in TLS handshake receive"); ++ RDEBUG("(TLS) Failed in TLS handshake receive"); + goto do_close; + } + +@@ -246,24 +307,93 @@ static int tls_socket_recv(rad_listen_t *listener) + * More ACK data to send. Do so. + */ + if (sock->ssn->dirty_out.used > 0) { +- tls_socket_write(listener, request); ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ tls_socket_write(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } ++ ++ /* ++ * If SSL handshake still isn't finished, then there ++ * is more data to read. Release the mutex and ++ * return so this function will be called again ++ */ ++ if (!SSL_is_init_finished(sock->ssn->ssl)) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } ++ } ++ ++ /* ++ * Run the request through a virtual server in ++ * order to see if we like the certificate ++ * presented by the client. ++ */ ++ if (sock->state == LISTEN_TLS_INIT) { ++ if (!SSL_is_init_finished(sock->ssn->ssl)) { ++ RDEBUG("(TLS) OpenSSL says that the TLS session is still negotiating, but there's no more data to send!"); ++ goto do_close; ++ } ++ ++ sock->ssn->is_init_finished = true; ++ if (!listener->check_client_connections) { ++ sock->state = LISTEN_TLS_RUNNING; ++ goto get_application_data; ++ } ++ ++ request->packet->vps = fr_pair_list_copy(request->packet, sock->certs); ++ ++ /* ++ * Fake out a Status-Server packet, which ++ * does NOT have a Message-Authenticator, ++ * or any other contents. ++ */ ++ request->packet->code = PW_CODE_STATUS_SERVER; ++ request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); ++ request->packet->data[0] = PW_CODE_STATUS_SERVER; ++ request->packet->data[3] = 20; ++ request->listener = listener; ++ sock->state = LISTEN_TLS_CHECKING; ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); + + /* +- * FIXME: Run the request through a virtual +- * server in order to see if we like the +- * certificate presented by the client. ++ * Don't read from the socket until the request ++ * returns. + */ ++ listener->status = RAD_LISTEN_STATUS_PAUSE; ++ radius_update_listener(listener); ++ ++ return 1; + } + + /* + * Try to get application data. + */ + get_application_data: ++ /* ++ * More data to send. Do so. ++ */ ++ if (sock->ssn->dirty_out.used > 0) { ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ rcode = tls_socket_write(listener); ++ if (rcode < 0) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return rcode; ++ } ++ } ++ + status = tls_application_data(sock->ssn, request); +- RDEBUG("Application data status %d", status); ++ RDEBUG3("(TLS) Application data status %d", status); ++ ++ /* ++ * Some kind of failure. Close the socket. ++ */ ++ if (status == FR_TLS_FAIL) { ++ DEBUG("(TLS) Unable to recover from TLS error, closing socket from client port %u", sock->other_port); ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } + + if (status == FR_TLS_MORE_FRAGMENTS) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +@@ -275,6 +405,16 @@ get_application_data: + return 0; + } + ++ /* ++ * Hold application data if we're not yet in the RUNNING ++ * state. ++ */ ++ if (sock->state != LISTEN_TLS_RUNNING) { ++ RDEBUG3("(TLS) Holding application data until setup is complete"); ++ return 0; ++ } ++ ++read_application_data: + /* + * We now have a bunch of application data. + */ +@@ -286,7 +426,7 @@ get_application_data: + */ + if ((sock->ssn->clean_out.used < 20) || + (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) { +- RDEBUG("Received bad packet: Length %zd contents %d", ++ RDEBUG("(TLS) Received bad packet: Length %zd contents %d", + sock->ssn->clean_out.used, + (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]); + goto do_close; +@@ -299,9 +439,11 @@ get_application_data: + packet->vps = NULL; + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + ++ packet->tls = true; ++ + if (!rad_packet_ok(packet, 0, NULL)) { + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); +- DEBUG("Closing TLS socket from client"); ++ DEBUG("(TLS) Closing TLS socket from client"); + PTHREAD_MUTEX_LOCK(&sock->mutex); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +@@ -315,7 +457,7 @@ get_application_data: + char host_ipaddr[128]; + + if (is_radius_code(packet->code)) { +- RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d", ++ RDEBUG("(TLS): %s packet from host %s port %d, id=%d, length=%d", + fr_packet_codes[packet->code], + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, +@@ -323,7 +465,7 @@ get_application_data: + packet->src_port, + packet->id, (int) packet->data_len); + } else { +- RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d", ++ RDEBUG("(TLS): Packet from host %s port %d code=%d, id=%d, length=%d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), +@@ -333,8 +475,6 @@ get_application_data: + } + } + +- FR_STATS_INC(auth, total_requests); +- + return 1; + } + +@@ -359,6 +499,7 @@ redo: + rad_assert(client != NULL); + + packet = talloc_steal(NULL, sock->packet); ++ sock->request->packet = NULL; + sock->packet = NULL; + + /* +@@ -391,8 +532,26 @@ redo: + break; + #endif + ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ if (listener->type != RAD_LISTEN_COA) goto bad_packet; ++ FR_STATS_INC(coa, total_requests); ++ fun = rad_coa_recv; ++ break; ++ ++ case PW_CODE_DISCONNECT_REQUEST: ++ if (listener->type != RAD_LISTEN_COA) goto bad_packet; ++ FR_STATS_INC(dsc, total_requests); ++ fun = rad_coa_recv; ++ break; ++#endif ++ + case PW_CODE_STATUS_SERVER: +- if (!main_config.status_server) { ++ if (!main_config.status_server ++#ifdef WITH_TLS ++ && !listener->check_client_connections ++#endif ++ ) { + FR_STATS_INC(auth, total_unknown_types); + WARN("Ignoring Status-Server request due to security configuration"); + rad_free(&packet); +@@ -405,7 +564,7 @@ redo: + bad_packet: + FR_STATS_INC(auth, total_unknown_types); + +- DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED", ++ DEBUG("(TLS) Invalid packet code %d sent from client %s port %d : IGNORED", + packet->code, client->shortname, packet->src_port); + rad_free(&packet); + return 0; +@@ -432,7 +591,7 @@ redo: + int peek = SSL_peek(sock->ssn->ssl, buf, 1); + + if (peek > 0) { +- DEBUG("more TLS records after dual_tls_recv"); ++ DEBUG("(TLS) more TLS records after dual_tls_recv"); + goto redo; + } + } +@@ -455,6 +614,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + ++ /* ++ * See if the policies allowed this connection. ++ */ ++ if (sock->state == LISTEN_TLS_CHECKING) { ++ if (request->reply->code != PW_CODE_ACCESS_ACCEPT) { ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ listener->tls = NULL; /* parent owns this! */ ++ ++ /* ++ * Tell the event handler that an FD has disappeared. ++ */ ++ radius_update_listener(listener); ++ return 0; ++ } ++ ++ /* ++ * Resume reading from the listener. ++ */ ++ listener->status = RAD_LISTEN_STATUS_RESUME; ++ radius_update_listener(listener); ++ ++ rad_assert(sock->request->packet != request->packet); ++ ++ sock->state = LISTEN_TLS_SETUP; ++ (void) dual_tls_recv(listener); ++ return 0; ++ } ++ + /* + * Accounting reject's are silently dropped. + * +@@ -507,38 +694,43 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) + if (sock->ssn->dirty_out.used > 0) { + dump_hex("WRITE TO SSL", sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); + +- tls_socket_write(listener, request); ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ tls_socket_write(listener); + } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + + return 0; + } + +-static int try_connect(tls_session_t *ssn) ++static int try_connect(listen_socket_t *sock) + { + int ret; +- ret = SSL_connect(ssn->ssl); +- if (ret < 0) { +- switch (SSL_get_error(ssn->ssl, ret)) { +- default: +- break; ++ time_t now; + ++ now = time(NULL); ++ if ((sock->opened + sock->connect_timeout) < now) { ++ tls_error_io_log(NULL, sock->ssn, 0, "Timeout in SSL_connect"); ++ return -1; ++ } + ++ ret = SSL_connect(sock->ssn->ssl); ++ if (ret <= 0) { ++ switch (SSL_get_error(sock->ssn->ssl, ret)) { ++ default: ++ tls_error_io_log(NULL, sock->ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); ++ return -1; + + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) SSL_connect() returned WANT_READ"); ++ return 2; ++ + case SSL_ERROR_WANT_WRITE: +- ssn->connected = false; +- return 0; ++ DEBUG3("(TLS) SSL_connect() returned WANT_WRITE"); ++ return 2; + } + } + +- if (ret <= 0) { +- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); +- talloc_free(ssn); +- +- return -1; +- } +- ++ sock->ssn->connected = true; + return 1; + } + +@@ -564,15 +756,27 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + listen_socket_t *sock = listener->data; + + if (!sock->ssn->connected) { +- rcode = try_connect(sock->ssn); +- if (rcode == 0) return 0; ++ rcode = try_connect(sock); ++ if (rcode <= 0) return rcode; + +- if (rcode < 0) { +- SSL_shutdown(sock->ssn->ssl); +- return -1; +- } ++ if (rcode == 2) return 0; /* more negotiation needed */ ++ } + +- sock->ssn->connected = true; ++ if (sock->ssn->clean_out.used) { ++ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); ++ /* ++ * Write to SSL. ++ */ ++ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); ++ if (rcode > 0) { ++ if ((size_t) rcode < sock->ssn->clean_out.used) { ++ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, ++ sock->ssn->clean_out.used - rcode); ++ sock->ssn->clean_out.used -= rcode; ++ } else { ++ sock->ssn->clean_out.used = 0; ++ } ++ } + } + + /* +@@ -589,9 +793,14 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + if (rcode <= 0) { + int err = SSL_get_error(sock->ssn->ssl, rcode); + switch (err) { ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ return 0; ++ + case SSL_ERROR_WANT_WRITE: +- return 0; /* do some more work later */ ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); ++ return 0; + + case SSL_ERROR_ZERO_RETURN: + /* remote end sent close_notify, send one back */ +@@ -602,9 +811,12 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + do_close: + return -1; + +- default: +- tls_error_log(NULL, "Failed in proxy receive"); ++ case SSL_ERROR_SSL: ++ DEBUG("(TLS) Home server has closed the connection"); ++ goto do_close; + ++ default: ++ tls_error_log(NULL, "Failed in proxy receive with OpenSSL error %d", err); + goto do_close; + } + } +@@ -641,16 +853,28 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) + rcode = SSL_read(sock->ssn->ssl, data + sock->partial, + length - sock->partial); + if (rcode <= 0) { +- switch (SSL_get_error(sock->ssn->ssl, rcode)) { ++ int err = SSL_get_error(sock->ssn->ssl, rcode); ++ switch (err) { ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ return 0; ++ + case SSL_ERROR_WANT_WRITE: ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); + return 0; + + case SSL_ERROR_ZERO_RETURN: + /* remote end sent close_notify, send one back */ + SSL_shutdown(sock->ssn->ssl); + goto do_close; ++ ++ case SSL_ERROR_SSL: ++ DEBUG("(TLS) Home server has closed the connection"); ++ goto do_close; ++ + default: ++ DEBUG("(TLS) Unexpected OpenSSL error %d", err); + goto do_close; + } + } +@@ -683,18 +907,18 @@ int proxy_tls_recv(rad_listen_t *listener) + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + ++ rad_assert(sock->ssn != NULL); ++ + DEBUG3("Proxy SSL socket has data to read"); + PTHREAD_MUTEX_LOCK(&sock->mutex); + data_len = proxy_tls_read(listener); +- PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- + if (data_len < 0) { +- DEBUG("Closing TLS socket to home server"); +- PTHREAD_MUTEX_LOCK(&sock->mutex); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ DEBUG("Closing TLS socket to home server"); + return 0; + } ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); + + if (data_len == 0) return 0; /* not done yet */ + +@@ -713,6 +937,8 @@ int proxy_tls_recv(rad_listen_t *listener) + memcpy(packet->data, data, packet->data_len); + memcpy(packet->vector, packet->data + 4, 16); + ++ packet->tls = true; ++ + /* + * FIXME: Client MIB updates? + */ +@@ -727,6 +953,14 @@ int proxy_tls_recv(rad_listen_t *listener) + break; + #endif + ++#ifdef WITH_COA ++ case PW_CODE_COA_ACK: ++ case PW_CODE_COA_NAK: ++ case PW_CODE_DISCONNECT_ACK: ++ case PW_CODE_DISCONNECT_NAK: ++ break; ++#endif ++ + default: + /* + * FIXME: Update MIB for packet types? +@@ -765,42 +999,111 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) + * if there's no packet, encode it here. + */ + if (!request->proxy->data) { +- request->proxy_listener->encode(request->proxy_listener, +- request); ++ request->reply->tls = true; ++ request->proxy_listener->proxy_encode(request->proxy_listener, ++ request); + } + ++ rad_assert(sock->ssn != NULL); ++ + if (!sock->ssn->connected) { + PTHREAD_MUTEX_LOCK(&sock->mutex); +- rcode = try_connect(sock->ssn); ++ rcode = try_connect(sock); ++ if (rcode <= 0) { ++ tls_socket_close(listener); ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return rcode; ++ } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +- if (rcode == 0) return 0; + +- if (rcode < 0) { +- SSL_shutdown(sock->ssn->ssl); +- return -1; +- } ++ /* ++ * More negotiation is needed, but remember to ++ * save this packet to an intermediate buffer. ++ * Once the SSL connection is established, the ++ * later code writes the packet to the ++ * connection. ++ */ ++ if (rcode == 2) { ++ PTHREAD_MUTEX_LOCK(&sock->mutex); ++ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ RERROR("(TLS) Too much data buffered during SSL_connect()"); ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ radius_update_listener(listener); ++ return -1; ++ } + +- sock->ssn->connected = true; ++ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); ++ sock->ssn->clean_out.used += request->proxy->data_len; ++ RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); ++ ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 0; ++ } + } + + DEBUG3("Proxy is writing %u bytes to SSL", + (unsigned int) request->proxy->data_len); + PTHREAD_MUTEX_LOCK(&sock->mutex); +- rcode = SSL_write(sock->ssn->ssl, request->proxy->data, +- request->proxy->data_len); ++ ++ /* ++ * We may have previously cached data on SSL_connect(), which now needs to be written to the home server. ++ */ ++ if (sock->ssn->clean_out.used > 0) { ++ if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ RERROR("(TLS) Too much data buffered after SSL_connect()"); ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ radius_update_listener(listener); ++ return -1; ++ } ++ ++ /* ++ * Add in our packet. ++ */ ++ memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); ++ sock->ssn->clean_out.used += request->proxy->data_len; ++ ++ /* ++ * Write to SSL. ++ */ ++ DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); ++ ++ rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); ++ if (rcode > 0) { ++ if ((size_t) rcode < sock->ssn->clean_out.used) { ++ memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, ++ sock->ssn->clean_out.used - rcode); ++ sock->ssn->clean_out.used -= rcode; ++ } else { ++ sock->ssn->clean_out.used = 0; ++ } ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ return 1; ++ } ++ } else { ++ rcode = SSL_write(sock->ssn->ssl, request->proxy->data, ++ request->proxy->data_len); ++ } + if (rcode < 0) { + int err; + + err = ERR_get_error(); + switch (err) { + case SSL_ERROR_NONE: ++ break; ++ + case SSL_ERROR_WANT_READ: ++ DEBUG3("(TLS) OpenSSL returned WANT_READ"); ++ break; ++ + case SSL_ERROR_WANT_WRITE: +- break; /* let someone else retry */ ++ DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); ++ break; + + default: +- tls_error_log(NULL, "Failed in proxy send"); +- DEBUG("Closing TLS socket to home server"); ++ tls_error_log(NULL, "Failed in proxy send with OpenSSL error %d", err); ++ DEBUG("(TLS) Closing socket to home server"); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; +diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c +index 9fed166e62..1cd73ff246 100644 +--- a/src/modules/proto_dhcp/rlm_dhcp.c ++++ b/src/modules/proto_dhcp/rlm_dhcp.c +@@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request, + decoded++; + } + +- fr_pair_list_move(request->packet, &(request->packet->vps), &head); ++ fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD); + + /* Free any unmoved pairs */ + fr_pair_list_free(&head); +diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c +index 83e7252fa8..2f37663df1 100644 +--- a/src/modules/rlm_eap/libeap/eap_tls.c ++++ b/src/modules/rlm_eap/libeap/eap_tls.c +@@ -1,3 +1,4 @@ ++ + /* + * eap_tls.c + * +@@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + * + * Fragment length is Framed-MTU - 4. + */ +-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert) ++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13) + { + tls_session_t *ssn; + REQUEST *request = handler->request; +@@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ + * in Opaque. So that we can use these data structures + * when we get the response + */ +- ssn = tls_new_session(handler, tls_conf, request, client_cert); ++ ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13); + if (!ssn) { + return NULL; + } +@@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag) + */ + int eaptls_success(eap_handler_t *handler, int peap_flag) + { +- EAPTLS_PACKET reply; ++ EAPTLS_PACKET reply; + REQUEST *request = handler->request; + tls_session_t *tls_session = handler->opaque; + +@@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) + /* + * Automatically generate MPPE keying material. + */ +- if (tls_session->prf_label) { +- eaptls_gen_mppe_keys(handler->request, +- tls_session->ssl, tls_session->prf_label); ++ if (tls_session->label) { ++ uint8_t const *context = NULL; ++ size_t context_size = 0; ++#ifdef TLS1_3_VERSION ++ uint8_t const context_tls13[] = { handler->type }; ++#endif ++ ++ switch (SSL_version(tls_session->ssl)) { ++#ifdef TLS1_3_VERSION ++ case TLS1_3_VERSION: ++ context = context_tls13; ++ context_size = sizeof(context_tls13); ++ tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; ++ break; ++#endif ++ case TLS1_2_VERSION: ++ case TLS1_1_VERSION: ++ case TLS1_VERSION: ++ break; ++ case SSL2_VERSION: ++ case SSL3_VERSION: ++ default: ++ /* Should never happen */ ++ rad_assert(0); ++ return 0; ++ break; ++ } ++ eaptls_gen_mppe_keys(request, ++ tls_session->ssl, tls_session->label, ++ context, context_size); + } else if (handler->type != PW_EAP_FAST) { +- RWDEBUG("Not adding MPPE keys because there is no PRF label"); ++ RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label"); + } + +- eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, +- handler->type); ++ eaptls_gen_eap_key(handler); ++ + return 1; + } + +@@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag) + EAPTLS_PACKET reply; + REQUEST *request = handler->request; + +- RDEBUG2("ACKing Peer's TLS record fragment"); ++ RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data."); + reply.code = FR_TLS_ACK; + reply.length = TLS_HEADER_LEN + 1/*flags*/; + reply.flags = peap_flag; +@@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + /* + * First output the flags (for debugging) + */ +- RDEBUG3("Peer sent flags %c%c%c", ++ RDEBUG3("(TLS) EAP Peer sent flags %c%c%c", + TLS_START(eaptls_packet->flags) ? 'S' : '-', + TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-', + TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-'); +@@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) { + return tls_ack_handler(handler->opaque, request); + } else { +- REDEBUG("Received Invalid TLS ACK"); ++ REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection"); + return FR_TLS_INVALID; + } + } +@@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * We send TLS_START, but do not receive it. + */ + if (TLS_START(eaptls_packet->flags)) { +- REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)"); ++ REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)"); + return FR_TLS_INVALID; + } + +@@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; + + if (frag_len > total_len) { +- RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len, ++ RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len, + total_len); + } + +- RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len); ++ RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len); + if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { + /* + * The supplicant is free to send fragments of wildly varying +@@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * as they won't contain the length field. + */ + if (frag_len + 4) { /* check for wrap, else clang scan gets excited */ +- RDEBUG2("Expecting %i TLS record fragments", ++ RDEBUG2("(TLS) EAP Expecting %i fragments", + (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1)); + } + +@@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + */ + if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) || + !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) { +- RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments " +- "to follow", frag_len); ++ RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes). Peer says more fragments " ++ "will follow", frag_len); + tls_session->tls_record_in_total_len = total_len; + tls_session->tls_record_in_recvd_len = frag_len; + + return FR_TLS_FIRST_FRAGMENT; + } + +- RDEBUG2("Got additional TLS record fragment with length (%zu bytes). " +- "Peer indicated more fragments to follow", frag_len); ++ RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). " ++ "Peer says more fragments will follow", frag_len); + + /* + * Check we've not exceeded the originally indicated TLS record size. + */ + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " +- "total TLS record length (%zu bytes)", frag_len, total_len); ++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " ++ "total data length (%zu bytes)", frag_len, total_len); + } + + return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH; +@@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * value of the four octet TLS length field. + */ + if (total_len != frag_len) { +- RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) " +- "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len); ++ RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) " ++ "does not match expected data length (%zu bytes)", total_len, frag_len); + } + + tls_session->tls_record_in_total_len = total_len; + tls_session->tls_record_in_recvd_len = frag_len; +- RDEBUG2("Got complete TLS record (%zu bytes)", frag_len); ++ RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len); + return FR_TLS_LENGTH_INCLUDED; + } + +@@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * this must be the final record fragment + */ + if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { +- RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len); ++ RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len); + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated " +- "TLS record length (%zu bytes)", ++ RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected " ++ "expected data length (%zu bytes)", + tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); + } + } + + if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { +- RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow", ++ RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes). Peer says more fragments will follow", + frag_len); + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " +- "indicated TLS record length (%zu bytes)", ++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " ++ "expected length (%zu bytes)", + tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); + } + return FR_TLS_MORE_FRAGMENTS; +@@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + */ + if (TLS_LENGTH_INCLUDED(tlspacket->flags) && + (tlspacket->length < 5)) { /* flags + TLS message length */ +- REDEBUG("Invalid EAP-TLS packet received: Length bit is set, " ++ REDEBUG("(TLS) EAP Invalid packet received: Length bit is set," + "but packet too short to contain length field"); + talloc_free(tlspacket); + return NULL; +@@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + memcpy(&data_len, &eap_ds->response->type.data[1], 4); + data_len = ntohl(data_len); + if (data_len > MAX_RECORD_SIZE) { +- REDEBUG("Reassembled TLS record will be %u bytes, " +- "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", ++ REDEBUG("(TLS) EAP Reassembled data will be %u bytes, " ++ "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", + data_len); + talloc_free(tlspacket); + return NULL; +@@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + case FR_TLS_LENGTH_INCLUDED: + case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: + if (tlspacket->length < 5) { /* flags + TLS message length */ +- REDEBUG("Invalid EAP-TLS packet received: Expected length, got none"); ++ REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none"); + talloc_free(tlspacket); + return NULL; + } +@@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + break; + + default: +- REDEBUG("Invalid EAP-TLS packet received"); ++ REDEBUG("(TLS) EAP Invalid packet received"); + talloc_free(tlspacket); + return NULL; + } +@@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h + * is required then send another request. + */ + if (!tls_handshake_recv(handler->request, tls_session)) { +- REDEBUG("TLS receive handshake failed during operation"); ++ REDEBUG("(TLS) EAP Receive handshake failed during operation"); + tls_fail(tls_session); + return FR_TLS_FAIL; + } + ++#ifdef TLS1_3_VERSION ++ /* ++ * https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5 ++ * ++ * We need to signal the other end that TLS negotiation ++ * is done. We can't send a zero-length application data ++ * message, so we send application data which is one byte ++ * of zero. ++ * ++ * Note this is only done for when there is no application ++ * data to be sent. So this is done always for EAP-TLS but ++ * notibly not for PEAP even on resumption. ++ */ ++ if ((SSL_version(tls_session->ssl) == TLS1_3_VERSION) && ++ (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) { ++ if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) { ++ tls_session->authentication_success = true; ++ ++ RDEBUG("(TLS) EAP Sending final Commitment Message."); ++ tls_session->record_plus(&tls_session->clean_in, "\0", 1); ++ } ++ ++ tls_handshake_send(request, tls_session); ++ } ++#endif ++ + /* + * FIXME: return success/fail. + * +@@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h + /* + * If there is no data to send i.e + * dirty_out.used <=0 and if the SSL +- * handshake is finished, then return a +- * EPTLS_SUCCESS ++ * handshake is finished. + */ ++ if (tls_session->is_init_finished) return FR_TLS_SUCCESS; + +- if (tls_session->is_init_finished) { ++ /* ++ * If session is established, skip round-trip and ++ * try to process any inner tunnel data if present. ++ * ++ * This occurs for EAP-TTLS/PAP with TLSv1.3. ++ */ ++ if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) { + /* +- * Init is finished. The rest is +- * application data. ++ * Don't set is_init_finished, as that causes the ++ * rest of the code to make too many assumptions. + */ +- tls_session->info.content_type = application_data; +- return FR_TLS_SUCCESS; ++ return FR_TLS_OK; + } + + /* + * Who knows what happened... + */ +- REDEBUG("TLS failed during operation"); ++ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); + return FR_TLS_FAIL; + } + +@@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + + if (!request) return FR_TLS_FAIL; + +- RDEBUG2("Continuing EAP-TLS"); ++ RDEBUG3("(TLS) EAP Continuing ..."); + + SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); + +@@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + */ + status = eaptls_verify(handler); + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { +- REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + switch (status) { +@@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * data" phase. + */ + case FR_TLS_OK: +- RDEBUG2("Done initial handshake"); ++ RDEBUG2("(TLS) EAP Done initial handshake"); + + /* + * Get the rest of the fragments. +@@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * Extract the TLS packet from the buffer. + */ + if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) { ++ REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message"); + status = FR_TLS_FAIL; + goto done; + } +@@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + if (tlspacket->dlen != + (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) { + talloc_free(tlspacket); +- REDEBUG("Exceeded maximum record size"); ++ REDEBUG("(TLS) EAP Exceeded maximum record size"); + status = FR_TLS_FAIL; + goto done; + } +@@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * Send the ACK. + */ + eaptls_send_ack(handler, tls_session->peap_flag); +- RDEBUG2("Init is done, but tunneled data is fragmented"); ++ RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented"); + status = FR_TLS_HANDLED; + goto done; + } +@@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + + vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps); + if (!vps) { +- RWDEBUG("No information in cached session %s", buffer); ++ RWDEBUG("(TLS) EAP No information in cached session %s", buffer); + } else { + vp_cursor_t cursor; + VALUE_PAIR *vp; + fr_tls_server_conf_t *conf; + +- RDEBUG("Adding cached attributes from session %s", buffer); ++ RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer); + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF); + rad_assert(conf != NULL); +@@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + rdebug_pair(L_DBG_LVL_2, request, vp, "&request:"); + fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp)); + } ++ ++ } else if ((vp->da->vendor == 0) && ++ (vp->da->attr == PW_EAP_TYPE)) { ++ /* ++ * EAP-Type gets added to ++ * the control list, so ++ * that we can sanity check it. ++ */ ++ rdebug_pair(L_DBG_LVL_2, request, vp, "&control:"); ++ fr_pair_add(&request->config, fr_pair_copy(request, vp)); ++ + } else { ++ + rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:"); + fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); + } +diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h +index 73c7fdd53b..8e5fc773d6 100644 +--- a/src/modules/rlm_eap/libeap/eap_tls.h ++++ b/src/modules/rlm_eap/libeap/eap_tls.h +@@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); + int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); + + +-void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); +-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label); ++void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); ++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); + void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); +-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header); +-void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); ++void eaptls_gen_eap_key(eap_handler_t *handler); ++void eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull); + + #define BUFFER_SIZE 1024 + +@@ -100,7 +100,7 @@ typedef struct tls_packet { + /* EAP-TLS framework */ + EAPTLS_PACKET *eaptls_alloc(void); + void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr); +-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert); ++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13); + int eaptls_start(EAP_DS *eap_ds, int peap); + int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply); + +diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c +index 3a9e864104..385441c62f 100644 +--- a/src/modules/rlm_eap/libeap/mppe_keys.c ++++ b/src/modules/rlm_eap/libeap/mppe_keys.c +@@ -26,11 +26,16 @@ RCSID("$Id$") + USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include "eap_tls.h" ++#include <openssl/ssl.h> + #include <openssl/hmac.h> ++#include <freeradius-devel/openssl3.h> + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#include <openssl/provider.h> ++#endif + + /* +- * TLS PRF from RFC 2246 ++ * TLS P_hash from RFC 2246/5246 section 5 + */ + static void P_hash(EVP_MD const *evp_md, + unsigned char const *secret, unsigned int secret_len, +@@ -38,23 +43,18 @@ static void P_hash(EVP_MD const *evp_md, + unsigned char *out, unsigned int out_len) + { + HMAC_CTX *ctx_a, *ctx_out; +- unsigned char a[HMAC_MAX_MD_CBLOCK]; +- unsigned int size; ++ unsigned char a[EVP_MAX_MD_SIZE]; ++ unsigned int size = EVP_MAX_MD_SIZE; ++ unsigned int digest_len; + + ctx_a = HMAC_CTX_new(); + ctx_out = HMAC_CTX_new(); +-#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW +- HMAC_CTX_set_flags(ctx_a, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); +- HMAC_CTX_set_flags(ctx_out, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); +-#endif + HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL); + HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL); + +- size = HMAC_size(ctx_out); +- + /* Calculate A(1) */ + HMAC_Update(ctx_a, seed, seed_len); +- HMAC_Final(ctx_a, a, NULL); ++ HMAC_Final(ctx_a, a, &size); + + while (1) { + /* Calculate next part of output */ +@@ -63,13 +63,15 @@ static void P_hash(EVP_MD const *evp_md, + + /* Check if last part */ + if (out_len < size) { +- HMAC_Final(ctx_out, a, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_out, a, &digest_len); + memcpy(out, a, out_len); + break; + } + + /* Place digest in output buffer */ +- HMAC_Final(ctx_out, out, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_out, out, &digest_len); + HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL); + out += size; + out_len -= size; +@@ -77,7 +79,8 @@ static void P_hash(EVP_MD const *evp_md, + /* Calculate next A(i) */ + HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL); + HMAC_Update(ctx_a, a, size); +- HMAC_Final(ctx_a, a, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_a, a, &digest_len); + } + + HMAC_CTX_free(ctx_a); +@@ -85,6 +88,82 @@ static void P_hash(EVP_MD const *evp_md, + memset(a, 0, sizeof(a)); + } + ++/* ++ * TLS PRF from RFC 2246 section 5 ++ */ ++static void PRF(unsigned char const *secret, unsigned int secret_len, ++ unsigned char const *seed, unsigned int seed_len, ++ unsigned char *out, unsigned int out_len) ++{ ++ uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)]; ++ unsigned int i; ++ ++ unsigned int len = (secret_len + 1) / 2; ++ uint8_t const *s1 = secret; ++ uint8_t const *s2 = secret + (secret_len - len); ++ ++ EVP_MD const *md5 = NULL; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ EVP_MD *md5_to_free = NULL; ++ ++ /* ++ * If we are using OpenSSL >= 3.0 and FIPS mode is ++ * enabled, we need to load the default provider in a ++ * standalone context in order to access MD5. ++ */ ++ OSSL_LIB_CTX *libctx = NULL; ++ OSSL_PROVIDER *default_provider = NULL; ++ ++ if (EVP_default_properties_is_fips_enabled(NULL)) { ++ libctx = OSSL_LIB_CTX_new(); ++ default_provider = OSSL_PROVIDER_load(libctx, "default"); ++ ++ if (!default_provider) { ++ ERROR("Failed loading OpenSSL default provider."); ++ return; ++ } ++ ++ md5_to_free = EVP_MD_fetch(libctx, "MD5", NULL); ++ if (!md5_to_free) { ++ ERROR("Failed loading OpenSSL MD5 function."); ++ return; ++ } ++ ++ md5 = md5_to_free; ++ } else { ++ md5 = EVP_md5(); ++ } ++#else ++ md5 = EVP_md5(); ++#endif ++ ++ P_hash(md5, s1, len, seed, seed_len, out, out_len); ++ P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); ++ ++ for (i = 0; i < out_len; i++) { ++ out[i] ^= buf[i]; ++ } ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ if (libctx) { ++ OSSL_PROVIDER_unload(default_provider); ++ OSSL_LIB_CTX_free(libctx); ++ EVP_MD_free(md5_to_free); ++ } ++#endif ++} ++ ++/* ++ * TLS 1.2 PRF from RFC 5246 section 5 ++ */ ++static void PRFv12(unsigned char const *secret, unsigned int secret_len, ++ unsigned char const *seed, unsigned int seed_len, ++ unsigned char *out, unsigned int out_len) ++{ ++ P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len); ++} ++ + /* EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */ + void T_PRF(unsigned char const *secret, unsigned int secret_len, + char const *prf_label, +@@ -128,60 +207,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len, + talloc_free(buf); + } + +-static void PRF(unsigned char const *secret, unsigned int secret_len, +- unsigned char const *seed, unsigned int seed_len, +- unsigned char *out, unsigned char *buf, unsigned int out_len) +-{ +- unsigned int i; +- unsigned int len = (secret_len + 1) / 2; +- uint8_t const *s1 = secret; +- uint8_t const *s2 = secret + (secret_len - len); +- +- P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len); +- P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); +- +- for (i=0; i < out_len; i++) { +- out[i] ^= buf[i]; +- } +-} +- + #define EAPTLS_MPPE_KEY_LEN 32 + + /* + * Generate keys according to RFC 2716 and add to reply + */ +-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) ++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) + { + uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; + uint8_t *p; +- size_t prf_size; ++ size_t len; + +- prf_size = strlen(prf_label); ++ len = strlen(label); + + #if OPENSSL_VERSION_NUMBER >= 0x10001000L +- if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) { ++ if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { + ERROR("Failed generating keying material"); + return; + } + #else + { +- uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)]; ++ uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; + uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; + + p = seed; + +- memcpy(p, prf_label, prf_size); +- p += prf_size; ++ memcpy(p, label, len); ++ p += len; + + memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); + p += SSL3_RANDOM_SIZE; +- prf_size += SSL3_RANDOM_SIZE; ++ len += SSL3_RANDOM_SIZE; + + memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); +- prf_size += SSL3_RANDOM_SIZE; ++ p += SSL3_RANDOM_SIZE; ++ len += SSL3_RANDOM_SIZE; ++ ++ if (context) { ++ /* cloned and reversed FR_PUT_LE16 */ ++ p[0] = ((uint16_t) (context_size)) >> 8; ++ p[1] = ((uint16_t) (context_size)) & 0xff; ++ p += 2; ++ len += 2; ++ memcpy(p, context, context_size); ++ p += context_size; ++ len += context_size; ++ } + + PRF(s->session->master_key, s->session->master_key_length, +- seed, prf_size, out, buf, sizeof(out)); ++ seed, len, out, buf, sizeof(out)); + } + #endif + +@@ -195,7 +269,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) + } + + +-#define FR_TLS_PRF_CHALLENGE "ttls challenge" ++#define FR_TLS_PRF_CHALLENGE "ttls challenge" + + /* + * Generate the TTLS challenge +@@ -206,9 +280,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) + void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) + { + #if OPENSSL_VERSION_NUMBER >= 0x10001000L +- SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, +- sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0); +- ++ if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, ++ sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) { ++ ERROR("Failed generating keying material"); ++ } + #else + uint8_t out[32], buf[32]; + uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; +@@ -226,14 +301,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) + #endif + } + ++#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id" ++ + /* + * Actually generates EAP-Session-Id, which is an internal server + * attribute. Not all systems want to send EAP-Key-Name. + */ +-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) ++void eaptls_gen_eap_key(eap_handler_t *handler) + { ++ RADIUS_PACKET *packet = handler->request->reply; ++ tls_session_t *tls_session = handler->opaque; ++ SSL *s = tls_session->ssl; + VALUE_PAIR *vp; + uint8_t *buff, *p; ++ uint8_t type = handler->type & 0xff; + + vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0); + if (!vp) return; +@@ -241,11 +322,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) + vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE; + buff = p = talloc_array(vp, uint8_t, vp->vp_length); + +- *p++ = header & 0xff; ++ *p++ = type; + +- SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); +- p += SSL3_RANDOM_SIZE; +- SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); ++ switch (SSL_version(tls_session->ssl)) { ++ case TLS1_VERSION: ++ case TLS1_1_VERSION: ++ case TLS1_2_VERSION: ++ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); ++ p += SSL3_RANDOM_SIZE; ++ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); ++ break; ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++#ifdef TLS1_3_VERSION ++ case TLS1_3_VERSION: ++#endif ++ default: ++ { ++ uint8_t const context[] = { type }; ++ ++ if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE, ++ FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1, ++ context, sizeof(context), 1) != 1) { ++ ERROR("Failed generating keying material"); ++ return; ++ } ++ } ++#endif ++ } + + vp->vp_octets = buff; + fr_pair_add(&packet->vps, vp); +@@ -254,7 +357,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) + /* + * Same as before, but for EAP-FAST the order of {server,client}_random is flipped + */ +-void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) ++void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label) + { + uint8_t *p; + size_t len, master_key_len; +@@ -273,7 +376,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_ + p += SSL3_RANDOM_SIZE; + + master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key)); +- PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size); +-} +- + ++ if (version == TLS1_2_VERSION) ++ PRFv12(master_key, master_key_len, seed, p - seed, buffer, size); ++ else ++ PRF(master_key, master_key_len, seed, p - seed, buffer, size); ++} +diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c +index 553a6a6a57..cd504a8363 100644 +--- a/src/modules/rlm_eap/radeapclient.c ++++ b/src/modules/rlm_eap/radeapclient.c +@@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port); + static void rc_evprep_packet_timeout(rc_transaction_t *trans); + static void rc_deallocate_id(rc_transaction_t *trans); + ++/* ++ * For cbtls_cache_*() ++ */ ++rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request) ++{ ++ return RLM_MODULE_FAIL; ++} ++ + + static void NEVER_RETURNS usage(void) + { +diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +index b0953aa1d4..bbb5a03c95 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c ++++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +@@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl) + return -1; + + RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d " +- "IV_len=%d", EVP_CIPHER_key_length(c), md_size, +- EVP_CIPHER_iv_length(c)); ++ "IV_len=%d", EVP_CIPHER_key_length(c), md_size, ++ EVP_CIPHER_iv_length(c)); + return 2 * (EVP_CIPHER_key_length(c) + + md_size + + EVP_CIPHER_iv_length(c)); + #else + const SSL_CIPHER *ssl_cipher; + int cipher, digest; ++ int mac_key_len, enc_key_len, fixed_iv_len; + + ssl_cipher = SSL_get_current_cipher(ssl); + if (!ssl_cipher) + return -1; + cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher); + digest = SSL_CIPHER_get_digest_nid(ssl_cipher); +- RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest); ++ RDEBUG3("OpenSSL: cipher nid %d digest nid %d", ++ cipher, digest); + if (cipher < 0 || digest < 0) + return -1; ++ if (cipher == NID_undef) { ++ RDEBUG3("OpenSSL: no cipher in use?!"); ++ return -1; ++ } + c = EVP_get_cipherbynid(cipher); +- h = EVP_get_digestbynid(digest); +- if (!c || !h) ++ if (!c) + return -1; ++ enc_key_len = EVP_CIPHER_key_length(c); ++ if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE || ++ EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE) ++ fixed_iv_len = 4; /* only part of IV from PRF */ ++ else ++ fixed_iv_len = EVP_CIPHER_iv_length(c); ++ if (digest == NID_undef) { ++ RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)"); ++ mac_key_len = 0; ++ } else { ++ h = EVP_get_digestbynid(digest); ++ if (!h) ++ return -1; ++ mac_key_len = EVP_MD_size(h); ++ } + +- RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d", +- EVP_CIPHER_key_length(c), EVP_MD_size(h), +- EVP_CIPHER_iv_length(c)); +- return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) + +- EVP_CIPHER_iv_length(c)); ++ RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d", ++ mac_key_len, enc_key_len, fixed_iv_len); ++ return 2 * (mac_key_len + enc_key_len + fixed_iv_len); + #endif + } + +@@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + { + eap_fast_tunnel_t *t = tls_session->opaque; + uint8_t *buf; +- uint8_t *scratch; + size_t ksize; + + RDEBUG2("Deriving EAP-FAST keys"); +@@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + ksize = openssl_get_keyblock_size(request, tls_session->ssl); + rad_assert(ksize > 0); + buf = talloc_size(request, ksize + sizeof(*t->keyblock)); +- scratch = talloc_size(request, ksize + sizeof(*t->keyblock)); + + t->keyblock = talloc(t, eap_fast_keyblock_t); + +- eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion"); ++ eap_fast_tls_gen_challenge(tls_session->ssl, SSL_version(tls_session->ssl), buf, ksize + sizeof(*t->keyblock), "key expansion"); + memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock)); + memset(buf, 0, ksize + sizeof(*t->keyblock)); + +@@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + t->imckc = 0; + + talloc_free(buf); +- talloc_free(scratch); + } + + /** +@@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio + dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), + t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, + pac.opaque.data, pac.opaque.tag); ++ if (dlen < 0) return; + + pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE); + pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); +@@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session, + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG("Got tunneled Access-Accept"); ++ tls_session->authentication_success = true; + rcode = RLM_MODULE_OK; + + for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { +@@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session) + t->mode = EAP_FAST_PROVISIONING_AUTH; + } + +- if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6) ++ /* ++ * Send a new pac at ~0.6 times the lifetime. ++ */ ++ if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) { + t->pac.send = true; ++ } + } + + eap_fast_init_keys(request, tls_session); +diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +index 2ce2dd0c3b..093dc868cd 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c ++++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +@@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + return -1; + } + ++#ifdef TLS1_3_VERSION ++ if (inst->tls_conf->min_version == TLS1_3_VERSION) { ++ ERROR("There are no standards for using TLS 1.3 with EAP-FAST."); ++ ERROR("You MUST enable TLS 1.2 for EAP-FAST to work."); ++ return -1; ++ } ++ ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using EAP-FAST with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++#endif ++ + rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH); + FR_MD5_CTX ctx; + fr_md5_init(&ctx); +@@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg) + DICT_ATTR const *fast_da; + char const *errmsg; + int dlen, plen; +- uint16_t length; ++ int length; + eap_fast_attr_pac_opaque_t const *opaque = (eap_fast_attr_pac_opaque_t const *) data; + eap_fast_attr_pac_opaque_t opaque_plaintext; + +@@ -274,7 +293,7 @@ error: + * so we have to use the length in the PAC-Opaque header + */ + length = ntohs(opaque->hdr.length); +- if (len - sizeof(opaque->hdr) < length) { ++ if (len < (int) (length + sizeof(opaque->hdr))) { + errmsg = "PAC has bad length in header"; + goto error; + } +@@ -293,7 +312,7 @@ error: + plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH, + (uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv, + (uint8_t *)&opaque_plaintext); +- if (plen == -1) { ++ if (plen < 0) { + errmsg = "PAC failed to decrypt"; + goto error; + } +@@ -314,8 +333,8 @@ error: + break; + case PAC_INFO_PAC_LIFETIME: + rad_assert(t->pac.expires == 0); +- t->pac.expires = vp->vp_integer; +- t->pac.expired = (vp->vp_integer <= time(NULL)); ++ t->pac.expires = vp->vp_integer + time(NULL); ++ t->pac.expired = false; + break; + case PAC_INFO_PAC_KEY: + rad_assert(t->pac.key == NULL); +@@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + /* +@@ -553,7 +572,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + } else { + client_cert = inst->req_client_cert; + } +- handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert); ++ ++ /* ++ * Don't allow TLS 1.3 for us, even if it's allowed ++ * elsewhere. We haven't implemented the necessary ++ * changes, so we don't allow it. ++ */ ++ handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false); + + if (!tls_session) return 0; + +@@ -566,16 +591,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + } + } + +-#ifdef SSL_OP_NO_TLSv1_2 +- /* +- * Forcibly disable TLSv1.2 +- * +- * @fixme - TLSv1.2 uses a different PRF and +- * SSL_export_keying_material("key expansion") is +- * forbidden +- */ +- SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2); ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++ { ++ int i; ++ for (i = 0; ; i++) { ++ const char *cipher = SSL_get_cipher_list(tls_session->ssl, i); ++ if (!cipher) break; ++ if (!strstr(cipher, "ADH-")) continue; ++ RDEBUG("Setting security level to 0 to allow anonymous cipher suites"); ++ SSL_set_security_level(tls_session->ssl, 0); ++ break; ++ } ++ } + #endif ++ + #ifdef SSL_OP_NO_TLSv1_3 + /* + * Forcibly disable TLSv1.3 +@@ -599,6 +628,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + rcode = eap_fast_tls_start(handler->eap_ds, tls_session); + + if (rcode < 0) { ++ error: + talloc_free(tls_session); + return 0; + } +@@ -607,7 +637,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + + if (!SSL_set_session_ticket_ext_cb(tls_session->ssl, _session_ticket, tls_session)) { + RERROR("Failed setting SSL session ticket callback"); +- return 0; ++ goto error; + } + + handler->stage = PROCESS; +diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +index deaf702d61..5647f613af 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c ++++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +@@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG2("Tunneled authentication was successful"); ++ tls_session->authentication_success = true; + t->status = PEAP_STATUS_SENT_TLV_SUCCESS; + eappeap_success(handler, tls_session); + rcode = RLM_MODULE_HANDLED; +@@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + /* send an identity request */ + t->session_resumption_state = PEAP_RESUMPTION_NO; + t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT; ++ tls_session->session_not_resumed = true; + eappeap_identity(handler, tls_session); + } + return RLM_MODULE_HANDLED; +@@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + + return RLM_MODULE_REJECT; + +- case PEAP_STATUS_PHASE2_INIT: +- RDEBUG("In state machine in phase2 init?"); ++ case PEAP_STATUS_PHASE2_INIT: ++ RDEBUG("In state machine in phase2 init?"); + +- case PEAP_STATUS_PHASE2: +- break; ++ case PEAP_STATUS_PHASE2: ++ break; + +- default: +- REDEBUG("Unhandled state in peap"); +- return RLM_MODULE_REJECT; ++ default: ++ REDEBUG("Unhandled state in peap"); ++ return RLM_MODULE_REJECT; + } + + fake = request_alloc_fake(request); +@@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + + if (vp) { + eap_tunnel_data_t *tunnel; ++ bool proxy_as_eap = t->proxy_tunneled_request_as_eap; ++ VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY); ++ ++ if (flag) proxy_as_eap = flag->vp_integer; + + /* + * The tunneled request was NOT handled, +@@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + * Once the tunneled EAP session is ALMOST + * done, THEN we proxy it... + */ +- if (!t->proxy_tunneled_request_as_eap) { ++ if (!proxy_as_eap) { + fake->options |= RAD_REQUEST_OPTION_PROXY_EAP; + + /* +diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +index 3d23322663..d9f850cef2 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c ++++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +@@ -192,7 +192,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + client_cert = inst->req_client_cert; + } + +- ssn = eaptls_session(handler, inst->tls_conf, client_cert); ++ /* ++ * Allow TLS 1.3, it works. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); + if (!ssn) { + return 0; + } +@@ -200,9 +203,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + handler->opaque = ((void *)ssn); + + /* +- * Set up type-specific information. ++ * Set the label to a fixed string. For TLS 1.3, the ++ * label is the same for all TLS-based EAP methods. If ++ * the client is using TLS 1.3, then eaptls_success() ++ * will over-ride this label with the correct label for ++ * TLS 1.3. + */ +- ssn->prf_label = "client EAP encryption"; ++ ssn->label = "client EAP encryption"; + + /* + * As it is a poorly designed protocol, PEAP uses +@@ -230,7 +237,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + if (status == 0) return 0; + +@@ -274,7 +281,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + /* +@@ -311,6 +318,10 @@ static int mod_process(void *arg, eap_handler_t *handler) + * data. + */ + case FR_TLS_OK: ++ /* ++ * TLSv1.3 makes application data immediately avaliable ++ */ ++ if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED; + break; + + /* +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h +new file mode 100644 +index 0000000000..b717dd51b3 +--- /dev/null ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h +@@ -0,0 +1,190 @@ ++/* ++ * Helper functions for constant time operations ++ * Copyright (c) 2019, The Linux Foundation ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * These helper functions can be used to implement logic that needs to minimize ++ * externally visible differences in execution path by avoiding use of branches, ++ * avoiding early termination or other time differences, and forcing same memory ++ * access pattern regardless of values. ++ */ ++ ++#ifndef CONST_TIME_H ++#define CONST_TIME_H ++ ++ ++#if defined(__clang__) ++#define NO_UBSAN_UINT_OVERFLOW \ ++ __attribute__((no_sanitize("unsigned-integer-overflow"))) ++#else ++#define NO_UBSAN_UINT_OVERFLOW ++#endif ++ ++/** ++ * const_time_fill_msb - Fill all bits with MSB value ++ * @param val Input value ++ * @return Value with all the bits set to the MSB of the input val ++ */ ++static inline unsigned int const_time_fill_msb(unsigned int val) ++{ ++ /* Move the MSB to LSB and multiple by -1 to fill in all bits. */ ++ return (val >> (sizeof(val) * 8 - 1)) * ~0U; ++} ++ ++ ++/* @return -1 if val is zero; 0 if val is not zero */ ++static inline unsigned int const_time_is_zero(unsigned int val) ++ NO_UBSAN_UINT_OVERFLOW ++{ ++ /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */ ++ return const_time_fill_msb(~val & (val - 1)); ++} ++ ++ ++/* @return -1 if a == b; 0 if a != b */ ++static inline unsigned int const_time_eq(unsigned int a, unsigned int b) ++{ ++ return const_time_is_zero(a ^ b); ++} ++ ++ ++/* @return -1 if a == b; 0 if a != b */ ++static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b) ++{ ++ return (unsigned char) const_time_eq(a, b); ++} ++ ++ ++/** ++ * const_time_eq_bin - Constant time memory comparison ++ * @param a First buffer to compare ++ * @param b Second buffer to compare ++ * @param len Number of octets to compare ++ * @return -1 if buffers are equal, 0 if not ++ * ++ * This function is meant for comparing passwords or hash values where ++ * difference in execution time or memory access pattern could provide external ++ * observer information about the location of the difference in the memory ++ * buffers. The return value does not behave like memcmp(), i.e., ++ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike ++ * memcmp(), the execution time of const_time_eq_bin() does not depend on the ++ * contents of the compared memory buffers, but only on the total compared ++ * length. ++ */ ++static inline unsigned int const_time_eq_bin(const void *a, const void *b, ++ size_t len) ++{ ++ const unsigned char *aa = a; ++ const unsigned char *bb = b; ++ size_t i; ++ unsigned char res = 0; ++ ++ for (i = 0; i < len; i++) ++ res |= aa[i] ^ bb[i]; ++ ++ return const_time_is_zero(res); ++} ++ ++ ++/** ++ * const_time_select - Constant time unsigned int selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline unsigned int const_time_select(unsigned int mask, ++ unsigned int true_val, ++ unsigned int false_val) ++{ ++ return (mask & true_val) | (~mask & false_val); ++} ++ ++ ++/** ++ * const_time_select_int - Constant time int selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline int const_time_select_int(unsigned int mask, int true_val, ++ int false_val) ++{ ++ return (int) const_time_select(mask, (unsigned int) true_val, ++ (unsigned int) false_val); ++} ++ ++ ++/** ++ * const_time_select_u8 - Constant time u8 selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val) ++{ ++ return (unsigned char) const_time_select(mask, true_val, false_val); ++} ++ ++ ++/** ++ * const_time_select_s8 - Constant time s8 selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline char const_time_select_s8(char mask, char true_val, char false_val) ++{ ++ return (char) const_time_select(mask, (unsigned int) true_val, ++ (unsigned int) false_val); ++} ++ ++ ++/** ++ * const_time_select_bin - Constant time binary buffer selection copy ++ * @param mask 0 (false) or -1 (true) to identify which value to copy ++ * @param true_val Buffer to copy for the true case ++ * @param false_val Buffer to copy for the false case ++ * @param len Number of octets to copy ++ * @param dst Destination buffer for the copy ++ * ++ * This function copies the specified buffer into the destination buffer using ++ * operations with identical memory access pattern regardless of which buffer ++ * is being copied. ++ */ ++static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val, ++ const unsigned char *false_val, size_t len, ++ unsigned char *dst) ++{ ++ size_t i; ++ ++ for (i = 0; i < len; i++) ++ dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]); ++} ++ ++ ++static inline int const_time_memcmp(const void *a, const void *b, size_t len) ++{ ++ const unsigned char *aa = a; ++ const unsigned char *bb = b; ++ int diff, res = 0; ++ unsigned int mask; ++ ++ if (len == 0) ++ return 0; ++ do { ++ len--; ++ diff = (int) aa[len] - (int) bb[len]; ++ mask = const_time_is_zero((unsigned int) diff); ++ res = const_time_select_int(mask, res, diff); ++ } while (len); ++ ++ return res; ++} ++ ++#endif /* CONST_TIME_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c +index d94851c3aa..26260527a5 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c +@@ -1,7 +1,5 @@ +-/* +- * Copyright (c) Dan Harkins, 2012 +- * +- * Copyright holder grants permission for redistribution and use in source ++/** ++ * copyright holder grants permission for redistribution and use in source + * and binary forms, with or without modification, provided that the + * following conditions are met: + * 1. Redistribution of source code must retain the above copyright +@@ -29,100 +27,237 @@ + * This license and distribution terms cannot be changed. In other words, + * this code cannot simply be copied and put under a different distribution + * license (including the GNU public license). ++ * ++ * @copyright (c) Dan Harkins, 2012 + */ + + RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $") + USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include "eap_pwd.h" ++#include "const_time.h" ++#include <freeradius-devel/openssl3.h> + +-#include <freeradius-devel/radiusd.h> +-#include <freeradius-devel/modules.h> ++static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 }; + + /* The random function H(x) = HMAC-SHA256(0^32, x) */ +-static void H_Init(HMAC_CTX *ctx) ++static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest) + { +- uint8_t allzero[SHA256_DIGEST_LENGTH]; ++ unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ HMAC_Final(hmac_ctx, digest, &mdlen); ++// HMAC_CTX_reset(hmac_ctx); ++} + +- memset(allzero, 0, SHA256_DIGEST_LENGTH); ++/* a counter-based KDF based on NIST SP800-108 */ ++static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label, ++ int label_len, uint8_t *result, int result_bit_len) ++{ ++ HMAC_CTX *hmac_ctx; ++ uint8_t digest[SHA256_DIGEST_LENGTH]; ++ uint16_t i, ctr, L; ++ int result_byte_len, len = 0; ++ unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ uint8_t mask = 0xff; ++ ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ result_byte_len = (result_bit_len + 7) / 8; ++ ++ ctr = 0; ++ L = htons(result_bit_len); ++ while (len < result_byte_len) { ++ ctr++; i = htons(ctr); + +- HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); ++ HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL); ++ if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen); ++ HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t)); ++ HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len); ++ HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t)); ++ HMAC_Final(hmac_ctx, digest, &mdlen); ++ if ((len + (int) mdlen) > result_byte_len) { ++ memcpy(result + len, digest, result_byte_len - len); ++ } else { ++ memcpy(result + len, digest, mdlen); ++ } ++ len += mdlen; ++// HMAC_CTX_reset(hmac_ctx); ++ } ++ ++ /* since we're expanding to a bit length, mask off the excess */ ++ if (result_bit_len % 8) { ++ mask <<= (8 - (result_bit_len % 8)); ++ result[result_byte_len - 1] &= mask; ++ } ++ ++ HMAC_CTX_free(hmac_ctx); + } + +-static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len) ++static BIGNUM *consttime_BN (void) + { +- HMAC_Update(ctx, data, len); ++ BIGNUM *bn; ++ ++ bn = BN_new(); ++ if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME); ++ return bn; + } + +-static void H_Final(HMAC_CTX *ctx, uint8_t *digest) ++/* ++ * compute the legendre symbol in constant time ++ */ ++static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx) + { +- unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ int symbol; ++ unsigned int mask; ++ BIGNUM *res, *pm1over2; ++ ++ pm1over2 = consttime_BN(); ++ res = consttime_BN(); ++ ++ if (!BN_sub(pm1over2, p, BN_value_one()) || ++ !BN_rshift1(pm1over2, pm1over2) || ++ !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) { ++ BN_free(pm1over2); ++ BN_free(res); ++ return -2; ++ } ++ ++ symbol = -1; ++ mask = const_time_eq(BN_is_word(res, 1), 1); ++ symbol = const_time_select_int(mask, 1, symbol); ++ mask = const_time_eq(BN_is_zero(res), 1); ++ symbol = const_time_select_int(mask, -1, symbol); ++ ++ BN_free(pm1over2); ++ BN_free(res); + +- HMAC_Final(ctx, digest, &mdlen); ++ return symbol; + } + +-/* a counter-based KDF based on NIST SP800-108 */ +-static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen) ++static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx) + { +- HMAC_CTX *hctx = NULL; +- uint8_t digest[SHA256_DIGEST_LENGTH]; +- uint16_t i, ctr, L; +- int resultbytelen, len = 0; +- unsigned int mdlen = SHA256_DIGEST_LENGTH; +- uint8_t mask = 0xff; ++ BIGNUM *p, *a, *b, *tmp1, *pm1; + +- hctx = HMAC_CTX_new(); +- if (hctx == NULL) { +- DEBUG("failed allocating HMAC context"); +- return -1; ++ tmp1 = BN_new(); ++ pm1 = BN_new(); ++ p = BN_new(); ++ a = BN_new(); ++ b = BN_new(); ++ EC_GROUP_get_curve(group, p, a, b, bnctx); ++ ++ BN_sub(pm1, p, BN_value_one()); ++ ++ /* ++ * y2 = x^3 + ax + b ++ */ ++ BN_mod_sqr(tmp1, x, p, bnctx); ++ BN_mod_mul(y2, tmp1, x, p, bnctx); ++ BN_mod_mul(tmp1, a, x, p, bnctx); ++ BN_mod_add_quick(y2, y2, tmp1, p); ++ BN_mod_add_quick(y2, y2, b, p); ++ ++ BN_free(tmp1); ++ BN_free(pm1); ++ BN_free(p); ++ BN_free(a); ++ BN_free(b); ++ ++ return; ++} ++ ++static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx) ++{ ++ int offset, check, ret = 0; ++ BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL; ++ unsigned int mask; ++ unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL; ++ ++ if (((r = consttime_BN()) == NULL) || ++ ((res = consttime_BN()) == NULL) || ++ ((qr_or_qnr = consttime_BN()) == NULL) || ++ ((pm1 = consttime_BN()) == NULL)) { ++ ret = -2; ++ goto fail; + } +- resultbytelen = (resultbitlen + 7)/8; +- ctr = 0; +- L = htons(resultbitlen); +- while (len < resultbytelen) { +- ctr++; i = htons(ctr); +- HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL); +- if (ctr > 1) { +- HMAC_Update(hctx, digest, mdlen); +- } +- HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t)); +- HMAC_Update(hctx, (uint8_t const *)label, labellen); +- HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t)); +- HMAC_Final(hctx, digest, &mdlen); +- if ((len + (int) mdlen) > resultbytelen) { +- memcpy(result + len, digest, resultbytelen - len); +- } else { +- memcpy(result + len, digest, mdlen); +- } +- len += mdlen; ++ ++ if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || ++ ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || ++ ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) { ++ ret = -2; ++ goto fail; + } +- HMAC_CTX_free(hctx); + +- /* since we're expanding to a bit length, mask off the excess */ +- if (resultbitlen % 8) { +- mask <<= (8 - (resultbitlen % 8)); +- result[resultbytelen - 1] &= mask; ++ /* ++ * we select binary in constant time so make them binary ++ */ ++ memset(qr_bin, 0, BN_num_bytes(p)); ++ memset(qnr_bin, 0, BN_num_bytes(p)); ++ memset(qr_or_qnr_bin, 0, BN_num_bytes(p)); ++ ++ offset = BN_num_bytes(p) - BN_num_bytes(qr); ++ BN_bn2bin(qr, qr_bin + offset); ++ ++ offset = BN_num_bytes(p) - BN_num_bytes(qnr); ++ BN_bn2bin(qnr, qnr_bin + offset); ++ ++ /* ++ * r = (random() mod p-1) + 1 ++ */ ++ BN_sub(pm1, p, BN_value_one()); ++ BN_rand_range(r, pm1); ++ BN_add(r, r, BN_value_one()); ++ ++ BN_copy(res, val); ++ ++ /* ++ * res = val * r * r which ensures res != val but has same quadratic residocity ++ */ ++ BN_mod_mul(res, res, r, p, bnctx); ++ BN_mod_mul(res, res, r, p, bnctx); ++ ++ /* ++ * if r is even (mask is -1) then multiply by qnr and our check is qnr ++ * otherwise multiply by qr and our check is qr ++ */ ++ mask = const_time_is_zero(BN_is_odd(r)); ++ const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin); ++ BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr); ++ BN_mod_mul(res, res, qr_or_qnr, p, bnctx); ++ check = const_time_select_int(mask, -1, 1); ++ ++ if ((ret = legendre(res, p, bnctx)) == -2) { ++ ret = -1; /* just say no it's not */ ++ goto fail; + } ++ mask = const_time_eq(ret, check); ++ ret = const_time_select_int(mask, 1, 0); + +- return 0; ++fail: ++ if (qr_bin != NULL) free(qr_bin); ++ if (qnr_bin != NULL) free(qnr_bin); ++ if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin); ++ BN_free(r); ++ BN_free(res); ++ BN_free(qr_or_qnr); ++ BN_free(pm1); ++ ++ return ret; + } + +-int compute_password_element (pwd_session_t *session, uint16_t grp_num, ++int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num, + char const *password, int password_len, + char const *id_server, int id_server_len, + char const *id_peer, int id_peer_len, + uint32_t *token) + { +- BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL; +- HMAC_CTX *ctx = NULL; +- uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr; +- int nid, is_odd, primebitlen, primebytelen, ret = 0; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG("failed allocating HMAC context"); +- goto fail; +- } ++ BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL; ++ EVP_MD_CTX *hmac_ctx; ++ EVP_PKEY *hmac_pkey; ++ uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = NULL, ctr; ++ int nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask; ++ int save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp; ++ unsigned int skip; ++ ++ MEM(hmac_ctx = EVP_MD_CTX_new()); ++ MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero))); + + switch (grp_num) { /* from IANA registry for IKE D-H groups */ + case 19: +@@ -159,17 +294,23 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + goto fail; + } + +- if (((rnd = BN_new()) == NULL) || +- ((cofactor = BN_new()) == NULL) || ++ if (((rnd = consttime_BN()) == NULL) || + ((session->pwe = EC_POINT_new(session->group)) == NULL) || +- ((session->order = BN_new()) == NULL) || +- ((session->prime = BN_new()) == NULL) || +- ((x_candidate = BN_new()) == NULL)) { ++ ((session->order = consttime_BN()) == NULL) || ++ ((session->prime = consttime_BN()) == NULL) || ++ ((qr = consttime_BN()) == NULL) || ++ ((qnr = consttime_BN()) == NULL) || ++ ((x_candidate = consttime_BN()) == NULL) || ++ ((y_sqrd = consttime_BN()) == NULL) || ++ ((y1 = consttime_BN()) == NULL) || ++ ((y2 = consttime_BN()) == NULL) || ++ ((y = consttime_BN()) == NULL) || ++ ((exp = consttime_BN()) == NULL)) { + DEBUG("unable to create bignums"); + goto fail; + } + +- if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) { ++ if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) { + DEBUG("unable to get prime for GFp curve"); + goto fail; + } +@@ -179,46 +320,80 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + goto fail; + } + +- if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { +- DEBUG("unable to get cofactor for curve"); +- goto fail; +- } +- + primebitlen = BN_num_bits(session->prime); + primebytelen = BN_num_bytes(session->prime); + if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for prf buffer"); + goto fail; + } ++ if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for x buffer"); ++ goto fail; ++ } ++ if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for pm1 buffer"); ++ goto fail; ++ } ++ if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for y1 buffer"); ++ goto fail; ++ } ++ if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for y2 buffer"); ++ goto fail; ++ } ++ if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for y buffer"); ++ goto fail; ++ } ++ ++ ++ /* ++ * derive random quadradic residue and quadratic non-residue ++ */ ++ do { ++ BN_rand_range(qr, session->prime); ++ } while (legendre(qr, session->prime, session->bnctx) != 1); ++ ++ do { ++ BN_rand_range(qnr, session->prime); ++ } while (legendre(qnr, session->prime, session->bnctx) != -1); ++ ++ if (!BN_sub(rnd, session->prime, BN_value_one())) { ++ goto fail; ++ } ++ BN_bn2bin(rnd, pm1buf); ++ ++ save_is_odd = 0; ++ found = 0; ++ memset(xbuf, 0, primebytelen); + ctr = 0; +- while (1) { +- if (ctr > 100) { +- DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num); +- goto fail; +- } ++ while (ctr < 40) { + ctr++; + + /* + * compute counter-mode password value and stretch to prime +- * pwd-seed = H(token | peer-id | server-id | password | +- * counter) ++ * pwd-seed = H(token | peer-id | server-id | password | ++ * counter) + */ +- H_Init(ctx); +- H_Update(ctx, (uint8_t *)token, sizeof(*token)); +- H_Update(ctx, (uint8_t const *)id_peer, id_peer_len); +- H_Update(ctx, (uint8_t const *)id_server, id_server_len); +- H_Update(ctx, (uint8_t const *)password, password_len); +- H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr)); +- H_Final(ctx, pwe_digest); ++ EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey); ++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token)); ++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len); ++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len); ++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len); ++ EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr)); ++ ++ { ++ size_t mdlen = SHA256_DIGEST_LENGTH; ++ ++ EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen); ++ EVP_MD_CTX_reset(hmac_ctx); ++ } + + BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd); +- if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", +- strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) { +- DEBUG("key derivation function failed"); +- goto fail; +- } ++ eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", ++ strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen); + +- BN_bin2bn(prfbuf, primebytelen, x_candidate); + /* + * eap_pwd_kdf() returns a string of bits 0..primebitlen but + * BN_bin2bn will treat that string of bits as a big endian +@@ -226,49 +401,86 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + * then excessive bits-- those _after_ primebitlen-- so now + * we have to shift right the amount we masked off. + */ +- if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8))); +- if (BN_ucmp(x_candidate, session->prime) >= 0) continue; ++ if (primebitlen % 8) { ++ rbits = 8 - (primebitlen % 8); ++ for (i = primebytelen - 1; i > 0; i--) { ++ prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits); ++ } ++ prfbuf[0] >>= rbits; ++ } ++ BN_bin2bn(prfbuf, primebytelen, x_candidate); + + /* +- * need to unambiguously identify the solution, if there is +- * one... +- */ +- is_odd = BN_is_odd(rnd) ? 1 : 0; ++ * it would've been better if the spec reduced the candidate ++ * modulo the prime but it didn't. So if the candidate >= prime ++ * we need to skip it but still run through the operations below ++ */ ++ cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen); ++ skip = const_time_fill_msb((unsigned int)cmp); + + /* +- * solve the quadratic equation, if it's not solvable then we +- * don't have a point +- */ +- if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) { +- continue; +- } ++ * need to unambiguously identify the solution, if there is ++ * one.. ++ */ ++ is_odd = BN_is_odd(rnd); + + /* +- * If there's a solution to the equation then the point must be +- * on the curve so why check again explicitly? OpenSSL code +- * says this is required by X9.62. We're not X9.62 but it can't +- * hurt just to be sure. +- */ +- if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) { +- DEBUG("EAP-pwd: point is not on curve"); +- continue; +- } ++ * check whether x^3 + a*x + b is a quadratic residue ++ * ++ * save the first quadratic residue we find in the loop but do ++ * it in constant time. ++ */ ++ do_equation(session->group, y_sqrd, x_candidate, session->bnctx); ++ qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx); + +- if (BN_cmp(cofactor, BN_value_one())) { +- /* make sure the point is not in a small sub-group */ +- if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe, +- cofactor, NULL)) { +- DEBUG("EAP-pwd: cannot multiply generator by order"); +- continue; +- } ++ /* ++ * if the candidate >= prime then we want to skip it ++ */ ++ qr_or_qnr = const_time_select(skip, 0, qr_or_qnr); + +- if (EC_POINT_is_at_infinity(session->group, session->pwe)) { +- DEBUG("EAP-pwd: point is at infinity"); +- continue; +- } +- } +- /* if we got here then we have a new generator. */ +- break; ++ /* ++ * if we haven't found PWE yet (found = 0) then mask will be true, ++ * if we have found PWE then mask will be false ++ */ ++ mask = const_time_select(found, 0, -1); ++ ++ /* ++ * save will be 1 if we want to save this value-- i.e. we haven't ++ * found PWE yet and this is a quadratic residue-- and 0 otherwise ++ */ ++ save = const_time_select(mask, qr_or_qnr, 0); ++ ++ /* ++ * mask will be true (-1) if we want to save this and false (0) ++ * otherwise ++ */ ++ mask = const_time_eq(save, 1); ++ ++ const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf); ++ save_is_odd = const_time_select(mask, is_odd, save_is_odd); ++ found = const_time_select(mask, -1, found); ++ } ++ ++ /* ++ * now we can savely construct PWE ++ */ ++ BN_bin2bn(xbuf, primebytelen, x_candidate); ++ do_equation(session->group, y_sqrd, x_candidate, session->bnctx); ++ if ( !BN_add(exp, session->prime, BN_value_one()) || ++ !BN_rshift(exp, exp, 2) || ++ !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) || ++ !BN_sub(y2, session->prime, y1) || ++ !BN_bn2bin(y1, y1buf) || ++ !BN_bn2bin(y2, y2buf)) { ++ DEBUG("unable to compute y"); ++ goto fail; ++ } ++ mask = const_time_eq(save_is_odd, BN_is_odd(y1)); ++ const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf); ++ if (BN_bin2bn(ybuf, primebytelen, y) == NULL || ++ !EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) { ++ DEBUG("unable to set point coordinate"); ++ goto fail; + } + + session->group_num = grp_num; +@@ -278,78 +490,89 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + } + + /* cleanliness and order.... */ +- BN_clear_free(cofactor); + BN_clear_free(x_candidate); ++ BN_clear_free(y_sqrd); ++ BN_clear_free(qr); ++ BN_clear_free(qnr); + BN_clear_free(rnd); +- talloc_free(prfbuf); +- HMAC_CTX_free(ctx); ++ BN_clear_free(y1); ++ BN_clear_free(y2); ++ BN_clear_free(y); ++ BN_clear_free(exp); ++ ++ if (prfbuf) talloc_free(prfbuf); ++ if (xbuf) talloc_free(xbuf); ++ if (pm1buf) talloc_free(pm1buf); ++ if (y1buf) talloc_free(y1buf); ++ if (y2buf) talloc_free(y2buf); ++ if (ybuf) talloc_free(ybuf); ++ ++ EVP_MD_CTX_free(hmac_ctx); ++ EVP_PKEY_free(hmac_pkey); + + return ret; + } + +-int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) { ++int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx) ++{ + BIGNUM *mask = NULL; + int ret = -1; + +- if (((session->private_value = BN_new()) == NULL) || +- ((session->my_element = EC_POINT_new(session->group)) == NULL) || +- ((session->my_scalar = BN_new()) == NULL) || +- ((mask = BN_new()) == NULL)) { +- DEBUG2("server scalar allocation failed"); +- goto fail; +- } ++ MEM(session->private_value = BN_new()); ++ MEM(session->my_element = EC_POINT_new(session->group)); ++ MEM(session->my_scalar = BN_new()); ++ ++ MEM(mask = BN_new()); + + if (BN_rand_range(session->private_value, session->order) != 1) { +- DEBUG2("Unable to get randomness for private_value"); +- goto fail; ++ REDEBUG("Unable to get randomness for private_value"); ++ goto error; + } + if (BN_rand_range(mask, session->order) != 1) { +- DEBUG2("Unable to get randomness for mask"); +- goto fail; ++ REDEBUG("Unable to get randomness for mask"); ++ goto error; + } + BN_add(session->my_scalar, session->private_value, mask); +- BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx); ++ BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx); + +- if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) { +- DEBUG2("server element allocation failed"); +- goto fail; ++ if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) { ++ REDEBUG("Server element allocation failed"); ++ goto error; + } + +- if (!EC_POINT_invert(session->group, session->my_element, bnctx)) { +- DEBUG2("server element inversion failed"); +- goto fail; ++ if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) { ++ REDEBUG("Server element inversion failed"); ++ goto error; + } + + ret = 0; + +-fail: ++error: + BN_clear_free(mask); + + return ret; + } + +-int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx) ++int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx) + { +- uint8_t *ptr; +- size_t data_len; +- BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; +- EC_POINT *K = NULL, *point = NULL; +- int res = 1; +- +- if (((session->peer_scalar = BN_new()) == NULL) || +- ((session->k = BN_new()) == NULL) || +- ((cofactor = BN_new()) == NULL) || +- ((x = BN_new()) == NULL) || +- ((y = BN_new()) == NULL) || +- ((point = EC_POINT_new(session->group)) == NULL) || +- ((K = EC_POINT_new(session->group)) == NULL) || +- ((session->peer_element = EC_POINT_new(session->group)) == NULL)) { +- DEBUG2("pwd: failed to allocate room to process peer's commit"); +- goto finish; +- } ++ uint8_t *ptr; ++ size_t data_len; ++ BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; ++ EC_POINT *K = NULL, *point = NULL; ++ int ret = 1; ++ ++ MEM(session->peer_scalar = BN_new()); ++ MEM(session->k = BN_new()); ++ MEM(session->peer_element = EC_POINT_new(session->group)); ++ MEM(point = EC_POINT_new(session->group)); ++ MEM(K = EC_POINT_new(session->group)); ++ ++ MEM(cofactor = BN_new()); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { +- DEBUG2("pwd: unable to get group co-factor"); ++ REDEBUG("Unable to get group co-factor"); + goto finish; + } + +@@ -361,7 +584,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + * Did the peer send enough data? + */ + if (in_len < (2 * data_len + BN_num_bytes(session->order))) { +- DEBUG("pwd: Invalid commit packet"); ++ REDEBUG("Invalid commit packet"); + goto finish; + } + +@@ -377,54 +600,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + if (BN_is_zero(session->peer_scalar) || + BN_is_one(session->peer_scalar) || + BN_cmp(session->peer_scalar, session->order) >= 0) { +- ERROR("Peer's scalar is not within the allowed range"); ++ REDEBUG("Peer's scalar is not within the allowed range"); + goto finish; + } + +- if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + /* validate received element */ +- if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) || ++ if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) || + EC_POINT_is_at_infinity(session->group, session->peer_element)) { +- ERROR("Peer's element is not a point on the elliptic curve"); ++ REDEBUG("Peer's element is not a point on the elliptic curve"); + goto finish; + } + + /* check to ensure peer's element is not in a small sub-group */ + if (BN_cmp(cofactor, BN_value_one())) { + if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) { +- DEBUG2("pwd: unable to multiply element by co-factor"); ++ REDEBUG("Unable to multiply element by co-factor"); + goto finish; + } + + if (EC_POINT_is_at_infinity(session->group, point)) { +- DEBUG2("pwd: peer's element is in small sub-group"); ++ REDEBUG("Peer's element is in small sub-group"); + goto finish; + } + } + + /* detect reflection attacks */ + if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 || +- EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) { +- ERROR("Reflection attack detected"); ++ EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) { ++ REDEBUG("Reflection attack detected"); + goto finish; + } + + /* compute the shared key, k */ +- if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) || +- (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) || +- (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) { +- DEBUG2("pwd: unable to compute shared key, k"); ++ if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) || ++ (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) || ++ (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) { ++ REDEBUG("Unable to compute shared key, k"); + goto finish; + } + + /* ensure that the shared key isn't in a small sub-group */ + if (BN_cmp(cofactor, BN_value_one())) { + if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) { +- DEBUG2("pwd: unable to multiply k by co-factor"); ++ REDEBUG("Unable to multiply k by co-factor"); + goto finish; + } + } +@@ -436,15 +659,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + * sure" so let's be safe. + */ + if (EC_POINT_is_at_infinity(session->group, K)) { +- DEBUG2("pwd: k is point-at-infinity!"); ++ REDEBUG("K is point-at-infinity"); + goto finish; + } + +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) { +- DEBUG2("pwd: unable to get shared secret from K"); ++ if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) { ++ REDEBUG("Unable to get shared secret from K"); + goto finish; + } +- res = 0; ++ ret = 0; + + finish: + EC_POINT_clear_free(K); +@@ -453,36 +676,29 @@ finish: + BN_clear_free(x); + BN_clear_free(y); + +- return res; ++ return ret; + } + +-int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) ++int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) + { +- BIGNUM *x = NULL, *y = NULL; +- HMAC_CTX *ctx = NULL; +- uint8_t *cruft = NULL; +- int offset, req = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ BIGNUM *x = NULL, *y = NULL; ++ HMAC_CTX *hmac_ctx = NULL; ++ uint8_t *cruft = NULL; ++ int offset, req = -1; + + /* + * Each component of the cruft will be at most as big as the prime + */ +- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || +- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { +- DEBUG2("pwd: unable to allocate space to compute confirm!"); +- goto finish; +- } ++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + /* + * commit is H(k | server_element | server_scalar | peer_element | + * peer_scalar | ciphersuite) + */ +- H_Init(ctx); ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + /* + * Zero the memory each time because this is mod prime math and some +@@ -492,24 +708,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + */ + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * next is server element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of server element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of server element"); + goto finish; + } + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and server scalar +@@ -517,25 +733,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * next is peer element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and peer scalar +@@ -543,52 +759,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * finally, ciphersuite + */ +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + +- H_Final(ctx, out); ++ pwd_hmac_final(hmac_ctx, out); + + req = 0; ++ + finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); + BN_free(x); + BN_free(y); +- HMAC_CTX_free(ctx); + + return req; + } + +-int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) ++int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) + { +- BIGNUM *x = NULL, *y = NULL; +- HMAC_CTX *ctx = NULL; +- uint8_t *cruft = NULL; +- int offset, req = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ BIGNUM *x = NULL, *y = NULL; ++ HMAC_CTX *hmac_ctx = NULL; ++ uint8_t *cruft = NULL; ++ int offset, req = -1; + + /* + * Each component of the cruft will be at most as big as the prime + */ +- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || +- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { +- DEBUG2("pwd: unable to allocate space to compute confirm!"); +- goto finish; +- } ++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + /* + * commit is H(k | server_element | server_scalar | peer_element | + * peer_scalar | ciphersuite) + */ +- H_Init(ctx); ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + /* + * Zero the memory each time because this is mod prime math and some +@@ -598,25 +808,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + */ + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * then peer element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and peer scalar +@@ -624,24 +834,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * then server element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of server element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of server element"); + goto finish; + } + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and server scalar +@@ -649,94 +859,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * finally, ciphersuite + */ +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + +- H_Final(ctx, out); ++ pwd_hmac_final(hmac_ctx, out); + + req = 0; + finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); + BN_free(x); + BN_free(y); +- HMAC_CTX_free(ctx); + + return req; + } + +-int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) ++int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) + { +- HMAC_CTX *ctx = NULL; +- uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL; +- uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; +- uint8_t msk_emsk[128]; /* 64 each */ +- int offset, ret = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ HMAC_CTX *hmac_ctx; ++ uint8_t mk[SHA256_DIGEST_LENGTH], *cruft; ++ uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; ++ uint8_t msk_emsk[128]; /* 64 each */ ++ int offset; + +- if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) { +- DEBUG2("pwd: unable to allocate space to compute keys"); +- goto finish; +- } ++ MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(hmac_ctx = HMAC_CTX_new()); + + /* + * first compute the session-id = TypeCode | H(ciphersuite | scal_p | + * scal_s) + */ + session_id[0] = PW_EAP_PWD; +- H_Init(ctx); +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + memset(cruft, 0, BN_num_bytes(session->prime)); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + memset(cruft, 0, BN_num_bytes(session->prime)); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); +- H_Final(ctx, (uint8_t *)&session_id[1]); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); ++ pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]); + + /* then compute MK = H(k | commit-peer | commit-server) */ +- H_Init(ctx); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + +- H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH); ++ HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH); + +- H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH); ++ HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH); + +- H_Final(ctx, mk); ++ pwd_hmac_final(hmac_ctx, mk); + + /* stretch the mk with the session-id to get MSK | EMSK */ +- if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, +- SHA256_DIGEST_LENGTH + 1, msk_emsk, +- /* it's bits, ((64 + 64) * 8) */ +- 1024) != 0) { +- DEBUG("key derivation function failed"); +- goto finish; +- } ++ eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, ++ SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024); /* it's bits, ((64 + 64) * 8) */ + + memcpy(msk, msk_emsk, 64); + memcpy(emsk, msk_emsk + 64, 64); + +- ret = 0; +-finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); +- HMAC_CTX_free(ctx); +- return ret; ++ return 0; + } +- +- +- +- +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h +index ca12778f61..a40a346069 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h +@@ -102,18 +102,22 @@ typedef struct _pwd_session_t { + EC_POINT *my_element; + EC_POINT *peer_element; + uint8_t my_confirm[SHA256_DIGEST_LENGTH]; ++ uint8_t prep; ++ uint8_t salt_present; ++ uint8_t salt_len; ++ uint8_t salt[255]; + } pwd_session_t; + +-int compute_password_element(pwd_session_t *sess, uint16_t grp_num, ++int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num, + char const *password, int password_len, + char const *id_server, int id_server_len, + char const *id_peer, int id_peer_len, + uint32_t *token); +-int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx); +-int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); +-int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +-int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +-int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm, ++int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx); ++int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); ++int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); ++int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); ++int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm, + uint8_t *msk, uint8_t *emsk); + #ifdef PRINTBUF + void print_buf(char *str, uint8_t *buf, int len); +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c +index 18ab97f148..4992a2aeef 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c +@@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #define MPPE_KEY_LEN 32 + #define MSK_EMSK_LEN (2*MPPE_KEY_LEN) + ++/* EAP-PWD can use different preprocessing (prep) modes to mangle the password ++ * before proving to both parties that they both know the same (mangled) password. ++ * ++ * The server advertises a preprocessing mode to the client. Only "none" is ++ * mandatory to implement. ++ * ++ * What is a good selection on the preprocessing mode? ++ * ++ * a) the server uses a hashed password ++ * b) the client uses a hashed password ++ * ++ * a | b | result ++ * --+---+--------------------------------------- ++ * n | n | none ++ * n | y | hint needed (cannot know automatically) ++ * y | n | select by hash given ++ * y | y | only works if both have the same hash; select by hash given ++ * ++ * Which hash functions does the server or client need to implement? ++ * ++ * a | b | server | client ++ * --+---+------------------------+---------------------- ++ * n | n | none | none ++ * n | y | as configured | none ++ * y | n | none | as selected by server ++ * y | y | none | none ++ * ++ * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement. ++ * Can we avoid implementing them all? Only if they are provided as hash by some ++ * other module, e.g. in SQL or statically in password database. ++ * ++ * Therefore we select the preprocessing mode by the type of password given if ++ * in automatic mode: ++ * a) Cleartext-Password or User-Password: None. ++ * If the client only supports a hash (e.g. on Windows it might only have an ++ * NT-Password), do not provide a Cleartext-Password attribute but instead ++ * preprocess the password externally (e.g. hash the Cleartext-Password ++ * into an NT-Password and drop the Cleartext-Password). ++ * b) NT-Password: rfc2759 (prep=MS). ++ * The NT-Password Hash is hashed into a HashNTPasswordHash hash. ++ * c) EAP-Pwd-Password-Hash - provides hash as binary ++ * EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client ++ * (RFC 8146) ++ * EAP-Pwd-Password-Prep - constant to transmit to client in prep field ++ * ++ * Though, there is one issue left. The method needs to be selected in ++ * EAP-PWD-ID/Request, that is the first message from server and thus before ++ * the client sent its peer-id. This is feasable using the EAP-Identity frame ++ * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway. ++ * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use ++ * only the peer_id (inner identity). This toogle is an integer to also support ++ * setting currently unknown nor not implemented preprocessing methods. ++ * ++ * The toogle is named "prep", is a module configuration item, and accepts the ++ * following values: ++ * prep | meaning ++ * -------+-------------------------------------------------------------------- ++ * -1 | [automatic] discover using method described above from EAP-Identity ++ * | as User-Name before EAP-PWD-Id/Request ++ * 0..255 | [static] Fixed password preprocessing method. Expects virtual ++ * | server to provide matching password given EAP-PWD ++ * | peer-id as User-Name. The virtual server is provided ++ * | with EAP-Pwd-Password-Prep containing the configured ++ * | prep value. ++ * else | reserved/invalid ++ * ++ * Attributes to provide Password/Password-Hash and possibly salt. ++ * prep | accepted attributes ++ * -------+-------------------------------------------------------------------- ++ * -1 | see above for automatic discovery ++ * 0 | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash ++ * 1 | Use NT-Password, Cleartext-Password, User-Password or ++ * | give hashed NT-Password hash in EAP-Pwd-Password-Hash ++ * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt. ++ * ++ * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex ++ * string, they are decoded as hex if module config option unhex=1 (default). ++ * Set it to zero if you provide binary input. ++ */ ++ + static CONF_PARSER pwd_module_config[] = { + { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" }, + { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" }, + { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL }, ++ { "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" }, ++ { "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" }, + CONF_PARSER_TERMINATOR + }; + +@@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance) + return -1; + } + ++ if (inst->prep < -1 || inst->prep > 255) { ++ cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep); ++ return -1; ++ } ++ + return 0; + } + +@@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds) + return 1; + } + ++static void normify(REQUEST *request, VALUE_PAIR *vp) ++{ ++ size_t decoded; ++ size_t expected_len; ++ uint8_t *buffer; ++ ++ rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING)); ++ ++ if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return; ++ ++ expected_len = vp->vp_length / 2; ++ buffer = talloc_zero_array(request, uint8_t, expected_len); ++ rad_assert(buffer); ++ ++ decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length); ++ if (decoded == expected_len) { ++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes", ++ vp->da->name, vp->vp_length, decoded); ++ fr_pair_value_memcpy(vp, buffer, decoded); ++ } else { ++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes", ++ vp->da->name, vp->vp_length, expected_len, decoded); ++ } ++ ++ talloc_free(buffer); ++} ++ ++static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) { ++ REQUEST *fake; ++ VALUE_PAIR *vp, *pw; ++ const char *pwbuf; ++ int pw_len; ++ uint8_t nthash[MD4_DIGEST_LENGTH]; ++ uint8_t nthashash[MD4_DIGEST_LENGTH]; ++ int ret = -1; ++ eap_type_t old_eap_type = 0; ++ ++ if ((fake = request_alloc_fake(request)) == NULL) { ++ RDEBUG("pwd unable to create fake request!"); ++ return ret; ++ } ++ fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); ++ if (!fake->username) { ++ RDEBUG("Failed creating pair for peer id"); ++ goto out; ++ } ++ fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); ++ fr_pair_add(&fake->packet->vps, fake->username); ++ ++ if (inst->prep >= 0) { ++ vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0); ++ rad_assert(vp != NULL); ++ vp->vp_byte = inst->prep; ++ fr_pair_add(&fake->packet->vps, vp); ++ } ++ ++ if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { ++ fake->server = vp->vp_strvalue; ++ } else if (inst->virtual_server) { ++ fake->server = inst->virtual_server; ++ } /* else fake->server == request->server */ ++ ++ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { ++ /* EAP-Type = NAK here if inst->prep == -1. ++ * But this does not help the virtual server to differentiate ++ * based on which EAP method was selected, that is to properly ++ * prepare session-state: for PWD. ++ * So fake EAP-Type = PWD here for the time of the inner request. ++ */ ++ old_eap_type = vp->vp_integer; ++ vp->vp_integer = PW_EAP_PWD; ++ } ++ RDEBUG("Sending tunneled request"); ++ rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); ++ ++ if (fake->server) { ++ RDEBUG("server %s {", fake->server); ++ } else { ++ RDEBUG("server {"); ++ } ++ ++ /* ++ * Call authorization recursively, which will ++ * get the password. ++ */ ++ RINDENT(); ++ process_authorize(0, fake); ++ REXDENT(); ++ ++ /* ++ * Note that we don't do *anything* with the reply ++ * attributes. ++ */ ++ if (fake->server) { ++ RDEBUG("} # server %s", fake->server); ++ } else { ++ RDEBUG("}"); ++ } ++ ++ RDEBUG("Got tunneled reply code %d", fake->reply->code); ++ rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); ++ ++ if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { ++ vp->vp_integer = old_eap_type; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); ++ if (!pw) { ++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); ++ } ++ ++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_NONE; ++ ++ RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ pwbuf = pw->vp_strvalue; ++ pw_len = pw->vp_length; ++ ++ goto success; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY); ++ ++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_MS; ++ ++ RDEBUG("Use NT-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (pw->vp_length != MD4_DIGEST_LENGTH) { ++ RDEBUG("NT-Password invalid length"); ++ goto out; ++ } ++ ++ fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length); ++ pwbuf = (const char*) nthashash; ++ pw_len = MD4_DIGEST_LENGTH; ++ ++ goto success; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); ++ if (!pw) { ++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); ++ } ++ ++ if (pw && inst->prep == EAP_PWD_PREP_MS) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_NONE; ++ ++ RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ // compute NT-Hash from Cleartext-Password ++ ssize_t len; ++ uint8_t ucs2_password[512]; ++ len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length); ++ if (len < 0) { ++ ERROR("rlm_eap_pwd: Error converting password to UCS2"); ++ goto out; ++ } ++ fr_md4_calc(nthash, ucs2_password, len); ++ ++ fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH); ++ pwbuf = (const char*) nthashash; ++ pw_len = MD4_DIGEST_LENGTH; ++ ++ goto success; ++ } ++ ++ vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY); ++ if (vp) { ++ VERIFY_VP(vp); ++ } ++ if (vp && inst->prep < 0) { ++ RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication", ++ vp->vp_byte, session->peer_id); ++ session->prep = vp->vp_byte; ++ } else if (vp && inst->prep != vp->vp_byte) { ++ RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s", ++ session->peer_id); ++ goto out; ++ } else if (inst->prep < 0) { ++ RDEBUG2("Missing EAP-Pwd-Password-Prep for %s", ++ session->peer_id); ++ goto out; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY); ++ if (pw) { ++ VERIFY_VP(pw); ++ ++ RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (inst->unhex) normify(request, pw); ++ ++ if (pw->vp_length > 255) { ++ /* salt len is 1 byte */ ++ RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)"); ++ goto out; ++ } ++ rad_assert(pw->vp_length <= sizeof(session->salt)); ++ ++ session->salt_present = 1; ++ session->salt_len = pw->vp_length; ++ memcpy(session->salt, pw->vp_octets, pw->vp_length); ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY); ++ if (pw) { ++ VERIFY_VP(pw); ++ ++ RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (inst->unhex) normify(request, pw); ++ ++ pwbuf = (const char*) pw->vp_octets; ++ pw_len = pw->vp_length; ++ ++ goto success; ++ } ++ ++ RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s", ++ session->peer_id); ++ goto out; ++ ++success: ++ if (RDEBUG_ENABLED4) { ++ char outbuf[1024]; ++ char *p = outbuf; ++ for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) { ++ p += sprintf(p, "%02hhX", pwbuf[i]); ++ } ++ RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len); ++ } ++ ++ if (compute_password_element(request, session, session->group_num, ++ pwbuf, pw_len, ++ inst->server_id, strlen(inst->server_id), ++ session->peer_id, strlen(session->peer_id), ++ &session->token)) { ++ RDEBUG("failed to obtain password element"); ++ goto out; ++ } ++ ++ ret = 0; ++out: ++ talloc_free(fake); ++ return ret; ++} ++ + static int mod_session_init (void *instance, eap_handler_t *handler) + { + pwd_session_t *session; + eap_pwd_t *inst = (eap_pwd_t *)instance; + VALUE_PAIR *vp; + pwd_id_packet_t *packet; ++ REQUEST *request; + + if (!inst || !handler) { + ERROR("rlm_eap_pwd: Initiate, NULL data provided"); + return 0; + } + ++ request = handler->request; ++ if (!request) { ++ ERROR("rlm_eap_pwd: NULL request provided"); ++ return 0; ++ } ++ + /* + * make sure the server's been configured properly + */ +@@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler) + session->out_pos = 0; + handler->opaque = session; + ++ session->token = fr_rand(); ++ if (inst->prep < 0) { ++ RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity); ++ session->peer_id_len = strlen(handler->identity); ++ if (session->peer_id_len >= sizeof(session->peer_id)) { ++ RDEBUG("identity is malformed"); ++ return 0; ++ } ++ memcpy(session->peer_id, handler->identity, session->peer_id_len); ++ session->peer_id[session->peer_id_len] = '\0'; ++ ++ /* ++ * make fake request to get the password for the usable ID ++ * in order to identity prep ++ */ ++ if (fetch_and_process_password(session, handler->request, inst) < 0) { ++ RDEBUG("failed to find password for %s to do pwd authentication (init)", ++ session->peer_id); ++ return 0; ++ } ++ } else { ++ session->prep = inst->prep; ++ } ++ + /* + * construct an EAP-pwd-ID/Request + */ +@@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler) + packet->group_num = htons(session->group_num); + packet->random_function = EAP_PWD_DEF_RAND_FUN; + packet->prf = EAP_PWD_DEF_PRF; +- session->token = fr_rand(); + memcpy(packet->token, (char *)&session->token, 4); +- packet->prep = EAP_PWD_PREP_NONE; ++ packet->prep = session->prep; + memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) ); + + handler->stage = PROCESS; +@@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler) + pwd_session_t *session; + pwd_hdr *hdr; + pwd_id_packet_t *packet; ++ REQUEST *request; + eap_packet_t *response; +- REQUEST *request, *fake; +- VALUE_PAIR *pw, *vp; + EAP_DS *eap_ds; +- size_t in_len; ++ size_t in_len, peer_id_len; + int ret = 0; + eap_pwd_t *inst = (eap_pwd_t *)arg; + uint16_t offset; + uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; + uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; ++ char *peer_id; + + if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0; + +@@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + + if ((packet->prf != EAP_PWD_DEF_PRF) || + (packet->random_function != EAP_PWD_DEF_RAND_FUN) || +- (packet->prep != EAP_PWD_PREP_NONE) || ++ (packet->prep != session->prep) || + (CRYPTO_memcmp(packet->token, &session->token, 4)) || + (packet->group_num != ntohs(session->group_num))) { + RDEBUG2("pwd id response is invalid"); +@@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler) + ptr += sizeof(uint8_t); + *ptr = EAP_PWD_DEF_PRF; + +- session->peer_id_len = in_len - sizeof(pwd_id_packet_t); +- if (session->peer_id_len >= sizeof(session->peer_id)) { ++ peer_id_len = in_len - sizeof(pwd_id_packet_t); ++ if (peer_id_len >= sizeof(session->peer_id)) { + RDEBUG2("pwd id response is malformed"); + return 0; + } ++ peer_id = packet->identity; + +- memcpy(session->peer_id, packet->identity, session->peer_id_len); +- session->peer_id[session->peer_id_len] = '\0'; +- +- /* +- * make fake request to get the password for the usable ID +- */ +- if ((fake = request_alloc_fake(handler->request)) == NULL) { +- RDEBUG("pwd unable to create fake request!"); +- return 0; +- } +- fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); +- if (!fake->username) { +- RDEBUG("Failed creating pair for peer id"); +- talloc_free(fake); +- return 0; +- } +- fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); +- fr_pair_add(&fake->packet->vps, fake->username); +- +- if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { +- fake->server = vp->vp_strvalue; +- } else if (inst->virtual_server) { +- fake->server = inst->virtual_server; +- } /* else fake->server == request->server */ +- +- RDEBUG("Sending tunneled request"); +- rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); +- +- if (fake->server) { +- RDEBUG("server %s {", fake->server); +- } else { +- RDEBUG("server {"); +- } ++ if (inst->prep >= 0) { ++ /* ++ * make fake request to get the password for the usable ID ++ */ + +- /* +- * Call authorization recursively, which will +- * get the password. +- */ +- RINDENT(); +- process_authorize(0, fake); +- REXDENT(); ++ session->peer_id_len = peer_id_len; ++ memcpy(session->peer_id, peer_id, peer_id_len); ++ session->peer_id[peer_id_len] = '\0'; + +- /* +- * Note that we don't do *anything* with the reply +- * attributes. +- */ +- if (fake->server) { +- RDEBUG("} # server %s", fake->server); ++ if (fetch_and_process_password(session, request, inst) < 0) { ++ RDEBUG2("failed to find password for %s to do pwd authentication", ++ session->peer_id); ++ return 0; ++ } + } else { +- RDEBUG("}"); ++ /* verify inner identity == outer identity */ ++ if (session->peer_id_len != peer_id_len || ++ memcmp(session->peer_id, peer_id, peer_id_len) != 0) { ++ char buf[sizeof(session->peer_id)]; ++ memcpy(buf, peer_id, peer_id_len); ++ buf[peer_id_len] = '\0'; ++ ++ RDEBUG2("inner identity(peer_id) %s does not match outer identity %s", ++ buf, session->peer_id); ++ return 0; ++ } ++ RDEBUG2("inner identity matched for %s", session->peer_id); + } + +- RDEBUG("Got tunneled reply code %d", fake->reply->code); +- rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); +- +- if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) { +- DEBUG2("failed to find password for %s to do pwd authentication", +- session->peer_id); +- talloc_free(fake); +- return 0; +- } +- +- if (compute_password_element(session, session->group_num, +- pw->data.strvalue, strlen(pw->data.strvalue), +- inst->server_id, strlen(inst->server_id), +- session->peer_id, strlen(session->peer_id), +- &session->token)) { +- DEBUG2("failed to obtain password element"); +- talloc_free(fake); +- return 0; +- } +- TALLOC_FREE(fake); +- + /* + * compute our scalar and element + */ +- if (compute_scalar_element(session, session->bnctx)) { ++ if (compute_scalar_element(request, session, session->bnctx)) { + DEBUG2("failed to compute server's scalar and element"); + return 0; + } +@@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * element is a point, get both coordinates: x and y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, + session->bnctx)) { + DEBUG2("server point assignment failed"); + BN_clear_free(x); +@@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler) + * construct request + */ + session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); ++ if (session->salt_present) ++ session->out_len += 1 + session->salt_len; ++ + if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { + return 0; + } + memset(session->out, 0, session->out_len); + + ptr = session->out; ++ if (session->salt_present) { ++ *ptr = session->salt_len; ++ ptr++; ++ ++ memcpy(ptr, session->salt, session->salt_len); ++ ptr += session->salt_len; ++ } ++ + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, ptr + offset); + BN_clear_free(x); +@@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + } + break; + +- case PWD_STATE_COMMIT: ++ case PWD_STATE_COMMIT: + if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { + RDEBUG2("pwd exchange is incorrect: not commit!"); + return 0; +@@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * process the peer's commit and generate the shared key, k + */ +- if (process_peer_commit(session, in, in_len, session->bnctx)) { ++ if (process_peer_commit(request, session, in, in_len, session->bnctx)) { + RDEBUG2("failed to process peer's commit"); + return 0; + } +@@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * compute our confirm blob + */ +- if (compute_server_confirm(session, session->my_confirm, session->bnctx)) { ++ if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) { + ERROR("rlm_eap_pwd: failed to compute confirm!"); + return 0; + } +@@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + RDEBUG2("pwd exchange is incorrect: not commit!"); + return 0; + } +- if (compute_peer_confirm(session, peer_confirm, session->bnctx)) { ++ if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) { + RDEBUG2("pwd exchange cannot compute peer's confirm"); + return 0; + } +@@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); + return 0; + } +- if (compute_keys(session, peer_confirm, msk, emsk)) { ++ if (compute_keys(request, session, peer_confirm, msk, emsk)) { + RDEBUG2("pwd exchange cannot generate (E)MSK!"); + return 0; + } +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h +index 2264566bb6..966646c360 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h +@@ -44,6 +44,8 @@ typedef struct _eap_pwd_t { + uint32_t fragment_size; + char const *server_id; + char const *virtual_server; ++ int32_t prep; ++ int32_t unhex; + } eap_pwd_t; + + #endif /* _RLM_EAP_PWD_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +index 4d41cd42e6..d327c575fc 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +@@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + static CONF_PARSER module_config[] = { + { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL }, ++ { "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + return -1; + } + ++#ifdef TLS1_3_VERSION ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++#endif ++ + return 0; + } + +@@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + tls_session_t *ssn; + rlm_eap_tls_t *inst; + REQUEST *request = handler->request; ++ bool require_client_cert = true; + + inst = type_arg; + + handler->tls = true; + + /* +- * EAP-TLS always requires a client certificate. ++ * Respect EAP-TLS-Require-Client-Cert, but only if ++ * enabled in the module configuration. ++ * ++ * We can't change behavior of existing systems, so this ++ * change has to be enabled via a new configuration ++ * option. + */ +- ssn = eaptls_session(handler, inst->tls_conf, true); ++ if (inst->configurable_client_cert) { ++ VALUE_PAIR *vp; ++ ++ vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY); ++ if (vp && !vp->vp_integer) require_client_cert = false; ++ } ++ ++ /* ++ * EAP-TLS always requires a client certificate, and ++ * allows for TLS 1.3 if permitted. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true); + if (!ssn) { + return 0; + } + + handler->opaque = ((void *)ssn); +- +- /* +- * Set up type-specific information. +- */ +- ssn->prf_label = "client EAP encryption"; ++ ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */ + + /* + * TLS session initialization is over. Now handle TLS +@@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + if (status == 0) return 0; + +@@ -141,7 +168,7 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + +@@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) + /* success */ + } + ++ /* ++ * Set the label to a fixed string. For TLS 1.3, ++ * the label is the same for all TLS-based EAP ++ * methods. ++ */ ++ tls_session->label = "client EAP encryption"; ++ + /* + * Success: Automatically return MPPE keys. + */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +index cebcb92f57..550cbbdce3 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h ++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +@@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t { + * Virtual server for checking certificates + */ + char const *virtual_server; ++ ++ /* ++ * Configurable EAP-TLS-Require-Client-Cert ++ */ ++ bool configurable_client_cert; + } rlm_eap_tls_t; + + #endif /* _RLM_EAP_TLS_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +index a3c575bceb..4e53c92244 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +@@ -181,7 +181,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + client_cert = inst->req_client_cert; + } + +- ssn = eaptls_session(handler, inst->tls_conf, client_cert); ++ /* ++ * Allow TLS 1.3, it works. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); + if (!ssn) { + return 0; + } +@@ -189,9 +192,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + handler->opaque = ((void *)ssn); + + /* +- * Set up type-specific information. ++ * Set the label to a fixed string. For TLS 1.3, the ++ * label is the same for all TLS-based EAP methods. If ++ * the client is using TLS 1.3, then eaptls_success() ++ * will over-ride this label with the correct label for ++ * TLS 1.3. + */ +- ssn->prf_label = "ttls keying material"; ++ ssn->label = "ttls keying material"; + + /* + * TLS session initialization is over. Now handle TLS +@@ -201,7 +208,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + if (status == 0) return 0; + +@@ -238,7 +245,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + /* +@@ -342,8 +349,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + * Success: Automatically return MPPE keys. + */ + case PW_CODE_ACCESS_ACCEPT: +- ret = eaptls_success(handler, 0); +- goto done; ++ goto do_keys; + + /* + * No response packet, MUST be proxying it. +diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +index 627d722ed7..cbe423951a 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +@@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + size_t size; + size_t data_left = data_len; + VALUE_PAIR *first = NULL; +- VALUE_PAIR *vp; ++ VALUE_PAIR *vp = NULL; + RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ + vp_cursor_t out; ++ DICT_ATTR const *da; + + fr_cursor_init(&out, &first); + +@@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + if (decoded < 0) { + REDEBUG2("diameter2vp failed decoding attr: %s", + fr_strerror()); +- goto do_octets; ++ goto raw; + } + + if ((size_t) decoded != size + 2) { + REDEBUG2("diameter2vp failed to entirely decode VSA"); + fr_pair_list_free(&vp); +- goto do_octets; ++ goto raw; + } + + fr_cursor_merge(&out, vp); +@@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + /* + * Create it. If this fails, it's because we're OOM. + */ +- do_octets: +- vp = fr_pair_afrom_num(packet, attr, vendor); ++ da = dict_attrbyvalue(attr, vendor); ++ if (!da) goto raw; ++ ++ vp = fr_pair_afrom_da(packet, da); + if (!vp) { + RDEBUG2("Failure in creating VP"); + fr_pair_list_free(&first); +@@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + case PW_TYPE_INTEGER: + case PW_TYPE_DATE: + if (size != vp->vp_length) { +- DICT_ATTR const *da; +- + /* + * Bad format. Create a "raw" + * attribute. +@@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + */ + if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || + ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { +- uint8_t challenge[16]; ++ uint8_t challenge[17]; + + if ((vp->vp_length < 8) || + (vp->vp_length > 16)) { +@@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + return NULL; + } + +- eapttls_gen_challenge(ssl, challenge, +- sizeof(challenge)); ++ /* ++ * TLSv1.3 exports a different key depending on the length ++ * requested so ask for *exactly* what the spec requires ++ */ ++ eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1); + + if (memcmp(challenge, vp->vp_octets, + vp->vp_length) != 0) { +@@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se + */ + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: ++ tls_session->authentication_success = true; + RDEBUG("Got tunneled Access-Accept"); + + rcode = RLM_MODULE_OK; +diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c +index e7dbfa685e..f7e23628e6 100644 +--- a/src/modules/rlm_exec/rlm_exec.c ++++ b/src/modules/rlm_exec/rlm_exec.c +@@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r + * If we're not waiting, then there are no output pairs. + */ + if (inst->output) { +- fr_pair_list_move(ctx, output_pairs, &answer); ++ fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD); + } + fr_pair_list_free(&answer); + +@@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + /* + * Always add the value-pairs to the reply. + */ +- fr_pair_list_move(request->reply, &request->reply->vps, &tmp); ++ fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD); + fr_pair_list_free(&tmp); + + finish: +diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c +index c449d7776b..f835800376 100644 +--- a/src/modules/rlm_expr/rlm_expr.c ++++ b/src/modules/rlm_expr/rlm_expr.c +@@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request, + return strlen(out); + } + ++/** Calculate the MD4 hash of a string or attribute. ++ * ++ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2" ++ */ ++static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ uint8_t digest[16]; ++ ssize_t i, len, inlen; ++ uint8_t const *p; ++ FR_MD4_CTX ctx; ++ ++ /* ++ * We need room for at least one octet of output. ++ */ ++ if (outlen < 3) { ++ *out = '\0'; ++ return 0; ++ } ++ ++ inlen = xlat_fmt_to_ref(&p, request, fmt); ++ if (inlen < 0) { ++ return -1; ++ } ++ ++ fr_md4_init(&ctx); ++ fr_md4_update(&ctx, p, inlen); ++ fr_md4_final(digest, &ctx); ++ ++ /* ++ * Each digest octet takes two hex digits, plus one for ++ * the terminating NUL. ++ */ ++ len = (outlen / 2) - 1; ++ if (len > 16) len = 16; ++ ++ for (i = 0; i < len; i++) { ++ snprintf(out + i * 2, 3, "%02x", digest[i]); ++ } ++ ++ return strlen(out); ++} ++ + /** Calculate the SHA1 hash of a string or attribute. + * + * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" +@@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request, + return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now)); + } + ++/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s). ++ * ++ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520. ++ */ ++static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ long num; ++ ++ char const *p; ++ char *q; ++ time_t now; ++ struct tm *local, local_buff; ++ ++ now = time(NULL); ++ local = localtime_r(&now, &local_buff); ++ ++ p = fmt; ++ ++ num = strtoul(p, &q, 10); ++ if (!q || *q == '\0') { ++ REDEBUG("nexttime: <int> must be followed by period specifier (h|d|w|m|y)"); ++ return -1; ++ } ++ ++ if (p == q) { ++ num = 1; ++ } else { ++ p += q - p; ++ } ++ ++ local->tm_sec = 0; ++ local->tm_min = 0; ++ ++ switch (*p) { ++ case 'h': ++ local->tm_hour -= num; ++ break; ++ ++ case 'd': ++ local->tm_hour = 0; ++ local->tm_mday -= num; ++ break; ++ ++ case 'w': ++ local->tm_hour = 0; ++ local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1)); ++ break; ++ ++ case 'm': ++ local->tm_hour = 0; ++ local->tm_mday = 1; ++ local->tm_mon -= num; ++ break; ++ ++ case 'y': ++ local->tm_hour = 0; ++ local->tm_mday = 1; ++ local->tm_mon = 0; ++ local->tm_year -= num; ++ break; ++ ++ default: ++ REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p); ++ return -1; ++ } ++ ++ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local))); ++} ++ + + /* + * Parse the 3 arguments to lpad / rpad. +@@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) + xlat_register("unescape", unescape_xlat, NULL, inst); + xlat_register("tolower", tolower_xlat, NULL, inst); + xlat_register("toupper", toupper_xlat, NULL, inst); ++ xlat_register("md4", md4_xlat, NULL, inst); + xlat_register("md5", md5_xlat, NULL, inst); + xlat_register("sha1", sha1_xlat, NULL, inst); + #ifdef HAVE_OPENSSL_EVP_H +@@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) + xlat_register("explode", explode_xlat, NULL, inst); + + xlat_register("nexttime", next_time_xlat, NULL, inst); ++ xlat_register("lasttime", last_time_xlat, NULL, inst); + xlat_register("lpad", lpad_xlat, NULL, inst); + xlat_register("rpad", rpad_xlat, NULL, inst); + +diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c +index c825a9230b..9e77cd7ff1 100644 +--- a/src/modules/rlm_files/rlm_files.c ++++ b/src/modules/rlm_files/rlm_files.c +@@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * + /* ctx may be reply or proxy */ + reply_tmp = fr_pair_list_copy(reply_packet, pl->reply); + radius_pairmove(request, &reply_packet->vps, reply_tmp, true); +- fr_pair_list_move(request, &request->config, &check_tmp); ++ fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD); + fr_pair_list_free(&check_tmp); + + /* +diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h +index 7dc874d35e..29e7cfd757 100644 +--- a/src/modules/rlm_ldap/ldap.h ++++ b/src/modules/rlm_ldap/ldap.h +@@ -20,6 +20,7 @@ + * always need to support that. + */ + #define LDAP_DEPRECATED 1 ++USES_APPLE_DEPRECATED_API /* Apple wants us to use OpenDirectory Framework, we don't want that */ + #include <lber.h> + #include <ldap.h> + #include "config.h" +@@ -265,6 +266,9 @@ typedef struct ldap_instance { + + int tls_require_cert; //!< OpenLDAP constant representing the require cert string. + ++ char const *tls_min_version_str; //!< Minimum TLS version ++ int tls_min_version; ++ + /* + * Options + */ +diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c +index 83c84a63e2..25bcd9c44e 100644 +--- a/src/modules/rlm_mschap/rlm_mschap.c ++++ b/src/modules/rlm_mschap/rlm_mschap.c +@@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request, + data = response->vp_octets + 2; + data_len = 24; + ++ /* ++ * Pull the domain name out of the User-Name, if it exists. ++ * ++ * This is the full domain name, not just the name after host/ ++ */ ++ } else if (strncasecmp(fmt, "Domain-Name", 11) == 0) { ++ char *p; ++ ++ user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); ++ if (!user_name) { ++ REDEBUG("No User-Name was found in the request"); ++ return -1; ++ } ++ ++ /* ++ * First check to see if this is a host/ style User-Name ++ * (a la Kerberos host principal) ++ */ ++ if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) { ++ /* ++ * If we're getting a User-Name formatted in this way, ++ * it's likely due to PEAP. The Windows Domain will be ++ * the first domain component following the hostname, ++ * or the machine name itself if only a hostname is supplied ++ */ ++ p = strchr(user_name->vp_strvalue, '.'); ++ if (!p) { ++ RDEBUG2("setting NT-Domain to same as machine name"); ++ strlcpy(out, user_name->vp_strvalue + 5, outlen); ++ } else { ++ p++; /* skip the period */ ++ strlcpy(out, p, outlen); ++ } ++ } else { ++ p = strchr(user_name->vp_strvalue, '\\'); ++ if (!p) { ++ REDEBUG("No NT-Domain was found in the User-Name"); ++ return -1; ++ } ++ ++ /* ++ * Hack. This is simpler than the alternatives. ++ */ ++ *p = '\0'; ++ strlcpy(out, user_name->vp_strvalue, outlen); ++ *p = '\\'; ++ } ++ ++ return strlen(out); ++ + /* + * Pull the NT-Domain out of the User-Name, if it exists. + */ +@@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) + return -1; + } + #else +- cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time"); ++ cf_log_err_cs(conf, "'winbind' is not enabled in this build."); + return -1; + #endif + } +@@ -942,7 +992,6 @@ ntlm_auth_err: + ssize_t result_len; + char result[253]; + uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH]; +- RC4_KEY key; + + if (!nt_password) { + RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute"); +@@ -951,11 +1000,47 @@ ntlm_auth_err: + RDEBUG("Doing MS-CHAPv2 password change locally"); + } + +- /* +- * Decrypt the blob +- */ +- RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); +- RC4(&key, 516, new_nt_password, nt_pass_decrypted); ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ { ++ EVP_CIPHER_CTX *ctx; ++ int ntlen = sizeof(nt_pass_decrypted); ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (!ctx) { ++ REDEBUG("Failed getting RC4 from OpenSSL"); ++ error: ++ if (ctx) EVP_CIPHER_CTX_free(ctx); ++ return -1; ++ } ++ ++ if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { ++ REDEBUG("Failed setting key length"); ++ goto error; ++ } ++ ++ if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { ++ REDEBUG("Failed setting key value"); ++ goto error;; ++ } ++ ++ if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { ++ REDEBUG("Failed getting output"); ++ goto error; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ } ++#else ++ { ++ RC4_KEY key; ++ ++ /* ++ * Decrypt the blob ++ */ ++ RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); ++ RC4(&key, 516, new_nt_password, nt_pass_decrypted); ++ } ++#endif + + /* + * pwblock is +diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c +index 399689abf3..932e44abe9 100644 +--- a/src/modules/rlm_otp/otp_mppe.c ++++ b/src/modules/rlm_otp/otp_mppe.c +@@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include <string.h> + ++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) ++#define UNUSED3 ++#else ++#define UNUSED3 UNUSED ++#endif ++ + /* + * Add MPPE attributes to a request, if required. + */ +-void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode) ++void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode) + { + VALUE_PAIR *cvp, *rvp; + +@@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const + case PWE_CHAP: + return; + ++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) + case PWE_MSCHAP: + /* First, set some related attributes. */ + pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ); +@@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const + + break; /* PWE_MSCHAP2 */ + } /* PWE_MSCHAP2 */ +- ++#else ++ case PWE_MSCHAP: ++ case PWE_MSCHAP2: ++ REDEBUG("MS-CHAP is unsupported for OpenSSL 3."); ++ break; ++#endif + } /* switch (pwe) */ + + return; +diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c +index 66fd8b4987..256437a552 100644 +--- a/src/modules/rlm_otp/otp_radstate.c ++++ b/src/modules/rlm_otp/otp_radstate.c +@@ -113,6 +113,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], + HMAC_CTX *hmac_ctx; + uint8_t hmac[MD5_DIGEST_LENGTH]; + char *p; ++ unsigned int len = sizeof(hmac); + + /* + * Generate the hmac. We already have a dependency on openssl for +@@ -125,7 +126,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], + HMAC_Update(hmac_ctx, (uint8_t const *) challenge, clen); + HMAC_Update(hmac_ctx, (uint8_t *) &flags, 4); + HMAC_Update(hmac_ctx, (uint8_t *) &when, 4); +- HMAC_Final(hmac_ctx, hmac, NULL); ++ HMAC_Final(hmac_ctx, hmac, &len); + HMAC_CTX_free(hmac_ctx); + + /* +diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c +index 8b85df0662..fcb3fd11fc 100644 +--- a/src/modules/rlm_rest/rest.c ++++ b/src/modules/rlm_rest/rest.c +@@ -115,6 +115,15 @@ do {\ + }\ + } while (0) + ++/* ++ * that macro is originally declared in include/curl/curlver.h ++ * We have to use this as curl uses lots of enums ++ */ ++#ifndef CURL_AT_LEAST_VERSION ++# define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z)) ++# define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) ++#endif ++ + const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = { + 0, // HTTP_AUTH_UNKNOWN + 0, // HTTP_AUTH_NONE +@@ -221,6 +230,40 @@ const FR_NAME_NUMBER http_content_type_table[] = { + { NULL , -1 } + }; + ++/** Conversion table for "HTTP" protocol version to use. ++ * ++ * Used by rlm_rest_t for specify the http client version. ++ * ++ * Values we expect to use in curl_easy_setopt() ++ * ++ * @see fr_str2int ++ * @see fr_int2str ++ */ ++const FR_NAME_NUMBER http_negotiation_table[] = { ++ ++ { "1.0", CURL_HTTP_VERSION_1_0 }, //!< Enforce HTTP 1.0 requests. ++ { "1.1", CURL_HTTP_VERSION_1_1 }, //!< Enforce HTTP 1.1 requests. ++/* ++ * These are all enum values ++ */ ++#if CURL_AT_LEAST_VERSION(7,49,0) ++ { "2.0", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, //!< Enforce HTTP 2.0 requests. ++#endif ++#if CURL_AT_LEAST_VERSION(7,33,0) ++ { "2.0+auto", CURL_HTTP_VERSION_2_0 }, //!< Attempt HTTP 2 requests. libcurl will fall back ++ ///< to HTTP 1.1 if HTTP 2 can't be negotiated with the ++ ///< server. (Added in 7.33.0) ++#endif ++#if CURL_AT_LEAST_VERSION(7,47,0) ++ { "2.0+tls", CURL_HTTP_VERSION_2TLS }, //!< Attempt HTTP 2 over TLS (HTTPS) only. ++ ///< libcurl will fall back to HTTP 1.1 if HTTP 2 ++ ///< can't be negotiated with the HTTPS server. ++ ///< For clear text HTTP servers, libcurl will use 1.1. ++#endif ++ { "default", CURL_HTTP_VERSION_NONE } //!< We don't care about what version the library uses. ++ ///< libcurl will use whatever it thinks fit. ++}; ++ + /* + * Encoder specific structures. + * @todo split encoders/decoders into submodules. +@@ -603,19 +646,30 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd + } + + /* +- * there are more attributes, insert a separator ++ * there are no more attributes, stop + */ +- if (fr_cursor_next(&ctx->cursor)) { +- if (freespace < 1) goto no_space; +- *p++ = '&'; +- freespace--; ++ if (!fr_cursor_next_peek(&ctx->cursor)) { ++ ctx->state = READ_STATE_END; ++ break; + } + ++ if (freespace < 1) goto no_space; ++ *p++ = '&'; ++ freespace--; ++ /* ++ * Only advance once we have a separator ++ * really we should have an additional ++ * state for encoding the separator, ++ * but, we don't, and v3.0.x is stable ++ * so let's do the easiest fix with the ++ * lowest risk. ++ */ ++ fr_cursor_next(&ctx->cursor); ++ + /* + * We wrote one full attribute value pair, record progress. + */ + encoded = p; +- + ctx->state = READ_STATE_ATTR_BEGIN; + } + +@@ -747,7 +801,13 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd + + type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"); + +- len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); ++ if (ctx->section->attr_num) { ++ len = snprintf(p, freespace + 1, "\"%s\":{\"attr_num\":%d,\"type\":\"%s\",\"value\":[", ++ vp->da->name, vp->da->attr, type); ++ } else { ++ len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); ++ } ++ + if (len >= freespace) goto no_space; + p += len; + freespace -= len; +@@ -783,7 +843,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd + * write that out. + */ + attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace; +- len = vp_prints_value_json(p, attr_space + 1, vp); ++ len = vp_prints_value_json(p, attr_space + 1, vp, ctx->section->raw_value); + if (is_truncated(len, attr_space + 1)) goto no_space; + + /* +@@ -1575,8 +1635,9 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us + * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n + * + * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14 ++ * "HTTP/2 " (8) + "100 " (4) + "\r\n" (2) = 12 + */ +- if (s < 14) { ++ if (s < 12) { + REDEBUG("Malformed HTTP header: Status line too short"); + goto malformed; + } +@@ -1614,8 +1675,10 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us + p++; + s--; + +- /* Char after reason code must be a space, or \r */ +- if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed; ++ /* ++ * "xxx( |\r)" status code and terminator. ++ */ ++ if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) goto malformed; + + ctx->code = atoi(p); + +@@ -1996,11 +2059,24 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section, + SET_OPTION(CURLOPT_NOSIGNAL, 1); + SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING); + ++ /* ++ * As described in https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html, ++ * The libcurl decides which http version should be ++ * used by default accoring by library version. ++ */ ++ if (instance->http_negotiation != CURL_HTTP_VERSION_NONE) { ++ RDEBUG3("Set HTTP negotiation for %s", instance->http_negotiation_str); ++ SET_OPTION(CURLOPT_HTTP_VERSION, instance->http_negotiation); ++ } ++ + content_type = fr_int2str(http_content_type_table, type, section->body_str); + snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type); + ctx->headers = curl_slist_append(ctx->headers, buffer); + if (!ctx->headers) goto error_header; + ++ // Pass configuration to the request ++ ctx->request.section = section; ++ + SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout); + SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout); + +@@ -2322,6 +2398,7 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t + rlm_rest_handle_t *randle = handle; + CURL *candle = randle->handle; + CURLcode ret; ++ VALUE_PAIR *vp; + + ret = curl_easy_perform(candle); + if (ret != CURLE_OK) { +@@ -2330,6 +2407,14 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t + return -1; + } + ++ /* ++ * Save the HTTP return status code. ++ */ ++ vp = pair_make_reply("REST-HTTP-Status-Code", NULL, T_OP_SET); ++ vp->vp_integer = rest_get_handle_code(handle); ++ ++ RDEBUG2("Adding reply:REST-HTTP-Status-Code = \"%d\"", vp->vp_integer); ++ + return 0; + } + +diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h +index 9e278d16c4..4c41cf46c2 100644 +--- a/src/modules/rlm_rest/rest.h ++++ b/src/modules/rlm_rest/rest.h +@@ -31,6 +31,10 @@ RCSIDH(other_h, "$Id$") + #define CURL_NO_OLDIES 1 + #include <curl/curl.h> + ++#ifdef HAVE_WDOCUMENTATION ++DIAG_OFF(documentation) ++#endif ++ + #ifdef HAVE_JSON + # if defined(HAVE_JSONMC_JSON_H) + # include <json-c/json.h> +@@ -39,6 +43,10 @@ RCSIDH(other_h, "$Id$") + # endif + #endif + ++#ifdef HAVE_WDOCUMENTATION ++DIAG_ON(documentation) ++#endif ++ + #define REST_URI_MAX_LEN 2048 + #define REST_BODY_MAX_LEN 8192 + #define REST_BODY_INIT 1024 +@@ -102,6 +110,8 @@ extern const FR_NAME_NUMBER http_body_type_table[]; + + extern const FR_NAME_NUMBER http_content_type_table[]; + ++extern const FR_NAME_NUMBER http_negotiation_table[]; ++ + /* + * Structure for section configuration + */ +@@ -115,6 +125,9 @@ typedef struct rlm_rest_section_t { + char const *body_str; //!< The string version of the encoding/content type. + http_body_type_t body; //!< What encoding type should be used. + ++ bool attr_num; //!< If true, the the attribute number is supplied for each attribute. ++ bool raw_value; //!< If true, enumerated attributes are provided as a numeric value ++ + char const *force_to_str; //!< Force decoding with this decoder. + http_body_type_t force_to; //!< Override the Content-Type header in the response + //!< to force decoding as a particular type. +@@ -154,6 +167,9 @@ typedef struct rlm_rest_t { + struct timeval connect_timeout_tv; //!< Connection timeout timeval. + long connect_timeout; //!< Connection timeout ms. + ++ char const *http_negotiation_str; //!< The string version of the http_negotiation ++ long http_negotiation; //!< The HTTP protocol version to use ++ + fr_connection_pool_t *pool; //!< Pointer to the connection pool. + + rlm_rest_section_t authorize; //!< Configuration specific to authorisation. +@@ -205,6 +221,8 @@ typedef struct rlm_rest_request_t { + + size_t chunk; //!< Chunk size + ++ rlm_rest_section_t *section; //!< Configuration data ++ + void *encoder; //!< Encoder specific data. + } rlm_rest_request_t; + +diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c +index 6fa1345614..1337ae5425 100644 +--- a/src/modules/rlm_rest/rlm_rest.c ++++ b/src/modules/rlm_rest/rlm_rest.c +@@ -60,6 +60,8 @@ static const CONF_PARSER section_config[] = { + { "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), "" }, + { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" }, + { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" }, ++ { "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" }, ++ { "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" }, + { "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL }, + { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL }, + +@@ -81,6 +83,8 @@ static const CONF_PARSER section_config[] = { + static const CONF_PARSER module_config[] = { + { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL }, + { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" }, ++ { "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" }, ++ + CONF_PARSER_TERMINATOR + }; + +@@ -223,6 +227,8 @@ static ssize_t rest_xlat(void *instance, REQUEST *request, + .name = "xlat", + .method = HTTP_METHOD_GET, + .body = HTTP_BODY_NONE, ++ .attr_num = false, ++ .raw_value = false, + .body_str = "application/x-www-form-urlencoded", + .require_auth = false, + .force_to = HTTP_BODY_PLAIN +@@ -926,6 +932,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) + return -1; + } + ++ inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1); ++ if (inst->http_negotiation == -1) { ++ cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str); ++ return -1; ++ } ++ + /* + * Initialise REST libraries. + */ +diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c +new file mode 100644 +index 0000000000..e14086e78c +--- /dev/null ++++ b/src/modules/rlm_wimax/milenage.c +@@ -0,0 +1,642 @@ ++/** ++ * @file src/modules/rlm_wimax/milenage.c ++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) ++ * ++ * This file implements an example authentication algorithm defined for 3GPP ++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow ++ * EAP-AKA to be tested properly with real USIM cards. ++ * ++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in ++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, ++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to ++ * be AES (Rijndael). ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * @copyright 2017 The FreeRADIUS server project ++ * @copyright 2006-2007 (j@w1.fi) ++ */ ++#include <stddef.h> ++#include <string.h> ++ ++#include <freeradius-devel/radiusd.h> ++#include <freeradius-devel/modules.h> ++#include <openssl/evp.h> ++#include "milenage.h" ++ ++#define MILENAGE_MAC_A_SIZE 8 ++#define MILENAGE_MAC_S_SIZE 8 ++ ++static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx, ++ uint8_t const key[16], uint8_t const in[16], uint8_t out[16]) ++{ ++ size_t len; ++ ++ if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { ++ fr_strerror_printf("Failed initialising AES-128-ECB context"); ++ return -1; ++ } ++ ++ /* ++ * By default OpenSSL will try and pad out a 16 byte ++ * plaintext to 32 bytes so that it's detectable that ++ * there was padding. ++ * ++ * In this case we know the length of the plaintext ++ * we're trying to recover, so we explicitly tell ++ * OpenSSL not to pad here, and not to expected padding ++ * when decrypting. ++ */ ++ EVP_CIPHER_CTX_set_padding(evp_ctx, 0); ++ if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) || ++ unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) { ++ fr_strerror_printf("Failed encrypting data"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/** milenage_f1 - Milenage f1 and f1* algorithms ++ * ++ * @param[in] opc 128-bit value derived from OP and K. ++ * @param[in] k 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] sqn 48-bit sequence number. ++ * @param[in] amf 16-bit authentication management field. ++ * @param[out] mac_a Buffer for MAC-A = 64-bit network authentication code, or NULL ++ * @param[out] mac_s Buffer for MAC-S = 64-bit resync authentication code, or NULL ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE], ++ uint8_t mac_s[MILENAGE_MAC_S_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const k[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const sqn[MILENAGE_SQN_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE]) ++{ ++ uint8_t tmp1[16], tmp2[16], tmp3[16]; ++ int i; ++ EVP_CIPHER_CTX *evp_ctx; ++ ++ /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ ++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ //tls_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) { ++ error: ++ EVP_CIPHER_CTX_free(evp_ctx); ++ return -1; ++ } ++ ++ /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ ++ memcpy(tmp2, sqn, 6); ++ memcpy(tmp2 + 6, amf, 2); ++ memcpy(tmp2 + 8, tmp2, 8); ++ ++ /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ ++ ++ /* ++ * rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) ++ */ ++ for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; ++ ++ /* ++ * XOR with TEMP = E_K(RAND XOR OP_C) ++ */ ++ for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i]; ++ /* XOR with c1 (= ..00, i.e., NOP) */ ++ ++ /* ++ * f1 || f1* = E_K(tmp3) XOR OP_c ++ */ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */ ++ ++ for (i = 0; i < 16; i++) tmp1[i] ^= opc[i]; ++ ++ if (mac_a) memcpy(mac_a, tmp1, 8); /* f1 */ ++ if (mac_s) memcpy(mac_s, tmp1 + 8, 8); /* f1* */ ++ ++ EVP_CIPHER_CTX_free(evp_ctx); ++ ++ return 0; ++} ++ ++/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms ++ * ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL ++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL ++ * @param[out] ak_resync Buffer for AK = 48-bit anonymity key (f5*), or NULL ++ * @param[in] opc 128-bit value derived from OP and K. ++ * @param[in] k 128-bit subscriber key ++ * @param[in] rand 128-bit random challenge ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t ak_resync[MILENAGE_AK_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const k[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t tmp1[16], tmp2[16], tmp3[16]; ++ int i; ++ EVP_CIPHER_CTX *evp_ctx; ++ ++ /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ ++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ fr_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) { ++ error: ++ EVP_CIPHER_CTX_free(evp_ctx); ++ return -1; ++ } ++ ++ /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ ++ /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ ++ /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ ++ /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ ++ ++ /* f2 and f5 */ ++ /* rotate by r2 (= 0, i.e., NOP) */ ++ for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 1; /* XOR c2 (= ..01) */ ++ /* f5 || f2 = E_K(tmp1) XOR OP_c */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) tmp3[i] ^= opc[i]; ++ if (res) memcpy(res, tmp3 + 8, 8); /* f2 */ ++ if (ak) memcpy(ak, tmp3, 6); /* f5 */ ++ ++ /* f3 */ ++ if (ck) { ++ /* rotate by r3 = 0x20 = 4 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 2; /* XOR c3 (= ..02) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) ck[i] ^= opc[i]; ++ } ++ ++ /* f4 */ ++ if (ik) { ++ /* rotate by r4 = 0x40 = 8 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 4; /* XOR c4 (= ..04) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) ik[i] ^= opc[i]; ++ } ++ ++ /* f5* */ ++ if (ak_resync) { ++ /* rotate by r5 = 0x60 = 12 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 8; /* XOR c5 (= ..08) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error; ++ ++ for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i]; ++ } ++ EVP_CIPHER_CTX_free(evp_ctx); ++ ++ return 0; ++} ++ ++/** Derive OPc from OP and Ki ++ * ++ * @param[out] opc The derived Operator Code used as an input to other Milenage ++ * functions. ++ * @param[in] op Operator Code. ++ * @param[in] ki Subscriber key. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], ++ uint8_t const op[MILENAGE_OP_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE]) ++{ ++ int ret; ++ uint8_t tmp[MILENAGE_OPC_SIZE]; ++ EVP_CIPHER_CTX *evp_ctx; ++ size_t i; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ fr_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp); ++ EVP_CIPHER_CTX_free(evp_ctx); ++ if (ret < 0) return ret; ++ ++ for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i]; ++ ++ return 0; ++} ++ ++/** Generate AKA AUTN, IK, CK, RES ++ * ++ * @param[out] autn Buffer for AUTN = 128-bit authentication token. ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. ++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] amf 16-bit authentication management field. ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] sqn 48-bit sequence number (host byte order). ++ * @param[in] rand 128-bit random challenge. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t mac_a[8], ak_buff[MILENAGE_AK_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ uint8_t *p = autn; ++ size_t i; ++ ++ if ((milenage_f1(mac_a, NULL, opc, ki, rand, ++ uint48_to_buff(sqn_buff, sqn), amf) < 0) || ++ (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1; ++ ++ /* ++ * AUTN = (SQN ^ AK) || AMF || MAC_A ++ */ ++ for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i]; ++ memcpy(p, amf, MILENAGE_AMF_SIZE); ++ p += MILENAGE_AMF_SIZE; ++ memcpy(p, mac_a, sizeof(mac_a)); ++ ++ /* ++ * Output the anonymity key if required ++ */ ++ if (ak) memcpy(ak, ak_buff, sizeof(ak_buff)); ++ ++ return 0; ++} ++ ++/** Milenage AUTS validation ++ * ++ * @param[out] sqn SQN = 48-bit sequence number (host byte order). ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] auts 112-bit authentication token from client. ++ * @return ++ * - 0 on success with sqn filled. ++ * - -1 on failure. ++ */ ++int milenage_auts(uint64_t *sqn, ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const auts[MILENAGE_AUTS_SIZE]) ++{ ++ uint8_t amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ ++ uint8_t ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ size_t i; ++ ++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; ++ for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i]; ++ ++ if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1; ++ ++ *sqn = uint48_from_buff(sqn_buff); ++ ++ return 0; ++} ++ ++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet ++ * ++ * @param[out] sres Buffer for SRES = 32-bit SRES. ++ * @param[out] kc 64-bit Kc. ++ * @param[in] ik 128-bit integrity. ++ * @param[in] ck Confidentiality key. ++ * @param[in] res 64-bit signed response. ++ */ ++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const ik[MILENAGE_IK_SIZE], ++ uint8_t const ck[MILENAGE_CK_SIZE], ++ uint8_t const res[MILENAGE_RES_SIZE]) ++{ ++ int i; ++ ++ for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; ++ ++#ifdef GSM_MILENAGE_ALT_SRES ++ memcpy(sres, res, 4); ++#else /* GSM_MILENAGE_ALT_SRES */ ++ for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4]; ++#endif /* GSM_MILENAGE_ALT_SRES */ ++} ++ ++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet ++ * ++ * @param[out] sres Buffer for SRES = 32-bit SRES. ++ * @param[out] kc 64-bit Kc. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE]; ++ ++ if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1; ++ ++ milenage_gsm_from_umts(sres, kc, ik, ck, res); ++ ++ return 0; ++} ++ ++/** Milenage check ++ * ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. ++ * @param[in] auts 112-bit buffer for AUTS. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] sqn 48-bit sequence number. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] autn 128-bit authentication token. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ * - -2 on synchronization failure ++ */ ++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t auts[MILENAGE_AUTS_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const autn[MILENAGE_AUTN_SIZE]) ++{ ++ ++ uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ const uint8_t *amf; ++ size_t i; ++ ++ uint48_to_buff(sqn_buff, sqn); ++ ++ //FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN"); ++ //FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND"); ++ ++ if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1; ++ ++ //FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES"); ++ //FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK"); ++ //FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK"); ++ //FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK"); ++ ++ /* AUTN = (SQN ^ AK) || AMF || MAC */ ++ for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i]; ++ //FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN"); ++ ++ if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) { ++ uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ ++ ++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; ++ ++ //FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*"); ++ for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i]; ++ ++ if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1; ++ //FR_PROTO_HEX_DUMP(auts, 14, "AUTS"); ++ return -2; ++ } ++ ++ amf = autn + 6; ++ //FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF"); ++ if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A"); ++ ++ if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) { ++ //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A"); ++ fr_strerror_printf("MAC mismatch"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++#ifdef TESTING_MILENAGE ++/* ++ * cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage ++ */ ++#include <freeradius-devel/util/acutest.h> ++ ++void test_set_1(void) ++{ ++ /* ++ * Inputs ++ */ ++ uint8_t ki[] = { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f, ++ 0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc }; ++ uint8_t rand[] = { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d, ++ 0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35 }; ++ uint8_t sqn[] = { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 }; ++ uint8_t amf[] = { 0xb9, 0xb9 }; ++ uint8_t op[] = { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6, ++ 0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 }; ++ uint8_t opc[] = { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e, ++ 0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf }; ++ ++ /* ++ * Outputs ++ */ ++ uint8_t opc_out[MILENAGE_OPC_SIZE]; ++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; ++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; ++ uint8_t res_out[MILENAGE_RES_SIZE]; ++ uint8_t ck_out[MILENAGE_CK_SIZE]; ++ uint8_t ik_out[MILENAGE_IK_SIZE]; ++ uint8_t ak_out[MILENAGE_AK_SIZE]; ++ uint8_t ak_resync_out[MILENAGE_AK_SIZE]; ++ ++ /* function 1 */ ++ uint8_t mac_a[] = { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 }; ++ /* function 1* */ ++ uint8_t mac_s[] = { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 }; ++ /* function 2 */ ++ uint8_t res[] = { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf }; ++ /* function 3 */ ++ uint8_t ck[] = { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05, ++ 0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb }; ++ /* function 4 */ ++ uint8_t ik[] = { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04, ++ 0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 }; ++ /* function 5 */ ++ uint8_t ak[] = { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 }; ++ /* function 5* */ ++ uint8_t ak_resync[] = { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b }; ++ ++ int ret = 0; ++ ++/* ++ fr_debug_lvl = 4; ++*/ ++ ret = milenage_opc_generate(opc_out, op, ki); ++ TEST_CHECK(ret == 0); ++ ++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); ++ ++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); ++ ++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || ++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); ++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); ++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); ++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); ++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); ++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); ++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); ++ ++ TEST_CHECK(ret == 0); ++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); ++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); ++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); ++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); ++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); ++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); ++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); ++} ++ ++void test_set_19(void) ++{ ++ /* ++ * Inputs ++ */ ++ uint8_t ki[] = { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72, ++ 0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 }; ++ uint8_t rand[] = { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e, ++ 0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 }; ++ uint8_t sqn[] = { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 }; ++ uint8_t amf[] = { 0xc3, 0xab }; ++ uint8_t op[] = { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff, ++ 0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b }; ++ uint8_t opc[] = { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e, ++ 0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf }; ++ ++ /* ++ * Outputs ++ */ ++ uint8_t opc_out[MILENAGE_OPC_SIZE]; ++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; ++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; ++ uint8_t res_out[MILENAGE_RES_SIZE]; ++ uint8_t ck_out[MILENAGE_CK_SIZE]; ++ uint8_t ik_out[MILENAGE_IK_SIZE]; ++ uint8_t ak_out[MILENAGE_AK_SIZE]; ++ uint8_t ak_resync_out[MILENAGE_AK_SIZE]; ++ ++ /* function 1 */ ++ uint8_t mac_a[] = { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 }; ++ /* function 1* */ ++ uint8_t mac_s[] = { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 }; ++ /* function 2 */ ++ uint8_t res[] = { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 }; ++ /* function 3 */ ++ uint8_t ck[] = { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94, ++ 0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f }; ++ /* function 4 */ ++ uint8_t ik[] = { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb, ++ 0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a }; ++ /* function 5 */ ++ uint8_t ak[] = { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 }; ++ /* function 5* */ ++ uint8_t ak_resync[] = { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d }; ++ ++ int ret = 0; ++ ++/* ++ fr_debug_lvl = 4; ++*/ ++ ++ ret = milenage_opc_generate(opc_out, op, ki); ++ TEST_CHECK(ret == 0); ++ ++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); ++ ++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); ++ ++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || ++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); ++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); ++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); ++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); ++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); ++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); ++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); ++ ++ TEST_CHECK(ret == 0); ++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); ++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); ++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); ++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); ++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); ++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); ++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); ++} ++ ++TEST_LIST = { ++ { "test_set_1", test_set_1 }, ++ { "test_set_19", test_set_19 }, ++ { NULL } ++}; ++#endif +diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h +new file mode 100644 +index 0000000000..758383aba2 +--- /dev/null ++++ b/src/modules/rlm_wimax/milenage.h +@@ -0,0 +1,128 @@ ++#pragma once ++/** ++ * @file src/modules/rlm_wimax/milenage.h ++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) ++ * ++ * This file implements an example authentication algorithm defined for 3GPP ++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow ++ * EAP-AKA to be tested properly with real USIM cards. ++ * ++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in ++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, ++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to ++ * be AES (Rijndael). ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * @copyright 2017 The FreeRADIUS server project ++ * @copyright 2006-2007 (j@w1.fi) ++ */ ++#include <stddef.h> ++ ++/* ++ * Inputs ++ */ ++#define MILENAGE_KI_SIZE 16 //!< Subscriber key. ++#define MILENAGE_OP_SIZE 16 //!< Operator code (unique to the operator) ++#define MILENAGE_OPC_SIZE 16 //!< Derived operator code (unique to the operator and subscriber). ++#define MILENAGE_AMF_SIZE 2 //!< Authentication management field. ++#define MILENAGE_SQN_SIZE 6 //!< Sequence number. ++#define MILENAGE_RAND_SIZE 16 //!< Random challenge. ++ ++/* ++ * UMTS Outputs ++ */ ++#define MILENAGE_AK_SIZE 6 //!< Anonymisation key. ++#define MILENAGE_AUTN_SIZE 16 //!< Network authentication key. ++#define MILENAGE_IK_SIZE 16 //!< Integrity key. ++#define MILENAGE_CK_SIZE 16 //!< Ciphering key. ++#define MILENAGE_RES_SIZE 8 ++#define MILENAGE_AUTS_SIZE 14 ++ ++/* ++ * GSM (COMP128-4) outputs ++ */ ++#define MILENAGE_SRES_SIZE 4 ++#define MILENAGE_KC_SIZE 8 ++ ++/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order ++ * ++ * There may be fast ways of doing this, but this is the *correct* ++ * way, and does not make assumptions about how integers are laid ++ * out in memory. ++ * ++ * @param[out] out 6 byte butter to store value. ++ * @param[in] i integer value. ++ * @return pointer to out. ++ */ ++static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i) ++{ ++ out[0] = (i & 0xff0000000000) >> 40; ++ out[1] = (i & 0x00ff00000000) >> 32; ++ out[2] = (i & 0x0000ff000000) >> 24; ++ out[3] = (i & 0x000000ff0000) >> 16; ++ out[4] = (i & 0x00000000ff00) >> 8; ++ out[5] = (i & 0x0000000000ff); ++ ++ return out; ++} ++ ++/** Convert a 48bit big endian value into a unsigned 64bit integer ++ * ++ */ ++static inline uint64_t uint48_from_buff(uint8_t const in[6]) ++{ ++ uint64_t i = 0; ++ ++ i |= ((uint64_t)in[0]) << 40; ++ i |= ((uint64_t)in[1]) << 32; ++ i |= ((uint32_t)in[2]) << 24; ++ i |= ((uint32_t)in[3]) << 16; ++ i |= ((uint16_t)in[4]) << 8; ++ i |= in[5]; ++ ++ return i; ++} ++ ++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], ++ uint8_t const op[MILENAGE_OP_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE]); ++ ++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE]); ++ ++int milenage_auts(uint64_t *sqn, ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const auts[MILENAGE_AUTS_SIZE]); ++ ++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const ik[MILENAGE_IK_SIZE], ++ uint8_t const ck[MILENAGE_CK_SIZE], ++ uint8_t const res[MILENAGE_RES_SIZE]); ++ ++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]); ++ ++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t auts[MILENAGE_AUTS_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const autn[MILENAGE_AUTN_SIZE]); +diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c +index f0fb394fcd..d2125eb3a5 100644 +--- a/src/modules/rlm_wimax/rlm_wimax.c ++++ b/src/modules/rlm_wimax/rlm_wimax.c +@@ -26,16 +26,43 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include <freeradius-devel/radiusd.h> + #include <freeradius-devel/modules.h> ++#include "milenage.h" + + #ifdef HAVE_OPENSSL_HMAC_H + #include <openssl/hmac.h> + #endif + ++#include <freeradius-devel/openssl3.h> ++ ++#define WIMAX_EPSAKA_RAND_SIZE 16 ++#define WIMAX_EPSAKA_KI_SIZE 16 ++#define WIMAX_EPSAKA_OPC_SIZE 16 ++#define WIMAX_EPSAKA_AMF_SIZE 2 ++#define WIMAX_EPSAKA_SQN_SIZE 6 ++#define WIMAX_EPSAKA_MAC_A_SIZE 8 ++#define WIMAX_EPSAKA_MAC_S_SIZE 8 ++#define WIMAX_EPSAKA_XRES_SIZE 8 ++#define WIMAX_EPSAKA_CK_SIZE 16 ++#define WIMAX_EPSAKA_IK_SIZE 16 ++#define WIMAX_EPSAKA_AK_SIZE 6 ++#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 ++#define WIMAX_EPSAKA_KK_SIZE 32 ++#define WIMAX_EPSAKA_KS_SIZE 14 ++#define WIMAX_EPSAKA_PLMN_SIZE 3 ++#define WIMAX_EPSAKA_KASME_SIZE 32 ++#define WIMAX_EPSAKA_AUTN_SIZE 16 ++#define WIMAX_EPSAKA_AUTS_SIZE 14 ++ + /* + * FIXME: Fix the build system to create definitions from names. + */ + typedef struct rlm_wimax_t { + bool delete_mppe_keys; ++ ++ DICT_ATTR const *resync_info; ++ DICT_ATTR const *xres; ++ DICT_ATTR const *autn; ++ DICT_ATTR const *kasme; + } rlm_wimax_t; + + /* +@@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = { + CONF_PARSER_TERMINATOR + }; + ++/* ++ * Print hex values in a readable format for debugging ++ * Example: ++ * FOO: 00 11 AA 22 00 FF ++ */ ++static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) ++{ ++ int i; ++ char buffer[256]; /* large enough for largest len */ ++ ++ /* ++ * Leave a trailing space, we don't really care about that. ++ */ ++ for (i = 0; i < len; i++) { ++ snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]); ++ } ++ ++ RDEBUG("%s %s", prefix, buffer); ++} ++#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex ++ + /* + * Find the named user in this modules database. Create the set + * of attribute-value pairs to check and reply with for this user + * from the database. The authentication code only needs to check + * the password, the rest is done here. + */ +-static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request) ++static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) + { + VALUE_PAIR *vp; ++ rlm_wimax_t *inst = instance; + + /* + * Fix Calling-Station-Id. Damn you, WiMAX! +@@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST + return RLM_MODULE_OK; + } + ++ /* ++ * Check for attr WiMAX-Re-synchronization-Info ++ * which contains the concatenation of RAND and AUTS ++ * ++ * If it is present then we proceed to verify the SIM and ++ * extract the new value of SQN ++ */ ++ VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand; ++ int m_ret; ++ ++ /* Look for the Re-synchronization-Info attribute in the request */ ++ resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY); ++ if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) { ++ RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it"); ++ resync_info = NULL; ++ } ++ ++ /* ++ * These are the private keys which should be added to the control ++ * list after looking them up in a database by IMSI ++ * ++ * We grab them from the control list here ++ */ ++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); ++ if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it"); ++ ki = NULL; ++ } ++ ++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); ++ if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it"); ++ opc = NULL; ++ } ++ ++ /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */ ++ if (resync_info && ki && opc) { ++ uint64_t sqn_bin; ++ uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE]; ++ uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE]; ++ ++ RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync"); ++ ++ /* Split Re-synchronization-Info into seperate RAND and AUTS */ ++ ++ memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE); ++ memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE); ++ ++ RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE); ++ RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE); ++ ++ /* ++ * This procedure uses the secret keys Ki and OPc to authenticate ++ * the SIM and extract the SQN ++ */ ++ m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin); ++ ++ /* ++ * If the SIM verification fails then we can't go any further as ++ * we don't have the keys. And that probably means something bad ++ * is happening so we bail out now ++ */ ++ if (m_ret < 0) { ++ RDEBUG("SIM verification failed"); ++ return RLM_MODULE_REJECT; ++ } ++ ++ /* ++ * If we got this far it means have got a new SQN and RAND ++ * so we store them in: ++ * control:WiMAX-SIM-SQN ++ * control:WiMAX-SIM-RAND ++ * ++ * From there they can be grabbed by unlang and used later ++ */ ++ ++ /* SQN is six bytes so we extract what we need from the 64 bit variable */ ++ uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = { ++ (sqn_bin & 0x0000FF0000000000ull) >> 40, ++ (sqn_bin & 0x000000FF00000000ull) >> 32, ++ (sqn_bin & 0x00000000FF000000ull) >> 24, ++ (sqn_bin & 0x0000000000FF0000ull) >> 16, ++ (sqn_bin & 0x000000000000FF00ull) >> 8, ++ (sqn_bin & 0x00000000000000FFull) >> 0 ++ }; ++ ++ /* Add SQN to control:WiMAX-SIM-SQN */ ++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); ++ if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it"); ++ sqn = NULL; ++ } ++ ++ if (!sqn) { ++ MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE); ++ } ++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE); ++ ++ /* Add RAND to control:WiMAX-SIM-RAND */ ++ rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); ++ if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it"); ++ rand = NULL; ++ } ++ ++ if (!rand) { ++ MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE); ++ } ++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE); ++ ++ return RLM_MODULE_UPDATED; ++ } ++ + return RLM_MODULE_NOOP; + } + +- + /* + * Massage the request before recording it or proxying it + */ +@@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request + return mod_authorize(instance, request); + } + +-/* +- * Write accounting information to this modules database. +- */ +-static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request) +-{ +- return RLM_MODULE_OK; +-} + + /* +- * Generate the keys after the user has been authenticated. ++ * This function generates the keys for old style WiMAX (v1 to v2.0) + */ +-static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) ++static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk) + { + rlm_wimax_t *inst = instance; +- VALUE_PAIR *msk, *emsk, *vp; ++ VALUE_PAIR *vp; + VALUE_PAIR *mn_nai, *ip, *fa_rk; + HMAC_CTX *hmac; + unsigned int rk1_len, rk2_len, rk_len; +@@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; + uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; + +- msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); +- emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); +- if (!msk || !emsk) { +- RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys"); +- return RLM_MODULE_NOOP; +- } +- + /* + * If we delete the MS-MPPE-*-Key attributes, then add in + * the WiMAX-MSK so that the client has a key available. +@@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); + +- vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ); +- if (vp) { +- fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); +- } ++ MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ)); ++ fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); + } + + /* +@@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + */ + hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); ++ rk1_len = SHA256_DIGEST_LENGTH; + + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); +@@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + + HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len); + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); ++ rk2_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_2[0], &rk2_len); + + memcpy(mip_rk, mip_rk_1, rk1_len); +@@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12); ++ rk1_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + (mip_rk_1[2] << 8) | mip_rk_1[3]); + if (mip_spi < 256) mip_spi += 256; + +- if (rad_debug_lvl) { +- int len = rk_len; +- char buffer[512]; +- +- if (len > 128) len = 128; /* buffer size */ +- +- fr_bin2hex(buffer, mip_rk, len); +- RDEBUG("MIP-RK = 0x%s", buffer); +- RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); +- } ++ RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len); ++ RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); + + /* + * FIXME: Perform SPI collision prevention +@@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + + HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5); + ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len); +@@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + return RLM_MODULE_UPDATED; + } + ++/* ++ * Generate the EPS-AKA authentication vector ++ * ++ * These are the keys needed for new style WiMAX (LTE / 3gpp authentication), ++ for WiMAX v2.1 ++ */ ++static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc, ++ VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn) ++{ ++ size_t i; ++ VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme; ++ ++ /* ++ * For most authentication requests we need to generate a fresh RAND ++ * ++ * The exception is after SQN re-syncronisation - in this case we ++ * get RAND in the request, and this module if called in authorize should ++ * have put it in control:WiMAX-SIM-RAND so we can grab it from there) ++ */ ++ rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); ++ if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it."); ++ rand_previous = NULL; ++ } ++ ++ MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET)); ++ if (!rand_previous) { ++ uint32_t lvalue; ++ uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE]; ++ ++ for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) { ++ lvalue = fr_rand(); ++ memcpy(buffer + i * 4, &lvalue, sizeof(lvalue)); ++ } ++ ++ fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE); ++ ++ } else { ++ fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE); ++ } ++ ++ /* ++ * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5) ++ * which returns AUTN, AK, CK, IK, XRES. ++ */ ++ uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE]; ++ uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE]; ++ uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE]; ++ uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE]; ++ uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE]; ++ ++ /* But first convert uint8 SQN to uint64 */ ++ uint64_t sqn_bin = 0x0000000000000000; ++ for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i]; ++ ++ if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ ++ /* Call milenage */ ++ milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets, ++ amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets); ++ ++ /* ++ * Now we genertate KASME ++ * ++ * Officially described in 33401-g30.doc section A.2 ++ * But an easier to read explanation can be found at: ++ * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec ++ * ++ */ ++ ++ /* k = CK || IK */ ++ uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE]; ++ memcpy(kk_bin, ck_bin, sizeof(ck_bin)); ++ memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin)); ++ ++ /* Initialize a 14 byte buffer s */ ++ uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE]; ++ ++ /* Assign the first byte of s as 0x10 */ ++ ks_bin[0] = 0x10; ++ ++ /* Copy the 3 bytes of PLMN into s */ ++ memcpy(ks_bin + 1, plmn->vp_octets, 3); ++ ++ /* Assign 5th and 6th byte as 0x00 and 0x03 */ ++ ks_bin[4] = 0x00; ++ ks_bin[5] = 0x03; ++ ++ /* Assign the next 6 bytes as SQN XOR AK */ ++ for (i = 0; i < 6; i++) { ++ ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i]; ++ } ++ ++ /* Assign the last two bytes as 0x00 and 0x06 */ ++ ks_bin[12] = 0x00; ++ ks_bin[13] = 0x06; ++ ++ /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */ ++ uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE]; ++ HMAC_CTX *hmac; ++ unsigned int kasme_len = sizeof(kasme_bin); ++ ++ hmac = HMAC_CTX_new(); ++ HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL); ++ HMAC_Update(hmac, ks_bin, sizeof(ks_bin)); ++ kasme_len = SHA256_DIGEST_LENGTH; ++ HMAC_Final(hmac, &kasme_bin[0], &kasme_len); ++ HMAC_CTX_free(hmac); ++ ++ /* ++ * Add reply attributes XRES, AUTN and KASME (RAND we added earlier) ++ * ++ * Note that we can't call fr_pair_find_by_num(), as ++ * these attributes are buried deep inside of the WiMAX ++ * hierarchy. ++ */ ++ xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY); ++ if (!xres) { ++ MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE); ++ } ++ ++ autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY); ++ if (!autn) { ++ MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE); ++ } ++ ++ kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY); ++ if (!kasme) { ++ MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE); ++ } ++ ++ /* Print keys to log for debugging */ ++ if (rad_debug_lvl) { ++ RDEBUG("-------- Milenage in --------"); ++ RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length); ++ RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length); ++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length); ++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length); ++ RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length); ++ RDEBUG("-------- Milenage out -------"); ++ RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length); ++ RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin)); ++ RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin)); ++ RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin)); ++ RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length); ++ RDEBUG("-----------------------------"); ++ RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin)); ++ RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin)); ++ RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length); ++ } ++ ++ return RLM_MODULE_UPDATED; ++} ++ ++/* ++ * Generate the keys after the user has been authenticated. ++ */ ++static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) ++{ ++ VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn; ++ ++ /* ++ * If we have MSK and EMSK then assume we want MIP keys ++ * Else if we have the SIM keys then we want the EPS-AKA vector ++ */ ++ ++ msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); ++ emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); ++ ++ if (msk && emsk) { ++ RDEBUG("MSK and EMSK found. Generating MIP keys"); ++ return mip_keys_generate(instance, request, msk, emsk); ++ } ++ ++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); ++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); ++ amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY); ++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); ++ plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY); ++ ++ if (ki && opc && amf && sqn && plmn) { ++ RDEBUG("AKA attributes found. Generating AKA keys."); ++ return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn); ++ } ++ ++ RDEBUG("Input keys not found. Cannot create WiMAX keys"); ++ return RLM_MODULE_NOOP; ++} ++ ++ ++static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) ++{ ++ rlm_wimax_t *inst = instance; ++ ++ inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info"); ++ inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES"); ++ inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN"); ++ inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME"); ++ ++ return 0; ++} + + /* + * The module name should be the only globally exported symbol. +@@ -472,10 +833,10 @@ module_t rlm_wimax = { + .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_wimax_t), + .config = module_config, ++ .instantiate = mod_instantiate, + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preacct, +- [MOD_ACCOUNTING] = mod_accounting, + [MOD_POST_AUTH] = mod_post_auth + }, + }; +diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4 +new file mode 100644 +index 0000000000..7e9b1ffcdf +--- /dev/null ++++ b/src/tests/keywords/md4 +@@ -0,0 +1,58 @@ ++# ++# PRE: update if ++# ++update reply { ++ Filter-Id := "filter" ++} ++ ++update { ++ control:Cleartext-Password := 'hello' ++ request:Tmp-String-0 := "This is a string\n" ++ request:Tmp-Octets-0 := 0x000504030201 ++ request:Tmp-String-1 := "what do ya want for nothing?" ++ request:Tmp-String-2 := "Jefe" ++} ++ ++# ++# Put "This is a string" into a file and call "md5sum" on it. ++# You should get this string. ++# ++if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 1' ++ } ++} ++ ++if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 2' ++ } ++} ++ ++if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 3' ++ } ++} ++ ++if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 4' ++ } ++} ++ ++# ++# MD4 should also be able to cope with references to octet attributes ++# ++if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') { ++ update reply { ++ Filter-Id += 'fail 5' ++ } ++} ++ ++if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') { ++ update reply { ++ Filter-Id += 'fail 6' ++ } ++} ++ diff --git a/freeradius-Fix-resource-hard-limit-error.patch b/freeradius-Fix-resource-hard-limit-error.patch new file mode 100644 index 0000000..800c06c --- /dev/null +++ b/freeradius-Fix-resource-hard-limit-error.patch @@ -0,0 +1,32 @@ +commit 1ce4508c92493cf03ea1b3c42e83540b387884fa +Author: Antonio Torres <antorres@redhat.com> +Date: Fri Jul 2 07:12:48 2021 -0400 +Subject: [PATCH] debug: don't set resource hard limit to zero + + Setting the resource hard limit to zero is irreversible, meaning if it + is set to zero then there is no way to set it higher. This means + enabling core dump is not possible, since setting a new resource limit + for RLIMIT_CORE would fail. By only setting the soft limit to zero, we + can disable and enable core dumps without failures. + + This fix is present in both main and 3.0.x upstream branches. + + Ticket in RHEL Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1977572 + Signed-off-by: Antonio Torres antorres@redhat.com +--- + src/lib/debug.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib/debug.c b/src/lib/debug.c +index 576bcb2a65..6330c9cb66 100644 +--- a/src/lib/debug.c ++++ b/src/lib/debug.c +@@ -599,7 +599,7 @@ int fr_set_dumpable(bool allow_core_dumps) + struct rlimit no_core; + + no_core.rlim_cur = 0; +- no_core.rlim_max = 0; ++ no_core.rlim_max = core_limits.rlim_max; + + if (setrlimit(RLIMIT_CORE, &no_core) < 0) { + fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno)); diff --git a/freeradius-Use-system-crypto-policy-by-default.patch b/freeradius-Use-system-crypto-policy-by-default.patch new file mode 100644 index 0000000..199e583 --- /dev/null +++ b/freeradius-Use-system-crypto-policy-by-default.patch @@ -0,0 +1,86 @@ +From a7ed62fbcc043a9ec7a4f09962a2cd2acffa019b Mon Sep 17 00:00:00 2001 +From: Alexander Scheel <ascheel@redhat.com> +Date: Wed, 8 May 2019 10:16:31 -0400 +Subject: [PATCH] Use system-provided crypto-policies by default + +Signed-off-by: Alexander Scheel <ascheel@redhat.com> +--- + raddb/mods-available/eap | 4 ++-- + raddb/mods-available/inner-eap | 2 +- + raddb/sites-available/abfab-tls | 2 +- + raddb/sites-available/tls | 4 ++-- + 4 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap +index 36849e10f2..b28c0f19c6 100644 +--- a/raddb/mods-available/eap ++++ b/raddb/mods-available/eap +@@ -368,7 +368,7 @@ eap { + # + # For EAP-FAST, use "ALL:!EXPORT:!eNULL:!SSLv2" + # +- cipher_list = "DEFAULT" ++ cipher_list = "PROFILE=SYSTEM" + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) +@@ -912,7 +912,7 @@ eap { + # Note - for OpenSSL 1.1.0 and above you may need + # to add ":@SECLEVEL=0" + # +- # cipher_list = "ALL:!EXPORT:!eNULL:!SSLv2" ++ # cipher_list = "PROFILE=SYSTEM" + + # PAC lifetime in seconds (default: seven days) + # +diff --git a/raddb/mods-available/inner-eap b/raddb/mods-available/inner-eap +index 576eb7739e..ffa07188e2 100644 +--- a/raddb/mods-available/inner-eap ++++ b/raddb/mods-available/inner-eap +@@ -77,7 +77,7 @@ eap inner-eap { + # certificates. If so, edit this file. + ca_file = ${cadir}/ca.pem + +- cipher_list = "DEFAULT" ++ cipher_list = "PROFILE=SYSTEM" + + # You may want to set a very small fragment size. + # The TLS data here needs to go inside of the +diff --git a/raddb/sites-available/abfab-tls b/raddb/sites-available/abfab-tls +index 92f1d6330e..cd69b3905a 100644 +--- a/raddb/sites-available/abfab-tls ++++ b/raddb/sites-available/abfab-tls +@@ -19,7 +19,7 @@ listen { + dh_file = ${certdir}/dh + fragment_size = 8192 + ca_path = ${cadir} +- cipher_list = "DEFAULT" ++ cipher_list = "PROFILE=SYSTEM" + + cache { + enable = no +diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls +index bbc761b1c5..83cd35b851 100644 +--- a/raddb/sites-available/tls ++++ b/raddb/sites-available/tls +@@ -215,7 +215,7 @@ listen { + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". +- cipher_list = "DEFAULT" ++ cipher_list = "PROFILE=SYSTEM" + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) +@@ -517,7 +517,7 @@ home_server tls { + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". +- cipher_list = "DEFAULT" ++ cipher_list = "PROFILE=SYSTEM" + } + + } +-- +2.21.0 + diff --git a/freeradius-bootstrap-create-only.patch b/freeradius-bootstrap-create-only.patch new file mode 100644 index 0000000..17cab04 --- /dev/null +++ b/freeradius-bootstrap-create-only.patch @@ -0,0 +1,89 @@ +From acaf4be8e301a01041acba189194d9502994611d Mon Sep 17 00:00:00 2001 +From: Alexander Scheel <ascheel@redhat.com> +Date: Wed, 13 May 2020 10:01:47 -0400 +Subject: [PATCH] Don't clobber existing files on bootstrap + +Signed-off-by: Alexander Scheel <ascheel@redhat.com> +--- + raddb/certs/bootstrap | 31 +++++++++++++++---------------- + 1 file changed, 15 insertions(+), 16 deletions(-) + +diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap +index ede09bc..e555491 100755 +--- a/raddb/certs/bootstrap ++++ b/raddb/certs/bootstrap +@@ -20,56 +20,55 @@ cd `dirname $0` + # Don't edit the following text. Instead, edit the Makefile, and + # re-generate these commands. + # +-if [ ! -f dh ]; then ++if [ ! -e dh ]; then + openssl dhparam -out dh 2048 || exit 1 +- if [ -e /dev/urandom ] ; then +- ln -sf /dev/urandom random +- else +- date > ./random; +- fi ++ ln -sf /dev/urandom random + fi + +-if [ ! -f server.key ]; then ++if [ ! -e server.key ]; then + openssl req -new -out server.csr -keyout server.key -config ./server.cnf || exit 1 + chmod g+r server.key + fi + +-if [ ! -f ca.key ]; then ++if [ ! -e ca.key ]; then + openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf || exit 1 + fi + +-if [ ! -f index.txt ]; then ++if [ ! -e index.txt ]; then + touch index.txt + fi + +-if [ ! -f serial ]; then ++if [ ! -e serial ]; then + echo '01' > serial + fi + +-if [ ! -f server.crt ]; then ++if [ ! -e server.crt ]; then + openssl ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out server.crt -extensions xpserver_ext -extfile xpextensions -config ./server.cnf || exit 1 + fi + +-if [ ! -f server.p12 ]; then ++if [ ! -e server.p12 ]; then + openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1 + chmod g+r server.p12 + fi + +-if [ ! -f server.pem ]; then ++if [ ! -e server.pem ]; then + openssl pkcs12 -in server.p12 -out server.pem -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1 + openssl verify -CAfile ca.pem server.pem || exit 1 + chmod g+r server.pem + fi + +-if [ ! -f ca.der ]; then ++if [ ! -e ca.der ]; then + openssl x509 -inform PEM -outform DER -in ca.pem -out ca.der || exit 1 + fi + +-if [ ! -f client.key ]; then ++if [ ! -e client.key ]; then + openssl req -new -out client.csr -keyout client.key -config ./client.cnf + chmod g+r client.key + fi + +-if [ ! -f client.crt ]; then ++if [ ! -e client.crt ]; then + openssl ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out client.crt -extensions xpclient_ext -extfile xpextensions -config ./client.cnf + fi ++ ++chown root:radiusd dh ca.* client.* server.* ++chmod 640 dh ca.* client.* server.* +-- +2.26.2 + diff --git a/freeradius-bootstrap-make-permissions.patch b/freeradius-bootstrap-make-permissions.patch new file mode 100644 index 0000000..3548fa6 --- /dev/null +++ b/freeradius-bootstrap-make-permissions.patch @@ -0,0 +1,29 @@ +From ea164ceafa05f96079204a3f0ae379e46e64a455 Mon Sep 17 00:00:00 2001 +From: Alexander Scheel <ascheel@redhat.com> +Date: Tue, 4 Aug 2020 10:08:15 -0400 +Subject: [PATCH] Fix permissions after generating certificates with make + +Signed-off-by: Alexander Scheel <ascheel@redhat.com> +--- + raddb/certs/bootstrap | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap +index 336a2bd..9920ecf 100755 +--- a/raddb/certs/bootstrap ++++ b/raddb/certs/bootstrap +@@ -21,7 +21,10 @@ make -h > /dev/null 2>&1 + # + if [ "$?" = "0" ]; then + make all +- exit $? ++ ret=$? ++ chown root:radiusd dh ca.* client.* server.* ++ chmod 640 dh ca.* client.* server.* ++ exit $ret + fi + + # +-- +2.26.2 + diff --git a/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch b/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch new file mode 100644 index 0000000..b3dc68e --- /dev/null +++ b/freeradius-bootstrap-pass-noenc-to-certificate-generation.patch @@ -0,0 +1,136 @@ +From e089777942552c4fe3e58aa328566e7bb745dbf8 Mon Sep 17 00:00:00 2001 +From: Antonio Torres <antorres@redhat.com> +Date: Fri, 22 Apr 2022 12:27:43 +0200 +Subject: [PATCH] bootstrap: pass -noenc to certificate generation + +Bootstrap script would fail to generate certificates if run on systems +with FIPS enabled. By passing the -noenc option, we can skip the usage +of unsupported algorithms on these systems. + +After generating the certificates, correct permissions are set. + +Signed-off-by: Antonio Torres <antorres@redhat.com> + +[antorres@redhat.com]: patch adapted to work together with freeradius-bootstrap-create-only.patch. +In bootstrap diff, -f is changed to -e in conditionals. +--- + raddb/certs/Makefile | 20 ++++++++++++++++---- + raddb/certs/bootstrap | 6 +++--- + 2 files changed, 19 insertions(+), 7 deletions(-) + +diff --git a/raddb/certs/Makefile b/raddb/certs/Makefile +index 5cbfd467ce..cb10394ec3 100644 +--- a/raddb/certs/Makefile ++++ b/raddb/certs/Makefile +@@ -60,6 +60,8 @@ passwords.mk: server.cnf ca.cnf client.cnf inner-server.cnf + ###################################################################### + dh: + $(OPENSSL) dhparam -out dh -2 $(DH_KEY_SIZE) ++ chown root:radiusd dh ++ chmod 640 dh + + ###################################################################### + # +@@ -71,8 +73,10 @@ ca.key ca.pem: ca.cnf + @[ -f serial ] || $(MAKE) serial + $(OPENSSL) req -new -x509 -keyout ca.key -out ca.pem \ + -days $(CA_DEFAULT_DAYS) -config ./ca.cnf \ +- -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA) ++ -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA) -noenc + chmod g+r ca.key ++ chown root:radiusd ca.* ++ chmod 640 ca.* + + ca.der: ca.pem + $(OPENSSL) x509 -inform PEM -outform DER -in ca.pem -out ca.der +@@ -81,6 +85,8 @@ ca.crl: ca.pem + $(OPENSSL) ca -gencrl -keyfile ca.key -cert ca.pem -config ./ca.cnf -out ca-crl.pem -key $(PASSWORD_CA) + $(OPENSSL) crl -in ca-crl.pem -outform der -out ca.crl + rm ca-crl.pem ++ chown root:radiusd ca.* ++ chmod 640 ca.* + + ###################################################################### + # +@@ -88,7 +94,7 @@ ca.crl: ca.pem + # + ###################################################################### + server.csr server.key: server.cnf +- $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf ++ $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf -noenc + chmod g+r server.key + + server.crt: server.csr ca.key ca.pem +@@ -101,6 +107,8 @@ server.p12: server.crt + server.pem: server.p12 + $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.pem ++ chown root:radiusd server.* ++ chmod 640 server.* + + .PHONY: server.vrfy + server.vrfy: ca.pem +@@ -113,7 +121,7 @@ server.vrfy: ca.pem + # + ###################################################################### + client.csr client.key: client.cnf +- $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf ++ $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf -noenc + chmod g+r client.key + + client.crt: client.csr ca.pem ca.key +@@ -127,6 +135,8 @@ client.pem: client.p12 + $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.pem + cp client.pem $(USER_NAME).pem ++ chown root:radiusd client.* ++ chmod 640 client.* + + .PHONY: client.vrfy + client.vrfy: ca.pem client.pem +@@ -139,7 +149,7 @@ client.vrfy: ca.pem client.pem + # + ###################################################################### + inner-server.csr inner-server.key: inner-server.cnf +- $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf ++ $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf -noenc + chmod g+r inner-server.key + + inner-server.crt: inner-server.csr ca.key ca.pem +@@ -152,6 +162,8 @@ inner-server.p12: inner-server.crt + inner-server.pem: inner-server.p12 + $(OPENSSL) pkcs12 -in inner-server.p12 -out inner-server.pem -passin pass:$(PASSWORD_INNER) -passout pass:$(PASSWORD_INNER) + chmod g+r inner-server.pem ++ chown root:radiusd inner-server.* ++ chmod 640 inner-server.* + + .PHONY: inner-server.vrfy + inner-server.vrfy: ca.pem +diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap +index 57de8cf0d7..c258ec45e0 100755 +--- a/raddb/certs/bootstrap ++++ b/raddb/certs/bootstrap +@@ -41,12 +41,12 @@ if [ ! -f dh ]; then + fi + + if [ ! -e server.key ]; then +- openssl req -new -out server.csr -keyout server.key -config ./server.cnf || exit 1 ++ openssl req -new -out server.csr -keyout server.key -config ./server.cnf -noenc || exit 1 + chmod g+r server.key + fi + + if [ ! -e ca.key ]; then +- openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf || exit 1 ++ openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf -noenc || exit 1 + fi + + if [ ! -e index.txt ]; then +@@ -77,7 +77,7 @@ if [ ! -f ca.der ]; then + fi + + if [ ! -e client.key ]; then +- openssl req -new -out client.csr -keyout client.key -config ./client.cnf ++ openssl req -new -out client.csr -keyout client.key -config ./client.cnf -noenc + chmod g+r client.key + fi +
\ No newline at end of file diff --git a/freeradius-fix-crash-on-invalid-abinary-data.patch b/freeradius-fix-crash-on-invalid-abinary-data.patch new file mode 100644 index 0000000..862c6b5 --- /dev/null +++ b/freeradius-fix-crash-on-invalid-abinary-data.patch @@ -0,0 +1,47 @@ +From: Antonio Torres <antorres@redhat.com> +Date: Fri, 09 Dec 2022 +Subject: Fix crash on invalid abinary data + +A malicious RADIUS client or home server can send a malformed abinary +attribute which can cause the server to crash. + +Backport of https://github.com/FreeRADIUS/freeradius-server/commit/0ec2b39d260e + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151707 +Signed-off-by: Antonio Torres <antorres@redhat.com> +--- +diff --git a/src/lib/filters.c b/src/lib/filters.c +index 4868cd385d9f..3f3b63daeef3 100644 +--- a/src/lib/filters.c ++++ b/src/lib/filters.c +@@ -1205,13 +1205,19 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in + } + } + } else if (filter->type == RAD_FILTER_GENERIC) { +- int count; ++ size_t count, masklen; ++ ++ masklen = ntohs(filter->u.generic.len); ++ if (masklen >= sizeof(filter->u.generic.mask)) { ++ *p = '\0'; ++ return; ++ } + + i = snprintf(p, outlen, " %u ", (unsigned int) ntohs(filter->u.generic.offset)); + p += i; + + /* show the mask */ +- for (count = 0; count < ntohs(filter->u.generic.len); count++) { ++ for (count = 0; count < masklen; count++) { + i = snprintf(p, outlen, "%02x", filter->u.generic.mask[count]); + p += i; + outlen -= i; +@@ -1222,7 +1228,7 @@ void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, in + outlen--; + + /* show the value */ +- for (count = 0; count < ntohs(filter->u.generic.len); count++) { ++ for (count = 0; count < masklen; count++) { + i = snprintf(p, outlen, "%02x", filter->u.generic.value[count]); + p += i; + outlen -= i; diff --git a/freeradius-fix-crash-unknown-eap-sim.patch b/freeradius-fix-crash-unknown-eap-sim.patch new file mode 100644 index 0000000..d2b7956 --- /dev/null +++ b/freeradius-fix-crash-unknown-eap-sim.patch @@ -0,0 +1,115 @@ +From: Antonio Torres <antorres@redhat.com> +Date: Fri, 09 Dec 2022 +Subject: Fix crash on unknown option in EAP-SIM + +When an EAP-SIM supplicant sends an unknown SIM option, the server will try to +look that option up in the internal dictionaries. This lookup will fail, but the +SIM code will not check for that failure. Instead, it will dereference a NULL +pointer, and cause the server to crash. + +Backport of: +https://github.com/FreeRADIUS/freeradius-server/commit/f1cdbb33ec61c4a64a +https://github.com/FreeRADIUS/freeradius-server/commit/71128cac3ee236a88a05cc7bddd43e43a88a3089 + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2151705 +Signed-off-by: Antonio Torres <antorres@redhat.com> +--- +diff --git a/src/modules/rlm_eap/libeap/eapsimlib.c b/src/modules/rlm_eap/libeap/eapsimlib.c +index cf1e8a7dd92..e438a844eab 100644 +--- a/src/modules/rlm_eap/libeap/eapsimlib.c ++++ b/src/modules/rlm_eap/libeap/eapsimlib.c +@@ -307,42 +307,77 @@ int unmap_eapsim_basictypes(RADIUS_PACKET *r, + newvp->vp_length = 1; + fr_pair_add(&(r->vps), newvp); + ++ /* ++ * EAP-SIM has a 1 octet of subtype, and 2 octets ++ * reserved. ++ */ + attr += 3; + attrlen -= 3; + +- /* now, loop processing each attribute that we find */ +- while(attrlen > 0) { ++ /* ++ * Loop over each attribute. The format is: ++ * ++ * 1 octet of type ++ * 1 octet of length (value 1..255) ++ * ((4 * length) - 2) octets of data. ++ */ ++ while (attrlen > 0) { + uint8_t *p; + +- if(attrlen < 2) { ++ if (attrlen < 2) { + fr_strerror_printf("EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen); + return 0; + } + ++ if (!attr[1]) { ++ fr_strerror_printf("EAP-Sim attribute %d (no.%d) has no data", attr[0], ++ es_attribute_count); ++ return 0; ++ } ++ + eapsim_attribute = attr[0]; + eapsim_len = attr[1] * 4; + ++ /* ++ * The length includes the 2-byte header. ++ */ + if (eapsim_len > attrlen) { + fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)", + eapsim_attribute, es_attribute_count, eapsim_len, attrlen); + return 0; + } + +- if(eapsim_len > MAX_STRING_LEN) { +- eapsim_len = MAX_STRING_LEN; +- } +- if (eapsim_len < 2) { +- fr_strerror_printf("EAP-Sim attribute %d (no.%d) has length too small", eapsim_attribute, +- es_attribute_count); +- return 0; +- } ++ newvp = fr_pair_afrom_num(r, eapsim_attribute + PW_EAP_SIM_BASE, 0); ++ if (!newvp) { ++ /* ++ * RFC 4186 Section 8.1 says 0..127 are ++ * "non-skippable". If one such ++ * attribute is found and we don't ++ * understand it, the server has to send: ++ * ++ * EAP-Request/SIM/Notification packet with an ++ * (AT_NOTIFICATION code, which implies general failure ("General ++ * failure after authentication" (0), or "General failure" (16384), ++ * depending on the phase of the exchange), which terminates the ++ * authentication exchange. ++ */ ++ if (eapsim_attribute <= 127) { ++ fr_strerror_printf("Unknown mandatory attribute %d, failing", ++ eapsim_attribute); ++ return 0; ++ } + +- newvp = fr_pair_afrom_num(r, eapsim_attribute+PW_EAP_SIM_BASE, 0); +- newvp->vp_length = eapsim_len-2; +- newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length); +- memcpy(p, &attr[2], eapsim_len-2); +- fr_pair_add(&(r->vps), newvp); +- newvp = NULL; ++ } else { ++ /* ++ * It's known, ccount for header, and ++ * copy the value over. ++ */ ++ newvp->vp_length = eapsim_len - 2; ++ ++ newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length); ++ memcpy(p, &attr[2], newvp->vp_length); ++ fr_pair_add(&(r->vps), newvp); ++ } + + /* advance pointers, decrement length */ + attr += eapsim_len; diff --git a/freeradius-fix-python3-library-suffix.patch b/freeradius-fix-python3-library-suffix.patch new file mode 100644 index 0000000..b6d6ab3 --- /dev/null +++ b/freeradius-fix-python3-library-suffix.patch @@ -0,0 +1,635 @@ +From: Antonio Torres <antorres@redhat.com> +Date: Mon, 06 Nov 2023 +Subject: Fix Python3.8+ library name suffix + +Python 3.8 has removed the "m" suffix in the library name, add a check for it. + +Backport of https://github.com/FreeRADIUS/freeradius-server/commit/fa837465493158257e600f28bca009ba890db863 + +Resolves: https://issues.redhat.com/browse/RHEL-15503 +Signed-off-by: Antonio Torres <antorres@redhat.com> +--- +diff --git a/src/modules/rlm_python3/configure b/src/modules/rlm_python3/configure +index f421558ac0c0..05907f12c359 100755 +--- a/src/modules/rlm_python3/configure ++++ b/src/modules/rlm_python3/configure +@@ -588,7 +588,17 @@ LIBOBJS + targetname + mod_cflags + mod_ldflags ++AWK + PYTHON3_CONFIG_BIN ++pkgpyexecdir ++pyexecdir ++pkgpythondir ++pythondir ++PYTHON_PLATFORM ++PYTHON_EXEC_PREFIX ++PYTHON_PREFIX ++PYTHON_VERSION ++PYTHON + CPP + OBJEXT + EXEEXT +@@ -648,7 +658,8 @@ CFLAGS + LDFLAGS + LIBS + CPPFLAGS +-CPP' ++CPP ++PYTHON' + + + # Initialize some variables set by options. +@@ -1266,6 +1277,7 @@ Some influential environment variables: + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if + you have headers in a nonstandard directory <include dir> + CPP C preprocessor ++ PYTHON the Python interpreter + + Use these variables to override the choices made by `configure' or to help + it to find libraries and programs with nonstandard names/locations. +@@ -1421,6 +1433,119 @@ fi + as_fn_set_status $ac_retval + + } # ac_fn_c_try_cpp ++ ++# ac_fn_c_try_link LINENO ++# ----------------------- ++# Try to link conftest.$ac_ext, and return whether this succeeded. ++ac_fn_c_try_link () ++{ ++ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack ++ rm -f conftest.$ac_objext conftest$ac_exeext ++ if { { ac_try="$ac_link" ++case "(($ac_try" in ++ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; ++ *) ac_try_echo=$ac_try;; ++esac ++eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" ++$as_echo "$ac_try_echo"; } >&5 ++ (eval "$ac_link") 2>conftest.err ++ ac_status=$? ++ if test -s conftest.err; then ++ grep -v '^ *+' conftest.err >conftest.er1 ++ cat conftest.er1 >&5 ++ mv -f conftest.er1 conftest.err ++ fi ++ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 ++ test $ac_status = 0; } && { ++ test -z "$ac_c_werror_flag" || ++ test ! -s conftest.err ++ } && test -s conftest$ac_exeext && { ++ test "$cross_compiling" = yes || ++ test -x conftest$ac_exeext ++ }; then : ++ ac_retval=0 ++else ++ $as_echo "$as_me: failed program was:" >&5 ++sed 's/^/| /' conftest.$ac_ext >&5 ++ ++ ac_retval=1 ++fi ++ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information ++ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would ++ # interfere with the next link command; also delete a directory that is ++ # left behind by Apple's compiler. We do this before executing the actions. ++ rm -rf conftest.dSYM conftest_ipa8_conftest.oo ++ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno ++ as_fn_set_status $ac_retval ++ ++} # ac_fn_c_try_link ++ ++# ac_fn_c_check_func LINENO FUNC VAR ++# ---------------------------------- ++# Tests whether FUNC exists, setting the cache variable VAR accordingly ++ac_fn_c_check_func () ++{ ++ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 ++$as_echo_n "checking for $2... " >&6; } ++if eval \${$3+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ cat confdefs.h - <<_ACEOF >conftest.$ac_ext ++/* end confdefs.h. */ ++/* Define $2 to an innocuous variant, in case <limits.h> declares $2. ++ For example, HP-UX 11i <limits.h> declares gettimeofday. */ ++#define $2 innocuous_$2 ++ ++/* System header to define __stub macros and hopefully few prototypes, ++ which can conflict with char $2 (); below. ++ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since ++ <limits.h> exists even on freestanding compilers. */ ++ ++#ifdef __STDC__ ++# include <limits.h> ++#else ++# include <assert.h> ++#endif ++ ++#undef $2 ++ ++/* Override any GCC internal prototype to avoid an error. ++ Use char because int might match the return type of a GCC ++ builtin and then its argument prototype would still apply. */ ++#ifdef __cplusplus ++extern "C" ++#endif ++char $2 (); ++/* The GNU C library defines this for functions which it implements ++ to always fail with ENOSYS. Some functions are actually named ++ something starting with __ and the normal name is an alias. */ ++#if defined __stub_$2 || defined __stub___$2 ++choke me ++#endif ++ ++int ++main () ++{ ++return $2 (); ++ ; ++ return 0; ++} ++_ACEOF ++if ac_fn_c_try_link "$LINENO"; then : ++ eval "$3=yes" ++else ++ eval "$3=no" ++fi ++rm -f core conftest.err conftest.$ac_objext \ ++ conftest$ac_exeext conftest.$ac_ext ++fi ++eval ac_res=\$$3 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 ++$as_echo "$ac_res" >&6; } ++ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno ++ ++} # ac_fn_c_check_func + cat >config.log <<_ACEOF + This file contains any messages produced by compilers while + running configure, to aid debugging if configure makes a mistake. +@@ -2705,6 +2830,267 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $ + ac_compiler_gnu=$ac_cv_c_compiler_gnu + + ++ ++ ++ ++ ++ ++ if test -n "$PYTHON"; then ++ # If the user set $PYTHON, use it and don't search something else. ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 3.0" >&5 ++$as_echo_n "checking whether $PYTHON version is >= 3.0... " >&6; } ++ prog="import sys ++# split strings by '.' and convert to numeric. Append some zeros ++# because we need at least 4 digits for the hex conversion. ++# map returns an iterator in Python 3.0 and a list in 2.x ++minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0] ++minverhex = 0 ++# xrange is not present in Python 3.0 and range returns an iterator ++for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] ++sys.exit(sys.hexversion < minverhex)" ++ if { echo "$as_me:$LINENO: $PYTHON -c "$prog"" >&5 ++ ($PYTHON -c "$prog") >&5 2>&5 ++ ac_status=$? ++ echo "$as_me:$LINENO: \$? = $ac_status" >&5 ++ (exit $ac_status); }; then : ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 ++$as_echo "yes" >&6; } ++else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++$as_echo "no" >&6; } ++ as_fn_error $? "Python interpreter is too old" "$LINENO" 5 ++fi ++ am_display_PYTHON=$PYTHON ++ else ++ # Otherwise, try each interpreter until we find one that satisfies ++ # VERSION. ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 3.0" >&5 ++$as_echo_n "checking for a Python interpreter with version >= 3.0... " >&6; } ++if ${am_cv_pathless_PYTHON+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ ++ for am_cv_pathless_PYTHON in python python2 python3 python3.9 python3.8 python3.7 python3.6 python3.5 python3.4 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do ++ test "$am_cv_pathless_PYTHON" = none && break ++ prog="import sys ++# split strings by '.' and convert to numeric. Append some zeros ++# because we need at least 4 digits for the hex conversion. ++# map returns an iterator in Python 3.0 and a list in 2.x ++minver = list(map(int, '3.0'.split('.'))) + [0, 0, 0] ++minverhex = 0 ++# xrange is not present in Python 3.0 and range returns an iterator ++for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] ++sys.exit(sys.hexversion < minverhex)" ++ if { echo "$as_me:$LINENO: $am_cv_pathless_PYTHON -c "$prog"" >&5 ++ ($am_cv_pathless_PYTHON -c "$prog") >&5 2>&5 ++ ac_status=$? ++ echo "$as_me:$LINENO: \$? = $ac_status" >&5 ++ (exit $ac_status); }; then : ++ break ++fi ++ done ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_pathless_PYTHON" >&5 ++$as_echo "$am_cv_pathless_PYTHON" >&6; } ++ # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. ++ if test "$am_cv_pathless_PYTHON" = none; then ++ PYTHON=: ++ else ++ # Extract the first word of "$am_cv_pathless_PYTHON", so it can be a program name with args. ++set dummy $am_cv_pathless_PYTHON; ac_word=$2 ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 ++$as_echo_n "checking for $ac_word... " >&6; } ++if ${ac_cv_path_PYTHON+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ case $PYTHON in ++ [\\/]* | ?:[\\/]*) ++ ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. ++ ;; ++ *) ++ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR ++for as_dir in $PATH ++do ++ IFS=$as_save_IFS ++ test -z "$as_dir" && as_dir=. ++ for ac_exec_ext in '' $ac_executable_extensions; do ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ++ ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" ++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 ++ break 2 ++ fi ++done ++ done ++IFS=$as_save_IFS ++ ++ ;; ++esac ++fi ++PYTHON=$ac_cv_path_PYTHON ++if test -n "$PYTHON"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 ++$as_echo "$PYTHON" >&6; } ++else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++$as_echo "no" >&6; } ++fi ++ ++ ++ fi ++ am_display_PYTHON=$am_cv_pathless_PYTHON ++ fi ++ ++ ++ if test "$PYTHON" = :; then ++ : ++ else ++ ++ ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON version" >&5 ++$as_echo_n "checking for $am_display_PYTHON version... " >&6; } ++if ${am_cv_python_version+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[:3])"` ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_version" >&5 ++$as_echo "$am_cv_python_version" >&6; } ++ PYTHON_VERSION=$am_cv_python_version ++ ++ ++ ++ PYTHON_PREFIX='${prefix}' ++ ++ PYTHON_EXEC_PREFIX='${exec_prefix}' ++ ++ ++ ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON platform" >&5 ++$as_echo_n "checking for $am_display_PYTHON platform... " >&6; } ++if ${am_cv_python_platform+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"` ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_platform" >&5 ++$as_echo "$am_cv_python_platform" >&6; } ++ PYTHON_PLATFORM=$am_cv_python_platform ++ ++ ++ # Just factor out some code duplication. ++ am_python_setup_sysconfig="\ ++import sys ++# Prefer sysconfig over distutils.sysconfig, for better compatibility ++# with python 3.x. See automake bug#10227. ++try: ++ import sysconfig ++except ImportError: ++ can_use_sysconfig = 0 ++else: ++ can_use_sysconfig = 1 ++# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: ++# <https://github.com/pypa/virtualenv/issues/118> ++try: ++ from platform import python_implementation ++ if python_implementation() == 'CPython' and sys.version[:3] == '2.7': ++ can_use_sysconfig = 0 ++except ImportError: ++ pass" ++ ++ ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 ++$as_echo_n "checking for $am_display_PYTHON script directory... " >&6; } ++if ${am_cv_python_pythondir+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ if test "x$prefix" = xNONE ++ then ++ am_py_prefix=$ac_default_prefix ++ else ++ am_py_prefix=$prefix ++ fi ++ am_cv_python_pythondir=`$PYTHON -c " ++$am_python_setup_sysconfig ++if can_use_sysconfig: ++ sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) ++else: ++ from distutils import sysconfig ++ sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') ++sys.stdout.write(sitedir)"` ++ case $am_cv_python_pythondir in ++ $am_py_prefix*) ++ am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` ++ am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` ++ ;; ++ *) ++ case $am_py_prefix in ++ /usr|/System*) ;; ++ *) ++ am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages ++ ;; ++ esac ++ ;; ++ esac ++ ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pythondir" >&5 ++$as_echo "$am_cv_python_pythondir" >&6; } ++ pythondir=$am_cv_python_pythondir ++ ++ ++ ++ pkgpythondir=\${pythondir}/$PACKAGE ++ ++ ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON extension module directory" >&5 ++$as_echo_n "checking for $am_display_PYTHON extension module directory... " >&6; } ++if ${am_cv_python_pyexecdir+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ if test "x$exec_prefix" = xNONE ++ then ++ am_py_exec_prefix=$am_py_prefix ++ else ++ am_py_exec_prefix=$exec_prefix ++ fi ++ am_cv_python_pyexecdir=`$PYTHON -c " ++$am_python_setup_sysconfig ++if can_use_sysconfig: ++ sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) ++else: ++ from distutils import sysconfig ++ sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') ++sys.stdout.write(sitedir)"` ++ case $am_cv_python_pyexecdir in ++ $am_py_exec_prefix*) ++ am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` ++ am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` ++ ;; ++ *) ++ case $am_py_exec_prefix in ++ /usr|/System*) ;; ++ *) ++ am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages ++ ;; ++ esac ++ ;; ++ esac ++ ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pyexecdir" >&5 ++$as_echo "$am_cv_python_pyexecdir" >&6; } ++ pyexecdir=$am_cv_python_pyexecdir ++ ++ ++ ++ pkgpyexecdir=\${pyexecdir}/$PACKAGE ++ ++ ++ ++ fi ++ ++ ++ + PYTHON3_CONFIG_BIN= + + # Check whether --with-rlm-python3-config-bin was given. +@@ -2771,8 +3157,6 @@ test -n "$PYTHON3_CONFIG_BIN" || PYTHON3_CONFIG_BIN="not-found" + fi + + if test "x$PYTHON3_CONFIG_BIN" = xnot-found; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python3-config not found" >&5 +-$as_echo "$as_me: WARNING: python3-config not found" >&2;} + fail="$fail python3-config" + else + old_CFLAGS="$CFLAGS" +@@ -2794,7 +3178,85 @@ $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s cflags were \"${python3_cflags}\"" >&6 + { $as_echo "$as_me:${as_lineno-$LINENO}: Sanitized cflags were \"${mod_cflags}\"" >&5 + $as_echo "$as_me: Sanitized cflags were \"${mod_cflags}\"" >&6;} + +- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags` ++ for ac_prog in gawk mawk nawk awk ++do ++ # Extract the first word of "$ac_prog", so it can be a program name with args. ++set dummy $ac_prog; ac_word=$2 ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 ++$as_echo_n "checking for $ac_word... " >&6; } ++if ${ac_cv_prog_AWK+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ if test -n "$AWK"; then ++ ac_cv_prog_AWK="$AWK" # Let the user override the test. ++else ++as_save_IFS=$IFS; IFS=$PATH_SEPARATOR ++for as_dir in $PATH ++do ++ IFS=$as_save_IFS ++ test -z "$as_dir" && as_dir=. ++ for ac_exec_ext in '' $ac_executable_extensions; do ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ++ ac_cv_prog_AWK="$ac_prog" ++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 ++ break 2 ++ fi ++done ++ done ++IFS=$as_save_IFS ++ ++fi ++fi ++AWK=$ac_cv_prog_AWK ++if test -n "$AWK"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 ++$as_echo "$AWK" >&6; } ++else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++$as_echo "no" >&6; } ++fi ++ ++ ++ test -n "$AWK" && break ++done ++ ++ ++ ++ ++ # Used to indicate true or false condition ++ ax_compare_version=false ++ ++ # Convert the two version strings to be compared into a format that ++ # allows a simple string comparison. The end result is that a version ++ # string of the form 1.12.5-r617 will be converted to the form ++ # 0001001200050617. In other words, each number is zero padded to four ++ # digits, and non digits are removed. ++ ++ ax_compare_version_A=`echo "${PYTHON_VERSION}" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ ++ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ ++ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ ++ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ ++ -e 's/[^0-9]//g'` ++ ++ ++ ax_compare_version_B=`echo "3.8" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ ++ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ ++ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ ++ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ ++ -e 's/[^0-9]//g'` ++ ++ ++ ax_compare_version=`echo "x$ax_compare_version_A ++x$ax_compare_version_B" | sed 's/^ *//' | sort -r | sed "s/x${ax_compare_version_A}/true/;s/x${ax_compare_version_B}/false/;1q"` ++ ++ ++ ++ if test "$ax_compare_version" = "true" ; then ++ EMBED="--embed" ++ fi ++ ++ ++ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED` + { $as_echo "$as_me:${as_lineno-$LINENO}: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&5 + $as_echo "$as_me: ${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"" >&6;} + +@@ -2811,6 +3273,18 @@ $as_echo "$as_me: Sanitized ldflags were \"${mod_ldflags}\"" >&6;} + + targetname="rlm_python3" + fi ++ ++for ac_func in dl_iterate_phdr ++do : ++ ac_fn_c_check_func "$LINENO" "dl_iterate_phdr" "ac_cv_func_dl_iterate_phdr" ++if test "x$ac_cv_func_dl_iterate_phdr" = xyes; then : ++ cat >>confdefs.h <<_ACEOF ++#define HAVE_DL_ITERATE_PHDR 1 ++_ACEOF ++ ++fi ++done ++ + else + targetname= + echo \*\*\* module rlm_python3 is disabled. +@@ -2833,11 +3307,7 @@ ac_config_headers="$ac_config_headers config.h" + + + +- +- unset ac_cv_env_LIBS_set +- unset ac_cv_env_LIBS_value +- +- ac_config_files="$ac_config_files all.mk" ++ac_config_files="$ac_config_files all.mk" + + cat >confcache <<\_ACEOF + # This file is a shell script that caches the results of configure +@@ -3417,6 +3887,7 @@ gives unlimited permission to copy, distribute and modify it." + + ac_pwd='$ac_pwd' + srcdir='$srcdir' ++AWK='$AWK' + test -n "\$AWK" || AWK=awk + _ACEOF + +@@ -4111,4 +4582,3 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + fi + +- +diff --git a/src/modules/rlm_python3/configure.ac b/src/modules/rlm_python3/configure.ac +index 295a2486d2ac..698a8c1d1874 100644 +--- a/src/modules/rlm_python3/configure.ac ++++ b/src/modules/rlm_python3/configure.ac +@@ -7,6 +7,7 @@ if test x$with_[]modname != xno; then + + AC_PROG_CC + AC_PROG_CPP ++ AM_PATH_PYTHON([3.0],, [:]) + + dnl extra argument: --with-rlm-python3-config-bin + PYTHON3_CONFIG_BIN= +@@ -58,7 +59,11 @@ if test x$with_[]modname != xno; then + '` + AC_MSG_NOTICE([Sanitized cflags were \"${mod_cflags}\"]) + +- python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags` ++ dnl # From python 3.8, --embed is required ++ dnl # https://bugs.python.org/issue36721 ++ AX_COMPARE_VERSION(${PYTHON_VERSION}, [ge], [3.8], [EMBED="--embed"], []) ++ ++ python3_ldflags=`${PYTHON3_CONFIG_BIN} --ldflags $EMBED` + AC_MSG_NOTICE([${PYTHON3_CONFIG_BIN}'s ldflags were \"$python3_ldflags}\"]) + + dnl # Strip -Wl,-O1... Is -O even a valid linker flag?? +@@ -77,6 +82,7 @@ if test x$with_[]modname != xno; then + + targetname="rlm_python3" + fi ++ AC_CHECK_FUNCS([dl_iterate_phdr]) + else + targetname= + echo \*\*\* module modname is disabled. +diff --git a/src/modules/rlm_python3/rlm_python3.c b/src/modules/rlm_python3/rlm_python3.c +index df223f0f401b..5da23f4d7116 100644 +--- a/src/modules/rlm_python3/rlm_python3.c ++++ b/src/modules/rlm_python3/rlm_python3.c +@@ -41,8 +41,17 @@ RCSID("$Id$") + #include <link.h> + #endif + ++/* ++ * Since version 3.8, the "m" suffix is no longer available. ++ * https://bugs.python.org/issue36707 ++ */ ++#if PY_MINOR_VERSION >= 8 ++#define LIBPYTHON_LINKER_NAME \ ++ "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) LT_SHREXT ++#else + #define LIBPYTHON_LINKER_NAME \ + "libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) "m" LT_SHREXT ++#endif + + static uint32_t python_instances = 0; + static void *python_dlhandle; diff --git a/freeradius-ldap-infinite-timeout-on-starttls.patch b/freeradius-ldap-infinite-timeout-on-starttls.patch new file mode 100644 index 0000000..40df134 --- /dev/null +++ b/freeradius-ldap-infinite-timeout-on-starttls.patch @@ -0,0 +1,31 @@ +From: Antonio Torres <antorres@redhat.com> +Date: Fri, 28 Jan 2022 +Subject: Use infinite timeout when using LDAP+start-TLS + +This will ensure that the TLS connection to the LDAP server will complete +before starting FreeRADIUS, as it forces libldap to use a blocking socket during +the process. Infinite timeout is the OpenLDAP default. +Avoids this: https://git.openldap.org/openldap/openldap/-/blob/87ffc60006298069a5a044b8e63dab27a61d3fdf/libraries/libldap/tls2.c#L1134 + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1992551 +Signed-off-by: Antonio Torres <antorres@redhat.com> +--- + src/modules/rlm_ldap/ldap.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c +index cf7a84e069..841bf888a1 100644 +--- a/src/modules/rlm_ldap/ldap.c ++++ b/src/modules/rlm_ldap/ldap.c +@@ -1472,7 +1472,10 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance) + } + + #ifdef LDAP_OPT_NETWORK_TIMEOUT +- if (inst->net_timeout) { ++ bool using_tls = inst->start_tls || ++ inst->port == 636 || ++ strncmp(inst->server, "ldaps://", strlen("ldaps://")) == 0; ++ if (inst->net_timeout && !using_tls) { + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = inst->net_timeout; + diff --git a/freeradius-logrotate b/freeradius-logrotate new file mode 100644 index 0000000..c962254 --- /dev/null +++ b/freeradius-logrotate @@ -0,0 +1,56 @@ +# You can use this to rotate the /var/log/radius/* files, simply copy +# it to /etc/logrotate.d/radiusd + +# There are different detail-rotating strategies you can use. One is +# to write to a single detail file per IP and use the rotate config +# below. Another is to write to a daily detail file per IP with: +# detailfile = ${radacctdir}/%{Client-IP-Address}/%Y%m%d-detail +# (or similar) in radiusd.conf, without rotation. If you go with the +# second technique, you will need another cron job that removes old +# detail files. You do not need to comment out the below for method #2. +/var/log/radius/radacct/*/detail { + monthly + rotate 4 + nocreate + missingok + compress + su radiusd radiusd +} + +/var/log/radius/checkrad.log { + monthly + rotate 4 + create + missingok + compress + su radiusd radiusd +} + +/var/log/radius/radius.log { + monthly + rotate 4 + create + missingok + compress + su radiusd radiusd + postrotate + /usr/bin/systemctl reload-or-try-restart radiusd + endscript +} + +/var/log/radius/radwtmp { + monthly + rotate 4 + create + compress + missingok + su radiusd radiusd +} +/var/log/radius/sqltrace.sql { + monthly + rotate 4 + create + compress + missingok + su radiusd radiusd +} diff --git a/freeradius-no-buildtime-cert-gen.patch b/freeradius-no-buildtime-cert-gen.patch new file mode 100644 index 0000000..aa3be66 --- /dev/null +++ b/freeradius-no-buildtime-cert-gen.patch @@ -0,0 +1,104 @@ +From e6f7c9d4c2af1cda7760ca8155166bb5d4d541d0 Mon Sep 17 00:00:00 2001 +From: Alexander Scheel <ascheel@redhat.com> +Date: Wed, 8 May 2019 12:58:02 -0400 +Subject: [PATCH] Don't generate certificates in reproducible builds + +Signed-off-by: Alexander Scheel <ascheel@redhat.com> +--- + Make.inc.in | 5 +++++ + configure | 4 ++++ + configure.ac | 3 +++ + raddb/all.mk | 4 ++++ + 4 files changed, 16 insertions(+) + +diff --git a/Make.inc.in b/Make.inc.in +index 0b2cd74de8..8c623cf95c 100644 +--- a/Make.inc.in ++++ b/Make.inc.in +@@ -173,3 +173,8 @@ else + TESTBINDIR = ./$(BUILD_DIR)/bin + TESTBIN = ./$(BUILD_DIR)/bin + endif ++ ++# ++# With reproducible builds, do not generate certificates during installation ++# ++ENABLE_REPRODUCIBLE_BUILDS = @ENABLE_REPRODUCIBLE_BUILDS@ +diff --git a/configure b/configure +index c2c599c92b..3d4403a844 100755 +--- a/configure ++++ b/configure +@@ -655,6 +655,7 @@ RUSERS + SNMPWALK + SNMPGET + PERL ++ENABLE_REPRODUCIBLE_BUILDS + openssl_version_check_config + WITH_DHCP + modconfdir +@@ -5586,6 +5587,7 @@ else + fi + + ++ENABLE_REPRODUCIBLE_BUILDS=yes + # Check whether --enable-reproducible-builds was given. + if test "${enable_reproducible_builds+set}" = set; then : + enableval=$enable_reproducible_builds; case "$enableval" in +@@ -5597,6 +5599,7 @@ $as_echo "#define ENABLE_REPRODUCIBLE_BUILDS 1" >>confdefs.h + ;; + *) + reproducible_builds=no ++ ENABLE_REPRODUCIBLE_BUILDS=no + esac + + fi +@@ -5604,6 +5607,7 @@ fi + + + ++ + CHECKRAD=checkrad + # Extract the first word of "perl", so it can be a program name with args. + set dummy perl; ac_word=$2 +diff --git a/configure.ac b/configure.ac +index a7abf0025a..35b013f4af 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -619,6 +619,7 @@ AC_SUBST([openssl_version_check_config]) + dnl # + dnl # extra argument: --enable-reproducible-builds + dnl # ++ENABLE_REPRODUCIBLE_BUILDS=yes + AC_ARG_ENABLE(reproducible-builds, + [AS_HELP_STRING([--enable-reproducible-builds], + [ensure the build does not change each time])], +@@ -630,8 +631,10 @@ AC_ARG_ENABLE(reproducible-builds, + ;; + *) + reproducible_builds=no ++ ENABLE_REPRODUCIBLE_BUILDS=no + esac ] + ) ++AC_SUBST(ENABLE_REPRODUCIBLE_BUILDS) + + + dnl ############################################################# +diff --git a/raddb/all.mk b/raddb/all.mk +index c966edd657..c8e976a499 100644 +--- a/raddb/all.mk ++++ b/raddb/all.mk +@@ -124,7 +124,11 @@ $(R)$(raddbdir)/users: $(R)$(modconfdir)/files/authorize + ifneq "$(LOCAL_CERT_PRODUCTS)" "" + $(LOCAL_CERT_PRODUCTS): + @echo BOOTSTRAP raddb/certs/ ++ifeq "$(ENABLE_REPRODUCIBLE_BUILDS)" "yes" ++ @$(MAKE) -C $(R)$(raddbdir)/certs/ passwords.mk ++else + @$(MAKE) -C $(R)$(raddbdir)/certs/ ++endif + + # Bootstrap is special + $(R)$(raddbdir)/certs/bootstrap: | raddb/certs/bootstrap $(LOCAL_CERT_PRODUCTS) +-- +2.21.0 + diff --git a/freeradius-pam-conf b/freeradius-pam-conf new file mode 100644 index 0000000..090c4a5 --- /dev/null +++ b/freeradius-pam-conf @@ -0,0 +1,6 @@ +#%PAM-1.0 +auth include password-auth +account required pam_nologin.so +account include password-auth +password include password-auth +session include password-auth diff --git a/freeradius-tmpfiles.conf b/freeradius-tmpfiles.conf new file mode 100644 index 0000000..bccd09d --- /dev/null +++ b/freeradius-tmpfiles.conf @@ -0,0 +1,2 @@ +D /run/radiusd 0710 radiusd radiusd - +D /run/radiusd/tmp 0700 radiusd radiusd - diff --git a/freeradius.spec b/freeradius.spec new file mode 100644 index 0000000..e88d7f2 --- /dev/null +++ b/freeradius.spec @@ -0,0 +1,2617 @@ +Summary: High-performance and highly configurable free RADIUS server +Name: freeradius +Version: 3.0.21 +Release: 42%{?dist} +License: GPLv2+ and LGPLv2+ +URL: http://www.freeradius.org/ + +# Is elliptic curve cryptography supported? +%if 0%{?rhel} >= 7 || 0%{?fedora} >= 20 +%global HAVE_EC_CRYPTO 1 +%else +%global HAVE_EC_CRYPTO 0 +%endif + +%global dist_base freeradius-server-%{version} + +Source0: ftp://ftp.freeradius.org/pub/radius/%{dist_base}.tar.bz2 +Source100: radiusd.service +Source102: freeradius-logrotate +Source103: freeradius-pam-conf +Source104: freeradius-tmpfiles.conf +Source105: freeradius.sysusers + +Patch1: freeradius-Adjust-configuration-to-fit-Red-Hat-specifics.patch +Patch2: freeradius-Use-system-crypto-policy-by-default.patch +Patch3: freeradius-bootstrap-create-only.patch +Patch4: freeradius-no-buildtime-cert-gen.patch +Patch5: freeradius-bootstrap-make-permissions.patch +Patch6: freeradius-Fix-resource-hard-limit-error.patch +Patch7: freeradius-ldap-infinite-timeout-on-starttls.patch +Patch8: freeradius-Backport-OpenSSL3-fixes.patch +Patch9: freeradius-bootstrap-pass-noenc-to-certificate-generation.patch +Patch10: freeradius-fix-crash-unknown-eap-sim.patch +Patch11: freeradius-fix-crash-on-invalid-abinary-data.patch +Patch12: freeradius-fix-python3-library-suffix.patch + +%global docdir %{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}} + +BuildRequires: autoconf +BuildRequires: make +BuildRequires: gcc +BuildRequires: gdbm-devel +BuildRequires: openssl +BuildRequires: openssl-devel +BuildRequires: pam-devel +BuildRequires: zlib-devel +BuildRequires: net-snmp-devel +BuildRequires: net-snmp-utils +BuildRequires: readline-devel +BuildRequires: libpcap-devel +BuildRequires: systemd-units +BuildRequires: libtalloc-devel +BuildRequires: pcre-devel +BuildRequires: chrpath +BuildRequires: systemd-rpm-macros + +%if ! 0%{?rhel} +BuildRequires: libyubikey-devel +BuildRequires: ykclient-devel +%endif + +# Require OpenSSL version we built with, or newer, to avoid startup failures +# due to runtime OpenSSL version checks. +Requires: openssl >= %(rpm -q --queryformat '%%{VERSION}' openssl) +Requires: openssl-perl +Requires(pre): shadow-utils glibc-common +Requires(post): systemd-sysv +Requires(post): systemd-units +# Needed for certificate generation as upstream bootstrap script isn't +# compatible with Makefile equivalent. +Requires: make +Requires(preun): systemd-units +Requires(postun): systemd-units + +%description +The FreeRADIUS Server Project is a high performance and highly configurable +GPL'd free RADIUS server. The server is similar in some respects to +Livingston's 2.0 server. While FreeRADIUS started as a variant of the +Cistron RADIUS server, they don't share a lot in common any more. It now has +many more features than Cistron or Livingston, and is much more configurable. + +FreeRADIUS is an Internet authentication daemon, which implements the RADIUS +protocol, as defined in RFC 2865 (and others). It allows Network Access +Servers (NAS boxes) to perform authentication for dial-up users. There are +also RADIUS clients available for Web servers, firewalls, Unix logins, and +more. Using RADIUS allows authentication and authorization for a network to +be centralized, and minimizes the amount of re-configuration which has to be +done when adding or deleting new users. + +%package doc +Summary: FreeRADIUS documentation + +%description doc +All documentation supplied by the FreeRADIUS project is included +in this package. + +%package utils +Summary: FreeRADIUS utilities +Requires: %{name} = %{version}-%{release} +Requires: libpcap >= 0.9.4 + +%description utils +The FreeRADIUS server has a number of features found in other servers, +and additional features not found in any other server. Rather than +doing a feature by feature comparison, we will simply list the features +of the server, and let you decide if they satisfy your needs. + +Support for RFC and VSA Attributes Additional server configuration +attributes Selecting a particular configuration Authentication methods + +%package devel +Summary: FreeRADIUS development files +Requires: %{name} = %{version}-%{release} + +%description devel +Development headers and libraries for FreeRADIUS. + +%package ldap +Summary: LDAP support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: openldap-devel + +%description ldap +This plugin provides the LDAP support for the FreeRADIUS server project. + +%package krb5 +Summary: Kerberos 5 support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: krb5-devel + +%description krb5 +This plugin provides the Kerberos 5 support for the FreeRADIUS server project. + +%package perl +Summary: Perl support for freeradius +Requires: %{name} = %{version}-%{release} +Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +%{?fedora:BuildRequires: perl-devel} +BuildRequires: perl-devel +BuildRequires: perl-generators +BuildRequires: perl(ExtUtils::Embed) + +%description perl +This plugin provides the Perl support for the FreeRADIUS server project. + +%if 0%{?fedora} <= 30 && 0%{?rhel} < 8 +%package -n python2-freeradius +Summary: Python 2 support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: python2-devel +%{?python_provide:%python_provide python2-freeradius} +# Remove before F30 +Provides: %{name}-python = %{version}-%{release} +Provides: %{name}-python%{?_isa} = %{version}-%{release} +Obsoletes: %{name}-python < %{version}-%{release} + +%description -n python2-freeradius +This plugin provides the Python 2 support for the FreeRADIUS server project. +%endif + +%package -n python3-freeradius +Summary: Python 3 support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: python3-devel +%{?python_provide:%python_provide python3-freeradius} + +%description -n python3-freeradius +This plugin provides the Python 3 support for the FreeRADIUS server project. + +%package mysql +Summary: MySQL support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: mariadb-connector-c-devel + +%description mysql +This plugin provides the MySQL support for the FreeRADIUS server project. + +%package postgresql +Summary: Postgresql support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: libpq-devel + +%description postgresql +This plugin provides the postgresql support for the FreeRADIUS server project. + +%package sqlite +Summary: SQLite support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: sqlite-devel + +%description sqlite +This plugin provides the SQLite support for the FreeRADIUS server project. + +%package unixODBC +Summary: Unix ODBC support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: unixODBC-devel + +%description unixODBC +This plugin provides the unixODBC support for the FreeRADIUS server project. + +%package rest +Summary: REST support for freeradius +Requires: %{name} = %{version}-%{release} +BuildRequires: libcurl-devel +BuildRequires: json-c-devel + +%description rest +This plugin provides the REST support for the FreeRADIUS server project. + +%prep +%setup -q -n %{dist_base} +# Note: We explicitly do not make patch backup files because 'make install' +# mistakenly includes the backup files, especially problematic for raddb config files. +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 +%patch11 -p1 +%patch12 -p1 + +%build +# Force compile/link options, extra security for network facing daemon +%global _hardened_build 1 + +# Hack: rlm_python3 as stable; prevents building other unstable modules. +sed 's/rlm_python/rlm_python3/g' src/modules/stable -i + +%global build_ldflags %{build_ldflags} $(python3-config --embed --libs) +export PY3_LIB_DIR="$(python3-config --configdir)" +export PY3_INC_DIR="$(python3 -c 'import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))')" + +# Enable FIPS support +%global build_cflags %{build_cflags} -DWITH_FIPS + +# In order for the above hack to stick, do a fake configure so +# we can run reconfig before cleaning up after ourselves and running +# configure for real. +./configure && make reconfig && (make clean distclean || true) + +%configure \ + --libdir=%{_libdir}/freeradius \ + --enable-reproducible-builds \ + --disable-openssl-version-check \ + --with-openssl \ + --with-udpfromto \ + --with-threads \ + --with-docdir=%{docdir} \ + --with-rlm-sql_postgresql-include-dir=/usr/include/pgsql \ + --with-rlm-sql-postgresql-lib-dir=%{_libdir} \ + --with-rlm-sql_mysql-include-dir=/usr/include/mysql \ + --with-mysql-lib-dir=%{_libdir}/mariadb \ + --with-unixodbc-lib-dir=%{_libdir} \ + --with-rlm-dbm-lib-dir=%{_libdir} \ + --with-rlm-krb5-include-dir=/usr/kerberos/include \ + --with-rlm_python3 \ + --with-rlm-python3-lib-dir=$PY3_LIB_DIR \ + --with-rlm-python3-include-dir=$PY3_INC_DIR \ + --without-rlm_eap_ikev2 \ + --without-rlm_eap_tnc \ + --without-rlm_sql_iodbc \ + --without-rlm_sql_firebird \ + --without-rlm_sql_db2 \ + --without-rlm_sql_oracle \ + --without-rlm_unbound \ + --without-rlm_redis \ + --without-rlm_rediswho \ + --without-rlm_cache_memcached + +# Build fast, but get better errors if we fail +make %{?_smp_mflags} || make -j1 + +%install +mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/radiusd +make install R=$RPM_BUILD_ROOT + +# logs +mkdir -p $RPM_BUILD_ROOT/var/log/radius/radacct +touch $RPM_BUILD_ROOT/var/log/radius/{radutmp,radius.log} + +install -D -m 644 %{SOURCE100} $RPM_BUILD_ROOT/%{_unitdir}/radiusd.service +install -D -m 644 %{SOURCE102} $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/radiusd +install -D -m 644 %{SOURCE103} $RPM_BUILD_ROOT/%{_sysconfdir}/pam.d/radiusd + +mkdir -p %{buildroot}%{_tmpfilesdir} +mkdir -p %{buildroot}%{_localstatedir}/run/ +install -d -m 0710 %{buildroot}%{_localstatedir}/run/radiusd/ +install -d -m 0700 %{buildroot}%{_localstatedir}/run/radiusd/tmp +install -m 0644 %{SOURCE104} %{buildroot}%{_tmpfilesdir}/radiusd.conf +install -p -D -m 0644 %{SOURCE105} %{buildroot}%{_sysusersdir}/freeradius.conf + +# install SNMP MIB files +mkdir -p $RPM_BUILD_ROOT%{_datadir}/snmp/mibs/ +install -m 644 mibs/*RADIUS*.mib $RPM_BUILD_ROOT%{_datadir}/snmp/mibs/ + +# remove rpath where needed +chrpath --delete $RPM_BUILD_ROOT%{_libdir}/freeradius/*.so +for f in $RPM_BUILD_ROOT/usr/sbin/*; do chrpath --delete $f || true; done +for f in $RPM_BUILD_ROOT/usr/bin/*; do chrpath --delete $f || true; done + +# update ld with freeradius libs +mkdir -p %{buildroot}/%{_sysconfdir}/ld.so.conf.d +echo "%{_libdir}/freeradius" > %{buildroot}/%{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf + +# remove unneeded stuff +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.crt +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.crl +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.csr +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.der +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.key +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.pem +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/*.p12 +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/index.* +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/serial* +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/dh +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/certs/random + +rm -f $RPM_BUILD_ROOT/usr/sbin/rc.radiusd +rm -f $RPM_BUILD_ROOT/usr/bin/rbmonkey +rm -rf $RPM_BUILD_ROOT/%{_libdir}/freeradius/*.a +rm -rf $RPM_BUILD_ROOT/%{_libdir}/freeradius/*.la + +rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/main/mssql + +rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool/oracle +rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool/mssql +rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/ippool-dhcp/oracle +rm -rf $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/main/oracle +rm -r $RPM_BUILD_ROOT/etc/raddb/mods-config/sql/moonshot-targeted-ids + +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/unbound +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/unbound/default.conf +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/couchbase +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/abfab* +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-available/moonshot-targeted-ids +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/policy.d/abfab* +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/policy.d/moonshot-targeted-ids +rm $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/sites-available/abfab* + +rm $RPM_BUILD_ROOT/%{_libdir}/freeradius/rlm_test.so + +# remove unsupported config files +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/experimental.conf + +# Mongo will never be supported on Fedora or RHEL +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/sql/ippool/mongo/queries.conf +rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/raddb/mods-config/sql/main/mongo/queries.conf + +# install doc files omitted by standard install +for f in COPYRIGHT CREDITS INSTALL.rst README.rst VERSION; do + cp $f $RPM_BUILD_ROOT/%{docdir} +done +cp LICENSE $RPM_BUILD_ROOT/%{docdir}/LICENSE.gpl +cp src/lib/LICENSE $RPM_BUILD_ROOT/%{docdir}/LICENSE.lgpl +cp src/LICENSE.openssl $RPM_BUILD_ROOT/%{docdir}/LICENSE.openssl + +# add Red Hat specific documentation +cat >> $RPM_BUILD_ROOT/%{docdir}/REDHAT << EOF + +Red Hat, RHEL, Fedora, and CentOS specific information can be found on the +FreeRADIUS Wiki in the Red Hat FAQ. + +http://wiki.freeradius.org/guide/Red-Hat-FAQ + +Please reference that document. + +All documentation is in the freeradius-doc sub-package. + +EOF + + +# Make sure our user/group is present prior to any package or subpackage installation +%pre +%sysusers_create_compat %{SOURCE105} + +%preun +%systemd_preun radiusd.service + +%postun +%systemd_postun_with_restart radiusd.service + +/bin/systemctl try-restart radiusd.service >/dev/null 2>&1 || : + + +%files + +# doc +%license %{docdir}/LICENSE.gpl +%license %{docdir}/LICENSE.lgpl +%license %{docdir}/LICENSE.openssl +%doc %{docdir}/REDHAT + +# system +%config(noreplace) %{_sysconfdir}/pam.d/radiusd +%config(noreplace) %{_sysconfdir}/logrotate.d/radiusd +%config(noreplace) %{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf +%{_unitdir}/radiusd.service +%{_tmpfilesdir}/radiusd.conf +%{_sysusersdir}/freeradius.conf +%dir %attr(710,radiusd,radiusd) %{_localstatedir}/run/radiusd +%dir %attr(700,radiusd,radiusd) %{_localstatedir}/run/radiusd/tmp +%dir %attr(755,radiusd,radiusd) %{_localstatedir}/lib/radiusd + +# configs (raddb) +%dir %attr(755,root,radiusd) /etc/raddb +%defattr(-,root,radiusd) +/etc/raddb/README.rst +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/panic.gdb + +%attr(644,root,radiusd) %config(noreplace) /etc/raddb/dictionary +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/clients.conf + +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/templates.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/trigger.conf + +# symlink: /etc/raddb/hints -> ./mods-config/preprocess/hints +%config /etc/raddb/hints + +# symlink: /etc/raddb/huntgroups -> ./mods-config/preprocess/huntgroups +%config /etc/raddb/huntgroups + +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/proxy.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/radiusd.conf + +# symlink: /etc/raddb/users -> ./mods-config/files/authorize +%config(noreplace) /etc/raddb/users + +# certs +%dir %attr(770,root,radiusd) /etc/raddb/certs +%config(noreplace) /etc/raddb/certs/Makefile +%config(noreplace) /etc/raddb/certs/passwords.mk +/etc/raddb/certs/README +%config(noreplace) /etc/raddb/certs/xpextensions +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/certs/*.cnf +%attr(750,root,radiusd) /etc/raddb/certs/bootstrap + +# mods-config +%dir %attr(750,root,radiusd) /etc/raddb/mods-config +/etc/raddb/mods-config/README.rst +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/attr_filter +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/attr_filter/* +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/files +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/files/* +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/preprocess +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/preprocess/* + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main + +# sites-available +%dir %attr(750,root,radiusd) /etc/raddb/sites-available +/etc/raddb/sites-available/README +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/control-socket +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/decoupled-accounting +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/robust-proxy-accounting +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/soh +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/coa +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/coa-relay +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/example +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/inner-tunnel +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dhcp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/check-eap-tls +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/status +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dhcp.relay +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/virtual.example.com +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/originate-coa +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/vmps +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/default +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/proxy-inner-tunnel +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/dynamic-clients +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/copy-acct-to-home-server +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/buffered-sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/tls +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/channel_bindings +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/sites-available/challenge + +# sites-enabled +# symlink: /etc/raddb/sites-enabled/xxx -> ../sites-available/xxx +%dir %attr(750,root,radiusd) /etc/raddb/sites-enabled +%config(missingok) /etc/raddb/sites-enabled/inner-tunnel +%config(missingok) /etc/raddb/sites-enabled/default + +# mods-available +%dir %attr(750,root,radiusd) /etc/raddb/mods-available +/etc/raddb/mods-available/README.rst +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/always +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/attr_filter +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cache_eap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/chap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/counter +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/cui +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/date +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail.example.com +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/detail.log +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dhcp_sqlippool +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/digest +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/dynamic_clients +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/eap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/echo +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/etc_group +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/exec +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/expiration +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/expr +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/files +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/idn +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/inner-eap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ippool +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/linelog +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/logintime +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mac2ip +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mac2vlan +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/mschap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ntlm_auth +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/opendirectory +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/otp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/pam +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/pap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/passwd +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/preprocess +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/python +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/python3 +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/radutmp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/realm +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/redis +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rediswho +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/replicate +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/smbpasswd +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/smsotp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/soh +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sometimes +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sqlcounter +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sqlippool +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/sradutmp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/unix +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/unpack +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/utf8 +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/wimax +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/yubikey + +# mods-enabled +# symlink: /etc/raddb/mods-enabled/xxx -> ../mods-available/xxx +%dir %attr(750,root,radiusd) /etc/raddb/mods-enabled +%config(missingok) /etc/raddb/mods-enabled/always +%config(missingok) /etc/raddb/mods-enabled/attr_filter +%config(missingok) /etc/raddb/mods-enabled/cache_eap +%config(missingok) /etc/raddb/mods-enabled/chap +%config(missingok) /etc/raddb/mods-enabled/date +%config(missingok) /etc/raddb/mods-enabled/detail +%config(missingok) /etc/raddb/mods-enabled/detail.log +%config(missingok) /etc/raddb/mods-enabled/digest +%config(missingok) /etc/raddb/mods-enabled/dynamic_clients +%config(missingok) /etc/raddb/mods-enabled/eap +%config(missingok) /etc/raddb/mods-enabled/echo +%config(missingok) /etc/raddb/mods-enabled/exec +%config(missingok) /etc/raddb/mods-enabled/expiration +%config(missingok) /etc/raddb/mods-enabled/expr +%config(missingok) /etc/raddb/mods-enabled/files +%config(missingok) /etc/raddb/mods-enabled/linelog +%config(missingok) /etc/raddb/mods-enabled/logintime +%config(missingok) /etc/raddb/mods-enabled/mschap +%config(missingok) /etc/raddb/mods-enabled/ntlm_auth +%config(missingok) /etc/raddb/mods-enabled/pap +%config(missingok) /etc/raddb/mods-enabled/passwd +%config(missingok) /etc/raddb/mods-enabled/preprocess +%config(missingok) /etc/raddb/mods-enabled/radutmp +%config(missingok) /etc/raddb/mods-enabled/realm +%config(missingok) /etc/raddb/mods-enabled/replicate +%config(missingok) /etc/raddb/mods-enabled/soh +%config(missingok) /etc/raddb/mods-enabled/sradutmp +%config(missingok) /etc/raddb/mods-enabled/unix +%config(missingok) /etc/raddb/mods-enabled/unpack +%config(missingok) /etc/raddb/mods-enabled/utf8 + +# policy +%dir %attr(750,root,radiusd) /etc/raddb/policy.d +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/accounting +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/canonicalization +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/control +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/cui +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/debug +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/dhcp +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/eap +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/filter +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/operator-name +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/policy.d/rfc7542 + + +# binaries +%defattr(-,root,root) +/usr/sbin/checkrad +/usr/sbin/raddebug +/usr/sbin/radiusd +/usr/sbin/radmin + +# dictionaries +%dir %attr(755,root,root) /usr/share/freeradius +/usr/share/freeradius/* + +# logs +%dir %attr(700,radiusd,radiusd) /var/log/radius/ +%dir %attr(700,radiusd,radiusd) /var/log/radius/radacct/ +%ghost %attr(644,radiusd,radiusd) /var/log/radius/radutmp +%ghost %attr(600,radiusd,radiusd) /var/log/radius/radius.log + +# libs +%attr(755,root,root) %{_libdir}/freeradius/lib*.so* + +# loadable modules +%dir %attr(755,root,root) %{_libdir}/freeradius +%{_libdir}/freeradius/proto_dhcp.so +%{_libdir}/freeradius/proto_vmps.so +%{_libdir}/freeradius/rlm_always.so +%{_libdir}/freeradius/rlm_attr_filter.so +%{_libdir}/freeradius/rlm_cache.so +%{_libdir}/freeradius/rlm_cache_rbtree.so +%{_libdir}/freeradius/rlm_chap.so +%{_libdir}/freeradius/rlm_counter.so +%{_libdir}/freeradius/rlm_cram.so +%{_libdir}/freeradius/rlm_date.so +%{_libdir}/freeradius/rlm_detail.so +%{_libdir}/freeradius/rlm_dhcp.so +%{_libdir}/freeradius/rlm_digest.so +%{_libdir}/freeradius/rlm_dynamic_clients.so +%{_libdir}/freeradius/rlm_eap.so +%{_libdir}/freeradius/rlm_eap_fast.so +%{_libdir}/freeradius/rlm_eap_gtc.so +%{_libdir}/freeradius/rlm_eap_leap.so +%{_libdir}/freeradius/rlm_eap_md5.so +%{_libdir}/freeradius/rlm_eap_mschapv2.so +%{_libdir}/freeradius/rlm_eap_peap.so +%if %{HAVE_EC_CRYPTO} +%{_libdir}/freeradius/rlm_eap_pwd.so +%endif +%{_libdir}/freeradius/rlm_eap_sim.so +%{_libdir}/freeradius/rlm_eap_tls.so +%{_libdir}/freeradius/rlm_eap_ttls.so +%{_libdir}/freeradius/rlm_exec.so +%{_libdir}/freeradius/rlm_expiration.so +%{_libdir}/freeradius/rlm_expr.so +%{_libdir}/freeradius/rlm_files.so +%{_libdir}/freeradius/rlm_ippool.so +%{_libdir}/freeradius/rlm_linelog.so +%{_libdir}/freeradius/rlm_logintime.so +%{_libdir}/freeradius/rlm_mschap.so +%{_libdir}/freeradius/rlm_otp.so +%{_libdir}/freeradius/rlm_pam.so +%{_libdir}/freeradius/rlm_pap.so +%{_libdir}/freeradius/rlm_passwd.so +%{_libdir}/freeradius/rlm_preprocess.so +%{_libdir}/freeradius/rlm_radutmp.so +%{_libdir}/freeradius/rlm_realm.so +%{_libdir}/freeradius/rlm_replicate.so +%{_libdir}/freeradius/rlm_soh.so +%{_libdir}/freeradius/rlm_sometimes.so +%{_libdir}/freeradius/rlm_sql.so +%{_libdir}/freeradius/rlm_sqlcounter.so +%{_libdir}/freeradius/rlm_sqlippool.so +%{_libdir}/freeradius/rlm_sql_null.so +%{_libdir}/freeradius/rlm_unix.so +%{_libdir}/freeradius/rlm_unpack.so +%{_libdir}/freeradius/rlm_utf8.so +%{_libdir}/freeradius/rlm_wimax.so +%{_libdir}/freeradius/rlm_yubikey.so + +# main man pages +%doc %{_mandir}/man5/clients.conf.5.gz +%doc %{_mandir}/man5/dictionary.5.gz +%doc %{_mandir}/man5/radiusd.conf.5.gz +%doc %{_mandir}/man5/radrelay.conf.5.gz +%doc %{_mandir}/man5/rlm_always.5.gz +%doc %{_mandir}/man5/rlm_attr_filter.5.gz +%doc %{_mandir}/man5/rlm_chap.5.gz +%doc %{_mandir}/man5/rlm_counter.5.gz +%doc %{_mandir}/man5/rlm_detail.5.gz +%doc %{_mandir}/man5/rlm_digest.5.gz +%doc %{_mandir}/man5/rlm_expr.5.gz +%doc %{_mandir}/man5/rlm_files.5.gz +%doc %{_mandir}/man5/rlm_idn.5.gz +%doc %{_mandir}/man5/rlm_mschap.5.gz +%doc %{_mandir}/man5/rlm_pap.5.gz +%doc %{_mandir}/man5/rlm_passwd.5.gz +%doc %{_mandir}/man5/rlm_realm.5.gz +%doc %{_mandir}/man5/rlm_sql.5.gz +%doc %{_mandir}/man5/rlm_unix.5.gz +%doc %{_mandir}/man5/unlang.5.gz +%doc %{_mandir}/man5/users.5.gz +%doc %{_mandir}/man8/raddebug.8.gz +%doc %{_mandir}/man8/radiusd.8.gz +%doc %{_mandir}/man8/radmin.8.gz +%doc %{_mandir}/man8/radrelay.8.gz + +# MIB files +%{_datadir}/snmp/mibs/*RADIUS*.mib + +%files doc + +%doc %{docdir}/ + + +%files utils +/usr/bin/* + +# utils man pages +%doc %{_mandir}/man1/radclient.1.gz +%doc %{_mandir}/man1/radeapclient.1.gz +%doc %{_mandir}/man1/radlast.1.gz +%doc %{_mandir}/man1/radtest.1.gz +%doc %{_mandir}/man1/radwho.1.gz +%doc %{_mandir}/man1/radzap.1.gz +%doc %{_mandir}/man1/rad_counter.1.gz +%doc %{_mandir}/man1/smbencrypt.1.gz +%doc %{_mandir}/man1/dhcpclient.1.gz +%doc %{_mandir}/man5/checkrad.5.gz +%doc %{_mandir}/man8/radcrypt.8.gz +%doc %{_mandir}/man8/radsniff.8.gz +%doc %{_mandir}/man8/radsqlrelay.8.gz +%doc %{_mandir}/man8/rlm_ippool_tool.8.gz + +%files devel +/usr/include/freeradius + +%files krb5 +%{_libdir}/freeradius/rlm_krb5.so +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/krb5 + +%files perl +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/perl + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/perl +%attr(640,root,radiusd) /etc/raddb/mods-config/perl/example.pl + +%{_libdir}/freeradius/rlm_perl.so + +%if 0%{?fedora} <= 30 && 0%{?rhel} < 8 +%files -n python2-freeradius +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/python +/etc/raddb/mods-config/python/example.py* +/etc/raddb/mods-config/python/radiusd.py* +%{_libdir}/freeradius/rlm_python.so +%endif + +%files -n python3-freeradius +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/python3 +/etc/raddb/mods-config/python3/example.py* +/etc/raddb/mods-config/python3/radiusd.py* +%{_libdir}/freeradius/rlm_python3.so + +%files mysql +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/mysql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/dailycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/expire_on_login.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/monthlycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/mysql/noresetcounter.conf + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/mysql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/mysql/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/mysql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/mysql/procedure.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/mysql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/setup.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/process-radacct.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql/extras +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/mysql/extras/wimax +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/extras/wimax/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/mysql/extras/wimax/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/ndb +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/ndb/setup.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/ndb/schema.sql +/etc/raddb/mods-config/sql/main/ndb/README + +%{_libdir}/freeradius/rlm_sql_mysql.so + +%files postgresql +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/postgresql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/dailycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/postgresql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/postgresql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/postgresql/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/postgresql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/postgresql/procedure.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/postgresql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/setup.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/process-radacct.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/postgresql/extras +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/extras/voip-postpaid.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/postgresql/extras/cisco_h323_db_schema.sql + +%{_libdir}/freeradius/rlm_sql_postgresql.so + +%files sqlite +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/counter/sqlite +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/dailycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/cui/sqlite +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/sqlite/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/cui/sqlite/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool/sqlite +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/sqlite/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool/sqlite/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql + +%dir %attr(750,root,radiusd) /etc/raddb/mods-config/sql/main/sqlite +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/queries.conf +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/schema.sql +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/process-radacct-refresh.sh +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-config/sql/main/sqlite/process-radacct-schema.sql + +%{_libdir}/freeradius/rlm_sql_sqlite.so + +%files ldap +%{_libdir}/freeradius/rlm_ldap.so +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/ldap + +%files unixODBC +%{_libdir}/freeradius/rlm_sql_unixodbc.so + +%files rest +%{_libdir}/freeradius/rlm_rest.so +%attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest + +%changelog +* Wed Jul 10 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-42 +- Backport fixes for BlastRADIUS CVE + Resolves: RHEL-46567 + +* Wed Apr 24 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-41 +- Rebuild for OpenSSL rebase to 3.2.1 + Resolves: RHEL-33857 + +* Mon Apr 01 2024 Antonio Torres <antorres@redhat.com> - 3.0.21-40 +- Comment out unneeded options from mods-available/eap + Resolves: RHEL-30830 + +* Mon Nov 06 2023 Antonio Torres <antorres@redhat.com> - 3.0.21-39 +- Fix Python3.8+ library name suffix + Resolves: #15503 + +* Mon May 22 2023 Antonio Torres <antorres@redhat.com> - 3.0.21-38 +- Fix crash when verifying client certificate + Resolves: #2183447 + +* Wed Dec 14 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-37 +- Fix defect found by covscan + Resolves: #2151705 + +* Fri Dec 09 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-36 +- Fix multiple CVEs + Resolves: #2151705 + Resolves: #2151703 + Resolves: #2151707 + +* Fri Sep 16 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-35 +- Rebuild to add subpackages to CRB report + Resolves: #2126380 + +* Wed Jun 29 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-34 +- Use GID / UID 95 as it's reserved for FreeRADIUS (https://pagure.io/setup/blob/07f8debf03dfb0e5ed36051c13c86c8cd00cd241/f/uidgid#_107) + Resolves: #2095403 + +* Fri Jun 24 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-33 +- Dynamically allocate users using sysusers.d format + Resolves: #2095403 + +* Mon May 30 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-32 +- Add WITH_FIPS macro to CFLAGS + Related: rhbz#2083699 + +* Tue May 24 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-31 +- Update OpenSSL 3.0 support backport to current v3.0.x branch state +- Add "--enable-fips-workaround" to build options + Related: rhbz#2083699 + +* Tue May 10 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-30 +- Add openssl-perl dependency + Related: rhbz#2078816 + +* Thu Apr 28 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-29 +- Set correct permissions for certificates generated by bootstrap Makefile + Related: rhbz#2069224 + +* Mon Apr 25 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-28 +- bootstrap: pass -noenc to certificate generation, do it on script as well + Related: rhbz#2069224 + +* Fri Apr 22 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-27 +- bootstrap: pass -noenc to certificate generation + Related: rhbz#2069224 + +* Mon Jan 31 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-26 +- Move remaining files from /var/run to /run + Related: rhbz#2047972 + +* Fri Jan 28 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-25 +- Revert "Allow to connect to partially open LDAP handle" +- Use infinite timeout (openldap default) when using LDAP+start-TLS +- Update openssl dependency to not check epoch (was causing detection issues) + Related: rhbz#1992551 + +* Thu Jan 13 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-24 +- Avoid segfault when trying to use MD4 without legacy provider + Related: rhbz#1978216 + +* Wed Jan 12 2022 Antonio Torres <antorres@redhat.com> - 3.0.21-23 +- Backport OpenSSL3 fixes + Related: rhbz#1978216 + +* Wed Oct 13 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-22 +- Allow to connect to partially open LDAP handle + Related: rhbz#1992551 + +* Mon Sep 27 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-21 +- Move FR's systemd unit PID file from /var/run to /run + Related: rhbz#2006368 + +* Thu Aug 19 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-20 +- Rebuild to pick up new build flags from redhat-rpm-config + Related: rhbz#1984652 + +* Thu Aug 12 2021 Filip Dvorak <fdvorak@redhat.com> - 3.0.21-19 +- Install psutil module and generate def. certs during test script + Resolves: rhbz#1990392 + +* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-18 +- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags + Related: rhbz#1991688 + +* Tue Aug 03 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-17 +- Ignore badfuncs error in rpminspect + Resolves: bz#1986972 + +* Mon Aug 02 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-16 +- Remove RPATH usage + Resolves: bz#1986968 + +* Mon Jul 19 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-15 +- Fix coredump not being able to be enabled + Resolves: bz#1977722 + +* Wed Jun 30 2021 Antonio Torres <antorres@redhat.com> - 3.0.21-14 +- Fix Python3.8 not being linked correctly + Related: rhbz#1948622 + +* Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-13 +- Rebuilt for RHEL 9 BETA for openssl 3.0 + Related: rhbz#1971065 + +* Thu Apr 15 2021 Mohan Boddu <mboddu@redhat.com> - 3.0.21-12 +- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937 + +* Wed Mar 10 2021 Robbie Harwood <rharwood@redhat.com> - 3.0.21-11 +- Disable automatic bootstrap + +* Tue Mar 02 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.21-10 +- Rebuilt for updated systemd-rpm-macros + See https://pagure.io/fesco/issue/2583. + +* Mon Feb 08 2021 Pavel Raiskup <praiskup@redhat.com> - 3.0.21-9 +- rebuild for libpq ABI fix rhbz#1908268 + +* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.21-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Tue Aug 04 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-7 +- Fix certificate permissions after make-based generation + Resolves: bz#1835249 + +* Tue Aug 04 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-6 +- Fix certificate permissions after make-based generation + Resolves: bz#1835249 + +* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.21-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Tue Jun 23 2020 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.21-4 +- Perl 5.32 rebuild + +* Wed May 13 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-3 +- Fix certificate generation + Resolves: bz#1835249 + +* Tue Apr 21 2020 Björn Esser <besser82@fedoraproject.org> - 3.0.21-2 +- Rebuild (json-c) + +* Wed Apr 01 2020 Alexander Scheel <ascheel@redhat.com> - 3.0.21-1 +- Rebased to 3.0.21 + Resolves: bz#1816745 + +* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.20-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Sat Jan 11 2020 Paul Wouters <pwouters@redhat.com> - 3.0.20-2 +- fixup tmpfile to use /run instead of /var/run + +* Fri Nov 15 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.20-1 +- Rebased to 3.0.20 + Resolves: bz#1772710 +- Introduced new rlm_python3 module + +* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.19-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Fri May 31 2019 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.19-4 +- Perl 5.30 rebuild + +* Wed May 08 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-3 +- Update boostrap to change ownership of all certificates to root:radiusd + +* Wed May 08 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-2 +- Updated crypto-policies patch +- Updated /etc/raddb/certs/bootstrap to only create certificates if missing: bz#1705165 bz#1672284 +- Updated logrotate definitions to run as radiusd:radiusd: bz#1705343 +- Drop python2 package on Fedora 31+ +- Add database dependencies: bz#1658697 +- Don't generate certificate during build + +* Wed Apr 10 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.19-1 +- Rebased to 3.0.19 + +* Wed Mar 06 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.18-1 +- Rebased to 3.0.18 + +* Sun Feb 17 2019 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.0.17-6 +- Rebuild for readline 8.0 + +* Tue Feb 05 2019 Alexander Scheel <ascheel@redhat.com> - 3.0.17-5 +- Unit file generates certificates if not present. + Resolves: bz#1672284 + +* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.17-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Mon Jan 14 2019 Björn Esser <besser82@fedoraproject.org> - 3.0.17-3 +- Rebuilt for libcrypt.so.2 (#1666033) + +* Fri Dec 14 2018 Alexander Scheel <ascheel@redhat.com> - 3.0.17-2 +- Updates radiusd.service to start after network-online.target + Resolves: bz#1637275 + +* Thu Oct 18 2018 Alexander Scheel <ascheel@redhat.com> - 3.0.17-1 +- Update to FreeRADIUS server version 3.0.17 +- Adds OpenSSL HMAC patches from upstream (unreleased) +- Adds Python2 shebang patches from upstream (unreleased) + +* Mon Sep 17 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-18 +- Actually apply patches added previously. + Related: Bug#1611286 Man page scan results for freeradius + +* Fri Sep 14 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-17 +- Fix a few minor manpage issues. + Resolves: Bug#1611286 Man page scan results for freeradius + +* Fri Sep 07 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-16 +- Add make to BuildRequires and Requires(post) to fix build and certificate + generation on install. + Resolves: Bug#1574783 Installing freeradius without make results in an + unworkable default configuration + +* Tue Sep 04 2018 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-15 +- Add gcc to BuildRequires. + Resolves: Bug#1622470 FTBFS freeradius (rawhide) + +* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-14 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Fri Jun 29 2018 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.15-13 +- Perl 5.28 rebuild + +* Tue Mar 06 2018 Björn Esser <besser82@fedoraproject.org> - 3.0.15-12 +- Rebuilt for libjson-c.so.4 (json-c v0.13.1) + +* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.0.15-11 +- Escape macros in %%changelog + +* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Sat Jan 20 2018 Björn Esser <besser82@fedoraproject.org> - 3.0.15-9 +- Rebuilt for switch to libxcrypt + +* Fri Jan 05 2018 Iryna Shcherbina <ishcherb@redhat.com> - 3.0.15-8 +- Update Python 2 dependency declarations to new packaging standards + (See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3) + +* Sun Dec 10 2017 Björn Esser <besser82@fedoraproject.org> - 3.0.15-7 +- Rebuilt for libjson-c.so.3 + +* Thu Oct 26 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-6 +- Use mariadb-connector-c-devel instead of mysql-devel or mariadb-devel + Resolves: Bug#1493904 Use mariadb-connector-c-devel instead of mysql-devel + or mariadb-devel + +* Sun Aug 20 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.15-5 +- Add Provides for the old name without %%_isa + +* Sat Aug 19 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 3.0.15-4 +- Python 2 binary package renamed to python2-freeradius + See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3 + +* Wed Aug 02 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.15-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Tue Jul 18 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.15-1 +- Upgrade to upstream v3.0.15 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). +- Resolves: Bug#1471848 CVE-2017-10978 freeradius: Out-of-bounds read/write + due to improper output buffer size check in + make_secret() +- Resolves: Bug#1471860 CVE-2017-10983 freeradius: Out-of-bounds read in + fr_dhcp_decode() when decoding option 63 +- Resolves: Bug#1471861 CVE-2017-10984 freeradius: Out-of-bounds write in + data2vp_wimax() +- Resolves: Bug#1471863 CVE-2017-10985 freeradius: Infinite loop and memory + exhaustion with 'concat' attributes +- Resolves: Bug#1471864 CVE-2017-10986 freeradius: Infinite read in + dhcp_attr2vp() +- Resolves: Bug#1471865 CVE-2017-10987 freeradius: Buffer over-read in + fr_dhcp_decode_suboptions() +- Resolves: Bug#1456220 freeradius-3.0.15 is available + +* Thu Jul 13 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.14-3 +- Rebuild with updated MySQL client library + +* Sun Jun 04 2017 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.14-2 +- Perl 5.26 rebuild + +* Tue May 30 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.14-1 +- Upgrade to upstream v3.0.14 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). +- Fix TLS resumption authentication bypass (CVE-2017-9148) + +* Wed Mar 29 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-3 +- Explicitly disable rlm_cache_memcached to avoid error when the module's + dependencies are installed, and it is built, but not packaged. +- Prevent segfaults by adding a missing handling of connection errors in + rlm_ldap. +- Make radtest use Cleartext-Password for EAP, fixing its support for eap-md5. + +* Wed Mar 15 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-2 +- Fix permissions of default key files in raddb/certs. +- Require OpenSSL version we built with, or newer, to avoid startup failures + due to runtime OpenSSL version checks. + Resolves: Bug#1299388 +- Fix some issues found with static analyzers. + +* Tue Mar 07 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.13-1 +- Upgrade to upstream v3.0.13 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + +* Tue Feb 21 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-3 +- Do not fail logrotate if radiusd is not running. +- Fix output to log file specified with -l option. +- Fix long hostnames interpreted as IP addresses. +- Avoid clashes with libtool library symbols. +- Remove mentions of Auth-Type = System from docs. +- Improve ip/v4/v6/addr documentation. + +* Mon Feb 20 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-2 +- Fix three cases of comparing pointers to zero characters +- Support OpenSSL v1.1.0 + Resolves: Bug#1385588 + +* Fri Feb 17 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.12-1 +- Upgrade to upstream v3.0.12 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + +* Fri Feb 17 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-7 +- Make sure FreeRADIUS starts after IPA, directory, and Kerberos servers +- Don't rotate radutmp, as it's not a log file +- Logrotate with "systemctl" instead of "service" +- Remove executable bits from "radiusd.service" + +* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.11-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Jan 16 2017 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-5 +- Move tmpfiles.d config to %%{_tmpfilesdir} +- Install license files as %%license + +* Thu Jan 12 2017 Igor Gnatenko <ignatenko@redhat.com> - 3.0.11-4 +- Rebuild for readline 7.x + +* Mon Sep 26 2016 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-3 +- Switch default configuration to use system's crypto policy. + Resolves: Bug#1179224 + +* Tue May 17 2016 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.11-2 +- Perl 5.24 rebuild + +* Tue Apr 12 2016 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.11-1 +- Upgrade to upstream v3.0.10 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + +* Wed Feb 03 2016 Fedora Release Engineering <releng@fedoraproject.org> - 3.0.10-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Wed Dec 09 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.10-1 +- Upgrade to upstream v3.0.10 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + Related: Bug#1133959 +- Remove rlm_eap_tnc support as the required package "tncfhh" was retired. + +* Wed Aug 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.9-1 +- Upgrade to upstream v3.0.9 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + Resolves: Bug#1133959 + +* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.8-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Fri Jun 05 2015 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.8-2 +- Perl 5.22 rebuild + +* Tue Apr 28 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.8-1 +- Upgrade to upstream v3.0.7 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + Related: Bug#1133959 + +* Thu Mar 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.7-1 +- Upgrade to upstream v3.0.7 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + Resolves: Bug#1133959 +- Add freeradius-rest package containing rlm_rest module. + Resolves: Bug#1196276 + +* Fri Feb 13 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-4 +- Bump release number to catch up with Fedora 21. + +* Mon Jan 19 2015 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-3 +- Fix OpenSSL version parsing when checking for compatibility at run time. + Resolves: Bug#1173821 +- Don't remove backslash from unknown escape sequences in LDAP values. + Resolves: Bug#1173526 +- Improve dhcpclient and rad_counter online help. + Resolves: Bug#1146966 +- raddb: Move trigger.conf INCLUDE before modules, making it easier to refer + to trigger variables from module configurations. + Resolves: Bug#1155961 +- Fix ipaddr option fallback onto ipv6. + Resolves: Bug#1168868 +- raddb: Comment on ipaddr/ipv4addr/ipv6addr use. + Resolves: Bug#1168247 +- Disable rlm_rest building explicitly to avoid unintended builds on some + architectures breaking RPM build. + Resolves: Bug#1162156 +- Add -D option support to dhcpclient. + Related: Bug#1146939 +- Don't install rbmonkey - a test tool only useful to developers. + Related: Bug#1146966 +- Update clients(5) man page + Resolves: Bug#1147464 +- Fix possible group info corruption/segfault in rlm_unix' fr_getgrnam. +- Fix file configuration item parsing. +- Fix a number of trigger issues. + Resolves: Bug#1110407 radiusd doesn't send snmp trap after "radmin -e 'hup + files'" + Resolves: Bug#1110414 radiusd doesn't send snmp trap when sql connection is + opened,closed or fail + Resolves: Bug#1110186 radiusd doesn't send snmp trap when ldap connection + fails/opens/closes + Resolves: Bug#1109164 snmp trap messages send by radiusd are inconsistent + and incomplete +- Fix two omissions from radtest manpage. + Resolves: Bug#1146898 'eap-md5' value is missing in -t option in SYNOPSIS + of radtest man page + Resolves: Bug#1114669 Missing -P option in radtest manpage +- Disable OpenSSL version checking to avoid the need to edit radiusd.conf to + confirm heartbleed is fixed. + Resolves: Bug#1155070 FreeRADIUS doesn't start after upgrade due to failing + OpenSSL version check + +* Mon Oct 6 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-2 +- Fix abort on home server triggers. +- Fix segfault upon example.pl read failure. +- Fix example.pl permissions. +- Fix integer handling in various cases. +- Fix dhcpclient's dictionary.dhcp loading. + +* Mon Sep 15 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-1 +- Upgrade to upstream 3.0.4 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). +- Resolves: Bug#1099620 + +* Tue Sep 09 2014 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.4-0.2.rc2 +- Perl 5.20 mass + +* Mon Sep 8 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.4-0.1.rc2 +- Upgrade to upstream 3.0.4-rc2 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). + +* Tue Aug 26 2014 Jitka Plesnikova <jplesnik@redhat.com> - 3.0.3-5 +- Perl 5.20 rebuild + +* Sat Aug 16 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.3-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.0.3-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Mon Jun 2 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.3-2 +- Add explicit dependency on OpenSSL package with fixed CVE-2014-0160 + (Heartbleed bug). +- Add confirmation of CVE-2014-0160 being fixed in OpenSSL to radiusd.conf. + +* Wed May 14 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.3-1 +- Upgrade to upstream 3.0.3 release. + See upstream ChangeLog for details (in freeradius-doc subpackage). +- Minor configuration parsing change: "Double-escaping of characters in Perl, + and octal characters has been fixed. If your configuration has text like + "\\000", you will need to remove one backslash." +- Additionally includes post-release fixes for: + * case-insensitive matching in compiled regular expressions not working, + * upstream issue #634 "3.0.3 SIGSEGV on config parse", + * upstream issue #635 "3.0.x - rlm_perl - strings are still + escaped when passed to perl from FreeRADIUS", + * upstream issue #639 "foreach may cause ABORT". +- Fixes bugs 1097266 1070447 + +* Wed May 7 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.2-1 +- Upgrade to upstream 3.0.2 release, configuration compatible with 3.0.1. + See upstream ChangeLog for details (in freeradius-doc subpackage) +- Fixes bugs 1058884 1061408 1070447 1079500 + +* Mon Feb 24 2014 Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> - 3.0.1-4 +- Fix CVE-2014-2015 "freeradius: stack-based buffer overflow flaw in rlm_pap + module" +- resolves: bug#1066984 (fedora 1066763) + +* Fri Feb 21 2014 John Dennis <jdennis@redhat.com> - 3.0.1-3 +- resolves: bug#1068798 (fedora 1068795) + rlm_perl attribute values truncated + +* Sun Jan 19 2014 John Dennis <jdennis@redhat.com> - 3.0.1-2 +- resolves: bug#1055073 (fedora 1055072) + rlm_ippool; bad config file attribute and fails to send reply attributes +- resolves: bug#1055567 (fedora 1056227) + bad mysql sql syntax +- change CFLAGS -imacros to -include to address gcc/gdb bug 1004526 + where gdb will not display source information, only <command-line> + +* Tue Jan 14 2014 John Dennis <jdennis@redhat.com> - 3.0.1-1 +- Upgrade to upstream 3.0.1 release, full config compatible with 3.0.0. + This is a roll-up of all upstream bugs fixes found in 3.0.0 + See upstream ChangeLog for details (in freeradius-doc subpackage) +- fixes bugs 1053020 1044747 1048474 1043036 + +* Tue Nov 26 2013 John Dennis <jdennis@redhat.com> - 3.0.0-4 +- resolves: bug#1031035 + remove radeapclient man page, + upstream no longer supports radeapclient, use eapol_test instead +- resolves: bug#1031061 + rlm_eap_leap memory corruption, see freeradius-rlm_leap.patch +- move man pages for utils into utils subpackage from doc subpackage +- fix HAVE_EC_CRYPTO test to include f20 +- add new directory /var/run/radiusd/tmp + update mods-available/eap so tls-common.verify.tmpdir to point to it + +* Wed Nov 13 2013 John Dennis <jdennis@redhat.com> - 3.0.0-3 +- resolves: bug#1029941 + PW_TYPE_BOOLEAN config item should be declared int, not bool + +* Mon Oct 28 2013 John Dennis <jdennis@redhat.com> - 3.0.0-2 +- resolves: bug#1024119 + tncfhh-devel is now available in RHEL-7, remove conditional BuildRequires + +* Sun Oct 13 2013 John Dennis <jdennis@redhat.com> - 3.0.0-1 +- Offical 3.0 gold release from upstream +- resolves: bug#1016873 +- resolves: bug#891297 + +* Sun Sep 8 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.4.rc1 +- upgrade to second 3.0 release candidate rc1 + +* Mon Aug 26 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.3.rc0 +- add missingok config attribute to /etc/raddb/sites-enabled/* symlinks + +* Sat Aug 03 2013 Petr Pisar <ppisar@redhat.com> - 3.0.0-0.2.rc0 +- Perl 5.18 rebuild + +* Fri Jul 26 2013 Ville Skyttä <ville.skytta@iki.fi> - 3.0.0-0.1.rc0 +- Install docs to %%{_pkgdocdir} where available. + +* Mon Jul 22 2013 John Dennis <jdennis@redhat.com> - 3.0.0-0.rc0 +- Upgrade to new upstream major version release + +* Wed Jul 17 2013 Petr Pisar <ppisar@redhat.com> - 2.2.0-7 +- Perl 5.18 rebuild + +* Wed Feb 13 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.2.0-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Fri Dec 14 2012 John Dennis <jdennis@redhat.com> - 2.2.0-5 +- resolves: bug#850119 - Introduce new systemd-rpm macros (>= F18) + +* Thu Dec 13 2012 John Dennis <jdennis@redhat.com> - 2.2.0-4 +- add compile option -fno-strict-aliasing + +* Thu Dec 13 2012 John Dennis <jdennis@redhat.com> - 2.2.0-3 +- specify homedir (/var/lib/radiusd) for radiusd user in useradd, + do not permit useradd to default the homedir. + +* Wed Dec 12 2012 John Dennis <jdennis@redhat.com> - 2.2.0-2 +- add security options to compiler/linker + +* Mon Dec 10 2012 John Dennis <jdennis@redhat.com> - 2.2.0-1 +- resolves: bug#876564 - fails to start without freeradius-mysql +- use upstream version of freeradius-exclude-config-file.patch + +* Wed Oct 3 2012 John Dennis <jdennis@redhat.com> - 2.2.0-0 +- Add new patch to avoid reading .rpmnew, .rpmsave and other invalid + files when loading config files +- Upgrade to new 2.2.0 upstream release +- Upstream changelog for 2.1.12: + Feature improvements + * 100% configuration file compatible with 2.1.x. + The only fix needed is to disallow "hashsize=0" for rlm_passwd + * Update Aruba, Alcatel Lucent, APC, BT, PaloAlto, Pureware, + Redback, and Mikrotik dictionaries + * Switch to using SHA1 for certificate digests instead of MD5. + See raddb/certs/*.cnf + * Added copyright statements to the dictionaries, so that we know + when people are using them. + * Better documentation for radrelay and detail file writer. + See raddb/modules/radrelay and raddb/radrelay.conf + * Added TLS-Cert-Subject-Alt-Name-Email from patch by Luke Howard + * Added -F <file> to radwho + * Added query timeouts to MySQL driver. Patch from Brian De Wolf. + * Add /etc/default/freeradius to debian package. + Patch from Matthew Newton + * Finalize DHCP and DHCP relay code. It should now work everywhere. + See raddb/sites-available/dhcp, src_ipaddr and src_interface. + * DHCP capabilitiies are now compiled in by default. + It runs as a DHCP server ONLY when manually enabled. + * Added one letter expansions: %%G - request minute and %%I request + ID. + * Added script to convert ISC DHCP lease files to SQL pools. + See scripts/isc2ippool.pl + * Added rlm_cache to cache arbitrary attributes. + * Added max_use to rlm_ldap to force connection to be re-established + after a given number of queries. + * Added configtest option to Debian init scripts, and automatic + config test on restart. + * Added cache config item to rlm_krb5. When set to "no" ticket + caching is disabled which may increase performance. + + Bug fixes + * Fix CVE-2012-3547. All users of 2.1.10, 2.1.11, 2.1.12, + and 802.1X should upgrade immediately. + * Fix typo in detail file writer, to skip writing if the packet + was read from this detail file. + * Free cached replies when closing resumed SSL sessions. + * Fix a number of issues found by Coverity. + * Fix memory leak and race condition in the EAP-TLS session cache. + Thanks to Phil Mayers for tracking down OpenSSL APIs. + * Restrict ATTRIBUTE names to character sets that make sense. + * Fix EAP-TLS session Id length so that OpenSSL doesn't get + excited. + * Fix SQL IPPool logic for non-timer attributes. Closes bug #181 + * Change some informational messages to DEBUG rather than error. + * Portability fixes for FreeBSD. Closes bug #177 + * A much better fix for the _lt__PROGRAM__LTX_preloaded_symbols + nonsense. + * Safely handle extremely long lines in conf file variable expansion + * Fix for Debian bug #606450 + * Mutex lock around rlm_perl Clone routines. Patch from Eike Dehling + * The passwd module no longer permits "hashsize = 0". Setting that + is pointless for a host of reasons. It will also break the server. + * Fix proxied inner-tunnel packets sometimes having zero authentication + vector. Found by Brian Julin. + * Added $(EXEEXT) to Makefiles for portability. Closes bug #188. + * Fix minor build issue which would cause rlm_eap to be built twice. + * When using "status_check=request" for a home server, the username + and password must be specified, or the server will not start. + * EAP-SIM now calculates keys from the SIM identity, not from the + EAP-Identity. Changing the EAP type via NAK may result in + identities changing. Bug reported by Microsoft EAP team. + * Use home server src_ipaddr when sending Status-Server packets + * Decrypt encrypted ERX attributes in CoA packets. + * Fix registration of internal xlat's so %%{mschap:...} doesn't + disappear after a HUP. + * Can now reference tagged attributes in expansions. + e.g. %%{Tunnel-Type:1} and %%{Tunnel-Type:1[0]} now work. + * Correct calculation of Message-Authenticator for CoA and Disconnect + replies. Patch from Jouni Malinen + * Install rad_counter, for managing rlm_counter files. + * Add unique index constraint to all SQL flavours so that alternate + queries work correctly. + * The TTLS diameter decoder is now more lenient. It ignores + unknown attributes, instead of rejecting the TTLS session. + * Use "globfree" in detail file reader. Prevents very slow leak. + Closes bug #207. + * Operator =~ shouldn't copy the attribute, like :=. It should + instead behave more like ==. + * Build main Debian package without SQL dependencies + * Use max_queue_size in threading code + * Update permissions in raddb/sql/postgresql/admin.sql + * Added OpenSSL_add_all_algorithms() to fix issues where OpenSSL + wouldn't use methods it knew about. + * Add more sanity checks in dynamic_clients code so the server won't + crash if it attempts to load a badly formated client definition. + +* Thu Jul 19 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.12-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Fri Jun 08 2012 Petr Pisar <ppisar@redhat.com> - 2.1.12-9 +- Perl 5.16 rebuild + +* Tue May 15 2012 John Dennis <jdennis@redhat.com> - 2.1.12-8 +- resolves: bug#821407 - openssl dependency + +* Sat Apr 14 2012 John Dennis <jdennis@redhat.com> - 2.1.12-7 +- resolves: bug#810605 Segfault with freeradius-perl threading + +* Tue Feb 28 2012 John Dennis <jdennis@redhat.com> - 2.1.12-6 + Fixing bugs in RHEL6 rebase, applying fixes here as well + resolves: bug#700870 freeradius not compiled with --with-udpfromto + resolves: bug#753764 shadow password expiration does not work + resolves: bug#712803 radtest script is not working with eap-md5 option + resolves: bug#690756 errors in raddb/sql/postgresql/admin.sql template + +* Tue Feb 7 2012 John Dennis <jdennis@redhat.com> - 2.1.12-5 +- resolves: bug#781877 (from RHEL5) rlm_dbm_parse man page misspelled +- resolves: bug#760193 (from RHEL5) radtest PPPhint option is not parsed properly + +* Sun Jan 15 2012 John Dennis <jdennis@redhat.com> - 2.1.12-4 +- resolves: bug#781744 + systemd service file incorrectly listed pid file as + /var/run/radiusd/radiusd which it should have been + /var/run/radiusd/radiusd.pid + +* Fri Jan 13 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.12-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Mon Oct 31 2011 John Dennis <jdennis@redhat.com> - 2.1.12-2 +- rename /etc/tmpfiles.d/freeradius.conf to /etc/tmpfiles.d/radiusd.conf + remove config(noreplace) because it must match files section and + permissions differ between versions. +- fixup macro usage for /var/run & /var/lib + +* Mon Oct 3 2011 John Dennis <jdennis@redhat.com> - 2.1.12-1 +- Upgrade to latest upstream release: 2.1.12 +- Upstream changelog for 2.1.12: + Feature improvements + * Updates to dictionary.erx, dictionary.siemens, dictionary.starent, + dictionary.starent.vsa1, dictionary.zyxel, added dictionary.symbol + * Added support for PCRE from Phil Mayers + * Configurable file permission in rlm_linelog + * Added "relaxed" option to rlm_attr_filter. This copies attributes + if at least one match occurred. + * Added documentation on dynamic clients. + See raddb/modules/dynamic_clients. + * Added support for elliptical curve cryptography. + See ecdh_curve in raddb/eap.conf. + * Added support for 802.1X MIBs in checkrad + * Added support for %%{rand:...}, which generates a uniformly + distributed number between 0 and the number you specify. + * Created "man" pages for all installed commands, and documented + options for all commands. Patch from John Dennis. + * Allow radsniff to decode encrypted VSAs and CoA packets. + Patch from Bjorn Mork. + * Always send Message-Authenticator in radtest. Patch from John Dennis. + radclient continues to be more flexible. + * Updated Oracle schema and queries + * Added SecurID module. See src/modules/rlm_securid/README + + Bug fixes + * Fix memory leak in rlm_detail + * Fix "failed to insert event" + * Allow virtual servers to be reloaded on HUP. + It no longer complains about duplicate virtual servers. + * Fix %%{string:...} expansion + * Fix "server closed socket" loop in radmin + * Set ownership of control socket when starting up + * Always allow root to connect to control socket, even if + "uid" is set. They're root. They can already do anything. + * Save all attributes in Access-Accept when proxying inner-tunnel + EAP-MSCHAPv2 + * Fixes for DHCP relaying. + * Check certificate validity when using OCSP. + * Updated Oracle "configure" script + * Fixed typos in dictionary.alvarion + * WARNING on potential proxy loop. + * Be more aggressive about clearing old requests from the + internal queue + * Don't open network sockets when using -C + +* Wed Sep 21 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-7 +- restore defattr customization in the main package + +* Fri Sep 9 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-6 +- add missing systemd scriptlets + +* Thu Sep 8 2011 Tom Callaway <spot@fedoraproject.org> - 2.1.11-5 +- convert to systemd + +* Thu Jul 21 2011 Petr Sabata <contyk@redhat.com> - 2.1.11-4 +- Perl mass rebuild + +* Wed Jul 20 2011 Petr Sabata <contyk@redhat.com> - 2.1.11-3 +- Perl mass rebuild + +* Thu Jun 23 2011 John Dennis <jdennis@redhat.com> - 2.1.11-2 +- reload the server (i.e. HUP) after logrotate + +* Wed Jun 22 2011 John Dennis <jdennis@redhat.com> - 2.1.11-1 +- Upgrade to latest upstream release: 2.1.11 +- Remove the following two patches as upstream has incorporated them: + freeradius-radtest-ipv6.patch + freeradius-lt-dladvise.patch +- Upstream changelog for 2.1.11: + Feature improvements + * Added doc/rfc/rfc6158.txt: RADIUS Design Guidelines. + All vendors need to read it and follow its directions. + * Microsoft SoH support for PEAP from Phil Mayers. + See doc/SoH.txt + * Certificate "bootstrap" script now checks for certificate expiry. + See comments in raddb/eap.conf, and then "make_cert_command". + * Support for dynamic expansion of EAP-GTC challenges. + Patch from Alexander Clouter. + * OCSP support from Alex Bergmann. See raddb/eap.conf, "ocsp" + section. + * Updated dictionary.huawei, dictionary.3gpp, dictionary.3gpp3. + * Added dictionary.eltex, dictionary.motorola, and dictionary.ukerna. + * Experimental redis support from Gabriel Blanchard. + See raddb/modules/redis and raddb/modules/rediswho + * Add "key" to rlm_fastusers. Closes bug #126. + * Added scripts/radtee from original software at + http://horde.net/~jwm/software/misc/comparison-tee + * Updated radmin "man" page for new commands. + * radsniff now prints the hex decoding of the packet (-x -x -x) + * mschap module now reloads its configuration on HUP + * Added experimental "replicate" module. See raddb/modules/replicate + * Policy "foo" can now refer to module "foo". This lets you + over-ride the behavior of a module. + * Policy "foo.authorize" can now over-ride the behavior of module + "foo", "authorize" method. + * Produce errors in more situations when the configuration files + have invalid syntax. + + Bug fixes + * Ignore pre/post-proxy sections if proxying is disabled + * Add configure checks for pcap_fopen*. + * Fix call to otp_write in rlm_otp + * Fix issue with Access-Challenge checking from 2.1.10, when the + debug flag was set after server startup. Closes #116 and #117. + * Fix typo in zombie period start time. + * Fix leak in src/main/valuepair.c. Patch from James Ballantine. + * Allow radtest to use spaces in shared secret. + Patch from Cedric Carree. + * Remove extra calls to HMAC_CTX_init() in rlm_wimax, fixing leak. + Patch from James Ballantine. + * Remove MN-FA key generation. The NAS does this, not AAA. + Patch from Ben Weichman. + * Include dictionary.mikrotik by default. Closes bug #121. + * Add group membership query to MS-SQL examples. Closes bug #120. + * Don't cast NAS-Port to integer in Postgresql queries. + Closes bug #112. + * Fixes for libtool and autoconf from Sam Hartman. + * radsniff should read the dictionaries in more situations. + * Use fnmatch to check for detail file reader==writer. + Closes bug #128. + * Check for short writes (i.e. disk full) in rlm_detail. + Closes bug #130. Patches and testing from John Morrissey. + * Fix typo in src/lib/token.c. Closes bug #124 + * Allow workstation trust accounts to use MS-CHAP. + Closes bug #123. + * Assigning foo=`/bin/echo hello` now produces a syntax error + if it is done outside of an "update" section. + * Fix "too many open file descriptors" problem when using + "verify client" in eap.conf. + * Many fixes to dialup_admin for PHP5, by Stefan Winter. + * Allow preprocess module to have "hints = " and "huntgroups =", + which allows them to be empty or non-existent. + * Renamed "php3" files to "php" in dialup_admin/ + * Produce error when sub-TLVs are used in a dictionary. They are + supported only in the "master" branch, and not in 2.1.x. + * Minor fix in dictionary.redback. Closes bug #138. + * Fixed MySQL "NULL" issues in ippool.conf. Closes bug #129. + * Fix to Access-Challenge warning from Ken-ichirou Matsuzawa. + Closes bug #118. + * DHCP fixes to send unicast packets in more situations. + * Fix to udpfromto, to enable it to work on IPv6 networks. + * Fixes to the Oracle accounting_onoff_query. + * When using both IPv4 and IPv6 home servers, ensure that we use the + correct local socket for proxying. Closes bug #143. + * Suppress messages when thread pool is nearly full, all threads + are busy, and we can't create new threads. + * IPv6 is now enabled for udpfromto. Closes bug #141 + * Make sqlippool query buffer the same size as sql module. + Closes bug #139. + * Make Coa / Disconnect proxying work again. + * Configure scripts for rlm_caching from Nathaniel McCallum + * src/lib/dhcp.c and src/include/libradius.h are LGPL, not GPL. + * Updated password routines to use time-insensitive comparisons. + This prevents timing attacks (though none are known). + * Allow sqlite module to do normal SELECT queries. + * rlm_wimax now has a configure script + * Moved Ascend, USR, and Motorola "illegal" dictionaries to separate + files. See share/dictionary for explanations. + * Check for duplicate module definitions in the modules{} section, + and refuse to start if duplicates are found. + * Check for duplicate virtual servers, and refuse to start if + duplicates are found. + * Don't use udpfromto if source is INADDR_ANY. Closes bug #148. + * Check pre-conditions before running radmin "inject file". + * Don't over-ride "no match" with "match" for regexes. + Closes bug #152. + * Make retry and error message configurable in mschap. + See raddb/modules/mschap + * Allow EAP-MSCHAPv2 to send error message to client. This change + allows some clients to prompt the user for a new password. + See raddb/eap.conf, mschapv2 section, "send_error". + * Load the default virtual server before any others. + This matches what users expect, and reduces confusion. + * Fix configure checks for udpfromto. Fixes Debian bug #606866 + * Definitive fix for bug #35, where the server could crash under + certain loads. Changes src/lib/packet.c to use RB trees. + * Updated "configure" checks to allow IPv6 udpfromto on Linux. + * SQL module now returns NOOP if the accounting start/interim/stop + queries don't do anything. + * Allow %%{outer.control: ... } in string expansions + * home_server coa config now matches raddb/proxy.conf + * Never send a reply to a DHCP Release. + +* Thu Jun 16 2011 Marcela Mašláňová <mmaslano@redhat.com> - 2.1.10-8 +- Perl mass rebuild + +* Wed Mar 23 2011 John Dennis <jdennis@redhat.com> - 2.1.10-7 +- Resolves: #689045 Using rlm_perl cause radiusd failed to start + Fix configure typo which caused lt_dladvise_* functions to be skipped. + run autogen.sh because HAVE_LT_DLADVISE_INIT isn't in src/main/autogen.h + Implemented by: freeradius-lt-dladvise.patch + +* Wed Mar 23 2011 John Dennis <jdennis@redhat.com> - 2.1.10-6 +- Resolves: #599528 - make radtest IPv6 compatible + +* Wed Mar 23 2011 Dan Horák <dan@danny.cz> - 2.1.10-5 +- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) + +* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.10-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Sat Jan 1 2011 John Dennis <jdennis@redhat.com> - 2.1.10-3 +- bug 666589 - removing freeradius from system does not delete the user "radiusd" + fix scriptlet argument testing, simplify always exiting with zero + +* Thu Dec 30 2010 John Dennis <jdennis@redhat.com> - 2.1.10-2 +- rebuild for new MySQL libs + +* Tue Oct 19 2010 John Dennis <jdennis@redhat.com> - 2.1.10-1 + Feature improvements + * Install the "radcrypt" program. + * Enable radclient to send requests containing MS-CHAPv1 + Send packets with: MS-CHAP-Password = "password". It will + be automatically converted to the correct MS-CHAP attributes. + * Added "-t" command-line option to radtest. You can use "-t pap", + "-t chap", "-t mschap", or "-t eap-md5". The default is "-t pap" + * Make the "inner-tunnel" virtual server listen on 127.0.0.1:18120 + This change and the previous one makes PEAP testing much easier. + * Added more documentation and examples for the "passwd" module. + * Added dictionaries for RFC 5607 and RFC 5904. + * Added note in proxy.conf that we recommend setting + "require_message_authenticator = yes" for all home servers. + * Added example of second "files" configuration, with documentation. + This shows how and where to use two instances of a module. + * Updated radsniff to have it write pcap files, too. See '-w'. + * Print out large WARNING message if we send an Access-Challenge + for EAP, and receive no follow-up messages from the client. + * Added Cached-Session-Policy for EAP session resumption. See + raddb/eap.conf. + * Added support for TLS-Cert-* attributes. For details, see + raddb/sites-available/default, "post-auth" section. + * Added sample raddb/modules/{opendirectory,dynamic_clients} + * Updated Cisco and Huawei, HP, Redback, and ERX dictionaries. + * Added RFCs 5607, 5904, and 5997. + * For EAP-TLS, client certificates can now be validated using an + external command. See eap.conf, "validate" subsection of "tls". + * Made rlm_pap aware of {nthash} prefix, for compatibility with + legacy RADIUS systems. + * Add Module-Failure-Message for mschap module (ntlm_auth) + * made rlm_sql_sqlite database configurable. Use "filename" + in sql{} section. + * Added %%{tolower: ...string ... }, which returns the lowercase + version of the string. Also added %%{toupper: ... } for uppercase. + + Bug fixes + * Fix endless loop when there are multiple sub-options for + DHCP option 82. + * More debug output when sending / receiving DHCP packets. + * EAP-MSCHAPv2 should return the MPPE keys when used outside + of a TLS tunnel. This is needed for IKE. + * Added SSL "no ticket" option to prevent SSL from creating sessions + without IDs. We need the IDs, so this option should be set. + * Fix proxying of packets from inside a TTLS/PEAP tunnel. + Closes bug #25. + * Allow IPv6 address attributes to be created from domain names + Closes bug #82. + * Set the string length to the correct value when parsing double + quotes. Closes bug #88. + * No longer look users up in /etc/passwd in the default configuration. + This can be reverted by enabling "unix" in the "authorize" section. + * More #ifdef's to enable building on systems without certain + features. + * Fixed SQL-Group comparison to register only if the group + query is defined. + * Fixed SQL-Group comparison to register <instance>-SQL-Group, + just like rlm_ldap. This lets you have multiple SQL group checks. + * Fix scanning of octal numbers in "unlang". Closes bug #89. + * Be less aggressive about freeing "stuck" requests. Closes bug #35. + * Fix example in "originate-coa" to refer to the correct packet. + * Change default timeout for dynamic clients to 1 hour, not 1 day. + * Allow passwd module to map IP addresses, too. + * Allow passwd module to be used for CoA packets + * Put boot filename into DHCP header when DHCP-Boot-Filename + is specified. + * raddb/certs/Makefile no longer has certs depend on index.txt and + serial. Closes bug #64. + * Ignore NULL errorcode in PostgreSQL client. Closes bug #39 + * Made Exec-Program and Exec-Program-Wait work in accounting + section again. See sites-available/default. + * Fix long-standing memory leak in esoteric conditions. Found + by Jerry Nichols. + * Added "Password-With-Header == userPassword" to raddb/ldap.attrmap + This will automatically convert more passwords. + * Updated rlm_pap to decode Password-With-Header, if it was base64 + encoded, and to treat the contents as potentially binary data. + * Fix Novell eDir code to use the right function parameters. + Closes bug #86. + * Allow spaces to be escaped when executing external programs. + Closes bug #93. + * Be less restrictive about checking permissions on control socket. + If we're root, allow connecting to a non-root socket. + * Remove control socket on normal server exit. If the server isn't + running, the control socket should not exist. + * Use MS-CHAP-User-Name as Name field from EAP-MSCHAPv2 for MS-CHAP + calculations. It *MAY* be different (upper / lower case) from + the User-Name attribute. Closes bug #17. + * If the EAP-TLS methods have problems, more SSL errors are now + available in the Module-Failure-Message attribute. + * Update Oracle configure scripts. Closes bug #57. + * Added text to DESC fields of doc/examples/openldap.schema + * Updated more documentation to use "Restructured Text" format. + Thanks to James Lockie. + * Fixed typos in raddb/sql/mssql/dialup.conf. Closes bug #11. + * Return error for potential proxy loops when using "-XC" + * Produce better error messages when slow databases block + the server. + * Added notes on DHCP broadcast packets for FreeBSD. + * Fixed crash when parsing some date strings. Closes bug #98 + * Improperly formatted Attributes are now printed as "Attr-##". + If they are not correct, they should not use the dictionary name. + * Fix rlm_digest to be check the format of the Digest attributes, + and return "noop" rather than "fail" if they're not right. + * Enable "digest" in raddb/sites-available/default. This change + enables digest authentication to work "out of the box". + * Be less aggressive about marking home servers as zombie. + If they are responding to some packets, they are still alive. + * Added Packet-Transmit-Counter, to track detail file retransmits. + Closes bug #13. + * Added configure check for lt_dladvise_init(). If it exists, then + using it solves some issues related to libraries loading libraries. + * Added indexes to the MySQL IP Pool schema. + * Print WARNING message if too many attributes are put into a packet. + * Include dhcp test client (not built by default) + * Added checks for LDAP constraint violation. Closes bug #18. + * Change default raddebug timeout to 60 seconds. + * Made error / warning messages more consistent. + * Correct back-slash handling in variable expansion. Closes bug #46. + You SHOULD check your configuration for backslash expansion! + * Fix typo in "configure" script (--enable-libltdl-install) + * Use local libltdl in more situations. This helps to avoid + compile issues complaining about lt__PROGRAM__LTX_preloaded_symbols. + * Fix hang on startup when multiple home servers were defined + with "src_ipaddr" field. + * Fix 32/64 bit issue in rlm_ldap. Closes bug #105. + * If the first "listen" section defines 127.0.0.1, don't use that + as a source IP for proxying. It won't work. + * When Proxy-To-Realm is set to a non-existent realm, the EAP module + should handle the request, rather than expecting it to be proxied. + * Fix IPv4 issues with udpfromto. Closes bug #110. + * Clean up child processes of raddebug. Closes bugs #108 and #109 + * retry OTP if the OTP daemon fails. Closes bug #58. + * Multiple calls to ber_printf seem to work better. Closes #106. + * Fix "unlang" so that "attribute not found" is treated as a "false" + comparison, rather than a syntax error in the configuration. + * Fix issue with "Group" attribute. + +* Sat Jul 31 2010 Orcan Ogetbil <oget[dot]fedora[at]gmail[dot]com> - 2.1.9-3 +- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild + +* Tue Jun 01 2010 Marcela Maslanova <mmaslano@redhat.com> - 2.1.9-2 +- Mass rebuild with perl-5.12.0 + +* Mon May 24 2010 John Dennis <jdennis@redhat.com> - 2.1.9-1 +- update to latest upstream, mainly bug fix release + Feature improvements + * Add radmin command "stats detail <file>" to see what + is going on inside of a detail file reader. + * Added documentation for CoA. See raddb/sites-available/coa + * Add sub-option support for Option 82. See dictionary.dhcp + * Add "server" field to default SQL NAS table, and documented it. + + Bug fixes + * Reset "received ping" counter for Status-Server checks. In some + corner cases it was not getting reset. + * Handle large VMPS attributes. + * Count accounting responses from a home server in SNMP / statistics + code. + * Set EAP-Session-Resumed = Yes, not "No" when session is resumed. + * radmin packet counter statistics are now unsigned, for numbers + 2^31..2^32. After that they roll over to zero. + * Be more careful about expanding data in PAP and MS-CHAP modules. + This prevents login failures when passwords contain '{'. + * Clean up zombie children if there were many "exec" modules being + run for one packet, all with "wait = no". + * re-open log file after HUP. Closes bug #63. + * Fix "no response to proxied packet" complaint for Coa / Disconnect + packets. It shouldn't ignore replies to packets it sent. + * Calculate IPv6 netmasks correctly. Closes bug #69. + * Fix SQL module to re-open sockets if they unexpectedly close. + * Track scope for IPv6 addresses. This lets us use link-local + addresses properly. Closes bug #70. + * Updated Makefiles to no longer use the shell for recursing into + subdirs. "make -j 2" should now work. + * Updated raddb/sql/mysql/ippool.conf to use "= NULL". Closes + bug #75. + * Updated Makefiles so that "make reconfig" no longer uses the shell + for recursing into subdirs, and re-builds all "configure" files. + * Used above method to regenerate all configure scripts. + Closes bug #34. + * Updated SQL module to allow "server" field of "nas" table + to be blank: "". This means the same as it being NULL. + * Fixed regex realm example. Create Realm attribute with value + of realm from User-Name, not from regex. Closes bug #40. + * If processing a DHCP Discover returns "fail / reject", ignore + the packet rather than sending a NAK. + * Allow '%%' to be escaped in sqlcounter module. + * Fix typo internal hash table. + * For PEAP and TTLS, the tunneled reply is added to the reply, + rather than integrated via the operators. This allows multiple + VSAs to be added, where they would previously be discarded. + * Make request number unsigned. This changes nothing other than + the debug output when the server receives more than 2^31 packets. + * Don't block when reading child output in 'exec wait'. This means + that blocked children get killed, instead of blocking the server. + * Enabled building without any proxy functionality + * radclient now prefers IPv4, to match the default server config. + * Print useful error when a realm regex is invalid + * relaxed rules for preprocess module "with_cisco_vsa_hack". The + attributes can now be integer, ipaddr, etc. (i.e. non-string) + * Allow rlm_ldap to build if ldap_set_rebind_proc() has only + 2 arguments. + * Update configure script for rlm_python to avoid dynamic linking + problems on some platforms. + * Work-around for bug #35 + * Do suid to "user" when running in debug mode as root + * Make "allow_core_dumps" work in more situations. + * In detail file reader, treat bad records as EOF. + This allows it to continue working when the disk is full. + * Fix Oracle default accounting queries to work when there are no + gigawords attributes. Other databases already had the fix. + * Fix rlm_sql to show when it opens and closes sockets. It already + says when it cannot connect, so it should say when it can connect. + * "chmod -x" for a few C source files. + * Pull update spec files, etc. from RedHat into the redhat/ directory. + * Allow spaces when parsing integer values. This helps people who + put "too much" into an SQL value field. + +* Thu Jan 7 2010 John Dennis <jdennis@redhat.com> - 2.1.8-2 +- resolves: bug #526559 initial install should run bootstrap to create certificates + running radiusd in debug mode to generate inital temporary certificates + is no longer necessary, the /etc/raddb/certs/bootstrap is invoked on initial + rpm install (not upgrade) if there is no existing /etc/raddb/certs/server.pem file +- resolves: bug #528493 use sha1 algorithm instead of md5 during cert generation + the certificate configuration (/etc/raddb/certs/{ca,server,client}.cnf) files + were modifed to use sha1 instead of md5 and the validity reduced from 1 year to 2 months + +* Wed Dec 30 2009 John Dennis <jdennis@redhat.com> - 2.1.8-1 +- update to latest upstream + Feature improvements + * Print more descriptive error message for too many EAP sessions. + This gives hints on what to do when "failed to store handler" + * Commands received from radmin are now printed on stdout when + in debugging mode. + * Allow accounting packets to be written to a detail file, even + if they were read from a different detail file. + * Added OpenSSL license exception (src/LICENSE.openssl) + + Bug fixes + * DHCP sockets can now set the broadcast flag before binding to a + socket. You need to set "broadcast = yes" in the DHCP listener. + * Be more restrictive on string parsing in the config files + * Fix password length in scripts/create-users.pl + * Be more flexible about parsing the detail file. This allows + it to read files where the attributes have been edited. + * Ensure that requests read from the detail file are cleaned up + (i.e. don't leak) if they are proxied without a response. + * Write the PID file after opening sockets, not before + (closes bug #29) + * Proxying large numbers of packets no longer gives error + "unable to open proxy socket". + * Avoid mutex locks in libc after fork + * Retry packet from detail file if there was no response. + * Allow old-style dictionary formats, where the vendor name is the + last field in an ATTRIBUTE definition. + * Removed all recursive use of mutexes. Some systems just don't + support this. + * Allow !* to work as documented. + * make templates work (see templates.conf) + * Enabled "allow_core_dumps" to work again + * Print better errors when reading invalid dictionaries + * Sign client certificates with CA, rather than server certs. + * Fix potential crash in rlm_passwd when file was closed + * Fixed corner cases in conditional dynamic expansion. + * Use InnoDB for MySQL IP Pools, to gain transactional support + * Apply patch to libltdl for CVE-2009-3736. + * Fixed a few issues found by LLVM's static checker + * Keep track of "bad authenticators" for accounting packets + * Keep track of "dropped packets" for auth/acct packets + * Synced the "debian" directory with upstream + * Made "unlang" use unsigned 32-bit integers, to match the + dictionaries. + +* Wed Dec 30 2009 John Dennis <jdennis@redhat.com> - 2.1.7-7 +- Remove devel subpackage. It doesn't make much sense to have a devel package since + we don't ship libraries and it produces multilib conflicts. + +* Mon Dec 21 2009 John Dennis <jdennis@redhat.com> - 2.1.7-6 +- more spec file clean up from review comments +- remove freeradius-libs subpackage, move libfreeradius-eap and + libfreeradius-radius into the main package +- fix subpackage requires, change from freeradius-libs to main package +- fix description of the devel subpackage, remove referene to non-shipped libs +- remove execute permissions on src files included in debuginfo +- remove unnecessary use of ldconfig +- since all sub-packages now require main package remove user creation for sub-packages +- also include the LGPL library license file in addition to the GPL license file +- fix BuildRequires for perl so it's compatible with both Fedora, RHEL5 and RHEL6 + +* Mon Dec 21 2009 John Dennis <jdennis@redhat.com> - 2.1.7-5 +- fix various rpmlint issues. + +* Fri Dec 4 2009 Stepan Kasal <skasal@redhat.com> - 2.1.7-4 +- rebuild against perl 5.10.1 + +* Thu Dec 3 2009 John Dennis <jdennis@redhat.com> - 2.1.7-3 +- resolves: bug #522111 non-conformant initscript + also change permission of /var/run/radiusd from 0700 to 0755 + so that "service radiusd status" can be run as non-root + +* Wed Sep 16 2009 Tomas Mraz <tmraz@redhat.com> - 2.1.7-2 +- use password-auth common PAM configuration instead of system-auth + +* Tue Sep 15 2009 John Dennis <jdennis@redhat.com> - 2.1.7-1 +- enable building of the rlm_wimax module +- pcap wire analysis support is enabled and available in utils subpackage +- Resolves bug #523053 radtest manpage in wrong package +- update to latest upstream release, from upstream Changelog: + Feature improvements + * Full support for CoA and Disconnect packets as per RFC 3576 + and RFC 5176. Both receiving and proxying CoA is supported. + * Added "src_ipaddr" configuration to "home_server". See + proxy.conf for details. + * radsniff now accepts -I, to read from a filename instead of + a device. + * radsniff also prints matching requests and any responses to those + requests when '-r' is used. + * Added example of attr_filter for Access-Challenge packets + * Added support for udpfromto in DHCP code + * radmin can now selectively mark modules alive/dead. + See "set module state". + * Added customizable messages on login success/fail. + See msg_goodpass && msg_badpass in log{} section of radiusd.conf + * Document "chase_referrals" and "rebind" in raddb/modules/ldap + * Preliminary implementation of DHCP relay. + * Made thread pool section optional. If it doesn't exist, + the server will run single-threaded. + * Added sample radrelay.conf for people upgrading from 1.x + * Made proxying more stable by failing over, rather than + rejecting the first request. See "response_window" in proxy.conf + * Allow home_server_pools to exist without realms. + * Add dictionary.iea (closes bug #7) + * Added support for RFC 5580 + * Added experimental sql_freetds module from Gabriel Blanchard. + * Updated dictionary.foundry + * Added sample configuration for MySQL cluster in raddb/sql/ndb + See the README file for explanations. + Bug fixes + * Fixed corner case where proxied packets could have extra + character in User-Password attribute. Fix from Niko Tyni. + * Extended size of "attribute" field in SQL to 64. + * Fixes to ruby module to be more careful about when it builds. + * Updated Perl module "configure" script to check for broken + Perl installations. + * Fix "status_check = none". It would still send packets + in some cases. + * Set recursive flag on the proxy mutex, which enables safer + cleanup on some platforms. + * Copy the EAP username verbatim, rather than escaping it. + * Update handling so that robust-proxy-accounting works when + all home servers are down for extended periods of time. + * Look for DHCP option 53 anywhere in the packet, not just + at the start. + * Fix processing of proxy fail handler with virtual servers. + * DHCP code now prints out correct src/dst IP addresses + when sending packets. + * Removed requirement for DHCP to have clients + * Fixed handling of DHCP packets with message-type buried in the packet + * Fixed corner case with negation in unlang. + * Minor fixes to default MySQL & PostgreSQL schemas + * Suppress MSCHAP complaints in debugging mode. + * Fix SQL module for multiple instance, and possible crash on HUP + * Fix permissions for radius.log for sites that change user/group, + but which don't create the file before starting radiusd. + * Fix double counting of packets when proxying + * Make %%l work + * Fix pthread keys in rlm_perl + * Log reasons for EAP failure (closes bug #8) + * Load home servers and pools that aren't referenced from a realm. + * Handle return codes from virtual attributes in "unlang" + (e.g. LDAP-Group). This makes "!(expr)" work for them. + * Enable VMPS to see contents of virtual server again + * Fix WiMAX module to be consistent with examples. (closes bug #10) + * Fixed crash with policies dependent on NAS-Port comparisons + * Allowed vendor IDs to be be higher than 32767. + * Fix crash on startup with certain regexes in "hints" file. + * Fix crash in attr_filter module when packets don't exist + * Allow detail file reader to be faster when "load_factor = 100" + * Add work-around for build failures with errors related to + lt__PROGRAM__LTX_preloaded_symbols. libltdl / libtool are horrible. + * Made ldap module "rebind" option aware of older, incompatible + versions of OpenLDAP. + * Check value of Fall-Through in attr_filter module. + +* Fri Aug 21 2009 Tomas Mraz <tmraz@redhat.com> - 2.1.6-6 +- rebuilt with new openssl + +* Fri Jul 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.6-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Fri Jul 10 2009 John Dennis <jdennis@redhat.com> - 2.1.6-4 +- install COPYRIGHT CREDITS INSTALL LICENSE README into docdir + +* Tue Jun 23 2009 John Dennis <jdennis@redhat.com> - 2.1.6-3 +- resolves bug #507571 freeradius packages do not check for user/group existence + +* Tue Jun 2 2009 John Dennis <jdennis@redhat.com> - 2.1.6-2 +- make /etc/raddb/sites-available/* be config(noreplace) + +* Mon May 18 2009 John Dennis <jdennis@redhat.com> - 2.1.6-1 +- update to latest upstream release, from upstream Changelog: + Feature improvements + * radclient exits with 0 on successful (accept / ack), and 1 + otherwise (no response / reject) + * Added support for %%{sql:UPDATE ..}, and insert/delete + Patch from Arran Cudbard-Bell + * Added sample "do not respond" policy. See raddb/policy.conf + and raddb/sites-available/do_not_respond + * Cleanups to Suse spec file from Norbert Wegener + * New VSAs for Juniper from Bjorn Mork + * Include more RFC dictionaries in the default install + * More documentation for the WiMAX module + * Added "chase_referrals" and "rebind" configuration to rlm_ldap. + This helps with Active Directory. See raddb/modules/ldap + * Don't load pre/post-proxy if proxying is disabled. + * Added %%{md5:...}, which returns MD5 hash in hex. + * Added configurable "retry_interval" and "poll_interval" + for "detail" listeners. + * Added "delete_mppe_keys" configuration option to rlm_wimax. + Apparently some WiMAX clients misbehave when they see those keys. + * Added experimental rlm_ruby from + http://github.com/Antti/freeradius-server/tree/master + * Add Tunnel attributes to ldap.attrmap + * Enable virtual servers to be reloaded on HUP. For now, only + the "authorize", "authenticate", etc. processing sections are + reloaded. Clients and "listen" sections are NOT reloaded. + * Updated "radwatch" script to be more robust. See scripts/radwatch + * Added certificate compatibility notes in raddb/certs/README, + for compatibility with different operating systems. (i.e. Windows) + * Permit multiple "-e" in radmin. + * Add support for originating CoA-Request and Disconnect-Request. + See raddb/sites-available/originate-coa. + * Added "lifetime" and "max_queries" to raddb/sql.conf. + This helps address the problem of hung SQL sockets. + * Allow packets to be injected via radmin. See "inject help" + in radmin. + * Answer VMPS reconfirmation request. Patch from Hermann Lauer. + * Sample logrotate script in scripts/logrotate.freeradius + * Add configurable poll interval for "detail" listeners + * New "raddebug" command. This prints debugging information from + a running server. See "man raddebug. + * Add "require_message_authenticator" configuration to home_server + configuration. This makes the server add Message-Authenticator + to all outgoing Access-Request packets. + * Added smsotp module, as contributed by Siemens. + * Enabled the administration socket in the default install. + See raddb/sites-available/control-socket, and "man radmin" + * Handle duplicate clients, such as with replicated or + load-balanced SQL servers and "readclients = yes" + Bug fixes + * Minor changes to allow building without VQP. + * Minor fixes from John Center + * Fixed raddebug example + * Don't crash when deleting attributes via unlang + * Be friendlier to very fast clients + * Updated the "detail" listener so that it only polls once, + and not many times in a row, leaking memory each time... + * Update comparison for Packet-Src-IP-Address (etc.) so that + the operators other than '==' work. + * Did autoconf magic to work around weird libtool bug + * Make rlm_perl keep tags for tagged attributes in more situations + * Update UID checking for radmin + * Added "include_length" field for TTLS. It's needed for RFC + compliance, but not (apparently) for interoperability. + * Clean up control sockets when they are closed, so that we don't + leak memory. + * Define SUN_LEN for systems that don't have it. + * Correct some boundary conditions in the conditional checker ("if") + in "unlang". Bug noted by Arran Cudbard-Bell. + * Work around minor building issues in gmake. This should only + have affected developers. + * Change how we manage unprivileged user/group, so that we do not + create control sockets owned by root. + * Fixed more minor issues found by Coverity. + * Allow raddb/certs/bootstrap to run when there is no "make" + command installed. + * In radiusd.conf, run_dir depends on the name of the program, + and isn't hard-coded to "..../radiusd" + * Check for EOF in more places in the "detail" file reader. + * Added Freeswitch dictionary. + * Chop ethernet frames in VMPS, rather than droppping packets. + * Fix EAP-TLS bug. Patch from Arnaud Ebalard + * Don't lose string for regex-compares in the "users" file. + * Expose more functions in rlm_sql to rlm_sqlippool, which + helps on systems where RTLD_GLOBAL is off. + * Fix typos in MySQL schemas for ippools. + * Remove macro that was causing build issues on some platforms. + * Fixed issues with dead home servers. Bug noted by Chris Moules. + * Fixed "access after free" with some dynamic clients. + +- fix packaging bug, some directories missing execute permission + /etc/raddb/dictionary now readable by all. + +* Tue Feb 24 2009 John Dennis <jdennis@redhat.com> - 2.1.3-4 +- fix type usage in unixodbc to match new type usage in unixodbc API + +* Thu Feb 19 2009 John Dennis <jdennis@redhat.com> - 2.1.3-3 +- add pointer to Red Hat documentation in docdir + +* Sat Jan 24 2009 Caolán McNamara <caolanm@redhat.com> - 2.1.3-2 +- rebuild for dependencies + +* Thu Dec 4 2008 John Dennis <jdennis@redhat.com> - 2.1.3-1 +- upgrade to latest upstream release, upstream summary follows: + The focus of this release is stability. + Feature Improvements: + * Allow running with "user=radiusd" and binding to secure sockets. + * Start sending Status-Server "are you alive" messages earlier, which + helps with proxying multiple realms to a home server. + * Removed thread pool code from rlm_perl. It's not necessary. + * Added example Perl configuration to raddb/modules/perl + * Force OpenSSL to support certificates with SHA256. This seems to be + necessary for WiMAX certs. + Bug fixes: + * Fix Debian patch to allow it to build. + * Fix potential NULL dereference in debugging mode on certain + platforms for TTLS and PEAP inner tunnels. + * Fix uninitialized memory in handling of vendor definitions + * Fix parsing of quoted (but non-string) attributes in the "users" file. + * Initialize uknown NAS IP to 255.255.255.255, rather than 0.0.0.0 + * use SUN_LEN in control socket, to avoid truncation on some platforms. + * Correct internal handling of "debug condition" to prevent it from + being over-written. + * Check return code of regcomp in "unlang", so that invalid regular + expressions are caught rather than mishandled. + * Make rlm_sql use <ltdl.h>. Addresses bug #610. + * Document list "type = status" better. Closes bug #580. + * Set "default days" for certificates, because OpenSSL won't do it. + This closes bug #615. + * Reference correct list in example raddb/modules/ldap. Closes #596. + * Increase default schema size for Acct-Session-Id to 64. Closes #540. + * Fix use of temporary files in dialup-admin. Closes #605 and + addresses CVE-2008-4474. + * Addressed a number of minor issues found by Coverity. + * Added DHCP option 150 to the dictionary. Closes #618. + +* Wed Dec 3 2008 John Dennis <jdennis@redhat.com> - 2.1.1-8 +- add --with-system-libtool to configure as a workaround for +undefined reference to lt__PROGRAM__LTX_preloaded_symbols + +* Mon Dec 1 2008 John Dennis <jdennis@redhat.com> - 2.1.1-7 +- add obsoletes tag for dialupadmin subpackages which were removed + +* Mon Dec 1 2008 John Dennis <jdennis@redhat.com> - 2.1.1-7 +- add readline-devel BuildRequires + +* Sun Nov 30 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm@gmail.com> - 2.1.1-4 +- Rebuild for Python 2.6 + +* Fri Nov 21 2008 John Dennis <jdennis@redhat.com> - 2.1.1-3 +- make spec file buildable on RHEL5.2 by making perl-devel a fedora only dependency. +- remove diaupadmin packages, it's not well supported and there are problems with it. + +* Fri Sep 26 2008 John Dennis <jdennis@redhat.com> - 2.1.1-1 +- Resolves: bug #464119 bootstrap code could not create initial certs in /etc/raddb/certs because + permissions were 750, radiusd running as euid radiusd could not write there, permissions now 770 + +* Thu Sep 25 2008 John Dennis <jdennis@redhat.com> - 2.1.1-1 +- upgrade to new upstream 2.1.1 release + +* Wed Jul 30 2008 John Dennis <jdennis@redhat.com> - 2.0.5-2 +- Resolves: bug #453761: FreeRADIUS %%post should not include chown -R + specify file attributes for /etc/raddb/ldap.attrmap + fix consistent use of tabs/spaces (rpmlint warning) + +* Mon Jun 9 2008 John Dennis <jdennis@redhat.com> - 2.0.5-1 +- upgrade to latest upstream, see Changelog for details, + upstream now has more complete fix for bug #447545, local patch removed + +* Wed May 28 2008 John Dennis <jdennis@redhat.com> - 2.0.4-1 +- upgrade to latest upstream, see Changelog for details +- resolves: bug #447545: freeradius missing /etc/raddb/sites-available/inner-tunnel + +* Fri May 16 2008 <jdennis@redhat.com> - 2.0.3-3 +- # Temporary fix for bug #446864, turn off optimization + +* Fri Apr 18 2008 John Dennis <jdennis@redhat.com> - 2.0.3-2 +- remove support for radrelay, it's different now +- turn off default inclusion of SQL config files in radiusd.conf since SQL + is an optional RPM install +- remove mssql config files + +* Thu Apr 17 2008 John Dennis <jdennis@redhat.com> - 2.0.3-1 +- Upgrade to current upstream 2.0.3 release +- Many thanks to Enrico Scholz for his spec file suggestions incorporated here +- Resolve: bug #438665: Contains files owned by buildsystem +- Add dialupadmin-mysql, dialupadmin-postgresql, dialupadmin-ldap subpackages + to further partition external dependencies. +- Clean up some unnecessary requires dependencies +- Add versioned requires between subpackages + +* Tue Mar 18 2008 Tom "spot" Callaway <tcallawa@redhat.com> - 2.0.2-2 +- add Requires for versioned perl (libperl.so) + +* Thu Feb 28 2008 <jdennis@redhat.com> - 2.0.2-1 +- upgrade to new 2.0 release +- split into subpackages for more fine grained installation + +* Tue Feb 19 2008 Fedora Release Engineering <rel-eng@fedoraproject.org> - 1.1.7-4.4.ipa +- Autorebuild for GCC 4.3 + +* Thu Dec 06 2007 Release Engineering <rel-eng at fedoraproject dot org> - 1.1.7-3.4.ipa +- Rebuild for deps + +* Sat Nov 10 2007 <jdennis@redhat.com> - 1.1.7-3.3.ipa +- add support in rlm_ldap for reading clients from ldap +- fix TLS parameter controling if a cert which fails to validate + will be accepted (i.e. self-signed), + rlm_ldap config parameter=tls_require_cert + ldap LDAP_OPT_X_TLS_REQUIRE_CERT parameter was being passed to + ldap_set_option() when it should have been ldap_int_tls_config() + +* Sat Nov 3 2007 <jdennis@redhat.com> - 1.1.7-3.2.ipa +- add support in rlm_ldap for SASL/GSSAPI binds to the LDAP server + +* Mon Sep 17 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-3.1 +- made init script fully lsb conform + +* Mon Sep 17 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-3 +- fixed initscript problem (rhbz#292521) + +* Tue Aug 28 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-2 +- fixed initscript for LSB (rhbz#243671, rhbz#243928) +- fixed license tag + +* Tue Aug 7 2007 Thomas Woerner <twoerner@redhat.com> 1.1.7-1 +- new versin 1.1.7 +- install snmp MIB files +- dropped LDAP_DEPRECATED flag, it is upstream +- marked config files for sub packages as config (rhbz#240400) +- moved db files to /var/lib/raddb (rhbz#199082) + +* Fri Jun 15 2007 Thomas Woerner <twoerner@redhat.com> 1.1.6-2 +- radiusd expects /etc/raddb to not be world readable or writable + /etc/raddb now belongs to radiusd, post script sets permissions + +* Fri Jun 15 2007 Thomas Woerner <twoerner@redhat.com> 1.1.6-1 +- new version 1.1.6 + +* Fri Mar 9 2007 Thomas Woerner <twoerner@redhat.com> 1.1.5-1 +- new version 1.1.5 + - no /etc/raddb/otppasswd.sample anymore + - build is pie by default, dropped pie patch +- fixed build requirement for perl (perl-devel) + +* Fri Feb 23 2007 Karsten Hopp <karsten@redhat.com> 1.1.3-3 +- remove trailing dot from summary +- fix buildroot +- fix post/postun/preun requirements +- use rpm macros + +* Fri Dec 8 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-2.1 +- rebuild for new postgresql library version + +* Thu Nov 30 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-2 +- fixed ldap code to not use internals, added LDAP_DEPRECATED compile time flag + (#210912) + +* Tue Aug 15 2006 Thomas Woerner <twoerner@redhat.com> 1.1.3-1 +- new version 1.1.3 with lots of upstream bug fixes, some security fixes + (#205654) + +* Tue Aug 15 2006 Thomas Woerner <twoerner@redhat.com> 1.1.2-2 +- commented out include for sql.conf in radiusd.conf (#202561) + +* Wed Jul 12 2006 Jesse Keating <jkeating@redhat.com> - 1.1.2-1.1 +- rebuild + +* Thu Jun 1 2006 Thomas Woerner <twoerner@redhat.com> 1.1.2-1 +- new version 1.1.2 + +* Wed May 31 2006 Thomas Woerner <twoerner@redhat.com> 1.1.1-1 +- new version 1.1.1 +- fixed incorrect rlm_sql globbing (#189095) + Thanks to Yanko Kaneti for the fix. +- fixed chown syntax in post script (#182777) +- dropped gcc34, libdir and realloc-return patch +- spec file cleanup with additional libtool build fixes + +* Fri Feb 10 2006 Jesse Keating <jkeating@redhat.com> - 1.0.5-1.2 +- bump again for double-long bug on ppc(64) + +* Tue Feb 07 2006 Jesse Keating <jkeating@redhat.com> - 1.0.5-1.1 +- rebuilt for new gcc4.1 snapshot and glibc changes + +* Tue Dec 13 2005 Thomas Woerner <twoerner@redhat.com> 1.0.5-1 +- new version 1.0.5 + +* Fri Dec 09 2005 Jesse Keating <jkeating@redhat.com> +- rebuilt + +* Sat Nov 12 2005 Tom Lane <tgl@redhat.com> - 1.0.4-5 +- Rebuild due to mysql update. + +* Wed Nov 9 2005 Tomas Mraz <tmraz@redhat.com> - 1.0.4-4 +- rebuilt with new openssl +- fixed ignored return value of realloc + +* Fri Sep 30 2005 Tomas Mraz <tmraz@redhat.com> - 1.0.4-3 +- use include instead of pam_stack in pam config + +* Wed Jul 20 2005 Thomas Woerner <twoerner@redhat.com> 1.0.4-2 +- added missing build requires for libtool-ltdl-devel (#160877) +- modified file list to get a report for missing plugins + +* Tue Jun 28 2005 Thomas Woerner <twoerner@redhat.com> 1.0.4-1 +- new version 1.0.4 +- droppend radrelay patch (fixed upstream) + +* Thu Apr 14 2005 Warren Togami <wtogami@redhat.com> 1.0.2-2 +- rebuild against new postgresql-libs + +* Mon Apr 4 2005 Thomas Woerner <twoerner@redhat.com> 1.0.2-1 +- new version 1.0.2 + +* Fri Nov 19 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-3 +- rebuild for MySQL 4 +- switched over to installed libtool + +* Fri Nov 5 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-2 +- Fixed install problem of radeapclient (#138069) + +* Wed Oct 6 2004 Thomas Woerner <twoerner@redhat.com> 1.0.1-1 +- new version 1.0.1 +- applied radrelay CVS patch from Kevin Bonner + +* Wed Aug 25 2004 Warren Togami <wtogami@redhat.com> 1.0.0-3 +- BuildRequires pam-devel and libtool +- Fix errant text in description +- Other minor cleanups + +* Wed Aug 25 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-2.1 +- renamed /etc/pam.d/radius to /etc/pam.d/radiusd to match default + configuration (#130613) + +* Wed Aug 25 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-2 +- fixed BuildRequires for openssl-devel (#130606) + +* Mon Aug 16 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-1 +- 1.0.0 final + +* Mon Jul 5 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-0.pre3.2 +- added buildrequires for zlib-devel (#127162) +- fixed libdir patch to prefer own libeap instead of installed one (#127168) +- fixed samba account maps in LDAP for samba v3 (#127173) + +* Thu Jul 1 2004 Thomas Woerner <twoerner@redhat.com> 1.0.0-0.pre3.1 +- third "pre" release of version 1.0.0 +- rlm_ldap is using SASLv2 (#126507) + +* Tue Jun 15 2004 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Thu Jun 3 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-4.1 +- fixed BuildRequires for gdbm-devel + +* Tue Mar 30 2004 Harald Hoyer <harald@redhat.com> - 0.9.3-4 +- gcc34 compilation fixes + +* Tue Mar 02 2004 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Tue Feb 24 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-3.2 +- added sql scripts for rlm_sql to documentation (#116435) + +* Fri Feb 13 2004 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Thu Feb 5 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-2.1 +- using -fPIC instead of -fpic for s390 ans s390x + +* Thu Feb 5 2004 Thomas Woerner <twoerner@redhat.com> 0.9.3-2 +- radiusd is pie, now + +* Tue Nov 25 2003 Thomas Woerner <twoerner@redhat.com> 0.9.3-1 +- new version 0.9.3 (bugfix release) + +* Fri Nov 7 2003 Thomas Woerner <twoerner@redhat.com> 0.9.2-1 +- new version 0.9.2 + +* Mon Sep 29 2003 Thomas Woerner <twoerner@redhat.com> 0.9.1-1 +- new version 0.9.1 + +* Mon Sep 22 2003 Nalin Dahyabhai <nalin@redhat.com> 0.9.0-2.2 +- modify default PAM configuration to remove the directory part of the module + name, so that 32- and 64-bit libpam (called from 32- or 64-bit radiusd) on + multilib systems will always load the right module for the architecture +- modify default PAM configuration to use pam_stack + +* Mon Sep 1 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-2.1 +- com_err.h moved to /usr/include/et + +* Tue Jul 22 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-1 +- 0.9.0 final + +* Wed Jul 16 2003 Thomas Woerner <twoerner@redhat.com> 0.9.0-0.9.0 +- new version 0.9.0 pre3 + +* Thu May 22 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-6 +- included directory /var/log/radius/radacct for logrotate + +* Wed May 21 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-5 +- moved log and run dir to files section, cleaned up post + +* Wed May 21 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-4 +- added missing run dir in post + +* Tue May 20 2003 Thomas Woerner <twoerner@redhat.com> 0.8.1-3 +- fixed module load patch + +* Fri May 16 2003 Thomas Woerner <twoerner@redhat.com> +- removed la files, removed devel package +- split into 4 packages: freeradius, freeradius-mysql, freeradius-postgresql, + freeradius-unixODBC +- fixed requires and buildrequires +- create logging dir in post if it does not exist +- fixed module load without la files + +* Thu Apr 17 2003 Thomas Woerner <twoerner@redhat.com> +- Initial build. diff --git a/freeradius.sysusers b/freeradius.sysusers new file mode 100644 index 0000000..c0d814e --- /dev/null +++ b/freeradius.sysusers @@ -0,0 +1,3 @@ +#Type Name ID GECOS Home directory Shell +u radiusd 95 "radiusd user" /var/lib/radiusd /sbin/nologin +g radiusd 95 - - -
\ No newline at end of file diff --git a/radiusd.service b/radiusd.service new file mode 100644 index 0000000..8ec47ba --- /dev/null +++ b/radiusd.service @@ -0,0 +1,15 @@ +[Unit] +Description=FreeRADIUS high performance RADIUS server. +After=syslog.target network-online.target ipa.service dirsrv.target krb5kdc.service mysql.service mariadb.service postgresql.service + +[Service] +Type=forking +PIDFile=/run/radiusd/radiusd.pid +ExecStartPre=-/bin/chown -R radiusd.radiusd /var/run/radiusd +ExecStartPre=/usr/sbin/radiusd -C +ExecStart=/usr/sbin/radiusd -d /etc/raddb +ExecReload=/usr/sbin/radiusd -C +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target @@ -0,0 +1 @@ +8b7f794f2ac0d686d9aecfa083a63614 freeradius-server-3.0.21.tar.bz2 |