From 6e84d9aea658907436446c3de9299b4fe2e1a81b Mon Sep 17 00:00:00 2001 From: jbnadal Date: Mon, 20 Mar 2017 18:32:22 +0100 Subject: [PATCH] Bump uhttpd version 2016-10-25 --- .../package/uhttpd/Config.in | 14 + .../package/uhttpd/uhttpd.mk | 24 + src/3P/uhttpd/CMakeLists.txt | 88 ++ src/3P/uhttpd/auth.c | 135 +++ src/3P/uhttpd/cgi.c | 118 +++ src/3P/uhttpd/client.c | 656 +++++++++++++ src/3P/uhttpd/file.c | 900 ++++++++++++++++++ src/3P/uhttpd/handler.c | 258 +++++ src/3P/uhttpd/listen.c | 222 +++++ src/3P/uhttpd/lua.c | 300 ++++++ src/3P/uhttpd/main.c | 527 ++++++++++ src/3P/uhttpd/mimetypes.h | 94 ++ src/3P/uhttpd/plugin.c | 70 ++ src/3P/uhttpd/plugin.h | 45 + src/3P/uhttpd/proc.c | 389 ++++++++ src/3P/uhttpd/relay.c | 205 ++++ src/3P/uhttpd/session-test.sh | 55 ++ src/3P/uhttpd/tls.c | 103 ++ src/3P/uhttpd/tls.h | 46 + src/3P/uhttpd/ubus.c | 685 +++++++++++++ src/3P/uhttpd/uhttpd.h | 342 +++++++ src/3P/uhttpd/utils.c | 251 +++++ src/3P/uhttpd/utils.h | 77 ++ 23 files changed, 5604 insertions(+) create mode 100644 bsp/buildroot_external/package/uhttpd/Config.in create mode 100644 bsp/buildroot_external/package/uhttpd/uhttpd.mk create mode 100644 src/3P/uhttpd/CMakeLists.txt create mode 100644 src/3P/uhttpd/auth.c create mode 100644 src/3P/uhttpd/cgi.c create mode 100644 src/3P/uhttpd/client.c create mode 100644 src/3P/uhttpd/file.c create mode 100644 src/3P/uhttpd/handler.c create mode 100644 src/3P/uhttpd/listen.c create mode 100644 src/3P/uhttpd/lua.c create mode 100644 src/3P/uhttpd/main.c create mode 100644 src/3P/uhttpd/mimetypes.h create mode 100644 src/3P/uhttpd/plugin.c create mode 100644 src/3P/uhttpd/plugin.h create mode 100644 src/3P/uhttpd/proc.c create mode 100644 src/3P/uhttpd/relay.c create mode 100755 src/3P/uhttpd/session-test.sh create mode 100644 src/3P/uhttpd/tls.c create mode 100644 src/3P/uhttpd/tls.h create mode 100644 src/3P/uhttpd/ubus.c create mode 100644 src/3P/uhttpd/uhttpd.h create mode 100644 src/3P/uhttpd/utils.c create mode 100644 src/3P/uhttpd/utils.h diff --git a/bsp/buildroot_external/package/uhttpd/Config.in b/bsp/buildroot_external/package/uhttpd/Config.in new file mode 100644 index 00000000..349ddd3a --- /dev/null +++ b/bsp/buildroot_external/package/uhttpd/Config.in @@ -0,0 +1,14 @@ +config BR2_PACKAGE_LIBUBOX + bool "libubox" + depends on !BR2_STATIC_LIBS + select BR2_PACKAGE_JSON_C + help + This library originates from the OpenWrt project to + handle the configuration file infrastructure, but can + also be used for the same purposes in projects other + than OpenWrt. + + http://nbd.name/gitweb.cgi?p=luci2/libubox.git;a=summary + +comment "libubox needs a toolchain w/ dynamic library" + depends on BR2_STATIC_LIBS diff --git a/bsp/buildroot_external/package/uhttpd/uhttpd.mk b/bsp/buildroot_external/package/uhttpd/uhttpd.mk new file mode 100644 index 00000000..ac6e7334 --- /dev/null +++ b/bsp/buildroot_external/package/uhttpd/uhttpd.mk @@ -0,0 +1,24 @@ +################################################################################ +# +# LIB UBOX +# +################################################################################ + +UHTTPD_VERSION:= 2016-10-25 + +UHTTPD_SITE = $(TOPDIR)/../../src/3P/libubox +UHTTPD_SITE_METHOD = local +UHTTPD_LICENSE = LGPLv2.1, GPLv2, BSD-3c, MIT +UHTTPD_INSTALL_STAGING = YES + +UHTTPD_DEPENDENCIES = json-c + +UHTTPD_CONF = SRC_DIR=$(TOPDIR)/../.. + +UHTTPD_CONF_OPTS +=-DBUILD_LUA=OFF + +UHTTPD_CONF_ENV = $(UHTTPD_CONF) +UHTTPD_MAKE_ENV = $(UHTTPD_CONF) +UHTTPD_CONF_OPTS += -DMODULE_PATH=$(TOPDIR)/../../bsp/cmake-modules -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) + +$(eval $(cmake-package)) diff --git a/src/3P/uhttpd/CMakeLists.txt b/src/3P/uhttpd/CMakeLists.txt new file mode 100644 index 00000000..7ae8ba4d --- /dev/null +++ b/src/3P/uhttpd/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(uhttpd C) + +INCLUDE (CheckFunctionExists) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations --std=gnu99 -g3) + +OPTION(TLS_SUPPORT "TLS support" ON) +OPTION(LUA_SUPPORT "Lua support" ON) +OPTION(UBUS_SUPPORT "ubus support" ON) + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +FIND_LIBRARY(LIBS crypt) +IF(LIBS STREQUAL "LIBS-NOTFOUND") + SET(LIBS "") +ENDIF() + +FIND_PATH(ubox_include_dir libubox/usock.h) +INCLUDE_DIRECTORIES(${ubox_include_dir}) + +SET(SOURCES main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c plugin.c handler.c) +IF(TLS_SUPPORT) + SET(SOURCES ${SOURCES} tls.c) + ADD_DEFINITIONS(-DHAVE_TLS) +ENDIF() + +CHECK_FUNCTION_EXISTS(getspnam HAVE_SHADOW) +IF(HAVE_SHADOW) + ADD_DEFINITIONS(-DHAVE_SHADOW) +ENDIF() + +ADD_EXECUTABLE(uhttpd ${SOURCES}) +FIND_LIBRARY(libjson NAMES json-c json) +TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS}) + +SET(PLUGINS "") +IF(LUA_SUPPORT) + FIND_PROGRAM(PKG_CONFIG pkg-config) + + IF(NOT LUA_CFLAGS AND PKG_CONFIG) + EXECUTE_PROCESS( + COMMAND pkg-config --silence-errors --cflags lua5.1 + OUTPUT_VARIABLE LUA_CFLAGS + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() + + IF(NOT LUA_LIBS AND PKG_CONFIG) + EXECUTE_PROCESS( + COMMAND pkg-config --silence-errors --libs lua5.1 + OUTPUT_VARIABLE LUA_LIBS + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() + + IF(NOT LUA_LIBS) + SET(LUA_LIBS "lua") + ENDIF() + + SET(PLUGINS ${PLUGINS} uhttpd_lua) + ADD_DEFINITIONS(-DHAVE_LUA ${LUA_CFLAGS}) + ADD_LIBRARY(uhttpd_lua MODULE lua.c) + TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl) +ENDIF() + +IF(UBUS_SUPPORT) + SET(PLUGINS ${PLUGINS} uhttpd_ubus) + ADD_DEFINITIONS(-DHAVE_UBUS) + ADD_LIBRARY(uhttpd_ubus MODULE ubus.c) + TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json ${libjson}) +ENDIF() + +IF(PLUGINS) + SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES + PREFIX "" + ) +ENDIF() + +INSTALL(TARGETS uhttpd ${PLUGINS} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib +) diff --git a/src/3P/uhttpd/auth.c b/src/3P/uhttpd/auth.c new file mode 100644 index 00000000..0c4872fe --- /dev/null +++ b/src/3P/uhttpd/auth.c @@ -0,0 +1,135 @@ +/* + * 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. + */ + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 700 +#include +#ifdef HAVE_SHADOW +#include +#endif +#include "uhttpd.h" + +static LIST_HEAD(auth_realms); + +void uh_auth_add(const char *path, const char *user, const char *pass) +{ + struct auth_realm *new = NULL; + struct passwd *pwd; + const char *new_pass = NULL; + char *dest_path, *dest_user, *dest_pass; + +#ifdef HAVE_SHADOW + struct spwd *spwd; +#endif + + /* given password refers to a passwd entry */ + if ((strlen(pass) > 3) && !strncmp(pass, "$p$", 3)) { +#ifdef HAVE_SHADOW + /* try to resolve shadow entry */ + spwd = getspnam(&pass[3]); + if (spwd) + new_pass = spwd->sp_pwdp; +#endif + if (!new_pass) { + pwd = getpwnam(&pass[3]); + if (pwd && pwd->pw_passwd && pwd->pw_passwd[0] && + pwd->pw_passwd[0] != '!') + new_pass = pwd->pw_passwd; + } + } else { + new_pass = pass; + } + + if (!new_pass || !new_pass[0]) + return; + + new = calloc_a(sizeof(*new), + &dest_path, strlen(path) + 1, + &dest_user, strlen(user) + 1, + &dest_pass, strlen(new_pass) + 1); + + if (!new) + return; + + new->path = strcpy(dest_path, path); + new->user = strcpy(dest_user, user); + new->pass = strcpy(dest_pass, new_pass); + list_add(&new->list, &auth_realms); +} + +bool uh_auth_check(struct client *cl, struct path_info *pi) +{ + struct http_request *req = &cl->request; + struct auth_realm *realm; + bool user_match = false; + char *user = NULL; + char *pass = NULL; + int plen; + + if (pi->auth && !strncasecmp(pi->auth, "Basic ", 6)) { + const char *auth = pi->auth + 6; + + uh_b64decode(uh_buf, sizeof(uh_buf), auth, strlen(auth)); + pass = strchr(uh_buf, ':'); + if (pass) { + user = uh_buf; + *pass++ = 0; + } + } + + req->realm = NULL; + plen = strlen(pi->name); + list_for_each_entry(realm, &auth_realms, list) { + int rlen = strlen(realm->path); + + if (plen < rlen) + continue; + + if (strncasecmp(pi->name, realm->path, rlen) != 0) + continue; + + req->realm = realm; + if (!user) + break; + + if (strcmp(user, realm->user) != 0) + continue; + + user_match = true; + break; + } + + if (!req->realm) + return true; + + if (user_match && + (!strcmp(pass, realm->pass) || + !strcmp(crypt(pass, realm->pass), realm->pass))) + return true; + + uh_http_header(cl, 401, "Authorization Required"); + ustream_printf(cl->us, + "WWW-Authenticate: Basic realm=\"%s\"\r\n" + "Content-Type: text/plain\r\n\r\n", + conf.realm); + uh_chunk_printf(cl, "Authorization Required\n"); + uh_request_done(cl); + + return false; +} diff --git a/src/3P/uhttpd/cgi.c b/src/3P/uhttpd/cgi.c new file mode 100644 index 00000000..0ffb1308 --- /dev/null +++ b/src/3P/uhttpd/cgi.c @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#define _GNU_SOURCE +#include +#include "uhttpd.h" + +static LIST_HEAD(interpreters); + +void uh_interpreter_add(const char *ext, const char *path) +{ + struct interpreter *in; + char *new_ext, *new_path; + + in = calloc_a(sizeof(*in), + &new_ext, strlen(ext) + 1, + &new_path, strlen(path) + 1); + + in->ext = strcpy(new_ext, ext); + in->path = strcpy(new_path, path); + list_add_tail(&in->list, &interpreters); +} + +static void cgi_main(struct client *cl, struct path_info *pi, char *url) +{ + const struct interpreter *ip = pi->ip; + struct env_var *var; + + clearenv(); + setenv("PATH", conf.cgi_path, 1); + + for (var = uh_get_process_vars(cl, pi); var->name; var++) { + if (!var->value) + continue; + + setenv(var->name, var->value, 1); + } + + if (!chdir(pi->root)) { + if (ip) + execl(ip->path, ip->path, pi->phys, NULL); + else + execl(pi->phys, pi->phys, NULL); + } + + printf("Status: 500 Internal Server Error\r\n\r\n" + "Unable to launch the requested CGI program:\n" + " %s: %s\n", ip ? ip->path : pi->phys, strerror(errno)); +} + +static void cgi_handle_request(struct client *cl, char *url, struct path_info *pi) +{ + unsigned int mode = S_IFREG | S_IXOTH; + + if (!pi->ip && !((pi->stat.st_mode & mode) == mode)) { + uh_client_error(cl, 403, "Forbidden", + "You don't have permission to access %s on this server.", + url); + return; + } + + if (!uh_create_process(cl, pi, url, cgi_main)) { + uh_client_error(cl, 500, "Internal Server Error", + "Failed to create CGI process: %s", strerror(errno)); + return; + } + + return; +} + +static bool check_cgi_path(struct path_info *pi, const char *url) +{ + struct interpreter *ip; + const char *path = pi->phys; + int path_len = strlen(path); + + list_for_each_entry(ip, &interpreters, list) { + int len = strlen(ip->ext); + + if (len >= path_len) + continue; + + if (strcmp(path + path_len - len, ip->ext) != 0) + continue; + + pi->ip = ip; + return true; + } + + pi->ip = NULL; + + if (conf.cgi_docroot_path) + return uh_path_match(conf.cgi_docroot_path, pi->phys); + + return false; +} + +struct dispatch_handler cgi_dispatch = { + .script = true, + .check_path = check_cgi_path, + .handle_request = cgi_handle_request, +}; diff --git a/src/3P/uhttpd/client.c b/src/3P/uhttpd/client.c new file mode 100644 index 00000000..05044ed9 --- /dev/null +++ b/src/3P/uhttpd/client.c @@ -0,0 +1,656 @@ +/* + * 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 "uhttpd.h" +#include "tls.h" + +static LIST_HEAD(clients); +static bool client_done = false; + +int n_clients = 0; +struct config conf = {}; + +const char * const http_versions[] = { + [UH_HTTP_VER_0_9] = "HTTP/0.9", + [UH_HTTP_VER_1_0] = "HTTP/1.0", + [UH_HTTP_VER_1_1] = "HTTP/1.1", +}; + +const char * const http_methods[] = { + [UH_HTTP_MSG_GET] = "GET", + [UH_HTTP_MSG_POST] = "POST", + [UH_HTTP_MSG_HEAD] = "HEAD", + [UH_HTTP_MSG_OPTIONS] = "OPTIONS", +}; + +void uh_http_header(struct client *cl, int code, const char *summary) +{ + struct http_request *r = &cl->request; + struct blob_attr *cur; + const char *enc = "Transfer-Encoding: chunked\r\n"; + const char *conn; + int rem; + + cl->http_code = code; + + if (!uh_use_chunked(cl)) + enc = ""; + + if (r->connection_close) + conn = "Connection: close"; + else + conn = "Connection: Keep-Alive"; + + ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s", + http_versions[cl->request.version], + code, summary, conn, enc); + + if (!r->connection_close) + ustream_printf(cl->us, "Keep-Alive: timeout=%d\r\n", conf.http_keepalive); + + blobmsg_for_each_attr(cur, cl->hdr_response.head, rem) + ustream_printf(cl->us, "%s: %s\r\n", blobmsg_name(cur), + blobmsg_get_string(cur)); +} + +static void uh_connection_close(struct client *cl) +{ + cl->state = CLIENT_STATE_CLOSE; + cl->us->eof = true; + ustream_state_change(cl->us); +} + +static void uh_dispatch_done(struct client *cl) +{ + if (cl->dispatch.free) + cl->dispatch.free(cl); + if (cl->dispatch.req_free) + cl->dispatch.req_free(cl); +} + +static void client_timeout(struct uloop_timeout *timeout) +{ + struct client *cl = container_of(timeout, struct client, timeout); + + cl->state = CLIENT_STATE_CLOSE; + uh_connection_close(cl); +} + +static void uh_set_client_timeout(struct client *cl, int timeout) +{ + cl->timeout.cb = client_timeout; + uloop_timeout_set(&cl->timeout, timeout * 1000); +} + +static void uh_keepalive_poll_cb(struct uloop_timeout *timeout) +{ + struct client *cl = container_of(timeout, struct client, timeout); + int sec = cl->requests > 0 ? conf.http_keepalive : conf.network_timeout; + + uh_set_client_timeout(cl, sec); + cl->us->notify_read(cl->us, 0); +} + +static void uh_poll_connection(struct client *cl) +{ + cl->timeout.cb = uh_keepalive_poll_cb; + uloop_timeout_set(&cl->timeout, 1); +} + +void uh_request_done(struct client *cl) +{ + uh_chunk_eof(cl); + uh_dispatch_done(cl); + blob_buf_init(&cl->hdr_response, 0); + memset(&cl->dispatch, 0, sizeof(cl->dispatch)); + + if (!conf.http_keepalive || cl->request.connection_close) + return uh_connection_close(cl); + + cl->state = CLIENT_STATE_INIT; + cl->requests++; + uh_poll_connection(cl); +} + +void __printf(4, 5) +uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...) +{ + va_list arg; + + uh_http_header(cl, code, summary); + ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n"); + + uh_chunk_printf(cl, "

%s

