The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 72413589333cbc5bcdf8576bd87256d798aee1cd $
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  */
25 RCSID("$Id: 72413589333cbc5bcdf8576bd87256d798aee1cd $")
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  */
43 typedef struct {
44  char const *filename;
46  bool relaxed;
49 
50 static const conf_parser_t module_config[] = {
52  { FR_CONF_OFFSET("key", rlm_attr_filter_t, key), .dflt = "&Realm", .quote = T_BARE_WORD },
53  { FR_CONF_OFFSET("relaxed", rlm_attr_filter_t, relaxed), .dflt = "no" },
55 };
56 
57 static fr_dict_t const *dict_freeradius;
58 static fr_dict_t const *dict_radius;
59 
62  { .out = &dict_freeradius, .proto = "freeradius" },
63  { .out = &dict_radius, .proto = "radius" },
64  { NULL }
65 };
66 
70 
72 
75  { .out = &attr_stripped_user_name, .name = "Stripped-User-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
76  { .out = &attr_fall_through, .name = "Fall-Through", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
77  { .out = &attr_relax_filter, .name = "Relax-Filter", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
78 
79  { .out = &attr_vendor_specific, .name = "Vendor-Specific", .type = FR_TYPE_VSA, .dict = &dict_radius },
80  { NULL }
81 };
82 
83 static void check_pair(request_t *request, fr_pair_t *check_item, fr_pair_t *reply_item, int *pass, int *fail)
84 {
85  int compare;
86 
87  if (check_item->op == T_OP_SET) return;
88 
89  compare = fr_pair_cmp(check_item, reply_item);
90  if (compare < 0) RPEDEBUG("Comparison failed");
91 
92  if (compare == 1) {
93  ++*(pass);
94  } else {
95  ++*(fail);
96  }
97 
98  RDEBUG3("%pP %s %pP", reply_item, compare == 1 ? "allowed by" : "disallowed by", check_item);
99 
100  return;
101 }
102 
103 static int attr_filter_getfile(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *filename, PAIR_LIST_LIST *pair_list)
104 {
105  int rcode;
106  PAIR_LIST *entry = NULL;
107  map_t *map;
108 
109  rcode = pairlist_read(ctx, dict_radius, filename, pair_list);
110  if (rcode < 0) {
111  return -1;
112  }
113 
114  /*
115  * Walk through the 'attrs' file list.
116  */
117  while ((entry = fr_dlist_next(&pair_list->head, entry))) {
118  /*
119  * We apply the rules in the reply items.
120  */
121  if (!map_list_empty(&entry->check)) {
122  WARN("%s[%d] Check list is not empty for entry \"%s\".\n",
123  filename, entry->lineno, entry->name);
124  }
125 
126  map = NULL;
127  while ((map = map_list_next(&entry->reply, map))) {
128  if (!tmpl_is_attr(map->lhs)) {
129  ERROR("%s[%d] Left side of filter %s is not an attribute",
130  filename, entry->lineno, map->lhs->name);
131  return -1;
132  }
133 
134  if (tmpl_list(map->lhs) != request_attr_reply) {
135  ERROR("%s[%d] Left side of filter %s is not in the reply list",
136  filename, entry->lineno, map->lhs->name);
137  return -1;
138  }
139 
140  if (fr_assignment_op[map->op]) {
141  ERROR("%s[%d] Filter %s contains invalid operator '%s'",
142  filename, entry->lineno, map->lhs->name, fr_tokens[map->op]);
143  return -1;
144  }
145 
146  /*
147  * Make sure that bad things don't happen.
148  */
149  if (!map->rhs) {
150  ERROR("%s[%d] Right side of filter %s is a nested attribute - this is not (yet) supported",
151  filename, entry->lineno, map->lhs->name);
152  return -1;
153  }
154 
155  if (!tmpl_is_data(map->rhs)) {
156  ERROR("%s[%d] Right side of filter %s is not a static value",
157  filename, entry->lineno, map->lhs->name);
158  return -1;
159  }
160  }
161  }
162 
163  return 0;
164 }
165 
166 /*
167  * (Re-)read the "attrs" file into memory.
168  */
169 static int mod_instantiate(module_inst_ctx_t const *mctx)
170 {
171  rlm_attr_filter_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_attr_filter_t);
172  int rcode;
173  pairlist_list_init(&inst->attrs);
174 
175  rcode = attr_filter_getfile(inst, mctx, inst->filename, &inst->attrs);
176  if (rcode != 0) {
177  ERROR("Errors reading %s", inst->filename);
178 
179  return -1;
180  }
181 
182  return 0;
183 }
184 
185 
186 /*
187  * Common attr_filter checks
188  */
189 static unlang_action_t CC_HINT(nonnull) attr_filter_common(TALLOC_CTX *ctx, rlm_rcode_t *p_result,
190  module_ctx_t const *mctx, request_t *request,
191  fr_pair_list_t *list)
192 {
194  fr_pair_list_t output;
195  PAIR_LIST *pl = NULL;
196  int found = 0;
197  int pass, fail = 0;
198  char const *keyname = NULL;
199  char buffer[256];
200  ssize_t slen;
201 
202  slen = tmpl_expand(&keyname, buffer, sizeof(buffer), request, inst->key, NULL, NULL);
203  if (slen < 0) {
205  }
206  if ((keyname == buffer) && is_truncated((size_t)slen, sizeof(buffer))) {
207  REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen);
209  }
210 
211  /*
212  * Head of the output list
213  */
214  fr_pair_list_init(&output);
215 
216  /*
217  * Find the attr_filter profile entry for the entry.
218  */
219  while ((pl = fr_dlist_next(&inst->attrs.head, pl))) {
220  int fall_through = 0;
221  int relax_filter = inst->relaxed;
222  map_t *map = NULL;
223  fr_pair_list_t tmp_list;
224  fr_pair_t *check_item, *input_item;
225  fr_pair_list_t check_list;
226 
227  fr_pair_list_init(&tmp_list);
228  /*
229  * If the current entry is NOT a default,
230  * AND the realm does NOT match the current entry,
231  * then skip to the next entry.
232  */
233  if ((strcmp(pl->name, "DEFAULT") != 0) &&
234  (strcmp(keyname, pl->name) != 0)) {
235  continue;
236  }
237 
238  RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
239  found = 1;
240 
241  fr_pair_list_init(&check_list);
242 
243  while ((map = map_list_next(&pl->reply, map))) {
244  if (map_to_vp(ctx, &tmp_list, request, map, NULL) < 0) {
245  RPWARN("Failed parsing map %s for check item, skipping it", map->lhs->name);
246  continue;
247  }
248 
249  check_item = fr_pair_list_head(&tmp_list);
250  if (check_item->da == attr_fall_through) {
251  if (check_item->vp_uint32 == 1) {
252  fall_through = 1;
253  fr_pair_list_free(&tmp_list);
254  continue;
255  }
256  } else if (check_item->da == attr_relax_filter) {
257  relax_filter = check_item->vp_bool;
258  }
259 
260  /*
261  * Remove pair from temporary list ready to
262  * add to the correct destination
263  */
264  fr_pair_remove(&tmp_list, check_item);
265 
266  /*
267  * If it is a SET operator, add the attribute to
268  * the output list without checking it.
269  */
270  if (check_item->op == T_OP_SET ) {
271  fr_pair_append(&output, check_item);
272  continue;
273  }
274 
275  /*
276  * Append the realized VP to the check list.
277  */
278  fr_pair_append(&check_list, check_item);
279  }
280 
281  /*
282  * Iterate through the input items, comparing
283  * each item to every rule, then moving it to the
284  * output list only if it matches all rules
285  * for that attribute. IE, Idle-Timeout is moved
286  * only if it matches all rules that describe an
287  * Idle-Timeout.
288  */
289  for (input_item = fr_pair_list_head(list);
290  input_item;
291  input_item = fr_pair_list_next(list, input_item)) {
292  pass = fail = 0; /* reset the pass,fail vars for each reply item */
293 
294  /*
295  * Reset the check_item pointer to beginning of the list
296  */
297  for (check_item = fr_pair_list_head(&check_list);
298  check_item;
299  check_item = fr_pair_list_next(&check_list, check_item)) {
300  /*
301  * Vendor-Specific is special, and matches any VSA if the
302  * comparison is always true.
303  */
304  if ((check_item->da == attr_vendor_specific) &&
305  (fr_dict_vendor_num_by_da(input_item->da) != 0) &&
306  (check_item->op == T_OP_CMP_TRUE)) {
307  pass++;
308  continue;
309  }
310 
311  if (input_item->da == check_item->da) {
312  check_pair(request, check_item, input_item, &pass, &fail);
313  }
314  }
315 
316  RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
317  input_item->da->name, pass, fail);
318  /*
319  * Only move attribute if it passed all rules, or if the config says we
320  * should copy unmatched attributes ('relaxed' mode).
321  */
322  if (fail == 0 && (pass > 0 || relax_filter)) {
323  fr_pair_t *prev = fr_pair_list_prev(list, input_item);
324 
325  if (!pass) {
326  RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
327  }
328  fr_pair_remove(list, input_item);
329  fr_assert(input_item != NULL);
330  fr_pair_append(&output, input_item);
331  input_item = prev; /* Set input_item to previous in the list for outer loop */
332  }
333  }
334 
335  /* If we shouldn't fall through, break */
336  if (!fall_through) {
337  break;
338  }
339  }
340 
341  /*
342  * No entry matched. We didn't do anything.
343  */
344  if (!found) {
345  fr_assert(fr_pair_list_empty(&output));
347  }
348 
349  /*
350  * Replace the existing request list with our filtered one
351  */
352  fr_pair_list_free(list);
353  fr_pair_list_append(list, &output);
354 
356 }
357 
358 #define RLM_AF_FUNC(_x, _y) static unlang_action_t CC_HINT(nonnull) mod_##_x(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) \
359  { \
360  return attr_filter_common(request->_y##_ctx, p_result, mctx, request, &request->_y##_pairs); \
361  }
362 
363 RLM_AF_FUNC(request, request)
364 RLM_AF_FUNC(reply, reply)
366 RLM_AF_FUNC(session, session_state)
367 
368 /* globally exported name */
371  .common = {
372  .magic = MODULE_MAGIC_INIT,
373  .name = "attr_filter",
374  .inst_size = sizeof(rlm_attr_filter_t),
377  },
378  .method_group = {
379  .bindings = (module_method_binding_t[]){
380  /*
381  * Hack to support old configurations
382  */
383  { .section = SECTION_NAME("accounting", CF_IDENT_ANY), .method = mod_reply },
384  { .section = SECTION_NAME("authorize", CF_IDENT_ANY), .method = mod_request },
385 
386  { .section = SECTION_NAME("recv", "accounting-request"), .method = mod_request },
387  { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_request },
388 
389  { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_reply },
390 
391  /*
392  * List name based methods
393  */
394  { .section = SECTION_NAME("request", CF_IDENT_ANY), .method = mod_request },
395  { .section = SECTION_NAME("reply", CF_IDENT_ANY), .method = mod_reply },
396  { .section = SECTION_NAME("control", CF_IDENT_ANY), .method = mod_control },
397  { .section = SECTION_NAME("session-state", CF_IDENT_ANY), .method = mod_session },
399  }
400  }
401 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
static int const char char buffer[256]
Definition: acutest.h:574
#define RCSID(id)
Definition: build.h:481
#define STRINGIFY(x)
Definition: build.h:195
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:627
#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
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: cf_parse.h:405
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition: cf_parse.h:411
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:564
#define CF_IDENT_ANY
Definition: cf_util.h:78
static fr_control_t * control
Definition: control_test.c:50
#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:267
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:280
Specifies an attribute which must be present for the module to function.
Definition: dict.h:266
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:279
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:212
#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 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:1487
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_BOOL
A truth value.
Definition: merged_model.c:95
@ FR_TYPE_VSA
Vendor-Specific, for RADIUS attribute 26.
Definition: merged_model.c:121
long int ssize_t
Definition: merged_model.c:24
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:1969
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
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition: pair.c:46
#define is_truncated(_ret, _max)
Definition: print.h:48
static const conf_parser_t config[]
Definition: base.c:183
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define WARN(fmt,...)
Definition: radclient.h:47
#define RETURN_MODULE_NOOP
Definition: rcode.h:62
#define RETURN_MODULE_UPDATED
Definition: rcode.h:63
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
fr_dict_attr_t const * request_attr_reply
Definition: request.c:46
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
static unlang_action_t attr_filter_common(TALLOC_CTX *ctx, rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, fr_pair_list_t *list)
static const conf_parser_t module_config[]
static int mod_instantiate(module_inst_ctx_t const *mctx)
fr_dict_autoload_t rlm_attr_filter_dict[]
char const * filename
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1302
static sql_fall_through_t fall_through(map_list_t *maps)
Definition: rlm_sql.c:298
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition: section.h:40
void * data
Module's instance data.
Definition: module.h:271
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition: module.h:151
Named methods exported by a module.
Definition: module.h:173
#define tmpl_is_attr(vpt)
Definition: tmpl.h:213
#define tmpl_is_data(vpt)
Definition: tmpl.h:211
static fr_dict_attr_t const * tmpl_list(tmpl_t const *vpt)
Definition: tmpl.h:915
#define tmpl_expand(_out, _buff, _buff_len, _request, _vpt, _escape, _escape_ctx)
Expand a tmpl to a C type, using existing storage to hold variably sized types.
Definition: tmpl.h:1060
RETURN_MODULE_FAIL
fr_assert(0)
eap_aka_sim_process_conf_t * inst
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:282
const bool fr_assignment_op[T_TOKEN_LAST]
Definition: token.c:168
char const * fr_tokens[T_TOKEN_LAST]
Definition: token.c:78
@ T_OP_CMP_TRUE
Definition: token.h:104
@ T_BARE_WORD
Definition: token.h:120
@ T_OP_SET
Definition: token.h:84
int pairlist_read(TALLOC_CTX *ctx, fr_dict_t const *dict, char const *file, PAIR_LIST_LIST *list)
Definition: users_file.c:235
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:59
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
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition: pair_inline.c:43
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:94
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
Definition: pair_inline.c:125
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:70
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
Definition: pair_inline.c:113
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.
Definition: pair_inline.c:182
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:83
int nonnull(2, 5))