From eb0f8a028cf7ddb1b708ee97e6a9c777b45d4d4f Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 18 Jan 2015 04:07:05 +0100
Subject: Implement a Lua 5.3 plugin loader plugin
Also implemented SOCKS detection in said language.
There are probably going to be some bugs.
The program is no longer Valgrind-clean, as that would require plugin
deinitialization, in which there is very little point.
---
CMakeLists.txt | 6 +-
plugin-api.h | 6 +-
plugins/http.c | 4 +-
plugins/irc.c | 4 +-
plugins/lua-loader.c | 446 +++++++++++++++++++++++++++++++++++++++++++++++++++
plugins/socks.lua | 99 ++++++++++++
plugins/ssh.c | 4 +-
ponymap.c | 10 +-
8 files changed, 573 insertions(+), 6 deletions(-)
create mode 100644 plugins/lua-loader.c
create mode 100644 plugins/socks.lua
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea214e2..57d04ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -73,7 +73,11 @@ set_target_properties (plugin-http PROPERTIES OUTPUT_NAME http PREFIX "")
install (TARGETS plugin-http DESTINATION ${plugin_dir})
# Build the other plugins
-foreach (plugin irc ssh)
+set (plugins irc ssh)
+if (WITH_LUA)
+ list (APPEND plugins lua-loader)
+endif (WITH_LUA)
+foreach (plugin ${plugins})
set (target plugin-${plugin})
add_library (${target} SHARED plugins/${plugin}.c plugin-api.h)
target_link_libraries (${target} ${project_libraries})
diff --git a/plugin-api.h b/plugin-api.h
index f74b069..109f729 100644
--- a/plugin-api.h
+++ b/plugin-api.h
@@ -38,11 +38,12 @@ struct service
{
const char *name; ///< Name of the service
int flags; ///< Service flags
+ void *user_data; ///< User data
// scan_init -> on_data* -> [on_eof/on_error] -> on_aborted -> scan_free
/// Initialize a scan, returning a handle to it
- void *(*scan_init) (struct unit *u);
+ void *(*scan_init) (struct service *self, struct unit *u);
/// Destroy the handle created for the scan
void (*scan_free) (void *handle);
@@ -67,6 +68,9 @@ struct plugin_api
/// Register the plugin for a service
void (*register_service) (void *ctx, struct service *info);
+ /// Retrieve an item from the configuration
+ const char *(*get_config) (void *ctx, const char *key);
+
/// Get the IP address of the target as a string
const char *(*unit_get_address) (struct unit *u);
diff --git a/plugins/http.c b/plugins/http.c
index 1aa2bd9..a6a58e2 100644
--- a/plugins/http.c
+++ b/plugins/http.c
@@ -94,8 +94,10 @@ on_headers_complete (http_parser *parser)
}
static void *
-scan_init (struct unit *u)
+scan_init (struct service *service, struct unit *u)
{
+ (void) service;
+
struct str hello;
str_init (&hello);
str_append_printf (&hello, "GET / HTTP/1.0\r\n"
diff --git a/plugins/irc.c b/plugins/irc.c
index 554c00c..4db36ba 100644
--- a/plugins/irc.c
+++ b/plugins/irc.c
@@ -214,8 +214,10 @@ struct scan_data
};
static void *
-scan_init (struct unit *u)
+scan_init (struct service *service, struct unit *u)
{
+ (void) service;
+
char nick[IRC_MAX_NICKNAME + 1];
size_t i;
for (i = 0; i < sizeof nick - 1; i++)
diff --git a/plugins/lua-loader.c b/plugins/lua-loader.c
new file mode 100644
index 0000000..341327b
--- /dev/null
+++ b/plugins/lua-loader.c
@@ -0,0 +1,446 @@
+/*
+ * lua-loader.c: Lua plugin loader plugin
+ *
+ * Copyright (c) 2015, Přemysl Janouch
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or 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.
+ *
+ */
+
+// I can't really recommend using this interface as it adds a lot of overhead
+
+#include "../utils.c"
+#include "../plugin-api.h"
+
+#include
+
+#include
+#include
+#include
+
+// --- 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 ------------------------------------------------------------
+
+struct unit_wrapper
+{
+ struct unit *unit; ///< The underlying unit
+};
+
+#define UNIT_METATABLE "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_abort (lua_State *L)
+{
+ struct unit_wrapper *data = luaL_checkudata (L, 1, UNIT_METATABLE);
+ g_data.api->unit_abort (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 },
+ { "abort", xlua_unit_abort },
+ { "__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_abort (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_aborted (void *handle)
+{
+ struct scan_data *data = handle;
+ if (!prepare_scan_method (data, "on_aborted"))
+ 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_aborted = on_aborted;
+
+ 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 luaL_Reg xlua_library[] =
+{
+ { "register_service", xlua_register_service },
+ { "get_config", xlua_get_config },
+ { NULL, NULL }
+};
+
+static bool
+load_one_plugin (lua_State *L, const char *name, const char *path)
+{
+ int ret;
+ if (!(ret = luaL_loadfile (L, path))
+ && !(ret = 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_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 buf, *iter;
+ while (true)
+ {
+ if (readdir_r (dir, &buf, &iter))
+ {
+ print_fatal ("%s: %s: %s", "Lua", "readdir_r", strerror (errno));
+ break;
+ }
+ if (!iter)
+ {
+ success = true;
+ break;
+ }
+
+ 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);
+ }
+
+end:
+ closedir (dir);
+ return success;
+}
+
+struct plugin_info ponymap_plugin_info =
+{
+ .api_version = API_VERSION,
+ .initialize = initialize
+};
diff --git a/plugins/socks.lua b/plugins/socks.lua
new file mode 100644
index 0000000..a03f72d
--- /dev/null
+++ b/plugins/socks.lua
@@ -0,0 +1,99 @@
+--
+-- socks.lua: SOCKS service detection plugin
+--
+-- Copyright (c) 2015, Přemysl Janouch
+-- All rights reserved.
+--
+-- Permission to use, copy, modify, and/or 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.
+--
+
+-- This plugin serves as an example of how to write Lua plugins for ponymap
+
+-- SOCKS 4
+
+local Socks4 = {}
+Socks4.__index = Socks4
+
+function Socks4.new (unit)
+ unit:write (string.pack ("> I1 I1 I2 I1 I1 I1 I1 z",
+ 4, 1, 80, 127, 0, 0, 1, ""))
+ return setmetatable ({ unit = unit, buf = "" }, Socks4)
+end
+
+function Socks4:on_data (data)
+ self.buf = self.buf .. data
+ if #self.buf >= 8 then
+ null, code = string.unpack ("> I1 I1", self.buf)
+ if null == 0 and code >= 90 and code <= 93 then
+ self.unit:set_success (true)
+ end
+ self.unit:abort ()
+ end
+end
+
+-- SOCKS 4A
+
+local Socks4A = {}
+Socks4A.__index = Socks4A
+
+function Socks4A.new (unit)
+ unit:write (string.pack ("> I1 I1 I2 I4 z z",
+ 4, 1, 80, 1, "", "google.com"))
+ return setmetatable ({ unit = unit, buf = "" }, Socks4A)
+end
+
+Socks4A.on_data = Socks4.on_data
+
+-- SOCKS 5
+
+local Socks5 = {}
+Socks5.__index = Socks5
+
+function Socks5.new (unit)
+ unit:write (string.pack ("> I1 I1 I1", 5, 1, 0))
+ return setmetatable ({ unit = unit, buf = "" }, Socks5)
+end
+
+function Socks5:on_data (data)
+ self.buf = self.buf .. data
+ if #self.buf >= 2 then
+ version, result = string.unpack ("> I1 I1", self.buf)
+ if version == 5 and (result == 0 or result == 255) then
+ if result == 0 then
+ self.unit:add_info ("no authentication required")
+ end
+ self.unit:set_success (true)
+ end
+ self.unit:abort ()
+ end
+end
+
+-- Register everything
+
+ponymap.register_service ({
+ name = "SOCKS4",
+ flags = 0,
+ new_scan = Socks4.new
+})
+
+ponymap.register_service ({
+ name = "SOCKS4A",
+ flags = 0,
+ new_scan = Socks4A.new
+})
+
+ponymap.register_service ({
+ name = "SOCKS5",
+ flags = 0,
+ new_scan = Socks5.new
+})
diff --git a/plugins/ssh.c b/plugins/ssh.c
index 8a1cc3a..68f8d52 100644
--- a/plugins/ssh.c
+++ b/plugins/ssh.c
@@ -37,8 +37,10 @@ struct scan_data
};
static void *
-scan_init (struct unit *u)
+scan_init (struct service *service, struct unit *u)
{
+ (void) service;
+
struct scan_data *scan = xcalloc (1, sizeof *scan);
str_init (&scan->input);
scan->u = u;
diff --git a/ponymap.c b/ponymap.c
index c1cca02..1ab521a 100644
--- a/ponymap.c
+++ b/ponymap.c
@@ -623,7 +623,7 @@ unit_start_scan (struct unit *u)
u->scan_started = true;
poller_timer_set (&u->timeout_event, u->target->ctx->scan_timeout * 1000);
- u->service_data = u->service->scan_init (u);
+ u->service_data = u->service->scan_init (u->service, u);
u->fd_event.dispatcher = (poller_fd_fn) on_unit_ready;
unit_update_poller (u, NULL);
}
@@ -791,6 +791,13 @@ plugin_api_register_service (void *app_context, struct service *info)
str_map_set (&ctx->services, info->name, info);
}
+static const char *
+plugin_api_get_config (void *app_context, const char *key)
+{
+ struct app_context *ctx = app_context;
+ return str_map_find (&ctx->config, key);
+}
+
static const char *
plugin_api_unit_get_address (struct unit *u)
{
@@ -828,6 +835,7 @@ plugin_api_unit_abort (struct unit *u)
static struct plugin_api g_plugin_vtable =
{
.register_service = plugin_api_register_service,
+ .get_config = plugin_api_get_config,
.unit_get_address = plugin_api_unit_get_address,
.unit_write = plugin_api_unit_write,
.unit_set_success = plugin_api_unit_set_success,
--
cgit v1.2.3-70-g09d2