]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
first draft of "front end" for TACACS+ client
authorAlan T. DeKok <aland@freeradius.org>
Sat, 7 Jan 2023 16:30:39 +0000 (11:30 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 13 Jan 2023 14:02:07 +0000 (09:02 -0500)
src/modules/rlm_tacacs/all.mk [new file with mode: 0644]
src/modules/rlm_tacacs/rlm_tacacs.c [new file with mode: 0644]
src/modules/rlm_tacacs/rlm_tacacs.h [new file with mode: 0644]
src/modules/rlm_tacacs/rlm_tacacs.mk [new file with mode: 0644]

diff --git a/src/modules/rlm_tacacs/all.mk b/src/modules/rlm_tacacs/all.mk
new file mode 100644 (file)
index 0000000..48f5c58
--- /dev/null
@@ -0,0 +1,4 @@
+SUBMAKEFILES := rlm_tacacs.mk
+
+# rlm_tacacs_tcp.mk
+
diff --git a/src/modules/rlm_tacacs/rlm_tacacs.c b/src/modules/rlm_tacacs/rlm_tacacs.c
new file mode 100644 (file)
index 0000000..7e7bbf3
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ *   This program is 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 rlm_tacacs.c
+ * @brief A TACACS client library.
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/server/modpriv.h>
+#include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/util/dlist.h>
+
+#include "rlm_tacacs.h"
+
+static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, CONF_PARSER const *rule);
+/*
+ *     A mapping of configuration file names to internal variables.
+ */
+static CONF_PARSER const module_config[] = {
+       { FR_CONF_OFFSET("type", FR_TYPE_UINT32 | FR_TYPE_MULTI | FR_TYPE_NOT_EMPTY | FR_TYPE_REQUIRED, rlm_tacacs_t, types),
+         .func = type_parse },
+
+       { FR_CONF_OFFSET("max_attributes", FR_TYPE_UINT32, rlm_tacacs_t, max_attributes), .dflt = STRINGIFY(FR_MAX_ATTRIBUTES) },
+
+       { FR_CONF_OFFSET("response_window", FR_TYPE_TIME_DELTA, rlm_tacacs_t, response_window), .dflt = STRINGIFY(20) },
+
+       { FR_CONF_OFFSET("zombie_period", FR_TYPE_TIME_DELTA, rlm_tacacs_t, zombie_period), .dflt = STRINGIFY(40) },
+
+       { FR_CONF_OFFSET("revive_interval", FR_TYPE_TIME_DELTA, rlm_tacacs_t, revive_interval) },
+
+       { FR_CONF_OFFSET("pool", FR_TYPE_SUBSECTION, rlm_tacacs_t, trunk_conf), .subcs = (void const *) fr_trunk_config, },
+
+       CONF_PARSER_TERMINATOR
+};
+
+static fr_dict_t const *dict_tacacs;
+
+extern fr_dict_autoload_t rlm_tacacs_dict[];
+fr_dict_autoload_t rlm_tacacs_dict[] = {
+       { .out = &dict_tacacs, .proto = "tacacs" },
+       { NULL }
+};
+
+static fr_dict_attr_t const *attr_packet_type;
+
+extern fr_dict_attr_autoload_t rlm_tacacs_dict_attr[];
+fr_dict_attr_autoload_t rlm_tacacs_dict_attr[] = {
+       { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
+       { NULL }
+};
+
+/** Set which types of packets we can parse
+ *
+ * @param[in] ctx      to allocate data in (instance of rlm_tacacs).
+ * @param[out] out     Where to write the parsed data.
+ * @param[in] parent   Base structure address.
+ * @param[in] ci       #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule     unused.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
+                     CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+       char const              *type_str = cf_pair_value(cf_item_to_pair(ci));
+       CONF_SECTION            *cs = cf_item_to_section(cf_parent(ci));
+       fr_dict_enum_value_t const      *type_enum;
+       uint32_t                code;
+
+       /*
+        *      Must be the TACACS+ module
+        */
+       fr_assert(cs && (strcmp(cf_section_name1(cs), "tacacs") == 0));
+
+       /*
+        *      Allow the process module to be specified by
+        *      packet type.
+        */
+       type_enum = fr_dict_enum_by_name(attr_packet_type, type_str, -1);
+       if (!type_enum) {
+       invalid_code:
+               cf_log_err(ci, "Unknown or invalid TACACS+ packet type '%s'", type_str);
+               return -1;
+       }
+
+       code = type_enum->value->vb_uint32;
+
+       if (!code || (code >= FR_TAC_PLUS_MAX)) goto invalid_code;
+
+       memcpy(out, &code, sizeof(code));
+
+       return 0;
+}
+
+static void mod_tacacs_signal(module_ctx_t const *mctx, request_t *request, fr_state_signal_t action)
+{
+       rlm_tacacs_t const      *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_tacacs_t);
+       rlm_tacacs_io_t const   *io = (rlm_tacacs_io_t const *)inst->io_submodule->module;              /* Public symbol exported by the module */
+
+       /*
+        *      We received a duplicate packet, ignore the dup, and rely on the
+        *      IO submodule / trunk to do it's own retransmissions.
+        */
+       if (action == FR_SIGNAL_DUP) return;
+
+       if (!io->signal) return;
+
+       io->signal(MODULE_CTX(inst->io_submodule->dl_inst,
+                             module_thread(inst->io_submodule)->data,
+                             mctx->rctx), request, action);
+}
+
+/** Send packets outbound.
+ *
+ */
+static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+       rlm_tacacs_t const      *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_tacacs_t);
+       rlm_rcode_t             rcode;
+       unlang_action_t         ua;
+
+       void                    *rctx = NULL;
+
+       if (!request->packet->code) {
+               REDEBUG("You MUST specify a packet code");
+               RETURN_MODULE_FAIL;
+       }
+
+       if (request->packet->code >= FR_TAC_PLUS_MAX) {
+               REDEBUG("Invalid packet code %d", request->packet->code);
+               RETURN_MODULE_FAIL;
+       }
+
+       if (!inst->allowed[request->packet->code]) {
+               REDEBUG("Packet code %s is disallowed by the configuration",
+                      fr_tacacs_packet_codes[request->packet->code]);
+               RETURN_MODULE_FAIL;
+       }
+
+       /*
+        *      Push the request and it's data to the IO submodule.
+        *
+        *      This may return YIELD, for "please yield", or it may
+        *      return another code which indicates what happened to
+        *      the request...
+        */
+       ua = inst->io->enqueue(&rcode, &rctx, inst->io_submodule->dl_inst->data,
+                              module_thread(inst->io_submodule)->data, request);
+       if (ua != UNLANG_ACTION_YIELD) {
+               fr_assert(rctx == NULL);
+               RETURN_MODULE_RCODE(rcode);
+       }
+
+       return unlang_module_yield(request, inst->io->resume, mod_tacacs_signal, rctx);
+}
+
+static int mod_bootstrap(module_inst_ctx_t const *mctx)
+{
+       size_t i, num_types;
+       rlm_tacacs_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_tacacs_t);
+
+       inst->io = (rlm_tacacs_io_t const *)inst->io_submodule->module; /* Public symbol exported by the module */
+       inst->name = mctx->inst->name;
+
+       /*
+        *      These limits are specific to TACACS, and cannot be over-ridden, due to 8-bit ID fields!
+        */
+       FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
+       FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
+       FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
+
+       FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
+       FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
+
+       FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, >=, fr_time_delta_from_sec(1));
+       FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, <=, fr_time_delta_from_sec(120));
+
+       FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, >=, fr_time_delta_from_sec(10));
+       FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, <=, fr_time_delta_from_sec(3600));
+
+       num_types = talloc_array_length(inst->types);
+       fr_assert(num_types > 0);
+
+       /*
+        *      Allow for O(1) lookup later...
+        */
+       for (i = 0; i < num_types; i++) {
+               uint32_t code;
+
+               code = inst->types[i];
+               fr_assert(code > 0);
+               fr_assert(code < FR_TAC_PLUS_MAX);
+
+               inst->allowed[code] = true;
+       }
+
+
+       return 0;
+}
+
+static int mod_load(void)
+{
+       if (fr_tacacs_init() < 0) {
+               PERROR("Failed initialising protocol library");
+               return -1;
+       }
+       return 0;
+}
+
+static void mod_unload(void)
+{
+       fr_tacacs_free();
+}
+
+/*
+ *     The module name should be the only globally exported symbol.
+ *     That is, everything else should be 'static'.
+ *
+ *     If the module needs to temporarily modify it's instantiation
+ *     data, the type should be changed to MODULE_TYPE_THREAD_UNSAFE.
+ *     The server will then take care of ensuring that the module
+ *     is single-threaded.
+ */
+extern module_rlm_t rlm_tacacs;
+module_rlm_t rlm_tacacs = {
+       .common = {
+               .magic          = MODULE_MAGIC_INIT,
+               .name           = "tacacs",
+               .type           = MODULE_TYPE_THREAD_SAFE | MODULE_TYPE_RESUMABLE,
+               .inst_size      = sizeof(rlm_tacacs_t),
+               .config         = module_config,
+
+               .onload         = mod_load,
+               .unload         = mod_unload,
+
+               .bootstrap      = mod_bootstrap,
+       },
+       .method_names = (module_method_name_t[]){
+               { .name1 = CF_IDENT_ANY,        .name2 = CF_IDENT_ANY,  .method = mod_process },
+               MODULE_NAME_TERMINATOR
+       },
+};
diff --git a/src/modules/rlm_tacacs/rlm_tacacs.h b/src/modules/rlm_tacacs/rlm_tacacs.h
new file mode 100644 (file)
index 0000000..5807f9e
--- /dev/null
@@ -0,0 +1,74 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+#include <freeradius-devel/io/atomic_queue.h>
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/server/map.h>
+#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/server/trunk.h>
+#include <freeradius-devel/util/dlist.h>
+#include <freeradius-devel/util/retry.h>
+#include <freeradius-devel/unlang/module.h>
+#include <freeradius-devel/tacacs/tacacs.h>
+
+/*
+ * $Id$
+ *
+ * @file rlm_tacacs.h
+ * @brief Structures for the TACACS+ client packets
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+typedef struct rlm_tacacs_s rlm_tacacs_t;
+typedef struct rlm_tacacs_io_s rlm_tacacs_io_t;
+
+/*
+ *     Define a structure for our module configuration.
+ */
+struct rlm_tacacs_s {
+       char const              *name;
+       module_instance_t       *io_submodule;
+       rlm_tacacs_io_t const   *io;                    //!< Public symbol exported by the submodule.
+
+       fr_time_delta_t         response_window;
+       fr_time_delta_t         zombie_period;
+       fr_time_delta_t         revive_interval;
+
+       uint32_t                max_attributes;         //!< Maximum number of attributes to decode in response.
+
+       uint32_t                *types;                 //!< array of allowed packet types
+
+       bool                    allowed[FR_TAC_PLUS_MAX];
+
+       fr_trunk_conf_t         trunk_conf;             //!< trunk configuration
+};
+
+/** Enqueue a request_t to an IO submodule
+ *
+ */
+typedef unlang_action_t (*rlm_tacacs_io_enqueue_t)(rlm_rcode_t *p_result, void **rctx, void *instance, void *thread, request_t *request);
+
+/** Public structure describing an I/O path for an outgoing socket.
+ *
+ * This structure is exported by client I/O modules e.g. rlm_tacacs_udp.
+ */
+struct rlm_tacacs_io_s {
+       module_t                common;                 //!< Common fields to all loadable modules.
+       rlm_tacacs_io_enqueue_t enqueue;                //!< Enqueue a request_t with an IO submodule.
+       unlang_module_signal_t  signal;                 //!< Send a signal to an IO module.
+       unlang_module_resume_t  resume;                 //!< Resume a request, and get rcode.
+};
diff --git a/src/modules/rlm_tacacs/rlm_tacacs.mk b/src/modules/rlm_tacacs/rlm_tacacs.mk
new file mode 100644 (file)
index 0000000..7e3d387
--- /dev/null
@@ -0,0 +1,7 @@
+TARGETNAME     := rlm_tacacs
+TARGET         := $(TARGETNAME)$(L)
+
+SOURCES                := rlm_tacacs.c
+
+TGT_PREREQS    := libfreeradius-tacacs$(L)
+LOG_ID_LIB     = 59