Skip '&' on attribute maps. Closes #423
[freeradius.git] / src / main / map.c
1 /*
2  *   This program 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
5  *   (at 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$
19  *
20  * @brief map / template functions
21  * @file main/map.c
22  *
23  * @ingroup AVP
24  *
25  * @copyright 2013 The FreeRADIUS server project
26  * @copyright 2013  Alan DeKok <aland@freeradius.org>
27  */
28
29 RCSID("$Id$")
30
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/rad_assert.h>
33
34 #include <ctype.h>
35
36 /** Release memory allocated to value pair template.
37  *
38  * @param[in,out] tmpl to free.
39  */
40 void radius_tmplfree(value_pair_tmpl_t **tmpl)
41 {
42         if (*tmpl == NULL) return;
43
44         dict_attr_free(&((*tmpl)->da));
45
46         talloc_free(*tmpl);
47
48         *tmpl = NULL;
49 }
50
51 /** Parse qualifiers to convert attrname into a value_pair_tmpl_t.
52  *
53  * VPTs are used in various places where we need to pre-parse configuration
54  * sections into attribute mappings.
55  *
56  * Note: name field is just a copy of the input pointer, if you know that
57  * string might be freed before you're done with the vpt use radius_attr2tmpl
58  * instead.
59  *
60  * @param[in] name attribute name including qualifiers.
61  * @param[out] vpt to modify.
62  * @param[in] request_def The default request to insert unqualified
63  *      attributes into.
64  * @param[in] list_def The default list to insert unqualified attributes into.
65  * @return -1 on error or 0 on success.
66  */
67 int radius_parse_attr(char const *name, value_pair_tmpl_t *vpt,
68                       request_refs_t request_def,
69                       pair_lists_t list_def)
70 {
71         DICT_ATTR const *da;
72         char const *p;
73         size_t len;
74
75         memset(vpt, 0, sizeof(*vpt));
76         vpt->name = name;
77         p = name;
78
79         vpt->request = radius_request_name(&p, request_def);
80         len = p - name;
81         if (vpt->request == REQUEST_UNKNOWN) {
82                 ERROR("Invalid request qualifier \"%.*s\"", (int) len, name);
83                 return -1;
84         }
85         name += len;
86
87         vpt->list = radius_list_name(&p, list_def);
88         if (vpt->list == PAIR_LIST_UNKNOWN) {
89                 len = p - name;
90                 ERROR("Invalid list qualifier \"%.*s\"", (int) len, name);
91                 return -1;
92         }
93
94         if (*p == '\0') {
95                 vpt->type = VPT_TYPE_LIST;
96                 return 0;
97         }
98
99         da = dict_attrbyname(p);
100         if (!da) {
101                 da = dict_attrunknownbyname(p, false);
102                 if (!da) {
103                         ERROR("Unknown attribute \"%s\"", p);
104                         return -1;
105                 }
106         }
107         vpt->da = da;
108
109         vpt->type = VPT_TYPE_ATTR;
110         return 0;
111 }
112
113 /** Parse qualifiers to convert attrname into a value_pair_tmpl_t.
114  *
115  * VPTs are used in various places where we need to pre-parse configuration
116  * sections into attribute mappings.
117  *
118  * @param[in] ctx for talloc
119  * @param[in] name attribute name including qualifiers.
120  * @param[in] request_def The default request to insert unqualified
121  *      attributes into.
122  * @param[in] list_def The default list to insert unqualified attributes into.
123  * @return pointer to a value_pair_tmpl_t struct (must be freed with
124  *      radius_tmplfree) or NULL on error.
125  */
126 value_pair_tmpl_t *radius_attr2tmpl(TALLOC_CTX *ctx, char const *name,
127                                     request_refs_t request_def,
128                                     pair_lists_t list_def)
129 {
130         value_pair_tmpl_t *vpt;
131         char const *copy;
132
133         vpt = talloc(ctx, value_pair_tmpl_t); /* parse_attr zeroes it */
134         copy = talloc_strdup(vpt, name);
135
136         if (radius_parse_attr(copy, vpt, request_def, list_def) < 0) {
137                 radius_tmplfree(&vpt);
138                 return NULL;
139         }
140
141         return vpt;
142 }
143
144 /** Convert module specific attribute id to value_pair_tmpl_t.
145  *
146  * @param[in] ctx for talloc
147  * @param[in] name string to convert.
148  * @param[in] type Type of quoting around value.
149  * @return pointer to new VPT.
150  */
151 value_pair_tmpl_t *radius_str2tmpl(TALLOC_CTX *ctx, char const *name, FR_TOKEN type)
152 {
153         value_pair_tmpl_t *vpt;
154
155         vpt = talloc_zero(ctx, value_pair_tmpl_t);
156         vpt->name = talloc_strdup(vpt, name);
157
158         switch (type) {
159         case T_BARE_WORD:
160                 if (*name == '&') name++;
161
162                 if (!isdigit((int) *name)) {
163                         request_refs_t ref;
164                         pair_lists_t list;
165                         char const *p = name;
166
167                         ref = radius_request_name(&p, REQUEST_CURRENT);
168                         if (ref == REQUEST_UNKNOWN) goto literal;
169
170                         list = radius_list_name(&p, PAIR_LIST_REQUEST);
171                         if (list == PAIR_LIST_UNKNOWN) goto literal;
172
173                         if ((p != name) && !*p) {
174                                 vpt->type = VPT_TYPE_LIST;
175
176                         } else {
177                                 DICT_ATTR const *da;
178                                 da = dict_attrbyname(p);
179                                 if (!da) {
180                                         vpt->type = VPT_TYPE_LITERAL;
181                                         break;
182                                 }
183                                 vpt->da = da;
184                                 vpt->type = VPT_TYPE_ATTR;
185                         }
186
187                         vpt->request = ref;
188                         vpt->list = list;
189                         break;
190                 }
191                 /* FALL-THROUGH */
192
193         case T_SINGLE_QUOTED_STRING:
194         literal:
195                 vpt->type = VPT_TYPE_LITERAL;
196                 break;
197         case T_DOUBLE_QUOTED_STRING:
198                 vpt->type = VPT_TYPE_XLAT;
199                 break;
200         case T_BACK_QUOTED_STRING:
201                 vpt->type = VPT_TYPE_EXEC;
202                 break;
203         case T_OP_REG_EQ: /* hack */
204                 vpt->type = VPT_TYPE_REGEX;
205                 break;
206         default:
207                 rad_assert(0);
208                 return NULL;
209         }
210
211         return vpt;
212 }
213
214
215 /** Convert strings to value_pair_map_e
216  *
217  * Treatment of operands depends on quotation, barewords are treated
218  * as attribute references, double quoted values are treated as
219  * expandable strings, single quoted values are treated as literal
220  * strings.
221  *
222  * Return must be freed with talloc_free
223  *
224  * @param[in] ctx for talloc
225  * @param[in] lhs of the operation
226  * @param[in] lhs_type type of the LHS string
227  * @param[in] op the operation to perform
228  * @param[in] rhs of the operation
229  * @param[in] rhs_type type of the RHS string
230  * @param[in] dst_request_def The default request to insert unqualified
231  *      attributes into.
232  * @param[in] dst_list_def The default list to insert unqualified attributes
233  *      into.
234  * @param[in] src_request_def The default request to resolve attribute
235  *      references in.
236  * @param[in] src_list_def The default list to resolve unqualified attributes
237  *      in.
238  * @return value_pair_map_t if successful or NULL on error.
239  */
240 value_pair_map_t *radius_str2map(TALLOC_CTX *ctx, char const *lhs, FR_TOKEN lhs_type,
241                                  FR_TOKEN op, char const *rhs, FR_TOKEN rhs_type,
242                                  request_refs_t dst_request_def,
243                                  pair_lists_t dst_list_def,
244                                  request_refs_t src_request_def,
245                                  pair_lists_t src_list_def)
246 {
247         value_pair_map_t *map;
248
249         map = talloc_zero(ctx, value_pair_map_t);
250
251         if ((lhs_type == T_BARE_WORD) && (*lhs == '&')) {
252                 map->dst = radius_attr2tmpl(map, lhs + 1, dst_request_def, dst_list_def);
253         } else {
254                 map->dst = radius_str2tmpl(map, lhs, lhs_type);
255         }
256
257         if (!map->dst) {
258         error:
259                 talloc_free(map);
260                 return NULL;
261         }
262
263         map->op = op;
264
265         /*
266          *      Ignore the RHS if it's a true / false comparison.
267          */
268         if ((map->op == T_OP_CMP_TRUE) || (map->op == T_OP_CMP_FALSE)) {
269                 return map;
270         }
271
272         if ((rhs_type == T_BARE_WORD) && (*rhs == '&')) {
273                 map->src = radius_attr2tmpl(map, rhs + 1, src_request_def, src_list_def);
274         } else {
275                 map->src = radius_str2tmpl(map, rhs, rhs_type);
276         }
277
278         if (!map->dst) goto error;
279
280         return map;
281 }
282
283
284 /** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t.
285  *
286  * Treats the left operand as an attribute reference
287  * @verbatim<request>.<list>.<attribute>@endverbatim
288  *
289  * Treatment of left operand depends on quotation, barewords are treated as
290  * attribute references, double quoted values are treated as expandable strings,
291  * single quoted values are treated as literal strings.
292  *
293  * Return must be freed with talloc_free
294  *
295  * @param[in] ctx for talloc
296  * @param[in] cp to convert to map.
297  * @param[in] dst_request_def The default request to insert unqualified
298  *      attributes into.
299  * @param[in] dst_list_def The default list to insert unqualified attributes
300  *      into.
301  * @param[in] src_request_def The default request to resolve attribute
302  *      references in.
303  * @param[in] src_list_def The default list to resolve unqualified attributes
304  *      in.
305  * @return value_pair_map_t if successful or NULL on error.
306  */
307 value_pair_map_t *radius_cp2map(TALLOC_CTX *ctx, CONF_PAIR *cp,
308                                 request_refs_t dst_request_def,
309                                 pair_lists_t dst_list_def,
310                                 request_refs_t src_request_def,
311                                 pair_lists_t src_list_def)
312 {
313         value_pair_map_t *map;
314         char const *attr;
315         char const *value;
316         FR_TOKEN type;
317         CONF_ITEM *ci = cf_pairtoitem(cp);
318
319         if (!cp) return NULL;
320
321         map = talloc_zero(ctx, value_pair_map_t);
322
323         attr = cf_pair_attr(cp);
324         value = cf_pair_value(cp);
325         if (!value) {
326                 cf_log_err(ci, "Missing attribute value");
327                 goto error;
328         }
329
330         map->dst = radius_attr2tmpl(map, attr, dst_request_def, dst_list_def);
331         if (!map->dst) {
332                 cf_log_err(ci, "Syntax error in attribute definition");
333                 goto error;
334         }
335
336         /*
337          *      Bare words always mean attribute references.
338          */
339         type = cf_pair_value_type(cp);
340         if (type == T_BARE_WORD) {
341                 if (*value == '&') {
342                         map->src = radius_attr2tmpl(map, value + 1, src_request_def, src_list_def);
343
344                 } else {
345                         if (!isdigit((int) *value) &&
346                             ((strchr(value, ':') != NULL) ||
347                              (dict_attrbyname(value) != NULL))) {
348                                 map->src = radius_attr2tmpl(map, value, src_request_def, src_list_def);
349                         }
350                         if (map->src) {
351                                 WDEBUG("%s[%d]: Please add '&' for attribute reference '%s = &%s'",
352                                        cf_pair_filename(cp), cf_pair_lineno(cp),
353                                        attr, value);
354                         } else {
355                                 map->src = radius_str2tmpl(map, value, type);
356                         }
357                 }
358         } else {
359                 map->src = radius_str2tmpl(map, value, type);
360         }
361
362         if (!map->src) {
363                 goto error;
364         }
365
366         map->op = cf_pair_operator(cp);
367         map->ci = ci;
368
369         /*
370          *      Lots of sanity checks for insane people...
371          */
372
373         /*
374          *      We don't support implicit type conversion,
375          *      except for "octets"
376          */
377         if (map->dst->da && map->src->da &&
378             (map->src->da->type != map->dst->da->type) &&
379             (map->src->da->type != PW_TYPE_OCTETS) &&
380             (map->dst->da->type != PW_TYPE_OCTETS)) {
381                 cf_log_err(ci, "Attribute type mismatch");
382                 goto error;
383         }
384
385         /*
386          *      What exactly where you expecting to happen here?
387          */
388         if ((map->dst->type == VPT_TYPE_ATTR) &&
389             (map->src->type == VPT_TYPE_LIST)) {
390                 cf_log_err(ci, "Can't copy list into an attribute");
391                 goto error;
392         }
393
394         /*
395          *      Can't copy an xlat expansion or literal into a list,
396          *      we don't know what type of attribute we'd need
397          *      to create
398          */
399         if ((map->dst->type == VPT_TYPE_LIST) &&
400             ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
401                 cf_log_err(ci, "Can't copy value into list (we don't know which attribute to create)");
402                 goto error;
403         }
404
405         /*
406          *      Depending on the attribute type, some operators are
407          *      disallowed.
408          */
409         if (map->dst->type == VPT_TYPE_ATTR) {
410                 if ((map->op != T_OP_EQ) &&
411                     (map->op != T_OP_CMP_EQ) &&
412                     (map->op != T_OP_ADD) &&
413                     (map->op != T_OP_SUB) &&
414                     (map->op != T_OP_LE) &&
415                     (map->op != T_OP_GE) &&
416                     (map->op != T_OP_CMP_FALSE) &&
417                     (map->op != T_OP_SET)) {
418                         cf_log_err(ci, "Invalid operator for attribute");
419                         goto error;
420                 }
421         }
422
423         switch (map->src->type) {
424                 /*
425                  *      Only += and -= operators are supported for list copy.
426                  */
427                 case VPT_TYPE_LIST:
428                         switch (map->op) {
429                         case T_OP_SUB:
430                         case T_OP_ADD:
431                                 break;
432
433                         default:
434                                 cf_log_err(ci, "Operator \"%s\" not allowed "
435                                            "for list copy",
436                                            fr_int2str(fr_tokens, map->op, "<INVALID>"));
437                                 goto error;
438                         }
439                 break;
440
441                 default:
442                         break;
443         }
444
445         return map;
446
447 error:
448         talloc_free(map);
449         return NULL;
450 }
451
452 /** Convert an 'update' config section into an attribute map.
453  *
454  * Uses 'name2' of section to set default request and lists.
455  *
456  * @param[in] cs the update section
457  * @param[out] head Where to store the head of the map.
458  * @param[in] dst_list_def The default destination list, usually dictated by
459  *      the section the module is being called in.
460  * @param[in] src_list_def The default source list, usually dictated by the
461  *      section the module is being called in.
462  * @param[in] max number of mappings to process.
463  * @return -1 on error, else 0.
464  */
465 int radius_attrmap(CONF_SECTION *cs, value_pair_map_t **head,
466                    pair_lists_t dst_list_def, pair_lists_t src_list_def,
467                    unsigned int max)
468 {
469         char const *cs_list, *p;
470
471         request_refs_t request_def = REQUEST_CURRENT;
472
473         CONF_ITEM *ci;
474         CONF_PAIR *cp;
475
476         unsigned int total = 0;
477         value_pair_map_t **tail, *map;
478         TALLOC_CTX *ctx;
479
480         *head = NULL;
481         tail = head;
482
483         if (!cs) return 0;
484
485         /*
486          *      The first map has cs as the parent.
487          *      The rest have the previous map as the parent.
488          */
489         ctx = cs;
490
491         ci = cf_sectiontoitem(cs);
492
493         cs_list = p = cf_section_name2(cs);
494         if (cs_list) {
495                 request_def = radius_request_name(&p, REQUEST_UNKNOWN);
496                 if (request_def == REQUEST_UNKNOWN) {
497                         cf_log_err(ci, "Default request specified "
498                                    "in mapping section is invalid");
499                         return -1;
500                 }
501
502                 dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN);
503                 if (dst_list_def == PAIR_LIST_UNKNOWN) {
504                         cf_log_err(ci, "Default list \"%s\" specified "
505                                    "in mapping section is invalid", p);
506                         return -1;
507                 }
508         }
509
510         for (ci = cf_item_find_next(cs, NULL);
511              ci != NULL;
512              ci = cf_item_find_next(cs, ci)) {
513                 if (total++ == max) {
514                         cf_log_err(ci, "Map size exceeded");
515                         goto error;
516                 }
517
518                 if (!cf_item_is_pair(ci)) {
519                         cf_log_err(ci, "Entry is not in \"attribute = "
520                                        "value\" format");
521                         goto error;
522                 }
523
524                 cp = cf_itemtopair(ci);
525                 map = radius_cp2map(ctx, cp, request_def, dst_list_def,
526                                     REQUEST_CURRENT, src_list_def);
527                 if (!map) {
528                         goto error;
529                 }
530
531                 ctx = *tail = map;
532                 tail = &(map->next);
533         }
534
535         return 0;
536 error:
537         talloc_free(*head);
538         return -1;
539 }
540
541
542 /**  Print a template to a string
543  *
544  * @param[out] buffer for the output string
545  * @param[in] bufsize of the buffer
546  * @param[in] vpt to print
547  * @return the size of the string printed
548  */
549 size_t radius_tmpl2str(char *buffer, size_t bufsize, value_pair_tmpl_t const *vpt)
550 {
551         char c;
552         char const *p;
553         char *q = buffer;
554         char *end;
555
556         switch (vpt->type) {
557         default:
558                 return 0;
559
560         case VPT_TYPE_REGEX:
561                 c = '/';
562                 break;
563
564         case VPT_TYPE_XLAT:
565                 c = '"';
566                 break;
567
568         case VPT_TYPE_LITERAL:  /* single-quoted or bare word */
569                 /*
570                  *      Hack
571                  */
572                 for (p = vpt->name; *p != '\0'; p++) {
573                         if (*p == ' ') break;
574                         if (*p == '\'') break;
575                         if (!dict_attr_allowed_chars[(int) *p]) break;
576                 }
577
578                 if (!*p) {
579                         strlcpy(buffer, vpt->name, bufsize);
580                         return strlen(buffer);
581                 }
582
583                 c = '\'';
584                 break;
585
586         case VPT_TYPE_EXEC:
587                 c = '`';
588                 break;
589
590         case VPT_TYPE_ATTR:
591                 buffer[0] = '&';
592                 if (vpt->request == REQUEST_CURRENT) {
593                         if (vpt->list == PAIR_LIST_REQUEST) {
594                                 strlcpy(buffer + 1, vpt->da->name, bufsize - 1);
595                         } else {
596                                 snprintf(buffer + 1, bufsize - 1, "%s:%s",
597                                          fr_int2str(pair_lists, vpt->list, ""),
598                                          vpt->da->name);
599                         }
600
601                 } else {
602                         snprintf(buffer + 1, bufsize - 1, "%s.%s:%s",
603                                  fr_int2str(request_refs, vpt->request, ""),
604                                  fr_int2str(pair_lists, vpt->list, ""),
605                                  vpt->da->name);
606                 }
607                 return strlen(buffer);
608
609         case VPT_TYPE_DATA:
610                 {
611                         VALUE_PAIR *vp;
612                         TALLOC_CTX *ctx;
613
614                         memcpy(&ctx, &vpt, sizeof(ctx)); /* hack */
615
616                         vp = pairalloc(ctx, vpt->da);
617                         memcpy(&vp->data, vpt->vpd, sizeof(vp->data));
618                         vp->length = vpt->length;
619
620                         q = vp_aprint(vp, vp);
621
622                         if ((vpt->da->type != PW_TYPE_STRING) &&
623                             (vpt->da->type != PW_TYPE_DATE)) {
624                                 strlcpy(buffer, q, bufsize);
625                         } else {
626                                 /*
627                                  *      FIXME: properly escape the string...
628                                  */
629                                 snprintf(buffer, bufsize, "\"%s\"", q);
630                         }
631
632                         talloc_free(q);
633                         pairfree(&vp);
634                         return strlen(buffer);
635                 }
636         }
637
638         if (bufsize <= 3) {
639         no_room:
640                 *buffer = '\0';
641                 return 0;
642         }
643
644         p = vpt->name;
645         *(q++) = c;
646         end = buffer + bufsize - 3; /* quotes + EOS */
647
648         while (*p && (q < end)) {
649                 if (*p == c) {
650                         if ((q - end) < 4) goto no_room; /* escape, char, quote, EOS */
651                         *(q++) = '\\';
652                         *(q++) = *(p++);
653                         continue;
654                 }
655
656                 switch (*p) {
657                 case '\\':
658                         if ((q - end) < 4) goto no_room;
659                         *(q++) = '\\';
660                         *(q++) = *(p++);
661                         break;
662
663                 case '\r':
664                         if ((q - end) < 4) goto no_room;
665                         *(q++) = '\\';
666                         *(q++) = 'r';
667                         p++;
668                         break;
669
670                 case '\n':
671                         if ((q - end) < 4) goto no_room;
672                         *(q++) = '\\';
673                         *(q++) = 'r';
674                         p++;
675                         break;
676
677                 case '\t':
678                         if ((q - end) < 4) goto no_room;
679                         *(q++) = '\\';
680                         *(q++) = 't';
681                         p++;
682                         break;
683
684                 default:
685                         *(q++) = *(p++);
686                         break;
687                 }
688         }
689
690         *(q++) = c;
691         *q = '\0';
692
693         return q - buffer;
694 }
695
696
697 /**  Print a map to a string
698  *
699  * @param[out] buffer for the output string
700  * @param[in] bufsize of the buffer
701  * @param[in] map to print
702  * @return the size of the string printed
703  */
704 size_t radius_map2str(char *buffer, size_t bufsize, value_pair_map_t const *map)
705 {
706         size_t len;
707         char *p = buffer;
708         char *end = buffer + bufsize;
709
710         len = radius_tmpl2str(buffer, bufsize, map->dst);
711         p += len;
712
713         *(p++) = ' ';
714         strlcpy(p, fr_token_name(map->op), end - p);
715         p += strlen(p);
716         *(p++) = ' ';
717
718         /*
719          *      The RHS doesn't matter for many operators
720          */
721         if ((map->op == T_OP_CMP_TRUE) ||
722             (map->op == T_OP_CMP_FALSE)) {
723                 strlcpy(p, "ANY", (end - p));
724                 p += strlen(p);
725                 return p - buffer;
726         }
727
728         rad_assert(map->src != NULL);
729
730         if ((map->dst->type == VPT_TYPE_ATTR) &&
731             (map->dst->da->type == PW_TYPE_STRING) &&
732             (map->src->type == VPT_TYPE_LITERAL)) {
733                 *(p++) = '\'';
734                 len = radius_tmpl2str(p, end - p, map->src);
735                 p += len;
736                 *(p++) = '\'';
737                 *p = '\0';
738         } else {
739                 len = radius_tmpl2str(p, end - p, map->src);
740                 p += len;
741         }
742
743         return p - buffer;
744 }