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: bad95a7a399984a8666995193669f21bb97bc015 $
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: bad95a7a399984a8666995193669f21bb97bc015 $")
26 
27 #define LOG_PREFIX mctx->inst->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->inst->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(1,2)) attr_filter_common(rlm_rcode_t *p_result,
190  module_ctx_t const *mctx, request_t *request,
191  fr_packet_t *packet, 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  if (!packet) {
204  }
205 
206  slen = tmpl_expand(&keyname, buffer, sizeof(buffer), request, inst->key, NULL, NULL);
207  if (slen < 0) {
209  }
210  if ((keyname == buffer) && is_truncated((size_t)slen, sizeof(buffer))) {
211  REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen);
213  }
214 
215  /*
216  * Head of the output list
217  */
218  fr_pair_list_init(&output);
219 
220  /*
221  * Find the attr_filter profile entry for the entry.
222  */
223  while ((pl = fr_dlist_next(&inst->attrs.head, pl))) {
224  int fall_through = 0;
225  int relax_filter = inst->relaxed;
226  map_t *map = NULL;
227  fr_pair_list_t tmp_list;
228  fr_pair_t *check_item, *input_item;
229  fr_pair_list_t check_list;
230 
231  fr_pair_list_init(&tmp_list);
232  /*
233  * If the current entry is NOT a default,
234  * AND the realm does NOT match the current entry,
235  * then skip to the next entry.
236  */
237  if ((strcmp(pl->name, "DEFAULT") != 0) &&
238  (strcmp(keyname, pl->name) != 0)) {
239  continue;
240  }
241 
242  RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
243  found = 1;
244 
245  fr_pair_list_init(&check_list);
246 
247  while ((map = map_list_next(&pl->reply, map))) {
248  if (map_to_vp(packet, &tmp_list, request, map, NULL) < 0) {
249  RPWARN("Failed parsing map %s for check item, skipping it", map->lhs->name);
250  continue;
251  }
252 
253  check_item = fr_pair_list_head(&tmp_list);
254  if (check_item->da == attr_fall_through) {
255  if (check_item->vp_uint32 == 1) {
256  fall_through = 1;
257  fr_pair_list_free(&tmp_list);
258  continue;
259  }
260  } else if (check_item->da == attr_relax_filter) {
261  relax_filter = check_item->vp_bool;
262  }
263 
264  /*
265  * Remove pair from temporary list ready to
266  * add to the correct destination
267  */
268  fr_pair_remove(&tmp_list, check_item);
269 
270  /*
271  * If it is a SET operator, add the attribute to
272  * the output list without checking it.
273  */
274  if (check_item->op == T_OP_SET ) {
275  fr_pair_append(&output, check_item);
276  continue;
277  }
278 
279  /*
280  * Append the realized VP to the check list.
281  */
282  fr_pair_append(&check_list, check_item);
283  }
284 
285  /*
286  * Iterate through the input items, comparing
287  * each item to every rule, then moving it to the
288  * output list only if it matches all rules
289  * for that attribute. IE, Idle-Timeout is moved
290  * only if it matches all rules that describe an
291  * Idle-Timeout.
292  */
293  for (input_item = fr_pair_list_head(list);
294  input_item;
295  input_item = fr_pair_list_next(list, input_item)) {
296  pass = fail = 0; /* reset the pass,fail vars for each reply item */
297 
298  /*
299  * Reset the check_item pointer to beginning of the list
300  */
301  for (check_item = fr_pair_list_head(&check_list);
302  check_item;
303  check_item = fr_pair_list_next(&check_list, check_item)) {
304  /*
305  * Vendor-Specific is special, and matches any VSA if the
306  * comparison is always true.
307  */
308  if ((check_item->da == attr_vendor_specific) &&
309  (fr_dict_vendor_num_by_da(input_item->da) != 0) &&
310  (check_item->op == T_OP_CMP_TRUE)) {
311  pass++;
312  continue;
313  }
314 
315  if (input_item->da == check_item->da) {
316  check_pair(request, check_item, input_item, &pass, &fail);
317  }
318  }
319 
320  RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
321  input_item->da->name, pass, fail);
322  /*
323  * Only move attribute if it passed all rules, or if the config says we
324  * should copy unmatched attributes ('relaxed' mode).
325  */
326  if (fail == 0 && (pass > 0 || relax_filter)) {
327  fr_pair_t *prev = fr_pair_list_prev(list, input_item);
328 
329  if (!pass) {
330  RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
331  }
332  fr_pair_remove(list, input_item);
333  fr_assert(input_item != NULL);
334  fr_pair_append(&output, input_item);
335  input_item = prev; /* Set input_item to previous in the list for outer loop */
336  }
337  }
338 
339  /* If we shouldn't fall through, break */
340  if (!fall_through) {
341  break;
342  }
343  }
344 
345  /*
346  * No entry matched. We didn't do anything.
347  */
348  if (!found) {
349  fr_assert(fr_pair_list_empty(&output));
351  }
352 
353  /*
354  * Replace the existing request list with our filtered one
355  */
356  fr_pair_list_free(list);
357  fr_pair_list_append(list, &output);
358 
360 }
361 
362 #define RLM_AF_FUNC(_x, _y, _z) static unlang_action_t CC_HINT(nonnull) mod_##_x(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) \
363  { \
364  return attr_filter_common(p_result, mctx, request, request->_y, &request->_z##_pairs); \
365  }
366 
367 RLM_AF_FUNC(authorize, packet, request)
368 RLM_AF_FUNC(post_auth, reply, reply)
369 
370 RLM_AF_FUNC(preacct, packet, request)
371 RLM_AF_FUNC(accounting, reply, reply)
372 
373 /* globally exported name */
376  .common = {
377  .magic = MODULE_MAGIC_INIT,
378  .name = "attr_filter",
379  .inst_size = sizeof(rlm_attr_filter_t),
382  },
383  .method_names = (module_method_name_t[]){
384  /*
385  * Hack to support old configurations
386  */
387  { .name1 = "authorize", .name2 = CF_IDENT_ANY, .method = mod_authorize },
388 
389  { .name1 = "recv", .name2 = "accounting-request", .method = mod_preacct },
390  { .name1 = "recv", .name2 = CF_IDENT_ANY, .method = mod_authorize },
391  { .name1 = "accounting", .name2 = CF_IDENT_ANY, .method = mod_accounting },
392  { .name1 = "send", .name2 = CF_IDENT_ANY, .method = mod_post_auth },
394  }
395 };
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:444
#define STRINGIFY(x)
Definition: build.h:195
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#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:406
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition: cf_parse.h:412
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
#define CF_IDENT_ANY
Definition: cf_util.h:78
#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:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
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
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:182
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
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:1489
@ 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
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
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
module_t common
Common fields presented by all modules.
Definition: module_rlm.h:37
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:1966
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:1340
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:188
#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:42
static fr_dict_attr_t const * attr_relax_filter
static unlang_action_t attr_filter_common(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, fr_packet_t *packet, fr_pair_list_t *list)
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
#define RLM_AF_FUNC(_x, _y, _z)
static fr_dict_t const * dict_freeradius
module_rlm_t rlm_attr_filter
static fr_dict_t const * dict_radius
PAIR_LIST_LIST attrs
fr_dict_attr_autoload_t rlm_attr_filter_dict_attr[]
static fr_dict_attr_t const * attr_vendor_specific
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 unlang_action_t mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_chap.c:176
static unlang_action_t mod_accounting(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Write accounting data to Couchbase documents.
static unlang_action_t mod_post_auth(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_detail.c:400
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1312
static sql_fall_through_t fall_through(map_list_t *maps)
Definition: rlm_sql.c:143
static unlang_action_t mod_preacct(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request)
Definition: rlm_test.c:248
#define MODULE_NAME_TERMINATOR
Definition: module.h:135
#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:899
#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:1044
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:270
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))