commit 4718bae098d4cb9bbba6893c3b408c1feef37560
parent 2f5d274059dcb6a55b4c5b7014b7064ffe31f716
Author: Joris Vink <joris@coders.se>
Date:   Sat, 21 Jan 2023 23:41:35 +0100
Initial lua runtime.
Works enough so one can do basic configuration and handle HTTP.
Diffstat:
10 files changed, 763 insertions(+), 21 deletions(-)
diff --git a/Makefile b/Makefile
@@ -117,6 +117,16 @@ ifneq ("$(PYTHON)", "")
 	FEATURES_INC+=$(KORE_PYTHON_INC)
 endif
 
+ifneq ("$(LUA)", "")
+	S_SRC+=src/lua.c
+	KORE_LUA_LIB?=$(shell pkg-config --libs lua)
+	KORE_LUA_INC?=$(shell pkg-config --cflags lua)
+	LDFLAGS+=$(KORE_LUA_LIB)
+	CFLAGS+=$(KORE_LUA_INC) -DKORE_USE_LUA
+	FEATURES+=-DKORE_USE_LUA
+	FEATURES_INC+=$(KORE_LUA_INC)
+endif
+
 OSNAME=$(shell uname -s | sed -e 's/[-_].*//g' | tr A-Z a-z)
 ifeq ("$(OSNAME)", "freebsd")
 	KORE_CURL_LIB=-L/usr/local/lib -lcurl
diff --git a/include/kore/kore.h b/include/kore/kore.h
@@ -274,6 +274,7 @@ extern struct connection_list	disconnected;
 
 #define KORE_RUNTIME_NATIVE	0
 #define KORE_RUNTIME_PYTHON	1
