The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_tacacs.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 29c8a8cd13335b431f45c1aba234d355fc00e168 $
19  * @file rlm_tacacs.c
20  * @brief A TACACS client library.
21  *
22  * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
23  */
24 RCSID("$Id: 29c8a8cd13335b431f45c1aba234d355fc00e168 $")
25 
26 #include <freeradius-devel/io/application.h>
27 #include <freeradius-devel/server/modpriv.h>
28 #include <freeradius-devel/util/debug.h>
29 #include <freeradius-devel/util/dlist.h>
30 
31 #include "rlm_tacacs.h"
32 
33 static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
34 
35 /*
36  * Retransmission intervals for the packets we support.
37  */
39  { FR_CONF_OFFSET("initial_rtx_time", fr_retry_config_t, irt), .dflt = STRINGIFY(2) },
40  { FR_CONF_OFFSET("max_rtx_time", fr_retry_config_t, mrt), .dflt = STRINGIFY(16) },
41  { FR_CONF_OFFSET("max_rtx_count", fr_retry_config_t, mrc), .dflt = STRINGIFY(5) },
42  { FR_CONF_OFFSET("max_rtx_duration", fr_retry_config_t, mrd), .dflt = STRINGIFY(30) },
44 };
45 
46 /*
47  * A mapping of configuration file names to internal variables.
48  */
49 static conf_parser_t const module_config[] = {
50  { FR_CONF_OFFSET_TYPE_FLAGS("transport", FR_TYPE_VOID, 0, rlm_tacacs_t, io_submodule),
52 
54  .func = type_parse },
55 
56  { FR_CONF_OFFSET("max_attributes", rlm_tacacs_t, max_attributes), .dflt = STRINGIFY(FR_MAX_ATTRIBUTES) },
57 
58  { FR_CONF_OFFSET("response_window", rlm_tacacs_t, response_window), .dflt = STRINGIFY(20) },
59 
60  { FR_CONF_OFFSET("zombie_period", rlm_tacacs_t, zombie_period), .dflt = STRINGIFY(40) },
61 
62  { FR_CONF_OFFSET("revive_interval", rlm_tacacs_t, revive_interval) },
63 
64  { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_tacacs_t, trunk_conf, fr_trunk_config ) },
65 
66  { FR_CONF_OFFSET_SUBSECTION("retry", 0, rlm_tacacs_t, retry, retry_config ) },
67 
69 };
70 
71 static fr_dict_t const *dict_tacacs;
72 
75  { .out = &dict_tacacs, .proto = "tacacs" },
76  { NULL }
77 };
78 
80 
83  { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
84  { NULL }
85 };
86 
87 /** Set which types of packets we can parse
88  *
89  * @param[in] ctx to allocate data in (instance of rlm_tacacs).
90  * @param[out] out Where to write the parsed data.
91  * @param[in] parent Base structure address.
92  * @param[in] ci #CONF_PAIR specifying the name of the type module.
93  * @param[in] rule unused.
94  * @return
95  * - 0 on success.
96  * - -1 on failure.
97  */
98 static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
99  CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
100 {
101  char const *type_str = cf_pair_value(cf_item_to_pair(ci));
102  fr_dict_enum_value_t const *type_enum;
103  uint32_t code;
104 
105 #ifndef NDEBUG
107 #endif
108 
109  /*
110  * Must be the TACACS+ module
111  */
112  fr_assert(cs && (strcmp(cf_section_name1(cs), "tacacs") == 0));
113 
114  /*
115  * Allow the process module to be specified by
116  * packet type.
117  */
118  type_enum = fr_dict_enum_by_name(attr_packet_type, type_str, -1);
119  if (!type_enum) {
120  cf_log_err(ci, "Unknown TACACS+ packet type '%s'", type_str);
121  return -1;
122  }
123 
124  code = type_enum->value->vb_uint32;
125 
126  memcpy(out, &code, sizeof(code));
127 
128  return 0;
129 }
130 
131 static void mod_tacacs_signal(module_ctx_t const *mctx, request_t *request, fr_signal_t action)
132 {
134  rlm_tacacs_io_t const *io = (rlm_tacacs_io_t const *)inst->io_submodule->module; /* Public symbol exported by the module */
135 
136  /*
137  * We received a duplicate packet, ignore the dup, and rely on the
138  * IO submodule / trunk to do it's own retransmissions.
139  */
140  if (action == FR_SIGNAL_DUP) return;
141 
142  if (!io->signal) return;
143 
144  io->signal(MODULE_CTX(inst->io_submodule->dl_inst,
145  module_thread(inst->io_submodule)->data, mctx->env_data,
146  mctx->rctx), request, action);
147 }
148 
149 /** Send packets outbound.
150  *
151  */
152 static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
153 {
155  rlm_rcode_t rcode;
156  unlang_action_t ua;
157 
158  void *rctx = NULL;
159 
160  if (!FR_TACACS_PACKET_CODE_VALID(request->packet->code)) {
161  REDEBUG("Invalid packet code %d", request->packet->code);
163  }
164 
165  if (!inst->allowed[request->packet->code]) {
166  REDEBUG("Packet code %s is disallowed by the configuration",
167  fr_tacacs_packet_names[request->packet->code]);
169  }
170 
171  /*
172  * Push the request and it's data to the IO submodule.
173  *
174  * This may return YIELD, for "please yield", or it may
175  * return another code which indicates what happened to
176  * the request...
177  */
178  ua = inst->io->enqueue(&rcode, &rctx, inst->io_submodule->dl_inst->data,
179  module_thread(inst->io_submodule)->data, request);
180  if (ua != UNLANG_ACTION_YIELD) {
181  fr_assert(rctx == NULL);
182  RETURN_MODULE_RCODE(rcode);
183  }
184 
185  return unlang_module_yield(request, inst->io->resume, mod_tacacs_signal, 0, rctx);
186 }
187 
188 static int mod_bootstrap(module_inst_ctx_t const *mctx)
189 {
190  size_t i, num_types;
191  rlm_tacacs_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_tacacs_t);
192 
193  inst->io = (rlm_tacacs_io_t const *)inst->io_submodule->module; /* Public symbol exported by the module */
194  inst->name = mctx->inst->name;
195 
196  /*
197  * These limits are specific to TACACS, and cannot be over-ridden, due to 8-bit ID fields!
198  */
199  FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
200  FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
201  FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
202 
203  FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
204  FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
205 
206  FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, >=, fr_time_delta_from_sec(1));
207  FR_TIME_DELTA_BOUND_CHECK("zombie_period", inst->zombie_period, <=, fr_time_delta_from_sec(120));
208 
209  FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, >=, fr_time_delta_from_sec(10));
210  FR_TIME_DELTA_BOUND_CHECK("revive_interval", inst->revive_interval, <=, fr_time_delta_from_sec(3600));
211 
212  num_types = talloc_array_length(inst->types);
213  fr_assert(num_types > 0);
214 
215  /*
216  * Allow for O(1) lookup later...
217  */
218  for (i = 0; i < num_types; i++) {
219  uint32_t code;
220 
221  code = inst->types[i];
223 
224  inst->allowed[code] = true;
225  }
226 
227 
228  return 0;
229 }
230 
231 static int mod_load(void)
232 {
233  if (fr_tacacs_global_init() < 0) {
234  PERROR("Failed initialising protocol library");
235  return -1;
236  }
237  return 0;
238 }
239 
240 static void mod_unload(void)
241 {
243 }
244 
245 /*
246  * The module name should be the only globally exported symbol.
247  * That is, everything else should be 'static'.
248  *
249  * If the module needs to temporarily modify it's instantiation
250  * data, the type should be changed to MODULE_TYPE_THREAD_UNSAFE.
251  * The server will then take care of ensuring that the module
252  * is single-threaded.
253  */
254 extern module_rlm_t rlm_tacacs;
256  .common = {
257  .magic = MODULE_MAGIC_INIT,
258  .name = "tacacs",
260  .inst_size = sizeof(rlm_tacacs_t),
262 
263  .onload = mod_load,
264  .unload = mod_unload,
265 
266  .bootstrap = mod_bootstrap,
267  },
268  .method_names = (module_method_name_t[]){
269  { .name1 = CF_IDENT_ANY, .name2 = CF_IDENT_ANY, .method = mod_process },
271  },
272 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition: action.h:42
#define RCSID(id)
Definition: build.h:444
#define STRINGIFY(x)
Definition: build.h:195
#define UNUSED
Definition: build.h:313
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition: cf_parse.h:486
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:268
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:256
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
Definition: cf_parse.h:297
#define FR_TIME_DELTA_BOUND_CHECK(_name, _var, _op, _bound)
Definition: cf_parse.h:497
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: cf_parse.h:406
@ CONF_FLAG_MULTI
CONF_PAIR can have multiple copies.
Definition: cf_parse.h:420
@ CONF_FLAG_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition: cf_parse.h:421
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:241
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
Common header for all CONF_* types.
Definition: cf_priv.h:49
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
CONF_PAIR * cf_item_to_pair(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_PAIR.
Definition: cf_util.c:629
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition: cf_util.c:1511
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition: cf_util.c:649
char const * cf_section_name1(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition: cf_util.c:1112
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
#define cf_parent(_cf)
Definition: cf_util.h:98
#define CF_IDENT_ANY
Definition: cf_util.h:78
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition: dict.h:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
fr_value_box_t const * value
Enum value (what name maps to).
Definition: dict.h:213
fr_dict_enum_value_t * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition: dict_util.c:2992
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
Value of an enumerated attribute.
Definition: dict.h:209
char const *_CONST name
Instance name.
Definition: dl_module.h:163
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
#define PERROR(_fmt,...)
Definition: log.h:228
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
unsigned int uint32_t
Definition: merged_model.c:33
void * env_data
Per call environment data.
Definition: module_ctx.h:44
void * rctx
Resume ctx that a module previously set.
Definition: module_ctx.h:45
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
#define MODULE_CTX(_dl_inst, _thread, _env_data, _rctx)
Wrapper to create a module_ctx_t as a compound literal.
Definition: module_ctx.h:123
Temporary structure to hold arguments for module calls.
Definition: module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition: module_ctx.h:51
Specifies a module method identifier.
Definition: module_method.c:36
int module_rlm_submodule_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
Generic conf_parser_t func for loading drivers.
Definition: module_rlm.c:913
module_t common
Common fields presented by all modules.
Definition: module_rlm.h:37
static const conf_parser_t config[]
Definition: base.c:188
char const * fr_tacacs_packet_names[FR_TACACS_CODE_MAX]
Definition: base.c:119
void fr_tacacs_global_free(void)
Definition: base.c:167
int fr_tacacs_global_init(void)
Definition: base.c:144
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RETURN_MODULE_RCODE(_rcode)
Definition: rcode.h:64
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
static int mod_load(void)
Definition: rlm_tacacs.c:231
static fr_dict_attr_t const * attr_packet_type
Definition: rlm_tacacs.c:79
fr_dict_attr_autoload_t rlm_tacacs_dict_attr[]
Definition: rlm_tacacs.c:82
static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Send packets outbound.
Definition: rlm_tacacs.c:152
static conf_parser_t retry_config[]
Definition: rlm_tacacs.c:38
static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
static fr_dict_t const * dict_tacacs
Definition: rlm_tacacs.c:71
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Definition: rlm_tacacs.c:188
fr_dict_autoload_t rlm_tacacs_dict[]
Definition: rlm_tacacs.c:74
static void mod_unload(void)
Definition: rlm_tacacs.c:240
module_rlm_t rlm_tacacs
Definition: rlm_tacacs.c:255
static void mod_tacacs_signal(module_ctx_t const *mctx, request_t *request, fr_signal_t action)
Definition: rlm_tacacs.c:131
static conf_parser_t const module_config[]
Definition: rlm_tacacs.c:49
struct rlm_tacacs_s rlm_tacacs_t
Definition: rlm_tacacs.h:36
unlang_module_signal_t signal
Send a signal to an IO module.
Definition: rlm_tacacs.h:76
Public structure describing an I/O path for an outgoing socket.
Definition: rlm_tacacs.h:73
@ MODULE_TYPE_RESUMABLE
does yield / resume
Definition: module.h:52
@ MODULE_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: module.h:49
#define MODULE_NAME_TERMINATOR
Definition: module.h:135
void * data
Thread specific instance data.
Definition: module.h:220
fr_signal_t
Definition: signal.h:48
module_thread_instance_t * module_thread(module_instance_t *mi)
Retrieve module/thread specific instance for a module.
Definition: module.c:459
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition: module.c:575
RETURN_MODULE_FAIL
if(!subtype_vp) goto fail
fr_assert(0)
eap_aka_sim_process_conf_t * inst
#define FR_TACACS_PACKET_CODE_VALID(_code)
Definition: tacacs.h:321
#define FR_MAX_ATTRIBUTES
Definition: tacacs.h:28
#define talloc_get_type_abort_const
Definition: talloc.h:270
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition: time.h:588
conf_parser_t const fr_trunk_config[]
Config parser definitions to populate a fr_trunk_conf_t.
Definition: trunk.c:306
static fr_slen_t parent
Definition: pair.h:844
int nonnull(2, 5))
static size_t char ** out
Definition: value.h:984