]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-dns, lib-lua: Add initial support for using lib-dns via Lua
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 1 Apr 2022 12:07:04 +0000 (15:07 +0300)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Mon, 25 Apr 2022 09:07:11 +0000 (09:07 +0000)
For now only dns_client:lookup(host) is implemented. The dns_client is
expected to be provided by the calling C code.

src/lib-dns/Makefile.am
src/lib-dns/dns-lua.c [new file with mode: 0644]
src/lib-dns/dns-lua.h [new file with mode: 0644]
src/lib-lua/Makefile.am
src/lib-lua/test-dns-lua.c [new file with mode: 0644]

index b40cf72d82657b49ca9aa5a91e5e2218af89ef6b..3b5c2616cbb6fd50cb57c8620275498ab18dcc23 100644 (file)
@@ -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 (file)
index 0000000..0ed7c43
--- /dev/null
@@ -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 (file)
index 0000000..80259ce
--- /dev/null
@@ -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
index 34164892fa905ddfaac915c7de7b1bb95d739878..783a1749eb736a3915b72db0e481aa1603b9b50f 100644 (file)
@@ -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 (file)
index 0000000..fd43dd0
--- /dev/null
@@ -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);
+}