]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add "len" to tmpl_from_attr_substr()
authorAlan T. DeKok <aland@freeradius.org>
Sun, 25 Aug 2019 12:51:02 +0000 (08:51 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sun, 25 Aug 2019 15:36:50 +0000 (11:36 -0400)
so that we can call it on pre-parsed data, and so that it
doesn't do strlen()

23 files changed:
src/lib/server/tmpl.c
src/lib/server/tmpl.h
src/lib/server/xlat_builtin.c
src/lib/server/xlat_tokenize.c
src/lib/sim/xlat.c
src/modules/notes.md [new file with mode: 0644]
src/modules/proto_control/todo.md [new file with mode: 0644]
src/modules/proto_detail/TODO [new file with mode: 0644]
src/modules/proto_detail/old/proto_detail_file.c [new file with mode: 0644]
src/modules/proto_detail/old/proto_detail_file.h [new file with mode: 0644]
src/modules/proto_radius/dynamic_clients.c [new file with mode: 0644]
src/modules/proto_radius/fix [new file with mode: 0755]
src/modules/proto_radius/notes.md [new file with mode: 0644]
src/modules/proto_radius/notes.txt [new file with mode: 0644]
src/modules/proto_radius/tacacs [new file with mode: 0644]
src/modules/proto_vmps/udp.c [new file with mode: 0644]
src/modules/rlm_cache/rlm_cache.c
src/modules/rlm_eap/types/rlm_eap_tls/#rlm_eap_tls.c# [new file with mode: 0644]
src/modules/rlm_expr/rlm_expr.c
src/modules/rlm_json/libfreeradius-json.mk [new file with mode: 0644]
src/modules/rlm_radius/foo.c [new file with mode: 0644]
src/modules/rlm_radius/notes.md [new file with mode: 0644]
src/modules/rlm_redis/libfreeradius-redis.mk [new file with mode: 0644]

index 441b6e26ecfb0c71835cc5a9e13662079b85660d..c86175755cab058e9aa36854127f228bced666e0 100644 (file)
@@ -593,6 +593,7 @@ static vp_tmpl_rules_t const default_rules = {
  * @param[in] name             of attribute including #request_ref_t and #pair_list_t qualifiers.
  *                             If only #request_ref_t #pair_list_t qualifiers are found,
  *                             a #TMPL_TYPE_LIST #vp_tmpl_t will be produced.
+ * @param[in] name_len         Length of name, or -1 to do strlen()
  * @param[in] rules            Rules which control parsing:
  *                             - dict_def              The default dictionary to use if attributes
  *                                                     are unqualified.
@@ -628,7 +629,7 @@ static vp_tmpl_rules_t const default_rules = {
  *     - > 0 on success (number of bytes parsed).
  */
 ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, attr_ref_error_t *err,
-                              vp_tmpl_t **out, char const *name, vp_tmpl_rules_t const *rules)
+                              vp_tmpl_t **out, char const *name, ssize_t name_len, vp_tmpl_rules_t const *rules)
 {
        char const      *p, *q;
        long            num;
@@ -639,6 +640,8 @@ ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, attr_ref_error_t *err,
 
        if (err) *err = ATTR_REF_ERROR_NONE;
 
+       if (name_len < 0) name_len = strlen(name);
+
        p = name;
 
        if (!*p) {
@@ -727,6 +730,14 @@ ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, attr_ref_error_t *err,
        vpt->tmpl_num = NUM_ANY;
        vpt->type = TMPL_TYPE_ATTR;
 
+       /*
+        *      No more input after parsing the list ref, we're done.
+        */
+       if (p == (name + name_len)) {
+               vpt->type = TMPL_TYPE_LIST;
+               goto finish;
+       }
+
        /*
         *      This may be just a bare list, but it can still
         *      have instance selectors and tag selectors.
@@ -800,7 +811,7 @@ ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, attr_ref_error_t *err,
                 *      Copy the name to a field for later resolution
                 */
                vpt->type = TMPL_TYPE_ATTR_UNDEFINED;
-               for (q = p; fr_dict_attr_allowed_chars[(uint8_t) *q]; q++);
+               for (q = p; (q < (name + name_len)) && fr_dict_attr_allowed_chars[(uint8_t) *q]; q++);
                if (q == p) {
                        fr_strerror_printf("Invalid attribute name");
                        if (err) *err = ATTR_REF_ERROR_INVALID_ATTRIBUTE_NAME;
@@ -1001,16 +1012,17 @@ finish:
 ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, attr_ref_error_t *err,
                            vp_tmpl_t **out, char const *name, vp_tmpl_rules_t const *rules)
 {
-       ssize_t slen;
+       ssize_t slen, name_len;
 
        if (!rules) rules = &default_rules;     /* Use the defaults */
 
-       slen = tmpl_afrom_attr_substr(ctx, err, out, name, rules);
+       name_len = strlen(name);
+       slen = tmpl_afrom_attr_substr(ctx, err, out, name, name_len, rules);
        if (slen <= 0) return slen;
 
        if (!fr_cond_assert(*out)) return -1;
 
-       if (slen != (ssize_t)strlen(name)) {
+       if (slen != name_len) {
                /* This looks wrong, but it produces meaningful errors for unknown attrs with tags */
                fr_strerror_printf("Unexpected text after %s", fr_table_str_by_value(tmpl_type_table, (*out)->type, "<INVALID>"));
                return -slen;
@@ -1130,7 +1142,7 @@ ssize_t tmpl_afrom_str(TALLOC_CTX *ctx, vp_tmpl_t **out,
                 *      We can't define a template with garbage after
                 *      the attribute name.
                 */
-               slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, in, &mrules);
+               slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, in, inlen, &mrules);
                if (mrules.allow_undefined && (slen <= 0)) return slen;
                if (slen > 0) {
                        if ((size_t) slen < inlen) {
index 1964aed018003df1d81cf899c7422da3770adb23..6a4f16e72e641743a2a350596feb6fa131c4f0bb 100644 (file)
@@ -462,7 +462,7 @@ void                        tmpl_from_da(vp_tmpl_t *vpt, fr_dict_attr_t const *da, int8_t tag, int nu
 int                    tmpl_afrom_value_box(TALLOC_CTX *ctx, vp_tmpl_t **out, fr_value_box_t *data, bool steal);
 
 ssize_t                        tmpl_afrom_attr_substr(TALLOC_CTX *ctx, attr_ref_error_t *err,
-                                              vp_tmpl_t **out, char const *name,
+                                              vp_tmpl_t **out, char const *name, ssize_t name_len,
                                               vp_tmpl_rules_t const *rules);
 
 ssize_t                        tmpl_afrom_attr_str(TALLOC_CTX *ctx, attr_ref_error_t *err,
index ba5285a551f61d5ac4d73b51054aa59fd212924b..82b2df3c26bd4b41ea2863310610c3f9ddca9538 100644 (file)
@@ -1692,7 +1692,7 @@ static ssize_t xlat_func_explode(TALLOC_CTX *ctx, char **out, size_t outlen,
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, p, &(vp_tmpl_rules_t){ .dict_def = request->dict });
+       slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, p, -1, &(vp_tmpl_rules_t){ .dict_def = request->dict });
        if (slen <= 0) {
                RPEDEBUG("Invalid input");
                return -1;
@@ -1923,7 +1923,7 @@ static ssize_t parse_pad(vp_tmpl_t **vpt_p, size_t *pad_len_p, char *pad_char_p,
                return 0;
        }
 
-       slen = tmpl_afrom_attr_substr(request, NULL, &vpt, p, &(vp_tmpl_rules_t){ .dict_def = request->dict });
+       slen = tmpl_afrom_attr_substr(request, NULL, &vpt, p, -1, &(vp_tmpl_rules_t){ .dict_def = request->dict });
        if (slen <= 0) {
                RPEDEBUG("Failed parsing input string");
                return slen;
index f28a2b839cbdb52a198a8f1dfca04c485fba46ad..43de6e7affb7bc9339a0e29955a1bc67b1f6e9dd 100644 (file)
@@ -327,7 +327,7 @@ static inline ssize_t xlat_tokenize_attribute(TALLOC_CTX *ctx, xlat_exp_t **head
 
        our_rules.allow_undefined = true;               /* So we can check for virtual attributes later */
        our_rules.prefix = VP_ATTR_REF_PREFIX_NO;       /* Must be NO to stop %{&User-Name} */
-       slen = tmpl_afrom_attr_substr(NULL, &err, &vpt, p, &our_rules);
+       slen = tmpl_afrom_attr_substr(NULL, &err, &vpt, p, -1, &our_rules);
        if (slen <= 0) {
                /*
                 *      If the parse error occurred before the ':'
index 5058b401cddd5286afccc7271dda29c1829c3bbf..512e6e681184515f267edad0ba18b3e45e4d82e2 100644 (file)
@@ -49,7 +49,7 @@ static ssize_t sim_xlat_id_method(TALLOC_CTX *ctx, char **out, UNUSED size_t out
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -103,7 +103,7 @@ static ssize_t sim_xlat_id_type(TALLOC_CTX *ctx, char **out, UNUSED size_t outle
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -156,7 +156,7 @@ static ssize_t sim_xlat_3gpp_pseudonym_key_index(TALLOC_CTX *ctx, char **out, UN
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -204,7 +204,7 @@ static ssize_t sim_xlat_3gpp_pseudonym_decrypt(TALLOC_CTX *ctx, char **out, UNUS
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &id_vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &id_vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -223,7 +223,7 @@ static ssize_t sim_xlat_3gpp_pseudonym_decrypt(TALLOC_CTX *ctx, char **out, UNUS
        }
        p++;
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &key_vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &key_vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -316,7 +316,7 @@ static ssize_t sim_xlat_3gpp_pseudonym_encrypt(TALLOC_CTX *ctx, char **out, UNUS
         */
        fr_skip_spaces(p);
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &id_vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &id_vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
@@ -335,7 +335,7 @@ static ssize_t sim_xlat_3gpp_pseudonym_encrypt(TALLOC_CTX *ctx, char **out, UNUS
        }
        p++;
 
-       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &key_vpt, p,
+       slen = tmpl_afrom_attr_substr(our_ctx, NULL, &key_vpt, p, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
diff --git a/src/modules/notes.md b/src/modules/notes.md
new file mode 100644 (file)
index 0000000..9fb919f
--- /dev/null
@@ -0,0 +1,23 @@
+# Modules to fix for namespace
+
+Some modules need an explicit `dict` pointer.
+
+Other modules use xlat's, etc. which need to have a dict / namespace set.
+
+We MAY need to add a `namespace = ...` config to EVERY module.  <sigh>
+It would be much preferable to just get the namespace from where the
+module is being referenced.
+
+The daemon parses the config, and then bootstraps the modules *before*
+the virtual servers.  So we can't say "hey, this module is used from
+virtual server X, let's go figure out it's namespace!"
+
+* attr_filter
+* files
+* detail
+* cache
+  * get dict into TYPE_TMPL config file parsing
+* exec
+* passwd
+* radutmp
+* linelog
diff --git a/src/modules/proto_control/todo.md b/src/modules/proto_control/todo.md
new file mode 100644 (file)
index 0000000..b472451
--- /dev/null
@@ -0,0 +1,10 @@
+# TO DO
+
+Add `leftover` argument to `mod_write()`.  So the caller knows if the
+data has been partially written.  That way all of the pending / retry
+code is handled in `network.c`.
+
+This change will substantially simplify the writers.
+
+* EWOULDBLOCK, network side retries whole packet
+* `leftover != 0`, network side localizes message, and retries at a later time.
diff --git a/src/modules/proto_detail/TODO b/src/modules/proto_detail/TODO
new file mode 100644 (file)
index 0000000..1e5eb95
--- /dev/null
@@ -0,0 +1,12 @@
+# TODO
+
+* add config file items for
+  recv_type = { accounting, preacct, etc.}
+  send_type
+
+* code
+  for packet processing request->packet->code
+
+# DONE
+
+* priority is now set manually
diff --git a/src/modules/proto_detail/old/proto_detail_file.c b/src/modules/proto_detail/old/proto_detail_file.c
new file mode 100644 (file)
index 0000000..894362b
--- /dev/null
@@ -0,0 +1,1187 @@
+/*
+ * proto_detail.c      Process the detail file
+ *
+ * Version:    $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *  2007  The FreeRADIUS server project
+ *  2007  Alan DeKok <aland@deployingradius.com>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/protocol.h>
+#include <freeradius-devel/process.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include "detail.h"
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_GLOB_H
+#include <glob.h>
+#endif
+
+#include <pthread.h>
+
+#include <fcntl.h>
+
+#define USEC (1000000)
+
+static FR_NAME_NUMBER state_names[] = {
+       { "unopened", STATE_UNOPENED },
+       { "unlocked", STATE_UNLOCKED },
+       { "processing", STATE_PROCESSING },
+
+       { "header", STATE_HEADER },
+       { "vps", STATE_VPS },
+       { "queued", STATE_QUEUED },
+       { "running", STATE_RUNNING },
+       { "no-reply", STATE_NO_REPLY },
+       { "replied", STATE_REPLIED },
+
+       { NULL, 0 }
+};
+
+
+/*
+ *     If we're limiting outstanding packets, then mark the response
+ *     as being sent.
+ */
+static int detail_send(rad_listen_t *listener, REQUEST *request)
+{
+       char c = 0;
+       listen_detail_t *data = listener->data;
+
+       rad_assert(request->listener == listener);
+       rad_assert(listener->send == detail_send);
+
+       /*
+        *      This request timed out.  Remember that, and tell the
+        *      caller it's OK to read more "detail" file stuff.
+        */
+       if (request->reply->code == 0) {
+               data->delay_time = data->retry_interval * USEC;
+               data->signal = 1;
+               data->entry_state = STATE_NO_REPLY;
+
+               RDEBUG("detail (%s): No response to request.  Will retry in %d seconds",
+                      data->name, data->retry_interval);
+       } else {
+               int rtt;
+               struct timeval now;
+               /*
+                *      We call gettimeofday a lot.  But it should be OK,
+                *      because there's nothing else to do.
+                */
+               gettimeofday(&now, NULL);
+
+               /*
+                *      If we haven't sent a packet in the last second, reset
+                *      the RTT.
+                */
+               now.tv_sec -= 1;
+               if (fr_timeval_cmp(&data->last_packet, &now) < 0) {
+                       data->has_rtt = false;
+               }
+               now.tv_sec += 1;
+
+               /*
+                *      Only one detail packet may be outstanding at a time,
+                *      so it's safe to update some entries in the detail
+                *      structure.
+                *
+                *      We keep smoothed round trip time (SRTT), but not round
+                *      trip timeout (RTO).  We use SRTT to calculate a rough
+                *      load factor.
+                */
+               rtt = now.tv_sec - request->packet->timestamp.tv_sec;
+               rtt *= USEC;
+               rtt += now.tv_usec;
+               rtt -= request->packet->timestamp.tv_usec;
+
+               /*
+                *      If we're proxying, the RTT is our processing time,
+                *      plus the network delay there and back, plus the time
+                *      on the other end to process the packet.  Ideally, we
+                *      should remove the network delays from the RTT, but we
+                *      don't know what they are.
+                *
+                *      So, to be safe, we over-estimate the total cost of
+                *      processing the packet.
+                */
+               if (!data->has_rtt) {
+                       data->has_rtt = true;
+                       data->srtt = rtt;
+                       data->rttvar = rtt / 2;
+
+               } else {
+                       data->rttvar -= data->rttvar >> 2;
+                       data->rttvar += (data->srtt - rtt);
+                       data->srtt -= data->srtt >> 3;
+                       data->srtt += rtt >> 3;
+               }
+
+               /*
+                *      Calculate the time we wait before sending the next
+                *      packet.
+                *
+                *      rtt / (rtt + delay) = load_factor / 100
+                */
+               data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
+
+               /*
+                *      Cap delay at no less than 4 packets/s.  If the
+                *      end system can't handle this, then it's very
+                *      broken.
+                */
+               if (data->delay_time > (USEC / 4)) data->delay_time= USEC / 4;
+
+               RDEBUG3("detail (%s): Received response for request %" PRIu64 ".  "
+                       "Will read the next packet in %d seconds",
+                       data->name, request->number, data->delay_time / USEC);
+
+               data->last_packet = now;
+               data->signal = 1;
+               data->entry_state = STATE_REPLIED;
+               data->counter++;
+       }
+
+       if (write(data->child_pipe[1], &c, 1) < 0) {
+               RERROR("detail (%s): Failed writing ack to reader thread: %s", data->name, fr_syserror(errno));
+       }
+
+       return 0;
+}
+
+
+/*
+ *     Open the detail file, if we can.
+ *
+ *     FIXME: create it, if it's not already there, so that the main
+ *     server select() will wake us up if there's anything to read.
+ */
+static int detail_open(rad_listen_t *this)
+{
+       struct stat st;
+       listen_detail_t *data = this->data;
+
+       rad_assert(data->file_state == STATE_UNOPENED);
+       data->delay_time = USEC;
+
+       /*
+        *      Open detail.work first, so we don't lose
+        *      accounting packets.  It's probably better to
+        *      duplicate them than to lose them.
+        *
+        *      Note that we're not writing to the file, but
+        *      we've got to open it for writing in order to
+        *      establish the lock, to prevent rlm_detail from
+        *      writing to it.
+        *
+        *      This also means that if we're doing globbing,
+        *      this file will be read && processed before the
+        *      file globbing is done.
+        */
+       data->fp = NULL;
+       data->work_fd = open(data->filename_work, O_RDWR);
+
+       /*
+        *      Couldn't open it for a reason OTHER than "it doesn't
+        *      exist".  Complain and tell the admin.
+        */
+       if ((data->work_fd < 0) && (errno != ENOENT)) {
+               ERROR("Failed opening detail file %s: %s",
+                     data->filename_work, fr_syserror(errno));
+               return 0;
+       }
+
+       /*
+        *      The file doesn't exist.  Poll for it again.
+        */
+       if (data->work_fd < 0) {
+#ifndef HAVE_GLOB_H
+               return 0;
+#else
+               unsigned int    i;
+               int             found;
+               time_t          chtime;
+               char const      *filename;
+               glob_t          files;
+
+               DEBUG2("detail (%s): Polling for detail file", data->name);
+
+               memset(&files, 0, sizeof(files));
+               if (glob(data->filename, 0, NULL, &files) != 0) {
+               noop:
+                       globfree(&files);
+                       return 0;
+               }
+
+               /*
+                *      Loop over the glob'd files, looking for the
+                *      oldest one.
+                */
+               chtime = 0;
+               found = -1;
+               for (i = 0; i < files.gl_pathc; i++) {
+                       if (stat(files.gl_pathv[i], &st) < 0) continue;
+
+                       if ((i == 0) || (st.st_ctime < chtime)) {
+                               chtime = st.st_ctime;
+                               found = i;
+                       }
+               }
+
+               if (found < 0) goto noop;
+
+               /*
+                *      Rename detail to detail.work
+                */
+               filename = files.gl_pathv[found];
+
+               DEBUG("detail (%s): Renaming %s -> %s", data->name, filename, data->filename_work);
+               if (rename(filename, data->filename_work) < 0) {
+                       ERROR("detail (%s): Failed renaming %s to %s: %s",
+                             data->name, filename, data->filename_work, fr_syserror(errno));
+                       goto noop;
+               }
+
+               globfree(&files);       /* Shouldn't be using anything in files now */
+
+               /*
+                *      And try to open the filename.
+                */
+               data->work_fd = open(data->filename_work, O_RDWR);
+               if (data->work_fd < 0) {
+                       ERROR("detail (%s): Failed opening %s: %s",
+                             data->name, data->filename_work, fr_syserror(errno));
+                       return 0;
+               }
+#endif
+       } /* else detail.work existed, and we opened it */
+
+       rad_assert(data->vps == NULL);
+       rad_assert(data->fp == NULL);
+
+       data->file_state = STATE_UNLOCKED;
+
+       data->client_ip.af = AF_UNSPEC;
+       data->timestamp = 0;
+       data->offset = data->last_offset = data->timestamp_offset = 0;
+       data->packets = 0;
+       data->tries = 0;
+       data->done_entry = false;
+
+       return 1;
+}
+
+
+/*
+ *     FIXME: add a configuration "exit when done" so that the detail
+ *     file reader can be used as a one-off tool to update stuff.
+ *
+ *     The time sequence for reading from the detail file is:
+ *
+ *     t_0             signalled that the server is idle, and we
+ *                     can read from the detail file.
+ *
+ *     t_rtt           the packet has been processed successfully,
+ *                     wait for t_delay to enforce load factor.
+ *
+ *     t_rtt + t_delay wait for signal that the server is idle.
+ *
+ */
+static int detail_recv(rad_listen_t *listener)
+{
+       char c = 0;
+       ssize_t rcode;
+       RADIUS_PACKET *packet;
+       listen_detail_t *data = listener->data;
+       RAD_REQUEST_FUNP fun = NULL;
+
+       /*
+        *      Block until there's a packet ready.
+        */
+       rcode = read(data->master_pipe[0], &packet, sizeof(packet));
+       if (rcode <= 0) return rcode;
+
+       rad_assert(packet != NULL);
+
+       switch (packet->code) {
+       case FR_CODE_ACCOUNTING_REQUEST:
+               fun = rad_accounting;
+               break;
+
+       case FR_CODE_COA_REQUEST:
+       case FR_CODE_DISCONNECT_REQUEST:
+               fun = rad_coa_recv;
+               break;
+
+       default:
+               data->entry_state = STATE_REPLIED;
+               goto signal_thread;
+       }
+
+       if (!request_receive(NULL, listener, packet, &data->detail_client, fun)) {
+               data->entry_state = STATE_NO_REPLY;     /* try again later */
+
+       signal_thread:
+               fr_radius_free(&packet);
+               if (write(data->child_pipe[1], &c, 1) < 0) {
+                       ERROR("detail (%s): Failed writing ack to reader thread: %s", data->name,
+                             fr_syserror(errno));
+               }
+       }
+
+       /*
+        *      Wait for the child thread to write an answer to the pipe
+        */
+       return 0;
+}
+
+static RADIUS_PACKET *detail_poll(rad_listen_t *listener)
+{
+       char            key[256], op[8], value[1024];
+       vp_cursor_t     cursor;
+       VALUE_PAIR      *vp;
+       RADIUS_PACKET   *packet;
+       char            buffer[2048];
+       listen_detail_t *data = listener->data;
+
+       switch (data->file_state) {
+       case STATE_UNOPENED:
+open_file:
+               rad_assert(data->work_fd < 0);
+
+               if (!detail_open(listener)) return NULL;
+
+               rad_assert(data->file_state == STATE_UNLOCKED);
+               rad_assert(data->work_fd >= 0);
+
+               /* FALL-THROUGH */
+
+       /*
+        *      Try to lock fd.  If we can't, return.
+        *      If we can, continue.  This means that
+        *      the server doesn't block while waiting
+        *      for the lock to open...
+        */
+       case STATE_UNLOCKED:
+               /*
+                *      Note that we do NOT block waiting for
+                *      the lock.  We've re-named the file
+                *      above, so we've already guaranteed
+                *      that any *new* detail writer will not
+                *      be opening this file.  The only
+                *      purpose of the lock is to catch a race
+                *      condition where the execution
+                *      "ping-pongs" between radiusd &
+                *      radrelay.
+                */
+               if (rad_lockfd_nonblock(data->work_fd, 0) < 0) {
+                       /*
+                        *      Close the FD.  The main loop
+                        *      will wake up in a second and
+                        *      try again.
+                        */
+                       close(data->work_fd);
+                       data->fp = NULL;
+                       data->work_fd = -1;
+                       data->file_state = STATE_UNOPENED;
+                       return NULL;
+               }
+
+               /*
+                *      Only open for writing if we're
+                *      marking requests as completed.
+                */
+               data->fp = fdopen(data->work_fd, data->track ? "r+" : "r");
+               if (!data->fp) {
+                       ERROR("detail (%s): FATAL: Failed to re-open detail file: %s",
+                             data->name, fr_syserror(errno));
+                       fr_exit(1);
+               }
+
+               /*
+                *      Look for the header
+                */
+               data->file_state = STATE_PROCESSING;
+               data->entry_state = STATE_HEADER;
+               data->delay_time = USEC;
+               data->vps = NULL;
+               break;
+
+               /*
+                *      Go to the next switch statement.
+                */
+       case STATE_PROCESSING:
+               break;
+       }
+
+
+       switch (data->entry_state) {
+       case STATE_HEADER:
+       do_header:
+               data->done_entry = false;
+               data->timestamp_offset = 0;
+
+               data->tries = 0;
+               if (!data->fp) {
+                       data->file_state = STATE_UNOPENED;
+                       goto open_file;
+               }
+
+               {
+                       struct stat buf;
+
+                       if (fstat(data->work_fd, &buf) < 0) {
+                               ERROR("detail (%s): Failed to stat detail file: %s",
+                                     data->name, fr_syserror(errno));
+
+                               goto cleanup;
+                       }
+                       if (((off_t) ftell(data->fp)) == buf.st_size) { //-V595
+                               goto cleanup;
+                       }
+               }
+
+               /*
+                *      End of file.  Delete it, and re-set
+                *      everything.
+                */
+               if (feof(data->fp)) {
+               cleanup:
+                       DEBUG("detail (%s): Unlinking %s", data->name, data->filename_work);
+                       unlink(data->filename_work);
+                       if (data->fp) fclose(data->fp);
+                       data->fp = NULL;
+                       data->work_fd = -1;
+                       data->file_state = STATE_UNOPENED;
+                       rad_assert(data->vps == NULL);
+
+                       if (data->one_shot) {
+                               INFO("detail (%s): Finished reading \"one shot\" detail file - Exiting", data->name);
+                               radius_signal_self(RADIUS_SIGNAL_SELF_EXIT);
+                       }
+
+                       return NULL;
+               }
+
+               /*
+                *      Else go read something.
+                */
+               break;
+
+       /*
+        *      Read more value-pair's, unless we're
+        *      at EOF.  In that case, queue whatever
+        *      we have.
+        */
+       case STATE_VPS:
+               if (data->fp && !feof(data->fp)) break;
+               data->entry_state = STATE_QUEUED;
+
+               /* FALL-THROUGH */
+
+       case STATE_QUEUED:
+               goto alloc_packet;
+
+       /*
+        *      Periodically check what's going on.
+        *      If the request is taking too long,
+        *      retry it.
+        */
+       case STATE_RUNNING:
+               if (time(NULL) < (data->running + (int)data->retry_interval)) {
+                       return NULL;
+               }
+
+               DEBUG("detail (%s): No response to detail request.  Retrying", data->name);
+               /* FALL-THROUGH */
+
+       /*
+        *      If there's no reply, keep
+        *      retransmitting the current packet
+        *      forever.
+        */
+       case STATE_NO_REPLY:
+               data->entry_state = STATE_QUEUED;
+               goto alloc_packet;
+
+       /*
+        *      We have a reply.  Clean up the old
+        *      request, and go read another one.
+        */
+       case STATE_REPLIED:
+               if (data->track) {
+                       rad_assert(data->fp != NULL);
+
+                       if (fseek(data->fp, data->timestamp_offset, SEEK_SET) < 0) {
+                               WARN("detail (%s): Failed seeking to timestamp offset: %s",
+                                    data->name, fr_syserror(errno));
+                       } else if (fwrite("\tDone", 1, 5, data->fp) < 5) {
+                               WARN("detail (%s): Failed marking request as done: %s",
+                                    data->name, fr_syserror(errno));
+                       } else if (fflush(data->fp) != 0) {
+                               WARN("detail (%s): Failed flushing marked detail file to disk: %s",
+                                    data->name, fr_syserror(errno));
+                       }
+
+                       if (fseek(data->fp, data->offset, SEEK_SET) < 0) {
+                               WARN("detail (%s): Failed seeking to next detail request: %s",
+                                    data->name, fr_syserror(errno));
+                       }
+               }
+
+               fr_pair_list_free(&data->vps);
+               data->entry_state = STATE_HEADER;
+               goto do_header;
+       }
+
+       fr_pair_cursor_init(&cursor, &data->vps);
+
+       /*
+        *      Read a header, OR a value-pair.
+        */
+       while (fgets(buffer, sizeof(buffer), data->fp)) {
+               data->last_offset = data->offset;
+               data->offset = ftell(data->fp); /* for statistics */
+
+               /*
+                *      Badly formatted file: delete it.
+                *
+                *      FIXME: Maybe flag an error?
+                */
+               if (!strchr(buffer, '\n')) {
+                       fr_pair_list_free(&data->vps);
+                       goto cleanup;
+               }
+
+               /*
+                *      We're reading VP's, and got a blank line.
+                *      Queue the packet.
+                */
+               if ((data->entry_state == STATE_VPS) &&
+                   (buffer[0] == '\n')) {
+                       data->entry_state = STATE_QUEUED;
+                       break;
+               }
+
+               /*
+                *      Look for date/time header, and read VP's if
+                *      found.  If not, keep reading lines until we
+                *      find one.
+                */
+               if (data->entry_state == STATE_HEADER) {
+                       int y;
+
+                       if (sscanf(buffer, "%*s %*s %*d %*d:%*d:%*d %d", &y)) {
+                               data->entry_state = STATE_VPS;
+                       }
+                       continue;
+               }
+
+               /*
+                *      We have a full "attribute = value" line.
+                *      If it doesn't look reasonable, skip it.
+                *
+                *      FIXME: print an error for badly formatted attributes?
+                */
+               if (sscanf(buffer, "%255s %7s %1023s", key, op, value) != 3) {
+                       WARN("detail (%s): Skipping badly formatted line %s", data->name, buffer);
+                       continue;
+               }
+
+               /*
+                *      Should be =, :=, +=, ...
+                */
+               if (!strchr(op, '=')) {
+                       WARN("detail (%s): Skipping line without operator - %s", data->name, buffer);
+                       continue;
+               }
+
+               /*
+                *      Skip non-protocol attributes.
+                */
+               if (!strcasecmp(key, "Request-Authenticator")) continue;
+
+               /*
+                *      Set the original client IP address, based on
+                *      what's in the detail file.
+                *
+                *      Hmm... we don't set the server IP address.
+                *      or port.  Oh well.
+                */
+               if (!strcasecmp(key, "Client-IP-Address")) {
+                       data->client_ip.af = AF_INET;
+                       if (fr_inet_hton(&data->client_ip, AF_INET, value, false) < 0) {
+                               ERROR("detail (%s): Failed parsing Client-IP-Address", data->name);
+
+                               fr_pair_list_free(&data->vps);
+                               goto cleanup;
+                       }
+                       continue;
+               }
+
+               /*
+                *      The original time at which we received the
+                *      packet.  We need this to properly calculate
+                *      Acct-Delay-Time.
+                */
+               if (!strcasecmp(key, "Timestamp")) {
+                       data->timestamp = atoi(value);
+                       data->timestamp_offset = data->last_offset;
+
+                       vp = fr_pair_afrom_num(data, 0, FR_PACKET_ORIGINAL_TIMESTAMP);
+                       if (vp) {
+                               vp->vp_date = (uint32_t) data->timestamp;
+                               vp->type = VT_DATA;
+                               fr_pair_cursor_append(&cursor, vp);
+                       }
+                       continue;
+               }
+
+               if (!strcasecmp(key, "Donestamp")) {
+                       data->timestamp = atoi(value);
+                       data->done_entry = true;
+                       continue;
+               }
+
+               DEBUG3("detail (%s): Trying to read VP from line - %s", data->name, buffer);
+
+               /*
+                *      Read one VP.
+                *
+                *      FIXME: do we want to check for non-protocol
+                *      attributes like radsqlrelay does?
+                */
+               vp = NULL;
+               if ((fr_pair_list_afrom_str(data, buffer, &vp) > 0) &&
+                   (vp != NULL)) {
+                       fr_pair_cursor_merge(&cursor, vp);
+               } else {
+                       WARN("detail (%s): Failed reading VP from line - %s", data->name, buffer);
+               }
+       }
+
+       /*
+        *      Some kind of error.
+        *
+        *      FIXME: Leave the file in-place, and warn the
+        *      administrator?
+        */
+       if (ferror(data->fp)) goto cleanup;
+
+       data->tries = 0;
+       data->packets++;
+
+       /*
+        *      Process the packet.
+        */
+ alloc_packet:
+       if (data->done_entry) {
+               DEBUG2("detail (%s): Skipping record for timestamp %lu", data->name, data->timestamp);
+               fr_pair_list_free(&data->vps);
+               data->entry_state = STATE_HEADER;
+               goto do_header;
+       }
+
+       data->tries++;
+
+       /*
+        *      The writer doesn't check that the record was
+        *      completely written.  If the disk is full, this can
+        *      result in a truncated record.  When that happens,
+        *      treat it as EOF.
+        */
+       if (data->entry_state != STATE_QUEUED) {
+               ERROR("detail (%s): Truncated record: treating it as EOF for detail file %s",
+                     data->name, data->filename_work);
+               fr_pair_list_free(&data->vps);
+               goto cleanup;
+       }
+
+       /*
+        *      We're done reading the file, but we didn't read
+        *      anything.  Clean up, and don't return anything.
+        */
+       if (!data->vps) {
+               WARN("detail (%s): Read empty packet from file %s",
+                    data->name, data->filename_work);
+               data->entry_state = STATE_HEADER;
+               if (!data->fp || feof(data->fp)) goto cleanup;
+               return NULL;
+       }
+
+       /*
+        *      Allocate the packet.  If we fail, it's a serious
+        *      problem.
+        */
+       packet = fr_radius_alloc(NULL, true);
+       if (!packet) {
+               ERROR("detail (%s): FATAL: Failed allocating memory for detail", data->name);
+               fr_exit(1);
+       }
+
+       memset(packet, 0, sizeof(*packet));
+       packet->sockfd = -1;
+       packet->src_ipaddr.af = AF_INET;
+       packet->src_ipaddr.addr.v4.s_addr = htonl(INADDR_NONE);
+
+       /*
+        *      If everything's OK, this is a waste of memory.
+        *      Otherwise, it lets us re-send the original packet
+        *      contents, unmolested.
+        */
+       packet->vps = fr_pair_list_copy(packet, data->vps);
+
+       packet->code = FR_CODE_ACCOUNTING_REQUEST;
+       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_TYPE, TAG_ANY);
+       if (vp) packet->code = vp->vp_uint32;
+
+       gettimeofday(&packet->timestamp, NULL);
+
+       /*
+        *      Remember where it came from, so that we don't
+        *      proxy it to the place it came from...
+        */
+       if (data->client_ip.af != AF_UNSPEC) {
+               packet->src_ipaddr = data->client_ip;
+       }
+
+       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_SRC_IP_ADDRESS, TAG_ANY);
+       if (vp) {
+               packet->src_ipaddr.af = AF_INET;
+               packet->src_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr;
+               packet->src_ipaddr.prefix = 32;
+       } else {
+               vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_SRC_IPV6_ADDRESS, TAG_ANY);
+               if (vp) {
+                       packet->src_ipaddr.af = AF_INET6;
+                       memcpy(&packet->src_ipaddr.addr.v6,
+                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
+                       packet->src_ipaddr.prefix = 128;
+               }
+       }
+
+       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_DST_IP_ADDRESS, TAG_ANY);
+       if (vp) {
+               packet->dst_ipaddr.af = AF_INET;
+               packet->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr;
+               packet->dst_ipaddr.prefix = 32;
+       } else {
+               vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_DST_IPV6_ADDRESS, TAG_ANY);
+               if (vp) {
+                       packet->dst_ipaddr.af = AF_INET6;
+                       memcpy(&packet->dst_ipaddr.addr.v6,
+                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
+                       packet->dst_ipaddr.prefix = 128;
+               }
+       }
+
+       /*
+        *      Generate packet ID, ports, IP via a counter.
+        */
+       packet->id = data->counter & 0xff;
+       packet->src_port = 1024 + ((data->counter >> 8) & 0xff);
+       packet->dst_port = 1024 + ((data->counter >> 16) & 0xff);
+
+       packet->dst_ipaddr.af = AF_INET;
+       packet->dst_ipaddr.addr.v4.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | ((data->counter >> 24) & 0xff));
+
+       /*
+        *      Create / update accounting attributes.
+        */
+       if (packet->code == FR_CODE_ACCOUNTING_REQUEST) {
+               /*
+                *      Prefer the Event-Timestamp in the packet, if it
+                *      exists.  That is when the event occurred, whereas the
+                *      "Timestamp" field is when we wrote the packet to the
+                *      detail file, which could have been much later.
+                */
+               vp = fr_pair_find_by_num(packet->vps, 0, FR_EVENT_TIMESTAMP, TAG_ANY);
+               if (vp) {
+                       data->timestamp = vp->vp_uint32;
+               }
+
+               /*
+                *      Look for Acct-Delay-Time, and update
+                *      based on Acct-Delay-Time += (time(NULL) - timestamp)
+                */
+               vp = fr_pair_find_by_num(packet->vps, 0, FR_ACCT_DELAY_TIME, TAG_ANY);
+               if (!vp) {
+                       vp = fr_pair_afrom_num(packet, 0, FR_ACCT_DELAY_TIME);
+                       rad_assert(vp != NULL);
+                       fr_pair_add(&packet->vps, vp);
+               }
+               if (data->timestamp != 0) {
+                       vp->vp_uint32 += time(NULL) - data->timestamp;
+               }
+       }
+
+       /*
+        *      Set the transmission count.
+        */
+       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_TRANSMIT_COUNTER, TAG_ANY);
+       if (!vp) {
+               vp = fr_pair_afrom_num(packet, 0, FR_PACKET_TRANSMIT_COUNTER);
+               rad_assert(vp != NULL);
+               fr_pair_add(&packet->vps, vp);
+       }
+       vp->vp_uint32 = data->tries;
+
+       data->entry_state = STATE_RUNNING;
+       data->running = packet->timestamp.tv_sec;
+
+       return packet;
+}
+
+/*
+ *     Free detail-specific stuff.
+ */
+static int _detail_free(listen_detail_t *data)
+{
+       if (!check_config) {
+               ssize_t ret;
+               void *arg = NULL;
+
+               /*
+                *      Mark the child pipes as unusable
+                */
+               close(data->child_pipe[0]);
+               close(data->child_pipe[1]);
+               data->child_pipe[0] = -1;
+
+               /*
+                *      Tell it to stop (interrupting its sleep)
+                */
+               pthread_kill(data->pthread_id, SIGTERM);
+
+               /*
+                *      Wait for it to acknowledge that it's stopped.
+                */
+               ret = read(data->master_pipe[0], &arg, sizeof(arg));
+               if (ret < 0) {
+                       ERROR("detail (%s): Reader thread exited without informing the master: %s",
+                             data->name, fr_syserror(errno));
+               } else if (ret != sizeof(arg)) {
+                       ERROR("detail (%s): Invalid thread pointer received from reader thread during exit",
+                             data->name);
+                       ERROR("detail (%s): Expected %zu bytes, got %zi bytes", data->name, sizeof(arg), ret);
+               }
+
+               close(data->master_pipe[0]);
+               close(data->master_pipe[1]);
+
+               if (arg) pthread_join(data->pthread_id, &arg);
+       }
+
+       if (data->fp != NULL) {
+               fclose(data->fp);
+               data->fp = NULL;
+       }
+
+       return 0;
+}
+
+
+static int detail_print(rad_listen_t const *this, char *buffer, size_t bufsize)
+{
+       if (!this->server) {
+               return snprintf(buffer, bufsize, "%s",
+                               ((listen_detail_t *)(this->data))->filename);
+       }
+
+       return snprintf(buffer, bufsize, "detail file %s as server %s",
+                       ((listen_detail_t *)(this->data))->filename,
+                       this->server);
+}
+
+
+/*
+ *     Delay while waiting for a file to be ready
+ */
+static int detail_delay(listen_detail_t *data)
+{
+       int delay = (data->poll_interval - 1) * USEC;
+
+       /*
+        *      Add +/- 0.25s of jitter
+        */
+       delay += (USEC * 3) / 4;
+       delay += fr_rand() % (USEC / 2);
+
+       DEBUG2("detail (%s): Detail listener state %s waiting %d.%06d sec",
+              data->name,
+              fr_int2str(state_names, data->entry_state, "?"),
+              (delay / USEC), delay % USEC);
+
+       return delay;
+}
+
+static int detail_encode(rad_listen_t *this, REQUEST *request)
+{
+       listen_detail_t *data = this->data;
+
+       RDEBUG2("detail (%s): Finished %s packet", data->name,
+               fr_packet_codes[request->packet->code]);
+
+       return 0;
+}
+
+static int detail_decode(rad_listen_t *this, REQUEST *request)
+{
+       listen_detail_t *data = this->data;
+
+       if (DEBUG_ENABLED2) {
+               RDEBUG2("detail (%s): Read %s packet from %s", data->name,
+                       fr_packet_codes[request->packet->code], data->filename_work);
+               rdebug_pair_list(L_DBG_LVL_1, request, request->packet->vps, NULL);
+       }
+
+       return 0;
+}
+
+
+static void *detail_handler_thread(void *arg)
+{
+       char c;
+       rad_listen_t *this = arg;
+       listen_detail_t *data = this->data;
+
+       while (true) {
+               RADIUS_PACKET *packet;
+
+               while ((packet = detail_poll(this)) == NULL) {
+                       usleep(detail_delay(data));
+
+                       /*
+                        *      If we're supposed to exit then tell
+                        *      the master thread we've exited.
+                        */
+                       if (data->child_pipe[0] < 0) {
+                               packet = NULL;
+                               if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
+                                       ERROR("detail (%s): Failed writing exit status to master: %s",
+                                             data->name, fr_syserror(errno));
+                               }
+                               return NULL;
+                       }
+               }
+
+               /*
+                *      Keep retrying forever.
+                *
+                *      FIXME: cap the retries.
+                */
+               do {
+                       if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
+                               ERROR("detail (%s): Failed passing detail packet pointer to master: %s",
+                                     data->name, fr_syserror(errno));
+                       }
+
+                       if (read(data->child_pipe[0], &c, 1) < 0) {
+                               ERROR("detail (%s): Failed getting detail packet ack from master: %s",
+                                     data->name, fr_syserror(errno));
+                               break;
+                       }
+
+                       if (data->delay_time > 0) usleep(data->delay_time);
+
+                       packet = detail_poll(this);
+                       if (!packet) break;
+               } while (data->entry_state != STATE_REPLIED);
+       }
+
+       return NULL;
+}
+
+
+static const CONF_PARSER detail_config[] = {
+       { FR_CONF_OFFSET("detail", FR_TYPE_FILE_OUTPUT | FR_TYPE_DEPRECATED, listen_detail_t, filename) },
+       { FR_CONF_OFFSET("filename", FR_TYPE_FILE_OUTPUT | FR_TYPE_REQUIRED, listen_detail_t, filename) },
+       { FR_CONF_OFFSET("load_factor", FR_TYPE_UINT32, listen_detail_t, load_factor), .dflt = STRINGIFY(10) },
+       { FR_CONF_OFFSET("poll_interval", FR_TYPE_UINT32, listen_detail_t, poll_interval), .dflt = STRINGIFY(1) },
+       { FR_CONF_OFFSET("retry_interval", FR_TYPE_UINT32, listen_detail_t, retry_interval), .dflt = STRINGIFY(30) },
+       { FR_CONF_OFFSET("one_shot", FR_TYPE_BOOL, listen_detail_t, one_shot), .dflt = "no" },
+       { FR_CONF_OFFSET("track", FR_TYPE_BOOL, listen_detail_t, track), .dflt = "no" },
+       CONF_PARSER_TERMINATOR
+};
+
+/*
+ *     Parse a detail section.
+ */
+static int detail_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+       int             rcode;
+       listen_detail_t *data;
+       RADCLIENT       *client;
+       char            buffer[2048];
+
+       data = this->data;
+
+       if (cf_section_rules_push(cs, detail_config) < 0) return -1;
+
+       rcode = cf_section_parse(data, data, cs);
+       if (rcode < 0) {
+               cf_log_err(cs, "Failed parsing listen section");
+               return -1;
+       }
+
+       data->name = cf_section_name2(cs);
+       if (!data->name) data->name = data->filename;
+
+       /*
+        *      We don't do duplicate detection for "detail" sockets.
+        */
+       this->nodup = true;
+
+       if (!data->filename) {
+               cf_log_err(cs, "No detail file specified in listen section");
+               return -1;
+       }
+
+       FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, >=, 1);
+       FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, <=, 100);
+
+       FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, >=, 1);
+       FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, <=, 60);
+
+       FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, >=, 4);
+       FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, <=, 3600);
+
+       /*
+        *      Only checking the config.  Don't start threads or anything else.
+        */
+       if (check_config) return 0;
+
+       /*
+        *      If the filename is a glob, use "detail.work" as the
+        *      work file name.
+        */
+       if ((strchr(data->filename, '*') != NULL) ||
+           (strchr(data->filename, '[') != NULL)) {
+               char *p;
+
+#ifndef HAVE_GLOB_H
+               WARN("detail (%s): File \"%s\" appears to use file globbing, but it is not supported on this system",
+                    data->name, data->filename);
+#endif
+               strlcpy(buffer, data->filename, sizeof(buffer));
+               p = strrchr(buffer, FR_DIR_SEP);
+               if (p) {
+                       p[1] = '\0';
+               } else {
+                       buffer[0] = '\0';
+               }
+
+               /*
+                *      Globbing cannot be done across directories.
+                */
+               if ((strchr(buffer, '*') != NULL) ||
+                   (strchr(buffer, '[') != NULL)) {
+                       cf_log_err(cs, "Wildcard directories are not supported");
+                       return -1;
+               }
+
+               strlcat(buffer, "detail.work",
+                       sizeof(buffer) - strlen(buffer));
+
+       } else {
+               snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
+       }
+
+       data->filename_work = talloc_strdup(data, buffer);
+
+       data->work_fd = -1;
+       data->vps = NULL;
+       data->fp = NULL;
+       data->file_state = STATE_UNOPENED;
+       data->entry_state = STATE_HEADER;
+       data->delay_time = data->poll_interval * USEC;
+       data->signal = 1;
+
+       /*
+        *      Initialize the fake client.
+        */
+       client = &data->detail_client;
+       memset(client, 0, sizeof(*client));
+       client->ipaddr.af = AF_INET;
+       client->ipaddr.addr.v4.s_addr = INADDR_NONE;
+       client->ipaddr.prefix = 0;
+       client->longname = client->shortname = data->filename;
+       client->secret = client->shortname;
+       client->nas_type = talloc_strdup(data, "none"); /* Part of 'data' not dynamically allocated */
+
+       this->server_cs = cf_item_to_section(cf_parent(this->cs));
+       client->server_cs = this->server_cs;
+
+       return 0;
+}
+
+/*
+ *     Open detail files
+ */
+static int detail_socket_open(UNUSED CONF_SECTION *cs, rad_listen_t *this)
+{
+       listen_detail_t *data;
+
+       data = this->data;
+       talloc_set_destructor(data, _detail_free);
+
+       /*
+        *      Create the communication pipes.
+        */
+       if (pipe(data->master_pipe) < 0) {
+               ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
+               fr_exit(1);
+       }
+
+       if (pipe(data->child_pipe) < 0) {
+               ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
+               fr_exit(1);
+       }
+
+       if (pthread_create(&data->pthread_id, NULL, detail_handler_thread, this) != 0) {
+               ERROR("detail (%s): Error creating detail reader thread: %s", data->name, fr_syserror(errno));
+               fr_exit(1);
+       }
+
+       this->fd = data->master_pipe[0];
+
+       return 0;
+}
+
+extern rad_protocol_t proto_detail;
+rad_protocol_t proto_detail = {
+       .magic = RLM_MODULE_INIT,
+       .name = "detail",
+       .inst_size = sizeof(listen_detail_t),
+       .tls = false,
+       .parse = detail_parse,
+       .open = detail_socket_open,
+       .recv = detail_recv,
+       .send = detail_send,
+       .print = detail_print,
+       .debug = common_packet_debug,
+       .encode = detail_encode,
+       .decode = detail_decode
+};
diff --git a/src/modules/proto_detail/old/proto_detail_file.h b/src/modules/proto_detail/old/proto_detail_file.h
new file mode 100644 (file)
index 0000000..2fa24fb
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ *  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_DETAIL_H
+#define _FR_DETAIL_H
+/**
+ * $Id$
+ *
+ * @file proto_detail_file.h
+ * @brief API to deserialise packets in detail file format and inject them into the server.
+ *
+ * @copyright 2015  The FreeRADIUS server project
+ */
+RCSIDH(detail_h, "$Id$")
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum detail_file_state_t {
+       STATE_UNOPENED = 0,
+       STATE_UNLOCKED,
+       STATE_PROCESSING,
+} detail_file_state_t;
+
+typedef enum detail_entry_state_t {
+       STATE_HEADER = 0,
+       STATE_VPS,
+       STATE_QUEUED,
+       STATE_RUNNING,
+       STATE_NO_REPLY,
+       STATE_REPLIED
+} detail_entry_state_t;
+
+typedef struct listen_detail_t {
+       fr_event_timer_t const  *ev;    /* has to be first entry (ugh) */
+       char const      *name;                  //!< Identifier used in log messages
+       int             delay_time;
+       char const      *filename;
+       char const      *filename_work;
+       VALUE_PAIR      *vps;
+       int             work_fd;
+
+       int             master_pipe[2];
+       int             child_pipe[2];
+       pthread_t       pthread_id;
+
+       FILE            *fp;
+       off_t           offset;
+       detail_file_state_t     file_state;
+       detail_entry_state_t    entry_state;
+       time_t          timestamp;
+       time_t          running;
+       fr_ipaddr_t     client_ip;
+
+       off_t           last_offset;
+       off_t           timestamp_offset;
+       bool            done_entry;             //!< Are we done reading this entry?
+       bool            track;                  //!< Do we track progress through the file?
+
+       uint32_t        load_factor; /* 1..100 */
+       uint32_t        poll_interval;
+       uint32_t        retry_interval;
+
+       int             signal;
+       int             packets;
+       int             tries;
+       bool            one_shot;
+       int             outstanding;
+       int             has_rtt;
+       int             srtt;
+       int             rttvar;
+       uint32_t        counter;
+       struct timeval  last_packet;
+       RADCLIENT       detail_client;
+} listen_detail_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FR_DETAIL_H */
diff --git a/src/modules/proto_radius/dynamic_clients.c b/src/modules/proto_radius/dynamic_clients.c
new file mode 100644 (file)
index 0000000..05cf70d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ * @file dynamic_clients.c
+ * @brief Track dynamic clients
+ *
+ * @copyright 2018 The FreeRADIUS server project.
+ * @copyright 2018 Alan DeKok (aland@deployingradius.com)
+ */
+#include <netdb.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/protocol.h>
+#include <freeradius-devel/udp.h>
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/io/io.h>
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/io/track.h>
+#include <freeradius-devel/io/listen.h>
+#include <freeradius-devel/io/schedule.h>
+#include <freeradius-devel/rad_assert.h>
+#include "proto_radius.h"
+
+typedef struct dynamic_client_t {
+       dl_instance_t                   *submodule;             //!< proto_radius_dynamic_client
+       fr_ipaddr_t                     *network;               //!< dynamic networks to allow
+
+       RADCLIENT_LIST                  *clients;               //!< local clients
+       RADCLIENT_LIST                  *expired;               //!< expired local clients
+
+       fr_dlist_t                      packets;                //!< list of accepted packets
+       fr_dlist_t                      pending;                //!< pending clients
+
+       uint32_t                        max_clients;            //!< maximum number of dynamic clients
+       uint32_t                        num_clients;            //!< total number of active clients
+       uint32_t                        max_pending_clients;    //!< maximum number of pending clients
+       uint32_t                        num_pending_clients;    //!< number of pending clients
+       uint32_t                        max_pending_packets;    //!< maximum accepted pending packets
+       uint32_t                        num_pending_packets;    //!< how many packets are received, but not accepted
+
+       uint32_t                        lifetime;               //!< of the dynamic client, in seconds.
+} dynamic_client_t;
+
+typedef struct dynamic_packet_t {
+       uint8_t                 *packet;
+       fr_tracking_entry_t     *track;
+       fr_dlist_t              entry;
+} dynamic_packet_t;
+
+static const CONF_PARSER dynamic_client_config[] = {
+       { FR_CONF_OFFSET("network", FR_TYPE_COMBO_IP_PREFIX | FR_TYPE_MULTI, dynamic_client_t, network) },
+
+       { FR_CONF_OFFSET("max_clients", FR_TYPE_UINT32, dynamic_client_t, max_clients), .dflt = "65536" },
+       { FR_CONF_OFFSET("max_pending_clients", FR_TYPE_UINT32, dynamic_client_t, max_pending_clients), .dflt = "256" },
+       { FR_CONF_OFFSET("max_pending_packets", FR_TYPE_UINT32, dynamic_client_t, max_pending_packets), .dflt = "65536" },
+
+       { FR_CONF_OFFSET("lifetime", FR_TYPE_UINT32, dynamic_client_t, lifetime), .dflt = "600" },
+
+       CONF_PARSER_TERMINATOR
+};
diff --git a/src/modules/proto_radius/fix b/src/modules/proto_radius/fix
new file mode 100755 (executable)
index 0000000..0e1c32c
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+
+@array = ( "max_connections", "max_clients", "max_pending_packets", "num_connections", "num_clients",
+                "num_pending_packets", "cleanup_delay", "idle_timeout", "nak_lifetime", "check_interval",
+                "dynamic_clients", "listen", "sc", "ipproto", "transport", "el", "nr", "trie",
+                "networks", "pending_clients" );
+
+while (<>) {
+    for my $i (0 .. $#array) {
+       s/inst->$array[$i]/inst->io.$array[$i]/g;
+       s/proto_radius_t, $array[$i]/proto_radius_t, io.$array[$i]/g;
+    }
+
+    print;
+}
diff --git a/src/modules/proto_radius/notes.md b/src/modules/proto_radius/notes.md
new file mode 100644 (file)
index 0000000..c144307
--- /dev/null
@@ -0,0 +1,63 @@
+# TO DO
+
+* add proto_radius_connection_io as mostly a clone of master_io
+  * which lets us get rid of the horrible `get_inst` hack.
+  * We can then either split all functions into 2 which is code duplication...
+  * or, have each function call underlying one with both inst && connection...
+
+* REQUIRED doesn't work for "network"?
+  * src/main/cf_parse.c[884]: Configuration item "network" must have a value
+  * that's a TERRIBLE message.  it should be a fake filename?  <internal>...
+
+* add "new connection" method to sites-available/default ?
+  * to separate it from new client?
+
+## fr_io_instance_t
+
+* app_io_private
+  * connection set - should just move to app_io function callback
+  * network_get is run in proto_radius
+  * should be called from io bootstrap function
+
+* add mod_bootstrap to io.c
+  * and then grabs network information using public API
+* add mod_instantiate to io.c
+  * io.c calls app_io->instantiate
+  * and creates the client trie
+* add mod_open to io.c
+  * io.c makes the listener
+* move non-proto_radius headers to src/lib/io.h
+* move io.c to src/lib/io/io.c
+* and have it work!
+
+Things which need to be abstracted
+
+* priorities
+* process by code
+  * probably just process set?
+  * or abstract a way for the app method to know which packets it should accept.
+* cleanup delay
+  * for access-request.  Not everything needs this
+* connection set
+
+## proto_radius uses
+
+* code_allowed
+* process_by_code
+  * can probably be a callback
+
+* app_io for decode / encode
+  * app_io_instance
+
+* max_packet_size
+* num_messages
+
+## TO DO
+
+* move bootstrap / instantiate of app_io to io.c
+  * and add a bootstrap / instantiate method there
+  * have io.c initialize all of the stuff it uses
+* have fr_io_instance_t public
+  * so that proto_radius can initialize some things...
+  * have TALLOC_CTX at the start, so that it can clean things up
+  * for parsing simplicity, the struct should just be inline in proto_radius_t
diff --git a/src/modules/proto_radius/notes.txt b/src/modules/proto_radius/notes.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/modules/proto_radius/tacacs b/src/modules/proto_radius/tacacs
new file mode 100644 (file)
index 0000000..e38cfb3
--- /dev/null
@@ -0,0 +1,181 @@
+  Let me recap the history of this document.  There's a lot to take
+in, so I'll present concerns in point form.  First, the document.
+Second, engagement with the WG.  Third, reviews.  Fourth, process.
+And finally steps to take to address the ongoing issues.
+
+  First, the document.
+
+* my "Security Considerations" text was first plagarised in draft-06,
+
+* when I pointed this out (May 9, 2017), there was no reply to my
+  message by the authors, and no change was made to the document.
+
+* the ADs did respond, and indicated that they had talked to the
+  authors about the issue, and that it was a simple misunderstanding
+  and would be fixed.
+
+* A year later, I raised the issue again (March 2018).  There as no
+  reply to my concerns by the authors, and no change was made to the
+  document.
+
+* in all, 4 separate revisions of the document plagarized my text, for
+  over a year, sometimes with minor edits, despite repeated requests
+  to address the issue.
+
+  Those issues alone are surprising.
+
+* The text which was good enough to plagarize was then claimed to have
+  deficiencies
+
+* no one in the WG had noted any technical issues with the text
+
+* the only issues were with attribution, not with the text in the
+  Security Considerations section
+
+* there is now a -10, which has essentially the same points as the
+  previous text, just reworded
+
+  I should point out that the RFC process is supposed to be about
+content, not authorship.  There are many RFCs issued with text written
+by multiple people.  Where the authors cannot all be acknowledged on
+the first page, the primary editor can be listed with the (Ed.)
+suffix, to indicate editorship.  Other authors can be named as authors
+on the first page, or in the Acknowledgements section.
+
+  Second, concerns with engagement with the WG.  It continues.
+
+* multiple people in the WG have requested the authors engage with the
+  Working Group.  Most notably, many messages in May 2017.
+
+* multiple people in the WG have requested the authors explain what's
+  changed in each new revision, or perhaps to acknowlege comments and
+  reviews (May 2017 again, among other times).
+
+* This engagement has been minimal, despite multiple revisions of
+  the document being published after these WG requests.
+
+* new revisions have most often been "thrown over the wall" with
+  minimal (or no) explanation as to what changed, and why.
+
+* this new draft is no different, i.e. it "revised the security
+  section".  Why?  How?  What changed?  What were any alleged
+  "deficiencies"?
+
+* the author have stated again that they "will endeavor to be much
+  more reactive to comments".
+
+* this statement or similar ones have been made repeatedly, with
+  little change in observable behavior.
+
+* The authors request (again) that the WG review this new document
+  wholesale.
+
+  Speaking of reviews, let's continue with third, responses to
+reviews.
+
+* I have given multiple line-by-line reviews of the entire document,
+  for multiple revisions of the document.
+
+* as noted above, these reviews have generally been ignored.
+
+* as noted above, new versions of the document have appeared which may
+  or may not have addressed these reviews.
+
+* Given the lack of feedback on the reviews before a new draft is
+  issued, it is unclear whether the review comments have been
+  addressed.
+
+* due to these issues, I have stopped reviewing the document, as it
+  is not productive.
+
+  There are other issues with the larger community.  I'll continue.
+
+* there was broad support for publication of early revisions of the
+  document, despite it clearly not describing the protocol in a way
+  that permits inter-operable implementations
+
+* the people who supported adopting the document as a WG document
+  have generally not reviewed or commented on it
+
+* existing implementors of TACACS+ have not reviewed or commented on
+  the document (Alex Clouter's review was done as part of a brand-new
+  implementation)
+
+* we have therefore no idea whether or not this document describes
+  anything that anyone has implemented
+
+  In order for the document to be published, we should have (at the
+minimum) statements from multiple implementors that the draft matches
+their implementations.
+
+  Fourth, there are process issues, too.  I'll continue.
+
+* the IETF has traditionally started a new WG to standardize new
+  management protocols (i.e. a protocol new to the IETF)
+
+* examples include the protocols described in RFC 6632.  These
+  protocols have had their own working groups, going back to at least
+  1996, and continuing to the present
+
+* working on a new (and eventually standards track) document in the
+  OPS area is therefore a new step for the IETF
+
+* I have raised all of the concerns mentioned herein with the chairs
+  and ADs in private email, public email on the list, and in person at
+  multiple meetings
+
+* There does not appear to be any action taken as a result.
+
+* Messages to the list raising these issues have largely been
+  unaddressed
+
+  It is general IETF practice for document authors to interact with
+the WG. And, to respond to WG reviews and comments.  See the many
+comments in the OPSAWG archives in May 2017 for broad WG support of
+this position.
+
+  As the authors have largely ignored the WG comments, the chairs and
+ADs should have taken steps to address this issue.  As of this
+writing, it appears to be the same "status quo" of the past few years.
+
+  I don't think these comments are unreasonable.  If we truly don't
+care about the WG opinion, then the chairs and AD can:
+
+* make a public statement saying that WG feedback can be ignored
+
+* state that that the document can be published in whatever form the
+  authors choose
+
+* publish the document even if it does not describe the protocol in
+  sufficient detail to create inter-operable implementations.
+
+  I don't think that's the right approach, of course.  But it's
+largely what's been happening due to silence and/or inaction of the
+parties involved.
+
+  I see no point in further reviewing the document.  I again oppose
+publication of this document until such time as all of the WG issues
+have been addressed.
+
+  If we do actually care about what's in the document, then I suggest
+finally the following steps be taken to address the ongoing concerns:
+
+* the document should not be published until such time as multiple
+  implementors have agreed that the specification matches their
+  implementation.
+
+* the chairs and ADs should insist that the authors address the
+  outstanding WG concerns about this document
+
+* the chairs and ADs should insist that authors follow established
+  practice of *interacting* with the WG, including responding to
+  future reviews and comments.
+
+* the chairs and ADs should insist that new revisions of the document
+  are accompanied by an explanation of what changed, and why
+
+* that if the authors do not follow these steps (as they have not so
+  far), that the chairs and ADs should replace them with other WG
+  member(s) who will follow the IETF process.
+
+  Alan DeKok.
diff --git a/src/modules/proto_vmps/udp.c b/src/modules/proto_vmps/udp.c
new file mode 100644 (file)
index 0000000..32d4886
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ * @file proto_vmps_udp.c
+ * @brief VMPS handler for UDP.
+ *
+ * @copyright 2016 The Freeradius server project.
+ * @copyright 2016 Alan DeKok (aland@deployingradius.com)
+ */
+#include <netdb.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/protocol.h>
+#include <freeradius-devel/udp.h>
+#include <freeradius-devel/io/io.h>
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/io/listen.h>
+#include <freeradius-devel/rad_assert.h>
+#include "proto_vmps.h"
+
+typedef struct {
+       proto_vmps_t    const           *parent;                //!< The module that spawned us!
+
+       int                             sockfd;
+
+       fr_ipaddr_t                     ipaddr;                 //!< Ipaddr to listen on.
+
+       bool                            ipaddr_is_set;          //!< ipaddr config item is set.
+       bool                            ipv4addr_is_set;        //!< ipv4addr config item is set.
+       bool                            ipv6addr_is_set;        //!< ipv6addr config item is set.
+
+       char const                      *interface;             //!< Interface to bind to.
+       char const                      *port_name;             //!< Name of the port for getservent().
+
+       uint16_t                        port;                   //!< Port to listen on.
+       uint32_t                        recv_buff;              //!< How big the kernel's receive buffer should be.
+       bool                            recv_buff_is_set;       //!< Whether we were provided with a receive
+                                                               //!< buffer value.
+} proto_vmps_udp_t;
+
+static const CONF_PARSER udp_listen_config[] = {
+       { FR_CONF_IS_SET_OFFSET("ipaddr", FR_TYPE_COMBO_IP_ADDR, proto_vmps_udp_t, ipaddr) },
+       { FR_CONF_IS_SET_OFFSET("ipv4addr", FR_TYPE_IPV4_ADDR, proto_vmps_udp_t, ipaddr) },
+       { FR_CONF_IS_SET_OFFSET("ipv6addr", FR_TYPE_IPV6_ADDR, proto_vmps_udp_t, ipaddr) },
+
+       { FR_CONF_OFFSET("interface", FR_TYPE_STRING, proto_vmps_udp_t, interface) },
+       { FR_CONF_OFFSET("port_name", FR_TYPE_STRING, proto_vmps_udp_t, port_name) },
+
+       { FR_CONF_OFFSET("port", FR_TYPE_UINT16, proto_vmps_udp_t, port) },
+       { FR_CONF_IS_SET_OFFSET("recv_buff", FR_TYPE_UINT32, proto_vmps_udp_t, recv_buff) },
+
+       CONF_PARSER_TERMINATOR
+};
+
+
+/** Return the src address associated with the packet_ctx
+ *
+ */
+static int mod_src_address(fr_socket_addr_t *src, UNUSED void const *instance, void const *packet_ctx)
+{
+       fr_ip_srcdst_t const *ip = packet_ctx;
+
+       memset(src, 0, sizeof(*src));
+
+       src->proto = IPPROTO_UDP;
+       memcpy(&src->ipaddr, &ip->src_ipaddr, sizeof(src->ipaddr));
+
+       return 0;
+}
+
+/** Return the dst address associated with the packet_ctx
+ *
+ */
+static int mod_dst_address(fr_socket_addr_t *dst, UNUSED void const *instance, void const *packet_ctx)
+{
+       fr_ip_srcdst_t const *ip = packet_ctx;
+
+       memset(dst, 0, sizeof(*dst));
+
+       dst->proto = IPPROTO_UDP;
+       memcpy(&dst->ipaddr, &ip->dst_ipaddr, sizeof(dst->ipaddr));
+
+       return 0;
+}
+
+
+/** Decode the packet.
+ *
+ */
+static int mod_decode(UNUSED void const *instance, UNUSED REQUEST *request, UNUSED uint8_t *const data, UNUSED size_t data_len)
+{
+#if 0
+//     proto_vmps_udp_t const *inst = talloc_get_type_abort_const(instance, proto_vmps_udp_t);
+       fr_ip_srcdst_t *ip;
+       uint8_t *packet;
+       size_t packet_len;
+
+       ip = talloc_memdup(request, request->async->packet_ctx, sizeof(*ip));
+       if (!ip) return -1;
+
+       request->async->packet_ctx = ip;
+
+       packet = data + sizeof(*ip);
+       packet_len = data_len - sizeof(*ip);
+
+       // decode the packet into attributes.
+#endif
+
+       return 0;
+}
+
+static ssize_t mod_encode(UNUSED void const *instance, UNUSED REQUEST *request, UNUSED uint8_t *buffer, UNUSED size_t buffer_len)
+{
+#if 0
+//     proto_vmps_udp_t const *inst = talloc_get_type_abort_const(instance, proto_vmps_udp_t);
+       fr_ip_srcdst_t *ip;
+       uint8_t *packet;
+       size_t packet_len;
+
+       ip = request->async->packet_ctx;
+       packet = buffer + sizeof(*ip);
+       packet_len = buffer_len - sizeof(*ip);
+
+       memcpy(buffer, ip, sizeof(*ip));
+
+       // encode packet in buffer
+#endif
+
+       return 0;
+}
+
+static ssize_t mod_read(void *instance, void **packet_ctx, fr_time_t **recv_time, uint8_t *buffer, size_t buffer_len, size_t *leftover, uint32_t *priority, bool *is_dup)
+{
+       proto_vmps_udp_t const          *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+       fr_ip_srcdst_t                  *ip;
+       uint8_t                         *packet;
+       size_t                          packet_len;
+       ssize_t                         data_size;
+       struct timeval                  timestamp;
+
+       ip = (fr_ip_srcdst_t *) buffer; /* @todo - should be aligned */
+       packet = buffer + sizeof(*ip);
+       packet_len = buffer_len - sizeof(*ip);
+       *leftover = 0;
+       *is_dup = false;
+
+       data_size = udp_recv(inst->sockfd, packet, packet_len, 0,
+                            &ip->src_ipaddr, &ip->src_port,
+                            &ip->dst_ipaddr, &ip->dst_port,
+                            &ip->if_index, &timestamp);
+       if (data_size <= 0) return data_size;
+
+       packet_len = data_size;
+
+       /*
+        *      If it's not a VMPS packet, ignore it.
+        */
+       if (!fr_vqp_ok(packet, &packet_len)) return 0;
+
+       *packet_ctx = ip;
+       *recv_time = NULL;
+       *priority = PRIORITY_NORMAL;
+
+       return packet_len + sizeof(*ip);
+}
+
+static ssize_t mod_write(void *instance, void *packet_ctx,
+                        UNUSED fr_time_t request_time, uint8_t *buffer, size_t buffer_len)
+{
+       proto_vmps_udp_t        *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+       fr_ip_srcdst_t          *ip = packet_ctx;
+       uint8_t                 *packet;
+       size_t                  packet_len;
+       ssize_t                 data_size;
+
+       rad_assert(packet_ctx == buffer);
+
+       /*
+        *      Don't reply.
+        */
+       if (buffer_len == 1) return buffer_len;
+
+       packet = buffer + sizeof(*ip);
+       packet_len = buffer_len - sizeof(*ip);
+
+
+       /*
+        *      Only write replies if they're VMPS packets.
+        *      sometimes we want to NOT send a reply...
+        */
+       data_size = udp_send(inst->sockfd, packet, packet_len, 0,
+                            &ip->dst_ipaddr, ip->dst_port,
+                            ip->if_index,
+                            &ip->src_ipaddr, ip->src_port);
+       if (data_size < 0) return data_size;
+
+       /*
+        *      Tell the caller we've written it all.
+        */
+       return buffer_len;
+}
+
+/** Open a UDP listener for VMPS
+ *
+ * @param[in] instance of the VMPS UDP I/O path.
+ * @return
+ *     - <0 on error
+ *     - 0 on success
+ */
+static int mod_open(void *instance)
+{
+       proto_vmps_udp_t                *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+
+       int                             sockfd = 0;
+       uint16_t                        port = inst->port;
+
+       sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true);
+       if (sockfd < 0) {
+               PERROR("Failed creating UDP socket");
+       error:
+               return -1;
+       }
+
+       if (fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface) < 0) {
+               PERROR("Failed binding socket");
+               goto error;
+       }
+
+       inst->sockfd = sockfd;
+
+       return 0;
+}
+
+/** Get the file descriptor for this socket.
+ *
+ * @param[in] instance of the VMPS UDP I/O path.
+ * @return the file descriptor
+ */
+static int mod_fd(void const *instance)
+{
+       proto_vmps_udp_t const *inst = talloc_get_type_abort_const(instance, proto_vmps_udp_t);
+
+       return inst->sockfd;
+}
+
+
+static int mod_instantiate(void *instance, CONF_SECTION *cs)
+{
+       proto_vmps_udp_t *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+
+       /*
+        *      Default to all IPv6 interfaces (it's the future)
+        */
+       if (!inst->ipaddr_is_set && !inst->ipv4addr_is_set && !inst->ipv6addr_is_set) {
+               inst->ipaddr.af = AF_INET6;
+               inst->ipaddr.prefix = 128;
+               inst->ipaddr.addr.v6 = in6addr_any;     /* in6addr_any binds to all addresses */
+       }
+
+       if (inst->recv_buff_is_set) {
+               FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, 32);
+               FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, INT_MAX);
+       }
+
+       if (!inst->port) {
+               struct servent *s;
+
+               if (!inst->port_name) {
+                       cf_log_err(cs, "No 'port' specified in 'udp' section");
+                       return -1;
+               }
+
+               s = getservbyname(inst->port_name, "udp");
+               if (!s) {
+                       cf_log_err(cs, "Unknown value for 'port_name = %s", inst->port_name);
+                       return -1;
+               }
+
+               inst->port = ntohl(s->s_port);
+       }
+
+       return 0;
+}
+
+static int mod_bootstrap(void *instance, UNUSED CONF_SECTION *cs)
+{
+       proto_vmps_udp_t        *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+       dl_instance_t const     *dl_inst;
+
+       /*
+        *      Find the dl_instance_t holding our instance data
+        *      so we can find out what the parent of our instance
+        *      was.
+        */
+       dl_inst = dl_instance_find(instance);
+       rad_assert(dl_inst);
+
+       inst->parent = talloc_get_type_abort(dl_inst->parent->data, proto_vmps_t);
+
+       return 0;
+}
+
+static int mod_detach(void *instance)
+{
+       proto_vmps_udp_t        *inst = talloc_get_type_abort(instance, proto_vmps_udp_t);
+
+       /*
+        *      @todo - have our OWN event loop for timers, and a
+        *      "copy timer from -> to, which means we only have to
+        *      delete our child event loop from the parent on close.
+        */
+
+       close(inst->sockfd);
+       return 0;
+}
+
+
+/** Private interface for use by proto_vmps
+ *
+ */
+extern proto_vmps_app_io_t proto_vmps_app_io_private;
+proto_vmps_app_io_t proto_vmps_app_io_private = {
+       .src                    = mod_src_address,
+       .dst                    = mod_dst_address
+};
+
+extern fr_app_io_t proto_vmps_udp;
+fr_app_io_t proto_vmps_udp = {
+       .magic                  = RLM_MODULE_INIT,
+       .name                   = "vmps_udp",
+       .config                 = udp_listen_config,
+       .inst_size              = sizeof(proto_vmps_udp_t),
+       .detach                 = mod_detach,
+       .bootstrap              = mod_bootstrap,
+       .instantiate            = mod_instantiate,
+
+       .default_message_size   = 4096,
+       .open                   = mod_open,
+       .read                   = mod_read,
+       .decode                 = mod_decode,
+       .encode                 = mod_encode,
+       .write                  = mod_write,
+       .fd                     = mod_fd,
+};
index 9227b0a36cfacad337e734fe301a4354b3f80272..24c5161d486743af661b37d4b5642834708f9d96 100644 (file)
@@ -823,7 +823,7 @@ static ssize_t cache_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t freespace,
                              request, inst->config.key, NULL, NULL);
        if (key_len < 0) return -1;
 
