All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_csv.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: c53c827422b264f43057d5928740abd18228a4af $
19  * @file rlm_csv.c
20  * @brief Read and map CSV files
21  *
22  * @copyright 2015 The FreeRADIUS server project
23  * @copyright 2015 Alan DeKok <aland@freeradius.org>
24  */
25 RCSID("$Id: c53c827422b264f43057d5928740abd18228a4af $")
26 
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
30 
31 #include <freeradius-devel/map_proc.h>
32 
33 static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request,
34  char const *key, vp_map_t const *maps);
35 
36 /*
37  * Define a structure for our module configuration.
38  *
39  * These variables do not need to be in a structure, but it's
40  * a lot cleaner to do so, and a pointer to the structure can
41  * be used as the instance handle.
42  */
43 typedef struct rlm_csv_t {
44  char const *name;
45  char const *filename;
46  char const *delimiter;
47  char const *header;
48  char const *key;
49 
52  int key_field;
53 
54  char const **field_names;
55  int *field_offsets; /* field X from the file maps to array entry Y here */
57 } rlm_csv_t;
58 
59 typedef struct rlm_csv_entry_t {
61  char const *key;
62  char *data[];
64 
65 /*
66  * A mapping of configuration file names to internal variables.
67  */
68 static const CONF_PARSER module_config[] = {
70  { FR_CONF_OFFSET("delimiter", PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_NOT_EMPTY, rlm_csv_t, delimiter), .dflt = "," },
74 };
75 
76 static int csv_entry_cmp(void const *one, void const *two)
77 {
78  rlm_csv_entry_t const *a = one;
79  rlm_csv_entry_t const *b = two;
80 
81  return strcmp(a->key, b->key);
82 }
83 
84 /*
85  * Allow for quotation marks.
86  */
87 static bool buf2entry(rlm_csv_t *inst, char *buf, char **out)
88 {
89  char *p, *q;
90 
91  if (*buf != '"') {
92  *out = strchr(buf + 1, *inst->delimiter);
93 
94  if (!*out) { /* mash CR / LF */
95  for (p = buf + 1; *p != '\0'; p++) {
96  if (*p < ' ') {
97  *p = '\0';
98  break;
99  }
100  }
101  }
102 
103  return true;
104  }
105 
106  p = buf + 1;
107  q = buf;
108 
109  while (*p) {
110  if (*p < ' ') {
111  *q = '\0';
112  *out = NULL;
113  return true;
114  }
115 
116  /*
117  * Double quotes to single quotes.
118  */
119  if ((*p == '"') && (p[1] == '"')) {
120  *(q++) = '"';
121  p += 2;
122  continue;
123  }
124 
125  /*
126  * Double quotes and EOL mean we're done.
127  */
128  if ((*p == '"') && (p[1] < ' ')) {
129  *(q++) = '\0';
130 
131  *out = NULL;
132  return true;
133  }
134 
135  /*
136  * Double quotes and delimiter: point to the delimiter.
137  */
138  if ((*p == '"') && (p[1] == *inst->delimiter)) {
139  *(q++) = '\0';
140 
141  *out = p + 1;
142  return true;
143  }
144 
145  /*
146  * Everything else gets copied over verbatim
147  */
148  *(q++) = *(p++);
149  *q = '\0';
150  }
151 
152  return false;
153 }
154 
155 /*
156  * Convert a buffer to a CSV entry
157  */
158 static rlm_csv_entry_t *file2csv(CONF_SECTION *conf, rlm_csv_t *inst, int lineno, char *buffer)
159 {
160  rlm_csv_entry_t *e;
161  int i;
162  char *p, *q;
163 
164  e = (rlm_csv_entry_t *) talloc_zero_array(inst->tree, uint8_t, sizeof(*e) + inst->used_fields + sizeof(e->data[0]));
165  if (!e) {
166  cf_log_err_cs(conf, "Out of memory");
167  return NULL;
168  }
169 
170  for (p = buffer, i = 0; p != NULL; p = q, i++) {
171  if (!buf2entry(inst, p, &q)) {
172  cf_log_err_cs(conf, "Malformed entry in file %s line %d", inst->filename, lineno);
173  return NULL;
174  }
175 
176  if (q) *(q++) = '\0';
177 
178  if (i >= inst->num_fields) {
179  cf_log_err_cs(conf, "Too many fields at file %s line %d", inst->filename, lineno);
180  return NULL;
181  }
182 
183  /*
184  * This is the key field.
185  */
186  if (i == inst->key_field) {
187  e->key = talloc_strdup(e, p);
188  continue;
189  }
190 
191  /*
192  * This field is unused. Ignore it.
193  */
194  if (inst->field_offsets[i] < 0) continue;
195 
196  e->data[inst->field_offsets[i]] = talloc_strdup(e, p);
197  }
198 
199  if (i < inst->num_fields) {
200  cf_log_err_cs(conf, "Too few fields at file %s line %d (%d < %d)", inst->filename, lineno, i, inst->num_fields);
201  return NULL;
202  }
203 
204  /*
205  * FIXME: Allow duplicate keys later.
206  */
207  if (!rbtree_insert(inst->tree, e)) {
208  cf_log_err_cs(conf, "Failed inserting entry for filename %s line %d: duplicate entry",
209  inst->filename, lineno);
210  return NULL;
211  }
212 
213  return e;
214 }
215 
216 
217 static int fieldname2offset(rlm_csv_t *inst, char const *field_name)
218 {
219  int i;
220 
221  /*
222  * Find out which field the RHS maps to.
223  *
224  * For maps of less than 32 entries or so, an
225  * array is faster than more complex solutions.
226  */
227  for (i = 0; i < inst->num_fields; i++) {
228  if (strcmp(field_name, inst->field_names[i]) == 0) {
229  return inst->field_offsets[i];
230  }
231  }
232 
233  return -1;
234 }
235 
236 
237 /*
238  * Verify the result of the map.
239  */
240 static int csv_map_verify(UNUSED void *proc_inst, void *mod_inst, UNUSED vp_tmpl_t const *src, vp_map_t const *maps)
241 {
242  rlm_csv_t *inst = mod_inst;
243  vp_map_t const *map;
244 
245  for (map = maps;
246  map != NULL;
247  map = map->next) {
248  if (map->rhs->type != TMPL_TYPE_UNPARSED) continue;
249 
250  if (fieldname2offset(inst, map->rhs->name) < 0) {
251  cf_log_err(map->ci, "Unknown field '%s'", map->rhs->name);
252  return -1;
253  }
254  }
255 
256  return 0;
257 }
258 
259 
260 /*
261  * Do any per-module initialization that is separate to each
262  * configured instance of the module. e.g. set up connections
263  * to external databases, read configuration files, set up
264  * dictionary entries, etc.
265  *
266  * If configuration information is given in the config section
267  * that must be referenced in later calls, store a handle to it
268  * in *instance otherwise put a null pointer there.
269  */
270 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
271 {
272  rlm_csv_t *inst = instance;
273  int i;
274  char const *p;
275  char *q;
276  char *header;
277  FILE *fp;
278  int lineno;
279  char buffer[8192];
280 
281  inst->name = cf_section_name2(conf);
282  if (!inst->name) {
283  inst->name = cf_section_name1(conf);
284  }
285 
286  if (inst->delimiter[1]) {
287  cf_log_err_cs(conf, "'delimiter' must be one character long");
288  return -1;
289  }
290 
291  for (p = inst->header; p != NULL; p = strchr(p + 1, *inst->delimiter)) {
292  inst->num_fields++;
293  }
294 
295  if (inst->num_fields < 2) {
296  cf_log_err_cs(conf, "Must have at least a key field and data field");
297  return -1;
298  }
299 
300  inst->field_names = talloc_array(inst, const char *, inst->num_fields);
301  if (!inst->field_names) {
302  oom:
303  cf_log_err_cs(conf, "Out of memory");
304  return -1;
305  }
306 
307  inst->field_offsets = talloc_array(inst, int, inst->num_fields);
308  if (!inst->field_offsets) goto oom;
309 
310  for (i = 0; i < inst->num_fields; i++) {
311  inst->field_offsets[i] = -1; /* unused */
312  }
313 
314  /*
315  * Get a writeable copy of the header
316  */
317  header = talloc_strdup(inst, inst->header);
318  if (!header) goto oom;
319 
320  /*
321  * Mark up the field names. Note that they can be empty,
322  * in which case they don't map to anything.
323  */
324  inst->key_field = -1;
325 
326  /*
327  * FIXME: remove whitespace from field names, if we care.
328  */
329  for (p = header, i = 0; p != NULL; p = q, i++) {
330  q = strchr(p, *inst->delimiter);
331 
332  /*
333  * Fields 0..N-1
334  */
335  if (q) {
336  *q = '\0';
337 
338  if (q > (p + 1)) {
339  if (strcmp(p, inst->key) == 0) {
340  inst->key_field = i;
341  } else {
342  inst->field_offsets[i] = inst->used_fields++;
343  }
344  }
345  q++;
346 
347  } else { /* field N */
348  if (*p) {
349  if (strcmp(p, inst->key) == 0) {
350  inst->key_field = i;
351  } else {
352  inst->field_offsets[i] = inst->used_fields++;
353  }
354  }
355  }
356 
357  /*
358  * Save the field names, even when they're not used.
359  */
360  inst->field_names[i] = p;
361  }
362 
363  if (inst->key_field < 0) {
364  cf_log_err_cs(conf, "Key field '%s' does not appear in header", inst->key);
365  return -1;
366  }
367 
368  inst->tree = rbtree_create(inst, csv_entry_cmp, NULL, 0);
369  if (!inst->tree) goto oom;
370 
371  /*
372  * Read the file line by line.
373  */
374  fp = fopen(inst->filename, "r");
375  if (!fp) {
376  cf_log_err_cs(conf, "Error opening filename %s: %s", inst->filename, strerror(errno));
377  return -1;
378  }
379 
380  lineno = 1;
381  while (fgets(buffer, sizeof(buffer), fp)) {
382  rlm_csv_entry_t *e;
383 
384  e = file2csv(conf, inst, lineno, buffer);
385  if (!e) {
386  fclose(fp);
387  return -1;
388  }
389 
390  lineno++;
391  }
392 
393  fclose(fp);
394 
395  /*
396  * And register the map function.
397  */
398  map_proc_register(inst, inst->name, mod_map_proc, NULL, csv_map_verify, 0);
399 
400  return 0;
401 }
402 
403 /*
404  * Convert field X to a VP.
405  */
406 static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
407 {
408  char const *str = uctx;
409  VALUE_PAIR *head = NULL, *vp;
410  vp_cursor_t cursor;
411  fr_dict_attr_t const *da;
412 
413  rad_assert(ctx != NULL);
414  fr_cursor_init(&cursor, &head);
415 
416  /*
417  * FIXME: allow multiple entries.
418  */
419  if (map->lhs->type == TMPL_TYPE_ATTR) {
420  da = map->lhs->tmpl_da;
421 
422  } else {
423  char *attr;
424 
425  if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) {
426  RWDEBUG("Failed expanding string");
427  return -1;
428  }
429 
430  da = fr_dict_attr_by_name(NULL, attr);
431  if (!da) {
432  RWDEBUG("No such attribute '%s'", attr);
433  return -1;
434  }
435 
436  talloc_free(attr);
437  }
438 
439  vp = fr_pair_afrom_da(ctx, da);
440  rad_assert(vp);
441 
442  if (fr_pair_value_from_str(vp, str, talloc_array_length(str) - 1) < 0) {
443  char *escaped;
444 
445  escaped = fr_asprint(vp, str, talloc_array_length(str) - 1, '\'');
446  RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped,
447  map->lhs->tmpl_da->name, fr_strerror());
448 
449  talloc_free(vp); /* also frees escaped */
450  return -1;
451  }
452 
453  vp->op = map->op;
454  fr_cursor_merge(&cursor, vp);
455 
456  *out = head;
457  return 0;
458 }
459 
460 
461 
462 /** Perform a search and map the result of the search to server attributes
463  *
464  * @param[in] mod_inst #rlm_csv_t
465  * @param[in] proc_inst mapping map entries to field numbers.
466  * @param[in,out] request The current request.
467  * @param[in] key key to look for
468  * @param[in] maps Head of the map list.
469  * @return
470  * - #RLM_MODULE_NOOP no rows were returned.
471  * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST.
472  * - #RLM_MODULE_FAIL if an error occurred.
473  */
474 static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request,
475  char const *key, vp_map_t const *maps)
476 {
477  rlm_csv_t *inst = mod_inst;
478  rlm_csv_entry_t *e, my_entry;
479  vp_map_t const *map;
480 
481  my_entry.key = key;
482 
483  e = rbtree_finddata(inst->tree, &my_entry);
484  if (!e) return RLM_MODULE_NOOP;
485 
486  RINDENT();
487  for (map = maps;
488  map != NULL;
489  map = map->next) {
490  int field;
491  char *field_name;
492 
493  /*
494  * Avoid memory allocations if possible.
495  */
496  if (map->rhs->type != TMPL_TYPE_UNPARSED) {
497  if (tmpl_aexpand(request, &field_name, request, map->rhs, NULL, NULL) < 0) {
498  RDEBUG("Failed expanding RHS at %s", map->lhs->name);
499  return RLM_MODULE_FAIL;
500  }
501  } else {
502  memcpy(&field_name, &map->rhs->name, sizeof(field_name)); /* const */
503  }
504 
505  field = fieldname2offset(inst, field_name);
506 
507  if (field_name != map->rhs->name) talloc_free(field_name);
508 
509  if (field < 0) {
510  RDEBUG("No such field name %s", map->rhs->name);
511  return RLM_MODULE_FAIL;
512  }
513 
514  /*
515  * Pass the raw data to the callback, which will
516  * create the VP and add it to the map.
517  */
518  if (map_to_request(request, map, csv_map_getvalue, e->data[field]) < 0) {
519  return RLM_MODULE_FAIL;
520  }
521  }
522 
523  return RLM_MODULE_UPDATED;
524 }
525 
526 extern module_t rlm_csv;
527 module_t rlm_csv = {
529  .name = "csv",
530  .type = 0,
531  .inst_size = sizeof(rlm_csv_t),
532  .config = module_config,
533  .bootstrap = mod_bootstrap,
534 };
char * data[]
Definition: rlm_csv.c:62
char const * filename
Definition: rlm_csv.c:45
#define PW_TYPE_FILE_INPUT
File matching value must exist, and must be readable.
Definition: conffile.h:204
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
int int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert vp_map_t to VALUE_PAIR (s) and add them to a REQUEST.
Definition: map.c:1019
static const CONF_PARSER module_config[]
Definition: rlm_csv.c:68
int * field_offsets
Definition: rlm_csv.c:55
Metadata exported by the module.
Definition: modules.h:134
Dictionary attribute.
Definition: dict.h:77
char const * name
Raw string used to create the template.
Definition: tmpl.h:190
vp_tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition: map.h:47
Dictionary attribute.
Definition: tmpl.h:133
vp_tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition: map.h:48
int key_field
Definition: rlm_csv.c:52
static int csv_entry_cmp(void const *one, void const *two)
Definition: rlm_csv.c:76
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
Unparsed literal string.
Definition: tmpl.h:131
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
VALUE_PAIR * fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR *const *node)
Setup a cursor to iterate over attribute pairs.
Definition: cursor.c:60
static bool buf2entry(rlm_csv_t *inst, char *buf, char **out)
Definition: rlm_csv.c:87
int map_proc_register(void *mod_inst, char const *name, map_proc_func_t evaluate, xlat_escape_t escape, map_proc_instantiate_t instantiate, size_t inst_size)
Register a map processor.
Definition: map_proc.c:132
#define inst
void * rbtree_finddata(rbtree_t *tree, void const *data)
Find the user data.
Definition: rbtree.c:537
static expr_map_t map[]
Definition: rlm_expr.c:169
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
Abstraction to allow iterating over different configurations of VALUE_PAIRs.
Definition: pair.h:144
struct vp_map * next
The next valuepair map.
Definition: map.h:55
int num_fields
Definition: rlm_csv.c:50
#define rad_assert(expr)
Definition: rad_assert.h:38
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
Definition: rlm_csv.c:270
rbtree_t * rbtree_create(TALLOC_CTX *ctx, rb_comparator_t compare, rb_free_t node_free, int flags)
Create a new RED-BLACK tree.
Definition: rbtree.c:112
void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *vp)
Merges multiple VALUE_PAIR into the cursor.
Definition: cursor.c:394
struct rlm_csv_entry_t * next
Definition: rlm_csv.c:60
char const ** field_names
Definition: rlm_csv.c:54
static rlm_csv_entry_t * file2csv(CONF_SECTION *conf, rlm_csv_t *inst, int lineno, char *buffer)
Definition: rlm_csv.c:158
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
ssize_t tmpl_aexpand(TALLOC_CTX *ctx, char **out, REQUEST *request, vp_tmpl_t const *vpt, xlat_escape_t escape, void *escape_ctx)
Expand a template to a string, allocing a new buffer to hold the string.
Definition: tmpl.c:1653
static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
Definition: rlm_csv.c:406
struct rlm_csv_entry_t rlm_csv_entry_t
char const * name
Definition: rlm_csv.c:44
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
char const * key
Definition: rlm_csv.c:61
int fr_pair_value_from_str(VALUE_PAIR *vp, char const *value, size_t len)
Convert string value to native attribute value.
Definition: pair.c:1840
tmpl_type_t type
What type of value tmpl refers to.
Definition: tmpl.h:188
static rs_t * conf
Definition: radsniff.c:46
char const * fr_strerror(void)
Get the last library error.
Definition: log.c:212
int used_fields
Definition: rlm_csv.c:51
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
rbtree_t * tree
Definition: rlm_csv.c:56
#define PW_TYPE_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition: conffile.h:211
Module succeeded without doing anything.
Definition: radiusd.h:96
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
struct rlm_csv_t rlm_csv_t
Module failed, don't reply.
Definition: radiusd.h:90
void cf_log_err(CONF_ITEM const *ci, char const *fmt,...) CC_HINT(format(printf
FR_TOKEN op
The operator that controls insertion of the dst attribute.
Definition: map.h:50
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
char const * header
Definition: rlm_csv.c:47
char * fr_asprint(TALLOC_CTX *ctx, char const *in, ssize_t inlen, char quote)
Escape string that may contain binary data, and write it to a new buffer.
Definition: print.c:390
bool rbtree_insert(rbtree_t *tree, void *data)
Definition: rbtree.c:329
char const * key
Definition: rlm_csv.c:48
static int csv_map_verify(UNUSED void *proc_inst, void *mod_inst, UNUSED vp_tmpl_t const *src, vp_map_t const *maps)
Definition: rlm_csv.c:240
module_t rlm_csv
Definition: rlm_csv.c:527
#define PW_TYPE_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: conffile.h:200
VALUE_PAIR * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute.
Definition: pair.c:58
String of printable characters.
Definition: radius.h:33
#define RWDEBUG(fmt,...)
Definition: log.h:251
Definition: rlm_csv.c:59
char const * delimiter
Definition: rlm_csv.c:46
#define RCSID(id)
Definition: build.h:135
OK (pairs modified).
Definition: radiusd.h:97
Value pair map.
Definition: map.h:46
#define RDEBUG(fmt,...)
Definition: log.h:243
A source or sink of value data.
Definition: tmpl.h:187
static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request, char const *key, vp_map_t const *maps)
Perform a search and map the result of the search to server attributes.
Definition: rlm_csv.c:474
CONF_ITEM * ci
Config item that the map was created from.
Definition: map.h:52
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_t *dict, char const *attr)
Locate a fr_dict_attr_t by its name.
Definition: dict.c:3493
static int fieldname2offset(rlm_csv_t *inst, char const *field_name)
Definition: rlm_csv.c:217