/* * Copyright (c) 2023 Joris Vink * * 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 #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 *); 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; } 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); }