The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_attr_filter.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: 28632280953943c96312b9590c861bc504652efa $
19 * @file rlm_attr_filter.c
20 * @brief Filter the contents of a list, allowing only certain attributes.
21 *
22 * @copyright (C) 2001,2006 The FreeRADIUS server project
23 * @copyright (C) 2001 Chris Parker (cparker@starnetusa.net)
24 */
25RCSID("$Id: 28632280953943c96312b9590c861bc504652efa $")
26
27#define LOG_PREFIX mctx->mi->name
28
29#include <freeradius-devel/server/base.h>
30#include <freeradius-devel/server/module_rlm.h>
31#include <freeradius-devel/util/debug.h>
32#include <freeradius-devel/server/users_file.h>
33
34#include <sys/stat.h>
35
36#include <ctype.h>
37#include <fcntl.h>
38
39/*
40 * Define a structure with the module configuration, so it can
41 * be used as the instance handle.
42 */
43typedef struct {
44 char const *filename;
45 bool relaxed;
48
49typedef struct {
52
53static const conf_parser_t module_config[] = {
55 { FR_CONF_OFFSET("relaxed", rlm_attr_filter_t, relaxed), .dflt = "no" },
57};
58
60static fr_dict_t const *dict_radius;
61
64 { .out = &dict_freeradius, .proto = "freeradius" },
65 { .out = &dict_radius, .proto = "radius" },
66 { NULL }
67};
68
72
74
77 { .out = &attr_stripped_user_name, .name = "Stripped-User-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
78 { .out = &attr_fall_through, .name = "Fall-Through", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
79 { .out = &attr_relax_filter, .name = "Relax-Filter", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
80
81 { .out = &attr_vendor_specific, .name = "Vendor-Specific", .type = FR_TYPE_VSA, .dict = &dict_radius },
82 { NULL }
83};
84
93
94static void check_pair(request_t *request, fr_pair_t *check_item, fr_pair_t *reply_item, int *pass, int *fail)
95{
96 int compare;
97
98 if (check_item->op == T_OP_SET) return;
99
100 compare = fr_pair_cmp(check_item, reply_item);
101 if (compare < 0) RPEDEBUG("Comparison failed");
102
103 if (compare == 1) {
104 ++*(pass);
105 } else {
106 ++*(fail);
107 }
108
109 RDEBUG3("%pP %s %pP", reply_item, compare == 1 ? "allowed by" : "disallowed by", check_item);
110
111 return;
112}
113
114static int attr_filter_getfile(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *filename, PAIR_LIST_LIST *pair_list)
115{
116 int rcode;
117 PAIR_LIST *entry = NULL;
118 map_t *map;
119
120 rcode = pairlist_read(ctx, dict_radius, filename, pair_list, false);
121 if (rcode < 0) {
122 return -1;
123 }
124
125 /*
126 * Walk through the 'attrs' file list.
127 */
128 while ((entry = fr_dlist_next(&pair_list->head, entry))) {
129 /*
130 * We apply the rules in the reply items.
131 */
132 if (!map_list_empty(&entry->check)) {
133 WARN("%s[%d] Check list is not empty for entry \"%s\".\n",
134 filename, entry->lineno, entry->name);
135 }
136
137 map = NULL;
138 while ((map = map_list_next(&entry->reply, map))) {
139 if (!tmpl_is_attr(map->lhs)) {
140 ERROR("%s[%d] Left side of filter %s is not an attribute",
141 filename, entry->lineno, map->lhs->name);
142 return -1;
143 }
144
145 if (tmpl_list(map->lhs) != request_attr_reply) {
146 ERROR("%s[%d] Left side of filter %s is not in the reply list",
147 filename, entry->lineno, map->lhs->name);
148 return -1;
149 }
150
151 if (fr_assignment_op[map->op]) {
152 ERROR("%s[%d] Filter %s contains invalid operator '%s'",
153 filename, entry->lineno, map->lhs->name, fr_tokens[map->op]);
154 return -1;
155 }
156
157 /*
158 * Make sure that bad things don't happen.
159 */
160 if (!map->rhs) {
161 ERROR("%s[%d] Right side of filter %s is a nested attribute - this is not (yet) supported",
162 filename, entry->lineno, map->lhs->name);
163 return -1;
164 }
165
166 if (!tmpl_is_data(map->rhs)) {
167 ERROR("%s[%d] Right side of filter %s is not a static value",
168 filename, entry->lineno, map->lhs->name);
169 return -1;
170 }
171 }
172 }
173
174 return 0;
175}
176
177/*
178 * (Re-)read the "attrs" file into memory.
179 */
180static int mod_instantiate(module_inst_ctx_t const *mctx)
181{
182 rlm_attr_filter_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_attr_filter_t);
183 int rcode;
184 pairlist_list_init(&inst->attrs);
185
186 rcode = attr_filter_getfile(inst, mctx, inst->filename, &inst->attrs);
187 if (rcode != 0) {
188 ERROR("Errors reading %s", inst->filename);
189
190 return -1;
191 }
192
193 return 0;
194}
195
196
197/*
198 * Common attr_filter checks
199 */
200static unlang_action_t CC_HINT(nonnull) attr_filter_common(TALLOC_CTX *ctx, unlang_result_t *p_result,
201 module_ctx_t const *mctx, request_t *request,
202 fr_pair_list_t *list)
203{
205 attr_filter_env_t *env_data = talloc_get_type_abort(mctx->env_data, attr_filter_env_t);
206 fr_pair_list_t output;
207 PAIR_LIST *pl = NULL;
208 int found = 0;
209 int pass, fail = 0;
210 char const *keyname = NULL;
211
212 /* The key expanded to nothing - use DEFAULT */
213 if (env_data->key->type != FR_TYPE_STRING) {
214 keyname = "DEFAULT";
215 } else {
216 keyname = env_data->key->vb_strvalue;
217 }
218
219 /*
220 * Head of the output list
221 */
222 fr_pair_list_init(&output);
223
224 /*
225 * Find the attr_filter profile entry for the entry.
226 */
227 while ((pl = fr_dlist_next(&inst->attrs.head, pl))) {
228 int fall_through = 0;
229 int relax_filter = inst->relaxed;
230 map_t *map = NULL;
231 fr_pair_list_t tmp_list;
232 fr_pair_t *check_item, *input_item;
233 fr_pair_list_t check_list;
234
235 fr_pair_list_init(&tmp_list);
236 /*
237 * If the current entry is NOT a default,
238 * AND the realm does NOT match the current entry,
239 * then skip to the next entry.
240 */
241 if ((strcmp(pl->name, "DEFAULT") != 0) &&
242 (strcmp(keyname, pl->name) != 0)) {
243 continue;
244 }
245
246 RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
247 found = 1;
248
249 fr_pair_list_init(&check_list);
250
251 while ((map = map_list_next(&pl->reply, map))) {
252 /*
253 * Special case for !*
254 */
255 if (map->op == T_OP_CMP_FALSE) {
256 RWDEBUG("Unsupported operator '!*'");
257 continue;
258 }
259
260 /*
261 * Create an empty attribute. This functionality is used by the attr_filter module.
262 */
263 if (map->op == T_OP_CMP_TRUE) {
264 fr_pair_t *vp;
265
267 vp->op = T_OP_CMP_TRUE;
268 fr_pair_append(&check_list, vp);
269 continue;
270 }
271
272 /*
273 * @todo - this use of map_to_vp is completely wrong. Nothing else uses
274 * comparison operators for map_to_vp(). This module doesn't handle nested
275 * pairs, and dumps all pairs in one list no matter their depth. It requires
276 * that map_to_vp() appends the pair, rather than respecting the operator.
277 *
278 * i.e. it really only works for RADIUS. :(
279 */
280 if (map_to_vp(ctx, &tmp_list, request, map, NULL) < 0) {
281 RPWARN("Failed parsing map %s for check item, skipping it", map->lhs->name);
282 continue;
283 }
284
285 check_item = fr_pair_list_head(&tmp_list);
286 if (check_item->da == attr_fall_through) {
287 if (check_item->vp_uint32 == 1) {
288 fall_through = 1;
289 fr_pair_list_free(&tmp_list);
290 continue;
291 }
292 } else if (check_item->da == attr_relax_filter) {
293 relax_filter = check_item->vp_bool;
294 }
295
296 /*
297 * Remove pair from temporary list ready to
298 * add to the correct destination
299 */
300 fr_pair_remove(&tmp_list, check_item);
301
302 /*
303 * If it is a SET operator, add the attribute to
304 * the output list without checking it.
305 */
306 if (check_item->op == T_OP_SET ) {
307 fr_pair_append(&output, check_item);
308 continue;
309 }
310
311 /*
312 * Append the realized VP to the check list.
313 */
314 fr_pair_append(&check_list, check_item);
315 }
316
317 /*
318 * Iterate through the input items, comparing
319 * each item to every rule, then moving it to the
320 * output list only if it matches all rules
321 * for that attribute. IE, Idle-Timeout is moved
322 * only if it matches all rules that describe an
323 * Idle-Timeout.
324 */
325 for (input_item = fr_pair_list_head(list);
326 input_item;
327 input_item = fr_pair_list_next(list, input_item)) {
328 pass = fail = 0; /* reset the pass,fail vars for each reply item */
329
330 /*
331 * Reset the check_item pointer to beginning of the list
332 */
333 for (check_item = fr_pair_list_head(&check_list);
334 check_item;
335 check_item = fr_pair_list_next(&check_list, check_item)) {
336 /*
337 * Vendor-Specific is special, and matches any VSA if the
338 * comparison is always true.
339 */
340 if ((check_item->da == attr_vendor_specific) &&
341 (fr_dict_vendor_num_by_da(input_item->da) != 0) &&
342 (check_item->op == T_OP_CMP_TRUE)) {
343 pass++;
344 continue;
345 }
346
347 if (input_item->da == check_item->da) {
348 check_pair(request, check_item, input_item, &pass, &fail);
349 }
350 }
351
352 RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
353 input_item->da->name, pass, fail);
354 /*
355 * Only move attribute if it passed all rules, or if the config says we
356 * should copy unmatched attributes ('relaxed' mode).
357 */
358 if (fail == 0 && (pass > 0 || relax_filter)) {
359 fr_pair_t *prev = fr_pair_list_prev(list, input_item);
360
361 if (!pass) {
362 RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
363 }
364 fr_pair_remove(list, input_item);
365 fr_assert(input_item != NULL);
366 fr_pair_append(&output, input_item);
367 input_item = prev; /* Set input_item to previous in the list for outer loop */
368 }
369 }
370
371 /* If we shouldn't fall through, break */
372 if (!fall_through) {
373 break;
374 }
375 }
376
377 /*
378 * No entry matched. We didn't do anything.
379 */
380 if (!found) {
383 }
384
385 /*
386 * Replace the existing request list with our filtered one
387 */
388 fr_pair_list_free(list);
389 fr_pair_list_append(list, &output);
390
392}
393
394#define RLM_AF_FUNC(_x, _y) static unlang_action_t CC_HINT(nonnull) mod_##_x(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request) \
395 { \
396 return attr_filter_common(request->_y##_ctx, p_result, mctx, request, &request->_y##_pairs); \
397 }
398
399RLM_AF_FUNC(request, request)
400RLM_AF_FUNC(reply, reply)
401RLM_AF_FUNC(control, control)
402RLM_AF_FUNC(session, session_state)
403
404/* globally exported name */
407 .common = {
408 .magic = MODULE_MAGIC_INIT,
409 .name = "attr_filter",
410 .inst_size = sizeof(rlm_attr_filter_t),
412 .instantiate = mod_instantiate,
413 },
414 .method_group = {
415 .bindings = (module_method_binding_t[]){
416 /*
417 * Hack to support old configurations
418 */
419 { .section = SECTION_NAME("accounting", CF_IDENT_ANY), .method = mod_reply, .method_env = &attr_filter_env },
420 { .section = SECTION_NAME("authorize", CF_IDENT_ANY), .method = mod_request, .method_env = &attr_filter_env },
421
422 { .section = SECTION_NAME("recv", "accounting-request"), .method = mod_request, .method_env = &attr_filter_env },
423 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_request, .method_env = &attr_filter_env },
424
425 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_reply, .method_env = &attr_filter_env },
426
427 /*
428 * List name based methods
429 */
430 { .section = SECTION_NAME("request", CF_IDENT_ANY), .method = mod_request, .method_env = &attr_filter_env },
431 { .section = SECTION_NAME("reply", CF_IDENT_ANY), .method = mod_reply, .method_env = &attr_filter_env },
432 { .section = SECTION_NAME("control", CF_IDENT_ANY), .method = mod_control, .method_env = &attr_filter_env },
433 { .section = SECTION_NAME("session-state", CF_IDENT_ANY), .method = mod_session, .method_env = &attr_filter_env },
435 }
436 }
437};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
#define RCSID(id)
Definition build.h:485
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
Definition call_env.h:240
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
@ CALL_ENV_FLAG_CONCAT
If the tmpl produced multiple boxes they should be concatenated.
Definition call_env.h:76
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:662
#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:284
#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:272
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition cf_parse.h:434
@ CONF_FLAG_FILE_READABLE
File matching value must exist, and must be readable.
Definition cf_parse.h:440
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:599
#define CF_IDENT_ANY
Definition cf_util.h:78
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:287
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:300
Specifies an attribute which must be present for the module to function.
Definition dict.h:286
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:299
static uint32_t fr_dict_vendor_num_by_da(fr_dict_attr_t const *da)
Return the vendor number for an attribute.
Definition dict_ext.h:213
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:555
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RPWARN(fmt,...)
Definition log.h:301
#define RPEDEBUG(fmt,...)
Definition log.h:376
int map_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, UNUSED void *uctx)
Convert a map to a fr_pair_t.
Definition map.c:1593
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_BOOL
A truth value.
@ FR_TYPE_VSA
Vendor-Specific, for RADIUS attribute 26.
void * env_data
Per call environment data.
Definition module_ctx.h:44
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
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:50
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
int fr_pair_cmp(fr_pair_t const *a, fr_pair_t const *b)
Compare two pairs, using the operator from "a".
Definition pair.c:1967
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1345
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:287
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
static const conf_parser_t config[]
Definition base.c:186
#define fr_assert(_expr)
Definition rad_assert.h:38
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define WARN(fmt,...)
Definition radclient.h:47
#define RETURN_UNLANG_UPDATED
Definition rcode.h:66
#define RETURN_UNLANG_NOOP
Definition rcode.h:65
fr_dict_attr_t const * request_attr_reply
Definition request.c:44
static fr_dict_attr_t const * attr_relax_filter
static int attr_filter_getfile(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *filename, PAIR_LIST_LIST *pair_list)
static void check_pair(request_t *request, fr_pair_t *check_item, fr_pair_t *reply_item, int *pass, int *fail)
static fr_dict_attr_t const * attr_stripped_user_name
static fr_dict_attr_t const * attr_fall_through
static fr_dict_t const * dict_freeradius
module_rlm_t rlm_attr_filter
static fr_dict_t const * dict_radius
#define RLM_AF_FUNC(_x, _y)
PAIR_LIST_LIST attrs
fr_dict_attr_autoload_t rlm_attr_filter_dict_attr[]
static fr_dict_attr_t const * attr_vendor_specific
fr_value_box_t * key
static unlang_action_t attr_filter_common(TALLOC_CTX *ctx, unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request, fr_pair_list_t *list)
static const conf_parser_t module_config[]
static const call_env_method_t attr_filter_env
static int mod_instantiate(module_inst_ctx_t const *mctx)
fr_dict_autoload_t rlm_attr_filter_dict[]
char const * filename
static sql_fall_through_t fall_through(map_list_t *maps)
Definition rlm_sql.c:303
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:291
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#define tmpl_is_attr(vpt)
Definition tmpl.h:208
static fr_dict_attr_t const * tmpl_list(tmpl_t const *vpt)
Definition tmpl.h:904
#define tmpl_is_data(vpt)
Definition tmpl.h:206
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:801
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
Value pair map.
Definition map.h:77
fr_token_t op
The operator that controls insertion of the dst attribute.
Definition map.h:82
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition map.h:78
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition map.h:79
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
#define talloc_get_type_abort_const
Definition talloc.h:287
const bool fr_assignment_op[T_TOKEN_LAST]
Definition token.c:169
char const * fr_tokens[T_TOKEN_LAST]
Definition token.c:79
@ T_OP_CMP_TRUE
Definition token.h:104
@ T_BARE_WORD
Definition token.h:120
@ T_OP_SET
Definition token.h:84
@ T_OP_CMP_FALSE
Definition token.h:105
int pairlist_read(TALLOC_CTX *ctx, fr_dict_t const *dict, char const *file, PAIR_LIST_LIST *list, bool v3_compat)
Definition users_file.c:234
char const * name
Key for matching entry.
Definition users_file.h:39
int lineno
Line number entry read from.
Definition users_file.h:46
static void pairlist_list_init(PAIR_LIST_LIST *list)
Definition users_file.h:58
map_list_t check
List of maps for comparison / modifying control list.
Definition users_file.h:40
map_list_t reply
List of maps for modifying reply list.
Definition users_file.h:41
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
Definition pair_inline.c:69
fr_pair_t * fr_pair_remove(fr_pair_list_t *list, fr_pair_t *vp)
Remove fr_pair_t from a list without freeing.
Definition pair_inline.c:93
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
fr_pair_t * fr_pair_list_prev(fr_pair_list_t const *list, fr_pair_t const *item))
Get the previous item in a valuepair list before a specific entry.
Definition pair_inline.c:82
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition pair_inline.c:42
int nonnull(2, 5))