]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add ns_plugin_expandpath()
authorMichał Kępień <michal@isc.org>
Tue, 12 Feb 2019 14:59:54 +0000 (15:59 +0100)
committerEvan Hunt <each@isc.org>
Wed, 6 Mar 2019 00:52:49 +0000 (16:52 -0800)
Implement a helper function which, given an input string:

  - copies it verbatim if it contains at least one path separator,
  - prepends the named plugin installation directory to it otherwise.

This function will allow configuration parsing code to conveniently
determine the full path to a plugin module given either a path or a
filename.

While other, simpler ways exist for making sure filenames passed to
dlopen() cause the latter to look for shared objects in a specific
directory, they are very platform-specific.  Using full paths is thus
likely the most portable and reliable solution.

Also added unit tests for ns_plugin_expandpath() to ensure it behaves
as expected for absolute paths, relative paths, and filenames, for
various target buffer sizes.

(Note: plugins share a directory with named on Windows; there is no
default plugin path. Therefore the source path is copied to the
destination path with no modification.)

(cherry picked from commit d181c28c60d245219d5418c5485042500c5432d4)

lib/ns/Makefile.in
lib/ns/hooks.c
lib/ns/include/ns/hooks.h
lib/ns/tests/Kyuafile
lib/ns/tests/Makefile.in
lib/ns/tests/plugin_test.c [new file with mode: 0644]
lib/ns/win32/libns.def
util/copyrights

