From 3ceccd02d86bf4d6609f46d8b30963cc52034cc2 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 23 Nov 2021 11:47:08 +0100 Subject: ucode: add ucode plugin support The ucode plugin mirrors the functionality of the Lua module, but using the ucode script interpreter instead. Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 8 ++ main.c | 68 ++++++++- ucode.c | 435 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ uhttpd.h | 15 ++ 4 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 ucode.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ae8ba4..564842d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations OPTION(TLS_SUPPORT "TLS support" ON) OPTION(LUA_SUPPORT "Lua support" ON) OPTION(UBUS_SUPPORT "ubus support" ON) +OPTION(UCODE_SUPPORT "ucode support" ON) IF(APPLE) INCLUDE_DIRECTORIES(/opt/local/include) @@ -69,6 +70,13 @@ IF(LUA_SUPPORT) TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl) ENDIF() +IF(UCODE_SUPPORT) + SET(PLUGINS ${PLUGINS} uhttpd_ucode) + ADD_DEFINITIONS(-DHAVE_UCODE) + ADD_LIBRARY(uhttpd_ucode MODULE ucode.c) + TARGET_LINK_LIBRARIES(uhttpd_ucode ucode ${libjson}) +ENDIF() + IF(UBUS_SUPPORT) SET(PLUGINS ${PLUGINS} uhttpd_ubus) ADD_DEFINITIONS(-DHAVE_UBUS) diff --git a/main.c b/main.c index 73e3d42..13d02a4 100644 --- a/main.c +++ b/main.c @@ -188,6 +188,9 @@ static void init_defaults_pre(void) conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin"; INIT_LIST_HEAD(&conf.cgi_alias); INIT_LIST_HEAD(&conf.lua_prefix); +#if HAVE_UCODE + INIT_LIST_HEAD(&conf.ucode_prefix); +#endif } static void init_defaults_post(void) @@ -241,6 +244,25 @@ static void add_lua_prefix(const char *prefix, const char *handler) { } #endif +#ifdef HAVE_UCODE +static void add_ucode_prefix(const char *prefix, const char *handler) { + struct ucode_prefix *p; + char *pprefix, *phandler; + + p = calloc_a(sizeof(*p), + &pprefix, strlen(prefix) + 1, + &phandler, strlen(handler) + 1); + + if (!p) + return; + + p->prefix = strcpy(pprefix, prefix); + p->handler = strcpy(phandler, handler); + + list_add_tail(&p->list, &conf.ucode_prefix); +} +#endif + int main(int argc, char **argv) { struct alias *alias; @@ -256,6 +278,9 @@ int main(int argc, char **argv) #ifdef HAVE_LUA const char *lua_prefix = NULL, *lua_handler = NULL; #endif +#ifdef HAVE_UCODE + const char *ucode_prefix = NULL, *ucode_handler = NULL; +#endif BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX); @@ -263,7 +288,7 @@ int main(int argc, char **argv) init_defaults_pre(); signal(SIGPIPE, SIG_IGN); - while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:e:fh:H:I:i:K:k:L:l:m:N:n:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) { + while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:e:fh:H:I:i:K:k:L:l:m:N:n:O:o:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) { switch(ch) { #ifdef HAVE_TLS case 'C': @@ -475,6 +500,38 @@ int main(int argc, char **argv) "ignoring -%c\n", ch); break; #endif +#ifdef HAVE_UCODE + case 'o': + case 'O': + if (ch == 'o') { + if (ucode_prefix) + fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n", + ch, ucode_prefix); + + ucode_prefix = optarg; + } + else { + if (ucode_handler) + fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n", + ch, ucode_handler); + + ucode_handler = optarg; + } + + if (ucode_prefix && ucode_handler) { + add_ucode_prefix(ucode_prefix, ucode_handler); + ucode_prefix = NULL; + ucode_handler = NULL; + } + + break; +#else + case 'o': + case 'O': + fprintf(stderr, "uhttpd: ucode support not compiled, " + "ignoring -%c\n", ch); + break; +#endif #ifdef HAVE_UBUS case 'a': conf.ubus_noauth = 1; @@ -549,6 +606,15 @@ int main(int argc, char **argv) if (!list_empty(&conf.lua_prefix) && uh_plugin_init("uhttpd_lua.so")) return 1; #endif +#ifdef HAVE_UCODE + if (ucode_handler || ucode_prefix) { + fprintf(stderr, "Need handler and prefix to enable ucode support\n"); + return 1; + } + + if (!list_empty(&conf.ucode_prefix) && uh_plugin_init("uhttpd_ucode.so")) + return 1; +#endif #ifdef HAVE_UBUS if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so")) return 1; diff --git a/ucode.c b/ucode.c new file mode 100644 index 0000000..f408576 --- /dev/null +++ b/ucode.c @@ -0,0 +1,435 @@ +/* + * uhttpd - Tiny single-threaded httpd + * + * Copyright (C) 2010-2013 Jo-Philipp Wich + * Copyright (C) 2013 Felix Fietkau + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "uhttpd.h" +#include "plugin.h" + +#define UH_UCODE_CB "handle_request" + +static const struct uhttpd_ops *ops; +static struct config *_conf; +#define conf (*_conf) + +static struct ucode_prefix *current_prefix; + +static uc_value_t * +uh_ucode_recv(uc_vm_t *vm, size_t nargs) +{ + static struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN }; + int data_len = 0, len = BUFSIZ, rlen, r; + uc_value_t *v = uc_fn_arg(0); + uc_stringbuf_t *buf; + + if (ucv_type(v) == UC_INTEGER) { + len = ucv_int64_get(v); + } + else if (v != NULL) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument not an integer"); + + return NULL; + } + + buf = ucv_stringbuf_new(); + + while (len > 0) { + rlen = (len < BUFSIZ) ? len : BUFSIZ; + + if (printbuf_memset(buf, -1, 0, rlen)) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory"); + printbuf_free(buf); + + return NULL; + } + + buf->bpos -= rlen; + r = read(STDIN_FILENO, buf->buf + buf->bpos, rlen); + + if (r < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + pfd.revents = 0; + poll(&pfd, 1, 1000); + + if (pfd.revents & POLLIN) + continue; + } + + if (errno == EINTR) + continue; + + if (!data_len) + data_len = -1; + + break; + } + + buf->bpos += r; + data_len += r; + len -= r; + + if (r != rlen) + break; + } + + if (data_len > 0) { + /* add final guard \0 but do not count it */ + if (printbuf_memset(buf, -1, 0, 1)) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory"); + printbuf_free(buf); + + return NULL; + } + + buf->bpos--; + + return ucv_stringbuf_finish(buf); + } + + printbuf_free(buf); + + return NULL; +} + +static uc_value_t * +uh_ucode_send(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *val = uc_fn_arg(0); + ssize_t len; + char *p; + + if (ucv_type(val) == UC_STRING) { + len = write(STDOUT_FILENO, ucv_string_get(val), ucv_string_length(val)); + } + else if (val != NULL) { + p = ucv_to_string(vm, val); + len = p ? write(STDOUT_FILENO, p, strlen(p)) : 0; + free(p); + } + else { + len = 0; + } + + return ucv_int64_new(len); +} + +static uc_value_t * +uh_ucode_strconvert(uc_vm_t *vm, size_t nargs, int (*convert)(char *, int, const char *, int)) +{ + uc_value_t *val = uc_fn_arg(0); + static char out_buf[4096]; + int out_len; + char *p; + + if (ucv_type(val) == UC_STRING) { + out_len = convert(out_buf, sizeof(out_buf), + ucv_string_get(val), ucv_string_length(val)); + } + else if (val != NULL) { + p = ucv_to_string(vm, val); + out_len = p ? convert(out_buf, sizeof(out_buf), p, strlen(p)) : 0; + free(p); + } + else { + out_len = 0; + } + + if (out_len < 0) { + const char *error; + + if (out_len == -1) + error = "buffer overflow"; + else + error = "malformed string"; + + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "%s on URL conversion\n", error); + + return NULL; + } + + return ucv_string_new_length(out_buf, out_len); +} + +static uc_value_t * +uh_ucode_urldecode(uc_vm_t *vm, size_t nargs) +{ + return uh_ucode_strconvert(vm, nargs, ops->urldecode); +} + +static uc_value_t * +uh_ucode_urlencode(uc_vm_t *vm, size_t nargs) +{ + return uh_ucode_strconvert(vm, nargs, ops->urlencode); +} + +static uc_parse_config_t config = { + .strict_declarations = false, + .lstrip_blocks = true, + .trim_blocks = true +}; + +static void +uh_ucode_exception(uc_vm_t *vm, uc_exception_t *ex) +{ + uc_value_t *ctx; + + printf("Status: 500 Internal Server Error\r\n\r\n" + "Exception while executing ucode program %s:\n", + current_prefix->handler); + + switch (ex->type) { + case EXCEPTION_SYNTAX: printf("Syntax error"); break; + case EXCEPTION_RUNTIME: printf("Runtime error"); break; + case EXCEPTION_TYPE: printf("Type error"); break; + case EXCEPTION_REFERENCE: printf("Reference error"); break; + default: printf("Error"); + } + + printf(": %s\n", ex->message); + + ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL); + + if (ctx) + printf("%s\n", ucv_string_get(ctx)); +} + +static void +uh_ucode_state_init(struct ucode_prefix *ucode) +{ + char *syntax_error = NULL; + uc_vm_t *vm = &ucode->ctx; + uc_function_t *handler; + uc_vm_status_t status; + uc_source_t *src; + uc_value_t *v; + int exitcode; + + uc_vm_init(vm, &config); + uc_stdlib_load(uc_vm_scope_get(vm)); + + /* build uhttpd api table */ + v = ucv_object_new(vm); + + ucv_object_add(v, "send", ucv_cfunction_new("send", uh_ucode_send)); + ucv_object_add(v, "sendc", ucv_get(ucv_object_get(v, "send", NULL))); + ucv_object_add(v, "recv", ucv_cfunction_new("recv", uh_ucode_recv)); + ucv_object_add(v, "urldecode", ucv_cfunction_new("urldecode", uh_ucode_urldecode)); + ucv_object_add(v, "urlencode", ucv_cfunction_new("urlencode", uh_ucode_urlencode)); + ucv_object_add(v, "docroot", ucv_string_new(conf.docroot)); + + ucv_object_add(uc_vm_scope_get(vm), "uhttpd", v); + + src = uc_source_new_file(ucode->handler); + + if (!src) { + fprintf(stderr, "Error: Unable to open ucode handler: %s\n", + strerror(errno)); + + exit(1); + } + + handler = uc_compile(&config, src, &syntax_error); + + uc_source_put(src); + + if (!handler) { + fprintf(stderr, "Error: Unable to compile ucode handler: %s\n", + syntax_error); + + exit(1); + } + + free(syntax_error); + + vm->output = fopen("/dev/null", "w"); + + if (!vm->output) { + fprintf(stderr, "Error: Unable to open /dev/null for writing: %s\n", + strerror(errno)); + + exit(1); + } + + status = uc_vm_execute(vm, handler, &v); + exitcode = (int)ucv_int64_get(v); + + ucv_put(v); + + switch (status) { + case STATUS_OK: + break; + + case STATUS_EXIT: + fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode); + exit(exitcode ? exitcode : 1); + + case ERROR_COMPILE: + fprintf(stderr, "Error: Compilation error while executing ucode handler\n"); + exit(1); + + case ERROR_RUNTIME: + fprintf(stderr, "Error: Runtime error while executing ucode handler\n"); + exit(2); + } + + v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL); + + if (!ucv_is_callable(v)) { + fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n"); + exit(1); + } + + uc_vm_exception_handler_set(vm, uh_ucode_exception); + + ucv_gc(vm); + + fclose(vm->output); + + vm->output = stdout; +} + +static void +ucode_main(struct client *cl, struct path_info *pi, char *url) +{ + uc_vm_t *vm = ¤t_prefix->ctx; + uc_value_t *req, *hdr, *res; + int path_len, prefix_len; + struct blob_attr *cur; + struct env_var *var; + char *str; + int rem; + + /* new env table for this request */ + req = ucv_object_new(vm); + + prefix_len = strlen(pi->name); + path_len = strlen(url); + str = strchr(url, '?'); + + if (str) { + if (*(str + 1)) + pi->query = str + 1; + + path_len = str - url; + } + + if (prefix_len > 0 && pi->name[prefix_len - 1] == '/') + prefix_len--; + + if (path_len > prefix_len) { + ucv_object_add(req, "PATH_INFO", + ucv_string_new_length(url + prefix_len, path_len - prefix_len)); + } + + for (var = ops->get_process_vars(cl, pi); var->name; var++) { + if (!var->value) + continue; + + ucv_object_add(req, var->name, ucv_string_new(var->value)); + } + + ucv_object_add(req, "HTTP_VERSION", + ucv_double_new(0.9 + (cl->request.version / 10.0))); + + hdr = ucv_object_new(vm); + + blob_for_each_attr(cur, cl->hdr.head, rem) + ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur))); + + ucv_object_add(req, "headers", hdr); + + res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req); + + ucv_put(req); + ucv_put(res); + + exit(0); +} + +static void +ucode_handle_request(struct client *cl, char *url, struct path_info *pi) +{ + struct ucode_prefix *p; + static struct path_info _pi; + + list_for_each_entry(p, &conf.ucode_prefix, list) { + if (!ops->path_match(p->prefix, url)) + continue; + + pi = &_pi; + pi->name = p->prefix; + pi->phys = p->handler; + + current_prefix = p; + + if (!ops->create_process(cl, pi, url, ucode_main)) { + ops->client_error(cl, 500, "Internal Server Error", + "Failed to create CGI process: %s", + strerror(errno)); + } + + return; + } + + ops->client_error(cl, 500, "Internal Server Error", + "Failed to lookup matching handler"); +} + +static bool +check_ucode_url(const char *url) +{ + struct ucode_prefix *p; + + list_for_each_entry(p, &conf.ucode_prefix, list) + if (ops->path_match(p->prefix, url)) + return true; + + return false; +} + +static struct dispatch_handler ucode_dispatch = { + .script = true, + .check_url = check_ucode_url, + .handle_request = ucode_handle_request, +}; + +static int +ucode_plugin_init(const struct uhttpd_ops *o, struct config *c) +{ + struct ucode_prefix *p; + + ops = o; + _conf = c; + + list_for_each_entry(p, &conf.ucode_prefix, list) + uh_ucode_state_init(p); + + ops->dispatch_add(&ucode_dispatch); + return 0; +} + +struct uhttpd_plugin uhttpd_plugin = { + .init = ucode_plugin_init, +}; diff --git a/uhttpd.h b/uhttpd.h index e61e176..d230b31 100644 --- a/uhttpd.h +++ b/uhttpd.h @@ -33,6 +33,9 @@ #include #include #endif +#ifdef HAVE_UCODE +#include +#endif #ifdef HAVE_TLS #include #endif @@ -59,6 +62,15 @@ struct lua_prefix { void *ctx; }; +#ifdef HAVE_UCODE +struct ucode_prefix { + struct list_head list; + const char *handler; + const char *prefix; + uc_vm_t ctx; +}; +#endif + struct config { const char *docroot; const char *realm; @@ -85,6 +97,9 @@ struct config { int events_retry; struct list_head cgi_alias; struct list_head lua_prefix; +#ifdef HAVE_UCODE + struct list_head ucode_prefix; +#endif }; struct auth_realm { -- cgit v1.2.3