", summary); + + if (fmt) { + va_start(arg, fmt); + uh_chunk_vprintf(cl, fmt, arg); + va_end(arg); + } + + uh_request_done(cl); +} + +static void uh_header_error(struct client *cl, int code, const char *summary) +{ + uh_client_error(cl, code, summary, NULL); + uh_connection_close(cl); +} + +static int find_idx(const char * const *list, int max, const char *str) +{ + int i; + + for (i = 0; i < max; i++) + if (!strcmp(list[i], str)) + return i; + + return -1; +} + +static int client_parse_request(struct client *cl, char *data) +{ + struct http_request *req = &cl->request; + char *type, *path, *version; + int h_method, h_version; + + type = strtok(data, " "); + path = strtok(NULL, " "); + version = strtok(NULL, " "); + if (!type || !path || !version) + return CLIENT_STATE_DONE; + + blobmsg_add_string(&cl->hdr, "URL", path); + + memset(&cl->request, 0, sizeof(cl->request)); + h_method = find_idx(http_methods, ARRAY_SIZE(http_methods), type); + h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version); + if (h_method < 0 || h_version < 0) { + req->version = UH_HTTP_VER_1_0; + return CLIENT_STATE_DONE; + } + + req->method = h_method; + req->version = h_version; + if (req->version < UH_HTTP_VER_1_1 || req->method == UH_HTTP_MSG_POST || + !conf.http_keepalive) + req->connection_close = true; + + return CLIENT_STATE_HEADER; +} + +static bool client_init_cb(struct client *cl, char *buf, int len) +{ + char *newline; + + newline = strstr(buf, "\r\n"); + if (!newline) + return false; + + if (newline == buf) { + ustream_consume(cl->us, 2); + return true; + } + + *newline = 0; + blob_buf_init(&cl->hdr, 0); + cl->state = client_parse_request(cl, buf); + ustream_consume(cl->us, newline + 2 - buf); + if (cl->state == CLIENT_STATE_DONE) + uh_header_error(cl, 400, "Bad Request"); + + return true; +} + +static bool rfc1918_filter_check(struct client *cl) +{ + if (!conf.rfc1918_filter) + return true; + + if (!uh_addr_rfc1918(&cl->peer_addr) || uh_addr_rfc1918(&cl->srv_addr)) + return true; + + uh_client_error(cl, 403, "Forbidden", + "Rejected request from RFC1918 IP " + "to public server address"); + return false; +} + +static bool tls_redirect_check(struct client *cl) +{ + int rem, port; + struct blob_attr *cur; + char *ptr, *url = NULL, *host = NULL; + + if (cl->tls || !conf.tls_redirect) + return true; + + if ((port = uh_first_tls_port(cl->srv_addr.family)) == -1) + return true; + + blob_for_each_attr(cur, cl->hdr.head, rem) { + if (!strcmp(blobmsg_name(cur), "host")) + host = blobmsg_get_string(cur); + + if (!strcmp(blobmsg_name(cur), "URL")) + url = blobmsg_get_string(cur); + + if (url && host) + break; + } + + if (!url || !host) + return true; + + if ((ptr = strchr(host, ']')) != NULL) + *(ptr+1) = 0; + else if ((ptr = strchr(host, ':')) != NULL) + *ptr = 0; + + cl->request.disable_chunked = true; + cl->request.connection_close = true; + + uh_http_header(cl, 307, "Temporary Redirect"); + + if (port != 443) + ustream_printf(cl->us, "Location: https://%s:%d%s\r\n\r\n", host, port, url); + else + ustream_printf(cl->us, "Location: https://%s%s\r\n\r\n", host, url); + + uh_request_done(cl); + + return false; +} + +static void client_header_complete(struct client *cl) +{ + struct http_request *r = &cl->request; + + if (!rfc1918_filter_check(cl)) + return; + + if (!tls_redirect_check(cl)) + return; + + if (r->expect_cont) + ustream_printf(cl->us, "HTTP/1.1 100 Continue\r\n\r\n"); + + switch(r->ua) { + case UH_UA_MSIE_OLD: + if (r->method != UH_HTTP_MSG_POST) + break; + + /* fall through */ + case UH_UA_SAFARI: + r->connection_close = true; + break; + default: + break; + } + + uh_handle_request(cl); +} + +static void client_parse_header(struct client *cl, char *data) +{ + struct http_request *r = &cl->request; + char *err; + char *name; + char *val; + + if (!*data) { + uloop_timeout_cancel(&cl->timeout); + cl->state = CLIENT_STATE_DATA; + client_header_complete(cl); + return; + } + + val = uh_split_header(data); + if (!val) { + cl->state = CLIENT_STATE_DONE; + return; + } + + for (name = data; *name; name++) + if (isupper(*name)) + *name = tolower(*name); + + if (!strcmp(data, "expect")) { + if (!strcasecmp(val, "100-continue")) + r->expect_cont = true; + else { + uh_header_error(cl, 412, "Precondition Failed"); + return; + } + } else if (!strcmp(data, "content-length")) { + r->content_length = strtoul(val, &err, 0); + if (err && *err) { + uh_header_error(cl, 400, "Bad Request"); + return; + } + } else if (!strcmp(data, "transfer-encoding")) { + if (!strcmp(val, "chunked")) + r->transfer_chunked = true; + } else if (!strcmp(data, "connection")) { + if (!strcasecmp(val, "close")) + r->connection_close = true; + } else if (!strcmp(data, "user-agent")) { + char *str; + + if (strstr(val, "Opera")) + r->ua = UH_UA_OPERA; + else if ((str = strstr(val, "MSIE ")) != NULL) { + r->ua = UH_UA_MSIE_NEW; + if (str[5] && str[6] == '.') { + switch (str[5]) { + case '6': + if (strstr(str, "SV1")) + break; + /* fall through */ + case '5': + case '4': + r->ua = UH_UA_MSIE_OLD; + break; + } + } + } + else if (strstr(val, "Chrome/")) + r->ua = UH_UA_CHROME; + else if (strstr(val, "Safari/") && strstr(val, "Mac OS X")) + r->ua = UH_UA_SAFARI; + else if (strstr(val, "Gecko/")) + r->ua = UH_UA_GECKO; + else if (strstr(val, "Konqueror")) + r->ua = UH_UA_KONQUEROR; + } + + + blobmsg_add_string(&cl->hdr, data, val); + + cl->state = CLIENT_STATE_HEADER; +} + +void client_poll_post_data(struct client *cl) +{ + struct dispatch *d = &cl->dispatch; + struct http_request *r = &cl->request; + char *buf; + int len; + + if (cl->state == CLIENT_STATE_DONE) + return; + + while (1) { + char *sep; + int offset = 0; + int cur_len; + + buf = ustream_get_read_buf(cl->us, &len); + if (!buf || !len) + break; + + if (!d->data_send) + return; + + cur_len = min(r->content_length, len); + if (cur_len) { + if (d->data_blocked) + break; + + if (d->data_send) + cur_len = d->data_send(cl, buf, cur_len); + + r->content_length -= cur_len; + ustream_consume(cl->us, cur_len); + continue; + } + + if (!r->transfer_chunked) + break; + + if (r->transfer_chunked > 1) + offset = 2; + + sep = strstr(buf + offset, "\r\n"); + if (!sep) + break; + + *sep = 0; + + r->content_length = strtoul(buf + offset, &sep, 16); + r->transfer_chunked++; + ustream_consume(cl->us, sep + 2 - buf); + + /* invalid chunk length */ + if (sep && *sep) { + r->content_length = 0; + r->transfer_chunked = 0; + break; + } + + /* empty chunk == eof */ + if (!r->content_length) { + r->transfer_chunked = false; + break; + } + } + + buf = ustream_get_read_buf(cl->us, &len); + if (!r->content_length && !r->transfer_chunked && + cl->state != CLIENT_STATE_DONE) { + if (cl->dispatch.data_done) + cl->dispatch.data_done(cl); + + cl->state = CLIENT_STATE_DONE; + } +} + +static bool client_data_cb(struct client *cl, char *buf, int len) +{ + client_poll_post_data(cl); + return false; +} + +static bool client_header_cb(struct client *cl, char *buf, int len) +{ + char *newline; + int line_len; + + newline = strstr(buf, "\r\n"); + if (!newline) + return false; + + *newline = 0; + client_parse_header(cl, buf); + line_len = newline + 2 - buf; + ustream_consume(cl->us, line_len); + if (cl->state == CLIENT_STATE_DATA) + return client_data_cb(cl, newline + 2, len - line_len); + + return true; +} + +typedef bool (*read_cb_t)(struct client *cl, char *buf, int len); +static read_cb_t read_cbs[] = { + [CLIENT_STATE_INIT] = client_init_cb, + [CLIENT_STATE_HEADER] = client_header_cb, + [CLIENT_STATE_DATA] = client_data_cb, +}; + +void uh_client_read_cb(struct client *cl) +{ + struct ustream *us = cl->us; + char *str; + int len; + + client_done = false; + do { + str = ustream_get_read_buf(us, &len); + if (!str || !len) + break; + + if (cl->state >= array_size(read_cbs) || !read_cbs[cl->state]) + break; + + if (!read_cbs[cl->state](cl, str, len)) { + if (len == us->r.buffer_len && + cl->state != CLIENT_STATE_DATA) + uh_header_error(cl, 413, "Request Entity Too Large"); + break; + } + } while (!client_done); +} + +static void client_close(struct client *cl) +{ + if (cl->refcount) { + cl->state = CLIENT_STATE_CLEANUP; + return; + } + + client_done = true; + n_clients--; + uh_dispatch_done(cl); + uloop_timeout_cancel(&cl->timeout); + if (cl->tls) + uh_tls_client_detach(cl); + ustream_free(&cl->sfd.stream); + close(cl->sfd.fd.fd); + list_del(&cl->list); + blob_buf_free(&cl->hdr); + blob_buf_free(&cl->hdr_response); + free(cl); + + uh_unblock_listeners(); +} + +void uh_client_notify_state(struct client *cl) +{ + struct ustream *s = cl->us; + + if (!s->write_error && cl->state != CLIENT_STATE_CLEANUP) { + if (cl->state == CLIENT_STATE_DATA) + return; + + if (!s->eof || s->w.data_bytes) + return; + } + + return client_close(cl); +} + +static void client_ustream_read_cb(struct ustream *s, int bytes) +{ + struct client *cl = container_of(s, struct client, sfd.stream); + + uh_client_read_cb(cl); +} + +static void client_ustream_write_cb(struct ustream *s, int bytes) +{ + struct client *cl = container_of(s, struct client, sfd.stream); + + if (cl->dispatch.write_cb) + cl->dispatch.write_cb(cl); +} + +static void client_notify_state(struct ustream *s) +{ + struct client *cl = container_of(s, struct client, sfd.stream); + + uh_client_notify_state(cl); +} + +static void set_addr(struct uh_addr *addr, void *src) +{ + struct sockaddr_in *sin = src; + struct sockaddr_in6 *sin6 = src; + + addr->family = sin->sin_family; + if (addr->family == AF_INET) { + addr->port = ntohs(sin->sin_port); + memcpy(&addr->in, &sin->sin_addr, sizeof(addr->in)); + } else { + addr->port = ntohs(sin6->sin6_port); + memcpy(&addr->in6, &sin6->sin6_addr, sizeof(addr->in6)); + } +} + +bool uh_accept_client(int fd, bool tls) +{ + static struct client *next_client; + struct client *cl; + unsigned int sl; + int sfd; + static int client_id = 0; + struct sockaddr_in6 addr; + + if (!next_client) + next_client = calloc(1, sizeof(*next_client)); + + cl = next_client; + + sl = sizeof(addr); + sfd = accept(fd, (struct sockaddr *) &addr, &sl); + if (sfd < 0) + return false; + + set_addr(&cl->peer_addr, &addr); + sl = sizeof(addr); + getsockname(sfd, (struct sockaddr *) &addr, &sl); + set_addr(&cl->srv_addr, &addr); + + cl->us = &cl->sfd.stream; + if (tls) { + uh_tls_client_attach(cl); + } else { + cl->us->notify_read = client_ustream_read_cb; + cl->us->notify_write = client_ustream_write_cb; + cl->us->notify_state = client_notify_state; + } + + cl->us->string_data = true; + ustream_fd_init(&cl->sfd, sfd); + + uh_poll_connection(cl); + list_add_tail(&cl->list, &clients); + + next_client = NULL; + n_clients++; + cl->id = client_id++; + cl->tls = tls; + + return true; +} + +void uh_close_fds(void) +{ + struct client *cl; + + uloop_done(); + uh_close_listen_fds(); + list_for_each_entry(cl, &clients, list) { + close(cl->sfd.fd.fd); + if (cl->dispatch.close_fds) + cl->dispatch.close_fds(cl); + } +} diff --git a/src/3P/uhttpd/file.c b/src/3P/uhttpd/file.c new file mode 100644 index 00000000..047b4dac --- /dev/null +++ b/src/3P/uhttpd/file.c @@ -0,0 +1,900 @@ +/* + * 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. + */ + +#define _BSD_SOURCE +#define _DARWIN_C_SOURCE +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include + +#include + +#include "uhttpd.h" +#include "mimetypes.h" + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +static LIST_HEAD(index_files); +static LIST_HEAD(dispatch_handlers); +static LIST_HEAD(pending_requests); +static int n_requests; + +struct deferred_request { + struct list_head list; + struct dispatch_handler *d; + struct client *cl; + struct path_info pi; + bool called, path; +}; + +struct index_file { + struct list_head list; + const char *name; +}; + +enum file_hdr { + HDR_AUTHORIZATION, + HDR_IF_MODIFIED_SINCE, + HDR_IF_UNMODIFIED_SINCE, + HDR_IF_MATCH, + HDR_IF_NONE_MATCH, + HDR_IF_RANGE, + __HDR_MAX +}; + +void uh_index_add(const char *filename) +{ + struct index_file *idx; + + idx = calloc(1, sizeof(*idx)); + idx->name = filename; + list_add_tail(&idx->list, &index_files); +} + +static char * canonpath(const char *path, char *path_resolved) +{ + const char *path_cpy = path; + char *path_res = path_resolved; + + if (conf.no_symlinks) + return realpath(path, path_resolved); + + /* normalize */ + while ((*path_cpy != '\0') && (path_cpy < (path + PATH_MAX - 2))) { + if (*path_cpy != '/') + goto next; + + /* skip repeating / */ + if (path_cpy[1] == '/') { + path_cpy++; + continue; + } + + /* /./ or /../ */ + if (path_cpy[1] == '.') { + /* skip /./ */ + if ((path_cpy[2] == '/') || (path_cpy[2] == '\0')) { + path_cpy += 2; + continue; + } + + /* collapse /x/../ */ + if ((path_cpy[2] == '.') && + ((path_cpy[3] == '/') || (path_cpy[3] == '\0'))) { + while ((path_res > path_resolved) && (*--path_res != '/')); + + path_cpy += 3; + continue; + } + } + +next: + *path_res++ = *path_cpy++; + } + + /* remove trailing slash if not root / */ + if ((path_res > (path_resolved+1)) && (path_res[-1] == '/')) + path_res--; + else if (path_res == path_resolved) + *path_res++ = '/'; + + *path_res = '\0'; + + return path_resolved; +} + +/* Returns NULL on error. +** NB: improperly encoded URL should give client 400 [Bad Syntax]; returning +** NULL here causes 404 [Not Found], but that's not too unreasonable. */ +struct path_info * +uh_path_lookup(struct client *cl, const char *url) +{ + static char path_phys[PATH_MAX]; + static char path_info[PATH_MAX]; + static struct path_info p; + + const char *docroot = conf.docroot; + int docroot_len = strlen(docroot); + char *pathptr = NULL; + bool slash; + + int i = 0; + int len; + struct stat s; + struct index_file *idx; + + /* back out early if url is undefined */ + if (url == NULL) + return NULL; + + memset(&p, 0, sizeof(p)); + path_phys[0] = 0; + path_info[0] = 0; + + strcpy(uh_buf, docroot); + + /* separate query string from url */ + if ((pathptr = strchr(url, '?')) != NULL) { + p.query = pathptr[1] ? pathptr + 1 : NULL; + + /* urldecode component w/o query */ + if (pathptr > url) { + if (uh_urldecode(&uh_buf[docroot_len], + sizeof(uh_buf) - docroot_len - 1, + url, pathptr - url ) < 0) + return NULL; + } + } + + /* no query string, decode all of url */ + else if (uh_urldecode(&uh_buf[docroot_len], + sizeof(uh_buf) - docroot_len - 1, + url, strlen(url) ) < 0) + return NULL; + + /* create canon path */ + len = strlen(uh_buf); + slash = len && uh_buf[len - 1] == '/'; + len = min(len, sizeof(path_phys) - 1); + + for (i = len; i >= 0; i--) { + char ch = uh_buf[i]; + bool exists; + + if (ch != 0 && ch != '/') + continue; + + uh_buf[i] = 0; + exists = !!canonpath(uh_buf, path_phys); + uh_buf[i] = ch; + + if (!exists) + continue; + + /* test current path */ + if (stat(path_phys, &p.stat)) + continue; + + snprintf(path_info, sizeof(path_info), "%s", uh_buf + i); + break; + } + + /* check whether found path is within docroot */ + if (strncmp(path_phys, docroot, docroot_len) != 0 || + (path_phys[docroot_len] != 0 && + path_phys[docroot_len] != '/')) + return NULL; + + /* is a regular file */ + if (p.stat.st_mode & S_IFREG) { + p.root = docroot; + p.phys = path_phys; + p.name = &path_phys[docroot_len]; + p.info = path_info[0] ? path_info : NULL; + return &p; + } + + if (!(p.stat.st_mode & S_IFDIR)) + return NULL; + + if (path_info[0]) + return NULL; + + pathptr = path_phys + strlen(path_phys); + + /* ensure trailing slash */ + if (pathptr[-1] != '/') { + pathptr[0] = '/'; + pathptr[1] = 0; + pathptr++; + } + + /* if requested url resolves to a directory and a trailing slash + is missing in the request url, redirect the client to the same + url with trailing slash appended */ + if (!slash) { + uh_http_header(cl, 302, "Found"); + if (!uh_use_chunked(cl)) + ustream_printf(cl->us, "Content-Length: 0\r\n"); + ustream_printf(cl->us, "Location: %s%s%s\r\n\r\n", + &path_phys[docroot_len], + p.query ? "?" : "", + p.query ? p.query : ""); + uh_request_done(cl); + p.redirected = 1; + return &p; + } + + /* try to locate index file */ + len = path_phys + sizeof(path_phys) - pathptr - 1; + list_for_each_entry(idx, &index_files, list) { + if (strlen(idx->name) > len) + continue; + + strcpy(pathptr, idx->name); + if (!stat(path_phys, &s) && (s.st_mode & S_IFREG)) { + memcpy(&p.stat, &s, sizeof(p.stat)); + break; + } + + *pathptr = 0; + } + + p.root = docroot; + p.phys = path_phys; + p.name = &path_phys[docroot_len]; + + return p.phys ? &p : NULL; +} + +static const char * uh_file_mime_lookup(const char *path) +{ + const struct mimetype *m = &uh_mime_types[0]; + const char *e; + + while (m->extn) { + e = &path[strlen(path)-1]; + + while (e >= path) { + if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn)) + return m->mime; + + e--; + } + + m++; + } + + return "application/octet-stream"; +} + +static const char * uh_file_mktag(struct stat *s, char *buf, int len) +{ + snprintf(buf, len, "\"%" PRIx64 "-%" PRIx64 "-%" PRIx64 "\"", + s->st_ino, s->st_size, (uint64_t)s->st_mtime); + + return buf; +} + +static time_t uh_file_date2unix(const char *date) +{ + struct tm t; + + memset(&t, 0, sizeof(t)); + + if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL) + return timegm(&t); + + return 0; +} + +static char * uh_file_unix2date(time_t ts, char *buf, int len) +{ + struct tm *t = gmtime(&ts); + + strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT", t); + + return buf; +} + +static char *uh_file_header(struct client *cl, int idx) +{ + if (!cl->dispatch.file.hdr[idx]) + return NULL; + + return (char *) blobmsg_data(cl->dispatch.file.hdr[idx]); +} + +static void uh_file_response_ok_hdrs(struct client *cl, struct stat *s) +{ + char buf[128]; + + if (s) { + ustream_printf(cl->us, "ETag: %s\r\n", uh_file_mktag(s, buf, sizeof(buf))); + ustream_printf(cl->us, "Last-Modified: %s\r\n", + uh_file_unix2date(s->st_mtime, buf, sizeof(buf))); + } + ustream_printf(cl->us, "Date: %s\r\n", + uh_file_unix2date(time(NULL), buf, sizeof(buf))); +} + +static void uh_file_response_200(struct client *cl, struct stat *s) +{ + uh_http_header(cl, 200, "OK"); + return uh_file_response_ok_hdrs(cl, s); +} + +static void uh_file_response_304(struct client *cl, struct stat *s) +{ + uh_http_header(cl, 304, "Not Modified"); + + return uh_file_response_ok_hdrs(cl, s); +} + +static void uh_file_response_412(struct client *cl) +{ + uh_http_header(cl, 412, "Precondition Failed"); +} + +static bool uh_file_if_match(struct client *cl, struct stat *s) +{ + char buf[128]; + const char *tag = uh_file_mktag(s, buf, sizeof(buf)); + char *hdr = uh_file_header(cl, HDR_IF_MATCH); + char *p; + int i; + + if (!hdr) + return true; + + p = &hdr[0]; + for (i = 0; i < strlen(hdr); i++) + { + if ((hdr[i] == ' ') || (hdr[i] == ',')) { + hdr[i++] = 0; + p = &hdr[i]; + } else if (!strcmp(p, "*") || !strcmp(p, tag)) { + return true; + } + } + + uh_file_response_412(cl); + return false; +} + +static int uh_file_if_modified_since(struct client *cl, struct stat *s) +{ + char *hdr = uh_file_header(cl, HDR_IF_MODIFIED_SINCE); + + if (!hdr) + return true; + + if (uh_file_date2unix(hdr) >= s->st_mtime) { + uh_file_response_304(cl, s); + return false; + } + + return true; +} + +static int uh_file_if_none_match(struct client *cl, struct stat *s) +{ + char buf[128]; + const char *tag = uh_file_mktag(s, buf, sizeof(buf)); + char *hdr = uh_file_header(cl, HDR_IF_NONE_MATCH); + char *p; + int i; + + if (!hdr) + return true; + + p = &hdr[0]; + for (i = 0; i < strlen(hdr); i++) { + if ((hdr[i] == ' ') || (hdr[i] == ',')) { + hdr[i++] = 0; + p = &hdr[i]; + } else if (!strcmp(p, "*") || !strcmp(p, tag)) { + if ((cl->request.method == UH_HTTP_MSG_GET) || + (cl->request.method == UH_HTTP_MSG_HEAD)) + uh_file_response_304(cl, s); + else + uh_file_response_412(cl); + + return false; + } + } + + return true; +} + +static int uh_file_if_range(struct client *cl, struct stat *s) +{ + char *hdr = uh_file_header(cl, HDR_IF_RANGE); + + if (hdr) { + uh_file_response_412(cl); + return false; + } + + return true; +} + +static int uh_file_if_unmodified_since(struct client *cl, struct stat *s) +{ + char *hdr = uh_file_header(cl, HDR_IF_UNMODIFIED_SINCE); + + if (hdr && uh_file_date2unix(hdr) <= s->st_mtime) { + uh_file_response_412(cl); + return false; + } + + return true; +} + +static int dirent_cmp(const struct dirent **a, const struct dirent **b) +{ + bool dir_a = !!((*a)->d_type & DT_DIR); + bool dir_b = !!((*b)->d_type & DT_DIR); + + /* directories first */ + if (dir_a != dir_b) + return dir_b - dir_a; + + return alphasort(a, b); +} + +static void list_entries(struct client *cl, struct dirent **files, int count, + const char *path, char *local_path) +{ + const char *suffix = "/"; + const char *type = "directory"; + unsigned int mode = S_IXOTH; + struct stat s; + char *file; + char buf[128]; + int i; + + file = local_path + strlen(local_path); + for (i = 0; i < count; i++) { + const char *name = files[i]->d_name; + bool dir = !!(files[i]->d_type & DT_DIR); + + if (name[0] == '.' && name[1] == 0) + goto next; + + sprintf(file, "%s", name); + if (stat(local_path, &s)) + goto next; + + if (!dir) { + suffix = ""; + mode = S_IROTH; + type = uh_file_mime_lookup(local_path); + } + + if (!(s.st_mode & mode)) + goto next; + + uh_chunk_printf(cl, + "
  • %s%s" + "
    modified: %s" + "
    %s - %.02f kbyte
    " + "
  • ", + path, name, suffix, + name, suffix, + uh_file_unix2date(s.st_mtime, buf, sizeof(buf)), + type, s.st_size / 1024.0); + + *file = 0; +next: + free(files[i]); + } +} + +static void uh_file_dirlist(struct client *cl, struct path_info *pi) +{ + struct dirent **files = NULL; + int count = 0; + + uh_file_response_200(cl, NULL); + ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n"); + + uh_chunk_printf(cl, + "Index of %s" + "

    Index of %s


      ", + pi->name, pi->name); + + count = scandir(pi->phys, &files, NULL, dirent_cmp); + if (count > 0) { + strcpy(uh_buf, pi->phys); + list_entries(cl, files, count, pi->name, uh_buf); + } + free(files); + + uh_chunk_printf(cl, "

    "); + uh_request_done(cl); +} + +static void file_write_cb(struct client *cl) +{ + int fd = cl->dispatch.file.fd; + int r; + + while (cl->us->w.data_bytes < 256) { + r = read(fd, uh_buf, sizeof(uh_buf)); + if (r < 0) { + if (errno == EINTR) + continue; + } + + if (!r) { + uh_request_done(cl); + return; + } + + uh_chunk_write(cl, uh_buf, r); + } +} + +static void uh_file_free(struct client *cl) +{ + close(cl->dispatch.file.fd); +} + +static void uh_file_data(struct client *cl, struct path_info *pi, int fd) +{ + /* test preconditions */ + if (!cl->dispatch.no_cache && + (!uh_file_if_modified_since(cl, &pi->stat) || + !uh_file_if_match(cl, &pi->stat) || + !uh_file_if_range(cl, &pi->stat) || + !uh_file_if_unmodified_since(cl, &pi->stat) || + !uh_file_if_none_match(cl, &pi->stat))) { + ustream_printf(cl->us, "\r\n"); + uh_request_done(cl); + close(fd); + return; + } + + /* write status */ + uh_file_response_200(cl, &pi->stat); + + ustream_printf(cl->us, "Content-Type: %s\r\n", + uh_file_mime_lookup(pi->name)); + + ustream_printf(cl->us, "Content-Length: %" PRIu64 "\r\n\r\n", + pi->stat.st_size); + + + /* send body */ + if (cl->request.method == UH_HTTP_MSG_HEAD) { + uh_request_done(cl); + close(fd); + return; + } + + cl->dispatch.file.fd = fd; + cl->dispatch.write_cb = file_write_cb; + cl->dispatch.free = uh_file_free; + cl->dispatch.close_fds = uh_file_free; + file_write_cb(cl); +} + +static bool __handle_file_request(struct client *cl, char *url); + +static void uh_file_request(struct client *cl, const char *url, + struct path_info *pi, struct blob_attr **tb) +{ + int fd; + struct http_request *req = &cl->request; + char *error_handler; + + if (!(pi->stat.st_mode & S_IROTH)) + goto error; + + if (pi->stat.st_mode & S_IFREG) { + fd = open(pi->phys, O_RDONLY); + if (fd < 0) + goto error; + + req->disable_chunked = true; + cl->dispatch.file.hdr = tb; + uh_file_data(cl, pi, fd); + cl->dispatch.file.hdr = NULL; + return; + } + + if ((pi->stat.st_mode & S_IFDIR)) { + if (conf.no_dirlists) + goto error; + + uh_file_dirlist(cl, pi); + return; + } + +error: + /* check for a previously set 403 redirect status to prevent infinite + recursion when the error page itself lacks sufficient permissions */ + if (conf.error_handler && req->redirect_status != 403) { + req->redirect_status = 403; + error_handler = alloca(strlen(conf.error_handler) + 1); + strcpy(error_handler, conf.error_handler); + if (__handle_file_request(cl, error_handler)) + return; + } + + uh_client_error(cl, 403, "Forbidden", + "You don't have permission to access %s on this server.", + url); +} + +void uh_dispatch_add(struct dispatch_handler *d) +{ + list_add_tail(&d->list, &dispatch_handlers); +} + +static struct dispatch_handler * +dispatch_find(const char *url, struct path_info *pi) +{ + struct dispatch_handler *d; + + list_for_each_entry(d, &dispatch_handlers, list) { + if (pi) { + if (d->check_url) + continue; + + if (d->check_path(pi, url)) + return d; + } else { + if (d->check_path) + continue; + + if (d->check_url(url)) + return d; + } + } + + return NULL; +} + +static void +uh_invoke_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi) +{ + char *url = blobmsg_data(blob_data(cl->hdr.head)); + + n_requests++; + d->handle_request(cl, url, pi); +} + +static void uh_complete_request(struct client *cl) +{ + struct deferred_request *dr; + + n_requests--; + + while (!list_empty(&pending_requests)) { + if (n_requests >= conf.max_script_requests) + return; + + dr = list_first_entry(&pending_requests, struct deferred_request, list); + list_del(&dr->list); + + cl = dr->cl; + dr->called = true; + cl->dispatch.data_blocked = false; + uh_invoke_script(cl, dr->d, dr->path ? &dr->pi : NULL); + client_poll_post_data(cl); + } +} + + +static void +uh_free_pending_request(struct client *cl) +{ + struct deferred_request *dr = cl->dispatch.req_data; + + if (dr->called) + uh_complete_request(cl); + else + list_del(&dr->list); + free(dr); +} + +static int field_len(const char *ptr) +{ + if (!ptr) + return 0; + + return strlen(ptr) + 1; +} + +#define path_info_fields \ + _field(root) \ + _field(phys) \ + _field(name) \ + _field(info) \ + _field(query) \ + _field(auth) + +static void +uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi) +{ + struct deferred_request *dr; + char *_root, *_phys, *_name, *_info, *_query, *_auth; + + cl->dispatch.req_free = uh_free_pending_request; + + if (pi) { + /* allocate enough memory to duplicate all path_info strings in one block */ +#undef _field +#define _field(_name) &_##_name, field_len(pi->_name), + dr = calloc_a(sizeof(*dr), path_info_fields NULL); + + memcpy(&dr->pi, pi, sizeof(*pi)); + dr->path = true; + + /* copy all path_info strings */ +#undef _field +#define _field(_name) if (pi->_name) dr->pi._name = strcpy(_##_name, pi->_name); + path_info_fields + } else { + dr = calloc(1, sizeof(*dr)); + } + + cl->dispatch.req_data = dr; + cl->dispatch.data_blocked = true; + dr->cl = cl; + dr->d = d; + list_add(&dr->list, &pending_requests); +} + +static void +uh_invoke_handler(struct client *cl, struct dispatch_handler *d, char *url, struct path_info *pi) +{ + if (!d->script) + return d->handle_request(cl, url, pi); + + if (n_requests >= conf.max_script_requests) + return uh_defer_script(cl, d, pi); + + cl->dispatch.req_free = uh_complete_request; + uh_invoke_script(cl, d, pi); +} + +static bool __handle_file_request(struct client *cl, char *url) +{ + static const struct blobmsg_policy hdr_policy[__HDR_MAX] = { + [HDR_AUTHORIZATION] = { "authorization", BLOBMSG_TYPE_STRING }, + [HDR_IF_MODIFIED_SINCE] = { "if-modified-since", BLOBMSG_TYPE_STRING }, + [HDR_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", BLOBMSG_TYPE_STRING }, + [HDR_IF_MATCH] = { "if-match", BLOBMSG_TYPE_STRING }, + [HDR_IF_NONE_MATCH] = { "if-none-match", BLOBMSG_TYPE_STRING }, + [HDR_IF_RANGE] = { "if-range", BLOBMSG_TYPE_STRING }, + }; + struct dispatch_handler *d; + struct blob_attr *tb[__HDR_MAX]; + struct path_info *pi; + + pi = uh_path_lookup(cl, url); + if (!pi) + return false; + + if (pi->redirected) + return true; + + blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head)); + if (tb[HDR_AUTHORIZATION]) + pi->auth = blobmsg_data(tb[HDR_AUTHORIZATION]); + + if (!uh_auth_check(cl, pi)) + return true; + + d = dispatch_find(url, pi); + if (d) + uh_invoke_handler(cl, d, url, pi); + else + uh_file_request(cl, url, pi, tb); + + return true; +} + +static char *uh_handle_alias(char *old_url) +{ + struct alias *alias; + static char *new_url; + static int url_len; + + if (!list_empty(&conf.cgi_alias)) list_for_each_entry(alias, &conf.cgi_alias, list) { + int old_len; + int new_len; + int path_len = 0; + + if (!uh_path_match(alias->alias, old_url)) + continue; + + if (alias->path) + path_len = strlen(alias->path); + + old_len = strlen(old_url) + 1; + new_len = old_len + MAX(conf.cgi_prefix_len, path_len); + + if (new_len > url_len) { + new_url = realloc(new_url, new_len); + url_len = new_len; + } + + *new_url = '\0'; + + if (alias->path) + strcpy(new_url, alias->path); + else if (conf.cgi_prefix) + strcpy(new_url, conf.cgi_prefix); + strcat(new_url, old_url); + + return new_url; + } + return old_url; +} + +void uh_handle_request(struct client *cl) +{ + struct http_request *req = &cl->request; + struct dispatch_handler *d; + char *url = blobmsg_data(blob_data(cl->hdr.head)); + char *error_handler; + + blob_buf_init(&cl->hdr_response, 0); + url = uh_handle_alias(url); + + uh_handler_run(cl, &url, false); + if (!url) + return; + + req->redirect_status = 200; + d = dispatch_find(url, NULL); + if (d) + return uh_invoke_handler(cl, d, url, NULL); + + if (__handle_file_request(cl, url)) + return; + + if (uh_handler_run(cl, &url, true)) { + if (!url) + return; + + uh_handler_run(cl, &url, false); + if (__handle_file_request(cl, url)) + return; + } + + req->redirect_status = 404; + if (conf.error_handler) { + error_handler = alloca(strlen(conf.error_handler) + 1); + strcpy(error_handler, conf.error_handler); + if (__handle_file_request(cl, error_handler)) + return; + } + + uh_client_error(cl, 404, "Not Found", "The requested URL %s was not found on this server.", url); +} diff --git a/src/3P/uhttpd/handler.c b/src/3P/uhttpd/handler.c new file mode 100644 index 00000000..04e71e0f --- /dev/null +++ b/src/3P/uhttpd/handler.c @@ -0,0 +1,258 @@ +/* + * uhttpd - Tiny single-threaded httpd + * + * Copyright (C) 2015 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 "uhttpd.h" + +struct handler { + struct list_head list; + + struct json_script_file *request; + struct json_script_file *fallback; +}; + +static LIST_HEAD(handlers); +static struct json_script_ctx handler_ctx; +static struct env_var *cur_vars; +static struct blob_buf b; +static int handler_ret; +static struct client *cur_client; +static char **cur_url; + +static void +handle_redirect(struct json_script_ctx *ctx, struct blob_attr *data) +{ + struct client *cl = cur_client; + static struct blobmsg_policy policy[3] = { + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_INT32 }, + { .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[3]; + const char *status = "Found"; + int code = 302; + + blobmsg_parse_array(policy, ARRAY_SIZE(policy), tb, blobmsg_data(data), blobmsg_data_len(data)); + if (!tb[0]) + return; + + if (tb[1]) { + code = blobmsg_get_u32(tb[1]); + if (tb[2]) + status = blobmsg_get_string(tb[2]); + } + + uh_http_header(cl, code, status); + if (!uh_use_chunked(cl)) + ustream_printf(cl->us, "Content-Length: 0\r\n"); + ustream_printf(cl->us, "Location: %s\r\n\r\n", + blobmsg_get_string(tb[0])); + uh_request_done(cl); + *cur_url = NULL; + + handler_ret = 1; + json_script_abort(ctx); +} + +static void +handle_set_uri(struct json_script_ctx *ctx, struct blob_attr *data) +{ + struct client *cl = cur_client; + static struct blobmsg_policy policy = { + .type = BLOBMSG_TYPE_STRING, + }; + struct blob_attr *tb; + struct blob_attr *old_url = blob_data(cl->hdr.head); + + blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data)); + if (!tb) + return; + + blob_buf_init(&b, 0); + blob_put_raw(&b, blob_next(old_url), blob_len(cl->hdr.head) - blob_pad_len(old_url)); + + /* replace URL in client header cache */ + blob_buf_init(&cl->hdr, 0); + blobmsg_add_string(&cl->hdr, "URL", blobmsg_get_string(tb)); + blob_put_raw(&cl->hdr, blob_data(b.head), blob_len(b.head)); + *cur_url = blobmsg_data(blob_data(cl->hdr.head)); + cur_vars = NULL; + + blob_buf_init(&b, 0); + + handler_ret = 1; + json_script_abort(ctx); +} + +static void +handle_add_header(struct json_script_ctx *ctx, struct blob_attr *data) +{ + struct client *cl = cur_client; + static struct blobmsg_policy policy[2] = { + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[2]; + + blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb, blobmsg_data(data), blobmsg_data_len(data)); + if (!tb[0] || !tb[1]) + return; + + blobmsg_add_string(&cl->hdr_response, blobmsg_get_string(tb[0]), + blobmsg_get_string(tb[1])); +} + +static void +handle_no_cache(struct json_script_ctx *ctx, struct blob_attr *data) +{ + struct client *cl = cur_client; + + cl->dispatch.no_cache = true; +} + +static void +handle_command(struct json_script_ctx *ctx, const char *name, + struct blob_attr *data, struct blob_attr *vars) +{ + static const struct { + const char *name; + void (*func)(struct json_script_ctx *ctx, struct blob_attr *data); + } cmds[] = { + { "redirect", handle_redirect }, + { "rewrite", handle_set_uri }, + { "add-header", handle_add_header }, + { "no-cache", handle_no_cache }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + if (!strcmp(cmds[i].name, name)) { + cmds[i].func(ctx, data); + return; + } + } +} + +static const char * +handle_var(struct json_script_ctx *ctx, const char *name, + struct blob_attr *vars) +{ + struct client *cl = cur_client; + struct env_var *cur; + static struct path_info empty_path; + + if (!cur_vars) { + struct path_info *p = uh_path_lookup(cl, *cur_url); + + if (!p) + p = &empty_path; + + cur_vars = uh_get_process_vars(cl, p); + } + + for (cur = cur_vars; cur->name; cur++) { + if (!strcmp(cur->name, name)) + return cur->value; + } + return NULL; +} + +static void +handler_init(void) +{ + if (handler_ctx.handle_command) + return; + + json_script_init(&handler_ctx); + handler_ctx.handle_command = handle_command; + handler_ctx.handle_var = handle_var; +} + +static bool set_handler(struct json_script_file **dest, struct blob_attr *data) +{ + if (!data) + return true; + + *dest = json_script_file_from_blobmsg(NULL, blobmsg_data(data), blobmsg_data_len(data)); + return *dest; +} + +int uh_handler_add(const char *file) +{ + enum { + H_REQUEST, + H_FALLBACK, + __H_MAX, + }; + struct blobmsg_policy policy[__H_MAX] = { + [H_REQUEST] = { "request", BLOBMSG_TYPE_ARRAY }, + [H_FALLBACK] = { "fallback", BLOBMSG_TYPE_ARRAY }, + }; + struct blob_attr *tb[__H_MAX]; + struct handler *h; + + handler_init(); + blob_buf_init(&b, 0); + + if (!blobmsg_add_json_from_file(&b, file)) + return -1; + + blobmsg_parse(policy, __H_MAX, tb, blob_data(b.head), blob_len(b.head)); + if (!tb[H_REQUEST] && !tb[H_FALLBACK]) + return -1; + + h = calloc(1, sizeof(*h)); + if (!set_handler(&h->request, tb[H_REQUEST]) || + !set_handler(&h->fallback, tb[H_FALLBACK])) { + free(h->request); + free(h->fallback); + free(h); + return -1; + } + + list_add_tail(&h->list, &handlers); + return 0; +} + +int uh_handler_run(struct client *cl, char **url, bool fallback) +{ + struct json_script_file *f; + struct handler *h; + + cur_client = cl; + cur_url = url; + cur_vars = NULL; + + handler_ret = 0; + + list_for_each_entry(h, &handlers, list) { + f = fallback ? h->fallback : h->request; + if (!f) + continue; + + blob_buf_init(&b, 0); + json_script_run_file(&handler_ctx, f, b.head); + if (handler_ctx.abort) + break; + } + + return handler_ret; +} diff --git a/src/3P/uhttpd/listen.c b/src/3P/uhttpd/listen.c new file mode 100644 index 00000000..92ca680a --- /dev/null +++ b/src/3P/uhttpd/listen.c @@ -0,0 +1,222 @@ +/* + * 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 "uhttpd.h" + +struct listener { + struct list_head list; + struct uloop_fd fd; + int socket; + int n_clients; + struct sockaddr_in6 addr; + bool tls; + bool blocked; +}; + +static LIST_HEAD(listeners); +static int n_blocked; + +void uh_close_listen_fds(void) +{ + struct listener *l; + + list_for_each_entry(l, &listeners, list) + close(l->fd.fd); +} + +static void uh_block_listener(struct listener *l) +{ + uloop_fd_delete(&l->fd); + n_blocked++; + l->blocked = true; +} + +static void uh_poll_listeners(struct uloop_timeout *timeout) +{ + struct listener *l; + + if ((!n_blocked && conf.max_connections) || + n_clients >= conf.max_connections) + return; + + list_for_each_entry(l, &listeners, list) { + if (!l->blocked) + continue; + + l->fd.cb(&l->fd, ULOOP_READ); + if (n_clients >= conf.max_connections) + break; + + n_blocked--; + l->blocked = false; + uloop_fd_add(&l->fd, ULOOP_READ); + } +} + +void uh_unblock_listeners(void) +{ + static struct uloop_timeout poll_timer = { + .cb = uh_poll_listeners + }; + + uloop_timeout_set(&poll_timer, 1); +} + +static void listener_cb(struct uloop_fd *fd, unsigned int events) +{ + struct listener *l = container_of(fd, struct listener, fd); + + while (1) { + if (!uh_accept_client(fd->fd, l->tls)) + break; + } + + if (conf.max_connections && n_clients >= conf.max_connections) + uh_block_listener(l); +} + +void uh_setup_listeners(void) +{ + struct listener *l; + int yes = 1; + + list_for_each_entry(l, &listeners, list) { + int sock = l->fd.fd; + + /* TCP keep-alive */ + if (conf.tcp_keepalive > 0) { +#ifdef linux + int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt; + + tcp_ka_idl = 1; + tcp_ka_cnt = 3; + tcp_ka_int = conf.tcp_keepalive; + + setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &tcp_ka_idl, sizeof(tcp_ka_idl)); + setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int)); + setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &tcp_ka_cnt, sizeof(tcp_ka_cnt)); +#endif + + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); + } + + l->fd.cb = listener_cb; + uloop_fd_add(&l->fd, ULOOP_READ); + } +} + +int uh_socket_bind(const char *host, const char *port, bool tls) +{ + int sock = -1; + int yes = 1; + int status; + int bound = 0; + struct listener *l = NULL; + struct addrinfo *addrs = NULL, *p = NULL; + static struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE, + }; + + if ((status = getaddrinfo(host, port, &hints, &addrs)) != 0) { + fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status)); + return 0; + } + + /* try to bind a new socket to each found address */ + for (p = addrs; p; p = p->ai_next) { + /* get the socket */ + sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sock < 0) { + perror("socket()"); + goto error; + } + + /* "address already in use" */ + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) { + perror("setsockopt()"); + goto error; + } + + /* required to get parallel v4 + v6 working */ + if (p->ai_family == AF_INET6 && + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) { + perror("setsockopt()"); + goto error; + } + + /* bind */ + if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) { + perror("bind()"); + goto error; + } + + /* listen */ + if (listen(sock, UH_LIMIT_CLIENTS) < 0) { + perror("listen()"); + goto error; + } + + fd_cloexec(sock); + + l = calloc(1, sizeof(*l)); + if (!l) + goto error; + + l->fd.fd = sock; + l->tls = tls; + l->addr = *(struct sockaddr_in6 *)p->ai_addr; + list_add_tail(&l->list, &listeners); + bound++; + + continue; + +error: + if (sock > -1) + close(sock); + } + + freeaddrinfo(addrs); + + return bound; +} + +int uh_first_tls_port(int family) +{ + struct listener *l; + int tls_port = -1; + + list_for_each_entry(l, &listeners, list) { + if (!l->tls || l->addr.sin6_family != family) + continue; + + if (tls_port != -1 && ntohs(l->addr.sin6_port) != 443) + continue; + + tls_port = ntohs(l->addr.sin6_port); + } + + return tls_port; +} diff --git a/src/3P/uhttpd/lua.c b/src/3P/uhttpd/lua.c new file mode 100644 index 00000000..21349048 --- /dev/null +++ b/src/3P/uhttpd/lua.c @@ -0,0 +1,300 @@ +/* + * 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_LUA_CB "handle_request" + +static const struct uhttpd_ops *ops; +static struct config *_conf; +#define conf (*_conf) + +static lua_State *_L; + +static int uh_lua_recv(lua_State *L) +{ + static struct pollfd pfd = { + .fd = STDIN_FILENO, + .events = POLLIN, + }; + luaL_Buffer B; + int data_len = 0; + int len; + int r; + + len = luaL_checknumber(L, 1); + luaL_buffinit(L, &B); + while(len > 0) { + char *buf; + + buf = luaL_prepbuffer(&B); + r = read(STDIN_FILENO, buf, LUAL_BUFFERSIZE); + 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; + } + if (!r) + break; + + luaL_addsize(&B, r); + data_len += r; + if (r != LUAL_BUFFERSIZE) + break; + } + + luaL_pushresult(&B); + lua_pushnumber(L, data_len); + if (data_len > 0) { + lua_pushvalue(L, -2); + lua_remove(L, -3); + return 2; + } else { + lua_remove(L, -2); + return 1; + } +} + +static int uh_lua_send(lua_State *L) +{ + const char *buf; + size_t len; + + buf = luaL_checklstring(L, 1, &len); + if (len > 0) + len = write(STDOUT_FILENO, buf, len); + + lua_pushnumber(L, len); + return 1; +} + +static int +uh_lua_strconvert(lua_State *L, int (*convert)(char *, int, const char *, int)) +{ + const char *in_buf; + static char out_buf[4096]; + size_t in_len; + int out_len; + + in_buf = luaL_checklstring(L, 1, &in_len); + out_len = convert(out_buf, sizeof(out_buf), in_buf, in_len); + + if (out_len < 0) { + const char *error; + + if (out_len == -1) + error = "buffer overflow"; + else + error = "malformed string"; + + luaL_error(L, "%s on URL conversion\n", error); + } + + lua_pushlstring(L, out_buf, out_len); + return 1; +} + +static int uh_lua_urldecode(lua_State *L) +{ + return uh_lua_strconvert(L, ops->urldecode); +} + +static int uh_lua_urlencode(lua_State *L) +{ + return uh_lua_strconvert(L, ops->urlencode); +} + +static lua_State *uh_lua_state_init(void) +{ + const char *msg = "(unknown error)"; + const char *status; + lua_State *L; + int ret; + + L = luaL_newstate(); + luaL_openlibs(L); + + /* build uhttpd api table */ + lua_newtable(L); + + lua_pushcfunction(L, uh_lua_send); + lua_setfield(L, -2, "send"); + + lua_pushcfunction(L, uh_lua_send); + lua_setfield(L, -2, "sendc"); + + lua_pushcfunction(L, uh_lua_recv); + lua_setfield(L, -2, "recv"); + + lua_pushcfunction(L, uh_lua_urldecode); + lua_setfield(L, -2, "urldecode"); + + lua_pushcfunction(L, uh_lua_urlencode); + lua_setfield(L, -2, "urlencode"); + + lua_pushstring(L, conf.docroot); + lua_setfield(L, -2, "docroot"); + + lua_setglobal(L, "uhttpd"); + + ret = luaL_loadfile(L, conf.lua_handler); + if (ret) { + status = "loading"; + goto error; + } + + ret = lua_pcall(L, 0, 0, 0); + if (ret) { + status = "initializing"; + goto error; + } + + lua_getglobal(L, UH_LUA_CB); + if (!lua_isfunction(L, -1)) { + fprintf(stderr, "Error: Lua handler provides no " UH_LUA_CB "() callback.\n"); + exit(1); + } + + return L; + +error: + if (!lua_isnil(L, -1)) + msg = lua_tostring(L, -1); + + fprintf(stderr, "Error %s Lua handler: %s\n", status, msg); + exit(1); + return NULL; +} + +static void lua_main(struct client *cl, struct path_info *pi, char *url) +{ + struct blob_attr *cur; + const char *error; + struct env_var *var; + lua_State *L = _L; + int path_len, prefix_len; + char *str; + int rem; + + lua_getglobal(L, UH_LUA_CB); + + /* new env table for this request */ + lua_newtable(L); + + prefix_len = strlen(conf.lua_prefix); + path_len = strlen(url); + str = strchr(url, '?'); + if (str) { + if (*(str + 1)) + pi->query = str + 1; + path_len = str - url; + } + if (path_len > prefix_len) { + lua_pushlstring(L, url + prefix_len, + path_len - prefix_len); + lua_setfield(L, -2, "PATH_INFO"); + } + + for (var = ops->get_process_vars(cl, pi); var->name; var++) { + if (!var->value) + continue; + + lua_pushstring(L, var->value); + lua_setfield(L, -2, var->name); + } + + lua_pushnumber(L, 0.9 + (cl->request.version / 10.0)); + lua_setfield(L, -2, "HTTP_VERSION"); + + lua_newtable(L); + blob_for_each_attr(cur, cl->hdr.head, rem) { + lua_pushstring(L, blobmsg_data(cur)); + lua_setfield(L, -2, blobmsg_name(cur)); + } + lua_setfield(L, -2, "headers"); + + switch(lua_pcall(L, 1, 0, 0)) { + case LUA_ERRMEM: + case LUA_ERRRUN: + error = luaL_checkstring(L, -1); + if (!error) + error = "(unknown error)"; + + printf("Status: 500 Internal Server Error\r\n\r\n" + "Unable to launch the requested Lua program:\n" + " %s: %s\n", pi->phys, error); + } + + exit(0); +} + +static void lua_handle_request(struct client *cl, char *url, struct path_info *pi) +{ + static struct path_info _pi; + + pi = &_pi; + pi->name = conf.lua_prefix; + pi->phys = conf.lua_handler; + + if (!ops->create_process(cl, pi, url, lua_main)) { + ops->client_error(cl, 500, "Internal Server Error", + "Failed to create CGI process: %s", strerror(errno)); + } +} + +static bool check_lua_url(const char *url) +{ + return ops->path_match(conf.lua_prefix, url); +} + +static struct dispatch_handler lua_dispatch = { + .script = true, + .check_url = check_lua_url, + .handle_request = lua_handle_request, +}; + +static int lua_plugin_init(const struct uhttpd_ops *o, struct config *c) +{ + ops = o; + _conf = c; + _L = uh_lua_state_init(); + ops->dispatch_add(&lua_dispatch); + return 0; +} + +struct uhttpd_plugin uhttpd_plugin = { + .init = lua_plugin_init, +}; diff --git a/src/3P/uhttpd/main.c b/src/3P/uhttpd/main.c new file mode 100644 index 00000000..fb276657 --- /dev/null +++ b/src/3P/uhttpd/main.c @@ -0,0 +1,527 @@ +/* + * 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. + */ + +#define _BSD_SOURCE +#define _GNU_SOURCE +#define _XOPEN_SOURCE 700 +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "uhttpd.h" +#include "tls.h" + +char uh_buf[4096]; + +static int run_server(void) +{ + uloop_init(); + uh_setup_listeners(); + uh_plugin_post_init(); + uloop_run(); + + return 0; +} + +static void uh_config_parse(void) +{ + const char *path = conf.file; + FILE *c; + char line[512]; + char *col1; + char *col2; + char *eol; + + if (!path) + path = "/etc/httpd.conf"; + + c = fopen(path, "r"); + if (!c) + return; + + memset(line, 0, sizeof(line)); + + while (fgets(line, sizeof(line) - 1, c)) { + if ((line[0] == '/') && (strchr(line, ':') != NULL)) { + if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(col2 = strchr(col1, ':')) || (*col2++ = 0) || + !(eol = strchr(col2, '\n')) || (*eol++ = 0)) + continue; + + uh_auth_add(line, col1, col2); + } else if (!strncmp(line, "I:", 2)) { + if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(eol = strchr(col1, '\n')) || (*eol++ = 0)) + continue; + + uh_index_add(strdup(col1)); + } else if (!strncmp(line, "E404:", 5)) { + if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(eol = strchr(col1, '\n')) || (*eol++ = 0)) + continue; + + conf.error_handler = strdup(col1); + } + else if ((line[0] == '*') && (strchr(line, ':') != NULL)) { + if (!(col1 = strchr(line, '*')) || (*col1++ = 0) || + !(col2 = strchr(col1, ':')) || (*col2++ = 0) || + !(eol = strchr(col2, '\n')) || (*eol++ = 0)) + continue; + + uh_interpreter_add(col1, col2); + } + } + + fclose(c); +} + +static int add_listener_arg(char *arg, bool tls) +{ + char *host = NULL; + char *port = arg; + char *s; + int l; + + s = strrchr(arg, ':'); + if (s) { + host = arg; + port = s + 1; + *s = 0; + } + + if (host && *host == '[') { + l = strlen(host); + if (l >= 2) { + host[l-1] = 0; + host++; + } + } + + return uh_socket_bind(host, port, tls); +} + +static int usage(const char *name) +{ + fprintf(stderr, + "Usage: %s -p [addr:]port -h docroot\n" + " -f Do not fork to background\n" + " -c file Configuration file, default is '/etc/httpd.conf'\n" + " -p [addr:]port Bind to specified address and port, multiple allowed\n" +#ifdef HAVE_TLS + " -s [addr:]port Like -p but provide HTTPS on this port\n" + " -C file ASN.1 server certificate file\n" + " -K file ASN.1 server private key file\n" + " -q Redirect all HTTP requests to HTTPS\n" +#endif + " -h directory Specify the document root, default is '.'\n" + " -E string Use given virtual URL as 404 error handler\n" + " -I string Use given filename as index for directories, multiple allowed\n" + " -S Do not follow symbolic links outside of the docroot\n" + " -D Do not allow directory listings, send 403 instead\n" + " -R Enable RFC1918 filter\n" + " -n count Maximum allowed number of concurrent script requests\n" + " -N count Maximum allowed number of concurrent connections\n" +#ifdef HAVE_LUA + " -l string URL prefix for Lua handler, default is '/lua'\n" + " -L file Lua handler script, omit to disable Lua\n" +#endif +#ifdef HAVE_UBUS + " -u string URL prefix for UBUS via JSON-RPC handler\n" + " -U file Override ubus socket path\n" + " -a Do not authenticate JSON-RPC requests against UBUS session api\n" + " -X Enable CORS HTTP headers on JSON-RPC api\n" +#endif + " -x string URL prefix for CGI handler, default is '/cgi-bin'\n" + " -y alias[=path] URL alias handle\n" + " -i .ext=path Use interpreter at path for files with the given extension\n" + " -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n" + " -T seconds Network timeout in seconds, default is 30\n" + " -k seconds HTTP keepalive timeout\n" + " -d string URL decode given string\n" + " -r string Specify basic auth realm\n" + " -m string MD5 crypt given string\n" + "\n", name + ); + return 1; +} + +static void init_defaults_pre(void) +{ + conf.script_timeout = 60; + conf.network_timeout = 30; + conf.http_keepalive = 20; + conf.max_script_requests = 3; + conf.max_connections = 100; + conf.realm = "Protected Area"; + conf.cgi_prefix = "/cgi-bin"; + conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin"; + INIT_LIST_HEAD(&conf.cgi_alias); +} + +static void init_defaults_post(void) +{ + uh_index_add("index.html"); + uh_index_add("index.htm"); + uh_index_add("default.html"); + uh_index_add("default.htm"); + + if (conf.cgi_prefix) { + char *str = malloc(strlen(conf.docroot) + strlen(conf.cgi_prefix) + 1); + + strcpy(str, conf.docroot); + strcat(str, conf.cgi_prefix); + conf.cgi_docroot_path = str; + conf.cgi_prefix_len = strlen(conf.cgi_prefix); + }; +} + +static void fixup_prefix(char *str) +{ + int len; + + if (!str || !str[0]) + return; + + len = strlen(str) - 1; + + while (len >= 0 && str[len] == '/') + len--; + + str[len + 1] = 0; +} + +int main(int argc, char **argv) +{ + struct alias *alias; + bool nofork = false; + char *port; + int opt, ch; + int cur_fd; + int bound = 0; +#ifdef HAVE_TLS + int n_tls = 0; + const char *tls_key = NULL, *tls_crt = NULL; +#endif + + BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX); + + uh_dispatch_add(&cgi_dispatch); + init_defaults_pre(); + signal(SIGPIPE, SIG_IGN); + + while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) { + switch(ch) { +#ifdef HAVE_TLS + case 'C': + tls_crt = optarg; + break; + + case 'K': + tls_key = optarg; + break; + + case 'q': + conf.tls_redirect = 1; + break; + + case 's': + n_tls++; + /* fall through */ +#else + case 'C': + case 'K': + case 'q': + case 's': + fprintf(stderr, "uhttpd: TLS support not compiled, " + "ignoring -%c\n", ch); + break; +#endif + case 'p': + optarg = strdup(optarg); + bound += add_listener_arg(optarg, (ch == 's')); + break; + + case 'h': + if (!realpath(optarg, uh_buf)) { + fprintf(stderr, "Error: Invalid directory %s: %s\n", + optarg, strerror(errno)); + exit(1); + } + conf.docroot = strdup(uh_buf); + break; + + case 'H': + if (uh_handler_add(optarg)) { + fprintf(stderr, "Error: Failed to load handler script %s\n", + optarg); + exit(1); + } + break; + + case 'E': + if (optarg[0] != '/') { + fprintf(stderr, "Error: Invalid error handler: %s\n", + optarg); + exit(1); + } + conf.error_handler = optarg; + break; + + case 'I': + if (optarg[0] == '/') { + fprintf(stderr, "Error: Invalid index page: %s\n", + optarg); + exit(1); + } + uh_index_add(optarg); + break; + + case 'S': + conf.no_symlinks = 1; + break; + + case 'D': + conf.no_dirlists = 1; + break; + + case 'R': + conf.rfc1918_filter = 1; + break; + + case 'n': + conf.max_script_requests = atoi(optarg); + break; + + case 'N': + conf.max_connections = atoi(optarg); + break; + + case 'x': + fixup_prefix(optarg); + conf.cgi_prefix = optarg; + break; + + case 'y': + alias = calloc(1, sizeof(*alias)); + if (!alias) { + fprintf(stderr, "Error: failed to allocate alias\n"); + exit(1); + } + alias->alias = strdup(optarg); + alias->path = strchr(alias->alias, '='); + if (alias->path) + *alias->path++ = 0; + list_add(&alias->list, &conf.cgi_alias); + break; + + case 'i': + optarg = strdup(optarg); + port = strchr(optarg, '='); + if (optarg[0] != '.' || !port) { + fprintf(stderr, "Error: Invalid interpreter: %s\n", + optarg); + exit(1); + } + + *port++ = 0; + uh_interpreter_add(optarg, port); + break; + + case 't': + conf.script_timeout = atoi(optarg); + break; + + case 'T': + conf.network_timeout = atoi(optarg); + break; + + case 'k': + conf.http_keepalive = atoi(optarg); + break; + + case 'A': + conf.tcp_keepalive = atoi(optarg); + break; + + case 'f': + nofork = 1; + break; + + case 'd': + optarg = strdup(optarg); + port = alloca(strlen(optarg) + 1); + if (!port) + return -1; + + /* "decode" plus to space to retain compat */ + for (opt = 0; optarg[opt]; opt++) + if (optarg[opt] == '+') + optarg[opt] = ' '; + + /* opt now contains strlen(optarg) -- no need to re-scan */ + if (uh_urldecode(port, opt, optarg, opt) < 0) { + fprintf(stderr, "uhttpd: invalid encoding\n"); + return -1; + } + + printf("%s", port); + return 0; + break; + + /* basic auth realm */ + case 'r': + conf.realm = optarg; + break; + + /* md5 crypt */ + case 'm': + printf("%s\n", crypt(optarg, "$1$")); + return 0; + break; + + /* config file */ + case 'c': + conf.file = optarg; + break; + +#ifdef HAVE_LUA + case 'l': + conf.lua_prefix = optarg; + break; + + case 'L': + conf.lua_handler = optarg; + break; +#else + case 'l': + case 'L': + fprintf(stderr, "uhttpd: Lua support not compiled, " + "ignoring -%c\n", ch); + break; +#endif +#ifdef HAVE_UBUS + case 'a': + conf.ubus_noauth = 1; + break; + + case 'u': + conf.ubus_prefix = optarg; + break; + + case 'U': + conf.ubus_socket = optarg; + break; + + case 'X': + conf.ubus_cors = 1; + break; +#else + case 'a': + case 'u': + case 'U': + case 'X': + fprintf(stderr, "uhttpd: UBUS support not compiled, " + "ignoring -%c\n", ch); + break; +#endif + default: + return usage(argv[0]); + } + } + + uh_config_parse(); + + if (!conf.docroot) { + if (!realpath(".", uh_buf)) { + fprintf(stderr, "Error: Unable to determine work dir\n"); + return 1; + } + conf.docroot = strdup(uh_buf); + } + + init_defaults_post(); + + if (!bound) { + fprintf(stderr, "Error: No sockets bound, unable to continue\n"); + return 1; + } + +#ifdef HAVE_TLS + if (n_tls) { + if (!tls_crt || !tls_key) { + fprintf(stderr, "Please specify a certificate and " + "a key file to enable SSL support\n"); + return 1; + } + + if (uh_tls_init(tls_key, tls_crt)) + return 1; + } +#endif + +#ifdef HAVE_LUA + if (conf.lua_handler || conf.lua_prefix) { + if (!conf.lua_handler || !conf.lua_prefix) { + fprintf(stderr, "Need handler and prefix to enable Lua support\n"); + return 1; + } + if (uh_plugin_init("uhttpd_lua.so")) + return 1; + } +#endif +#ifdef HAVE_UBUS + if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so")) + return 1; +#endif + + /* fork (if not disabled) */ + if (!nofork) { + switch (fork()) { + case -1: + perror("fork()"); + exit(1); + + case 0: + /* daemon setup */ + if (chdir("/")) + perror("chdir()"); + + cur_fd = open("/dev/null", O_WRONLY); + if (cur_fd > 0) { + dup2(cur_fd, 0); + dup2(cur_fd, 1); + dup2(cur_fd, 2); + } + + break; + + default: + exit(0); + } + } + + return run_server(); +} diff --git a/src/3P/uhttpd/mimetypes.h b/src/3P/uhttpd/mimetypes.h new file mode 100644 index 00000000..06514861 --- /dev/null +++ b/src/3P/uhttpd/mimetypes.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef _UHTTPD_MIMETYPES_ + +struct mimetype { + const char *extn; + const char *mime; +}; + +static const struct mimetype uh_mime_types[] = { + + { "txt", "text/plain" }, + { "log", "text/plain" }, + { "js", "text/javascript" }, + { "css", "text/css" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "diff", "text/x-patch" }, + { "patch", "text/x-patch" }, + { "c", "text/x-csrc" }, + { "h", "text/x-chdr" }, + { "o", "text/x-object" }, + { "ko", "text/x-object" }, + + { "bmp", "image/bmp" }, + { "gif", "image/gif" }, + { "png", "image/png" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "svg", "image/svg+xml" }, + + { "json", "application/json" }, + { "jsonp", "application/javascript" }, + { "zip", "application/zip" }, + { "pdf", "application/pdf" }, + { "xml", "application/xml" }, + { "xsl", "application/xml" }, + { "doc", "application/msword" }, + { "ppt", "application/vnd.ms-powerpoint" }, + { "xls", "application/vnd.ms-excel" }, + { "odt", "application/vnd.oasis.opendocument.text" }, + { "odp", "application/vnd.oasis.opendocument.presentation" }, + { "pl", "application/x-perl" }, + { "sh", "application/x-shellscript" }, + { "php", "application/x-php" }, + { "deb", "application/x-deb" }, + { "iso", "application/x-cd-image" }, + { "tar.gz", "application/x-compressed-tar" }, + { "tgz", "application/x-compressed-tar" }, + { "gz", "application/x-gzip" }, + { "tar.bz2", "application/x-bzip-compressed-tar" }, + { "tbz", "application/x-bzip-compressed-tar" }, + { "bz2", "application/x-bzip" }, + { "tar", "application/x-tar" }, + { "rar", "application/x-rar-compressed" }, + + { "mp3", "audio/mpeg" }, + { "ogg", "audio/x-vorbis+ogg" }, + { "wav", "audio/x-wav" }, + + { "mpg", "video/mpeg" }, + { "mpeg", "video/mpeg" }, + { "avi", "video/x-msvideo" }, + + { "README", "text/plain" }, + { "log", "text/plain" }, + { "cfg", "text/plain" }, + { "conf", "text/plain" }, + + { "pac", "application/x-ns-proxy-autoconfig" }, + { "wpad.dat", "application/x-ns-proxy-autoconfig" }, + + { NULL, NULL } +}; + +#endif + diff --git a/src/3P/uhttpd/plugin.c b/src/3P/uhttpd/plugin.c new file mode 100644 index 00000000..b84236c7 --- /dev/null +++ b/src/3P/uhttpd/plugin.c @@ -0,0 +1,70 @@ +/* + * 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 "uhttpd.h" +#include "plugin.h" + +static LIST_HEAD(plugins); + +static const struct uhttpd_ops ops = { + .dispatch_add = uh_dispatch_add, + .path_match = uh_path_match, + .create_process = uh_create_process, + .get_process_vars = uh_get_process_vars, + .http_header = uh_http_header, + .client_error = uh_client_error, + .request_done = uh_request_done, + .chunk_write = uh_chunk_write, + .chunk_printf = uh_chunk_printf, + .urlencode = uh_urlencode, + .urldecode = uh_urldecode, +}; + +int uh_plugin_init(const char *name) +{ + struct uhttpd_plugin *p; + const char *sym; + void *dlh; + + dlh = dlopen(name, RTLD_LAZY | RTLD_GLOBAL); + if (!dlh) { + fprintf(stderr, "Could not open plugin %s: %s\n", name, dlerror()); + return -ENOENT; + } + + sym = "uhttpd_plugin"; + p = dlsym(dlh, sym); + if (!p) { + fprintf(stderr, "Could not find symbol '%s' in plugin '%s'\n", sym, name); + return -ENOENT; + } + + list_add(&p->list, &plugins); + return p->init(&ops, &conf); +} + +void uh_plugin_post_init(void) +{ + struct uhttpd_plugin *p; + + list_for_each_entry(p, &plugins, list) + if (p->post_init) + p->post_init(); +} diff --git a/src/3P/uhttpd/plugin.h b/src/3P/uhttpd/plugin.h new file mode 100644 index 00000000..822cb549 --- /dev/null +++ b/src/3P/uhttpd/plugin.h @@ -0,0 +1,45 @@ +/* + * 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 "uhttpd.h" + +struct uhttpd_ops { + void (*dispatch_add)(struct dispatch_handler *d); + bool (*path_match)(const char *prefix, const char *url); + + bool (*create_process)(struct client *cl, struct path_info *pi, char *url, + void (*cb)(struct client *cl, struct path_info *pi, char *url)); + struct env_var *(*get_process_vars)(struct client *cl, struct path_info *pi); + + void (*http_header)(struct client *cl, int code, const char *summary); + void (*client_error)(struct client *cl, int code, const char *summary, const char *fmt, ...); + void (*request_done)(struct client *cl); + void (*chunk_write)(struct client *cl, const void *data, int len); + void (*chunk_printf)(struct client *cl, const char *format, ...); + + int (*urlencode)(char *buf, int blen, const char *src, int slen); + int (*urldecode)(char *buf, int blen, const char *src, int slen); +}; + +struct uhttpd_plugin { + struct list_head list; + + int (*init)(const struct uhttpd_ops *ops, struct config *conf); + void (*post_init)(void); +}; diff --git a/src/3P/uhttpd/proc.c b/src/3P/uhttpd/proc.c new file mode 100644 index 00000000..4819e083 --- /dev/null +++ b/src/3P/uhttpd/proc.c @@ -0,0 +1,389 @@ +/* + * 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 "uhttpd.h" + +#define __headers \ + __header(accept, accept) \ + __header(accept_charset, accept-charset) \ + __header(accept_encoding, accept-encoding) \ + __header(accept_language, accept-language) \ + __header(authorization, authorization) \ + __header(connection, connection) \ + __header(cookie, cookie) \ + __header(host, host) \ + __header(referer, referer) \ + __header(user_agent, user-agent) \ + __header(content_type, content-type) \ + __header(content_length, content-length) \ + __header(x_http_method_override, x-http-method-override) + +#undef __header +#define __header __enum_header +enum client_hdr { + __headers + __HDR_MAX, +}; + +#undef __header +#define __header __blobmsg_header +static const struct blobmsg_policy hdr_policy[__HDR_MAX] = { + __headers +}; + +static const struct { + const char *name; + int idx; +} proc_header_env[] = { + { "HTTP_ACCEPT", HDR_accept }, + { "HTTP_ACCEPT_CHARSET", HDR_accept_charset }, + { "HTTP_ACCEPT_ENCODING", HDR_accept_encoding }, + { "HTTP_ACCEPT_LANGUAGE", HDR_accept_language }, + { "HTTP_AUTHORIZATION", HDR_authorization }, + { "HTTP_CONNECTION", HDR_connection }, + { "HTTP_COOKIE", HDR_cookie }, + { "HTTP_HOST", HDR_host }, + { "HTTP_REFERER", HDR_referer }, + { "HTTP_USER_AGENT", HDR_user_agent }, + { "HTTP_X_HTTP_METHOD_OVERRIDE", HDR_x_http_method_override }, + { "CONTENT_TYPE", HDR_content_type }, + { "CONTENT_LENGTH", HDR_content_length }, +}; + +enum extra_vars { + /* no update needed */ + _VAR_GW, + _VAR_SOFTWARE, + + /* updated by uh_get_process_vars */ + VAR_SCRIPT_NAME, + VAR_SCRIPT_FILE, + VAR_DOCROOT, + VAR_QUERY, + VAR_REQUEST, + VAR_PROTO, + VAR_METHOD, + VAR_PATH_INFO, + VAR_USER, + VAR_HTTPS, + VAR_REDIRECT, + VAR_SERVER_NAME, + VAR_SERVER_ADDR, + VAR_SERVER_PORT, + VAR_REMOTE_NAME, + VAR_REMOTE_ADDR, + VAR_REMOTE_PORT, + + __VAR_MAX, +}; + +static char local_addr[INET6_ADDRSTRLEN], remote_addr[INET6_ADDRSTRLEN]; +static char local_port[6], remote_port[6]; +static char redirect_status[4]; + +static struct env_var extra_vars[] = { + [_VAR_GW] = { "GATEWAY_INTERFACE", "CGI/1.1" }, + [_VAR_SOFTWARE] = { "SERVER_SOFTWARE", "uhttpd" }, + [VAR_SCRIPT_NAME] = { "SCRIPT_NAME" }, + [VAR_SCRIPT_FILE] = { "SCRIPT_FILENAME" }, + [VAR_DOCROOT] = { "DOCUMENT_ROOT" }, + [VAR_QUERY] = { "QUERY_STRING" }, + [VAR_REQUEST] = { "REQUEST_URI" }, + [VAR_PROTO] = { "SERVER_PROTOCOL" }, + [VAR_METHOD] = { "REQUEST_METHOD" }, + [VAR_PATH_INFO] = { "PATH_INFO" }, + [VAR_USER] = { "REMOTE_USER" }, + [VAR_HTTPS] = { "HTTPS" }, + [VAR_REDIRECT] = { "REDIRECT_STATUS", redirect_status }, + [VAR_SERVER_NAME] = { "SERVER_NAME", local_addr }, + [VAR_SERVER_ADDR] = { "SERVER_ADDR", local_addr }, + [VAR_SERVER_PORT] = { "SERVER_PORT", local_port }, + [VAR_REMOTE_NAME] = { "REMOTE_HOST", remote_addr }, + [VAR_REMOTE_ADDR] = { "REMOTE_ADDR", remote_addr }, + [VAR_REMOTE_PORT] = { "REMOTE_PORT", remote_port }, +}; + +struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi) +{ + struct http_request *req = &cl->request; + struct blob_attr *data = cl->hdr.head; + struct env_var *vars = (void *) uh_buf; + struct blob_attr *tb[__HDR_MAX]; + const char *url; + int len; + int i; + + url = blobmsg_data(blob_data(cl->hdr.head)); + len = ARRAY_SIZE(proc_header_env); + len += ARRAY_SIZE(extra_vars); + len *= sizeof(struct env_var); + + BUILD_BUG_ON(sizeof(uh_buf) < len); + + extra_vars[VAR_SCRIPT_NAME].value = pi->name; + extra_vars[VAR_SCRIPT_FILE].value = pi->phys; + extra_vars[VAR_DOCROOT].value = pi->root; + extra_vars[VAR_QUERY].value = pi->query ? pi->query : ""; + extra_vars[VAR_REQUEST].value = url; + extra_vars[VAR_PROTO].value = http_versions[req->version]; + extra_vars[VAR_METHOD].value = http_methods[req->method]; + extra_vars[VAR_PATH_INFO].value = pi->info; + extra_vars[VAR_USER].value = req->realm ? req->realm->user : NULL; + extra_vars[VAR_HTTPS].value = cl->tls ? "on" : NULL; + + snprintf(redirect_status, sizeof(redirect_status), + "%d", req->redirect_status); + inet_ntop(cl->srv_addr.family, &cl->srv_addr.in, local_addr, sizeof(local_addr)); + snprintf(local_port, sizeof(local_port), "%d", cl->srv_addr.port); + inet_ntop(cl->peer_addr.family, &cl->peer_addr.in, remote_addr, sizeof(remote_addr)); + snprintf(remote_port, sizeof(remote_port), "%d", cl->peer_addr.port); + + blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(data), blob_len(data)); + for (i = 0; i < ARRAY_SIZE(proc_header_env); i++) { + struct blob_attr *cur; + + cur = tb[proc_header_env[i].idx]; + vars[i].name = proc_header_env[i].name; + vars[i].value = cur ? blobmsg_data(cur) : ""; + } + + memcpy(&vars[i], extra_vars, sizeof(extra_vars)); + i += ARRAY_SIZE(extra_vars); + vars[i].name = NULL; + vars[i].value = NULL; + + return vars; +} + +static void proc_close_fds(struct client *cl) +{ + struct dispatch_proc *p = &cl->dispatch.proc; + + close(p->r.sfd.fd.fd); + if (p->wrfd.fd >= 0) + close(p->wrfd.fd); +} + +static void proc_handle_close(struct relay *r, int ret) +{ + if (r->header_cb) { + uh_client_error(r->cl, 502, "Bad Gateway", + "The process did not produce any response"); + return; + } + + uh_request_done(r->cl); +} + +static void proc_handle_header(struct relay *r, const char *name, const char *val) +{ + static char status_buf[64]; + struct client *cl = r->cl; + char *sep; + char buf[4]; + + if (!strcmp(name, "Status")) { + sep = strchr(val, ' '); + if (sep != val + 3) + return; + + memcpy(buf, val, 3); + buf[3] = 0; + snprintf(status_buf, sizeof(status_buf), "%s", sep + 1); + cl->dispatch.proc.status_msg = status_buf; + cl->dispatch.proc.status_code = atoi(buf); + return; + } + + blobmsg_add_string(&cl->dispatch.proc.hdr, name, val); +} + +static void proc_handle_header_end(struct relay *r) +{ + struct client *cl = r->cl; + struct dispatch_proc *p = &cl->dispatch.proc; + struct blob_attr *cur; + int rem; + + uloop_timeout_cancel(&p->timeout); + uh_http_header(cl, cl->dispatch.proc.status_code, cl->dispatch.proc.status_msg); + blob_for_each_attr(cur, cl->dispatch.proc.hdr.head, rem) + ustream_printf(cl->us, "%s: %s\r\n", blobmsg_name(cur), blobmsg_data(cur)); + + ustream_printf(cl->us, "\r\n"); + + if (cl->request.method == UH_HTTP_MSG_HEAD) + r->skip_data = true; +} + +static void proc_write_close(struct client *cl) +{ + struct dispatch_proc *p = &cl->dispatch.proc; + + if (p->wrfd.fd < 0) + return; + + uloop_fd_delete(&p->wrfd); + close(p->wrfd.fd); + p->wrfd.fd = -1; +} + +static void proc_free(struct client *cl) +{ + struct dispatch_proc *p = &cl->dispatch.proc; + + uloop_timeout_cancel(&p->timeout); + blob_buf_free(&p->hdr); + proc_write_close(cl); + uh_relay_free(&p->r); +} + +static void proc_write_cb(struct uloop_fd *fd, unsigned int events) +{ + struct client *cl = container_of(fd, struct client, dispatch.proc.wrfd); + + client_poll_post_data(cl); +} + +static void proc_relay_write_cb(struct client *cl) +{ + struct dispatch_proc *p = &cl->dispatch.proc; + + if (ustream_pending_data(cl->us, true)) + return; + + ustream_set_read_blocked(&p->r.sfd.stream, false); + p->r.sfd.stream.notify_read(&p->r.sfd.stream, 0); +} + +static int proc_data_send(struct client *cl, const char *data, int len) +{ + struct dispatch_proc *p = &cl->dispatch.proc; + int retlen = 0; + int ret; + + while (len) { + ret = write(p->wrfd.fd, data, len); + + if (ret < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + + /* consume all data */ + ret = len; + } + + if (!ret) + break; + + retlen += ret; + len -= ret; + data += ret; + } + + if (len) + uloop_fd_add(&p->wrfd, ULOOP_WRITE); + else + uloop_fd_delete(&p->wrfd); + + return retlen; +} + +static void proc_timeout_cb(struct uloop_timeout *timeout) +{ + struct dispatch_proc *proc = container_of(timeout, struct dispatch_proc, timeout); + struct client *cl = container_of(proc, struct client, dispatch.proc); + + uh_relay_kill(cl, &proc->r); +} + +bool uh_create_process(struct client *cl, struct path_info *pi, char *url, + void (*cb)(struct client *cl, struct path_info *pi, char *url)) +{ + struct dispatch *d = &cl->dispatch; + struct dispatch_proc *proc = &d->proc; + int rfd[2], wfd[2]; + int pid; + + blob_buf_init(&proc->hdr, 0); + proc->status_code = 200; + proc->status_msg = "OK"; + + if (pipe(rfd)) + return false; + + if (pipe(wfd)) + goto close_rfd; + + pid = fork(); + if (pid < 0) + goto close_wfd; + + if (!pid) { + close(0); + close(1); + + dup2(rfd[1], 1); + dup2(wfd[0], 0); + + close(rfd[0]); + close(rfd[1]); + close(wfd[0]); + close(wfd[1]); + + uh_close_fds(); + cb(cl, pi, url); + exit(0); + } + + close(rfd[1]); + close(wfd[0]); + + proc->wrfd.fd = wfd[1]; + uh_relay_open(cl, &proc->r, rfd[0], pid); + + d->free = proc_free; + d->close_fds = proc_close_fds; + d->data_send = proc_data_send; + d->data_done = proc_write_close; + d->write_cb = proc_relay_write_cb; + proc->r.header_cb = proc_handle_header; + proc->r.header_end = proc_handle_header_end; + proc->r.close = proc_handle_close; + proc->wrfd.cb = proc_write_cb; + proc->timeout.cb = proc_timeout_cb; + if (conf.script_timeout > 0) + uloop_timeout_set(&proc->timeout, conf.script_timeout * 1000); + + return true; + +close_wfd: + close(wfd[0]); + close(wfd[1]); +close_rfd: + close(rfd[0]); + close(rfd[1]); + + return false; +} diff --git a/src/3P/uhttpd/relay.c b/src/3P/uhttpd/relay.c new file mode 100644 index 00000000..7331a0a5 --- /dev/null +++ b/src/3P/uhttpd/relay.c @@ -0,0 +1,205 @@ +/* + * 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 "uhttpd.h" + +void uh_relay_free(struct relay *r) +{ + if (!r->cl) + return; + + if (r->proc.pending) + kill(r->proc.pid, SIGKILL); + + uloop_timeout_cancel(&r->timeout); + uloop_process_delete(&r->proc); + ustream_free(&r->sfd.stream); + close(r->sfd.fd.fd); + + r->cl = NULL; +} + +void uh_relay_close(struct relay *r, int ret) +{ + struct ustream *us = &r->sfd.stream; + + if (!us->notify_read) + return; + + us->notify_read = NULL; + us->notify_write = NULL; + us->notify_state = NULL; + + if (r->close) + r->close(r, ret); +} + +static void relay_error(struct relay *r) +{ + struct ustream *s = &r->sfd.stream; + int len; + + r->error = true; + s->eof = true; + ustream_get_read_buf(s, &len); + if (len) + ustream_consume(s, len); + ustream_state_change(s); +} + +static void relay_process_headers(struct relay *r) +{ + struct ustream *s = &r->sfd.stream; + char *buf, *newline; + int len; + + if (!r->header_cb) + return; + + while (r->header_cb) { + int line_len; + char *val; + + buf = ustream_get_read_buf(s, &len); + if (!buf || !len) + break; + + newline = strchr(buf, '\n'); + if (!newline) + break; + + line_len = newline + 1 - buf; + if (newline > buf && newline[-1] == '\r') + newline--; + + *newline = 0; + if (newline == buf) { + r->header_cb = NULL; + if (r->header_end) + r->header_end(r); + ustream_consume(s, line_len); + break; + } + + val = uh_split_header(buf); + if (!val) { + relay_error(r); + return; + } + + r->header_cb(r, buf, val); + ustream_consume(s, line_len); + } +} + +static void relay_read_cb(struct ustream *s, int bytes) +{ + struct relay *r = container_of(s, struct relay, sfd.stream); + struct client *cl = r->cl; + struct ustream *us = cl->us; + char *buf; + int len; + + if (r->process_done) + uloop_timeout_set(&r->timeout, 1); + + if (!r->error) + relay_process_headers(r); + + if (r->header_cb) { + /* + * if eof, ensure that remaining data is discarded, so the + * state change cb will tear down the stream + */ + if (s->eof) + relay_error(r); + return; + } + + if (!s->eof && ustream_pending_data(us, true)) { + ustream_set_read_blocked(s, true); + return; + } + + buf = ustream_get_read_buf(s, &len); + if (!buf || !len) + return; + + if (!r->skip_data) + uh_chunk_write(cl, buf, len); + + ustream_consume(s, len); +} + +static void relay_close_if_done(struct uloop_timeout *timeout) +{ + struct relay *r = container_of(timeout, struct relay, timeout); + struct ustream *s = &r->sfd.stream; + + while (ustream_poll(&r->sfd.stream)); + + if (!(r->process_done || s->eof) || ustream_pending_data(s, false)) + return; + + uh_relay_close(r, r->ret); +} + +static void relay_state_cb(struct ustream *s) +{ + struct relay *r = container_of(s, struct relay, sfd.stream); + + if (r->process_done) + uloop_timeout_set(&r->timeout, 1); +} + +static void relay_proc_cb(struct uloop_process *proc, int ret) +{ + struct relay *r = container_of(proc, struct relay, proc); + + r->process_done = true; + r->ret = ret; + uloop_timeout_set(&r->timeout, 1); +} + +void uh_relay_kill(struct client *cl, struct relay *r) +{ + struct ustream *us = &r->sfd.stream; + + kill(r->proc.pid, SIGKILL); + us->eof = true; + ustream_state_change(us); +} + +void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid) +{ + struct ustream *us = &r->sfd.stream; + + r->cl = cl; + us->notify_read = relay_read_cb; + us->notify_state = relay_state_cb; + us->string_data = true; + ustream_fd_init(&r->sfd, fd); + + r->proc.pid = pid; + r->proc.cb = relay_proc_cb; + uloop_process_add(&r->proc); + + r->timeout.cb = relay_close_if_done; +} diff --git a/src/3P/uhttpd/session-test.sh b/src/3P/uhttpd/session-test.sh new file mode 100755 index 00000000..e7666cc8 --- /dev/null +++ b/src/3P/uhttpd/session-test.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +. /usr/share/libubox/jshn.sh + +json_load "$(ubus call session create)" +json_get_var sid ubus_rpc_session + + +json_init +json_add_string ubus_rpc_session "$sid" +json_add_array "objects" +json_add_array "" +json_add_string "" "session" +json_add_string "" "list" +json_close_array +json_close_array + +ubus call session grant "$(json_dump)" + +echo "Session: $sid" +echo "Request 1" +wget -q -O- \ + --post-data='{ + "jsonrpc": "2.0", + "method" : "call", + "params" : [ + "'$sid'", + "session", + "test", + {}, + ] + }' "http://localhost:8080/ubus" +echo "Request 2" +wget -q -O- \ + --post-data='[ + { + "jsonrpc": "2.0", + "method" : "call", + "params" : [ + "'$sid'", + "session", + "list", + {}, + ] + }, + { + "jsonrpc": "2.0", + "method" : "call", + "params" : [ + "'$sid'", + "session", + "test", + {}, + ] + }, + ]' "http://localhost:8080/ubus" diff --git a/src/3P/uhttpd/tls.c b/src/3P/uhttpd/tls.c new file mode 100644 index 00000000..d969b828 --- /dev/null +++ b/src/3P/uhttpd/tls.c @@ -0,0 +1,103 @@ +/* + * 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 "uhttpd.h" +#include "tls.h" + +#ifdef __APPLE__ +#define LIB_EXT "dylib" +#else +#define LIB_EXT "so" +#endif + +static struct ustream_ssl_ops *ops; +static void *dlh; +static void *ctx; + +int uh_tls_init(const char *key, const char *crt) +{ + static bool _init = false; + + if (_init) + return 0; + + _init = true; + dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL); + if (!dlh) { + fprintf(stderr, "Failed to load ustream-ssl library: %s\n", dlerror()); + return -ENOENT; + } + + ops = dlsym(dlh, "ustream_ssl_ops"); + if (!ops) { + fprintf(stderr, "Could not find required symbol 'ustream_ssl_ops' in ustream-ssl library\n"); + return -ENOENT; + } + + ctx = ops->context_new(true); + if (!ctx) { + fprintf(stderr, "Failed to initialize ustream-ssl\n"); + return -EINVAL; + } + + if (ops->context_set_crt_file(ctx, crt) || + ops->context_set_key_file(ctx, key)) { + fprintf(stderr, "Failed to load certificate/key files\n"); + return -EINVAL; + } + + return 0; +} + +static void tls_ustream_read_cb(struct ustream *s, int bytes) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + + uh_client_read_cb(cl); +} + +static void tls_ustream_write_cb(struct ustream *s, int bytes) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + + if (cl->dispatch.write_cb) + cl->dispatch.write_cb(cl); +} + +static void tls_notify_state(struct ustream *s) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + + uh_client_notify_state(cl); +} + +void uh_tls_client_attach(struct client *cl) +{ + cl->us = &cl->ssl.stream; + ops->init(&cl->ssl, &cl->sfd.stream, ctx, true); + cl->us->notify_read = tls_ustream_read_cb; + cl->us->notify_write = tls_ustream_write_cb; + cl->us->notify_state = tls_notify_state; +} + +void uh_tls_client_detach(struct client *cl) +{ + ustream_free(&cl->ssl.stream); +} diff --git a/src/3P/uhttpd/tls.h b/src/3P/uhttpd/tls.h new file mode 100644 index 00000000..9be74ba8 --- /dev/null +++ b/src/3P/uhttpd/tls.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef __UHTTPD_TLS_H +#define __UHTTPD_TLS_H + +#ifdef HAVE_TLS + +int uh_tls_init(const char *key, const char *crt); +void uh_tls_client_attach(struct client *cl); +void uh_tls_client_detach(struct client *cl); + +#else + +static inline int uh_tls_init(const char *key, const char *crt) +{ + return -1; +} + +static inline void uh_tls_client_attach(struct client *cl) +{ +} + +static inline void uh_tls_client_detach(struct client *cl) +{ +} + +#endif + +#endif diff --git a/src/3P/uhttpd/ubus.c b/src/3P/uhttpd/ubus.c new file mode 100644 index 00000000..f7d1f11a --- /dev/null +++ b/src/3P/uhttpd/ubus.c @@ -0,0 +1,685 @@ +/* + * 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" + +static const struct uhttpd_ops *ops; +static struct config *_conf; +#define conf (*_conf) + +static struct ubus_context *ctx; +static struct blob_buf buf; + +#define UH_UBUS_MAX_POST_SIZE 4096 +#define UH_UBUS_DEFAULT_SID "00000000000000000000000000000000" + +enum { + RPC_JSONRPC, + RPC_METHOD, + RPC_PARAMS, + RPC_ID, + __RPC_MAX, +}; + +static const struct blobmsg_policy rpc_policy[__RPC_MAX] = { + [RPC_JSONRPC] = { .name = "jsonrpc", .type = BLOBMSG_TYPE_STRING }, + [RPC_METHOD] = { .name = "method", .type = BLOBMSG_TYPE_STRING }, + [RPC_PARAMS] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY }, + [RPC_ID] = { .name = "id", .type = BLOBMSG_TYPE_UNSPEC }, +}; + +enum { + SES_ACCESS, + __SES_MAX, +}; + +static const struct blobmsg_policy ses_policy[__SES_MAX] = { + [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL }, +}; + +struct rpc_data { + struct blob_attr *id; + const char *sid; + const char *method; + const char *object; + const char *function; + struct blob_attr *data; + struct blob_attr *params; +}; + +struct list_data { + bool verbose; + struct blob_buf *buf; +}; + +enum rpc_error { + ERROR_PARSE, + ERROR_REQUEST, + ERROR_METHOD, + ERROR_PARAMS, + ERROR_INTERNAL, + ERROR_OBJECT, + ERROR_SESSION, + ERROR_ACCESS, + ERROR_TIMEOUT, + __ERROR_MAX +}; + +static const struct { + int code; + const char *msg; +} json_errors[__ERROR_MAX] = { + [ERROR_PARSE] = { -32700, "Parse error" }, + [ERROR_REQUEST] = { -32600, "Invalid request" }, + [ERROR_METHOD] = { -32601, "Method not found" }, + [ERROR_PARAMS] = { -32602, "Invalid parameters" }, + [ERROR_INTERNAL] = { -32603, "Internal error" }, + [ERROR_OBJECT] = { -32000, "Object not found" }, + [ERROR_SESSION] = { -32001, "Session not found" }, + [ERROR_ACCESS] = { -32002, "Access denied" }, + [ERROR_TIMEOUT] = { -32003, "ubus request timed out" }, +}; + +enum cors_hdr { + HDR_ORIGIN, + HDR_ACCESS_CONTROL_REQUEST_METHOD, + HDR_ACCESS_CONTROL_REQUEST_HEADERS, + __HDR_MAX +}; + +static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout); + +static void uh_ubus_next_batched_request(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + + du->timeout.cb = __uh_ubus_next_batched_request; + uloop_timeout_set(&du->timeout, 1); +} + +static void uh_ubus_add_cors_headers(struct client *cl) +{ + struct blob_attr *tb[__HDR_MAX]; + static const struct blobmsg_policy hdr_policy[__HDR_MAX] = { + [HDR_ORIGIN] = { "origin", BLOBMSG_TYPE_STRING }, + [HDR_ACCESS_CONTROL_REQUEST_METHOD] = { "access-control-request-method", BLOBMSG_TYPE_STRING }, + [HDR_ACCESS_CONTROL_REQUEST_HEADERS] = { "access-control-request-headers", BLOBMSG_TYPE_STRING }, + }; + + blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head)); + + if (!tb[HDR_ORIGIN]) + return; + + if (tb[HDR_ACCESS_CONTROL_REQUEST_METHOD]) + { + char *hdr = (char *) blobmsg_data(tb[HDR_ACCESS_CONTROL_REQUEST_METHOD]); + + if (strcmp(hdr, "POST") && strcmp(hdr, "OPTIONS")) + return; + } + + ustream_printf(cl->us, "Access-Control-Allow-Origin: %s\r\n", + blobmsg_data(tb[HDR_ORIGIN])); + + if (tb[HDR_ACCESS_CONTROL_REQUEST_HEADERS]) + ustream_printf(cl->us, "Access-Control-Allow-Headers: %s\r\n", + blobmsg_data(tb[HDR_ACCESS_CONTROL_REQUEST_HEADERS])); + + ustream_printf(cl->us, "Access-Control-Allow-Methods: POST, OPTIONS\r\n"); + ustream_printf(cl->us, "Access-Control-Allow-Credentials: true\r\n"); +} + +static void uh_ubus_send_header(struct client *cl) +{ + ops->http_header(cl, 200, "OK"); + + if (conf.ubus_cors) + uh_ubus_add_cors_headers(cl); + + ustream_printf(cl->us, "Content-Type: application/json\r\n"); + + if (cl->request.method == UH_HTTP_MSG_OPTIONS) + ustream_printf(cl->us, "Content-Length: 0\r\n"); + + ustream_printf(cl->us, "\r\n"); +} + +static void uh_ubus_send_response(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + const char *sep = ""; + char *str; + + if (du->array && du->array_idx > 1) + sep = ","; + + str = blobmsg_format_json(buf.head, true); + ops->chunk_printf(cl, "%s%s", sep, str); + free(str); + + du->jsobj_cur = NULL; + if (du->array) + uh_ubus_next_batched_request(cl); + else + return ops->request_done(cl); +} + +static void uh_ubus_init_response(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + struct json_object *obj = du->jsobj_cur, *obj2 = NULL; + + blob_buf_init(&buf, 0); + blobmsg_add_string(&buf, "jsonrpc", "2.0"); + + if (obj) + json_object_object_get_ex(obj, "id", &obj2); + + if (obj2) + blobmsg_add_json_element(&buf, "id", obj2); + else + blobmsg_add_field(&buf, BLOBMSG_TYPE_UNSPEC, "id", NULL, 0); +} + +static void uh_ubus_json_error(struct client *cl, enum rpc_error type) +{ + void *c; + + uh_ubus_init_response(cl); + c = blobmsg_open_table(&buf, "error"); + blobmsg_add_u32(&buf, "code", json_errors[type].code); + blobmsg_add_string(&buf, "message", json_errors[type].msg); + blobmsg_close_table(&buf, c); + uh_ubus_send_response(cl); +} + +static void +uh_ubus_request_data_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req); + + blobmsg_add_field(&du->buf, BLOBMSG_TYPE_TABLE, "", blob_data(msg), blob_len(msg)); +} + +static void +uh_ubus_request_cb(struct ubus_request *req, int ret) +{ + struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req); + struct client *cl = container_of(du, struct client, dispatch.ubus); + struct blob_attr *cur; + void *r; + int rem; + + uloop_timeout_cancel(&du->timeout); + uh_ubus_init_response(cl); + r = blobmsg_open_array(&buf, "result"); + blobmsg_add_u32(&buf, "", ret); + blob_for_each_attr(cur, du->buf.head, rem) + blobmsg_add_blob(&buf, cur); + blobmsg_close_array(&buf, r); + uh_ubus_send_response(cl); +} + +static void +uh_ubus_timeout_cb(struct uloop_timeout *timeout) +{ + struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout); + struct client *cl = container_of(du, struct client, dispatch.ubus); + + ubus_abort_request(ctx, &du->req); + uh_ubus_json_error(cl, ERROR_TIMEOUT); +} + +static void uh_ubus_close_fds(struct client *cl) +{ + if (ctx->sock.fd < 0) + return; + + close(ctx->sock.fd); + ctx->sock.fd = -1; +} + +static void uh_ubus_request_free(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + + blob_buf_free(&du->buf); + uloop_timeout_cancel(&du->timeout); + + if (du->jsobj) + json_object_put(du->jsobj); + + if (du->jstok) + json_tokener_free(du->jstok); + + if (du->req_pending) + ubus_abort_request(ctx, &du->req); +} + +static void uh_ubus_single_error(struct client *cl, enum rpc_error type) +{ + uh_ubus_send_header(cl); + uh_ubus_json_error(cl, type); + ops->request_done(cl); +} + +static void uh_ubus_send_request(struct client *cl, json_object *obj, const char *sid, struct blob_attr *args) +{ + struct dispatch *d = &cl->dispatch; + struct dispatch_ubus *du = &d->ubus; + struct blob_attr *cur; + static struct blob_buf req; + int ret, rem; + + blob_buf_init(&req, 0); + blobmsg_for_each_attr(cur, args, rem) { + if (!strcmp(blobmsg_name(cur), "ubus_rpc_session")) + return uh_ubus_json_error(cl, ERROR_PARAMS); + blobmsg_add_blob(&req, cur); + } + + blobmsg_add_string(&req, "ubus_rpc_session", sid); + + blob_buf_init(&du->buf, 0); + memset(&du->req, 0, sizeof(du->req)); + ret = ubus_invoke_async(ctx, du->obj, du->func, req.head, &du->req); + if (ret) + return uh_ubus_json_error(cl, ERROR_INTERNAL); + + du->req.data_cb = uh_ubus_request_data_cb; + du->req.complete_cb = uh_ubus_request_cb; + ubus_complete_request_async(ctx, &du->req); + + du->timeout.cb = uh_ubus_timeout_cb; + uloop_timeout_set(&du->timeout, conf.script_timeout * 1000); + + du->req_pending = true; +} + +static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv) +{ + struct blob_attr *sig, *attr; + struct list_data *data = priv; + int rem, rem2; + void *t, *o; + + if (!data->verbose) { + blobmsg_add_string(data->buf, NULL, obj->path); + return; + } + + if (!obj->signature) + return; + + o = blobmsg_open_table(data->buf, obj->path); + blob_for_each_attr(sig, obj->signature, rem) { + t = blobmsg_open_table(data->buf, blobmsg_name(sig)); + rem2 = blobmsg_data_len(sig); + __blob_for_each_attr(attr, blobmsg_data(sig), rem2) { + if (blob_id(attr) != BLOBMSG_TYPE_INT32) + continue; + + switch (blobmsg_get_u32(attr)) { + case BLOBMSG_TYPE_INT8: + blobmsg_add_string(data->buf, blobmsg_name(attr), "boolean"); + break; + case BLOBMSG_TYPE_INT32: + blobmsg_add_string(data->buf, blobmsg_name(attr), "number"); + break; + case BLOBMSG_TYPE_STRING: + blobmsg_add_string(data->buf, blobmsg_name(attr), "string"); + break; + case BLOBMSG_TYPE_ARRAY: + blobmsg_add_string(data->buf, blobmsg_name(attr), "array"); + break; + case BLOBMSG_TYPE_TABLE: + blobmsg_add_string(data->buf, blobmsg_name(attr), "object"); + break; + default: + blobmsg_add_string(data->buf, blobmsg_name(attr), "unknown"); + break; + } + } + blobmsg_close_table(data->buf, t); + } + blobmsg_close_table(data->buf, o); +} + +static void uh_ubus_send_list(struct client *cl, json_object *obj, struct blob_attr *params) +{ + struct blob_attr *cur, *dup; + struct list_data data = { .buf = &cl->dispatch.ubus.buf, .verbose = false }; + void *r; + int rem; + + blob_buf_init(data.buf, 0); + + uh_client_ref(cl); + + if (!params || blob_id(params) != BLOBMSG_TYPE_ARRAY) { + r = blobmsg_open_array(data.buf, "result"); + ubus_lookup(ctx, NULL, uh_ubus_list_cb, &data); + blobmsg_close_array(data.buf, r); + } + else { + r = blobmsg_open_table(data.buf, "result"); + dup = blob_memdup(params); + if (dup) + { + rem = blobmsg_data_len(dup); + data.verbose = true; + __blob_for_each_attr(cur, blobmsg_data(dup), rem) + ubus_lookup(ctx, blobmsg_data(cur), uh_ubus_list_cb, &data); + free(dup); + } + blobmsg_close_table(data.buf, r); + } + + uh_client_unref(cl); + + uh_ubus_init_response(cl); + blobmsg_add_blob(&buf, blob_data(data.buf->head)); + uh_ubus_send_response(cl); +} + +static bool parse_json_rpc(struct rpc_data *d, struct blob_attr *data) +{ + const struct blobmsg_policy data_policy[] = { + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_TABLE }, + }; + struct blob_attr *tb[__RPC_MAX]; + struct blob_attr *tb2[4]; + struct blob_attr *cur; + + blobmsg_parse(rpc_policy, __RPC_MAX, tb, blob_data(data), blob_len(data)); + + cur = tb[RPC_JSONRPC]; + if (!cur || strcmp(blobmsg_data(cur), "2.0") != 0) + return false; + + cur = tb[RPC_METHOD]; + if (!cur) + return false; + + d->id = tb[RPC_ID]; + d->method = blobmsg_data(cur); + + cur = tb[RPC_PARAMS]; + if (!cur) + return true; + + d->params = blob_memdup(cur); + if (!d->params) + return false; + + blobmsg_parse_array(data_policy, ARRAY_SIZE(data_policy), tb2, + blobmsg_data(d->params), blobmsg_data_len(d->params)); + + if (tb2[0]) + d->sid = blobmsg_data(tb2[0]); + + if (conf.ubus_noauth && (!d->sid || !*d->sid)) + d->sid = UH_UBUS_DEFAULT_SID; + + if (tb2[1]) + d->object = blobmsg_data(tb2[1]); + + if (tb2[2]) + d->function = blobmsg_data(tb2[2]); + + d->data = tb2[3]; + + return true; +} + +static void uh_ubus_init_batch(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + + du->array = true; + uh_ubus_send_header(cl); + ops->chunk_printf(cl, "["); +} + +static void uh_ubus_complete_batch(struct client *cl) +{ + ops->chunk_printf(cl, "]"); + ops->request_done(cl); +} + +static void uh_ubus_allowed_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct blob_attr *tb[__SES_MAX]; + bool *allow = (bool *)req->priv; + + if (!msg) + return; + + blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg)); + + if (tb[SES_ACCESS]) + *allow = blobmsg_get_bool(tb[SES_ACCESS]); +} + +static bool uh_ubus_allowed(const char *sid, const char *obj, const char *fun) +{ + uint32_t id; + bool allow = false; + static struct blob_buf req; + + if (ubus_lookup_id(ctx, "session", &id)) + return false; + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "ubus_rpc_session", sid); + blobmsg_add_string(&req, "object", obj); + blobmsg_add_string(&req, "function", fun); + + ubus_invoke(ctx, id, "access", req.head, uh_ubus_allowed_cb, &allow, conf.script_timeout * 500); + + return allow; +} + +static void uh_ubus_handle_request_object(struct client *cl, struct json_object *obj) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + struct rpc_data data = {}; + enum rpc_error err = ERROR_PARSE; + + uh_client_ref(cl); + + if (json_object_get_type(obj) != json_type_object) + goto error; + + du->jsobj_cur = obj; + blob_buf_init(&buf, 0); + if (!blobmsg_add_object(&buf, obj)) + goto error; + + if (!parse_json_rpc(&data, buf.head)) + goto error; + + if (!strcmp(data.method, "call")) { + if (!data.sid || !data.object || !data.function || !data.data) + goto error; + + du->func = data.function; + if (ubus_lookup_id(ctx, data.object, &du->obj)) { + err = ERROR_OBJECT; + goto error; + } + + if (!conf.ubus_noauth && !uh_ubus_allowed(data.sid, data.object, data.function)) { + err = ERROR_ACCESS; + goto error; + } + + uh_ubus_send_request(cl, obj, data.sid, data.data); + goto out; + } + else if (!strcmp(data.method, "list")) { + uh_ubus_send_list(cl, obj, data.params); + goto out; + } + else { + err = ERROR_METHOD; + goto error; + } + +error: + uh_ubus_json_error(cl, err); +out: + if (data.params) + free(data.params); + + uh_client_unref(cl); +} + +static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout) +{ + struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout); + struct client *cl = container_of(du, struct client, dispatch.ubus); + struct json_object *obj = du->jsobj; + int len; + + len = json_object_array_length(obj); + if (du->array_idx >= len) + return uh_ubus_complete_batch(cl); + + obj = json_object_array_get_idx(obj, du->array_idx++); + uh_ubus_handle_request_object(cl, obj); +} + +static void uh_ubus_data_done(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + struct json_object *obj = du->jsobj; + + switch (obj ? json_object_get_type(obj) : json_type_null) { + case json_type_object: + uh_ubus_send_header(cl); + return uh_ubus_handle_request_object(cl, obj); + case json_type_array: + uh_ubus_init_batch(cl); + return uh_ubus_next_batched_request(cl); + default: + return uh_ubus_single_error(cl, ERROR_PARSE); + } +} + +static int uh_ubus_data_send(struct client *cl, const char *data, int len) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + + if (du->jsobj || !du->jstok) + goto error; + + du->post_len += len; + if (du->post_len > UH_UBUS_MAX_POST_SIZE) + goto error; + + du->jsobj = json_tokener_parse_ex(du->jstok, data, len); + return len; + +error: + uh_ubus_single_error(cl, ERROR_PARSE); + return 0; +} + +static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi) +{ + struct dispatch *d = &cl->dispatch; + + blob_buf_init(&buf, 0); + + switch (cl->request.method) + { + case UH_HTTP_MSG_POST: + d->data_send = uh_ubus_data_send; + d->data_done = uh_ubus_data_done; + d->close_fds = uh_ubus_close_fds; + d->free = uh_ubus_request_free; + d->ubus.jstok = json_tokener_new(); + break; + + case UH_HTTP_MSG_OPTIONS: + uh_ubus_send_header(cl); + ops->request_done(cl); + break; + + default: + ops->client_error(cl, 400, "Bad Request", "Invalid Request"); + } +} + +static bool +uh_ubus_check_url(const char *url) +{ + return ops->path_match(conf.ubus_prefix, url); +} + +static int +uh_ubus_init(void) +{ + static struct dispatch_handler ubus_dispatch = { + .check_url = uh_ubus_check_url, + .handle_request = uh_ubus_handle_request, + }; + + ctx = ubus_connect(conf.ubus_socket); + if (!ctx) { + fprintf(stderr, "Unable to connect to ubus socket\n"); + exit(1); + } + + ops->dispatch_add(&ubus_dispatch); + + uloop_done(); + return 0; +} + + +static int uh_ubus_plugin_init(const struct uhttpd_ops *o, struct config *c) +{ + ops = o; + _conf = c; + return uh_ubus_init(); +} + +static void uh_ubus_post_init(void) +{ + ubus_add_uloop(ctx); +} + +struct uhttpd_plugin uhttpd_plugin = { + .init = uh_ubus_plugin_init, + .post_init = uh_ubus_post_init, +}; diff --git a/src/3P/uhttpd/uhttpd.h b/src/3P/uhttpd/uhttpd.h new file mode 100644 index 00000000..fe05f0d9 --- /dev/null +++ b/src/3P/uhttpd/uhttpd.h @@ -0,0 +1,342 @@ +/* + * 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. + */ + +#ifndef __UHTTPD_H +#define __UHTTPD_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef HAVE_UBUS +#include +#include +#endif +#ifdef HAVE_TLS +#include +#endif + +#include "utils.h" + +#define UH_LIMIT_CLIENTS 64 + +#define __enum_header(_name, _val) HDR_##_name, +#define __blobmsg_header(_name, _val) [HDR_##_name] = { .name = #_val, .type = BLOBMSG_TYPE_STRING }, + +struct client; + +struct alias { + struct list_head list; + char *alias; + char *path; +}; + +struct config { + const char *docroot; + const char *realm; + const char *file; + const char *error_handler; + const char *cgi_prefix; + const char *cgi_docroot_path; + const char *cgi_path; + const char *lua_handler; + const char *lua_prefix; + const char *ubus_prefix; + const char *ubus_socket; + int no_symlinks; + int no_dirlists; + int network_timeout; + int rfc1918_filter; + int tls_redirect; + int tcp_keepalive; + int max_script_requests; + int max_connections; + int http_keepalive; + int script_timeout; + int ubus_noauth; + int ubus_cors; + int cgi_prefix_len; + struct list_head cgi_alias; +}; + +struct auth_realm { + struct list_head list; + const char *path; + const char *user; + const char *pass; +}; + +enum http_method { + UH_HTTP_MSG_GET, + UH_HTTP_MSG_POST, + UH_HTTP_MSG_HEAD, + UH_HTTP_MSG_OPTIONS, +}; + +enum http_version { + UH_HTTP_VER_0_9, + UH_HTTP_VER_1_0, + UH_HTTP_VER_1_1, +}; + +enum http_user_agent { + UH_UA_UNKNOWN, + UH_UA_GECKO, + UH_UA_CHROME, + UH_UA_SAFARI, + UH_UA_MSIE, + UH_UA_KONQUEROR, + UH_UA_OPERA, + UH_UA_MSIE_OLD, + UH_UA_MSIE_NEW, +}; + +struct http_request { + enum http_method method; + enum http_version version; + enum http_user_agent ua; + int redirect_status; + int content_length; + bool expect_cont; + bool connection_close; + bool disable_chunked; + uint8_t transfer_chunked; + const struct auth_realm *realm; +}; + +enum client_state { + CLIENT_STATE_INIT, + CLIENT_STATE_HEADER, + CLIENT_STATE_DATA, + CLIENT_STATE_DONE, + CLIENT_STATE_CLOSE, + CLIENT_STATE_CLEANUP, +}; + +struct interpreter { + struct list_head list; + const char *path; + const char *ext; +}; + +struct path_info { + const char *root; + const char *phys; + const char *name; + const char *info; + const char *query; + const char *auth; + bool redirected; + struct stat stat; + const struct interpreter *ip; +}; + +struct env_var { + const char *name; + const char *value; +}; + +struct relay { + struct ustream_fd sfd; + struct uloop_process proc; + struct uloop_timeout timeout; + struct client *cl; + + bool process_done; + bool error; + bool skip_data; + + int ret; + int header_ofs; + + void (*header_cb)(struct relay *r, const char *name, const char *value); + void (*header_end)(struct relay *r); + void (*close)(struct relay *r, int ret); +}; + +struct dispatch_proc { + struct uloop_timeout timeout; + struct blob_buf hdr; + struct uloop_fd wrfd; + struct relay r; + int status_code; + char *status_msg; +}; + +struct dispatch_handler { + struct list_head list; + bool script; + + bool (*check_url)(const char *url); + bool (*check_path)(struct path_info *pi, const char *url); + void (*handle_request)(struct client *cl, char *url, struct path_info *pi); +}; + +#ifdef HAVE_UBUS +struct dispatch_ubus { + struct ubus_request req; + + struct uloop_timeout timeout; + struct json_tokener *jstok; + struct json_object *jsobj; + struct json_object *jsobj_cur; + int post_len; + + uint32_t obj; + const char *func; + + struct blob_buf buf; + bool req_pending; + bool array; + int array_idx; +}; +#endif + +struct dispatch { + int (*data_send)(struct client *cl, const char *data, int len); + void (*data_done)(struct client *cl); + void (*write_cb)(struct client *cl); + void (*close_fds)(struct client *cl); + void (*free)(struct client *cl); + + void *req_data; + void (*req_free)(struct client *cl); + + bool data_blocked; + bool no_cache; + + union { + struct { + struct blob_attr **hdr; + int fd; + } file; + struct dispatch_proc proc; +#ifdef HAVE_UBUS + struct dispatch_ubus ubus; +#endif + }; +}; + +struct client { + struct list_head list; + int refcount; + int id; + + struct ustream *us; + struct ustream_fd sfd; +#ifdef HAVE_TLS + struct ustream_ssl ssl; +#endif + struct uloop_timeout timeout; + int requests; + + enum client_state state; + bool tls; + + int http_code; + struct http_request request; + struct uh_addr srv_addr, peer_addr; + + struct blob_buf hdr; + struct blob_buf hdr_response; + struct dispatch dispatch; +}; + +extern char uh_buf[4096]; +extern int n_clients; +extern struct config conf; +extern const char * const http_versions[]; +extern const char * const http_methods[]; +extern struct dispatch_handler cgi_dispatch; + +void uh_index_add(const char *filename); + +bool uh_accept_client(int fd, bool tls); + +void uh_unblock_listeners(void); +void uh_setup_listeners(void); +int uh_socket_bind(const char *host, const char *port, bool tls); + +int uh_first_tls_port(int family); + +bool uh_use_chunked(struct client *cl); +void uh_chunk_write(struct client *cl, const void *data, int len); +void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg); + +void __printf(2, 3) +uh_chunk_printf(struct client *cl, const char *format, ...); + +void uh_chunk_eof(struct client *cl); +void uh_request_done(struct client *cl); + +void uh_http_header(struct client *cl, int code, const char *summary); +void __printf(4, 5) +uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...); + +void uh_handle_request(struct client *cl); +void client_poll_post_data(struct client *cl); +void uh_client_read_cb(struct client *cl); +void uh_client_notify_state(struct client *cl); + +void uh_auth_add(const char *path, const char *user, const char *pass); +bool uh_auth_check(struct client *cl, struct path_info *pi); + +void uh_close_listen_fds(void); +void uh_close_fds(void); + +void uh_interpreter_add(const char *ext, const char *path); +void uh_dispatch_add(struct dispatch_handler *d); + +void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid); +void uh_relay_close(struct relay *r, int ret); +void uh_relay_free(struct relay *r); +void uh_relay_kill(struct client *cl, struct relay *r); + +struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi); +bool uh_create_process(struct client *cl, struct path_info *pi, char *url, + void (*cb)(struct client *cl, struct path_info *pi, char *url)); + +int uh_plugin_init(const char *name); +void uh_plugin_post_init(void); + +int uh_handler_add(const char *file); +int uh_handler_run(struct client *cl, char **url, bool fallback); + +struct path_info *uh_path_lookup(struct client *cl, const char *url); + +static inline void uh_client_ref(struct client *cl) +{ + cl->refcount++; +} + +static inline void uh_client_unref(struct client *cl) +{ + if (--cl->refcount) + return; + + if (cl->state == CLIENT_STATE_CLEANUP) + ustream_state_change(cl->us); +} + +#endif diff --git a/src/3P/uhttpd/utils.c b/src/3P/uhttpd/utils.c new file mode 100644 index 00000000..9342eb66 --- /dev/null +++ b/src/3P/uhttpd/utils.c @@ -0,0 +1,251 @@ +/* + * 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 "uhttpd.h" + +bool uh_use_chunked(struct client *cl) +{ + if (cl->request.version != UH_HTTP_VER_1_1) + return false; + + if (cl->request.method == UH_HTTP_MSG_HEAD || cl->request.method == UH_HTTP_MSG_OPTIONS) + return false; + + /* RFC2616 10.2.5, 10.3.5 */ + if (cl->http_code == 204 || cl->http_code == 304) + return false; + + return !cl->request.disable_chunked; +} + +void uh_chunk_write(struct client *cl, const void *data, int len) +{ + bool chunked = uh_use_chunked(cl); + + if (cl->state == CLIENT_STATE_CLEANUP) + return; + + uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000); + if (chunked) + ustream_printf(cl->us, "%X\r\n", len); + ustream_write(cl->us, data, len, true); + if (chunked) + ustream_printf(cl->us, "\r\n", len); +} + +void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg) +{ + char buf[256]; + va_list arg2; + int len; + + if (cl->state == CLIENT_STATE_CLEANUP) + return; + + uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000); + if (!uh_use_chunked(cl)) { + ustream_vprintf(cl->us, format, arg); + return; + } + + va_copy(arg2, arg); + len = vsnprintf(buf, sizeof(buf), format, arg2); + va_end(arg2); + + ustream_printf(cl->us, "%X\r\n", len); + if (len < sizeof(buf)) + ustream_write(cl->us, buf, len, true); + else + ustream_vprintf(cl->us, format, arg); + ustream_printf(cl->us, "\r\n", len); +} + +void uh_chunk_printf(struct client *cl, const char *format, ...) +{ + va_list arg; + + va_start(arg, format); + uh_chunk_vprintf(cl, format, arg); + va_end(arg); +} + +void uh_chunk_eof(struct client *cl) +{ + if (!uh_use_chunked(cl)) + return; + + if (cl->state == CLIENT_STATE_CLEANUP) + return; + + ustream_printf(cl->us, "0\r\n\r\n"); +} + +/* blen is the size of buf; slen is the length of src. The input-string need +** not be, and the output string will not be, null-terminated. Returns the +** length of the decoded string, -1 on buffer overflow, -2 on malformed string. */ +int uh_urldecode(char *buf, int blen, const char *src, int slen) +{ + int i; + int len = 0; + +#define hex(x) \ + (((x) <= '9') ? ((x) - '0') : \ + (((x) <= 'F') ? ((x) - 'A' + 10) : \ + ((x) - 'a' + 10))) + + for (i = 0; (i < slen) && (len < blen); i++) + { + if (src[i] != '%') { + buf[len++] = src[i]; + continue; + } + + if (i + 2 >= slen || !isxdigit(src[i + 1]) || !isxdigit(src[i + 2])) + return -2; + + buf[len++] = (char)(16 * hex(src[i+1]) + hex(src[i+2])); + i += 2; + } + buf[len] = 0; + + return (i == slen) ? len : -1; +} + +/* blen is the size of buf; slen is the length of src. The input-string need +** not be, and the output string will not be, null-terminated. Returns the +** length of the encoded string, or -1 on error (buffer overflow) */ +int uh_urlencode(char *buf, int blen, const char *src, int slen) +{ + int i; + int len = 0; + static const char hex[] = "0123456789abcdef"; + + for (i = 0; (i < slen) && (len < blen); i++) + { + if( isalnum(src[i]) || (src[i] == '-') || (src[i] == '_') || + (src[i] == '.') || (src[i] == '~') ) + { + buf[len++] = src[i]; + } + else if ((len+3) <= blen) + { + buf[len++] = '%'; + buf[len++] = hex[(src[i] >> 4) & 15]; + buf[len++] = hex[ src[i] & 15]; + } + else + { + len = -1; + break; + } + } + + return (i == slen) ? len : -1; +} + +int uh_b64decode(char *buf, int blen, const void *src, int slen) +{ + const unsigned char *str = src; + unsigned int cout = 0; + unsigned int cin = 0; + int len = 0; + int i = 0; + + for (i = 0; (i <= slen) && (str[i] != 0); i++) + { + cin = str[i]; + + if ((cin >= '0') && (cin <= '9')) + cin = cin - '0' + 52; + else if ((cin >= 'A') && (cin <= 'Z')) + cin = cin - 'A'; + else if ((cin >= 'a') && (cin <= 'z')) + cin = cin - 'a' + 26; + else if (cin == '+') + cin = 62; + else if (cin == '/') + cin = 63; + else if (cin == '=') + cin = 0; + else + continue; + + cout = (cout << 6) | cin; + + if ((i % 4) != 3) + continue; + + if ((len + 3) >= blen) + break; + + buf[len++] = (char)(cout >> 16); + buf[len++] = (char)(cout >> 8); + buf[len++] = (char)(cout); + } + + buf[len++] = 0; + return len; +} + +bool uh_path_match(const char *prefix, const char *url) +{ + int len = strlen(prefix); + + /* A prefix of "/" will - by definition - match any url */ + if (prefix[0] == '/' && len == 1) + return true; + + if (strncmp(url, prefix, len) != 0) + return false; + + return url[len] == '/' || url[len] == 0; +} + +char *uh_split_header(char *str) +{ + char *val; + + val = strchr(str, ':'); + if (!val) + return NULL; + + *val = 0; + val++; + + while (isspace(*val)) + val++; + + return val; +} + +bool uh_addr_rfc1918(struct uh_addr *addr) +{ + uint32_t a; + + if (addr->family != AF_INET) + return false; + + a = htonl(addr->in.s_addr); + return ((a >= 0x0A000000) && (a <= 0x0AFFFFFF)) || + ((a >= 0xAC100000) && (a <= 0xAC1FFFFF)) || + ((a >= 0xC0A80000) && (a <= 0xC0A8FFFF)); + + return 0; +} diff --git a/src/3P/uhttpd/utils.h b/src/3P/uhttpd/utils.h new file mode 100644 index 00000000..c583f453 --- /dev/null +++ b/src/3P/uhttpd/utils.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef _UHTTPD_UTILS_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct uh_addr { + uint8_t family; + uint16_t port; + union { + struct in_addr in; + struct in6_addr in6; + }; +}; + +#define min(x, y) (((x) < (y)) ? (x) : (y)) +#define max(x, y) (((x) > (y)) ? (x) : (y)) + +#define array_size(x) \ + (sizeof(x) / sizeof(x[0])) + +#define fd_cloexec(fd) \ + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC) + +#ifdef __APPLE__ +static inline void clearenv(void) +{ + extern char **environ; + *environ = NULL; +} + +time_t timegm (struct tm *tm); + +#endif + +#ifdef __GNUC__ +#define __printf(a, b) __attribute__((format(printf, a, b))) +#else +#define __printf(a, b) +#endif + +int uh_urldecode(char *buf, int blen, const char *src, int slen); +int uh_urlencode(char *buf, int blen, const char *src, int slen); +int uh_b64decode(char *buf, int blen, const void *src, int slen); +bool uh_path_match(const char *prefix, const char *url); +char *uh_split_header(char *str); +bool uh_addr_rfc1918(struct uh_addr *addr); + +#endif