+#define KORE_RUNTIME_LUA	2
 
 struct kore_runtime {
 	int	type;
@@ -413,8 +414,9 @@ struct kore_auth {
 #define KORE_MODULE_LOAD	1
 #define KORE_MODULE_UNLOAD	2
 
-#define KORE_MODULE_NATIVE	0
-#define KORE_MODULE_PYTHON	1
+#define KORE_MODULE_NATIVE	KORE_RUNTIME_NATIVE
+#define KORE_MODULE_PYTHON	KORE_RUNTIME_PYTHON
+#define KORE_MODULE_LUA		KORE_RUNTIME_LUA
 
 struct kore_module;
 
@@ -894,13 +896,10 @@ const char		*kore_connection_ip(struct connection *);
 void		kore_log_init(void);
 void		kore_log_file(const char *);
 
-#if defined(KORE_USE_PYTHON)
-int		kore_configure_setting(const char *, char *);
-#endif
-
 /* config.c */
 void		kore_parse_config(void);
 void		kore_parse_config_file(FILE *);
+int		kore_configure_setting(const char *, char *);
 
 /* mem.c */
 void		*kore_malloc(size_t);
diff --git a/include/kore/lua_api.h b/include/kore/lua_api.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __H_LUA_API_H
+#define __H_LUA_API_H
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+void		kore_lua_init(void);
+void		kore_lua_cleanup(void);
+
+extern struct kore_module_functions	kore_lua_module;
+extern struct kore_runtime		kore_lua_runtime;
+
+#endif
diff --git a/include/kore/lua_methods.h b/include/kore/lua_methods.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2023 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __H_LUA_METHODS_H
+#define __H_LUA_METHODS_H
+
+static int		lua_http_request_gc(lua_State *);
+static int		lua_http_request_index(lua_State *);
+
+static int		lua_http_response(lua_State *);
+static int		lua_http_request_header(lua_State *);
+static int		lua_http_response_header(lua_State *);
+
+static const luaL_Reg lua_http_request_meta[] = {
+	{ "__gc",			lua_http_request_gc },
+	{ "__index",			lua_http_request_index },
+	{ NULL, 			NULL },
+};
+
+static const luaL_Reg lua_http_request_methods[] = {
+	{ "response",			lua_http_response },
+	{ "request_header",		lua_http_request_header },
+	{ "response_header",		lua_http_response_header },
+	{ NULL, 			NULL },
+};
+
+static int		lua_kore_config(lua_State *);
+static int		lua_kore_server(lua_State *);
+
+static const luaL_Reg lua_kore_functions[] = {
+	{ "config",			lua_kore_config },
+	{ "server",			lua_kore_server },
+	{ NULL, 			NULL },
+};
+
+#endif
diff --git a/src/config.c b/src/config.c
@@ -93,6 +93,7 @@ static int		configure_socket_backlog(char *);
 static int		configure_privsep_skip(char *);
 static int		configure_privsep_root(char *);
 static int		configure_privsep_runas(char *);
+static int		configure_deployment(char *);
 
 #if defined(KORE_USE_PLATFORM_PLEDGE)
 static int		configure_add_pledge(char *);
@@ -157,7 +158,6 @@ static int		configure_task_threads(char *);
 #endif
 
 #if defined(KORE_USE_PYTHON)
-static int		configure_deployment(char *);
 static int		configure_python_path(char *);
 static int		configure_python_import(char *);
 #endif
@@ -240,6 +240,7 @@ static struct {
 	{ "tls_cipher",			configure_tls_cipher },
 	{ "tls_dhparam",		configure_tls_dhparam },
 	{ "rand_file",			configure_rand_file },
+	{ "deployment",			configure_deployment },
 #if defined(KORE_USE_ACME)
 	{ "acme_email",			configure_acme_email },
 	{ "acme_provider",		configure_acme_provider },
@@ -269,9 +270,6 @@ static struct {
 	{ "websocket_maxframe",		configure_websocket_maxframe },
 	{ "websocket_timeout",		configure_websocket_timeout },
 #endif
-#if defined(KORE_USE_PYTHON)
-	{ "deployment",			configure_deployment },
-#endif
 #if defined(KORE_USE_PGSQL)
 	{ "pgsql_conn_max",		configure_pgsql_conn_max },
 	{ "pgsql_queue_limit",		configure_pgsql_queue_limit },
@@ -524,7 +522,6 @@ kore_parse_config_file(FILE *fp)
 	}
 }
 
-#if defined(KORE_USE_PYTHON)
 int
 kore_configure_setting(const char *name, char *value)
 {
@@ -544,7 +541,6 @@ kore_configure_setting(const char *name, char *value)
 	kore_log(LOG_NOTICE, "ignoring unknown kore.config.%s setting", name);
 	return (KORE_RESULT_OK);
 }
-#endif
 
 static void
 configure_check_var(char **var, const char *other, const char *logmsg)
@@ -2015,7 +2011,6 @@ configure_task_threads(char *option)
 }
 #endif
 
-#if defined(KORE_USE_PYTHON)
 static int
 configure_deployment(char *value)
 {
@@ -2040,6 +2035,7 @@ configure_deployment(char *value)
 	return (KORE_RESULT_OK);
 }
 
+#if defined(KORE_USE_PYTHON)
 static int
 configure_python_path(char *path)
 {
diff --git a/src/kore.c b/src/kore.c
@@ -46,6 +46,10 @@
 #include "python_api.h"
 #endif
 
+#if defined(KORE_USE_LUA)
+#include "lua_api.h"
+#endif
+
 #if defined(KORE_USE_ACME)
 #include "acme.h"
 #endif
@@ -97,11 +101,11 @@ static const char	*parent_daemonized_hook = KORE_DAEMONIZED_HOOK;
 static void
 usage(void)
 {
-#if defined(KORE_USE_PYTHON)
-	printf("Usage: %s [options] [app | app.py]\n", __progname);
-#else
-	printf("Usage: %s [options]\n", __progname);
-#endif
+	if (kore_runtime_count() > 0) {
+		printf("Usage: %s [options] [app | script]\n", __progname);
+	} else {
+		printf("Usage: %s [options]\n", __progname);
+	}
 
 	printf("\n");
 	printf("Command-line options:\n");
@@ -151,6 +155,10 @@ version(void)
 #if defined(KORE_USE_PYTHON)
 	printf("python-%s ", PY_VERSION);
 #endif
+#if defined(KORE_USE_LUA)
+	printf("lua-%s.%s.%s ",
+	    LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
+#endif
 #if defined(KORE_USE_ACME)
 	printf("acme ");
 #endif
@@ -234,8 +242,8 @@ main(int argc, char *argv[])
 	kore_domain_init();
 	kore_module_init();
 
-#if !defined(KORE_SINGLE_BINARY) && !defined(KORE_USE_PYTHON)
-	if (config_file == NULL)
+#if !defined(KORE_SINGLE_BINARY)
+	if (kore_runtime_count() == 0 && config_file == NULL)
 		usage();
 #endif
 	kore_module_load(NULL, NULL, KORE_MODULE_NATIVE);
@@ -244,6 +252,10 @@ main(int argc, char *argv[])
 	kore_python_init();
 #endif
 
+#if defined(KORE_USE_LUA)
+	kore_lua_init();
+#endif
+
 #if !defined(KORE_SINGLE_BINARY)
 	if (kore_runtime_count() > 0 && rarg0 != NULL)
 		kore_runtime_resolve(rarg0, &st);
@@ -294,6 +306,10 @@ main(int argc, char *argv[])
 	kore_python_cleanup();
 #endif
 
+#if defined(KORE_USE_LUA)
+	kore_lua_cleanup();
+#endif
+
 	kore_mem_cleanup();
 
 	return (kore_quit);
diff --git a/src/lua.c b/src/lua.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (c) 2023 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include "kore.h"
+
+#if !defined(KORE_NO_HTTP)
+#include "http.h"
+#endif
+
+#include "lua_api.h"
+#include "lua_methods.h"
+
+struct lua_http_request {
+	struct http_request	*req;
+};
+
+struct lua_symbol {
+	lua_State		*L;
+	int			ref;
+	LIST_ENTRY(lua_symbol)	list;
+};
+
+struct lua_module {
+	lua_State			*L;
+	LIST_HEAD(, lua_symbol)		symbols;
+};
+
+static int	lua_runtime_resolve(const char *, const struct stat *);
+static int	lua_runtime_http_request(void *, struct http_request *);
+static void	lua_runtime_http_request_free(void *, struct http_request *);
+static void	lua_runtime_http_body_chunk(void *, struct http_request *,
+		    const void *, size_t);
+static int	lua_runtime_validator(void *, struct http_request *,
+		    const void *);
+static void	lua_runtime_wsmessage(void *, struct connection *,
+		    u_int8_t, const void *, size_t);
+static void	lua_runtime_execute(void *);
+static int	lua_runtime_onload(void *, int);
+static void	lua_runtime_signal(void *, int);
+static void	lua_runtime_configure(void *, int, char **);
+static void	lua_runtime_connect(void *, struct connection *);
+
+static void	lua_module_load(struct kore_module *);
+static void	lua_module_free(struct kore_module *);
+static void	lua_module_reload(struct kore_module *);
+static void	*lua_module_getsym(struct kore_module *, const char *);
+
+static void	*lua_mem_alloc(void *, void *, size_t, size_t);
+
+static int	lua_kore_module_init(lua_State *);
+static void	lua_symbol_resolve(struct lua_symbol *, lua_State **);
+
+static int		lua_argument_get_bool(lua_State *, const char *);
+static const char	*lua_argument_get_string(lua_State *, const char *);
+//static int		lua_argument_get_integer(lua_State *, const char *,
+//			    lua_Integer *);
+
+struct kore_module_functions kore_lua_module = {
+	.free = lua_module_free,
+	.load = lua_module_load,
+	.getsym = lua_module_getsym,
+	.reload = lua_module_reload
+};
+
+struct kore_runtime kore_lua_runtime = {
+	KORE_RUNTIME_LUA,
+	.resolve = lua_runtime_resolve,
+	.http_request = lua_runtime_http_request,
+	.http_body_chunk = lua_runtime_http_body_chunk,
+	.http_request_free = lua_runtime_http_request_free,
+	.validator = lua_runtime_validator,
+	.wsconnect = lua_runtime_connect,
+	.wsmessage = lua_runtime_wsmessage,
+	.wsdisconnect = lua_runtime_connect,
+	.onload = lua_runtime_onload,
+	.signal = lua_runtime_signal,
+	.connect = lua_runtime_connect,
+	.execute = lua_runtime_execute,
+	.configure = lua_runtime_configure,
+};
+
+#define LUA_CONSTANT(x)		{ #x, x }
+
+static struct {
+	const char		*symbol;
+	int			value;
+} lua_integers[] = {
+	LUA_CONSTANT(LOG_ERR),
+	LUA_CONSTANT(LOG_INFO),
+	LUA_CONSTANT(LOG_NOTICE),
+	LUA_CONSTANT(HTTP_METHOD_GET),
+	LUA_CONSTANT(HTTP_METHOD_PUT),
+	LUA_CONSTANT(HTTP_METHOD_POST),
+	LUA_CONSTANT(HTTP_METHOD_HEAD),
+	LUA_CONSTANT(HTTP_METHOD_PATCH),
+	LUA_CONSTANT(HTTP_METHOD_DELETE),
+	LUA_CONSTANT(HTTP_METHOD_OPTIONS),
+	{ NULL, -1 },
+};
+
+void
+kore_lua_init(void)
+{
+	if (!kore_configure_setting("deployment", "dev"))
+		fatal("failed to set initial deployment to dev");
+}
+
+void
+kore_lua_cleanup(void)
+{
+}
+
+static void *
+lua_mem_alloc(void *uptr, void *ptr, size_t osize, size_t nsize)
+{
+	if (nsize == 0) {
+		kore_free(ptr);
+		return (NULL);
+	}
+
+	return (kore_realloc(ptr, nsize));
+}
+
+static void
+lua_symbol_resolve(struct lua_symbol *sym, lua_State **L)
+{
+	lua_rawgeti(sym->L, LUA_REGISTRYINDEX, sym->ref);
+	*L = sym->L;
+}
+
+#if 0
+static int
+lua_argument_get_integer(lua_State *L, const char *field, lua_Integer *ret)
+{
+	int		type;
+
+	lua_pushstring(L, field);
+	type = lua_gettable(L, -2);
+
+	if (type == LUA_TNIL) {
+		lua_pop(L, 1);
+		return (KORE_RESULT_ERROR);
+	}
+
+	luaL_argcheck(L, type == LUA_TNUMBER, 0, field);
+
+	*ret = lua_tointeger(L, -1);
+	lua_pop(L, 1);
+
+	return (KORE_RESULT_OK);
+}
+#endif
+
+static int
+lua_argument_get_bool(lua_State *L, const char *field)
+{
+	int		ret;
+
+	lua_pushstring(L, field);
+	ret = lua_gettable(L, -2);
+
+	if (ret == LUA_TNIL) {
+		lua_pop(L, 1);
+		return (0);
+	}
+
+	luaL_argcheck(L, ret == LUA_TBOOLEAN, 0, field);
+
+	ret = lua_toboolean(L, -1);
+	lua_pop(L, 1);
+
+	return (ret);
+}
+
+static const char *
+lua_argument_get_string(lua_State *L, const char *field)
+{
+	const char	*v;
+	int		type;
+
+	lua_pushstring(L, field);
+	type = lua_gettable(L, -2);
+
+	if (type == LUA_TNIL) {
+		lua_pop(L, 1);
+		return (NULL);
+	}
+
+	luaL_argcheck(L, type == LUA_TSTRING, 0, field);
+
+	v = lua_tostring(L, -1);
+	lua_pop(L, 1);
+
+	return (v);
+}
+
+static int
+lua_kore_module_init(lua_State *L)
+{
+	int		i;
+
+	luaL_newlib(L, lua_kore_functions);
+
+	for (i = 0; lua_integers[i].symbol != NULL; i++) {
+		lua_pushstring(L, lua_integers[i].symbol);
+		lua_pushnumber(L, lua_integers[i].value);
+		lua_settable(L, -3);
+	}
+
+	return (1);
+}
+
+static void
+lua_module_free(struct kore_module *module)
+{
+	struct lua_symbol	*sym;
+	struct lua_module	*lua;
+
+	lua = module->handle;
+
+	while ((sym = LIST_FIRST(&lua->symbols)) != NULL) {
+		LIST_REMOVE(sym, list);
+		kore_free(sym);
+	}
+
+	kore_free(lua);
+}
+
+static void
+lua_module_reload(struct kore_module *module)
+{
+	lua_module_free(module);
+	lua_module_load(module);
+}
+
+static void
+lua_module_load(struct kore_module *module)
+{
+	struct lua_module	*lua;
+
+	lua = kore_calloc(1, sizeof(*lua));
+	LIST_INIT(&lua->symbols);
+
+	if ((lua->L = lua_newstate(lua_mem_alloc, NULL)) == NULL)
+		fatal("luaL_newstate");
+
+	luaL_openlibs(lua->L);
+
+	luaL_requiref(lua->L, "kore", lua_kore_module_init, 1);
+	lua_pop(lua->L, 1);
+
+	luaL_newmetatable(lua->L, "http_request");
+	luaL_setfuncs(lua->L, lua_http_request_meta, 0);
+	lua_pop(lua->L, 1);
+
+	lua_pushliteral(lua->L, "http_request_methods");
+	luaL_newlib(lua->L, lua_http_request_methods);
+	lua_settable(lua->L, LUA_REGISTRYINDEX);
+
+	luaL_newlib(lua->L, lua_http_request_methods);
+	lua_pop(lua->L, 1);
+
+	if (luaL_loadfile(lua->L, module->path) != LUA_OK) {
+		fatal("%s: failed to import module (%s)", module->path,
+		    lua_tostring(lua->L, -1));
+	}
+
+	if (lua_pcall(lua->L, 0, 0, 0) != LUA_OK) {
+		fatal("%s: failed to import module (%s)", module->path,
+		    lua_tostring(lua->L, -1));
+	}
+
+	module->handle = lua;
+}
+
+static void *
+lua_module_getsym(struct kore_module *module, const char *symbol)
+{
+	int			ref;
+	struct lua_module	*lua;
+	struct lua_symbol	*sym;
+
+	lua = module->handle;
+
+	if (lua_getglobal(lua->L, symbol) != LUA_TFUNCTION)
+		return (NULL);
+
+	if ((ref = luaL_ref(lua->L, LUA_REGISTRYINDEX)) == LUA_REFNIL)
+		return (NULL);
+
+	sym = kore_calloc(1, sizeof(*sym));
+
+	sym->ref = ref;
+	sym->L = lua->L;
+
+	LIST_INSERT_HEAD(&lua->symbols, sym, list);
+
+	return (sym);
+}
+
+static int
+lua_runtime_resolve(const char *module, const struct stat *st)
+{
+	const char	*ext;
+
+	if (!S_ISREG(st->st_mode))
+		return (KORE_RESULT_ERROR);
+
+	ext = strrchr(module, '.');
+
+	if (ext == NULL || strcasecmp(ext, ".lua"))
+		return (KORE_RESULT_ERROR);
+
+	kore_module_load(module, NULL, KORE_MODULE_LUA);
+
+	return (KORE_RESULT_OK);
+}
+
+static int
+lua_runtime_http_request(void *addr, struct http_request *req)
+{
+	lua_State			*L;
+	struct lua_http_request		*lreq;
+
+	lua_symbol_resolve(addr, &L);
+
+	lreq = lua_newuserdata(L, sizeof(*lreq));
+	luaL_setmetatable(L, "http_request");
+
+	lreq->req = req;
+
+	if (lua_pcall(L, 1, 0, 0)) {
+		kore_log(LOG_NOTICE, "%s: failed to call handler: %s", __func__,
+		    lua_tostring(L, -1));
+		http_response(req, 500, NULL, 0);
+		return (KORE_RESULT_OK);
+	}
+
+	return (KORE_RESULT_OK);
+}
+
+static void
+lua_runtime_http_request_free(void *addr, struct http_request *req)
+{
+	fatal("%s: not yet implemented", __func__);
+}
+
+static void
+lua_runtime_http_body_chunk(void *addr, struct http_request *req,
+    const void *data, size_t len)
+{
+	fatal("%s: not yet implemented", __func__);
+}
+
+static int
+lua_runtime_validator(void *addr, struct http_request *req, const void *data)
+{
+	fatal("%s: not yet implemented", __func__);
+
+	return (KORE_RESULT_ERROR);
+}
+
+static void
+lua_runtime_wsmessage(void *addr, struct connection *c, u_int8_t op,
+    const void *data, size_t len)
+{
+	fatal("%s: not yet implemented", __func__);
+}
+
+static void
+lua_runtime_execute(void *addr)
+{
+	lua_State	*L;
+
+	lua_symbol_resolve(addr, &L);
+
+	if (lua_pcall(L, 0, 0, 0)) {
+		fatal("failed to execute function: %s",
+		    lua_tostring(L, -1));
+	}
+}
+
+static void
+lua_runtime_configure(void *addr, int argc, char **argv)
+{
+	lua_State	*L;
+	int		idx;
+
+	lua_symbol_resolve(addr, &L);
+
+	lua_pushinteger(L, argc);
+	lua_newtable(L);
+
+	for (idx = 0; idx < argc; idx++) {
+		lua_pushstring(L, argv[idx]);
+		lua_rawseti(L, -2, idx);
+	}
+
+	if (lua_pcall(L, 2, 0, 0)) {
+		fatal("failed to configure your application (%s)",
+		    lua_tostring(L, -1));
+	}
+}
+
+static int
+lua_runtime_onload(void *addr, int action)
+{
+	fatal("%s: not yet implemented", __func__);
+
+	return (KORE_RESULT_ERROR);
+}
+
+static void
+lua_runtime_connect(void *addr, struct connection *c)
+{
+	fatal("%s: not yet implemented", __func__);
+}
+
+static void
+lua_runtime_signal(void *addr, int sig)
+{
+	fatal("%s: not yet implemented", __func__);
+}
+
+static int
+lua_kore_config(lua_State *L)
+{
+	char		*v;
+	const char	*opt, *val;
+
+	lua_pushnil(L);
+
+	while (lua_next(L, -2) != 0) {
+		if (!lua_isstring(L, -2))
+			fatal("kore.config: keyword not a string");
+
+		opt = lua_tostring(L, -2);
+
+		if (lua_isinteger(L, -1)) {
+			lua_pushvalue(L, -1);
+			val = lua_tostring(L, -1);
+			lua_pop(L, 1);
+		} else if (lua_isstring(L, -1)) {
+			val = lua_tostring(L, -1);
+		} else {
+			fatal("kore.config: value not a string or integer");
+		}
+
+		v = kore_strdup(val);
+
+		if (!kore_configure_setting(opt, v)) {
+			kore_free(v);
+			luaL_error(L, "kore.config: cannot be set at runtime");
+			lua_pop(L, 1);
+			return (0);
+		}
+
+		kore_free(v);
+		lua_pop(L, 1);
+	}
+
+	return (0);
+}
+
+static int
+lua_kore_server(lua_State *L)
+{
+	struct kore_server	*srv;
+	const char		*name, *ip, *port;
+
+	if ((name = lua_argument_get_string(L, "name")) == NULL)
+		name = "default";
+
+	if ((ip = lua_argument_get_string(L, "ip")) == NULL) {
+		luaL_error(L, "kore.server: missing ip keyword");
+		return (0);
+	}
+
+	if ((port = lua_argument_get_string(L, "port")) == NULL) {
+		luaL_error(L, "kore.server: missing port keyword");
+		return (0);
+	}
+
+	if ((srv = kore_server_lookup(name)) != NULL) {
+		luaL_error(L, "kore.server: server '%s' exists", name);
+		return (0);
+	}
+
+	srv = kore_server_create(name);
+	srv->tls = lua_argument_get_bool(L, "tls");
+
+	if (srv->tls && !kore_tls_supported()) {
+		kore_server_free(srv);
+		luaL_error(L, "kore.server: TLS not supported");
+		return (0);
+	}
+
+	if (!kore_server_bind(srv, ip, port, NULL)) {
+		kore_server_free(srv);
+		luaL_error(L, "kore.server: failed to bind %s:%s", ip, port);
+		return (0);
+	}
+
+	kore_server_finalize(srv);
+
+	return (0);
+}
+
+static int
+lua_http_request_gc(lua_State *L)
+{
+	struct lua_http_request		*lreq;
+
+	lreq = luaL_checkudata(L, 1, "http_request");
+	kore_free(lreq);
+
+	return (0);
+}
+
+static int
+lua_http_request_index(lua_State *L)
+{
+	struct lua_http_request		*lreq;
+	const char			*field;
+
+	lreq = luaL_checkudata(L, 1, "http_request");
+	field = luaL_checkstring(L, 2);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "http_request_methods");
+	lua_getfield(L, -1, field);
+
+	if (!lua_isnil(L, -1))
+		return (1);
+
+	lua_pop(L, 2);
+
+	if (!strcmp(field, "path")) {
+		lua_pushstring(L, lreq->req->path);
+		return (1);
+	} else if (!strcmp(field, "host")) {
+		lua_pushstring(L, lreq->req->host);
+		return (1);
+	} else if (!strcmp(field, "agent")) {
+		lua_pushstring(L, lreq->req->agent);
+		return (1);
+	} else if (!strcmp(field, "referer")) {
+		lua_pushstring(L, lreq->req->referer);
+		return (1);
+	} else if (!strcmp(field, "method")) {
+		lua_pushinteger(L, lreq->req->method);
+		return (1);
+	}
+
+	return (0);
+}
+
+static int
+lua_http_response_header(lua_State *L)
+{
+	struct lua_http_request		*lreq;
+	const char			*header, *value;
+
+	lreq = luaL_checkudata(L, 1, "http_request");
+	header = luaL_checkstring(L, 2);
+	value = luaL_checkstring(L, 3);
+
+	http_response_header(lreq->req, header, value);
+
+	return (0);
+}
+
+static int
+lua_http_request_header(lua_State *L)
+{
+	struct lua_http_request		*lreq;
+	const char			*header, *value;
+
+	lreq = luaL_checkudata(L, 1, "http_request");
+	header = luaL_checkstring(L, 2);
+
+	if (!http_request_header(lreq->req, header, &value)) {
+		lua_pushnil(L);
+	} else {
+		lua_pushstring(L, value);
+	}
+
+	return (1);
+}
+
+static int
+lua_http_response(lua_State *L)
+{
+	size_t				len;
+	struct lua_http_request		*lreq;
+	const void			*data;
+	int				status;
+
+	lreq = luaL_checkudata(L, 1, "http_request");
+	status = luaL_checkinteger(L, 2);
+
+	if (lua_isnil(L, 3)) {
+		len = 0;
+		data = NULL;
+	} else {
+		data = luaL_checklstring(L, 3, &len);
+	}
+
+	http_response(lreq->req, status, data, len);
+
+	return (0);
+}
diff --git a/src/module.c b/src/module.c
@@ -29,6 +29,10 @@
 #include "python_api.h"
 #endif
 
+#if defined(KORE_USE_LUA)
+#include "lua_api.h"
+#endif
+
 static TAILQ_HEAD(, kore_module)	modules;
 
 static void	native_free(struct kore_module *);
@@ -93,6 +97,12 @@ kore_module_load(const char *path, const char *onload, int type)
 		module->runtime = &kore_python_runtime;
 		break;
 #endif
+#if defined(KORE_USE_LUA)
+	case KORE_MODULE_LUA:
+		module->fun = &kore_lua_module;
+		module->runtime = &kore_lua_runtime;
+		break;
+#endif
 	default:
 		fatal("kore_module_load: unknown type %d", type);
 	}
diff --git a/src/pool.c b/src/pool.c
@@ -206,7 +206,6 @@ kore_pool_put(struct kore_pool *pool, void *ptr)
 		pool_mark_entry_none(pool, entry);
 
 	pool->freelist = entry;
-
 #if defined(KORE_USE_TASKS)
 	pool_unlock(pool);
 #endif
diff --git a/src/runtime.c b/src/runtime.c
@@ -27,6 +27,10 @@
 #include "python_api.h"
 #endif
 
+#if defined(KORE_USE_LUA)
+#include "lua_api.h"
+#endif
+
 static void	native_runtime_execute(void *);
 static int	native_runtime_onload(void *, int);
 static void	native_runtime_signal(void *, int);
@@ -66,6 +70,9 @@ static struct kore_runtime *runtimes[] = {
 #if defined(KORE_USE_PYTHON)
 	&kore_python_runtime,
 #endif
+#if defined(KORE_USE_LUA)
+	&kore_lua_runtime,
+#endif
 	NULL
 };