From: Arran Cudbard-Bell Date: Mon, 5 Dec 2016 00:57:21 +0000 (-0500) Subject: Move client xlats to rlm_client and add map client {} X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ec5a1b408652ff35b851223c8cbd82341fd0c8e;p=thirdparty%2Ffreeradius-server.git Move client xlats to rlm_client and add map client {} This allows multivalued properties to be retrieved from clients... --- diff --git a/raddb/mods-available/client b/raddb/mods-available/client new file mode 100644 index 00000000000..73310319e34 --- /dev/null +++ b/raddb/mods-available/client @@ -0,0 +1,19 @@ +# +# Currently takes no configuration. May be used to load static file definitions +# in future... +# +# For now allows: +# +# map client [] { +# Tmp-String-0 := 'nas_type' +# Tmp-String-1 := 'shortname' +# Tmp-String-3 += 'groups' # Creates multiple attributes from custom group attribute associated +# # with the client. +# } +# +# "%{client:nas_type}" # Expands to client's nas_type (or "" if nas_type not set) +# "%{client:.nas_type}" # Expands to the specified client's nas_type (or "" if nas_type not set or +# # client does not exist). +client { + +} \ No newline at end of file diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index a9dfa09aa49..c3eedb9b97c 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -26,6 +26,7 @@ RCSID("$Id$") #include #include #include +#include #include #include @@ -381,90 +382,6 @@ static ssize_t xlat_config(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, return strlen(*out); } - -/* - * Xlat for %{client:foo} - */ -static ssize_t xlat_client(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, - UNUSED void const *mod_inst, UNUSED void const *xlat_inst, - REQUEST *request, char const *fmt) -{ - char const *value = NULL; - CONF_PAIR *cp; - - if (!request->client) { - RWDEBUG("No client associated with this request"); - return 0; - } - - cp = cf_pair_find(request->client->cs, fmt); - if (!cp || !(value = cf_pair_value(cp))) { - if (strcmp(fmt, "shortname") == 0 && request->client->shortname) { - value = request->client->shortname; - } - else if (strcmp(fmt, "nas_type") == 0 && request->client->nas_type) { - value = request->client->nas_type; - } else { - **out = '\0'; - return 0; - } - } - - strlcpy(*out, value, outlen); - - return strlen(*out); -} - -/* - * Xlat for %{getclient:.foo} - */ -static ssize_t xlat_getclient(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, - UNUSED void const *mod_inst, UNUSED void const *xlat_inst, - REQUEST *request, char const *fmt) -{ - char const *value = NULL; - char buffer[INET6_ADDRSTRLEN], *q; - char const *p = fmt; - fr_ipaddr_t ip; - CONF_PAIR *cp; - RADCLIENT *client = NULL; - - q = strrchr(p, '.'); - if (!q || (q == p) || (((size_t)(q - p)) > sizeof(buffer))) { - REDEBUG("Invalid client string"); - goto error; - } - - strlcpy(buffer, p, (q + 1) - p); - if (fr_inet_pton(&ip, buffer, -1, AF_UNSPEC, false, true) < 0) { - REDEBUG("\"%s\" is not a valid IPv4 or IPv6 address", buffer); - goto error; - } - - fmt = q + 1; - - client = client_find(NULL, &ip, IPPROTO_IP); - if (!client) { - RDEBUG("No client found with IP \"%s\"", buffer); - return 0; - } - - cp = cf_pair_find(client->cs, fmt); - if (!cp || !(value = cf_pair_value(cp))) { - if (strcmp(fmt, "shortname") == 0) { - strlcpy(*out, request->client->shortname, outlen); - return strlen(*out); - } - return 0; - } - - strlcpy(*out, value, outlen); - return strlen(*out); - -error: - return -1; -} - /* * Xlat for %{listen:foo} */ @@ -1054,8 +971,6 @@ do {\ * ...and the client and listen xlats which need to be * defined before we start parsing the config. */ - xlat_register(NULL, "client", xlat_client, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN); - xlat_register(NULL, "getclient", xlat_getclient, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN); xlat_register(NULL, "listen", xlat_listen, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN); /* diff --git a/src/main/unit_test_module.c b/src/main/unit_test_module.c index 0498d65d02d..aa90c3b1dfb 100644 --- a/src/main/unit_test_module.c +++ b/src/main/unit_test_module.c @@ -660,6 +660,20 @@ static bool do_xlats(char const *filename, FILE *fp) return true; } +/* + * Verify the result of the map. + */ +static int map_proc_verify(CONF_SECTION *cs, UNUSED void *mod_inst, UNUSED void *proc_inst, + UNUSED vp_tmpl_t const *src, UNUSED vp_map_t const *maps) +{ + if (!src) { + cf_log_err_cs(cs, "Missing source"); + + return -1; + } + + return 0; +} static rlm_rcode_t mod_map_proc(UNUSED void *mod_inst, UNUSED void *proc_inst, UNUSED REQUEST *request, UNUSED vp_tmpl_t const *src, UNUSED vp_map_t const *maps) @@ -808,7 +822,7 @@ int main(int argc, char *argv[]) goto finish; } - if (map_proc_register(NULL, "test-fail", mod_map_proc, NULL, 0) < 0) { + if (map_proc_register(NULL, "test-fail", mod_map_proc, map_proc_verify, 0) < 0) { rcode = EXIT_FAILURE; goto finish; } diff --git a/src/modules/rlm_client/README.md b/src/modules/rlm_client/README.md new file mode 100644 index 00000000000..93685a43ab2 --- /dev/null +++ b/src/modules/rlm_client/README.md @@ -0,0 +1,8 @@ +# rlm_clients +## Metadata +
+
category
datastore
+
+ +## Summary +Registers xlats and maps to access client data diff --git a/src/modules/rlm_client/all.mk b/src/modules/rlm_client/all.mk new file mode 100644 index 00000000000..c64136a40c3 --- /dev/null +++ b/src/modules/rlm_client/all.mk @@ -0,0 +1,4 @@ +TARGET := rlm_client.a +SOURCES := rlm_client.c + + diff --git a/src/modules/rlm_client/rlm_client.c b/src/modules/rlm_client/rlm_client.c new file mode 100644 index 00000000000..e97980e00c5 --- /dev/null +++ b/src/modules/rlm_client/rlm_client.c @@ -0,0 +1,263 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file rlm_client.c + * @brief Retrieve attributes from a client. + * + * @copyright 2016 Arran Cudbard-Bell + */ +RCSID("$Id$") + +#include +#include +#include +#include + +/** Client field + * + */ +typedef struct { + CONF_SECTION *cs; //!< Client's CONF_SECTION. + CONF_PAIR *cp; //!< First instance of the field in the client's CONF_SECTION. + char const *field; //!< Field name. +} client_get_vp_ctx_t; + +static int _map_proc_client_get_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, + vp_map_t const *map, void *uctx) +{ + client_get_vp_ctx_t *client = uctx; + VALUE_PAIR *head = NULL, *vp; + vp_cursor_t cursor; + fr_dict_attr_t const *da; + CONF_PAIR const *cp; + + rad_assert(ctx != NULL); + + fr_cursor_init(&cursor, &head); + + /* + * FIXME: allow multiple entries. + */ + if (map->lhs->type == TMPL_TYPE_ATTR) { + da = map->lhs->tmpl_da; + } else { + char *attr; + + if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { + RWDEBUG("Failed expanding string"); + return -1; + } + + da = fr_dict_attr_by_name(NULL, attr); + if (!da) { + RWDEBUG("No such attribute '%s'", attr); + return -1; + } + + talloc_free(attr); + } + + for (cp = client->cp; + cp; + cp = cf_pair_find_next(client->cs, cp, client->field)) { + char const *value = cf_pair_value(cp); + + MEM(vp = fr_pair_afrom_da(ctx, da)); + if (fr_pair_value_from_str(vp, value, talloc_array_length(value) - 1) < 0) { + RWDEBUG("Failed parsing value \"%pS\" for attribute %s: %s", value, + map->lhs->tmpl_da->name, fr_strerror()); + fr_pair_list_free(&head); + talloc_free(vp); + return -1; + } + + vp->op = map->op; + fr_cursor_merge(&cursor, vp); + + if (map->op != T_OP_ADD) break; /* Create multiple attribute for multiple CONF_PAIRs */ + } + + *out = head; + + return 0; +} + +/** Map multiple attributes from a client into the request + * + * @param[in] mod_inst NULL. + * @param[in] proc_inst NULL. + * @param[in] request The current request. + * @param[in] client_override If NULL, use the current client, else use the client matching + * the ip given. + * @param[in] maps Head of the map list. + * @return + * - #RLM_MODULE_NOOP no rows were returned. + * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST. + * - #RLM_MODULE_FAIL if an error occurred. + */ +static rlm_rcode_t map_proc_client(UNUSED void *mod_inst, UNUSED void *proc_inst, REQUEST *request, + vp_tmpl_t const *client_override, vp_map_t const *maps) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + vp_map_t const *map; + RADCLIENT *client; + client_get_vp_ctx_t uctx; + + if (client_override) { + fr_ipaddr_t ip; + char *client_str = NULL; + + if (tmpl_aexpand(request, &client_str, request, client_override, NULL, NULL) < 0) { + return RLM_MODULE_FAIL; + } + + if (fr_inet_pton(&ip, client_str, -1, AF_UNSPEC, false, true) < 0) { + REDEBUG("\"%s\" is not a valid IPv4 or IPv6 address", client_str); + rcode = RLM_MODULE_FAIL; + talloc_free(client_str); + goto finish; + } + + client = client_find(NULL, &ip, IPPROTO_IP); + if (!client) { + RDEBUG("No client found with IP \"%s\"", client_str); + return 0; + } + talloc_free(client_str); + } else { + client = request->client; + } + uctx.cs = client->cs; + + RINDENT(); + for (map = maps; + map != NULL; + map = map->next) { + char *field = NULL; + + if (tmpl_aexpand(request, &field, request, map->rhs, NULL, NULL) < 0) { + RDEBUG("Failed expanding RHS at %s", map->lhs->name); + rcode = RLM_MODULE_FAIL; + talloc_free(field); + break; + } + + uctx.cp = cf_pair_find(client->cs, field); + if (!uctx.cp) { + RDEBUG3("No matching client property \"%s\", skipping...", field); + goto next; /* No matching CONF_PAIR found */ + } + uctx.field = field; + + /* + * Pass the raw data to the callback, which will + * create the VP and add it to the map. + */ + if (map_to_request(request, map, _map_proc_client_get_vp, &uctx) < 0) { + rcode = RLM_MODULE_FAIL; + talloc_free(field); + break; + } + rcode = RLM_MODULE_UPDATED; + + next: + talloc_free(field); + } + REXDENT(); + +finish: + return rcode; +} + +/* + * Xlat for %{client:[.]foo} + */ +static ssize_t xlat_client(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + char const *value = NULL; + char buffer[INET6_ADDRSTRLEN], *q; + char const *p = fmt; + fr_ipaddr_t ip; + CONF_PAIR *cp; + RADCLIENT *client = NULL; + + *out = NULL; + + q = strrchr(p, '.'); + if (q) { + strlcpy(buffer, p, (q + 1) - p); + if (fr_inet_pton(&ip, buffer, -1, AF_UNSPEC, false, true) < 0) goto request_client; + + p = q + 1; + + client = client_find(NULL, &ip, IPPROTO_IP); + if (!client) { + RDEBUG("No client found with IP \"%s\"", buffer); + return 0; + } + } else { + request_client: + client = request->client; + if (!client) { + RERROR("No client associated with this request"); + + return -1; + } + } + + cp = cf_pair_find(client->cs, p); + if (!cp || !(value = cf_pair_value(cp))) { + if (strcmp(fmt, "shortname") == 0 && request->client->shortname) { + value = request->client->shortname; + } + else if (strcmp(fmt, "nas_type") == 0 && request->client->nas_type) { + value = request->client->nas_type; + } + if (!value) return 0; + } + + *out = talloc_typed_strdup(ctx, value); + return talloc_array_length(*out) - 1; +} + +/* + * Do any per-module initialization that is separate to each + * configured instance of the module. e.g. set up connections + * to external databases, read configuration files, set up + * dictionary entries, etc. + * + * If configuration information is given in the config section + * that must be referenced in later calls, store a handle to it + * in *instance otherwise put a null pointer there. + */ +static int mod_bootstrap(UNUSED CONF_SECTION *conf, void *instance) +{ + xlat_register(instance, "client", xlat_client, NULL, NULL, 0, 0); + map_proc_register(instance, "client", map_proc_client, NULL, 0); + + return 0; +} + +extern rad_module_t rlm_client; +rad_module_t rlm_client = { + .magic = RLM_MODULE_INIT, + .name = "client", + .bootstrap = mod_bootstrap, +}; diff --git a/src/tests/modules/client/all.mk b/src/tests/modules/client/all.mk new file mode 100644 index 00000000000..8f1127f1ecd --- /dev/null +++ b/src/tests/modules/client/all.mk @@ -0,0 +1,3 @@ +# +# Test the "always" module +# diff --git a/src/tests/modules/client/clients.conf b/src/tests/modules/client/clients.conf new file mode 100644 index 00000000000..69e9d3e3709 --- /dev/null +++ b/src/tests/modules/client/clients.conf @@ -0,0 +1,23 @@ +client a_test_client { + ipaddr = 127.0.0.1 + secret = 'testing123' + + group = 'a' + group = 'b' + group = 'c' + + nas_type = 'a_type' + shortname = 'a_test' +} + +client b_test_client { + ipaddr = 127.0.0.0/24 + secret = 'supersecret' + + group = 'd' + group = 'e' + group = 'f' + + nas_type = 'b_type' + shortname = 'b_test' +} \ No newline at end of file diff --git a/src/tests/modules/client/map.unlang b/src/tests/modules/client/map.unlang new file mode 100644 index 00000000000..bc7f522a1b9 --- /dev/null +++ b/src/tests/modules/client/map.unlang @@ -0,0 +1,119 @@ +map client { + Tmp-String-0 := 'nas_type' +} + +if (&Tmp-String-0 == 'a_type') { + test_pass +} +else { + test_fail +} + +map client 127.0.0.1 { + Tmp-String-0 := 'nas_type' +} + +if (&Tmp-String-0 == 'a_type') { + test_pass +} +else { + test_fail +} + +map client 127.0.0.2 { + Tmp-String-0 := 'nas_type' +} + +if (&Tmp-String-0 == 'b_type') { + test_pass +} +else { + test_fail +} + +map client 127.0.0.5 { + Tmp-String-0 := 'nas_type' +} + +if (&Tmp-String-0 == 'b_type') { + test_pass +} +else { + test_fail +} + +# +# Test multi-valued maps +# +map client { + Tmp-String-1 += 'group' +} + +if (&Tmp-String-1[0] == 'a') { + test_pass +} +else { + test_fail +} + +if (&Tmp-String-1[1] == 'b') { + test_pass +} +else { + test_fail +} + +if (&Tmp-String-1[2] == 'c') { + test_pass +} +else { + test_fail +} + +map client 127.0.0.2 { + Tmp-String-2 += 'group' +} + +if (&Tmp-String-2[0] == 'd') { + test_pass +} +else { + test_fail +} + +if (&Tmp-String-2[1] == 'e') { + test_pass +} +else { + test_fail +} + +if (&Tmp-String-2[2] == 'f') { + test_pass +} +else { + test_fail +} + +# +# Test non-existent client properties +# +map client { + Tmp-String-3 := 'non-existent-attr' + Tmp-String-4 += 'non-existing-attr2' +} + +if (&Tmp-String-3) { + test_fail +} +else { + test_pass +} + +if (&Tmp-String-4) { + test_fail +} +else { + test_pass +} + diff --git a/src/tests/modules/client/module.conf b/src/tests/modules/client/module.conf new file mode 100644 index 00000000000..08a8b28e8ae --- /dev/null +++ b/src/tests/modules/client/module.conf @@ -0,0 +1,3 @@ +client { + +} \ No newline at end of file diff --git a/src/tests/modules/unit_test_module.conf b/src/tests/modules/unit_test_module.conf index 76bf2178949..54ee52ac7db 100644 --- a/src/tests/modules/unit_test_module.conf +++ b/src/tests/modules/unit_test_module.conf @@ -22,6 +22,8 @@ modules { $INCLUDE $ENV{MODULE_TEST_DIR}/module.conf } +$-INCLUDE $ENV{MODULE_TEST_DIR}/clients.conf + server default { authorize { #