The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 853bde97c05416d2b4df74c6f09b3a019a81517e $
19  * @file rlm_csv.c
20  * @brief Read and map CSV files
21  *
22  * @copyright 2019 The FreeRADIUS server project
23  * @copyright 2019 Alan DeKok (aland@freeradius.org)
24  */
25 RCSID("$Id: 853bde97c05416d2b4df74c6f09b3a019a81517e $")
26 
27 #include <freeradius-devel/server/base.h>
28 #include <freeradius-devel/server/module_rlm.h>
29 #include <freeradius-devel/util/htrie.h>
30 #include <freeradius-devel/util/debug.h>
31 
32 #include <freeradius-devel/server/map_proc.h>
33 
34 static unlang_action_t mod_map_proc(rlm_rcode_t *p_result, void const *mod_inst, UNUSED void *proc_inst, request_t *request,
35  fr_value_box_list_t *key, map_list_t const *maps);
36 
37 /*
38  * Define a structure for our module configuration.
39  *
40  * These variables do not need to be in a structure, but it's
41  * a lot cleaner to do so, and a pointer to the structure can
42  * be used as the instance handle.
43  */
44 typedef struct {
45  char const *filename;
46  char const *delimiter;
47  char const *fields;
48  char const *index_field_name;
49 
50  bool header;
53 
57 
58  char const **field_names;
59  int *field_offsets; /* field X from the file maps to array entry Y here */
63 
66 
67  map_list_t map; //!< if there is an "update" section in the configuration.
68 } rlm_csv_t;
69 
70 typedef struct rlm_csv_entry_s rlm_csv_entry_t;
75  char *data[];
76 };
77 
78 /*
79  * A mapping of configuration file names to internal variables.
80  */
81 static const conf_parser_t module_config[] = {
83  { FR_CONF_OFFSET_FLAGS("delimiter", CONF_FLAG_NOT_EMPTY, rlm_csv_t, delimiter), .dflt = "," },
84  { FR_CONF_OFFSET("fields", rlm_csv_t, fields) },
85  { FR_CONF_OFFSET("header", rlm_csv_t, header) },
86  { FR_CONF_OFFSET("allow_multiple_keys", rlm_csv_t, allow_multiple_keys) },
87  { FR_CONF_OFFSET_FLAGS("index_field", CONF_FLAG_REQUIRED | CONF_FLAG_NOT_EMPTY, rlm_csv_t, index_field_name) },
88  { FR_CONF_OFFSET("key", rlm_csv_t, key) },
90 };
91 
92 /*
93  * Allow for quotation marks.
94  */
95 static bool buf2entry(rlm_csv_t *inst, char *buf, char **out)
96 {
97  char *p, *q;
98 
99  if (*buf != '"') {
100  *out = strchr(buf + 1, *inst->delimiter);
101 
102  if (!*out) { /* mash CR / LF */
103  for (p = buf; *p != '\0'; p++) {
104  if (*p < ' ') {
105  *p = '\0';
106  break;
107  }
108  }
109  }
110 
111  return true;
112  }
113 
114  p = buf + 1;
115  q = buf;
116 
117  while (*p) {
118  if (*p < ' ') {
119  *q = '\0';
120  *out = NULL;
121  return true;
122  }
123 
124  /*
125  * Double quotes to single quotes.
126  */
127  if ((*p == '"') && (p[1] == '"')) {
128  *(q++) = '"';
129  p += 2;
130  continue;
131  }
132 
133  /*
134  * Double quotes and EOL mean we're done.
135  */
136  if ((*p == '"') && (p[1] < ' ')) {
137  *(q++) = '\0';
138 
139  *out = NULL;
140  return true;
141  }
142 
143  /*
144  * Double quotes and delimiter: point to the delimiter.
145  */
146  if ((*p == '"') && (p[1] == *inst->delimiter)) {
147  *(q++) = '\0';
148 
149  *out = p + 1;
150  return true;
151  }
152 
153  /*
154  * Everything else gets copied over verbatim
155  */
156  *(q++) = *(p++);
157  *q = '\0';
158  }
159 
160  return false;
161 }
162 
163 
164 static int8_t csv_cmp(void const *one, void const *two)
165 {
166  rlm_csv_entry_t const *a = (rlm_csv_entry_t const *) one; /* may not be talloc'd! */
167  rlm_csv_entry_t const *b = (rlm_csv_entry_t const *) two; /* may not be talloc'd! */
168 
169  return fr_value_box_cmp(a->key, b->key);
170 }
171 
172 static uint32_t csv_hash(void const *data)
173 {
174  rlm_csv_entry_t const *a = (rlm_csv_entry_t const *) data; /* may not be talloc'd! */
175 
176  return fr_value_box_hash(a->key);
177 }
178 
179 static int csv_to_key(uint8_t **out, size_t *outlen, void const *data)
180 {
181  rlm_csv_entry_t const *a = (rlm_csv_entry_t const *) data; /* may not be talloc'd! */
182 
183  return fr_value_box_to_key(out, outlen, a->key);
184 
185 }
186 
187 
189 {
190  rlm_csv_entry_t *old;
191 
192  fr_assert(e != NULL);
193 
194  old = fr_htrie_find(inst->trie, e);
195  if (old) {
196  if (!inst->allow_multiple_keys && !inst->multiple_index_fields) {
197  cf_log_err(conf, "%s[%d]: Multiple entries are disallowed", inst->filename, lineno);
198  goto fail;
199  }
200 
201  /*
202  * Save this entry at the tail of the matching
203  * entries.
204  */
205  while (old->next) old = old->next;
206  old->next = e;
207  return true;
208  }
209 
210  if (!fr_htrie_insert(inst->trie, e)) {
211  cf_log_err(conf, "Failed inserting entry for file %s line %d: %s",
212  inst->filename, lineno, fr_strerror());
213 fail:
214  talloc_free(e);
215  return false;
216  }
217 
218  return true;
219 }
220 
221 
222 static bool duplicate_entry(CONF_SECTION *conf, rlm_csv_t *inst, rlm_csv_entry_t *old, char *p, int lineno)
223 {
224  int i;
225  fr_type_t type = inst->key_data_type;
226  rlm_csv_entry_t *e;
227 
228  MEM(e = (rlm_csv_entry_t *)talloc_zero_array(inst, uint8_t,
229  sizeof(*e) + (inst->used_fields * sizeof(e->data[0]))));
230  talloc_set_type(e, rlm_csv_entry_t);
231 
233  if (!e->key) goto fail;
234 
235  if (fr_value_box_from_str(e->key, e->key, type, NULL, p, strlen(p), NULL, false) < 0) {
236  cf_log_err(conf, "Failed parsing key field in file %s line %d - %s", inst->filename, lineno,
237  fr_strerror());
238  fail:
239  talloc_free(e);
240  return false;
241  }
242 
243  /*
244  * Copy the other fields;
245  */
246  for (i = 0; i < inst->used_fields; i++) {
247  if (old->data[i]) e->data[i] = old->data[i]; /* no need to dup it, it's never freed... */
248  }
249 
250  return insert_entry(conf, inst, e, lineno);
251 }
252 
253 /*
254  * Convert a buffer to a CSV entry
255  */
256 static bool file2csv(CONF_SECTION *conf, rlm_csv_t *inst, int lineno, char *buffer)
257 {
258  rlm_csv_entry_t *e;
259  int i;
260  char *p, *q;
261 
262  MEM(e = (rlm_csv_entry_t *)talloc_zero_array(inst, uint8_t,
263  sizeof(*e) + (inst->used_fields * sizeof(e->data[0]))));
264  talloc_set_type(e, rlm_csv_entry_t);
265 
266  for (p = buffer, i = 0; p != NULL; p = q, i++) {
267  if (!buf2entry(inst, p, &q)) {
268  cf_log_err(conf, "Malformed entry in file %s line %d", inst->filename, lineno);
269  return false;
270  }
271 
272  if (q) *(q++) = '\0';
273 
274  if (i >= inst->num_fields) {
275  cf_log_err(conf, "Too many fields at file %s line %d", inst->filename, lineno);
276  return false;
277  }
278 
279  /*
280  * This is the key field.
281  */
282  if (i == inst->index_field) {
283  fr_type_t type = inst->key_data_type;
284 
285  /*
286  * Check for /etc/group style keys.
287  */
288  if (inst->multiple_index_fields) {
289  char *l;
290 
291  /*
292  * Silently omit empty entries.
293  */
294  if (!*p) {
295  talloc_free(e);
296  return true;
297  }
298 
299  /*
300  * Check & smash ','. duplicate
301  * 'e', and insert it into the
302  * hash table / trie.
303  */
304  l = strchr(p, ',');
305  while (l) {
306  *l = '\0';
307 
308  if (!duplicate_entry(conf, inst, e, p, lineno)) goto fail;
309 
310  p = l + 1;
311  l = strchr(p, ',');
312  }
313  }
314 
315  /*
316  * Set the last entry to use 'e'
317  */
319  if (!e->key) goto fail;
320 
321  if (fr_value_box_from_str(e->key, e->key, type, NULL,
322  p, strlen(p), NULL, false) < 0) {
323  cf_log_err(conf, "Failed parsing key field in file %s line %d - %s", inst->filename, lineno,
324  fr_strerror());
325  fail:
326  talloc_free(e);
327  return false;
328  }
329  continue;
330  }
331 
332  /*
333  * This field is unused. Ignore it.
334  */
335  if (inst->field_offsets[i] < 0) continue;
336 
337  /*
338  * Try to parse fields as data types if the data type is defined.
339  */
340  if (inst->field_types[i] != FR_TYPE_NULL) {
341  fr_value_box_t box;
342  fr_type_t type = inst->field_types[i];
343 
344  if (fr_value_box_from_str(e, &box, type, NULL,
345  p, strlen(p), NULL, false) < 0) {
346  cf_log_err(conf, "Failed parsing field '%s' in file %s line %d - %s", inst->field_names[i],
347  inst->filename, lineno, fr_strerror());
348  goto fail;
349  }
350 
351  fr_value_box_clear(&box);
352  }
353 
354  MEM(e->data[inst->field_offsets[i]] = talloc_typed_strdup(e, p));
355  }
356 
357  if (i < inst->num_fields) {
358  cf_log_err(conf, "Too few fields in file %s at line %d (%d < %d)", inst->filename, lineno, i, inst->num_fields);
359  goto fail;
360  }
361 
362  return insert_entry(conf, inst, e, lineno);
363 }
364 
365 
366 static int fieldname2offset(rlm_csv_t const *inst, char const *field_name, int *array_offset)
367 {
368  int i;
369 
370  /*
371  * Find out which field the RHS maps to.
372  *
373  * For maps of less than 32 entries or so, an
374  * array is faster than more complex solutions.
375  */
376  for (i = 0; i < inst->num_fields; i++) {
377  if (strcmp(field_name, inst->field_names[i]) == 0) {
378  if (array_offset) *array_offset = i;
379  return inst->field_offsets[i];
380  }
381  }
382 
383  return -1;
384 }
385 
386 #define CSV_MAX_ATTRMAP (128)
387 
388 /*
389  * Verify one map entry.
390  */
391 static int csv_map_verify(map_t *map, void *instance)
392 {
393  rlm_csv_t *inst = talloc_get_type_abort(instance, rlm_csv_t);
394  int offset;
396 
397  /*
398  * Destinations where we can put the fr_pair_ts we
399  * create using CSV values.
400  */
401  switch (map->lhs->type) {
402  case TMPL_TYPE_ATTR:
403  type = tmpl_attr_tail_da(map->lhs)->type;
404  break;
405 
407  cf_log_err(map->ci, "Unknown attribute %s", tmpl_attr_tail_unresolved(map->lhs));
408  return -1;
409 
410  default:
411  cf_log_err(map->ci, "Left hand side of map must be an attribute or list, not a %s",
412  tmpl_type_to_str(map->lhs->type));
413  return -1;
414  }
415 
416  /*
417  * Sources we can use to get the name of the attribute
418  * we're retrieving from LDAP.
419  */
420  switch (map->rhs->type) {
422  offset = -1;
423  if (fieldname2offset(inst, map->rhs->name, &offset) < 0) {
424  cf_log_err(map->ci, "Unknown field '%s'", map->rhs->name);
425  return -1;
426  }
427 
428  if (fr_type_is_null(type) || (offset < 0)) break;
429 
430  /*
431  * Try to set the data type of the field. But if
432  * they map the same field to two different data
433  * types, that's an error.
434  */
435  if (fr_type_is_null(inst->field_types[offset])) {
436  inst->field_types[offset] = type;
437  break;
438  }
439 
440  if (inst->field_types[offset] != type) {
441  cf_log_err(map->ci, "Field '%s' maps to two different data types, making it impossible to parse it.", map->rhs->name);
442  return -1;
443  }
444 
445  break;
446 
448  cf_log_err(map->ci, "Unknown attribute %s", tmpl_attr_tail_unresolved(map->rhs));
449  return -1;
450 
451  default:
452  cf_log_err(map->ci, "Right hand side of map must be a field name, not a %s",
453  tmpl_type_to_str(map->rhs->type));
454  return -1;
455  }
456 
457  /*
458  * Only some operators are allowed.
459  */
460  switch (map->op) {
461  case T_OP_SET:
462  case T_OP_EQ:
463  case T_OP_SUB_EQ:
464  case T_OP_ADD_EQ:
465  case T_OP_PREPEND:
466  case T_OP_LT:
467  case T_OP_GT:
468  case T_OP_LE:
469  case T_OP_GE:
470  break;
471 
472  default:
473  cf_log_err(map->ci, "Operator \"%s\" not allowed for CSV mappings",
474  fr_table_str_by_value(fr_tokens_table, map->op, "<INVALID>"));
475  return -1;
476  }
477 
478  return 0;
479 }
480 
481 /*
482  * Verify the result of the map.
483  */
484 static int csv_maps_verify(CONF_SECTION *cs, void const *mod_inst, UNUSED void *proc_inst,
485  tmpl_t const *src, map_list_t const *maps)
486 {
487  map_t const *map = NULL;
488 
489  if (!src) {
490  cf_log_err(cs, "Missing key expansion");
491 
492  return -1;
493  }
494 
495  while ((map = map_list_next(maps, map))) {
496  /*
497  * This function doesn't change the map, so it's OK.
498  */
499  if (csv_map_verify(UNCONST(map_t *, map), UNCONST(void *, mod_inst)) < 0) return -1;
500  }
501 
502  return 0;
503 }
504 
505 /*
506  * Do any per-module initialization that is separate to each
507  * configured instance of the module. e.g. set up connections
508  * to external databases, read configuration files, set up
509  * dictionary entries, etc.
510  *
511  * If configuration information is given in the config section
512  * that must be referenced in later calls, store a handle to it
513  * in *instance otherwise put a null pointer there.
514  */
515 static int mod_bootstrap(module_inst_ctx_t const *mctx)
516 {
517  rlm_csv_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_csv_t);
518  CONF_SECTION *conf = mctx->mi->conf;
519  int i;
520  char const *p;
521  char *q;
522  char *fields;
523  fr_htrie_type_t htype;
524 
525  if (inst->delimiter[1]) {
526  cf_log_err(conf, "'delimiter' must be one character long");
527  return -1;
528  }
529 
530  if (inst->key) {
531  inst->key_data_type = tmpl_expanded_type(inst->key);
532  switch (inst->key_data_type) {
533  case FR_TYPE_LEAF:
534  break;
535 
536  case FR_TYPE_NULL:
537  cf_log_err(conf, "Can't determine key data_type. 'key' must be an expression of type "
538  "xlat, attr, or data");
539  return -1;
540 
541  default:
542  cf_log_err(conf, "Invalid key data type '%s'",
543  fr_type_to_str(inst->key_data_type));
544  return -1;
545  }
546  } else {
547  inst->key_data_type = FR_TYPE_STRING;
548  }
549 
550  /*
551  * IP addresses go into tries. Everything else into binary tries.
552  */
553  htype = fr_htrie_hint(inst->key_data_type);
554  if (htype == FR_HTRIE_INVALID) {
555  cf_log_err(conf, "Invalid data type '%s' used for CSV file.",
556  fr_type_to_str(inst->key_data_type));
557  return -1;
558  }
559 
560  inst->trie = fr_htrie_alloc(inst, htype,
562  (fr_cmp_t) csv_cmp,
564  NULL);
565  if (!inst->trie) {
566  cf_log_err(conf, "Failed creating internal trie: %s", fr_strerror());
567  return -1;
568  }
569 
570  if ((*inst->index_field_name == ',') || (*inst->index_field_name == *inst->delimiter)) {
571  cf_log_err(conf, "Field names cannot begin with the '%c' character", *inst->index_field_name);
572  return -1;
573  }
574 
575  if (inst->delimiter[1] != '\0') {
576  cf_log_err(conf, "The 'delimiter' field MUST be one character long");
577  return -1;
578  }
579 
580  /*
581  * If there is a header in the file, then read that first.
582  */
583  if (inst->header) {
584  FILE *fp;
585  char buffer[8192];
586 
587  fp = fopen(inst->filename, "r");
588  if (!fp) {
589  cf_log_err(conf, "Error opening filename %s: %s", inst->filename, fr_syserror(errno));
590  return -1;
591  }
592 
593  p = fgets(buffer, sizeof(buffer), fp);
594  if (!p) {
595  error_eof:
596  cf_log_err(conf, "Error reading filename %s: Unexpected EOF", inst->filename);
597  fclose(fp);
598  return -1;
599  }
600 
601  q = strchr(buffer, '\n');
602  if (!q) goto error_eof;
603 
604  *q = '\0';
605 
606  /*
607  * Over-write whatever is in the config with the
608  * header from the file.
609  */
610  inst->fields = talloc_strdup(inst, buffer);
611  fclose(fp);
612  }
613 
614  /*
615  * Parse the field names AFTER opening the file. Because
616  * the field names might be taken from the header.
617  */
618  for (p = inst->fields; p != NULL; p = strchr(p + 1, *inst->delimiter)) {
619  inst->num_fields++;
620  }
621 
622  if (inst->num_fields < 2) {
623  cf_log_err(conf, "The CSV file MUST have at least a key field and data field");
624  return -1;
625  }
626 
627  MEM(inst->field_names = talloc_zero_array(inst, const char *, inst->num_fields));
628  MEM(inst->field_offsets = talloc_array(inst, int, inst->num_fields));
629  MEM(inst->field_types = talloc_array(inst, fr_type_t, inst->num_fields));
630 
631  for (i = 0; i < inst->num_fields; i++) {
632  inst->field_offsets[i] = -1; /* unused */
633  inst->field_types[i] = FR_TYPE_NULL;
634  }
635 
636  /*
637  * Get a writable copy of the fields definition
638  */
639  MEM(fields = talloc_typed_strdup(inst, inst->fields));
640 
641  /*
642  * Mark up the field names. Note that they can be empty,
643  * in which case they don't map to anything.
644  */
645  inst->index_field = -1;
646 
647  /*
648  * Parse the field names
649  */
650  i = 0;
651  p = q = fields;
652  while (*q) {
653  bool last_field;
654 
655  /*
656  * Skip the field name
657  */
658  while (*q && (*q != *inst->delimiter)) {
659  if ((*q == '\'') || (*q == '"')) {
660  cf_log_err(conf, "Field %d name cannot have quotation marks.",
661  i + 1);
662  return -1;
663  }
664 
665  if (*q < ' ') {
666  *q = '\0';
667  break;
668  }
669 
670  if (isspace((uint8_t) *q)) {
671  cf_log_err(conf, "Field %d name cannot have spaces.",
672  i + 1);
673  return -1;
674  }
675 
676  q++;
677  }
678 
679  /*
680  * Check for the last field.
681  */
682  if (!*q) {
683  last_field = true;
684  } else {
685  *q = '\0';
686  last_field = false;
687  }
688 
689  /*
690  * Track which field is the key, and which fields
691  * map to which entries.
692  *
693  * Some fields are unused, so there isn't a 1-1
694  * mapping between CSV file fields, and fields
695  * in the map.
696  */
697  if (last_field && (*p == ',') && (strcmp(p + 1, inst->index_field_name) == 0)) {
698  inst->index_field = i;
699  p++;
700 
701  inst->multiple_index_fields = true;
702 
703  } else if (strcmp(p, inst->index_field_name) == 0) {
704  inst->index_field = i;
705 
706  } else if ((*p == ',') || (*p == *inst->delimiter)) {
707  cf_log_err(conf, "Field names MUST NOT begin with the '%c' character", *p);
708  return -1;
709 
710  } else {
711  inst->field_offsets[i] = inst->used_fields++;
712  }
713 
714  /*
715  * Save the field names, even when the field names are empty.
716  */
717  inst->field_names[i] = p;
718 
719  if (last_field) break;
720 
721  q++;
722  i++;
723  p = q;
724  }
725 
726  if (inst->index_field < 0) {
727  cf_log_err(conf, "index_field '%s' does not appear in the list of field names",
728  inst->index_field_name);
729  return -1;
730  }
731 
732  /*
733  * Set the data type of the index field.
734  */
735  inst->field_types[inst->index_field] = inst->key_data_type;
736 
737  /*
738  * And register the `map csv <key> { ... }` function.
739  */
741 
742  return 0;
743 }
744 
745 /** Instantiate the module
746  *
747  * Creates a new instance of the module reading parameters from a configuration section.
748  *
749  * @param[in] mctx configuration data.
750  * @return
751  * - 0 on success.
752  * - < 0 on failure.
753  */
754 static int mod_instantiate(module_inst_ctx_t const *mctx)
755 {
756  rlm_csv_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_csv_t);
757  CONF_SECTION *conf = mctx->mi->conf;
758  CONF_SECTION *cs;
759  int lineno;
760  FILE *fp;
761  tmpl_rules_t parse_rules = {
762  .attr = {
763  .allow_foreign = true /* Because we don't know where we'll be called */
764  }
765  };
766  char buffer[8192];
767 
768  map_list_init(&inst->map);
769  /*
770  * "update" without "key" is invalid, as we can't run the
771  * module.
772  *
773  * "key" without "update" means we just ignore the key.
774  */
775  cs = cf_section_find(conf, "update", CF_IDENT_ANY);
776  if (cs) {
777  if (!inst->key) {
778  cf_log_err(conf, "There is no 'key' defined for the 'update' section");
779  return -1;
780  }
781 
782  /*
783  * Parse the "update" section. This parsing includes
784  * setting inst->field_types[], base on the attribute
785  * mappings.
786  */
787  if (map_afrom_cs(inst, &inst->map, cs,
788  &parse_rules, &parse_rules, csv_map_verify, inst,
789  CSV_MAX_ATTRMAP) < 0) {
790  return -1;
791  }
792  } else if (inst->key) {
793  cf_log_warn(conf, "Ignoring 'key', as no 'update' section has been defined.");
794  }
795 
796  /*
797  * Re-open the file and read it all.
798  */
799  fp = fopen(inst->filename, "r");
800  if (!fp) {
801  cf_log_err(conf, "Error opening filename %s: %s", inst->filename, fr_syserror(errno));
802  return -1;
803  }
804  lineno = 1;
805 
806  /*
807  * If there is a header in the file, then read that first.
808  * This time we just ignore it.
809  */
810  if (inst->header) {
811  char *p = fgets(buffer, sizeof(buffer), fp);
812  if (!p) {
813  cf_log_err(conf, "Error reading filename %s: Unexpected EOF", inst->filename);
814  fclose(fp);
815  return -1;
816  }
817  lineno++;
818  }
819 
820  /*
821  * Read the rest of the file.
822  */
823  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
824  if (!file2csv(conf, inst, lineno, buffer)) {
825  fclose(fp);
826  return -1;
827  }
828 
829  lineno++;
830  }
831  fclose(fp);
832 
833  return 0;
834 }
835 
836 
837 /*
838  * Convert field X to a VP.
839  */
840 static int csv_map_getvalue(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, void *uctx)
841 {
842  char const *str = uctx;
843  fr_pair_t *vp;
844  fr_dict_attr_t const *da;
845 
846  fr_assert(ctx != NULL);
847 
848  if (tmpl_is_attr(map->lhs)) {
849  da = tmpl_attr_tail_da(map->lhs);
850 
851  } else {
852  char *attr;
853 
854  if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) {
855  RWDEBUG("Failed expanding string");
856  return -1;
857  }
858 
859 
860  da = fr_dict_attr_by_name(NULL, fr_dict_root(request->dict), attr);
861  if (!da) {
862  RWDEBUG("No such attribute '%s'", attr);
863  talloc_free(attr);
864  return -1;
865  }
866 
867  talloc_free(attr);
868  }
869 
870  vp = fr_pair_afrom_da(ctx, da);
871  fr_assert(vp);
872 
873  if (fr_pair_value_from_str(vp, str, talloc_array_length(str) - 1, NULL, true) < 0) {
874  RPWDEBUG("Failed parsing value \"%pV\" for attribute %s", fr_box_strvalue_buffer(str),
875  tmpl_attr_tail_da(map->lhs)->name);
876  talloc_free(vp);
877 
878  return -1;
879  }
880 
882 
883  return 0;
884 }
885 
886 
887 /** Perform a search and map the result of the search to server attributes
888  *
889  * @param[in] inst #rlm_csv_t.
890  * @param[in,out] request The current request.
891  * @param[in] key key to look for
892  * @param[in] maps Head of the map list.
893  * @return
894  * - #RLM_MODULE_NOOP no rows were returned.
895  * - #RLM_MODULE_UPDATED if one or more #fr_pair_t were added to the #request_t.
896  * - #RLM_MODULE_FAIL if an error occurred.
897  */
899  fr_value_box_t const *key, map_list_t const *maps)
900 {
902  rlm_csv_entry_t *e;
903  map_t const *map = NULL;
904 
905  e = fr_htrie_find(inst->trie, &(rlm_csv_entry_t) { .key = UNCONST(fr_value_box_t *, key) } );
906  if (!e) {
907  rcode = RLM_MODULE_NOOP;
908  goto finish;
909  }
910 
911 redo:
912  RINDENT();
913  while ((map = map_list_next(maps, map))) {
914  int field;
915  char *field_name;
916 
917  /*
918  * Avoid memory allocations if possible.
919  */
920  if (!tmpl_is_data_unresolved(map->rhs)) {
921  if (tmpl_aexpand(request, &field_name, request, map->rhs, NULL, NULL) < 0) {
922  REXDENT();
923  REDEBUG("Failed expanding RHS at %s", map->lhs->name);
924  rcode = RLM_MODULE_FAIL;
925  goto finish;
926  }
927  } else {
928  field_name = UNCONST(char *, map->rhs->name);
929  }
930 
931  field = fieldname2offset(inst, field_name, NULL);
932 
933  if (field_name != map->rhs->name) talloc_free(field_name);
934 
935  if (field < 0) {
936  REXDENT();
937  REDEBUG("No such field name %s", map->rhs->name);
938  rcode = RLM_MODULE_FAIL;
939  goto finish;
940  }
941 
942  /*
943  * Pass the raw data to the callback, which will
944  * create the VP and add it to the map.
945  */
946  if (map_to_request(request, map, csv_map_getvalue, e->data[field]) < 0) {
947  REXDENT();
948  rcode = RLM_MODULE_FAIL;
949  goto finish;
950  }
951  }
952 
953  REXDENT();
954 
955  if (e->next) {
956  e = e->next;
957  goto redo;
958  }
959 
960 finish:
961  return rcode;
962 }
963 
964 
965 /** Perform a search and map the result of the search to server attributes
966  *
967 * @param[out] p_result Result of applying map:
968  * - #RLM_MODULE_NOOP no rows were returned.
969  * - #RLM_MODULE_UPDATED if one or more #fr_pair_t were added to the #request_t.
970  * - #RLM_MODULE_FAIL if an error occurred.
971  * @param[in] mod_inst #rlm_csv_t.
972  * @param[in] proc_inst mapping map entries to field numbers.
973  * @param[in,out] request The current request.
974  * @param[in] key key to look for
975  * @param[in] maps Head of the map list.
976  * @return UNLANG_ACTION_CALCULATE_RESULT
977  */
978 static unlang_action_t mod_map_proc(rlm_rcode_t *p_result, void const *mod_inst, UNUSED void *proc_inst, request_t *request,
979  fr_value_box_list_t *key, map_list_t const *maps)
980 {
982  fr_value_box_t *key_head = fr_value_box_list_head(key);
983 
984  if (!key_head) {
985  REDEBUG("CSV key cannot be (null)");
987  }
988 
989  if ((inst->key_data_type == FR_TYPE_OCTETS) || (inst->key_data_type == FR_TYPE_STRING)) {
991  key_head, key, inst->key_data_type,
993  SIZE_MAX) < 0) {
994  REDEBUG("Failed parsing key");
996  }
997  }
998 
999  RETURN_MODULE_RCODE(mod_map_apply(inst, request, key_head, maps));
1000 }
1001 
1002 
1003 static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1004 {
1006  rlm_rcode_t rcode;
1007  ssize_t slen;
1008  fr_value_box_t *key;
1009 
1010  if (map_list_empty(&inst->map) || !inst->key) RETURN_MODULE_NOOP;
1011 
1012  /*
1013  * Expand the key to whatever it is. For attributes,
1014  * this usually just means copying the value box.
1015  */
1016  slen = tmpl_aexpand_type(request, &key, FR_TYPE_VALUE_BOX, request, inst->key, NULL, NULL);
1017  if (slen < 0) {
1018  DEBUG("Failed expanding key '%s'", inst->key->name);
1020  }
1021 
1022  /*
1023  * If the output data was string and we wanted non-string
1024  * data, convert it now.
1025  */
1026  if (key->type != inst->key_data_type) {
1027  fr_value_box_t tmp;
1028 
1029  fr_value_box_copy(request, &tmp, key);
1030 
1031  slen = fr_value_box_cast(request, key, inst->key_data_type, NULL, &tmp);
1032  fr_value_box_clear(&tmp);
1033  if (slen < 0) {
1034  talloc_free(key);
1035  DEBUG("Failed casting %pV to data type '%s'",
1036  &key, fr_type_to_str(inst->key_data_type));
1038  }
1039  }
1040 
1041  RDEBUG2("Processing CVS map with key %pV", key);
1042  RINDENT();
1043  rcode = mod_map_apply(inst, request, key, &inst->map);
1044  REXDENT();
1045 
1046  talloc_free(key);
1047  RETURN_MODULE_RCODE(rcode);
1048 }
1049 
1050 extern module_rlm_t rlm_csv;
1052  .common = {
1053  .magic = MODULE_MAGIC_INIT,
1054  .name = "csv",
1055  .flags = MODULE_TYPE_DYNAMIC_UNSAFE,
1056  .inst_size = sizeof(rlm_csv_t),
1057  .config = module_config,
1058  .bootstrap = mod_bootstrap,
1060  },
1061  .method_group = {
1062  .bindings = (module_method_binding_t[]){
1063  { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = mod_process },
1065  }
1066  }
1067 };
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 UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition: build.h:165
#define RCSID(id)
Definition: build.h:481
#define UNUSED
Definition: build.h:313
#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
@ CONF_FLAG_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition: cf_parse.h:420
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:564
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:101
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition: cf_util.c:1028
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:289
#define cf_log_warn(_cf, _fmt,...)
Definition: cf_util.h:290
#define CF_IDENT_ANY
Definition: cf_util.h:78
fr_dcursor_eval_t void const * uctx
Definition: dcursor.h:546
#define DEBUG(fmt,...)
Definition: dhcpclient.c:39
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_attr_err_t *err, fr_dict_attr_t const *parent, char const *attr))
Locate a fr_dict_attr_t by its name.
Definition: dict_util.c:3263
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition: dict_util.c:2400
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:63
uint32_t(* fr_hash_t)(void const *)
Definition: hash.h:36
fr_htrie_t * fr_htrie_alloc(TALLOC_CTX *ctx, fr_htrie_type_t type, fr_hash_t hash_data, fr_cmp_t cmp_data, fr_trie_key_t get_key, fr_free_t free_data)
An abstraction over our internal hashes, rb trees, and prefix tries.
Definition: htrie.c:92
fr_htrie_type_t
Definition: htrie.h:49
@ FR_HTRIE_INVALID
Definition: htrie.h:50
static fr_htrie_type_t fr_htrie_hint(fr_type_t type)
Definition: htrie.h:149
static bool fr_htrie_insert(fr_htrie_t *ht, void const *data)
Insert data into a htrie.
Definition: htrie.h:112
static void * fr_htrie_find(fr_htrie_t *ht, void const *data)
Find data in a htrie.
Definition: htrie.h:104
A hash/rb/prefix trie abstraction.
Definition: htrie.h:80
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:443
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RPWDEBUG(fmt,...)
Definition: log.h:366
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:430
int map_afrom_cs(TALLOC_CTX *ctx, map_list_t *out, CONF_SECTION *cs, tmpl_rules_t const *lhs_rules, tmpl_rules_t const *rhs_rules, map_validate_t validate, void *uctx, unsigned int max)
Convert a config section into an attribute map.
Definition: map.c:1014
int map_to_request(request_t *request, map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert map_t to fr_pair_t (s) and add them to a request_t.
Definition: map.c:1781
talloc_free(reap)
int map_proc_register(TALLOC_CTX *ctx, void const *mod_inst, char const *name, map_proc_func_t evaluate, map_proc_instantiate_t instantiate, size_t inst_size, fr_value_box_safe_for_t literals_safe_for)
Register a map processor.
Definition: map_proc.c:131
fr_type_t
Definition: merged_model.c:80
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
Definition: merged_model.c:81
@ FR_TYPE_VALUE_BOX
A boxed value.
Definition: merged_model.c:125
@ FR_TYPE_OCTETS
Raw octets.
Definition: merged_model.c:84
unsigned int uint32_t
Definition: merged_model.c:33
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
int8_t(* fr_cmp_t)(void const *a, void const *b)
Definition: misc.h:38
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
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:283
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
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition: pair.c:2589
static const conf_parser_t config[]
Definition: base.c:183
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
static rs_t * conf
Definition: radsniff.c:53
The main red black tree structure.
Definition: rb.h:73
#define RETURN_MODULE_NOOP
Definition: rcode.h:62
#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
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition: rcode.h:42
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition: rcode.h:49
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition: rcode.h:48
static int8_t csv_cmp(void const *one, void const *two)
Definition: rlm_csv.c:164
static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_csv.c:1003
char * data[]
Definition: rlm_csv.c:75
static int csv_maps_verify(CONF_SECTION *cs, void const *mod_inst, UNUSED void *proc_inst, tmpl_t const *src, map_list_t const *maps)
Definition: rlm_csv.c:484
char const * index_field_name
Definition: rlm_csv.c:48
fr_htrie_t * trie
Definition: rlm_csv.c:62
fr_rb_node_t node
Definition: rlm_csv.c:72
int index_field
Definition: rlm_csv.c:56
fr_value_box_t * key
Definition: rlm_csv.c:74
char const ** field_names
Definition: rlm_csv.c:58
int used_fields
Definition: rlm_csv.c:55
bool allow_multiple_keys
Definition: rlm_csv.c:51
tmpl_t * key
Definition: rlm_csv.c:64
static int fieldname2offset(rlm_csv_t const *inst, char const *field_name, int *array_offset)
Definition: rlm_csv.c:366
static bool buf2entry(rlm_csv_t *inst, char *buf, char **out)
Definition: rlm_csv.c:95
static bool file2csv(CONF_SECTION *conf, rlm_csv_t *inst, int lineno, char *buffer)
Definition: rlm_csv.c:256
#define CSV_MAX_ATTRMAP
Definition: rlm_csv.c:386
static int csv_to_key(uint8_t **out, size_t *outlen, void const *data)
Definition: rlm_csv.c:179
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Definition: rlm_csv.c:515
static int csv_map_getvalue(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, void *uctx)
Definition: rlm_csv.c:840
fr_type_t * field_types
Definition: rlm_csv.c:60
int num_fields
Definition: rlm_csv.c:54
static uint32_t csv_hash(void const *data)
Definition: rlm_csv.c:172
module_rlm_t rlm_csv
Definition: rlm_csv.c:1051
char const * filename
Definition: rlm_csv.c:45
static bool insert_entry(CONF_SECTION *conf, rlm_csv_t *inst, rlm_csv_entry_t *e, int lineno)
Definition: rlm_csv.c:188
char const * delimiter
Definition: rlm_csv.c:46
bool multiple_index_fields
Definition: rlm_csv.c:52
map_list_t map
if there is an "update" section in the configuration.
Definition: rlm_csv.c:67
static bool duplicate_entry(CONF_SECTION *conf, rlm_csv_t *inst, rlm_csv_entry_t *old, char *p, int lineno)
Definition: rlm_csv.c:222
fr_rb_tree_t * tree
Definition: rlm_csv.c:61
char const * fields
Definition: rlm_csv.c:47
static int csv_map_verify(map_t *map, void *instance)
Definition: rlm_csv.c:391
rlm_csv_entry_t * next
Definition: rlm_csv.c:73
int * field_offsets
Definition: rlm_csv.c:59
static rlm_rcode_t mod_map_apply(rlm_csv_t const *inst, request_t *request, fr_value_box_t const *key, map_list_t const *maps)
Perform a search and map the result of the search to server attributes.
Definition: rlm_csv.c:898
static const conf_parser_t module_config[]
Definition: rlm_csv.c:81
bool header
Definition: rlm_csv.c:50
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
Definition: rlm_csv.c:754
fr_type_t key_data_type
Definition: rlm_csv.c:65
static unlang_action_t mod_map_proc(rlm_rcode_t *p_result, void const *mod_inst, UNUSED void *proc_inst, request_t *request, fr_value_box_list_t *key, map_list_t const *maps)
Perform a search and map the result of the search to server attributes.
Definition: rlm_csv.c:978
Definition: rlm_csv.c:71
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1302
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition: section.h:40
char const * name
Instance name e.g. user_database.
Definition: module.h:335
@ MODULE_TYPE_DYNAMIC_UNSAFE
Instances of this module cannot be created at runtime.
Definition: module.h:52
CONF_SECTION * conf
Module's instance configuration.
Definition: module.h:329
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
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:812
#define tmpl_is_attr(vpt)
Definition: tmpl.h:213
#define tmpl_aexpand_type(_ctx, _out, _type, _request, _vpt, _escape, _escape_ctx)
Expand a tmpl to a C type, allocing a new buffer to hold the string.
Definition: tmpl.h:1079
@ TMPL_TYPE_ATTR_UNRESOLVED
An attribute reference that we couldn't resolve but looked valid.
Definition: tmpl.h:189
@ TMPL_TYPE_ATTR
Reference to one or more attributes.
Definition: tmpl.h:146
@ TMPL_TYPE_DATA_UNRESOLVED
Unparsed literal string.
Definition: tmpl.h:183
#define tmpl_is_data_unresolved(vpt)
Definition: tmpl.h:222
tmpl_attr_rules_t attr
Rules/data for parsing attribute references.
Definition: tmpl.h:344
static char const * tmpl_attr_tail_unresolved(tmpl_t const *vpt)
Return the last attribute reference unresolved da.
Definition: tmpl.h:880
#define tmpl_aexpand(_ctx, _out, _request, _vpt, _escape, _escape_ctx)
Expand a tmpl to a C type, allocing a new buffer to hold the string.
Definition: tmpl.h:1070
fr_type_t tmpl_expanded_type(tmpl_t const *vpt)
Return the native data type of the expression.
Definition: tmpl_eval.c:209
static char const * tmpl_type_to_str(tmpl_type_t type)
Return a static string containing the type name.
Definition: tmpl.h:645
Optional arguments passed to vp_tmpl functions.
Definition: tmpl.h:341
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
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
CONF_ITEM * ci
Config item that the map was created from.
Definition: map.h:85
uint8_t allow_foreign
Allow arguments not found in dict_def.
Definition: tmpl.h:327
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:772
char * talloc_typed_strdup(TALLOC_CTX *ctx, char const *p)
Call talloc_strdup, setting the type on the new chunk correctly.
Definition: talloc.c:445
#define talloc_get_type_abort_const
Definition: talloc.h:282
fr_table_num_ordered_t const fr_tokens_table[]
Definition: token.c:33
@ T_OP_SUB_EQ
Definition: token.h:70
@ T_OP_EQ
Definition: token.h:83
@ T_OP_SET
Definition: token.h:84
@ T_OP_ADD_EQ
Definition: token.h:69
@ T_OP_LE
Definition: token.h:100
@ T_OP_GE
Definition: token.h:98
@ T_OP_GT
Definition: token.h:99
@ T_OP_LT
Definition: token.h:101
@ T_OP_PREPEND
Definition: token.h:85
int(* fr_trie_key_t)(uint8_t **out, size_t *outlen, void const *data)
Definition: trie.h:56
char const * fr_strerror(void)
Get the last library error.
Definition: strerror.c:554
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition: types.h:433
#define fr_type_is_null(_x)
Definition: types.h:326
#define FR_TYPE_LEAF
Definition: types.h:297
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules, bool tainted)
Definition: value.c:5315
uint32_t fr_value_box_hash(fr_value_box_t const *vb)
Hash the contents of a value box.
Definition: value.c:6129
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition: value.c:3352
int8_t fr_value_box_cmp(fr_value_box_t const *a, fr_value_box_t const *b)
Compare two values.
Definition: value.c:676
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition: value.c:3740
int fr_value_box_to_key(uint8_t **out, size_t *outlen, fr_value_box_t const *value)
Get a key from a value box.
Definition: value.c:2084
void fr_value_box_clear(fr_value_box_t *data)
Clear/free any existing value and metadata.
Definition: value.c:3723
int fr_value_box_list_concat_in_place(TALLOC_CTX *ctx, fr_value_box_t *out, fr_value_box_list_t *list, fr_type_t type, fr_value_box_list_action_t proc_action, bool flatten, size_t max_size)
Concatenate a list of value boxes.
Definition: value.c:5777
@ FR_VALUE_BOX_LIST_FREE
Definition: value.h:221
#define fr_box_strvalue_buffer(_val)
Definition: value.h:289
static fr_slen_t data
Definition: value.h:1265
int nonnull(2, 5))
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition: value.h:632
static size_t char ** out
Definition: value.h:997