summaryrefslogtreecommitdiffstats
path: root/daemon/engine.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:55:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:55:53 +0000
commit3d0386f27ca66379acf50199e1d1298386eeeeb8 (patch)
treef87bd4a126b3a843858eb447e8fd5893c3ee3882 /daemon/engine.c
parentInitial commit. (diff)
downloadknot-resolver-3d0386f27ca66379acf50199e1d1298386eeeeb8.tar.xz
knot-resolver-3d0386f27ca66379acf50199e1d1298386eeeeb8.zip
Adding upstream version 3.2.1.upstream/3.2.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'daemon/engine.c')
-rw-r--r--daemon/engine.c1055
1 files changed, 1055 insertions, 0 deletions
diff --git a/daemon/engine.c b/daemon/engine.c
new file mode 100644
index 0000000..5eebe3d
--- /dev/null
+++ b/daemon/engine.c
@@ -0,0 +1,1055 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <contrib/cleanup.h>
+#include <ccan/json/json.h>
+#include <ccan/asprintf/asprintf.h>
+#include <uv.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/param.h>
+#include <libzscanner/scanner.h>
+
+#include "daemon/engine.h"
+#include "daemon/bindings.h"
+#include "daemon/ffimodule.h"
+#include "lib/nsrep.h"
+#include "lib/cache/api.h"
+#include "lib/defines.h"
+#include "lib/cache/cdb_lmdb.h"
+#include "lib/dnssec/ta.h"
+
+/* Magic defaults for the engine. */
+#ifndef LRU_RTT_SIZE
+#define LRU_RTT_SIZE 65536 /**< NS RTT cache size */
+#endif
+#ifndef LRU_REP_SIZE
+#define LRU_REP_SIZE (LRU_RTT_SIZE / 4) /**< NS reputation cache size */
+#endif
+#ifndef LRU_COOKIES_SIZE
+ #ifdef ENABLE_COOKIES
+ #define LRU_COOKIES_SIZE LRU_RTT_SIZE /**< DNS cookies cache size. */
+ #else
+ #define LRU_COOKIES_SIZE LRU_ASSOC /* simpler than guards everywhere */
+ #endif
+#endif
+
+/** @internal Compatibility wrapper for Lua < 5.2 */
+#if LUA_VERSION_NUM < 502
+#define lua_rawlen(L, obj) lua_objlen((L), (obj))
+#endif
+
+/**@internal Maximum number of incomplete TCP connections in queue.
+* Default is from Redis and Apache. */
+#ifndef TCP_BACKLOG_DEFAULT
+#define TCP_BACKLOG_DEFAULT 511
+#endif
+
+/** @internal Annotate for static checkers. */
+KR_NORETURN int lua_error (lua_State *L);
+
+/* Cleanup engine state every 5 minutes */
+const size_t CLEANUP_TIMER = 5*60*1000;
+
+/* Execute byte code */
+#define l_dobytecode(L, arr, len, name) \
+ (luaL_loadbuffer((L), (arr), (len), (name)) || lua_pcall((L), 0, LUA_MULTRET, 0))
+/** Load file in a sandbox environment. */
+#define l_dosandboxfile(L, filename) \
+ (luaL_loadfile((L), (filename)) || engine_pcall((L), 0))
+
+/*
+ * Global bindings.
+ */
+
+/** Register module callback into Lua world. */
+#define REGISTER_MODULE_CALL(L, module, cb, name) do { \
+ lua_pushlightuserdata((L), (module)); \
+ lua_pushlightuserdata((L), (cb)); \
+ lua_pushcclosure((L), l_trampoline, 2); \
+ lua_setfield((L), -2, (name)); \
+ } while (0)
+
+/** Print help and available commands. */
+static int l_help(lua_State *L)
+{
+ static const char *help_str =
+ "help()\n show this help\n"
+ "quit()\n quit\n"
+ "hostname()\n hostname\n"
+ "package_version()\n return package version\n"
+ "user(name[, group])\n change process user (and group)\n"
+ "verbose(true|false)\n toggle verbose mode\n"
+ "option(opt[, new_val])\n get/set server option\n"
+ "mode(strict|normal|permissive)\n set resolver strictness level\n"
+ "reorder_RR([true|false])\n set/get reordering of RRs within RRsets\n"
+ "resolve(name, type[, class, flags, callback])\n resolve query, callback when it's finished\n"
+ "todname(name)\n convert name to wire format\n"
+ "tojson(val)\n convert value to JSON\n"
+ "map(expr)\n run expression on all workers\n"
+ "net\n network configuration\n"
+ "cache\n network configuration\n"
+ "modules\n modules configuration\n"
+ "kres\n resolver services\n"
+ "trust_anchors\n configure trust anchors\n"
+ ;
+ lua_pushstring(L, help_str);
+ return 1;
+}
+
+static bool update_privileges(int uid, int gid)
+{
+ if ((gid_t)gid != getgid()) {
+ if (setregid(gid, gid) < 0) {
+ return false;
+ }
+ }
+ if ((uid_t)uid != getuid()) {
+ if (setreuid(uid, uid) < 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/** Set process user/group. */
+static int l_setuser(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "user(user[, group)");
+ lua_error(L);
+ }
+ /* Fetch UID/GID based on string identifiers. */
+ struct passwd *user_pw = getpwnam(lua_tostring(L, 1));
+ if (!user_pw) {
+ lua_pushliteral(L, "invalid user name");
+ lua_error(L);
+ }
+ int uid = user_pw->pw_uid;
+ int gid = getgid();
+ if (n > 1 && lua_isstring(L, 2)) {
+ struct group *group_pw = getgrnam(lua_tostring(L, 2));
+ if (!group_pw) {
+ lua_pushliteral(L, "invalid group name");
+ lua_error(L);
+ }
+ gid = group_pw->gr_gid;
+ }
+ /* Drop privileges */
+ bool ret = update_privileges(uid, gid);
+ if (!ret) {
+ lua_pushstring(L, strerror(errno));
+ lua_error(L);
+ }
+ lua_pushboolean(L, ret);
+ return 1;
+}
+
+/** Quit current executable. */
+static int l_quit(lua_State *L)
+{
+ engine_stop(engine_luaget(L));
+ return 0;
+}
+
+/** Toggle verbose mode. */
+static int l_verbose(lua_State *L)
+{
+ if (lua_isboolean(L, 1) || lua_isnumber(L, 1)) {
+ kr_verbose_set(lua_toboolean(L, 1));
+ }
+ lua_pushboolean(L, kr_verbose_status);
+ return 1;
+}
+
+char *engine_get_hostname(struct engine *engine) {
+ static char hostname_str[KNOT_DNAME_MAXLEN];
+ if (!engine) {
+ return NULL;
+ }
+
+ if (!engine->hostname) {
+ if (gethostname(hostname_str, sizeof(hostname_str)) != 0)
+ return NULL;
+ return hostname_str;
+ }
+ return engine->hostname;
+}
+
+int engine_set_hostname(struct engine *engine, const char *hostname) {
+ if (!engine || !hostname) {
+ return kr_error(EINVAL);
+ }
+
+ char *new_hostname = strdup(hostname);
+ if (!new_hostname) {
+ return kr_error(ENOMEM);
+ }
+ if (engine->hostname) {
+ free(engine->hostname);
+ }
+ engine->hostname = new_hostname;
+ network_new_hostname(&engine->net, engine);
+
+ return 0;
+}
+
+/** Return hostname. */
+static int l_hostname(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (lua_gettop(L) == 0) {
+ lua_pushstring(L, engine_get_hostname(engine));
+ return 1;
+ }
+ if ((lua_gettop(L) != 1) || !lua_isstring(L, 1)) {
+ lua_pushstring(L, "hostname takes at most one parameter: (\"fqdn\")");
+ lua_error(L);
+ }
+
+ if (engine_set_hostname(engine, lua_tostring(L, 1)) != 0) {
+ lua_pushstring(L, "setting hostname failed");
+ lua_error(L);
+ }
+
+ lua_pushstring(L, engine_get_hostname(engine));
+ return 1;
+}
+
+/** Return server package version. */
+static int l_package_version(lua_State *L)
+{
+ lua_pushliteral(L, PACKAGE_VERSION);
+ return 1;
+}
+
+char *engine_get_moduledir(struct engine *engine) {
+ return engine->moduledir;
+}
+
+int engine_set_moduledir(struct engine *engine, const char *moduledir) {
+ if (!engine || !moduledir) {
+ return kr_error(EINVAL);
+ }
+
+ char *new_moduledir = strdup(moduledir);
+ if (!new_moduledir) {
+ return kr_error(ENOMEM);
+ }
+ if (engine->moduledir) {
+ free(engine->moduledir);
+ }
+ engine->moduledir = new_moduledir;
+
+ /* Use module path for including Lua scripts */
+ char l_paths[MAXPATHLEN] = { 0 };
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wformat" /* %1$ is not in C standard */
+ /* Save original package.path to package._path */
+ snprintf(l_paths, MAXPATHLEN - 1,
+ "if package._path == nil then package._path = package.path end\n"
+ "package.path = '%1$s/?.lua;%1$s/?/init.lua;'..package._path\n"
+ "if package._cpath == nil then package._cpath = package.cpath end\n"
+ "package.cpath = '%1$s/?%2$s;'..package._cpath\n",
+ new_moduledir, LIBEXT);
+ #pragma GCC diagnostic pop
+
+ int ret = l_dobytecode(engine->L, l_paths, strlen(l_paths), "");
+ if (ret != 0) {
+ lua_pop(engine->L, 1);
+ return ret;
+ }
+ return 0;
+}
+
+/** Return hostname. */
+static int l_moduledir(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (lua_gettop(L) == 0) {
+ lua_pushstring(L, engine_get_moduledir(engine));
+ return 1;
+ }
+ if ((lua_gettop(L) != 1) || !lua_isstring(L, 1)) {
+ lua_pushstring(L, "moduledir takes at most one parameter: (\"directory\")");
+ lua_error(L);
+ }
+
+ if (engine_set_moduledir(engine, lua_tostring(L, 1)) != 0) {
+ lua_pushstring(L, "setting moduledir failed");
+ lua_error(L);
+ }
+
+ lua_pushstring(L, engine_get_moduledir(engine));
+ return 1;
+}
+
+/** @internal for l_trustanchor: */
+static void ta_add(zs_scanner_t *zs)
+{
+ map_t *ta = zs->process.data;
+ if (!ta)
+ return;
+ if (kr_ta_add(ta, zs->r_owner, zs->r_type, zs->r_ttl, zs->r_data, zs->r_data_length))
+ zs->process.data = NULL; /* error signalling */
+}
+/** Enable/disable trust anchor. */
+static int l_trustanchor(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ const char *anchor = lua_tostring(L, 1);
+ bool enable = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true;
+ if (!anchor || strlen(anchor) == 0) {
+ return 0;
+ }
+ /* If disabling, parse the owner string only. */
+ if (!enable) {
+ knot_dname_t *owner = knot_dname_from_str(NULL, anchor, KNOT_DNAME_MAXLEN);
+ if (!owner) {
+ lua_pushstring(L, "invalid trust anchor owner");
+ lua_error(L);
+ }
+ lua_pushboolean(L, kr_ta_del(&engine->resolver.trust_anchors, owner) == 0);
+ free(owner);
+ return 1;
+ }
+
+ /* Parse the record */
+ zs_scanner_t *zs = malloc(sizeof(*zs));
+ if (!zs || zs_init(zs, ".", 1, 0) != 0) {
+ free(zs);
+ lua_pushstring(L, "not enough memory");
+ lua_error(L);
+ }
+ zs_set_processing(zs, ta_add, NULL, &engine->resolver.trust_anchors);
+ bool ok = zs_set_input_string(zs, anchor, strlen(anchor)) == 0
+ && zs_parse_all(zs) == 0;
+ ok = ok && zs->process.data; /* reset to NULL on error in ta_add */
+
+ zs_deinit(zs);
+ free(zs);
+ /* Report errors */
+ if (!ok) {
+ lua_pushstring(L, "failed to process trust anchor RR");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/** Load root hints from zonefile. */
+static int l_hint_root_file(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct kr_context *ctx = &engine->resolver;
+ const char *file = lua_tostring(L, 1);
+
+ const char *err = engine_hint_root_file(ctx, file);
+ if (err) {
+ if (!file) {
+ file = ROOTHINTS;
+ }
+ lua_push_printf(L, "error when opening '%s': %s", file, err);
+ lua_error(L);
+ } else {
+ lua_pushboolean(L, true);
+ return 1;
+ }
+}
+
+/** @internal for engine_hint_root_file */
+static void roothints_add(zs_scanner_t *zs)
+{
+ struct kr_zonecut *hints = zs->process.data;
+ if (!hints) {
+ return;
+ }
+ if (zs->r_type == KNOT_RRTYPE_A || zs->r_type == KNOT_RRTYPE_AAAA) {
+ kr_zonecut_add(hints, zs->r_owner, zs->r_data, zs->r_data_length);
+ }
+}
+const char* engine_hint_root_file(struct kr_context *ctx, const char *file)
+{
+ if (!file) {
+ file = ROOTHINTS;
+ }
+ if (strlen(file) == 0 || !ctx) {
+ return "invalid parameters";
+ }
+ struct kr_zonecut *root_hints = &ctx->root_hints;
+
+ zs_scanner_t zs;
+ if (zs_init(&zs, ".", 1, 0) != 0) {
+ return "not enough memory";
+ }
+ if (zs_set_input_file(&zs, file) != 0) {
+ zs_deinit(&zs);
+ return "failed to open root hints file";
+ }
+
+ kr_zonecut_set(root_hints, (const uint8_t *)"");
+ zs_set_processing(&zs, roothints_add, NULL, root_hints);
+ zs_parse_all(&zs);
+ zs_deinit(&zs);
+ return NULL;
+}
+
+/** Unpack JSON object to table */
+static void l_unpack_json(lua_State *L, JsonNode *table)
+{
+ /* Unpack POD */
+ switch(table->tag) {
+ case JSON_STRING: lua_pushstring(L, table->string_); return;
+ case JSON_NUMBER: lua_pushnumber(L, table->number_); return;
+ case JSON_BOOL: lua_pushboolean(L, table->bool_); return;
+ default: break;
+ }
+ /* Unpack object or array into table */
+ lua_newtable(L);
+ JsonNode *node = NULL;
+ json_foreach(node, table) {
+ /* Push node value */
+ switch(node->tag) {
+ case JSON_OBJECT: /* as array */
+ case JSON_ARRAY: l_unpack_json(L, node); break;
+ case JSON_STRING: lua_pushstring(L, node->string_); break;
+ case JSON_NUMBER: lua_pushnumber(L, node->number_); break;
+ case JSON_BOOL: lua_pushboolean(L, node->bool_); break;
+ default: continue;
+ }
+ /* Set table key */
+ if (node->key) {
+ lua_setfield(L, -2, node->key);
+ } else {
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ }
+ }
+}
+
+/** @internal Recursive Lua/JSON serialization. */
+static JsonNode *l_pack_elem(lua_State *L, int top)
+{
+ switch(lua_type(L, top)) {
+ case LUA_TSTRING: return json_mkstring(lua_tostring(L, top));
+ case LUA_TNUMBER: return json_mknumber(lua_tonumber(L, top));
+ case LUA_TBOOLEAN: return json_mkbool(lua_toboolean(L, top));
+ case LUA_TTABLE: break; /* Table, iterate it. */
+ default: return json_mknull();
+ }
+ /* Use absolute indexes here, as the table may be nested. */
+ JsonNode *node = NULL;
+ lua_pushnil(L);
+ while(lua_next(L, top) != 0) {
+ bool is_array = false;
+ if (!node) {
+ is_array = (lua_type(L, top + 1) == LUA_TNUMBER);
+ node = is_array ? json_mkarray() : json_mkobject();
+ if (!node) {
+ return NULL;
+ }
+ } else {
+ is_array = node->tag == JSON_ARRAY;
+ }
+
+ /* Insert to array/table. */
+ JsonNode *val = l_pack_elem(L, top + 2);
+ if (is_array) {
+ json_append_element(node, val);
+ } else {
+ const char *key = lua_tostring(L, top + 1);
+ json_append_member(node, key, val);
+ }
+ lua_pop(L, 1);
+ }
+ /* Return empty object for empty tables. */
+ return node ? node : json_mkobject();
+}
+
+/** @internal Serialize to string */
+static char *l_pack_json(lua_State *L, int top)
+{
+ JsonNode *root = l_pack_elem(L, top);
+ if (!root) {
+ return NULL;
+ }
+ char *result = json_encode(root);
+ json_delete(root);
+ return result;
+}
+
+static int l_tojson(lua_State *L)
+{
+ auto_free char *json_str = l_pack_json(L, lua_gettop(L));
+ if (!json_str) {
+ return 0;
+ }
+ lua_pushstring(L, json_str);
+ return 1;
+}
+
+static int l_fromjson(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "a JSON string is required");
+ lua_error(L);
+ }
+
+ const char *json_str = lua_tostring(L, 1);
+ JsonNode *root_node = json_decode(json_str);
+
+ if (!root_node) {
+ lua_pushliteral(L, "invalid JSON string");
+ lua_error(L);
+ }
+ l_unpack_json(L, root_node);
+ json_delete(root_node);
+
+ return 1;
+}
+
+/** @internal Throw Lua error if expr is false */
+#define expr_checked(expr) \
+ if (!(expr)) { lua_pushboolean(L, false); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); continue; }
+
+static int l_map(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "map('string with a lua expression')");
+ lua_error(L);
+ }
+
+ struct engine *engine = engine_luaget(L);
+ const char *cmd = lua_tostring(L, 1);
+ uint32_t len = strlen(cmd);
+ lua_newtable(L);
+
+ /* Execute on leader instance */
+ int ntop = lua_gettop(L);
+ engine_cmd(L, cmd, true);
+ lua_settop(L, ntop + 1); /* Push only one return value to table */
+ lua_rawseti(L, -2, 1);
+
+ for (size_t i = 0; i < engine->ipc_set.len; ++i) {
+ int fd = engine->ipc_set.at[i];
+ /* Send command */
+ expr_checked(write(fd, &len, sizeof(len)) == sizeof(len));
+ expr_checked(write(fd, cmd, len) == len);
+ /* Read response */
+ uint32_t rlen = 0;
+ if (read(fd, &rlen, sizeof(rlen)) == sizeof(rlen)) {
+ expr_checked(rlen < UINT32_MAX);
+ auto_free char *rbuf = malloc(rlen + 1);
+ expr_checked(rbuf != NULL);
+ expr_checked(read(fd, rbuf, rlen) == rlen);
+ rbuf[rlen] = '\0';
+ /* Unpack from JSON */
+ JsonNode *root_node = json_decode(rbuf);
+ if (root_node) {
+ l_unpack_json(L, root_node);
+ } else {
+ lua_pushlstring(L, rbuf, rlen);
+ }
+ json_delete(root_node);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ continue;
+ }
+ /* Didn't respond */
+ lua_pushboolean(L, false);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ }
+ return 1;
+}
+
+#undef expr_checked
+
+
+/** Trampoline function for module properties. */
+static int l_trampoline(lua_State *L)
+{
+ struct kr_module *module = lua_touserdata(L, lua_upvalueindex(1));
+ void* callback = lua_touserdata(L, lua_upvalueindex(2));
+ struct engine *engine = engine_luaget(L);
+ if (!module) {
+ lua_pushstring(L, "module closure missing upvalue");
+ lua_error(L);
+ }
+
+ /* Now we only have property callback or config,
+ * if we expand the callables, we might need a callback_type.
+ */
+ const char *args = NULL;
+ auto_free char *cleanup_args = NULL;
+ if (lua_gettop(L) > 0) {
+ if (lua_istable(L, 1) || lua_isboolean(L, 1)) {
+ cleanup_args = l_pack_json(L, 1);
+ args = cleanup_args;
+ } else {
+ args = lua_tostring(L, 1);
+ }
+ }
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wpedantic" /* void* vs. function pointer */
+ if (callback == module->config) {
+ module->config(module, args);
+ } else {
+ kr_prop_cb *prop = (kr_prop_cb *)callback;
+ auto_free char *ret = prop(engine, module, args);
+ if (!ret) { /* No results */
+ return 0;
+ }
+ JsonNode *root_node = json_decode(ret);
+ if (root_node) {
+ l_unpack_json(L, root_node);
+ } else {
+ lua_pushstring(L, ret);
+ }
+ json_delete(root_node);
+ return 1;
+ }
+ #pragma GCC diagnostic pop
+
+ /* No results */
+ return 0;
+}
+
+/*
+ * Engine API.
+ */
+
+static int init_resolver(struct engine *engine)
+{
+ /* Note: it had been zored by engine_init(). */
+ /* Open resolution context */
+ engine->resolver.trust_anchors = map_make(NULL);
+ engine->resolver.negative_anchors = map_make(NULL);
+ engine->resolver.pool = engine->pool;
+ engine->resolver.modules = &engine->modules;
+ engine->resolver.cache_rtt_tout_retry_interval = KR_NS_TIMEOUT_RETRY_INTERVAL;
+ /* Create OPT RR */
+ engine->resolver.opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t));
+ if (!engine->resolver.opt_rr) {
+ return kr_error(ENOMEM);
+ }
+ knot_edns_init(engine->resolver.opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool);
+ /* Use default TLS padding */
+ engine->resolver.tls_padding = -1;
+ /* Empty init; filled via ./lua/config.lua */
+ kr_zonecut_init(&engine->resolver.root_hints, (const uint8_t *)"", engine->pool);
+ /* Open NS rtt + reputation cache */
+ lru_create(&engine->resolver.cache_rtt, LRU_RTT_SIZE, engine->pool, NULL);
+ lru_create(&engine->resolver.cache_rep, LRU_REP_SIZE, engine->pool, NULL);
+ lru_create(&engine->resolver.cache_cookie, LRU_COOKIES_SIZE, engine->pool, NULL);
+
+ /* Load basic modules */
+ engine_register(engine, "iterate", NULL, NULL);
+ engine_register(engine, "validate", NULL, NULL);
+ engine_register(engine, "cache", NULL, NULL);
+
+ return array_push(engine->backends, kr_cdb_lmdb());
+}
+
+static int init_state(struct engine *engine)
+{
+ /* Initialize Lua state */
+ engine->L = luaL_newstate();
+ if (engine->L == NULL) {
+ return kr_error(ENOMEM);
+ }
+ /* Initialize used libraries. */
+ lua_gc(engine->L, LUA_GCSTOP, 0);
+ luaL_openlibs(engine->L);
+ /* Global functions */
+ lua_pushcfunction(engine->L, l_help);
+ lua_setglobal(engine->L, "help");
+ lua_pushcfunction(engine->L, l_quit);
+ lua_setglobal(engine->L, "quit");
+ lua_pushcfunction(engine->L, l_hostname);
+ lua_setglobal(engine->L, "hostname");
+ lua_pushcfunction(engine->L, l_package_version);
+ lua_setglobal(engine->L, "package_version");
+ lua_pushcfunction(engine->L, l_moduledir);
+ lua_setglobal(engine->L, "moduledir");
+ lua_pushcfunction(engine->L, l_verbose);
+ lua_setglobal(engine->L, "verbose");
+ lua_pushcfunction(engine->L, l_setuser);
+ lua_setglobal(engine->L, "user");
+ lua_pushcfunction(engine->L, l_trustanchor);
+ lua_setglobal(engine->L, "trustanchor");
+ lua_pushcfunction(engine->L, l_hint_root_file);
+ lua_setglobal(engine->L, "_hint_root_file");
+ lua_pushliteral(engine->L, libknot_SONAME);
+ lua_setglobal(engine->L, "libknot_SONAME");
+ lua_pushliteral(engine->L, libzscanner_SONAME);
+ lua_setglobal(engine->L, "libzscanner_SONAME");
+ lua_pushcfunction(engine->L, l_tojson);
+ lua_setglobal(engine->L, "tojson");
+ lua_pushcfunction(engine->L, l_fromjson);
+ lua_setglobal(engine->L, "fromjson");
+ lua_pushcfunction(engine->L, l_map);
+ lua_setglobal(engine->L, "map");
+ lua_pushlightuserdata(engine->L, engine);
+ lua_setglobal(engine->L, "__engine");
+ return kr_ok();
+}
+
+/**
+ * Start luacov measurement and store results to file specified by
+ * KRESD_COVERAGE_STATS environment variable.
+ * Do nothing if the variable is not set.
+ */
+static void init_measurement(struct engine *engine)
+{
+ const char * const statspath = getenv("KRESD_COVERAGE_STATS");
+ if (!statspath)
+ return;
+
+ char * snippet = NULL;
+ int ret = asprintf(&snippet,
+ "_luacov_runner = require('luacov.runner')\n"
+ "_luacov_runner.init({\n"
+ " statsfile = '%s',\n"
+ " exclude = {'test', 'tapered', 'lua/5.1'},\n"
+ "})\n"
+ "jit.off()\n", statspath
+ );
+ assert(ret > 0);
+
+ ret = luaL_loadstring(engine->L, snippet);
+ assert(ret == 0);
+ lua_call(engine->L, 0, 0);
+ free(snippet);
+}
+
+int engine_init(struct engine *engine, knot_mm_t *pool)
+{
+ if (engine == NULL) {
+ return kr_error(EINVAL);
+ }
+
+ memset(engine, 0, sizeof(*engine));
+ engine->pool = pool;
+
+ /* Initialize state */
+ int ret = init_state(engine);
+ if (ret != 0) {
+ engine_deinit(engine);
+ return ret;
+ }
+ init_measurement(engine);
+ /* Initialize resolver */
+ ret = init_resolver(engine);
+ if (ret != 0) {
+ engine_deinit(engine);
+ return ret;
+ }
+ /* Initialize network */
+ network_init(&engine->net, uv_default_loop(), TCP_BACKLOG_DEFAULT);
+
+ return ret;
+}
+
+static void engine_unload(struct engine *engine, struct kr_module *module)
+{
+ /* Unregister module */
+ auto_free char *name = strdup(module->name);
+ kr_module_unload(module);
+ /* Clear in Lua world, but not for embedded modules ('cache' in particular). */
+ if (name && !kr_module_embedded(name)) {
+ lua_pushnil(engine->L);
+ lua_setglobal(engine->L, name);
+ }
+ free(module);
+}
+
+void engine_deinit(struct engine *engine)
+{
+ if (engine == NULL) {
+ return;
+ }
+
+ /* Only close sockets and services,
+ * no need to clean up mempool. */
+ network_deinit(&engine->net);
+ kr_zonecut_deinit(&engine->resolver.root_hints);
+ kr_cache_close(&engine->resolver.cache);
+
+ /* The lru keys are currently malloc-ated and need to be freed. */
+ lru_free(engine->resolver.cache_rtt);
+ lru_free(engine->resolver.cache_rep);
+ lru_free(engine->resolver.cache_cookie);
+
+ /* Clear IPC pipes */
+ for (size_t i = 0; i < engine->ipc_set.len; ++i) {
+ close(engine->ipc_set.at[i]);
+ }
+
+ /* Unload modules and engine. */
+ for (size_t i = 0; i < engine->modules.len; ++i) {
+ engine_unload(engine, engine->modules.at[i]);
+ }
+ if (engine->L) {
+ lua_close(engine->L);
+ }
+
+ /* Free data structures */
+ array_clear(engine->modules);
+ array_clear(engine->backends);
+ array_clear(engine->ipc_set);
+ kr_ta_clear(&engine->resolver.trust_anchors);
+ kr_ta_clear(&engine->resolver.negative_anchors);
+ free(engine->hostname);
+ free(engine->moduledir);
+}
+
+int engine_pcall(lua_State *L, int argc)
+{
+#if LUA_VERSION_NUM >= 502
+ lua_getglobal(L, "_SANDBOX");
+ lua_setupvalue(L, -(2 + argc), 1);
+#endif
+ return lua_pcall(L, argc, LUA_MULTRET, 0);
+}
+
+int engine_cmd(lua_State *L, const char *str, bool raw)
+{
+ if (L == NULL) {
+ return kr_error(ENOEXEC);
+ }
+
+ /* Evaluate results */
+ lua_getglobal(L, "eval_cmd");
+ lua_pushstring(L, str);
+ lua_pushboolean(L, raw);
+
+ /* Check result. */
+ return engine_pcall(L, 2);
+}
+
+int engine_ipc(struct engine *engine, const char *expr)
+{
+ if (engine == NULL || engine->L == NULL) {
+ return kr_error(ENOEXEC);
+ }
+
+ /* Run expression and serialize response. */
+ engine_cmd(engine->L, expr, true);
+ if (lua_gettop(engine->L) > 0) {
+ l_tojson(engine->L);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int engine_load_sandbox(struct engine *engine)
+{
+ /* Init environment */
+ static const char sandbox_bytecode[] = {
+ #include "daemon/lua/sandbox.inc"
+ };
+ if (l_dobytecode(engine->L, sandbox_bytecode, sizeof(sandbox_bytecode), "init") != 0) {
+ fprintf(stderr, "[system] error %s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ return kr_error(ENOEXEC);
+ }
+ return kr_ok();
+}
+
+int engine_loadconf(struct engine *engine, const char *config_path)
+{
+ assert(config_path != NULL);
+ int ret = l_dosandboxfile(engine->L, config_path);
+ if (ret != 0) {
+ fprintf(stderr, "%s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ }
+ return ret;
+}
+
+int engine_load_defaults(struct engine *engine)
+{
+ /* Load defaults */
+ static const char config_bytecode[] = {
+ #include "daemon/lua/config.inc"
+ };
+ int ret = l_dobytecode(engine->L, config_bytecode, sizeof(config_bytecode), "config");
+ if (ret != 0) {
+ fprintf(stderr, "%s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ }
+ return ret;
+}
+
+int engine_start(struct engine *engine)
+{
+ /* Clean up stack and restart GC */
+ lua_settop(engine->L, 0);
+ lua_gc(engine->L, LUA_GCCOLLECT, 0);
+ lua_gc(engine->L, LUA_GCSETSTEPMUL, 50);
+ lua_gc(engine->L, LUA_GCSETPAUSE, 400);
+ lua_gc(engine->L, LUA_GCRESTART, 0);
+
+ return kr_ok();
+}
+
+void engine_stop(struct engine *engine)
+{
+ if (!engine) {
+ return;
+ }
+ uv_stop(uv_default_loop());
+}
+
+/** Register module properties in Lua environment, if any. */
+static int register_properties(struct engine *engine, struct kr_module *module)
+{
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wpedantic" /* casts in lua_pushlightuserdata() */
+ if (!module->config && !module->props) {
+ return kr_ok();
+ }
+ lua_newtable(engine->L);
+ if (module->config != NULL) {
+ REGISTER_MODULE_CALL(engine->L, module, module->config, "config");
+ }
+
+ const struct kr_prop *p = module->props == NULL ? NULL : module->props();
+ for (; p && p->name; ++p) {
+ if (p->cb != NULL) {
+ REGISTER_MODULE_CALL(engine->L, module, p->cb, p->name);
+ }
+ }
+ lua_setglobal(engine->L, module->name);
+ #pragma GCC diagnostic pop
+
+ /* Register module in Lua env */
+ lua_getglobal(engine->L, "modules_register");
+ lua_getglobal(engine->L, module->name);
+ if (engine_pcall(engine->L, 1) != 0) {
+ lua_pop(engine->L, 1);
+ }
+
+ return kr_ok();
+}
+
+/** @internal Find matching module */
+static size_t module_find(module_array_t *mod_list, const char *name)
+{
+ size_t found = mod_list->len;
+ for (size_t i = 0; i < mod_list->len; ++i) {
+ struct kr_module *mod = mod_list->at[i];
+ if (strcmp(mod->name, name) == 0) {
+ found = i;
+ break;
+ }
+ }
+ return found;
+}
+
+int engine_register(struct engine *engine, const char *name, const char *precedence, const char* ref)
+{
+ if (engine == NULL || name == NULL) {
+ return kr_error(EINVAL);
+ }
+ /* Make sure module is unloaded */
+ (void) engine_unregister(engine, name);
+ /* Find the index of referenced module. */
+ module_array_t *mod_list = &engine->modules;
+ size_t ref_pos = mod_list->len;
+ if (precedence && ref) {
+ ref_pos = module_find(mod_list, ref);
+ if (ref_pos >= mod_list->len) {
+ return kr_error(EIDRM);
+ }
+ }
+ /* Attempt to load binary module */
+ struct kr_module *module = malloc(sizeof(*module));
+ if (!module) {
+ return kr_error(ENOMEM);
+ }
+ module->data = engine;
+ int ret = kr_module_load(module, name, engine->moduledir);
+ /* Load Lua module if not a binary */
+ if (ret == kr_error(ENOENT)) {
+ ret = ffimodule_register_lua(engine, module, name);
+ } else if (ret == kr_error(ENOTSUP)) {
+ /* Print a more helpful message when module is linked against an old resolver ABI. */
+ fprintf(stderr, "[system] module '%s' links to unsupported ABI, please rebuild it\n", name);
+ }
+ if (ret != 0) {
+ free(module);
+ return ret;
+ }
+ if (array_push(engine->modules, module) < 0) {
+ engine_unload(engine, module);
+ return kr_error(ENOMEM);
+ }
+ /* Evaluate precedence operator */
+ if (precedence) {
+ struct kr_module **arr = mod_list->at;
+ size_t emplacement = mod_list->len;
+ if (strcasecmp(precedence, ">") == 0) {
+ if (ref_pos + 1 < mod_list->len)
+ emplacement = ref_pos + 1; /* Insert after target */
+ }
+ if (strcasecmp(precedence, "<") == 0) {
+ emplacement = ref_pos; /* Insert at target */
+ }
+ /* Move the tail if it has some elements. */
+ if (emplacement + 1 < mod_list->len) {
+ memmove(&arr[emplacement + 1], &arr[emplacement], sizeof(*arr) * (mod_list->len - (emplacement + 1)));
+ arr[emplacement] = module;
+ }
+ }
+
+ return register_properties(engine, module);
+}
+
+int engine_unregister(struct engine *engine, const char *name)
+{
+ module_array_t *mod_list = &engine->modules;
+ size_t found = module_find(mod_list, name);
+ if (found < mod_list->len) {
+ engine_unload(engine, mod_list->at[found]);
+ array_del(*mod_list, found);
+ return kr_ok();
+ }
+
+ return kr_error(ENOENT);
+}
+
+void engine_lualib(struct engine *engine, const char *name, lua_CFunction lib_cb)
+{
+ if (engine != NULL) {
+#if LUA_VERSION_NUM >= 502
+ luaL_requiref(engine->L, name, lib_cb, 1);
+ lua_pop(engine->L, 1);
+#else
+ lib_cb(engine->L);
+#endif
+ }
+}
+
+struct engine *engine_luaget(lua_State *L)
+{
+ lua_getglobal(L, "__engine");
+ struct engine *engine = lua_touserdata(L, -1);
+ if (!engine) luaL_error(L, "internal error, empty engine pointer");
+ lua_pop(L, 1);
+ return engine;
+}