-       slen = tmpl_afrom_attr_substr(ctx, NULL, &target, fmt,
+       slen = tmpl_afrom_attr_substr(ctx, NULL, &target, fmt, -1,
                                      &(vp_tmpl_rules_t){
                                                .dict_def = request->dict,
                                                .prefix = VP_ATTR_REF_PREFIX_AUTO
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#
new file mode 100644 (file)
index 0000000..09bff6c
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * rlm_eap_tls.c  contains the interfaces that are called from eap
+ *
+ * Version:     $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
+ * @copyright 2003  Alan DeKok <aland@freeradius.org>
+ * @copyright 2006  The FreeRADIUS server project
+ *
+ */
+RCSID("$Id$")
+USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
+
+#define LOG_PREFIX "rlm_eap_tls - "
+
+#ifdef HAVE_OPENSSL_RAND_H
+#  include <openssl/rand.h>
+#endif
+#ifdef HAVE_OPENSSL_EVP_H
+#  include <openssl/evp.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#  include <sys/stat.h>
+#endif
+
+#include <freeradius-devel/unlang/base.h>
+
+#include "rlm_eap_tls.h"
+
+static CONF_PARSER submodule_config[] = {
+       { FR_CONF_OFFSET("tls", FR_TYPE_STRING, rlm_eap_tls_t, tls_conf_name) },
+
+       { FR_CONF_OFFSET("require_client_cert", FR_TYPE_BOOL, rlm_eap_tls_t, req_client_cert), .dflt = "yes" },
+       { FR_CONF_OFFSET("include_length", FR_TYPE_BOOL, rlm_eap_tls_t, include_length), .dflt = "yes" },
+       { FR_CONF_OFFSET("virtual_server", FR_TYPE_STRING, rlm_eap_tls_t, virtual_server) },
+       CONF_PARSER_TERMINATOR
+};
+
+static fr_dict_t *dict_freeradius;
+
+extern fr_dict_autoload_t rlm_eap_tls_dict[];
+fr_dict_autoload_t rlm_eap_tls_dict[] = {
+       { .out = &dict_freeradius, .proto = "freeradius" },
+       { NULL }
+};
+
+static fr_dict_attr_t const *attr_eap_tls_require_client_cert;
+static fr_dict_attr_t const *attr_virtual_server;
+
+extern fr_dict_attr_autoload_t rlm_eap_tls_dict_attr[];
+fr_dict_attr_autoload_t rlm_eap_tls_dict_attr[] = {
+       { .out = &attr_eap_tls_require_client_cert, .name = "EAP-TLS-Require-Client-Cert", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
+       { .out = &attr_virtual_server, .name = "Virtual-Server", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
+       { NULL }
+};
+
+/*
+ *     Do authentication, by letting EAP-TLS do most of the work.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_process(void *instance, void *thread, REQUEST *request);
+
+static rlm_rcode_t eap_tls_success_with_prf(eap_session_t *eap_session)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+       tls_session_t           *tls_session = eap_tls_session->tls_session;
+
+       /*
+        *      Set the PRF label based on the TLS version negotiated
+        *      in the handshake.
+        */
+       switch (SSL_SESSION_get_protocol_version(SSL_get_session(tls_session->ssl))) {
+       case SSL2_VERSION:                      /* Should never happen */
+       case SSL3_VERSION:                      /* Should never happen */
+               rad_assert(0);
+               return RLM_MODULE_INVALID;
+
+       case TLS1_VERSION:
+       case TLS1_1_VERSION:
+       case TLS1_2_VERSION:
+#endif
+       {
+               static char const keying_prf_label[] = "client EAP encryption";
+
+               if (eap_tls_success(eap_session,
+                                   keying_prf_label, sizeof(keying_prf_label) - 1,
+                                   NULL, 0) < 0) return RLM_MODULE_FAIL;
+       }
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+               break;
+
+       case TLS1_3_VERSION:
+       default:
+       {
+               static char const keying_prf_label[] = "EXPORTER_EAP_TLS_Key_Material";
+               static char const sessid_prf_label[] = "EXPORTER_EAP_TLS_Method-Id";
+
+               if (eap_tls_success(eap_session,
+                                   keying_prf_label, sizeof(keying_prf_label) - 1,
+                                   sessid_prf_label, sizeof(sessid_prf_label) - 1) < 0) return RLM_MODULE_FAIL;
+       }
+               break;
+       }
+#endif
+       return RLM_MODULE_OK;
+}
+
+static unlang_action_t eap_tls_virtual_server_result(REQUEST *request, rlm_rcode_t *presult,
+                                                    UNUSED int *priority, void *uctx)
+{
+       eap_session_t   *eap_session = talloc_get_type_abort(uctx, eap_session_t);
+
+       switch (*presult) {
+       case RLM_MODULE_OK:
+       case RLM_MODULE_UPDATED:
+               *presult = eap_tls_success_with_prf(eap_session);
+               break;
+
+       default:
+               REDEBUG2("Certificate rejected by the virtual server");
+               eap_tls_fail(eap_session);
+               *presult = RLM_MODULE_REJECT;
+               break;
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+static rlm_rcode_t eap_tls_virtual_server(rlm_eap_tls_t *inst, eap_session_t *eap_session)
+{
+       REQUEST         *request = eap_session->request;
+       CONF_SECTION    *server_cs;
+       CONF_SECTION    *section;
+       VALUE_PAIR      *vp;
+
+       /* set the virtual server to use */
+       vp = fr_pair_find_by_da(request->control, attr_virtual_server, TAG_ANY);
+       if (vp) {
+               server_cs = virtual_server_find(vp->vp_strvalue);
+               if (!server_cs) {
+                       REDEBUG2("Virtual server \"%s\" not found", vp->vp_strvalue);
+               error:
+                       eap_tls_fail(eap_session);
+                       return RLM_MODULE_INVALID;
+               }
+       } else {
+               server_cs = virtual_server_find(inst->virtual_server);
+               rad_assert(server_cs);
+       }
+
+       section = cf_section_find(server_cs, "recv", "Access-Request");
+       if (!section) {
+               REDEBUG2("Failed finding 'recv Access-Request { ... }' section of virtual server %s",
+                        cf_section_name2(server_cs));
+               goto error;
+       }
+
+       if (!unlang_section(section)) {
+               REDEBUG("Failed to find pre-compiled unlang for section %s %s { ... }",
+                       cf_section_name1(server_cs), cf_section_name2(server_cs));
+               goto error;
+       }
+
+       RDEBUG2("Validating certificate");
+
+       /*
+        *      Catch the interpreter on the way back up the stack
+        */
+       unlang_interpret_push_function(request, NULL, eap_tls_virtual_server_result, eap_session);
+
+       /*
+        *      Push unlang instructions for the virtual server section
+        */
+       unlang_interpret_push_section(request, section, RLM_MODULE_NOOP, UNLANG_SUB_FRAME);
+
+       return RLM_MODULE_YIELD;
+}
+
+static rlm_rcode_t mod_process(void *instance, UNUSED void *thread, REQUEST *request)
+{
+       eap_tls_status_t        status;
+
+       rlm_eap_tls_t           *inst = talloc_get_type_abort(instance, rlm_eap_tls_t);
+       eap_session_t           *eap_session = eap_session_get(request);
+       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+       tls_session_t           *tls_session = eap_tls_session->tls_session;
+
+       status = eap_tls_process(eap_session);
+       if ((status == EAP_TLS_INVALID) || (status == EAP_TLS_FAIL)) {
+               REDEBUG("[eap-tls process] = %s", fr_int2str(eap_tls_status_table, status, "<INVALID>"));
+       } else {
+               RDEBUG2("[eap-tls process] = %s", fr_int2str(eap_tls_status_table, status, "<INVALID>"));
+       }
+
+       switch (status) {
+       /*
+        *      EAP-TLS handshake was successful, return an
+        *      EAP-TLS-Success packet here.
+        *
+        *      If a virtual server was configured, check that
+        *      it accepts the certificates, too.
+        */
+       case EAP_TLS_ESTABLISHED:
+               if (inst->virtual_server) return eap_tls_virtual_server(inst, eap_session);
+               return eap_tls_success_with_prf(eap_session);
+
+
+       /*
+        *      The TLS code is still working on the TLS
+        *      exchange, and it's a valid TLS request.
+        *      do nothing.
+        */
+       case EAP_TLS_HANDLED:
+               return RLM_MODULE_HANDLED;
+
+       /*
+        *      Handshake is done, proceed with decoding tunneled
+        *      data.
+        */
+       case EAP_TLS_RECORD_RECV_COMPLETE:
+               REDEBUG("Received unexpected tunneled data after successful handshake");
+               eap_tls_fail(eap_session);
+
+               return RLM_MODULE_INVALID;
+
+       /*
+        *      Anything else: fail.
+        *
+        *      Also, remove the session from the cache so that
+        *      the client can't re-use it.
+        */
+       default:
+               tls_cache_deny(tls_session);
+
+               return RLM_MODULE_REJECT;
+       }
+}
+
+/*
+ *     Send an initial eap-tls request to the peer, using the libeap functions.
+ */
+static rlm_rcode_t mod_session_init(void *instance, UNUSED void *thread, REQUEST *request)
+{
+       rlm_eap_tls_t           *inst = talloc_get_type_abort(instance, rlm_eap_tls_t);
+       eap_session_t           *eap_session = eap_session_get(request);
+       eap_tls_session_t       *eap_tls_session;
+
+       VALUE_PAIR              *vp;
+       bool                    client_cert;
+
+       eap_session->tls = true;
+
+       /*
+        *      EAP-TLS-Require-Client-Cert attribute will override
+        *      the require_client_cert configuration option.
+        */
+       vp = fr_pair_find_by_da(eap_session->request->control, attr_eap_tls_require_client_cert, TAG_ANY);
+       if (vp) {
+               client_cert = vp->vp_uint32 ? true : false;
+       } else {
+               client_cert = inst->req_client_cert;
+       }
+
+       /*
+        *      EAP-TLS always requires a client certificate.
+        */
+       eap_session->opaque = eap_tls_session = eap_tls_session_init(eap_session, inst->tls_conf, client_cert);
+       if (!eap_tls_session) return RLM_MODULE_FAIL;
+
+       eap_tls_session->include_length = inst->include_length;
+
+       /*
+        *      TLS session initialization is over.  Now handle TLS
+        *      related handshaking or application data.
+        */
+       if (eap_tls_start(eap_session) < 0) {
+               talloc_free(eap_tls_session);
+               return RLM_MODULE_FAIL;
+       }
+
+       eap_session->process = mod_process;
+
+       return RLM_MODULE_HANDLED;
+}
+
+/*
+ *     Attach the EAP-TLS module.
+ */
+static int mod_instantiate(void *instance, CONF_SECTION *cs)
+{
+       rlm_eap_tls_t *inst = talloc_get_type_abort(instance, rlm_eap_tls_t);
+
+       inst->tls_conf = eap_tls_conf_parse(cs, "tls");
+       if (!inst->tls_conf) {
+               ERROR("Failed initializing SSL context");
+               return -1;
+       }
+
+       if (inst->virtual_server && !virtual_server_find(inst->virtual_server)) {
+               cf_log_err_by_name(cs, "virtual_server", "Unknown virtual server '%s'", inst->virtual_server);
+               return -1;
+       }
+
+       return 0;
+}
+
+#define ACTION_SECTION(_out, _field, _verb, _name) \
+do { \
+       CONF_SECTION *_tmp; \
+       _tmp = cf_section_find(server_cs, _verb, _name); \
+       if (_tmp) { \
+               if (unlang_compile(_tmp, MOD_AUTHORIZE, NULL) < 0) return -1; \
+               found = true; \
+       } \
+       if (_out) _out->_field = _tmp; \
+} while (0)
+
+/** Compile virtual server sections
+ *
+ */
+static int mod_section_compile(eap_tls_actions_t *actions, CONF_SECTION *server_cs)
+{
+       bool found = false;
+
+       if (!fr_cond_assert(server_cs)) return -1;
+
+       ACTION_SECTION(actions, recv_access_request, "recv", "Access-Request");
+
+       /*
+        *      Warn if we couldn't find any actions.
+        */
+       if (!found) {
+               cf_log_warn(server_cs, "No \"eap-tls\" actions found in virtual server \"%s\"",
+                           cf_section_name2(server_cs));
+       }
+
+       return 0;
+}
+
+/** Compile any virtual servers with the "eap-tls" namespace
+ *
+ */
+static int mod_namespace_load(CONF_SECTION *server_cs)
+{
+       return mod_section_compile(NULL, server_cs);
+}
+
+static int mod_load(void)
+{
+//     if (virtual_server_namespace_register("eap-tls", mod_namespace_load) < 0) return -1;
+
+       return 0;
+}
+
+/*
+ *     The module name should be the only globally exported symbol.
+ *     That is, everything else should be 'static'.
+ */
+extern rlm_eap_submodule_t rlm_eap_tls;
+rlm_eap_submodule_t rlm_eap_tls = {
+       .name           = "eap_tls",
+       .magic          = RLM_MODULE_INIT,
+
+       .provides       = { FR_EAP_METHOD_TLS },
+       .inst_size      = sizeof(rlm_eap_tls_t),
+       .config         = submodule_config,
+       .instantiate    = mod_instantiate,      /* Create new submodule instance */
+
+       .onload         = mod_load,
+       .session_init   = mod_session_init,     /* Initialise a new EAP session */
+       .entry_point    = mod_process           /* Process next round of EAP method */
+};
index fde430830421733360196b2aa093ee849191f1aa..e2c0c1c6af29680eaca933b3f81eb3670c4fccec 100644 (file)
@@ -205,7 +205,7 @@ static bool get_number(REQUEST *request, char const **string, int64_t *answer)
                VALUE_PAIR      *vp;
                fr_cursor_t     cursor;
 
-               slen = tmpl_afrom_attr_substr(request, NULL, &vpt, p, &(vp_tmpl_rules_t){ .dict_def = request->dict });
+               slen = tmpl_afrom_attr_substr(request, NULL, &vpt, p, -1, &(vp_tmpl_rules_t){ .dict_def = request->dict });
                if (slen <= 0) {
                        RPEDEBUG("Failed parsing attribute name '%s'", p);
                        return false;
diff --git a/src/modules/rlm_json/libfreeradius-json.mk b/src/modules/rlm_json/libfreeradius-json.mk
new file mode 100644 (file)
index 0000000..e3c0087
--- /dev/null
@@ -0,0 +1,9 @@
+TARGETNAME     := libfreeradius-json
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := json_missing.c json.c jpath.c
+SRC_CFLAGS     :=
+TGT_LDLIBS     :=  -ljson-c
diff --git a/src/modules/rlm_radius/foo.c b/src/modules/rlm_radius/foo.c
new file mode 100644 (file)
index 0000000..3747cd6
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ *     rlm_radius state machine
+ */
+static void mod_radius_fd_idle(rlm_radius_connection_t *c);
+
+static void mod_radius_fd_active(rlm_radius_connection_t *c);
+
+static void mod_radius_conn_error(UNUSED fr_event_list_t *el, int sock, UNUSED int flags, int fd_errno, void *uctx);
+
+static int CC_HINT(nonnull) mod_add(rlm_radius_t *inst, rlm_radius_connection_t *c, REQUEST *request);
+
+
+#ifndef USEC
+#define USEC (1000000)
+#endif
+
+// @todo - pass in REQUEST, or maybe request_io_ctx, so we can store these numbers in the rlm_link_t?
+bool rlm_radius_update_delay(struct timeval *start, uint32_t *rt, uint32_t *count, int code, void *client_io_ctx, struct timeval *now)
+{
+       uint32_t delay, frac;
+       rlm_radius_retry_t const *retry;
+       rlm_radius_thread_t *t;
+
+       (void) talloc_get_type_abort(client_io_ctx, rlm_radius_client_io_ctx_t);
+
+       t = talloc_parent(client_io_ctx);
+
+       (void) talloc_get_type_abort(t, rlm_radius_thread_t);
+
+       rad_assert(code > 0);
+       rad_assert(code < FR_MAX_PACKET_CODE);
+       rad_assert(t->inst->packets[code].irt != 0);
+
+       retry = &t->inst->packets[code];
+
+       /*
+        *      First packet: use IRT.
+        */
+       if (!*rt) {
+               gettimeofday(start, NULL);
+               *rt = retry->irt * USEC;
+               *count = 1;
+               return true;
+       }
+
+       /*
+        *      Later packets, do more stuff.
+        */
+       (*count)++;
+
+       /*
+        *      We retried too many times.  Fail.
+        */
+       if (retry->mrc && (*count > retry->mrc)) {
+               return false;
+       }
+
+       /*
+        *      Cap delay at MRD
+        */
+       if (now && retry->mrd) {
+               struct timeval end;
+
+               end = *start;
+               end.tv_sec += retry->mrd;
+
+               if (timercmp(now, &end, >=)) {
+                       return false;
+               }
+       }
+
+       /*
+        *      RFC 5080 Section 2.2.1
+        *
+        *      RT = 2*RTprev + RAND*RTprev
+        *         = 1.9 * RTprev + rand(0,.2) * RTprev
+        *         = 1.9 * RTprev + rand(0,1) * (RTprev / 5)
+        */
+       delay = fr_rand();
+       delay ^= (delay >> 16);
+       delay &= 0xffff;
+       frac = *rt / 5;
+       delay = ((frac >> 16) * delay) + (((frac & 0xffff) * delay) >> 16);
+
+       delay += (2 * *rt) - (*rt / 10);
+
+       /*
+        *      Cap delay at MRT
+        */
+       if (retry->mrt && (delay > (retry->mrt * USEC))) {
+               int mrt_usec = retry->mrt * USEC;
+
+               /*
+                *      delay = MRT + RAND * MRT
+                *            = 0.9 MRT + rand(0,.2)  * MRT
+                */
+               delay = fr_rand();
+               delay ^= (delay >> 15);
+               delay &= 0x1ffff;
+               delay = ((mrt_usec >> 16) * delay) + (((mrt_usec & 0xffff) * delay) >> 16);
+               delay += mrt_usec - (mrt_usec / 10);
+       }
+
+       *rt = delay;
+
+       return true;
+}
+
+
+
+
+/*
+ *     So transports can calculate retransmission timers.
+ */
+bool rlm_radius_update_delay(struct timeval *start, uint32_t *rt, uint32_t *count, int code, void *client_io_ctx, struct timeval *now);
+
+typedef struct rlm_radius_client_io_ctx_t rlm_radius_client_io_ctx_t;
+
+
+
+static int mod_write(REQUEST *request, void *request_ctx, void *io_ctx)
+{
+       rlm_radius_client_io_ctx_t *io = talloc_get_type_abort(io_ctx, rlm_radius_client_io_ctx_t);
+       request_ctx_t *track = (request_ctx_t *) request_ctx; /* not talloc'd */
+       ssize_t packet_len, data_size;
+       rlm_radius_request_t *rr;
+
+       rad_assert(request->packet->code > 0);
+       rad_assert(request->packet->code < FR_MAX_PACKET_CODE);
+
+       /*
+        *      Create the tracking table if it doesn't already exist.
+        *
+        *      @todo - move this into the "init" routine?  where was can examine
+        *              the rlm_radius_t, and create the relevant data structures.
+        */
+       if (!io->id[request->packet->code]) {
+               io->id[request->packet->code] = rr_track_create(io_ctx);
+               if (!io->id[request->packet->code]) {
+                       RDEBUG("Failed creating tracking table for code %d", request->packet->code);
+                       return -1;
+               }
+       }
+
+       /*
+        *      Allocate an ID
+        */
+       rr = rr_track_alloc(io->id[request->packet->code], request, request->packet->code, io, request_ctx);
+       if (!rr) {
+               RDEBUG("Failed allocating packet ID for code %d", request->packet->code);
+               return -1;
+       }
+
+       packet_len = fr_radius_encode(io->buffer, io->buflen, NULL, io->inst->secret, strlen(io->inst->secret),
+                                     rr->code, rr->id, request->packet->vps);
+       if (packet_len < 0) {
+               RDEBUG("Failed encoding packet: %s", fr_strerror());
+
+               // @todo - distinguish write errors from encode errors?
+               return -1;
+       }
+
+       data_size = udp_send(io->fd, io->buffer, packet_len, 0,
+                            &io->dst_ipaddr, io->dst_port,
+                            0, /* if_index */
+                            &io->src_ipaddr, io->src_port);
+
+       // @todo - put the packet into an RB tree, too, so we can find replies...
+       memcpy(&track->header, io->buffer, 20);
+
+       if (data_size < packet_len) {
+               rad_assert(0 == 1);
+       }
+
+       return 1;
+}
+
+/** Get a printable name for the socket
+ *
+ */
+static char const *mod_get_name(TALLOC_CTX *ctx, void *io_ctx)
+{
+       rlm_radius_client_io_ctx_t *io = talloc_get_type_abort(io_ctx, rlm_radius_client_io_ctx_t);
+       char src_buf[FR_IPADDR_STRLEN], dst_buf[FR_IPADDR_STRLEN];
+
+       fr_inet_ntop(dst_buf, sizeof(dst_buf), &io->dst_ipaddr);
+
+       // @todo - make sure to get the local port number we're bound to
+
+       if (fr_ipaddr_is_inaddr_any(&io->inst->src_ipaddr)) {
+               return talloc_asprintf(ctx, "home server %s port %u", dst_buf, io->dst_port);
+       }
+
+       fr_inet_ntop(src_buf, sizeof(src_buf), &io->inst->src_ipaddr);
+       return talloc_asprintf(ctx, "from %s to home server %s port %u", src_buf, dst_buf, io->dst_port);
+}
+
+
+/** Shutdown/close a file descriptor
+ *
+ */
+static void mod_close(int fd, void *io_ctx)
+{
+       rlm_radius_client_io_ctx_t *io = talloc_get_type_abort(io_ctx, rlm_radius_client_io_ctx_t);
+
+       if (shutdown(fd, SHUT_RDWR) < 0) DEBUG3("Shutdown on socket (%i) failed: %s", fd, fr_syserror(errno));
+       if (close(fd) < 0) DEBUG3("Closing socket (%i) failed: %s", fd, fr_syserror(errno));
+
+       io->fd = -1;
+}
+
+/** Do more setup once the connection has been opened
+ *
+ */
+static fr_connection_state_t mod_open(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED void *io_ctx)
+{
+//     rlm_radius_client_io_ctx_t_t *io = talloc_get_type_abort(io_ctx, rlm_radius_client_io_ctx_t);
+
+       // @todo - create the initial Status-Server for negotiation and send that
+
+       return FR_CONNECTION_STATE_CONNECTED;
+}
+
+
+/** Initialize the connection.
+ *
+ */
+static fr_connection_state_t mod_init(int *fd_out, void *io_ctx, void const *uctx)
+{
+       int fd;
+       rlm_radius_client_io_ctx_t *io = talloc_get_type_abort(io_ctx, rlm_radius_client_io_ctx_t);
+       rlm_radius_udp_t const *inst = talloc_get_type_abort(uctx, rlm_radius_udp_t);
+
+       io->inst = inst;
+
+       io->max_packet_size = inst->max_packet_size;
+       io->buflen = io->max_packet_size;
+       io->buffer = talloc_array(io, uint8_t, io->buflen);
+
+       if (!io->buffer) {
+               return FR_CONNECTION_STATE_FAILED;
+       }
+
+       io->dst_ipaddr = inst->dst_ipaddr;
+       io->dst_port = inst->dst_port;
+       io->src_ipaddr = inst->src_ipaddr;
+       io->src_port = 0;
+
+       /*
+        *      Open the outgoing socket.
+        *
+        *      @todo - pass src_ipaddr, and remove later call to fr_socket_bind()
+        *      which does return the src_port, but doesn't set the "don't fragment" bit.
+        */
+       fd = fr_socket_client_udp(&io->src_ipaddr, &io->dst_ipaddr, io->dst_port, true);
+       if (fd < 0) {
+               DEBUG("Failed opening RADIUS client UDP socket: %s", fr_strerror());
+               return FR_CONNECTION_STATE_FAILED;
+       }
+
+#if 0
+       if (fr_socket_bind(fd, &io->src_ipaddr, &io->src_port, inst->interface) < 0) {
+               DEBUG("Failed binding RADIUS client UDP socket: %s FD %d %pV port %u interface %s", fr_strerror(), fd, fr_box_ipaddr(io->src_ipaddr),
+                       io->src_port, inst->interface);
+               return FR_CONNECTION_STATE_FAILED;
+       }
+#endif
+
+       // @todo - set recv_buff and send_buff socket options
+
+       io->fd = fd;
+
+       // @todo - initialize the tracking memory, etc.
+
+       *fd_out = fd;
+
+       return FR_CONNECTION_STATE_CONNECTING;
+}
diff --git a/src/modules/rlm_radius/notes.md b/src/modules/rlm_radius/notes.md
new file mode 100644 (file)
index 0000000..9819b29
--- /dev/null
@@ -0,0 +1,102 @@
+# converting to connection tracker...
+
+Notes:
+
+* probably want a "synchronous" flag in connection struct, it just makes things easier
+* and also whether or not we do status checks...
+
+## status_check_timeout()
+
+pretty much all RADIUS stuff
+
+## conn_zombie_timeout()
+
+about half RADIUS stuff
+
+* needs to know if status checks are used
+  * likely can go to a bool in connection
+
+* needs to have callback io_
+
+## state_transition()
+
+* add io_transition_state_out(ctx, u, c)
+ * returns true/false for continue
+ * on false the caller just returns
+ * due to status checks not transitioning to a different state
+  * and on true, sets c->slots_free
+
+* add io_transition_state_in(ctx, u, c)
+  * returns true/false for succeeded
+  * on false, it transitions u back to QUEUED
+  * and on true, sets c->slots_free
+
+## conn_read()
+
+* can move all of the RADIUS stuff into a callback
+  * it's all pretty well encapsulated
+
+## retransmit_packet()
+
+Essentially all RADIUS stuff
+
+* called from response_timeout (conn handler)
+* and mod_signal
+
+* should be io_retransmit_packet()
+
+## response_timeout()
+
+Top half is all RADIUS
+
+bottom half is connection handling
+* which calls retransmit_packet()
+
+## conn_write()
+
+Writes a packet to a connection
+
+Essentially 100% RADIUS
+
+## conn_writable()
+
+* checks for pending status packets, and writes those if necessary
+
+* calls conn_write() to do protocol-specific dirty work
+
+otherwise 100% non-RADIUS
+
+## udp_request_free()
+
+checks synchronous.  Otherwise 100% non-RADIUS
+
+## status_udp_request_free()
+
+essentially 100% RADIUS
+
+## _conn_failed()
+
+* needs RADIUS to delete status check retransmission timers and clean up the packets
+
+* ideally io_transition_failed()
+
+## _conn_open()
+
+* callback to print the name of the connection?
+
+* maybe io_transition_open() to build the status checks
+  * can be moved to separate function without too much trouble
+
+## conn_alloc()
+
+* calls rr_track_create, maybe io_alloc() ?
+
+## mod_push()
+
+* sets initial retry timer && u->code?
+
+everything else is non-RADIUS
+
+## mod_signal()
+
+check synchronous, but otherwise non-RADIUS
diff --git a/src/modules/rlm_redis/libfreeradius-redis.mk b/src/modules/rlm_redis/libfreeradius-redis.mk
new file mode 100644 (file)
index 0000000..7aef04f
--- /dev/null
@@ -0,0 +1,10 @@
+TARGETNAME     := libfreeradius-redis
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := redis.c crc16.c cluster.c
+
+SRC_CFLAGS     :=
+TGT_LDLIBS     :=  -lhiredis