1 /* $NetBSD: hooks.c,v 1.10 2024/02/21 22:52:46 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file */ 17 18 #include <errno.h> 19 #include <stdio.h> 20 #include <string.h> 21 #include <uv.h> 22 23 #include <isc/errno.h> 24 #include <isc/list.h> 25 #include <isc/log.h> 26 #include <isc/mem.h> 27 #include <isc/mutex.h> 28 #include <isc/print.h> 29 #include <isc/result.h> 30 #include <isc/types.h> 31 #include <isc/util.h> 32 33 #include <dns/view.h> 34 35 #include <ns/hooks.h> 36 #include <ns/log.h> 37 #include <ns/query.h> 38 39 #define CHECK(op) \ 40 do { \ 41 result = (op); \ 42 if (result != ISC_R_SUCCESS) { \ 43 goto cleanup; \ 44 } \ 45 } while (0) 46 47 struct ns_plugin { 48 isc_mem_t *mctx; 49 uv_lib_t handle; 50 void *inst; 51 char *modpath; 52 ns_plugin_check_t *check_func; 53 ns_plugin_register_t *register_func; 54 ns_plugin_destroy_t *destroy_func; 55 LINK(ns_plugin_t) link; 56 }; 57 58 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; 59 ns_hooktable_t *ns__hook_table = &default_hooktable; 60 61 isc_result_t 62 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { 63 int result; 64 65 /* 66 * On Unix systems, differentiate between paths and filenames. 67 */ 68 if (strchr(src, '/') != NULL) { 69 /* 70 * 'src' is an absolute or relative path. Copy it verbatim. 71 */ 72 result = snprintf(dst, dstsize, "%s", src); 73 } else { 74 /* 75 * 'src' is a filename. Prepend default plugin directory path. 76 */ 77 result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); 78 } 79 80 if (result < 0) { 81 return (isc_errno_toresult(errno)); 82 } else if ((size_t)result >= dstsize) { 83 return (ISC_R_NOSPACE); 84 } else { 85 return (ISC_R_SUCCESS); 86 } 87 } 88 89 static isc_result_t 90 load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name, 91 void **symbolp) { 92 void *symbol = NULL; 93 int r; 94 95 REQUIRE(handle != NULL); 96 REQUIRE(symbolp != NULL && *symbolp == NULL); 97 98 r = uv_dlsym(handle, symbol_name, &symbol); 99 if (r != 0) { 100 const char *errmsg = uv_dlerror(handle); 101 if (errmsg == NULL) { 102 errmsg = "returned function pointer is NULL"; 103 } 104 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 105 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 106 "failed to look up symbol %s in " 107 "plugin '%s': %s", 108 symbol_name, modpath, errmsg); 109 return (ISC_R_FAILURE); 110 } 111 112 *symbolp = symbol; 113 114 return (ISC_R_SUCCESS); 115 } 116 117 static void 118 unload_plugin(ns_plugin_t **pluginp); 119 120 static isc_result_t 121 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 122 isc_result_t result; 123 ns_plugin_t *plugin = NULL; 124 ns_plugin_version_t *version_func = NULL; 125 int version; 126 int r; 127 128 REQUIRE(pluginp != NULL && *pluginp == NULL); 129 130 plugin = isc_mem_get(mctx, sizeof(*plugin)); 131 memset(plugin, 0, sizeof(*plugin)); 132 isc_mem_attach(mctx, &plugin->mctx); 133 134 plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); 135 136 ISC_LINK_INIT(plugin, link); 137 138 r = uv_dlopen(modpath, &plugin->handle); 139 if (r != 0) { 140 const char *errmsg = uv_dlerror(&plugin->handle); 141 if (errmsg == NULL) { 142 errmsg = "unknown error"; 143 } 144 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 145 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 146 "failed to dlopen() plugin '%s': %s", modpath, 147 errmsg); 148 CHECK(ISC_R_FAILURE); 149 } 150 151 CHECK(load_symbol(&plugin->handle, modpath, "plugin_version", 152 (void **)&version_func)); 153 154 version = version_func(); 155 if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 156 version > NS_PLUGIN_VERSION) 157 { 158 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 159 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 160 "plugin API version mismatch: %d/%d", version, 161 NS_PLUGIN_VERSION); 162 CHECK(ISC_R_FAILURE); 163 } 164 165 CHECK(load_symbol(&plugin->handle, modpath, "plugin_check", 166 (void **)&plugin->check_func)); 167 CHECK(load_symbol(&plugin->handle, modpath, "plugin_register", 168 (void **)&plugin->register_func)); 169 CHECK(load_symbol(&plugin->handle, modpath, "plugin_destroy", 170 (void **)&plugin->destroy_func)); 171 172 *pluginp = plugin; 173 174 return (ISC_R_SUCCESS); 175 176 cleanup: 177 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 178 ISC_LOG_ERROR, 179 "failed to dynamically load plugin '%s': %s", modpath, 180 isc_result_totext(result)); 181 182 unload_plugin(&plugin); 183 184 return (result); 185 } 186 187 static void 188 unload_plugin(ns_plugin_t **pluginp) { 189 ns_plugin_t *plugin = NULL; 190 191 REQUIRE(pluginp != NULL && *pluginp != NULL); 192 193 plugin = *pluginp; 194 *pluginp = NULL; 195 196 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 197 ISC_LOG_DEBUG(1), "unloading plugin '%s'", 198 plugin->modpath); 199 200 if (plugin->inst != NULL) { 201 plugin->destroy_func(&plugin->inst); 202 } 203 204 uv_dlclose(&plugin->handle); 205 isc_mem_free(plugin->mctx, plugin->modpath); 206 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 207 } 208 209 isc_result_t 210 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, 211 const char *cfg_file, unsigned long cfg_line, 212 isc_mem_t *mctx, isc_log_t *lctx, void *actx, 213 dns_view_t *view) { 214 isc_result_t result; 215 ns_plugin_t *plugin = NULL; 216 217 REQUIRE(mctx != NULL); 218 REQUIRE(lctx != NULL); 219 REQUIRE(view != NULL); 220 221 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 222 ISC_LOG_INFO, "loading plugin '%s'", modpath); 223 224 CHECK(load_plugin(mctx, modpath, &plugin)); 225 226 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 227 ISC_LOG_INFO, "registering plugin '%s'", modpath); 228 229 CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx, 230 lctx, actx, view->hooktable, 231 &plugin->inst)); 232 233 ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); 234 235 cleanup: 236 if (result != ISC_R_SUCCESS && plugin != NULL) { 237 unload_plugin(&plugin); 238 } 239 240 return (result); 241 } 242 243 isc_result_t 244 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, 245 const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, 246 isc_log_t *lctx, void *actx) { 247 isc_result_t result; 248 ns_plugin_t *plugin = NULL; 249 250 CHECK(load_plugin(mctx, modpath, &plugin)); 251 252 result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, 253 lctx, actx); 254 255 cleanup: 256 if (plugin != NULL) { 257 unload_plugin(&plugin); 258 } 259 260 return (result); 261 } 262 263 void 264 ns_hooktable_init(ns_hooktable_t *hooktable) { 265 int i; 266 267 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 268 ISC_LIST_INIT((*hooktable)[i]); 269 } 270 } 271 272 isc_result_t 273 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { 274 ns_hooktable_t *hooktable = NULL; 275 276 REQUIRE(tablep != NULL && *tablep == NULL); 277 278 hooktable = isc_mem_get(mctx, sizeof(*hooktable)); 279 280 ns_hooktable_init(hooktable); 281 282 *tablep = hooktable; 283 284 return (ISC_R_SUCCESS); 285 } 286 287 void 288 ns_hooktable_free(isc_mem_t *mctx, void **tablep) { 289 ns_hooktable_t *table = NULL; 290 ns_hook_t *hook = NULL, *next = NULL; 291 int i = 0; 292 293 REQUIRE(tablep != NULL && *tablep != NULL); 294 295 table = *tablep; 296 *tablep = NULL; 297 298 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 299 for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL; 300 hook = next) 301 { 302 next = ISC_LIST_NEXT(hook, link); 303 ISC_LIST_UNLINK((*table)[i], hook, link); 304 if (hook->mctx != NULL) { 305 isc_mem_putanddetach(&hook->mctx, hook, 306 sizeof(*hook)); 307 } 308 } 309 } 310 311 isc_mem_put(mctx, table, sizeof(*table)); 312 } 313 314 void 315 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 316 ns_hookpoint_t hookpoint, const ns_hook_t *hook) { 317 ns_hook_t *copy = NULL; 318 319 REQUIRE(hooktable != NULL); 320 REQUIRE(mctx != NULL); 321 REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); 322 REQUIRE(hook != NULL); 323 324 copy = isc_mem_get(mctx, sizeof(*copy)); 325 memset(copy, 0, sizeof(*copy)); 326 327 copy->action = hook->action; 328 copy->action_data = hook->action_data; 329 isc_mem_attach(mctx, ©->mctx); 330 331 ISC_LINK_INIT(copy, link); 332 ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); 333 } 334 335 void 336 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { 337 ns_plugins_t *plugins = NULL; 338 339 REQUIRE(listp != NULL && *listp == NULL); 340 341 plugins = isc_mem_get(mctx, sizeof(*plugins)); 342 memset(plugins, 0, sizeof(*plugins)); 343 ISC_LIST_INIT(*plugins); 344 345 *listp = plugins; 346 } 347 348 void 349 ns_plugins_free(isc_mem_t *mctx, void **listp) { 350 ns_plugins_t *list = NULL; 351 ns_plugin_t *plugin = NULL, *next = NULL; 352 353 REQUIRE(listp != NULL && *listp != NULL); 354 355 list = *listp; 356 *listp = NULL; 357 358 for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) { 359 next = ISC_LIST_NEXT(plugin, link); 360 ISC_LIST_UNLINK(*list, plugin, link); 361 unload_plugin(&plugin); 362 } 363 364 isc_mem_put(mctx, list, sizeof(*list)); 365 } 366