index e785969b512481ab50c03fdb1d7f50233fedf46e..96228b3500e37b4c5628f7cff936c43469e86e73 100644 (file)
@@ -28,7 +28,7 @@ CINCLUDES =   -I. -I${top_srcdir}/lib/ns -Iinclude \
                ${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
                @OPENSSL_INCLUDES@ @DST_GSSAPI_INC@
 
-CDEFINES =
+CDEFINES =     -DNAMED_PLUGINDIR=\"${plugindir}\"
 
 CWARNINGS =
 
index 83c390de3a973e9aec4fea3af398432c09d54cb4..327ae6909e099246c280a31e64b9b08cb65e54de 100644 (file)
@@ -13,6 +13,8 @@
 
 #include <config.h>
 
+#include <errno.h>
+#include <stdio.h>
 #include <string.h>
 
 #if HAVE_DLFCN_H
 #include <windows.h>
 #endif
 
+#include <isc/errno.h>
 #include <isc/list.h>
 #include <isc/log.h>
 #include <isc/mem.h>
 #include <isc/mutex.h>
+#include <isc/print.h>
 #include <isc/result.h>
 #include <isc/platform.h>
 #include <isc/util.h>
@@ -58,6 +62,41 @@ struct ns_plugin {
 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT];
 LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &default_hooktable;
 
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) {
+       int result;
+
+#ifndef WIN32
+       /*
+        * On Unix systems, differentiate between paths and filenames.
+        */
+       if (strchr(src, '/') != NULL) {
+               /*
+                * 'src' is an absolute or relative path.  Copy it verbatim.
+                */
+               result = snprintf(dst, dstsize, "%s", src);
+       } else {
+               /*
+                * 'src' is a filename.  Prepend default plugin directory path.
+                */
+               result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src);
+       }
+#else
+       /*
+        * On Windows, always copy 'src' do 'dst'.
+        */
+       result = snprintf(dst, dstsize, "%s", src);
+#endif
+
+       if (result < 0) {
+               return (isc_errno_toresult(errno));
+       } else if ((size_t)result >= dstsize) {
+               return (ISC_R_NOSPACE);
+       } else {
+               return (ISC_R_SUCCESS);
+       }
+}
+
 #if HAVE_DLFCN_H && HAVE_DLOPEN
 static isc_result_t
 load_symbol(void *handle, const char *modpath,
@@ -253,7 +292,7 @@ load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
        CHECK(load_symbol(handle, modpath, "plugin_version",
                          (void **)&version_func));
 
-       version = version_func(NULL);
+       version = version_func();
        if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) ||
            version > NS_PLUGIN_VERSION)
        {
index d2d310183ce88abe9a8dc6152530b272832c97f9..5ad1074872cd752ae36b03cdff1f3e0f15ee688e 100644 (file)
@@ -315,6 +315,30 @@ ns_plugin_destroy_t plugin_destroy;
 ns_plugin_register_t plugin_register;
 ns_plugin_version_t plugin_version;
 
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
+/*%<
+ * Prepare the plugin location to be passed to dlopen() based on the plugin
+ * path or filename found in the configuration file ('src').  Store the result
+ * in 'dst', which is 'dstsize' bytes large.
+ *
+ * On Unix systems, two classes of 'src' are recognized:
+ *
+ *   - If 'src' is an absolute or relative path, it will be copied to 'dst'
+ *     verbatim.
+ *
+ *   - If 'src' is a filename (i.e. does not contain a path separator), the
+ *     path to the directory into which named plugins are installed will be
+ *     prepended to it and the result will be stored in 'dst'.
+ *
+ * On Windows, 'src' is always copied to 'dst' verbatim.
+ *
+ * Returns:
+ *\li  #ISC_R_SUCCESS  Success
+ *\li  #ISC_R_NOSPACE  'dst' is not large enough to hold the output string
+ *\li  Other result    snprintf() returned a negative value
+ */
+
 isc_result_t
 ns_plugin_register(const char *modpath, const char *parameters,
                   const void *cfg, const char *cfg_file,
index ca15d6fb8424a42f0d8d9008309cf8da7f25f6e6..adf0b88ca74daa5dbcd8d605baaffa2e1db8d23b 100644 (file)
@@ -3,4 +3,5 @@ test_suite('bind9')
 
 tap_test_program{name='listenlist_test'}
 tap_test_program{name='notify_test'}
+tap_test_program{name='plugin_test'}
 tap_test_program{name='query_test'}
index 345f900becb8ac58dd269aa6b959093aa062e38f..e6d3049ede8341cd007f54b0b520cbf6d49f45ec 100644 (file)
@@ -17,7 +17,7 @@ VERSION=@BIND9_VERSION@
 
 CINCLUDES =    -I. -Iinclude ${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
                @OPENSSL_INCLUDES@ @CMOCKA_CFLAGS@
-CDEFINES =     -DTESTS="\"${top_builddir}/lib/ns/tests/\""
+CDEFINES =     -DTESTS="\"${top_builddir}/lib/ns/tests/\"" -DNAMED_PLUGINDIR=\"${plugindir}\"
 
 ISCLIBS =      ../../isc/libisc.@A@ @OPENSSL_LIBS@
 ISCDEPLIBS =   ../../isc/libisc.@A@
@@ -33,12 +33,14 @@ OBJS =              nstest.@O@
 SRCS =         nstest.c \
                listenlist_test.c \
                notify_test.c \
+               plugin_test.c \
                query_test.c
 
 SUBDIRS =
 TARGETS =      listenlist_test@EXEEXT@ \
                notify_test@EXEEXT@ \
-               query_test
+               plugin_test@EXEEXT@ \
+               query_test@EXEEXT@
 
 @BIND9_MAKE_RULES@
 
@@ -52,6 +54,11 @@ notify_test@EXEEXT@: notify_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNS
                ${LDFLAGS} -o $@ notify_test.@O@ nstest.@O@ \
                ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
 
+plugin_test@EXEEXT@: plugin_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+       ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+               ${LDFLAGS} -o $@ plugin_test.@O@ nstest.@O@ \
+               ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
 query_test@EXEEXT@: query_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
        ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
                ${LDFLAGS} -o $@ query_test.@O@ nstest.@O@ \
diff --git a/lib/ns/tests/plugin_test.c b/lib/ns/tests/plugin_test.c
new file mode 100644 (file)
index 0000000..04ce737
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <config.h>
+
+#if HAVE_CMOCKA
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <ns/hooks.h>
+
+#include "nstest.h"
+
+static int
+_setup(void **state) {
+       isc_result_t result;
+
+       UNUSED(state);
+
+       result = ns_test_begin(NULL, false);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       return (0);
+}
+
+static int
+_teardown(void **state) {
+       if (*state != NULL) {
+               isc_mem_free(mctx, *state);
+       }
+
+       ns_test_end();
+
+       return (0);
+}
+
+/*%
+ * Structure containing parameters for run_full_path_test().
+ */
+typedef struct {
+       const ns_test_id_t id;  /* libns test identifier */
+       const char *input;      /* source string - plugin name or path */
+       size_t output_size;     /* size of target char array to allocate */
+       isc_result_t result;    /* expected return value */
+       const char *output;     /* expected output string */
+} ns_plugin_expandpath_test_params_t;
+
+/*%
+ * Perform a single ns_plugin_expandpath() check using given parameters.
+ */
+static void
+run_full_path_test(const ns_plugin_expandpath_test_params_t *test,
+                  void **state)
+{
+       char **target = (char **)state;
+       isc_result_t result;
+
+       REQUIRE(test != NULL);
+       REQUIRE(test->id.description != NULL);
+       REQUIRE(test->input != NULL);
+       REQUIRE(test->result != ISC_R_SUCCESS || test->output != NULL);
+
+       /*
+        * Prepare a target buffer of given size.  Store it in 'state' so that
+        * it can get cleaned up by _teardown() if the test fails.
+        */
+       *target = isc_mem_allocate(mctx, test->output_size);
+
+       /*
+        * Call ns_plugin_expandpath().
+        */
+       result = ns_plugin_expandpath(test->input,
+                                        *target, test->output_size);
+
+       /*
+        * Check return value.
+        */
+       if (result != test->result) {
+               fail_msg("# test \"%s\" on line %d: "
+                        "expected result %d (%s), got %d (%s)",
+                        test->id.description, test->id.lineno,
+                        test->result, isc_result_totext(test->result),
+                        result, isc_result_totext(result));
+       }
+
+       /*
+        * Check output string if return value indicates success.
+        */
+       if (result == ISC_R_SUCCESS && strcmp(*target, test->output) != 0) {
+               fail_msg("# test \"%s\" on line %d: "
+                        "expected output \"%s\", got \"%s\"",
+                        test->id.description, test->id.lineno,
+                        test->output, *target);
+       }
+
+       isc_mem_free(mctx, *target);
+}
+
+/* test ns_plugin_expandpath() */
+static void
+ns_plugin_expandpath_test(void **state) {
+       size_t i;
+
+       const ns_plugin_expandpath_test_params_t tests[] = {
+               {
+                       NS_TEST_ID("correct use with an absolute path"),
+                       .input = "/usr/lib/named/foo.so",
+                       .output_size = PATH_MAX,
+                       .result = ISC_R_SUCCESS,
+                       .output = "/usr/lib/named/foo.so",
+               },
+               {
+                       NS_TEST_ID("correct use with a relative path"),
+                       .input = "../../foo.so",
+                       .output_size = PATH_MAX,
+                       .result = ISC_R_SUCCESS,
+                       .output = "../../foo.so",
+               },
+               {
+                       NS_TEST_ID("correct use with a filename"),
+                       .input = "foo.so",
+                       .output_size = PATH_MAX,
+                       .result = ISC_R_SUCCESS,
+#ifndef WIN32
+                       .output = NAMED_PLUGINDIR "/foo.so",
+#else
+                       .output = "foo.so",
+#endif
+               },
+               {
+                       NS_TEST_ID("no space at all in target buffer"),
+                       .input = "/usr/lib/named/foo.so",
+                       .output_size = 0,
+                       .result = ISC_R_NOSPACE,
+               },
+               {
+                       NS_TEST_ID("target buffer too small to fit input"),
+                       .input = "/usr/lib/named/foo.so",
+                       .output_size = 1,
+                       .result = ISC_R_NOSPACE,
+               },
+               {
+                       NS_TEST_ID("target buffer too small to fit NULL byte"),
+                       .input = "/foo.so",
+                       .output_size = 7,
+                       .result = ISC_R_NOSPACE,
+               },
+#ifndef WIN32
+               {
+                       NS_TEST_ID("target buffer too small to fit full path"),
+                       .input = "foo.so",
+                       .output_size = 7,
+                       .result = ISC_R_NOSPACE,
+               },
+#endif
+       };
+
+       for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+               run_full_path_test(&tests[i], state);
+       }
+}
+
+int
+main(void) {
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test_setup_teardown(ns_plugin_expandpath_test,
+                                               _setup, _teardown),
+       };
+
+       return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+       printf("1..0 # Skipped: cmocka not available\n");
+       return (0);
+}
+
+#endif
index bc0d89175472c99f765fb68cf00edbd9f909d5c1..d47deaa6d5318cc3784ba1c870b58a805a4c4b40 100644 (file)
@@ -75,6 +75,7 @@ ns_log_init
 ns_log_setcontext
 ns_notify_start
 ns_plugin_check
+ns_plugin_expandpath
 ns_plugin_register
 ns_plugins_create
 ns_plugins_free
index ea719293ed89ad4ad820a00bda8baf58c2ae89fe..65174fb236c2e201ea2849f259671115024a5ee2 100644 (file)
 ./lib/ns/tests/notify_test.c                   C       2017,2018,2019
 ./lib/ns/tests/nstest.c                                C       2017,2018,2019
 ./lib/ns/tests/nstest.h                                C       2017,2018,2019
+./lib/ns/tests/plugin_test.c                   C       2019
 ./lib/ns/tests/query_test.c                    C       2017,2018,2019
 ./lib/ns/tests/testdata/notify/notify1.msg     X       2017,2018,2019
 ./lib/ns/update.c                              C       2017,2018,2019