ponymap/plugins/lua-loader.c

452 lines
11 KiB
C

/*
* lua-loader.c: Lua plugin loader plugin
*
* Copyright (c) 2015, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* 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.
*
*/
// I can't really recommend using this interface as it adds a lot of overhead
#include "config.h"
#include "../liberty/liberty.c"
#include "../plugin-api.h"
#include <dirent.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#define XLUA_API_VERSION 1 ///< Version of the Lua API
// --- Utilities ---------------------------------------------------------------
static struct plugin_data
{
void *ctx; ///< Application context
struct plugin_api *api; ///< Plugin API vtable
}
g_data;
static void *
xlua_alloc (void *ud, void *ptr, size_t o_size, size_t n_size)
{
(void) ud;
(void) o_size;
if (n_size)
return realloc (ptr, n_size);
free (ptr);
return NULL;
}
static int
xlua_panic (lua_State *L)
{
// XXX: we might be able to do something better
print_fatal ("Lua panicked: %s", lua_tostring (L, -1));
lua_close (L);
exit (EXIT_FAILURE);
return 0;
}
// --- Unit wrapper ------------------------------------------------------------
#define UNIT_METATABLE "unit" ///< Identifier for the Lua metatable
struct unit_wrapper
{
struct unit *unit; ///< The underlying unit
};
static int
xlua_unit_get_address (lua_State *L)
{
struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
lua_pushstring (L, g_data.api->unit_get_address (data->unit));
return 1;
}
static int
xlua_unit_write (lua_State *L)
{
struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
size_t buffer_len;
const char *buffer = luaL_checklstring (L, 2, &buffer_len);
lua_pushinteger (L, g_data.api->unit_write
(data->unit, buffer, buffer_len));
return 1;
}
static int
xlua_unit_set_success (lua_State *L)
{
struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
bool success = lua_toboolean (L, 2);
g_data.api->unit_set_success (data->unit, success);
return 0;
}
static int
xlua_unit_add_info (lua_State *L)
{
struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
const char *info = luaL_checkstring (L, 2);
g_data.api->unit_add_info (data->unit, info);
return 0;
}
static int
xlua_unit_stop (lua_State *L)
{
struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
g_data.api->unit_stop (data->unit);
return 0;
}
static int
xlua_unit_destroy (lua_State *L)
{
// TODO: when creating the wrapper object, increase the reference
// count for the unit and decrease it in here again. If we don't do
// this, the Lua plugin may cause a use-after-free error.
(void) L;
return 0;
}
static luaL_Reg xlua_unit_table[] =
{
{ "get_address", xlua_unit_get_address },
{ "write", xlua_unit_write },
{ "set_success", xlua_unit_set_success },
{ "add_info", xlua_unit_add_info },
{ "stop", xlua_unit_stop },
{ "__gc", xlua_unit_destroy },
{ NULL, NULL }
};
// --- Scan wrapper ------------------------------------------------------------
struct service_data
{
struct service *service; ///< The corresponding service
lua_State *L; ///< Lua state
int new_scan_cb_ref; ///< Reference to "new_scan" callback
};
struct scan_data
{
struct service *service; ///< The corresponding service
struct unit *unit; ///< The corresponding unit
lua_State *L; ///< Lua state
int scan_ref; ///< Reference to scan data in Lua
};
static void *
scan_init (struct service *self, struct unit *unit)
{
struct service_data *service = self->user_data;
lua_geti (service->L, LUA_REGISTRYINDEX, service->new_scan_cb_ref);
// Wrap the unit in Lua userdata so that Lua code can use it
struct unit_wrapper *wrapper =
lua_newuserdata (service->L, sizeof *wrapper);
wrapper->unit = unit;
luaL_setmetatable (service->L, UNIT_METATABLE);
// Ask for a Lua object (table) to use for protocol detection
if (lua_pcall (service->L, 1, 1, 0))
{
print_error ("Lua: service `%s': new_scan: %s",
service->service->name, lua_tostring (service->L, -1));
lua_pop (service->L, 1);
return NULL;
}
if (!lua_istable (service->L, -1))
{
print_error ("Lua: service `%s': new_scan must return a table",
service->service->name);
return NULL;
}
// Return a scan handle
struct scan_data *data = xmalloc (sizeof *data);
data->service = self;
data->L = service->L;
data->scan_ref = luaL_ref (service->L, LUA_REGISTRYINDEX);
data->unit = unit;
return data;
}
static void
scan_free (void *handle)
{
if (!handle)
return;
struct scan_data *data = handle;
luaL_unref (data->L, LUA_REGISTRYINDEX, data->scan_ref);
free (handle);
}
static void
handle_scan_method_failure (struct scan_data *data)
{
print_error ("Lua: service `%s': %s", data->service->name,
lua_tostring (data->L, -1));
g_data.api->unit_stop (data->unit);
lua_pop (data->L, 1);
}
static bool
prepare_scan_method (struct scan_data *data, const char *method_name)
{
if (!data)
return false;
lua_geti (data->L, LUA_REGISTRYINDEX, data->scan_ref);
lua_getfield (data->L, -1, method_name);
if (lua_isnil (data->L, -1))
{
lua_pop (data->L, 2);
return false;
}
if (!lua_isfunction (data->L, -1))
{
lua_pop (data->L, 2);
lua_pushfstring (data->L, "`%s' must be a function", method_name);
handle_scan_method_failure (data);
return false;
}
// Swap the first two values on the stack, so that the function
// is first and the object we're calling it on is second
lua_insert (data->L, -2);
return true;
}
static void
on_data (void *handle, const void *network_data, size_t len)
{
struct scan_data *data = handle;
if (!prepare_scan_method (data, "on_data"))
return;
lua_pushlstring (data->L, network_data, len);
if (lua_pcall (data->L, 2, 0, 0))
handle_scan_method_failure (data);
}
static void
on_eof (void *handle)
{
struct scan_data *data = handle;
if (!prepare_scan_method (data, "on_eof"))
return;
if (lua_pcall (data->L, 1, 0, 0))
handle_scan_method_failure (data);
}
static void
on_error (void *handle)
{
struct scan_data *data = handle;
if (!prepare_scan_method (data, "on_error"))
return;
if (lua_pcall (data->L, 1, 0, 0))
handle_scan_method_failure (data);
}
static void
on_stopped (void *handle)
{
struct scan_data *data = handle;
if (!prepare_scan_method (data, "on_stopped"))
return;
if (lua_pcall (data->L, 1, 0, 0))
handle_scan_method_failure (data);
}
// --- Top-level interface -----------------------------------------------------
static int
xlua_register_service (lua_State *L)
{
// Validate and decode the arguments
luaL_checktype (L, 1, LUA_TTABLE);
lua_getfield (L, 1, "name");
if (!lua_isstring (L, -1))
return luaL_error (L, "service registration failed: "
"invalid or missing `%s'", "name");
const char *name = lua_tostring (L, -1);
lua_pop (L, 1);
lua_getfield (L, 1, "flags");
lua_Unsigned flags;
if (lua_isnil (L, -1))
flags = 0;
else if (lua_isinteger (L, -1))
flags = lua_tointeger (L, -1);
else
return luaL_error (L, "service registration failed: "
"invalid or missing `%s'", "flags");
lua_pop (L, 1);
lua_getfield (L, 1, "new_scan");
if (!lua_isfunction (L, -1))
return luaL_error (L, "service registration failed: "
"invalid or missing `%s'", "new_scan");
// Reference the "new_scan" function for later use
struct service_data *data = xcalloc (1, sizeof *data);
data->L = L;
data->new_scan_cb_ref = luaL_ref (L, LUA_REGISTRYINDEX);
// Register a new service that proxies calls to Lua code
struct service *s = data->service = xcalloc (1, sizeof *s);
s->name = xstrdup (name);
s->flags = flags;
s->user_data = data;
s->scan_init = scan_init;
s->scan_free = scan_free;
s->on_data = on_data;
s->on_eof = on_eof;
s->on_error = on_error;
s->on_stopped = on_stopped;
g_data.api->register_service (g_data.ctx, s);
return 0;
}
static int
xlua_get_config (lua_State *L)
{
const char *key = luaL_checkstring (L, 1);
lua_pushstring (L, g_data.api->get_config (g_data.ctx, key));
return 0;
}
static int
xlua_check_api_version (lua_State *L)
{
lua_Integer requested = luaL_checkinteger (L, 1);
if (requested != XLUA_API_VERSION)
return luaL_error (L, "incompatible API version");
return 0;
}
static luaL_Reg xlua_library[] =
{
{ "register_service", xlua_register_service },
{ "get_config", xlua_get_config },
{ "check_api_version", xlua_check_api_version },
{ NULL, NULL }
};
static bool
load_one_plugin (lua_State *L, const char *name, const char *path)
{
if (!luaL_loadfile (L, path)
&& !lua_pcall (L, 0, 0, 0))
return true;
print_error ("Lua: could not load `%s': %s", name, lua_tostring (L, -1));
lua_pop (L, 1);
return false;
}
static bool
initialize (void *ctx, struct plugin_api *api)
{
g_data = (struct plugin_data) { .ctx = ctx, .api = api };
if (sizeof (lua_Integer) < 8)
{
print_error ("%s: %s", "Lua",
"at least 64-bit Lua integers are required");
return false;
}
const char *plugin_dir = api->get_config (ctx, "plugin_dir");
if (!plugin_dir)
{
print_fatal ("%s: %s", "Lua", "no plugin directory defined");
return false;
}
DIR *dir = opendir (plugin_dir);
if (!dir)
{
print_fatal ("%s: %s: %s", "Lua",
"cannot open plugin directory", strerror (errno));
return false;
}
bool success = false;
lua_State *L;
if (!(L = lua_newstate (xlua_alloc, NULL)))
{
print_fatal ("%s: %s", "Lua", "initialization failed");
goto end;
}
lua_atpanic (L, xlua_panic);
luaL_openlibs (L);
// Register the ponymap library
luaL_newlib (L, xlua_library);
lua_pushinteger (L, SERVICE_SUPPORTS_TLS);
lua_setfield (L, -2, "SERVICE_SUPPORTS_TLS");
lua_pushinteger (L, XLUA_API_VERSION);
lua_setfield (L, -2, "api_version");
lua_setglobal (L, PROGRAM_NAME);
// Create a metatable for the units
luaL_newmetatable (L, UNIT_METATABLE);
lua_pushvalue (L, -1);
lua_setfield (L, -2, "__index");
luaL_setfuncs (L, xlua_unit_table, 0);
struct dirent *iter;
while ((errno = 0, iter = readdir (dir)))
{
char *dot = strrchr (iter->d_name, '.');
if (!dot || strcmp (dot, ".lua"))
continue;
char *path = xstrdup_printf ("%s/%s", plugin_dir, iter->d_name);
(void) load_one_plugin (L, iter->d_name, path);
free (path);
}
if (errno)
print_fatal ("%s: %s: %s", "Lua", "readdir", strerror (errno));
else
success = true;
end:
closedir (dir);
return success;
}
struct plugin_info ponymap_plugin_info =
{
.api_version = API_VERSION,
.initialize = initialize
};