]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Move client xlats to rlm_client and add map client {}
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 5 Dec 2016 00:57:21 +0000 (19:57 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 5 Dec 2016 00:57:41 +0000 (19:57 -0500)
This allows multivalued properties to be retrieved from clients...

raddb/mods-available/client [new file with mode: 0644]
src/main/mainconfig.c
src/main/unit_test_module.c
src/modules/rlm_client/README.md [new file with mode: 0644]
src/modules/rlm_client/all.mk [new file with mode: 0644]
src/modules/rlm_client/rlm_client.c [new file with mode: 0644]
src/tests/modules/client/all.mk [new file with mode: 0644]
src/tests/modules/client/clients.conf [new file with mode: 0644]
src/tests/modules/client/map.unlang [new file with mode: 0644]
src/tests/modules/client/module.conf [new file with mode: 0644]
src/tests/modules/unit_test_module.conf

diff --git a/raddb/mods-available/client b/raddb/mods-available/client
new file mode 100644 (file)
index 0000000..7331031
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#  Currently takes no configuration.  May be used to load static file definitions
+#  in future...
+#
+#  For now allows:
+#
+#  map client [<ipaddr>] {
+#      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:<ipaddr>.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
index a9dfa09aa491abf17172129f2f24afea0b3ee59f..c3eedb9b97cdebafd8201fb996e2327ed2f44827 100644 (file)
@@ -26,6 +26,7 @@ RCSID("$Id$")
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/modules.h>
 #include <freeradius-devel/modpriv.h>
+#include <freeradius-devel/map_proc.h>
 #include <freeradius-devel/rad_assert.h>
 
 #include <sys/stat.h>
@@ -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:<ipaddr>.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);
 
        /*
index 0498d65d02d89e93d5eae4c6ec1acda364f4571d..aa90c3b1dfb8d347a790b7ddac4744ea4b4fbf13 100644 (file)
@@ -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 (file)
index 0000000..93685a4
--- /dev/null
@@ -0,0 +1,8 @@
+# rlm_clients
+## Metadata
+<dl>
+  <dt>category</dt><dd>datastore</dd>
+</dl>
+
+## 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 (file)
index 0000000..c64136a
--- /dev/null
@@ -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 (file)
index 0000000..e97980e
--- /dev/null
@@ -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 <a.cudbardb@freeradius.org>
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/map_proc.h>
+#include <freeradius-devel/rad_assert.h>
+
+/** 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:[<ipaddr>.]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 (file)
index 0000000..8f1127f
--- /dev/null
@@ -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 (file)
index 0000000..69e9d3e
--- /dev/null
@@ -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 (file)
index 0000000..bc7f522
--- /dev/null
@@ -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 (file)
index 0000000..08a8b28
--- /dev/null
@@ -0,0 +1,3 @@
+client {
+
+}
\ No newline at end of file
index 76bf2178949c5c557eb374d53a9f78b9412ddc2c..54ee52ac7db12e57674425e16b0ab25eb85e5702 100644 (file)
@@ -22,6 +22,8 @@ modules {
        $INCLUDE $ENV{MODULE_TEST_DIR}/module.conf
 }
 
+$-INCLUDE $ENV{MODULE_TEST_DIR}/clients.conf
+
 server default {
        authorize {
                #