From: Arran Cudbard-Bell Date: Sat, 16 May 2015 17:37:16 +0000 (-0400) Subject: Allow certain special inst selectors to work with lists e.g. %{reply:[*]} and %{reply... X-Git-Tag: release_3_0_9~349 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d3af9600582ecf197972916cdb7b60abc9388d81;p=thirdparty%2Ffreeradius-server.git Allow certain special inst selectors to work with lists e.g. %{reply:[*]} and %{reply:[#]}. Mostly useful for tests. --- diff --git a/man/man5/unlang.5 b/man/man5/unlang.5 index b0a85cbd8dd..0b07302efea 100644 --- a/man/man5/unlang.5 +++ b/man/man5/unlang.5 @@ -690,9 +690,17 @@ e.g. If a request contains "User-Name = bob", the expansion of %{User-Name[#]} will yeild "1". .IP %{Attribute-Name[*]} -All instances of Attribute-Name, concatenated together with \\n as the +All values of Attribute-Name, concatenated together with \\n as the separator. +.IP %{List-Name:[#]} +The number of attributes in the named list. + +.IP %{List-Name:[*]} +All values of attributes in the named-list, concatenated together with \\n +as the separator. Use the %{pairs:} xlat to get a list of attributes and +values. + e.g. If a response contains "Reply-Message = 'Hello', Reply-Message = 'bob' the expnsion of "%{reply:Reply-Message[*]} will yield "Hello\\nbob" diff --git a/src/main/tmpl.c b/src/main/tmpl.c index c54d8476b53..9157f5cf783 100644 --- a/src/main/tmpl.c +++ b/src/main/tmpl.c @@ -604,13 +604,27 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, return -(p - name); } - if (*p == '\0') { + attr.tag = TAG_ANY; + attr.num = NUM_ANY; + + /* + * This may be just a bare list, but it can still + * have instance selectors and tag selectors. + */ + switch (*p) { + case '\0': type = TMPL_TYPE_LIST; + attr.num = NUM_ALL; /* Hack - Should be removed once tests are updated */ goto finish; - } - attr.tag = TAG_ANY; - attr.num = NUM_ANY; + case '[': + type = TMPL_TYPE_LIST; + attr.num = NUM_ALL; /* Hack - Should be removed once tests are updated */ + goto do_num; + + default: + break; + } attr.da = dict_attrbyname_substr(&p); if (!attr.da) { @@ -638,7 +652,7 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, ((DICT_ATTR *)&attr.unknown.da)->vendor); if (attr.da) { vpt->auto_converted = true; - goto skip_tag; + goto do_num; } if (!allow_unknown) { @@ -652,7 +666,7 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, */ attr.da = (DICT_ATTR *)&attr.unknown.da; - goto skip_tag; /* unknown attributes can't have tags */ + goto do_num; /* unknown attributes can't have tags */ } /* @@ -676,14 +690,14 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, } *q = '\0'; - goto skip_tag; + goto do_num; } /* * The string MIGHT have a tag. */ if (*p == ':') { - if (!attr.da->flags.has_tag) { + if (attr.da && !attr.da->flags.has_tag) { /* Lists don't have a da */ fr_strerror_printf("Attribute '%s' cannot have a tag", attr.da->name); return -(p - name); } @@ -698,7 +712,7 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, p = q; } -skip_tag: +do_num: if (*p == '\0') goto finish; if (*p == '[') { @@ -1553,8 +1567,6 @@ size_t tmpl_prints(char *out, size_t outlen, vp_tmpl_t const *vpt, DICT_ATTR con case TMPL_TYPE_XLAT_STRUCT: c = '"'; break; - - case TMPL_TYPE_LIST: case TMPL_TYPE_LITERAL: /* single-quoted or bare word */ /* * Hack @@ -1737,6 +1749,7 @@ size_t tmpl_prints(char *out, size_t outlen, vp_tmpl_t const *vpt, DICT_ATTR con VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp_tmpl_t const *vpt) { VALUE_PAIR **vps, *vp = NULL; + int num; VERIFY_TMPL(vpt); @@ -1760,9 +1773,6 @@ VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp * May not may not be found, but it *is* a known name. */ case TMPL_TYPE_ATTR: - { - int num; - switch (vpt->tmpl_num) { case NUM_ANY: vp = fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag); @@ -1777,7 +1787,6 @@ VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp * Get the last instance of a VALUE_PAIR. */ case NUM_LAST: - { VALUE_PAIR *last = NULL; @@ -1785,7 +1794,7 @@ VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp VERIFY_VP(vp); last = vp; } - + VERIFY_VP(last); if (!last) break; return last; } @@ -1799,6 +1808,8 @@ VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp * total number of attributes. */ case NUM_COUNT: + return fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag); + default: num = vpt->tmpl_num; while ((vp = fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag))) { @@ -1810,10 +1821,49 @@ VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp if (err) *err = -1; return NULL; - } case TMPL_TYPE_LIST: - vp = fr_cursor_init(cursor, vps); + switch (vpt->tmpl_num) { + case NUM_COUNT: + case NUM_ANY: + case NUM_ALL: + vp = fr_cursor_init(cursor, vps); + if (!vp) { + if (err) *err = -1; + return NULL; + } + VERIFY_VP(vp); + return vp; + + /* + * Get the last instance of a VALUE_PAIR. + */ + case NUM_LAST: + { + VALUE_PAIR *last = NULL; + + for (vp = fr_cursor_init(cursor, vps); + vp; + vp = fr_cursor_next(cursor)) { + VERIFY_VP(vp); + last = vp; + } + if (!last) break; + VERIFY_VP(last); + return last; + } + + default: + num = vpt->tmpl_num; + for (vp = fr_cursor_init(cursor, vps); + vp; + vp = fr_cursor_next(cursor)) { + VERIFY_VP(vp); + if (num-- <= 0) return vp; + } + break; + } + break; default: @@ -1843,10 +1893,25 @@ VALUE_PAIR *tmpl_cursor_next(vp_cursor_t *cursor, vp_tmpl_t const *vpt) * May not may not be found, but it *is* a known name. */ case TMPL_TYPE_ATTR: - if (vpt->tmpl_num != NUM_ALL) return NULL; + switch (vpt->tmpl_num) { + default: + return NULL; + + case NUM_ALL: + case NUM_COUNT: /* This cursor is being used to count matching attrs */ + break; + } return fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag); case TMPL_TYPE_LIST: + switch (vpt->tmpl_num) { + default: + return NULL; + + case NUM_ALL: + case NUM_COUNT: /* This cursor is being used to count matching attrs */ + break; + } return fr_cursor_next(cursor); default: diff --git a/src/main/xlat.c b/src/main/xlat.c index 4cc6784c48d..c36c38afcda 100644 --- a/src/main/xlat.c +++ b/src/main/xlat.c @@ -1811,11 +1811,19 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt, RADIUS_PACKET *packet = NULL; DICT_VALUE *dv; char *ret = NULL; - int err; + vp_cursor_t cursor; char quote = escape ? '"' : '\0'; - vp_cursor_t cursor; + rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST)); + + /* + * We only support count and concatenate operations on lists. + */ + if (vpt->type == TMPL_TYPE_LIST) { + vp = tmpl_cursor_init(NULL, &cursor, request, vpt); + goto do_print; + } /* * See if we're dealing with an attribute in the request @@ -1823,7 +1831,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt, * This allows users to manipulate virtual attributes as if * they were real ones. */ - vp = tmpl_cursor_init(&err, &cursor, request, vpt); + vp = tmpl_cursor_init(NULL, &cursor, request, vpt); if (vp) goto do_print; /* @@ -2014,8 +2022,9 @@ do_print: { int count = 0; - fr_cursor_first(&cursor); - while (fr_cursor_next_by_da(&cursor, vpt->tmpl_da, vpt->tmpl_tag)) count++; + for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt); + vp; + vp = tmpl_cursor_next(&cursor, vpt)) count++; return talloc_typed_asprintf(ctx, "%d", count); } diff --git a/src/tests/keywords/xlat-list b/src/tests/keywords/xlat-list new file mode 100644 index 00000000000..fcd9e8406d3 --- /dev/null +++ b/src/tests/keywords/xlat-list @@ -0,0 +1,64 @@ +# +# PRE: update +# +update control { + control !* ANY +} + +update control { + Tmp-IP-Address-0 := 192.0.2.1 + Tmp-IP-Address-0 += 192.0.2.2 +} + +if ("%{control:[#]}" != 2) { + update { + reply:Filter-Id += 'fail 0' + } +} + +debug_control + +if (("%{control:[0]}" != 192.0.2.1) || ("%{control:[1]}" != 192.0.2.2)) { + update { + reply:Filter-Id += 'fail 1' + } +} + +if (("%{control:[n]}" != 192.0.2.2)) { + update { + reply:Filter-Id += 'fail 1a' + } +} + +if ("%{control:[*]}" != '192.0.2.1,192.0.2.2') { + update { + reply:Filter-Id += 'fail 2' + } +} + +# Try calling these xlats in mapping too, they may get optimised to VPTs which is a +# different code path. +update request { + Tmp-IP-Address-1 += "%{control:[1]}" + Tmp-IP-Address-1 += "%{control:[0]}" + Tmp-String-0 = "%{control:[*]}" + Tmp-Integer-0 = "%{control:[#]}" +} + +if (Tmp-String-0 != '192.0.2.1,192.0.2.2') { + update { + reply:Filter-Id += 'fail 3' + } +} + +if (Tmp-Integer-0 != 2) { + update { + reply:Filter-Id += 'fail 4' + } +} + +# Boilerplate junk +update { + control:Cleartext-Password := 'hello' + reply:Filter-Id := 'filter' +}