luaunbound-1.0.0/.hg_archival.txt0000644000000000000000000000025514072400636015104 0ustar 00000000000000repo: fc1c6ebfc967d435fd22a7c22ab39d8b76f7275b node: 56d9b20fa567139461ae6740da5aff5b6bb66292 branch: default latesttag: 1.0.0 latesttagdistance: 1 changessincelatesttag: 1 luaunbound-1.0.0/.hgtags0000644000000000000000000000042014072400636013266 0ustar 000000000000005449acf5fa441ff7a81f4e7386f1f5228a9bf8eb 0.1 06c3157d3c0fa36dc52c1669f1cb6dc4ca311e15 0.2 f744496fe4b0d3f09c4fd46d28c0e66cf21ed2aa 0.3 27c34ad0a1618d53aed6fdd649fbd96cbaa3cd6b 0.4 e1aa69ee78c94739f03c09693919347b376cb9b0 0.5 22af1dbd3e64b53e2069182d7b11f409b234b4ca 1.0.0 luaunbound-1.0.0/GNUmakefile0000644000000000000000000000125414072400636014070 0ustar 00000000000000 .PHONY: all clean .INTERMEDIATE: lunbound.o LUA_VERSION = 5.2 LUA_PC = lua-$(LUA_VERSION) LUA_LIBDIR = $(shell pkg-config --variable=INSTALL_CMOD $(LUA_PC)) CC = c99 CFLAGS += -fPIC $(shell pkg-config --cflags $(LUA_PC)) -Wall -Wextra -pedantic -ggdb LDLIBS += -lunbound LDFLAGS += -shared CFLAGS += $(MYCFLAGS) LDFLAGS += $(MYLDFLAGS) OUTPUT = lunbound.so MKDIR = install -d INSTALL = install -m644 default: lunbound.so all: $(OUTPUT) lunbound.o: lunbound.c %.so: %.o $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) install: $(MKDIR) $(DESTDIR)$(LUA_LIBDIR)/ $(INSTALL) lunbound.so $(DESTDIR)$(LUA_LIBDIR)/ clean: -rm -v $(OUTPUT) luaunbound-1.0.0/LICENSE0000644000000000000000000000207714072400636013027 0ustar 00000000000000luaunbound Copyright (C) 2012-2021 Kim Alvefur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. luaunbound-1.0.0/README.markdown0000644000000000000000000000576514072400636014532 0ustar 00000000000000# luaunbound This is a binding to [libunbound](https://unbound.net/) for [Lua](https://www.lua.org/) . ## Downloading Source can be downloaded with Mercurial from . Signed releases can be found at . It is also available from [luarocks](https://luarocks.org/) and can be installed by luarocks install luaunbound ## Building make ## API ### Creating a new context The lunbound module has a single function, `new()` for creating a new context. It takes a table with configuration as single optional argument. If no argument is given the `config` table on the module will be used. ### Config options `async` : Uses threads if `true` or forks a process if `false`. `hoststxt` : Path to `hosts.txt` file. If set to `true` then the default system `hosts.txt` file. `resolvconf` : Path to resolver configuration. If set to `true` then the default system resolvers are used. Otherwise root hints are used. `forward` : IP address of an upstream resolver(s) to use, a string or array of strings. `trusted` : DNSSEC root trust anchors, a string or array of strings. `trustfile` : Path to a file containing DNSSEC root trust anchors. Can be specified at compile-time (recommended for distributors). The built-in defaults are as follows: ``` lua local resolver = require"luaunbound".new({ async = true; hoststxt = true; resolvconf = true; }); ``` ### Context methods `ctx:resolve(name, type, class)` : Resolves name and returns a table with results. `ctx:resolve_async(callback, name, type, class)` : Starts a query in async mode. Results are passed to the callback when the query is completed. `ctx:fd()` : Returns a file descriptor that will appear readable when there are results available. `ctx:process()` : Calls callbacks for all completed queries. `ctx:wait()` : Blocks until all outstanding queries are completed and then calls callbacks for all completed queries. `ctx:poll()` : Returns `true` if new results are available. ### Result table The result table closely resembles libunbounds result struct. `qname`, `qtype` and `qclass` : Same as arguments to resolve methods. `canonname` : The canonical name if the queried name was a CNAME. Note that full CNAME chasing is done by libunbound. `rcode`, `havedata` and `nxdomain` : The DNS status code and flags indicating if any data is available. `secure` and `bogus` : Indicates DNSSEC validation status. There are three possible combinations: - Results are signed and validation succeeded, `secure` will be `true`. - Results are signed but validation failed, `secure` will be `false` and `bogus` will be a string with an error message. - The results were not signed. `secure` will be `false` and `bogus` will be `nil`. The actual result data will be in the array part of the result table, in the form of binary strings. It is your job to parse these into whatever form you want. luaunbound-1.0.0/lunbound.c0000644000000000000000000003377314072400636014023 0ustar 00000000000000/* Copyright (C) 2014-2021 - Kim Alvefur * * This file is MIT licensed. */ #include #include #include #if (LUA_VERSION_NUM == 501) #define lua_getuservalue(L, i) lua_getfenv(L, i) #define lua_setuservalue(L, i) lua_setfenv(L, i) #define lua_pcallk(L, nargs, nresults, errfunc, ctx, k) lua_pcall(L, nargs, nresults, errfunc) #define LUA_OK 0 #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif enum cb_state { cb_pending, cb_ready, cb_done }; typedef struct { int async_id; enum cb_state state; int err; struct ub_result *result; } cb_data; /* * Create a new context. * Takes an optional single table with options as argument. */ static int lub_new(lua_State *L) { int ret = 0; int i = 1; struct ub_ctx **ctx; /* Load table with default config if none given. */ if(lua_isnoneornil(L, 1)) { lua_settop(L, 0); luaL_getmetatable(L, "ub_default_config"); } else { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); } /* Create context and assign metatable. */ ctx = lua_newuserdata(L, sizeof(struct ub_ctx *)); *ctx = ub_ctx_create(); luaL_getmetatable(L, "ub_ctx"); lua_setmetatable(L, -2); /* Create table holding pending queries */ lua_createtable(L, 0, 1); lua_setuservalue(L, 2); /* Handle config table */ /* Enable threads? * ["async"] = true -- threads (default) * = false -- fork a process */ lua_getfield(L, 1, "async"); if(lua_isnil(L, -1)) { ret = ub_ctx_async(*ctx, 1); } else if(lua_isboolean(L, -1)) { ret = ub_ctx_async(*ctx, lua_toboolean(L, -1)); } else { luaL_argerror(L, 1, "'async' must be boolean"); } luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); lua_pop(L, 1); /* Path to resolv.conf * ["resolvconf"] = "/path/to/resolv.conf" * = true -- Use resolvers set by OS * = false -- Use root hints */ lua_getfield(L, 1, "resolvconf"); if(lua_isstring(L, -1)) { ret = ub_ctx_resolvconf(*ctx, (char *)lua_tostring(L, -1)); } else if(lua_isboolean(L, -1)) { if(lua_toboolean(L, -1)) { ret = ub_ctx_resolvconf(*ctx, NULL); } } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'resolvconf' must be string or boolean"); } /* else use root hits */ luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); lua_pop(L, 1); /* Path to hosts.txt * ["hoststxt"] = "/path/to/hosts.txt" * = true -- Use appropriate hosts.txt depending on OS */ lua_getfield(L, 1, "hoststxt"); if(lua_isstring(L, -1)) { ret = ub_ctx_hosts(*ctx, (char *)lua_tostring(L, -1)); } else if(lua_isboolean(L, -1)) { if(lua_toboolean(L, -1)) { ret = ub_ctx_hosts(*ctx, NULL); } } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'hoststxt' must be string or boolean"); } luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); lua_pop(L, 1); lua_getfield(L, 1, "forward"); if(lua_istable(L, -1)) { lua_rawgeti(L, -1, i++); while(ret == 0 && lua_isstring(L, -1)) { ret = ub_ctx_set_fwd(*ctx, (char *)lua_tostring(L, -1)); lua_pop(L, 1); lua_rawgeti(L, -1, i++); } lua_pop(L, 1); luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); i = 1; } else if(lua_isstring(L, -1)) { ret = ub_ctx_set_fwd(*ctx, (char *)lua_tostring(L, -1)); } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'forward' must be string or array"); } luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); lua_pop(L, 1); /* List of trust anchors * ["trusted"] = ". IN DS ..." -- Single string or array of strings */ lua_getfield(L, 1, "trusted"); if(lua_istable(L, -1)) { lua_rawgeti(L, -1, i++); while(ret == 0 && lua_isstring(L, -1)) { ret = ub_ctx_add_ta(*ctx, (char *)lua_tostring(L, -1)); lua_pop(L, 1); lua_rawgeti(L, -1, i++); } luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); } else if(lua_isstring(L, -1)) { ret = ub_ctx_add_ta(*ctx, (char *)lua_tostring(L, -1)); luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'trusted' must be string or array"); } lua_pop(L, 1); /* List of trust anchors * ["trustfile"] = "/usr/share/dns/root.ds" */ lua_getfield(L, 1, "trustfile"); if(lua_isstring(L, -1)) { ret = ub_ctx_add_ta_file(*ctx, (char *)lua_tostring(L, -1)); luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'trustfile' must be string"); } lua_pop(L, 1); /* Table of libunbound options */ lua_getfield(L, 1, "options"); if(lua_istable(L, -1)) { lua_pushnil(L); while(lua_next(L, -2) != 0) { ret = ub_ctx_set_option(*ctx, (char *)lua_tostring(L, -2), (char *)lua_tostring(L, -1)); luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); lua_pop(L, 1); } } else if(!lua_isnil(L, -1)) { luaL_argerror(L, 1, "'options' must be a table"); } lua_pop(L, 1); /* options table */ luaL_argcheck(L, ret == 0, 1, ub_strerror(ret)); return 1; } static int lub_ctx_destroy(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); lua_settop(L, 1); /* Cancel any outstanding queries so that they don't cause any interaction * with the ub_ctx after it has been freed. * * TODO Better to have queries cancel themselves on __gc? * They still need to reference the ub_ctx to ensure things gets collected in * a sane order. */ lua_getuservalue(L, 1); lua_pushnil(L); while(lua_next(L, 2) != 0) { lua_pop(L, 1); if(lua_type(L, 3) == LUA_TUSERDATA) { cb_data *my_data = luaL_checkudata(L, 3, "ub_query"); ub_cancel(*ctx, my_data->async_id); ub_resolve_free(my_data->result); my_data->state = cb_done; } } ub_ctx_delete(*ctx); return 0; } static int lub_ctx_tostring(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); lua_pushfstring(L, "ub_ctx: %p", ctx); return 1; } static int lub_query_tostring(lua_State *L) { cb_data *my_data = luaL_checkudata(L, 1, "ub_query"); char *state; switch(my_data->state) { case cb_pending: state = "pending"; break; case cb_ready: state = "ready"; break; case cb_done: state = "done"; break; default: state = "unknown"; break; } lua_pushfstring(L, "ub_query.%s(%d): %p", state, my_data->async_id, my_data); return 1; } /* * Get FD to watch in for readability in your event loop */ static int lub_ctx_getfd(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); lua_pushinteger(L, ub_fd(*ctx)); return 1; } /* * Turns ub_result into table */ static int lub_parse_result(lua_State *L, struct ub_result *result) { int i = 0; lua_createtable(L, 4, 10); lua_pushstring(L, result->qname); lua_setfield(L, -2, "qname"); lua_pushinteger(L, result->qtype); lua_setfield(L, -2, "qtype"); lua_pushinteger(L, result->qclass); lua_setfield(L, -2, "qclass"); lua_pushboolean(L, result->havedata); lua_setfield(L, -2, "havedata"); if(result->canonname) { lua_pushstring(L, result->canonname); lua_setfield(L, -2, "canonname"); } lua_pushboolean(L, result->nxdomain); lua_setfield(L, -2, "nxdomain"); /* Security status */ lua_pushboolean(L, result->secure); lua_setfield(L, -2, "secure"); if(result->bogus) { lua_pushstring(L, result->why_bogus); lua_setfield(L, -2, "bogus"); } lua_pushinteger(L, result->rcode); lua_setfield(L, -2, "rcode"); if(result->havedata) { while(result->len[i] > 0) { lua_pushlstring(L, result->data[i], result->len[i]); lua_rawseti(L, -2, ++i); } } lua_pushinteger(L, i); lua_setfield(L, -2, "n"); ub_resolve_free(result); return 1; } /* * Perform an synchronous lookup */ static int lub_resolve(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); struct ub_result *result; char *qname = (char *)luaL_checkstring(L, 2); int rrtype = luaL_optinteger(L, 3, 1); int rrclass = luaL_optinteger(L, 4, 1); int ret = ub_resolve(*ctx, qname, rrtype, rrclass, &result); if(ret != 0) { luaL_pushfail(L); lua_pushstring(L, ub_strerror(ret)); return 2; } return lub_parse_result(L, result); } /* * Callback for async queries */ void lub_callback(void *data, int err, struct ub_result *result) { cb_data *my_data = (cb_data *)data; my_data->err = err; my_data->result = err == 0 ? result : NULL; my_data->state = cb_ready; } /* * Start an asynchronous lookup */ static int lub_resolve_async(lua_State *L) { int ret, rrtype, rrclass; char *qname; cb_data *my_data; struct ub_ctx **ctx; lua_settop(L, 5); /* ub_ctx:resolve_async(callback, "example.net", rrtype, rrclass) */ ctx = luaL_checkudata(L, 1, "ub_ctx"); luaL_checktype(L, 2, LUA_TFUNCTION); qname = (char *)luaL_checkstring(L, 3); rrtype = luaL_optinteger(L, 4, 1); rrclass = luaL_optinteger(L, 5, 1); /* Structure with reference to Lua state */ my_data = (cb_data *)lua_newuserdata(L, sizeof(cb_data)); my_data->state = cb_pending; my_data->err = 1; my_data->result = NULL; luaL_getmetatable(L, "ub_query"); lua_setmetatable(L, -2); /* Start the query */ ret = ub_resolve_async(*ctx, qname, rrtype, rrclass, my_data, lub_callback, &my_data->async_id); if(ret != 0) { my_data->state = cb_done; luaL_pushfail(L); lua_pushstring(L, ub_strerror(ret)); return 2; } /* Anchor callback in cb_data so that it does not get garbage-collected * before we need it */ /* uservalue[my_data] = callback */ lua_getuservalue(L, 1); lua_pushvalue(L, 6); /* the cb_data userdata */ lua_pushvalue(L, 2); /* the callback */ lua_settable(L, -3); lua_pop(L, 1); /* return cb_data */ return 1; } /* * Cancel a query using the id returned from resolve_async */ static int lub_cancel(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); cb_data *my_data = luaL_checkudata(L, 2, "ub_query"); int ret = ub_cancel(*ctx, my_data->async_id); if(ret != 0) { luaL_pushfail(L); lua_pushstring(L, ub_strerror(ret)); return 2; } my_data->state = cb_done; lua_settop(L, 2); /* ub_ctx.uservalue[my_data] = nil */ lua_getuservalue(L, 1); lua_pushvalue(L, 2); lua_pushnil(L); lua_settable(L, 3); lua_pushboolean(L, 1); return 1; } #if LUA_VERSION_NUM < 503 #define lua_KContext ptrdiff_t #endif /* * Call callbacks */ static int lub_call_callbacks(lua_State *L); static int lub_call_callbacksk(lua_State *L, int status, __attribute__((unused)) lua_KContext ctx) { int count = 0; int msgh = 0; #if LUA_VERSION_NUM >= 503 #define SELF lub_call_callbacksk #else #define SELF lub_call_callbacks #endif luaL_checkudata(L, 1, "ub_ctx"); if(!lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TFUNCTION); msgh = 2; } if(status == LUA_YIELD) { /* * Arrange so that the for loop continues where it left off */ lua_settop(L, 4); } else { lua_settop(L, 2); lua_getuservalue(L, 1); lua_pushnil(L); } while(lua_next(L, 3) != 0) { if(lua_type(L, 4) == LUA_TUSERDATA && lua_type(L, 5) == LUA_TFUNCTION) { cb_data *my_data = luaL_checkudata(L, 4, "ub_query"); if(my_data->state == cb_ready) { my_data->state = cb_done; if(my_data->err != 0) { luaL_pushfail(L); lua_pushstring(L, ub_strerror(my_data->err)); } else { lub_parse_result(L, my_data->result); } lua_pushvalue(L, 4); /* my_data */ lua_pushnil(L); lua_settable(L, 3); /* ub_ctx.uservalue[my_data] = nil */ if(lua_pcallk(L, my_data->err == 0 ? 1 : 2, 0, msgh, 0, SELF) != 0) { luaL_pushfail(L); lua_insert(L, 5); return 2; } lua_settop(L, 3); count++; } } lua_settop(L, 4); } #undef SELF lua_pushinteger(L, count); return 1; } static int lub_call_callbacks(lua_State *L) { return lub_call_callbacksk(L, LUA_OK, 0); } /* * Process all completed queries and call their callbacks */ static int lub_process(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); ub_process(*ctx); /* calls lub_callback for each completed query */ return lub_call_callbacks(L); } /* * Wait for all queries to complete and call all callbacks */ static int lub_wait(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); ub_wait(*ctx); return lub_call_callbacks(L); } /* * Check if context has new results to process */ static int lub_poll(lua_State *L) { struct ub_ctx **ctx = luaL_checkudata(L, 1, "ub_ctx"); lua_pushboolean(L, ub_poll(*ctx)); return 1; } /* * Context metatable */ static luaL_Reg ctx_mt[] = { {"__gc", lub_ctx_destroy}, {"__tostring", lub_ctx_tostring}, {NULL, NULL} }; /* * Context methods */ static luaL_Reg ctx_methods[] = { {"getfd", lub_ctx_getfd}, {"resolve", lub_resolve}, {"resolve_async", lub_resolve_async}, {"cancel", lub_cancel}, {"process", lub_process}, {"wait", lub_wait}, {"poll", lub_poll}, {NULL, NULL} }; /* * Query metatable */ static luaL_Reg query_mt[] = { {"__tostring", lub_query_tostring}, {NULL, NULL} }; /* * Exported module functions */ static luaL_Reg lub_lib_funcs[] = { {"new", lub_new}, {NULL, NULL} }; #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif int luaopen_lunbound(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif /* Metatable for contexts */ luaL_newmetatable(L, "ub_ctx"); luaL_setfuncs(L, ctx_mt, 0); lua_createtable(L, 0, 7); luaL_setfuncs(L, ctx_methods, 0); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* Metatable for queries */ luaL_newmetatable(L, "ub_query"); luaL_setfuncs(L, query_mt, 0); lua_pop(L, 1); /* Main module table */ lua_createtable(L, 0, 2); luaL_setfuncs(L, lub_lib_funcs, 0); lua_pushliteral(L, "1.0.0"); lua_setfield(L, -2, "_VERSION"); lua_pushstring(L, ub_version()); lua_setfield(L, -2, "_LIBVER"); /* Defaults */ luaL_newmetatable(L, "ub_default_config"); { /* Threads enabled */ lua_pushboolean(L, 1); lua_setfield(L, -2, "async"); /* Use system resolv.conf */ lua_pushboolean(L, 1); lua_setfield(L, -2, "resolvconf"); /* Use system hosts.txt */ lua_pushboolean(L, 1); lua_setfield(L, -2, "hoststxt"); #ifdef IANA_ROOT_TA /* Hardcoded root */ lua_pushliteral(L, IANA_ROOT_TA); lua_setfield(L, -2, "trusted"); #endif #ifdef IANA_ROOT_TA_FILE lua_pushliteral(L, IANA_ROOT_TA_FILE); lua_setfield(L, -2, "trustfile"); #endif } lua_setfield(L, -2, "config"); return 1; }