From: Timo Sirainen Date: Fri, 1 Apr 2022 12:07:04 +0000 (+0300) Subject: lib-dns, lib-lua: Add initial support for using lib-dns via Lua X-Git-Tag: 2.4.0~4119 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a4e3ea5089400ab2037d3bdd0daa0ed351c1be46;p=thirdparty%2Fdovecot%2Fcore.git lib-dns, lib-lua: Add initial support for using lib-dns via Lua For now only dns_client:lookup(host) is implemented. The dns_client is expected to be provided by the calling C code. --- diff --git a/src/lib-dns/Makefile.am b/src/lib-dns/Makefile.am index b40cf72d82..3b5c2616cb 100644 --- a/src/lib-dns/Makefile.am +++ b/src/lib-dns/Makefile.am @@ -37,3 +37,22 @@ check-local: pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) + +# Internally, the dns methods yield via lua_yieldk() as implemented in Lua +# 5.3 and newer. +if DLUA_WITH_YIELDS +noinst_LTLIBRARIES += libdns_lua.la + +libdns_lua_la_SOURCES = \ + dns-lua.c +libdns_lua_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LUA_CFLAGS) \ + -I$(top_srcdir)/src/lib-lua +libdns_lua_la_LIBADD = +libdns_lua_la_DEPENDENCIES = \ + libdns.la + +headers += \ + dns-lua.h +endif diff --git a/src/lib-dns/dns-lua.c b/src/lib-dns/dns-lua.c new file mode 100644 index 0000000000..0ed7c43494 --- /dev/null +++ b/src/lib-dns/dns-lua.c @@ -0,0 +1,115 @@ +/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "dns-lookup.h" +#include "dlua-script-private.h" +#include "dlua-wrapper.h" +#include "dns-lua.h" + +struct lua_dns_lookup { + lua_State *L; + bool resume; +}; + +static int lua_dns_client_lookup(lua_State *); + +static luaL_Reg lua_dns_client_methods[] = { + { "lookup", lua_dns_client_lookup }, + { NULL, NULL }, +}; + +/* no actual ref counting */ +static void lua_dns_client_unref(struct dns_client *client ATTR_UNUSED) +{ +} + +DLUA_WRAP_C_DATA(dns_client, struct dns_client, + lua_dns_client_unref, lua_dns_client_methods); + +static int +lua_dns_client_async_continue(lua_State *L, int status ATTR_UNUSED, + lua_KContext ctx ATTR_UNUSED) +{ + if (lua_isnil(L, -1)) + return 3; + else + return 1; +} + +static void +lua_dns_client_lookup_callback(const struct dns_lookup_result *result, + struct lua_dns_lookup *lua_lookup) +{ + lua_State *L = lua_lookup->L; + + if (result->ret != 0) { + lua_pushnil(L); + lua_pushstring(L, result->error); + lua_pushinteger(L, result->ret); + } else { + lua_newtable(L); + for (unsigned int i = 0; i < result->ips_count; i++) { + lua_pushstring(L, net_ip2addr(&result->ips[i])); + lua_seti(L, -2, i + 1); + } + } + + if (lua_lookup->resume) + dlua_pcall_yieldable_resume(L, 1); + i_free(lua_lookup); +} + +/* Lookup dns record [-(2|3),+(1|3),e] + + Args: + 1) userdata: struct dns_client *dns_client + 2) string: hostname + 3) optional event + + Returns: + + On successful DNS lookup, returns a table with IP addresses (which has at + least one IP). + + On failure, returns nil, error string, net_gethosterror() compatible error + code (similar to e.g. Lua io.* calls). + */ +static int lua_dns_client_lookup(lua_State *L) +{ + struct dns_client *client; + const char *host; + struct event *event = NULL; + + DLUA_REQUIRE_ARGS_IN(L, 2, 3); + + client = xlua_dns_client_getptr(L, 1, NULL); + host = luaL_checkstring(L, 2); + if (lua_gettop(L) >= 3) + event = dlua_check_event(L, 3); + + struct lua_dns_lookup *lua_lookup = + i_new(struct lua_dns_lookup, 1); + lua_lookup->L = L; + struct dns_lookup *lookup; + if (dns_client_lookup(client, host, event, + lua_dns_client_lookup_callback, lua_lookup, + &lookup) < 0) { + /* return values are pushed to stack by the callback */ + return 3; + } + lua_lookup->resume = TRUE; + + return lua_dns_client_async_continue(L, + lua_yieldk(L, 0, 0, lua_dns_client_async_continue), 0); +} + +void dlua_push_dns_client(lua_State *L, struct dns_client *client) +{ + xlua_pushdns_client(L, client, FALSE); +} + +struct dns_client *dlua_check_dns_client(lua_State *L, int idx) +{ + return xlua_dns_client_getptr(L, idx, NULL); +} diff --git a/src/lib-dns/dns-lua.h b/src/lib-dns/dns-lua.h new file mode 100644 index 0000000000..80259ce668 --- /dev/null +++ b/src/lib-dns/dns-lua.h @@ -0,0 +1,15 @@ +#ifndef DNS_LUA_H +#define DNS_LUA_H + +struct dns_client; + +#ifdef DLUA_WITH_YIELDS +/* Internally, the dns methods yield via lua_yieldk() as implemented in Lua + 5.3 and newer. */ + +void dlua_push_dns_client(lua_State *L, struct dns_client *cliet); +struct dns_client *dlua_check_dns_client(lua_State *L, int idx); + +#endif + +#endif diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am index 34164892fa..783a1749eb 100644 --- a/src/lib-lua/Makefile.am +++ b/src/lib-lua/Makefile.am @@ -2,6 +2,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-master \ @@ -23,8 +24,12 @@ test_programs = test-lua LIBDICT_LUA= if DLUA_WITH_YIELDS -LIBDICT_LUA += ../lib-dict/libdict_lua.la -test_programs += test-dict-lua +LIBDICT_LUA += \ + ../lib-dict/libdict_lua.la \ + ../lib-dns/libdns_lua.la +test_programs += \ + test-dict-lua \ + test-dns-lua endif # Note: the only things this lib should depend on are libdovecot and lua. @@ -62,6 +67,10 @@ test_dict_lua_SOURCES = test-dict-lua.c test_dict_lua_LDADD = $(test_libs) $(LUA_LIBS) test_dict_lua_DEPENDENCIES = $(test_libs) +test_dns_lua_SOURCES = test-dns-lua.c +test_dns_lua_LDADD = $(test_libs) $(LUA_LIBS) +test_dns_lua_DEPENDENCIES = $(test_libs) + check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ diff --git a/src/lib-lua/test-dns-lua.c b/src/lib-lua/test-dns-lua.c new file mode 100644 index 0000000000..fd43dd036c --- /dev/null +++ b/src/lib-lua/test-dns-lua.c @@ -0,0 +1,191 @@ +/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "istream.h" +#include "ioloop.h" +#include "write-full.h" +#include "dlua-script-private.h" +#include "dns-lookup.h" +#include "dns-lua.h" +#include "test-common.h" + +#define TEST_DNS_SERVER_SOCKET_PATH ".test-dns-server" + +static struct ioloop *ioloop; +static struct io *io_client = NULL; +static struct istream *input_client = NULL; + +static void test_dns_finished(lua_State *L, struct ioloop *ioloop, int res) +{ + if (res < 0) { + i_error("%s", lua_tostring(L, -1)); + lua_pop(L, 1); + } + io_loop_stop(ioloop); +} + +static void test_dns_lua_init(void) +{ + ioloop = io_loop_create(); +} + +static void test_dns_lua_deinit(void) +{ + io_loop_destroy(&ioloop); +} + +static void test_dns_lua_common(const char *luascript) +{ + const struct dns_lookup_settings set = { + .dns_client_socket_path = TEST_DNS_SERVER_SOCKET_PATH, + .timeout_msecs = 1000, + }; + + struct dns_client *client = dns_client_init(&set); + + struct dlua_script *script; + const char *error; + if (dlua_script_create_string(luascript, &script, NULL, &error) < 0) + i_fatal("dlua_script_create_string() failed: %s", error); + if (dlua_script_init(script, &error) < 0) + i_fatal("dlua_script_init() failed: %s", error); + + lua_State *thread = dlua_script_new_thread(script); + dlua_push_dns_client(thread, client); + if (dlua_pcall_yieldable(thread, "test_dns", 1, test_dns_finished, + ioloop, &error) < 0) + i_fatal("dlua_pcall() failed: %s", error); + io_loop_run(ioloop); + i_assert(lua_gettop(thread) == 0); + dlua_script_close_thread(script, &thread); + + dlua_script_unref(&script); + dns_client_deinit(&client); +} + +static void test_dns_server_close(struct istream *input) +{ + i_assert(input == input_client); + input_client = NULL; + + i_stream_unref(&input); + io_remove(&io_client); +} + +static int test_dns_server_input_line(struct istream *input) +{ + const char *line = i_stream_read_next_line(input); + if (line == NULL) { + if (input->eof) + return -1; + return 0; + } + const char *host, *reply; + if (str_begins_with(line, "VERSION\t")) { + /* ignore */ + return 1; + } else if (str_begins(line, "IP\t", &host)) { + if (strcmp(host, "localhost") == 0) + reply = "0\t127.0.0.1\t127.0.0.2\n"; + else + reply = "-4\tUnknown host\n"; + if (write_full(i_stream_get_fd(input), reply, strlen(reply)) < 0) { + i_error("write(dns-client) failed: %m"); + return -1; + } + return 1; + } else { + i_error("unknown input: %s", line); + return -1; + } +} + +static void test_dns_server_input(struct istream *input) +{ + int ret; + + while ((ret = test_dns_server_input_line(input)) > 0) ; + if (ret < 0) + test_dns_server_close(input); +} + +static void test_dns_server_listen(int *fd_listenp) +{ + int fd_client = net_accept(*fd_listenp, NULL, NULL); + if (fd_client < 0) { + i_error("net_accept(dns-client) failed: %m"); + return; + } + net_set_nonblock(fd_client, TRUE); + + const char *handshake = "VERSION\tdns\t1\t0\n"; + if (write_full(fd_client, handshake, strlen(handshake)) < 0) { + i_error("write(dns-client) failed: %m"); + i_close_fd(&fd_client); + return; + } + + i_assert(io_client == NULL); + i_assert(input_client == NULL); + input_client = i_stream_create_fd_autoclose(&fd_client, 1024); + io_client = io_add_istream(input_client, test_dns_server_input, + input_client); +} + +static void test_dns_lua(void) +{ + static const char *luascript = +"function test_dns(client)\n" +" local arr, error, errno = client:lookup('localhost')\n" +" assert(#arr == 2)\n" +" assert(arr[1] == '127.0.0.1')\n" +" assert(arr[2] == '127.0.0.2')\n" +"end\n"; + test_begin("dns lua lookup"); + + i_unlink_if_exists(TEST_DNS_SERVER_SOCKET_PATH); + int fd_listen = net_listen_unix(TEST_DNS_SERVER_SOCKET_PATH, 1); + if (fd_listen == -1) { + i_fatal("net_listen_unix(%s) failed: %m", + TEST_DNS_SERVER_SOCKET_PATH); + } + struct io *io = io_add(fd_listen, IO_READ, + test_dns_server_listen, &fd_listen); + + test_dns_lua_common(luascript); + + io_remove(&io); + io_remove(&io_client); + i_stream_destroy(&input_client); + i_close_fd(&fd_listen); + i_unlink(TEST_DNS_SERVER_SOCKET_PATH); + test_end(); +} + +static void test_dns_lua_error(void) +{ + static const char *luascript = +"function test_dns(client)\n" +" local arr, error, errno = client:lookup('localhost')\n" +" assert(arr == nil)\n" +" assert(error == 'Failed to connect to "TEST_DNS_SERVER_SOCKET_PATH": No such file or directory')\n" +" assert(errno ~= 0)\n" +"end\n"; + test_begin("dns lua errors"); + i_unlink_if_exists(TEST_DNS_SERVER_SOCKET_PATH); + test_dns_lua_common(luascript); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_dns_lua_init, + test_dns_lua, + test_dns_lua_error, + test_dns_lua_deinit, + NULL + }; + return test_run(test_functions); +}