diff --git a/lib/ns/Makefile.in b/lib/ns/Makefile.in index e785969b51..96228b3500 100644 --- a/lib/ns/Makefile.in +++ b/lib/ns/Makefile.in @@ -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 = diff --git a/lib/ns/hooks.c b/lib/ns/hooks.c index 83c390de3a..327ae6909e 100644 --- a/lib/ns/hooks.c +++ b/lib/ns/hooks.c @@ -13,6 +13,8 @@ #include +#include +#include #include #if HAVE_DLFCN_H @@ -21,10 +23,12 @@ #include #endif +#include #include #include #include #include +#include #include #include #include @@ -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) { diff --git a/lib/ns/include/ns/hooks.h b/lib/ns/include/ns/hooks.h index d2d310183c..5ad1074872 100644 --- a/lib/ns/include/ns/hooks.h +++ b/lib/ns/include/ns/hooks.h @@ -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, diff --git a/lib/ns/tests/Kyuafile b/lib/ns/tests/Kyuafile index ca15d6fb84..adf0b88ca7 100644 --- a/lib/ns/tests/Kyuafile +++ b/lib/ns/tests/Kyuafile @@ -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'} diff --git a/lib/ns/tests/Makefile.in b/lib/ns/tests/Makefile.in index 345f900bec..e6d3049ede 100644 --- a/lib/ns/tests/Makefile.in +++ b/lib/ns/tests/Makefile.in @@ -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 index 0000000000..04ce737681 --- /dev/null +++ b/lib/ns/tests/plugin_test.c @@ -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 + +#if HAVE_CMOCKA + +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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 + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif diff --git a/lib/ns/win32/libns.def b/lib/ns/win32/libns.def index bc0d891754..d47deaa6d5 100644 --- a/lib/ns/win32/libns.def +++ b/lib/ns/win32/libns.def @@ -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 diff --git a/util/copyrights b/util/copyrights index ea719293ed..65174fb236 100644 --- a/util/copyrights +++ b/util/copyrights @@ -2510,6 +2510,7 @@ ./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