summaryrefslogtreecommitdiff
path: root/package/network/services/uhttpd
diff options
context:
space:
mode:
authornbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-10-10 12:32:29 +0000
committernbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-10-10 12:32:29 +0000
commit9c8997d54dc9df184bfcedeabf0b3c85cf5e6753 (patch)
tree46b83031a0da1b4458317413c00d13c252c72afa /package/network/services/uhttpd
parenteecf5b17520f6b3b6ffb45ac7dca298d93b27501 (diff)
packages: sort network related packages into package/network/
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@33688 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'package/network/services/uhttpd')
-rw-r--r--package/network/services/uhttpd/Makefile164
-rw-r--r--package/network/services/uhttpd/files/uhttpd.config89
-rwxr-xr-xpackage/network/services/uhttpd/files/uhttpd.init135
-rw-r--r--package/network/services/uhttpd/src/Makefile89
-rw-r--r--package/network/services/uhttpd/src/uhttpd-cgi.c556
-rw-r--r--package/network/services/uhttpd/src/uhttpd-cgi.h43
-rw-r--r--package/network/services/uhttpd/src/uhttpd-file.c438
-rw-r--r--package/network/services/uhttpd/src/uhttpd-file.h36
-rw-r--r--package/network/services/uhttpd/src/uhttpd-lua.c579
-rw-r--r--package/network/services/uhttpd/src/uhttpd-lua.h44
-rw-r--r--package/network/services/uhttpd/src/uhttpd-mimetypes.h86
-rw-r--r--package/network/services/uhttpd/src/uhttpd-tls.c170
-rw-r--r--package/network/services/uhttpd/src/uhttpd-tls.h36
-rw-r--r--package/network/services/uhttpd/src/uhttpd-ubus.c957
-rw-r--r--package/network/services/uhttpd/src/uhttpd-ubus.h70
-rw-r--r--package/network/services/uhttpd/src/uhttpd-utils.c1081
-rw-r--r--package/network/services/uhttpd/src/uhttpd-utils.h140
-rw-r--r--package/network/services/uhttpd/src/uhttpd.c1288
-rw-r--r--package/network/services/uhttpd/src/uhttpd.h214
19 files changed, 6215 insertions, 0 deletions
diff --git a/package/network/services/uhttpd/Makefile b/package/network/services/uhttpd/Makefile
new file mode 100644
index 0000000000..245426b4e2
--- /dev/null
+++ b/package/network/services/uhttpd/Makefile
@@ -0,0 +1,164 @@
+#
+# Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=uhttpd
+PKG_RELEASE:=40
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+PKG_CONFIG_DEPENDS := \
+ CONFIG_PACKAGE_uhttpd_debug \
+ CONFIG_PACKAGE_uhttpd-mod-lua \
+ CONFIG_PACKAGE_uhttpd-mod-tls \
+ CONFIG_PACKAGE_uhttpd-mod-tls_cyassl \
+ CONFIG_PACKAGE_uhttpd-mod-tls_openssl \
+ CONFIG_PACKAGE_uhttpd-mod-ubus
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/uhttpd/default
+ SECTION:=net
+ CATEGORY:=Network
+ SUBMENU:=Web Servers/Proxies
+ TITLE:=uHTTPd - tiny, single threaded HTTP server
+ MAINTAINER:=Jo-Philipp Wich <xm@subsignal.org>
+endef
+
+define Package/uhttpd
+ $(Package/uhttpd/default)
+ DEPENDS:=+libubox
+endef
+
+define Package/uhttpd/description
+ uHTTPd is a tiny single threaded HTTP server with TLS, CGI and Lua
+ support. It is intended as a drop-in replacement for the Busybox
+ HTTP daemon.
+endef
+
+define Package/uhttpd/config
+ config PACKAGE_uhttpd_debug
+ bool "Build with debug messages"
+ default n
+endef
+
+
+define Package/uhttpd-mod-tls
+ $(Package/uhttpd/default)
+ TITLE+= (TLS plugin)
+ DEPENDS:=uhttpd +PACKAGE_uhttpd-mod-tls_cyassl:libcyassl +PACKAGE_uhttpd-mod-tls_openssl:libopenssl
+endef
+
+define Package/uhttpd-mod-tls/description
+ The TLS plugin adds HTTPS support to uHTTPd.
+endef
+
+define Package/uhttpd-mod-tls/config
+ choice
+ depends on PACKAGE_uhttpd-mod-tls
+ prompt "TLS Provider"
+ default PACKAGE_uhttpd-mod-tls_cyassl
+
+ config PACKAGE_uhttpd-mod-tls_cyassl
+ bool "CyaSSL"
+
+ config PACKAGE_uhttpd-mod-tls_openssl
+ bool "OpenSSL"
+ endchoice
+endef
+
+UHTTPD_TLS:=
+TLS_CFLAGS:=
+TLS_LDFLAGS:=
+
+ifneq ($(CONFIG_PACKAGE_uhttpd-mod-tls_cyassl),)
+ UHTTPD_TLS:=cyassl
+ TLS_CFLAGS:=-I$(STAGING_DIR)/usr/include/cyassl -DTLS_IS_CYASSL
+ TLS_LDFLAGS:=-lcyassl -lm
+endif
+
+ifneq ($(CONFIG_PACKAGE_uhttpd-mod-tls_openssl),)
+ UHTTPD_TLS:=openssl
+ TLS_CFLAGS:=-DTLS_IS_OPENSSL
+ TLS_LDFLAGS:=-lssl
+endif
+
+
+define Package/uhttpd-mod-lua
+ $(Package/uhttpd/default)
+ TITLE+= (Lua plugin)
+ DEPENDS:=uhttpd +liblua
+endef
+
+define Package/uhttpd-mod-lua/description
+ The Lua plugin adds a CGI-like Lua runtime interface to uHTTPd.
+endef
+
+
+define Package/uhttpd-mod-ubus
+ $(Package/uhttpd/default)
+ TITLE+= (ubus plugin)
+ DEPENDS:=uhttpd +libubus +libblobmsg-json
+endef
+
+define Package/uhttpd-mod-ubus/description
+ The ubus plugin adds a HTTP/JSON RPC proxy for ubus and publishes the
+ session.* namespace and procedures.
+endef
+
+
+TARGET_CFLAGS += $(TLS_CFLAGS) $(if $(CONFIG_PACKAGE_uhttpd_debug),-DDEBUG) -ggdb3
+TARGET_LDFLAGS += -lubox -Wl,-rpath-link=$(STAGING_DIR)/usr/lib
+MAKE_VARS += \
+ FPIC="$(FPIC)" \
+ LUA_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-lua),1)" \
+ TLS_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-tls),1)" \
+ UBUS_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-ubus),1)" \
+ UHTTPD_TLS="$(UHTTPD_TLS)" \
+ TLS_CFLAGS="$(TLS_CFLAGS)" \
+ TLS_LDFLAGS="$(TLS_LDFLAGS)"
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+ $(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Package/uhttpd/conffiles
+/etc/config/uhttpd
+/etc/uhttpd.crt
+/etc/uhttpd.key
+endef
+
+define Package/uhttpd/install
+ $(INSTALL_DIR) $(1)/etc/init.d
+ $(INSTALL_BIN) ./files/uhttpd.init $(1)/etc/init.d/uhttpd
+ $(INSTALL_DIR) $(1)/etc/config
+ $(INSTALL_CONF) ./files/uhttpd.config $(1)/etc/config/uhttpd
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd $(1)/usr/sbin/uhttpd
+endef
+
+define Package/uhttpd-mod-tls/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd_tls.so $(1)/usr/lib/
+endef
+
+define Package/uhttpd-mod-lua/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd_lua.so $(1)/usr/lib/
+endef
+
+define Package/uhttpd-mod-ubus/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd_ubus.so $(1)/usr/lib/
+endef
+
+
+$(eval $(call BuildPackage,uhttpd))
+$(eval $(call BuildPackage,uhttpd-mod-tls))
+$(eval $(call BuildPackage,uhttpd-mod-lua))
+$(eval $(call BuildPackage,uhttpd-mod-ubus))
diff --git a/package/network/services/uhttpd/files/uhttpd.config b/package/network/services/uhttpd/files/uhttpd.config
new file mode 100644
index 0000000000..b33411e970
--- /dev/null
+++ b/package/network/services/uhttpd/files/uhttpd.config
@@ -0,0 +1,89 @@
+# Server configuration
+config uhttpd main
+
+ # HTTP listen addresses, multiple allowed
+ list listen_http 0.0.0.0:80
+# list listen_http [::]:80
+
+ # HTTPS listen addresses, multiple allowed
+ list listen_https 0.0.0.0:443
+# list listen_https [::]:443
+
+ # Server document root
+ option home /www
+
+ # Reject requests from RFC1918 IP addresses
+ # directed to the servers public IP(s).
+ # This is a DNS rebinding countermeasure.
+ option rfc1918_filter 1
+
+ # Maximum number of concurrent requests.
+ # If this number is exceeded, further requests are
+ # queued until the number of running requests drops
+ # below the limit again.
+ option max_requests 3
+
+ # Certificate and private key for HTTPS.
+ # If no listen_https addresses are given,
+ # the key options are ignored.
+ option cert /etc/uhttpd.crt
+ option key /etc/uhttpd.key
+
+ # CGI url prefix, will be searched in docroot.
+ # Default is /cgi-bin
+ option cgi_prefix /cgi-bin
+
+ # List of extension->interpreter mappings.
+ # Files with an associated interpreter can
+ # be called outside of the CGI prefix and do
+ # not need to be executable.
+# list interpreter ".php=/usr/bin/php-cgi"
+# list interpreter ".cgi=/usr/bin/perl"
+
+ # Lua url prefix and handler script.
+ # Lua support is disabled if no prefix given.
+# option lua_prefix /luci
+# option lua_handler /usr/lib/lua/luci/sgi/uhttpd.lua
+
+ # CGI/Lua timeout, if the called script does not
+ # write data within the given amount of seconds,
+ # the server will terminate the request with
+ # 504 Gateway Timeout response.
+ option script_timeout 60
+
+ # Network timeout, if the current connection is
+ # blocked for the specified amount of seconds,
+ # the server will terminate the associated
+ # request process.
+ option network_timeout 30
+
+ # TCP Keep-Alive, send periodic keep-alive probes
+ # over established connections to detect dead peers.
+ # The value is given in seconds to specify the
+ # interval between subsequent probes.
+ # Setting this to 0 will disable TCP keep-alive.
+ option tcp_keepalive 1
+
+ # Basic auth realm, defaults to local hostname
+# option realm OpenWrt
+
+ # Configuration file in busybox httpd format
+# option config /etc/httpd.conf
+
+
+# Certificate defaults for px5g key generator
+config cert px5g
+
+ # Validity time
+ option days 730
+
+ # RSA key size
+ option bits 1024
+
+ # Location
+ option country DE
+ option state Berlin
+ option location Berlin
+
+ # Common name
+ option commonname OpenWrt
diff --git a/package/network/services/uhttpd/files/uhttpd.init b/package/network/services/uhttpd/files/uhttpd.init
new file mode 100755
index 0000000000..379a9f5b5f
--- /dev/null
+++ b/package/network/services/uhttpd/files/uhttpd.init
@@ -0,0 +1,135 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2010 Jo-Philipp Wich
+
+START=50
+
+SERVICE_DAEMONIZE=1
+SERVICE_WRITE_PID=1
+
+UHTTPD_BIN="/usr/sbin/uhttpd"
+PX5G_BIN="/usr/sbin/px5g"
+
+append_arg() {
+ local cfg="$1"
+ local var="$2"
+ local opt="$3"
+ local def="$4"
+ local val
+
+ config_get val "$cfg" "$var"
+ [ -n "$val" -o -n "$def" ] && append UHTTPD_ARGS "$opt ${val:-$def}"
+}
+
+append_bool() {
+ local cfg="$1"
+ local var="$2"
+ local opt="$3"
+ local def="$4"
+ local val
+
+ config_get_bool val "$cfg" "$var" "$def"
+ [ "$val" = 1 ] && append UHTTPD_ARGS "$opt"
+}
+
+generate_keys() {
+ local cfg="$1"
+ local key="$2"
+ local crt="$3"
+ local days bits country state location commonname
+
+ config_get days "$cfg" days
+ config_get bits "$cfg" bits
+ config_get country "$cfg" country
+ config_get state "$cfg" state
+ config_get location "$cfg" location
+ config_get commonname "$cfg" commonname
+
+ [ -x "$PX5G_BIN" ] && {
+ $PX5G_BIN selfsigned -der \
+ -days ${days:-730} -newkey rsa:${bits:-1024} -keyout "$UHTTPD_KEY" -out "$UHTTPD_CERT" \
+ -subj /C="${country:-DE}"/ST="${state:-Saxony}"/L="${location:-Leipzig}"/CN="${commonname:-OpenWrt}"
+ }
+}
+
+start_instance()
+{
+ UHTTPD_ARGS=""
+ UHTTPD_CERT=""
+ UHTTPD_KEY=""
+
+ local cfg="$1"
+ local realm="$(uci_get system.@system[0].hostname)"
+ local listen http https interpreter path
+
+ append_arg "$cfg" home "-h"
+ append_arg "$cfg" realm "-r" "${realm:-OpenWrt}"
+ append_arg "$cfg" config "-c"
+ append_arg "$cfg" cgi_prefix "-x"
+ append_arg "$cfg" lua_prefix "-l"
+ append_arg "$cfg" lua_handler "-L"
+ append_arg "$cfg" script_timeout "-t"
+ append_arg "$cfg" network_timeout "-T"
+ append_arg "$cfg" tcp_keepalive "-A"
+ append_arg "$cfg" error_page "-E"
+ append_arg "$cfg" index_page "-I"
+ append_arg "$cfg" max_requests "-n" 3
+
+ append_bool "$cfg" no_symlinks "-S" 0
+ append_bool "$cfg" no_dirlists "-D" 0
+ append_bool "$cfg" rfc1918_filter "-R" 0
+
+ config_get http "$cfg" listen_http
+ for listen in $http; do
+ append UHTTPD_ARGS "-p $listen"
+ done
+
+ config_get interpreter "$cfg" interpreter
+ for path in $interpreter; do
+ append UHTTPD_ARGS "-i $path"
+ done
+
+ config_get https "$cfg" listen_https
+ config_get UHTTPD_KEY "$cfg" key /etc/uhttpd.key
+ config_get UHTTPD_CERT "$cfg" cert /etc/uhttpd.crt
+
+ [ -n "$https" ] && {
+ [ -f "$UHTTPD_CERT" -a -f "$UHTTPD_KEY" ] || {
+ config_foreach generate_keys cert
+ }
+
+ [ -f "$UHTTPD_CERT" -a -f "$UHTTPD_KEY" ] && {
+ append_arg "$cfg" cert "-C"
+ append_arg "$cfg" key "-K"
+
+ for listen in $https; do
+ append UHTTPD_ARGS "-s $listen"
+ done
+ }
+ }
+
+ SERVICE_PID_FILE=/var/run/uhttpd_${cfg}.pid
+ service_start $UHTTPD_BIN -f $UHTTPD_ARGS
+
+ # Check if daemon is running, if not then
+ # re-execute in foreground to display error.
+ sleep 1 && service_check $UHTTPD_BIN || \
+ $UHTTPD_BIN -f $UHTTPD_ARGS
+}
+
+stop_instance()
+{
+ local cfg="$1"
+
+ SERVICE_PID_FILE=/var/run/uhttpd_${cfg}.pid
+ service_stop $UHTTPD_BIN
+}
+
+start() {
+ config_load uhttpd
+ config_foreach start_instance uhttpd
+}
+
+stop() {
+ config_load uhttpd
+ config_foreach stop_instance uhttpd
+}
diff --git a/package/network/services/uhttpd/src/Makefile b/package/network/services/uhttpd/src/Makefile
new file mode 100644
index 0000000000..98226ed206
--- /dev/null
+++ b/package/network/services/uhttpd/src/Makefile
@@ -0,0 +1,89 @@
+CGI_SUPPORT ?= 1
+LUA_SUPPORT ?= 1
+TLS_SUPPORT ?= 1
+UHTTPD_TLS ?= cyassl
+
+CFLAGS ?= -I./lua-5.1.4/src $(TLS_CFLAGS) -O0 -ggdb3
+LDFLAGS ?= -L./lua-5.1.4/src
+
+CFLAGS += -Wall --std=gnu99
+
+ifeq ($(UHTTPD_TLS),openssl)
+ TLS_LDFLAGS ?= -L./openssl-0.9.8m -lssl
+ TLS_CFLAGS ?= -I./openssl-0.9.8m/include -DTLS_IS_OPENSSL
+else
+ TLS_LDFLAGS ?= -L./cyassl-1.4.0/src/.libs -lcyassl
+ TLS_CFLAGS ?= -I./cyassl-1.4.0/include -DTLS_IS_CYASSL
+endif
+
+OBJ := uhttpd.o uhttpd-file.o uhttpd-utils.o
+LIB := -Wl,--export-dynamic -lcrypt -ldl
+
+TLSLIB :=
+LUALIB :=
+
+HAVE_SHADOW=$(shell echo 'int main(void){ return !getspnam("root"); }' | \
+ $(CC) -include shadow.h -xc -o/dev/null - 2>/dev/null && echo yes)
+
+ifeq ($(HAVE_SHADOW),yes)
+ CFLAGS += -DHAVE_SHADOW
+endif
+
+ifeq ($(TLS_SUPPORT),1)
+ CFLAGS += -DHAVE_TLS
+endif
+
+ifeq ($(CGI_SUPPORT),1)
+ CFLAGS += -DHAVE_CGI
+endif
+
+ifeq ($(LUA_SUPPORT),1)
+ CFLAGS += -DHAVE_LUA
+endif
+
+ifeq ($(UBUS_SUPPORT),1)
+ CFLAGS += -DHAVE_UBUS
+endif
+
+
+world: compile
+
+ifeq ($(CGI_SUPPORT),1)
+ OBJ += uhttpd-cgi.o
+endif
+
+ifeq ($(LUA_SUPPORT),1)
+ LUALIB := uhttpd_lua.so
+
+ $(LUALIB): uhttpd-lua.c
+ $(CC) $(CFLAGS) $(LDFLAGS) $(FPIC) \
+ -shared -lm -llua -ldl \
+ -o $(LUALIB) uhttpd-lua.c
+endif
+
+ifeq ($(TLS_SUPPORT),1)
+ TLSLIB := uhttpd_tls.so
+
+ $(TLSLIB): uhttpd-tls.c
+ $(CC) $(CFLAGS) $(LDFLAGS) $(FPIC) \
+ -shared $(TLS_LDFLAGS) \
+ -o $(TLSLIB) uhttpd-tls.c
+endif
+
+ifeq ($(UBUS_SUPPORT),1)
+ UBUSLIB := uhttpd_ubus.so
+
+ $(UBUSLIB): uhttpd-ubus.c
+ $(CC) $(CFLAGS) $(LDFLAGS) $(FPIC) \
+ -shared -lubus -ljson -lblobmsg_json \
+ -o $(UBUSLIB) uhttpd-ubus.c
+endif
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+compile: $(OBJ) $(TLSLIB) $(LUALIB) $(UBUSLIB)
+ $(CC) -o uhttpd $(LDFLAGS) $(OBJ) $(LIB)
+
+clean:
+ rm -f *.o *.so uhttpd
diff --git a/package/network/services/uhttpd/src/uhttpd-cgi.c b/package/network/services/uhttpd/src/uhttpd-cgi.c
new file mode 100644
index 0000000000..69af90db45
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-cgi.c
@@ -0,0 +1,556 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - CGI handler
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+#include "uhttpd-cgi.h"
+
+
+static bool
+uh_cgi_header_parse(struct http_response *res, char *buf, int len, int *off)
+{
+ char *bufptr = NULL;
+ char *hdrname = NULL;
+ int hdrcount = 0;
+ int pos = 0;
+
+ if (((bufptr = strfind(buf, len, "\r\n\r\n", 4)) != NULL) ||
+ ((bufptr = strfind(buf, len, "\n\n", 2)) != NULL))
+ {
+ *off = (int)(bufptr - buf) + ((bufptr[0] == '\r') ? 4 : 2);
+
+ memset(res, 0, sizeof(*res));
+
+ res->statuscode = 200;
+ res->statusmsg = "OK";
+
+ bufptr = &buf[0];
+
+ for (pos = 0; pos < *off; pos++)
+ {
+ if (!hdrname && (buf[pos] == ':'))
+ {
+ buf[pos++] = 0;
+
+ if ((pos < len) && (buf[pos] == ' '))
+ pos++;
+
+ if (pos < len)
+ {
+ hdrname = bufptr;
+ bufptr = &buf[pos];
+ }
+ }
+
+ else if ((buf[pos] == '\r') || (buf[pos] == '\n'))
+ {
+ if (! hdrname)
+ break;
+
+ buf[pos++] = 0;
+
+ if ((pos < len) && (buf[pos] == '\n'))
+ pos++;
+
+ if (pos <= len)
+ {
+ if ((hdrcount+1) < array_size(res->headers))
+ {
+ if (!strcasecmp(hdrname, "Status"))
+ {
+ res->statuscode = atoi(bufptr);
+
+ if (res->statuscode < 100)
+ res->statuscode = 200;
+
+ if (((bufptr = strchr(bufptr, ' ')) != NULL) &&
+ (&bufptr[1] != 0))
+ {
+ res->statusmsg = &bufptr[1];
+ }
+
+ D("CGI: HTTP/1.x %03d %s\n",
+ res->statuscode, res->statusmsg);
+ }
+ else
+ {
+ D("CGI: HTTP: %s: %s\n", hdrname, bufptr);
+
+ res->headers[hdrcount++] = hdrname;
+ res->headers[hdrcount++] = bufptr;
+ }
+
+ bufptr = &buf[pos];
+ hdrname = NULL;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static char * uh_cgi_header_lookup(struct http_response *res,
+ const char *hdrname)
+{
+ int i;
+
+ foreach_header(i, res->headers)
+ {
+ if (!strcasecmp(res->headers[i], hdrname))
+ return res->headers[i+1];
+ }
+
+ return NULL;
+}
+
+static void uh_cgi_shutdown(struct uh_cgi_state *state)
+{
+ free(state);
+}
+
+static bool uh_cgi_socket_cb(struct client *cl)
+{
+ int i, len, blen, hdroff;
+ char buf[UH_LIMIT_MSGHEAD];
+
+ struct uh_cgi_state *state = (struct uh_cgi_state *)cl->priv;
+ struct http_response *res = &cl->response;
+ struct http_request *req = &cl->request;
+
+ /* there is unread post data waiting */
+ while (state->content_length > 0)
+ {
+ /* remaining data in http head buffer ... */
+ if (cl->httpbuf.len > 0)
+ {
+ len = min(state->content_length, cl->httpbuf.len);
+
+ D("CGI: Child(%d) feed %d HTTP buffer bytes\n", cl->proc.pid, len);
+
+ memcpy(buf, cl->httpbuf.ptr, len);
+
+ cl->httpbuf.len -= len;
+ cl->httpbuf.ptr +=len;
+ }
+
+ /* read it from socket ... */
+ else
+ {
+ len = uh_tcp_recv(cl, buf,
+ min(state->content_length, sizeof(buf)));
+
+ if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
+ break;
+
+ D("CGI: Child(%d) feed %d/%d TCP socket bytes\n",
+ cl->proc.pid, len, min(state->content_length, sizeof(buf)));
+ }
+
+ if (len)
+ state->content_length -= len;
+ else
+ state->content_length = 0;
+
+ /* ... write to CGI process */
+ len = uh_raw_send(cl->wpipe.fd, buf, len,
+ cl->server->conf->script_timeout);
+
+ /* explicit EOF notification for the child */
+ if (state->content_length <= 0)
+ uh_ufd_remove(&cl->wpipe);
+ }
+
+ /* try to read data from child */
+ while ((len = uh_raw_recv(cl->rpipe.fd, buf, state->header_sent
+ ? sizeof(buf) : state->httpbuf.len, -1)) > 0)
+ {
+ /* we have not pushed out headers yet, parse input */
+ if (!state->header_sent)
+ {
+ /* try to parse header ... */
+ memcpy(state->httpbuf.ptr, buf, len);
+ state->httpbuf.len -= len;
+ state->httpbuf.ptr += len;
+
+ blen = state->httpbuf.ptr - state->httpbuf.buf;
+
+ if (uh_cgi_header_parse(res, state->httpbuf.buf, blen, &hdroff))
+ {
+ /* write status */
+ ensure_out(uh_http_sendf(cl, NULL,
+ "%s %03d %s\r\n"
+ "Connection: close\r\n",
+ http_versions[req->version],
+ res->statuscode, res->statusmsg));
+
+ /* add Content-Type if no Location or Content-Type */
+ if (!uh_cgi_header_lookup(res, "Location") &&
+ !uh_cgi_header_lookup(res, "Content-Type"))
+ {
+ ensure_out(uh_http_send(cl, NULL,
+ "Content-Type: text/plain\r\n", -1));
+ }
+
+ /* if request was HTTP 1.1 we'll respond chunked */
+ if ((req->version > UH_HTTP_VER_1_0) &&
+ !uh_cgi_header_lookup(res, "Transfer-Encoding"))
+ {
+ ensure_out(uh_http_send(cl, NULL,
+ "Transfer-Encoding: chunked\r\n", -1));
+ }
+
+ /* write headers from CGI program */
+ foreach_header(i, res->headers)
+ {
+ ensure_out(uh_http_sendf(cl, NULL, "%s: %s\r\n",
+ res->headers[i], res->headers[i+1]));
+ }
+
+ /* terminate header */
+ ensure_out(uh_http_send(cl, NULL, "\r\n", -1));
+
+ state->header_sent = true;
+
+ /* push out remaining head buffer */
+ if (hdroff < blen)
+ {
+ D("CGI: Child(%d) relaying %d rest bytes\n",
+ cl->proc.pid, blen - hdroff);
+
+ ensure_out(uh_http_send(cl, req,
+ state->httpbuf.buf + hdroff,
+ blen - hdroff));
+ }
+ }
+
+ /* ... failed and head buffer exceeded */
+ else if (!state->httpbuf.len)
+ {
+ /* I would do this ...
+ *
+ * uh_cgi_error_500(cl, req,
+ * "The CGI program generated an "
+ * "invalid response:\n\n");
+ *
+ * ... but in order to stay as compatible as possible,
+ * treat whatever we got as text/plain response and
+ * build the required headers here.
+ */
+
+ ensure_out(uh_http_sendf(cl, NULL,
+ "%s 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "%s\r\n",
+ http_versions[req->version],
+ (req->version > UH_HTTP_VER_1_0)
+ ? "Transfer-Encoding: chunked\r\n" : ""
+ ));
+
+ state->header_sent = true;
+
+ D("CGI: Child(%d) relaying %d invalid bytes\n",
+ cl->proc.pid, len);
+
+ ensure_out(uh_http_send(cl, req, buf, len));
+ }
+ }
+ else
+ {
+ /* headers complete, pass through buffer to socket */
+ D("CGI: Child(%d) relaying %d normal bytes\n", cl->proc.pid, len);
+ ensure_out(uh_http_send(cl, req, buf, len));
+ }
+ }
+
+ /* got EOF or read error from child */
+ if ((len == 0) ||
+ ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1)))
+ {
+ D("CGI: Child(%d) presumed dead [%s]\n", cl->proc.pid, strerror(errno));
+
+ goto out;
+ }
+
+ return true;
+
+out:
+ if (!state->header_sent)
+ {
+ if (cl->timeout.pending)
+ uh_http_sendhf(cl, 502, "Bad Gateway",
+ "The CGI process did not produce any response\n");
+ else
+ uh_http_sendhf(cl, 504, "Gateway Timeout",
+ "The CGI process took too long to produce a "
+ "response\n");
+ }
+ else
+ {
+ uh_http_send(cl, req, "", 0);
+ }
+
+ uh_cgi_shutdown(state);
+ return false;
+}
+
+bool uh_cgi_request(struct client *cl, struct path_info *pi,
+ struct interpreter *ip)
+{
+ int i;
+
+ int rfd[2] = { 0, 0 };
+ int wfd[2] = { 0, 0 };
+
+ pid_t child;
+
+ struct uh_cgi_state *state;
+ struct http_request *req = &cl->request;
+
+ /* allocate state */
+ if (!(state = malloc(sizeof(*state))))
+ {
+ uh_http_sendhf(cl, 500, "Internal Server Error", "Out of memory");
+ return false;
+ }
+
+ /* spawn pipes for me->child, child->me */
+ if ((pipe(rfd) < 0) || (pipe(wfd) < 0))
+ {
+ if (rfd[0] > 0) close(rfd[0]);
+ if (rfd[1] > 0) close(rfd[1]);
+ if (wfd[0] > 0) close(wfd[0]);
+ if (wfd[1] > 0) close(wfd[1]);
+
+ uh_http_sendhf(cl, 500, "Internal Server Error",
+ "Failed to create pipe: %s\n", strerror(errno));
+
+ return false;
+ }
+
+ /* fork off child process */
+ switch ((child = fork()))
+ {
+ /* oops */
+ case -1:
+ uh_http_sendhf(cl, 500, "Internal Server Error",
+ "Failed to fork child: %s\n", strerror(errno));
+
+ return false;
+
+ /* exec child */
+ case 0:
+#ifdef DEBUG
+ sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0"));
+#endif
+
+ /* do not leak parent epoll descriptor */
+ uloop_done();
+
+ /* close loose pipe ends */
+ close(rfd[0]);
+ close(wfd[1]);
+
+ /* patch stdout and stdin to pipes */
+ dup2(rfd[1], 1);
+ dup2(wfd[0], 0);
+
+ /* avoid leaking our pipe into child-child processes */
+ fd_cloexec(rfd[1]);
+ fd_cloexec(wfd[0]);
+
+ /* check for regular, world-executable file _or_ interpreter */
+ if (((pi->stat.st_mode & S_IFREG) &&
+ (pi->stat.st_mode & S_IXOTH)) || (ip != NULL))
+ {
+ /* build environment */
+ clearenv();
+
+ /* common information */
+ setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
+ setenv("SERVER_SOFTWARE", "uHTTPd", 1);
+ setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin", 1);
+
+#ifdef HAVE_TLS
+ /* https? */
+ if (cl->tls)
+ setenv("HTTPS", "on", 1);
+#endif
+
+ /* addresses */
+ setenv("SERVER_NAME", sa_straddr(&cl->servaddr), 1);
+ setenv("SERVER_ADDR", sa_straddr(&cl->servaddr), 1);
+ setenv("SERVER_PORT", sa_strport(&cl->servaddr), 1);
+ setenv("REMOTE_HOST", sa_straddr(&cl->peeraddr), 1);
+ setenv("REMOTE_ADDR", sa_straddr(&cl->peeraddr), 1);
+ setenv("REMOTE_PORT", sa_strport(&cl->peeraddr), 1);
+
+ /* path information */
+ setenv("SCRIPT_NAME", pi->name, 1);
+ setenv("SCRIPT_FILENAME", pi->phys, 1);
+ setenv("DOCUMENT_ROOT", pi->root, 1);
+ setenv("QUERY_STRING", pi->query ? pi->query : "", 1);
+
+ if (pi->info)
+ setenv("PATH_INFO", pi->info, 1);
+
+ /* REDIRECT_STATUS, php-cgi wants it */
+ switch (req->redirect_status)
+ {
+ case 404:
+ setenv("REDIRECT_STATUS", "404", 1);
+ break;
+
+ default:
+ setenv("REDIRECT_STATUS", "200", 1);
+ break;
+ }
+
+ /* http version */
+ setenv("SERVER_PROTOCOL", http_versions[req->version], 1);
+
+ /* request method */
+ setenv("REQUEST_METHOD", http_methods[req->method], 1);
+
+ /* request url */
+ setenv("REQUEST_URI", req->url, 1);
+
+ /* remote user */
+ if (req->realm)
+ setenv("REMOTE_USER", req->realm->user, 1);
+
+ /* request message headers */
+ foreach_header(i, req->headers)
+ {
+ if (!strcasecmp(req->headers[i], "Accept"))
+ setenv("HTTP_ACCEPT", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Accept-Charset"))
+ setenv("HTTP_ACCEPT_CHARSET", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Accept-Encoding"))
+ setenv("HTTP_ACCEPT_ENCODING", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Accept-Language"))
+ setenv("HTTP_ACCEPT_LANGUAGE", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Authorization"))
+ setenv("HTTP_AUTHORIZATION", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Connection"))
+ setenv("HTTP_CONNECTION", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Cookie"))
+ setenv("HTTP_COOKIE", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Host"))
+ setenv("HTTP_HOST", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Referer"))
+ setenv("HTTP_REFERER", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "User-Agent"))
+ setenv("HTTP_USER_AGENT", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Content-Type"))
+ setenv("CONTENT_TYPE", req->headers[i+1], 1);
+
+ else if (!strcasecmp(req->headers[i], "Content-Length"))
+ setenv("CONTENT_LENGTH", req->headers[i+1], 1);
+ }
+
+
+ /* execute child code ... */
+ if (chdir(pi->root))
+ perror("chdir()");
+
+ if (ip != NULL)
+ execl(ip->path, ip->path, pi->phys, NULL);
+ else
+ execl(pi->phys, pi->phys, NULL);
+
+ /* in case it fails ... */
+ 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));
+ }
+
+ /* 403 */
+ else
+ {
+ printf("Status: 403 Forbidden\r\n\r\n"
+ "Access to this resource is forbidden\n");
+ }
+
+ close(wfd[0]);
+ close(rfd[1]);
+ exit(0);
+
+ break;
+
+ /* parent; handle I/O relaying */
+ default:
+ memset(state, 0, sizeof(*state));
+
+ cl->rpipe.fd = rfd[0];
+ cl->wpipe.fd = wfd[1];
+ cl->proc.pid = child;
+
+ /* make pipe non-blocking */
+ fd_nonblock(cl->rpipe.fd);
+ fd_nonblock(cl->wpipe.fd);
+
+ /* close unneeded pipe ends */
+ close(rfd[1]);
+ close(wfd[0]);
+
+ D("CGI: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]);
+
+ state->httpbuf.ptr = state->httpbuf.buf;
+ state->httpbuf.len = sizeof(state->httpbuf.buf);
+
+ state->content_length = cl->httpbuf.len;
+
+ /* find content length */
+ if (req->method == UH_HTTP_MSG_POST)
+ {
+ foreach_header(i, req->headers)
+ {
+ if (!strcasecmp(req->headers[i], "Content-Length"))
+ {
+ state->content_length = atoi(req->headers[i+1]);
+ break;
+ }
+ }
+ }
+
+ cl->cb = uh_cgi_socket_cb;
+ cl->priv = state;
+
+ break;
+ }
+
+ return true;
+}
diff --git a/package/network/services/uhttpd/src/uhttpd-cgi.h b/package/network/services/uhttpd/src/uhttpd-cgi.h
new file mode 100644
index 0000000000..c7094da424
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-cgi.h
@@ -0,0 +1,43 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - CGI header
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_CGI_
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <linux/limits.h>
+
+#include <time.h>
+
+
+struct uh_cgi_state {
+ struct {
+ char buf[UH_LIMIT_MSGHEAD];
+ char *ptr;
+ int len;
+ } httpbuf;
+ int content_length;
+ bool header_sent;
+};
+
+bool uh_cgi_request(struct client *cl, struct path_info *pi,
+ struct interpreter *ip);
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-file.c b/package/network/services/uhttpd/src/uhttpd-file.c
new file mode 100644
index 0000000000..0bafc2b38c
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-file.c
@@ -0,0 +1,438 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Static file handler
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _XOPEN_SOURCE 500 /* strptime() */
+#define _BSD_SOURCE /* scandir(), timegm() */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+#include "uhttpd-file.h"
+
+#include "uhttpd-mimetypes.h"
+
+
+static const char * uh_file_mime_lookup(const char *path)
+{
+ 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)
+{
+ static char tag[128];
+
+ snprintf(tag, sizeof(tag), "\"%x-%x-%x\"",
+ (unsigned int) s->st_ino,
+ (unsigned int) s->st_size,
+ (unsigned int) s->st_mtime);
+
+ return tag;
+}
+
+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)
+{
+ static char str[128];
+ struct tm *t = gmtime(&ts);
+
+ strftime(str, sizeof(str), "%a, %d %b %Y %H:%M:%S GMT", t);
+
+ return str;
+}
+
+static char * uh_file_header_lookup(struct client *cl, const char *name)
+{
+ int i;
+
+ foreach_header(i, cl->request.headers)
+ {
+ if (!strcasecmp(cl->request.headers[i], name))
+ return cl->request.headers[i+1];
+ }
+
+ return NULL;
+}
+
+
+static int uh_file_response_ok_hdrs(struct client *cl, struct stat *s)
+{
+ ensure_ret(uh_http_sendf(cl, NULL, "Connection: close\r\n"));
+
+ if (s)
+ {
+ ensure_ret(uh_http_sendf(cl, NULL, "ETag: %s\r\n", uh_file_mktag(s)));
+ ensure_ret(uh_http_sendf(cl, NULL, "Last-Modified: %s\r\n",
+ uh_file_unix2date(s->st_mtime)));
+ }
+
+ return uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
+}
+
+static int uh_file_response_200(struct client *cl, struct stat *s)
+{
+ ensure_ret(uh_http_sendf(cl, NULL, "%s 200 OK\r\n",
+ http_versions[cl->request.version]));
+
+ return uh_file_response_ok_hdrs(cl, s);
+}
+
+static int uh_file_response_304(struct client *cl, struct stat *s)
+{
+ ensure_ret(uh_http_sendf(cl, NULL, "%s 304 Not Modified\r\n",
+ http_versions[cl->request.version]));
+
+ return uh_file_response_ok_hdrs(cl, s);
+}
+
+static int uh_file_response_412(struct client *cl)
+{
+ return uh_http_sendf(cl, NULL,
+ "%s 412 Precondition Failed\r\n"
+ "Connection: close\r\n",
+ http_versions[cl->request.version]);
+}
+
+static int uh_file_if_match(struct client *cl, struct stat *s, int *ok)
+{
+ const char *tag = uh_file_mktag(s);
+ char *hdr = uh_file_header_lookup(cl, "If-Match");
+ char *p;
+ int i;
+
+ if (hdr)
+ {
+ 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))
+ {
+ *ok = 1;
+ return *ok;
+ }
+ }
+
+ *ok = 0;
+ ensure_ret(uh_file_response_412(cl));
+ return *ok;
+ }
+
+ *ok = 1;
+ return *ok;
+}
+
+static int uh_file_if_modified_since(struct client *cl, struct stat *s, int *ok)
+{
+ char *hdr = uh_file_header_lookup(cl, "If-Modified-Since");
+ *ok = 1;
+
+ if (hdr)
+ {
+ if (uh_file_date2unix(hdr) >= s->st_mtime)
+ {
+ *ok = 0;
+ ensure_ret(uh_file_response_304(cl, s));
+ }
+ }
+
+ return *ok;
+}
+
+static int uh_file_if_none_match(struct client *cl, struct stat *s, int *ok)
+{
+ const char *tag = uh_file_mktag(s);
+ char *hdr = uh_file_header_lookup(cl, "If-None-Match");
+ char *p;
+ int i;
+ *ok = 1;
+
+ if (hdr)
+ {
+ 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))
+ {
+ *ok = 0;
+
+ if ((cl->request.method == UH_HTTP_MSG_GET) ||
+ (cl->request.method == UH_HTTP_MSG_HEAD))
+ {
+ ensure_ret(uh_file_response_304(cl, s));
+ }
+ else
+ {
+ ensure_ret(uh_file_response_412(cl));
+ }
+
+ break;
+ }
+ }
+ }
+
+ return *ok;
+}
+
+static int uh_file_if_range(struct client *cl, struct stat *s, int *ok)
+{
+ char *hdr = uh_file_header_lookup(cl, "If-Range");
+ *ok = 1;
+
+ if (hdr)
+ {
+ *ok = 0;
+ ensure_ret(uh_file_response_412(cl));
+ }
+
+ return *ok;
+}
+
+static int uh_file_if_unmodified_since(struct client *cl, struct stat *s,
+ int *ok)
+{
+ char *hdr = uh_file_header_lookup(cl, "If-Unmodified-Since");
+ *ok = 1;
+
+ if (hdr)
+ {
+ if (uh_file_date2unix(hdr) <= s->st_mtime)
+ {
+ *ok = 0;
+ ensure_ret(uh_file_response_412(cl));
+ }
+ }
+
+ return *ok;
+}
+
+
+static int uh_file_scandir_filter_dir(const struct dirent *e)
+{
+ return strcmp(e->d_name, ".") ? 1 : 0;
+}
+
+static void uh_file_dirlist(struct client *cl, struct path_info *pi)
+{
+ int i;
+ int count = 0;
+ char filename[PATH_MAX];
+ char *pathptr;
+ struct dirent **files = NULL;
+ struct stat s;
+
+ ensure_out(uh_http_sendf(cl, &cl->request,
+ "<html><head><title>Index of %s</title></head>"
+ "<body><h1>Index of %s</h1><hr /><ol>",
+ pi->name, pi->name));
+
+ if ((count = scandir(pi->phys, &files, uh_file_scandir_filter_dir,
+ alphasort)) > 0)
+ {
+ memset(filename, 0, sizeof(filename));
+ memcpy(filename, pi->phys, sizeof(filename));
+ pathptr = &filename[strlen(filename)];
+
+ /* list subdirs */
+ for (i = 0; i < count; i++)
+ {
+ strncat(filename, files[i]->d_name,
+ sizeof(filename) - strlen(files[i]->d_name));
+
+ if (!stat(filename, &s) &&
+ (s.st_mode & S_IFDIR) && (s.st_mode & S_IXOTH))
+ {
+ ensure_out(uh_http_sendf(cl, &cl->request,
+ "<li><strong><a href='%s%s'>%s</a>/"
+ "</strong><br /><small>modified: %s"
+ "<br />directory - %.02f kbyte<br />"
+ "<br /></small></li>",
+ pi->name, files[i]->d_name,
+ files[i]->d_name,
+ uh_file_unix2date(s.st_mtime),
+ s.st_size / 1024.0));
+ }
+
+ *pathptr = 0;
+ }
+
+ /* list files */
+ for (i = 0; i < count; i++)
+ {
+ strncat(filename, files[i]->d_name,
+ sizeof(filename) - strlen(files[i]->d_name));
+
+ if (!stat(filename, &s) &&
+ !(s.st_mode & S_IFDIR) && (s.st_mode & S_IROTH))
+ {
+ ensure_out(uh_http_sendf(cl, &cl->request,
+ "<li><strong><a href='%s%s'>%s</a>"
+ "</strong><br /><small>modified: %s"
+ "<br />%s - %.02f kbyte<br />"
+ "<br /></small></li>",
+ pi->name, files[i]->d_name,
+ files[i]->d_name,
+ uh_file_unix2date(s.st_mtime),
+ uh_file_mime_lookup(filename),
+ s.st_size / 1024.0));
+ }
+
+ *pathptr = 0;
+ }
+ }
+
+ ensure_out(uh_http_sendf(cl, &cl->request, "</ol><hr /></body></html>"));
+ ensure_out(uh_http_sendf(cl, &cl->request, ""));
+
+out:
+ if (files)
+ {
+ for (i = 0; i < count; i++)
+ free(files[i]);
+
+ free(files);
+ }
+}
+
+
+bool uh_file_request(struct client *cl, struct path_info *pi)
+{
+ int rlen;
+ int ok = 1;
+ int fd = -1;
+ char buf[UH_LIMIT_MSGHEAD];
+
+ /* we have a file */
+ if ((pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0))
+ {
+ /* test preconditions */
+ if (ok) ensure_out(uh_file_if_modified_since(cl, &pi->stat, &ok));
+ if (ok) ensure_out(uh_file_if_match(cl, &pi->stat, &ok));
+ if (ok) ensure_out(uh_file_if_range(cl, &pi->stat, &ok));
+ if (ok) ensure_out(uh_file_if_unmodified_since(cl, &pi->stat, &ok));
+ if (ok) ensure_out(uh_file_if_none_match(cl, &pi->stat, &ok));
+
+ if (ok > 0)
+ {
+ /* write status */
+ ensure_out(uh_file_response_200(cl, &pi->stat));
+
+ ensure_out(uh_http_sendf(cl, NULL, "Content-Type: %s\r\n",
+ uh_file_mime_lookup(pi->name)));
+
+ ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n",
+ pi->stat.st_size));
+
+ /* if request was HTTP 1.1 we'll respond chunked */
+ if ((cl->request.version > 1.0) &&
+ (cl->request.method != UH_HTTP_MSG_HEAD))
+ {
+ ensure_out(uh_http_send(cl, NULL,
+ "Transfer-Encoding: chunked\r\n", -1));
+ }
+
+ /* close header */
+ ensure_out(uh_http_send(cl, NULL, "\r\n", -1));
+
+ /* send body */
+ if (cl->request.method != UH_HTTP_MSG_HEAD)
+ {
+ /* pump file data */
+ while ((rlen = read(fd, buf, sizeof(buf))) > 0)
+ ensure_out(uh_http_send(cl, &cl->request, buf, rlen));
+
+ /* send trailer in chunked mode */
+ ensure_out(uh_http_send(cl, &cl->request, "", 0));
+ }
+ }
+
+ /* one of the preconditions failed, terminate opened header and exit */
+ else
+ {
+ ensure_out(uh_http_send(cl, NULL, "\r\n", -1));
+ }
+ }
+
+ /* directory */
+ else if ((pi->stat.st_mode & S_IFDIR) && !cl->server->conf->no_dirlists)
+ {
+ /* write status */
+ ensure_out(uh_file_response_200(cl, NULL));
+
+ if (cl->request.version > 1.0)
+ ensure_out(uh_http_send(cl, NULL,
+ "Transfer-Encoding: chunked\r\n", -1));
+
+ ensure_out(uh_http_send(cl, NULL,
+ "Content-Type: text/html\r\n\r\n", -1));
+
+ /* content */
+ uh_file_dirlist(cl, pi);
+ }
+
+ /* 403 */
+ else
+ {
+ ensure_out(uh_http_sendhf(cl, 403, "Forbidden",
+ "Access to this resource is forbidden"));
+ }
+
+out:
+ if (fd > -1)
+ close(fd);
+
+ return false;
+}
diff --git a/package/network/services/uhttpd/src/uhttpd-file.h b/package/network/services/uhttpd/src/uhttpd-file.h
new file mode 100644
index 0000000000..08dbe2cf9a
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-file.h
@@ -0,0 +1,36 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Static file header
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_FILE_
+
+#include <fcntl.h>
+#include <time.h>
+#include <strings.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <linux/limits.h>
+
+struct mimetype {
+ const char *extn;
+ const char *mime;
+};
+
+bool uh_file_request(struct client *cl, struct path_info *pi);
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-lua.c b/package/network/services/uhttpd/src/uhttpd-lua.c
new file mode 100644
index 0000000000..e113937399
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-lua.c
@@ -0,0 +1,579 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Lua handler
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+#include "uhttpd-lua.h"
+
+
+static int uh_lua_recv(lua_State *L)
+{
+ size_t length;
+
+ char buffer[UH_LIMIT_MSGHEAD];
+
+ int to = 1;
+ int fd = fileno(stdin);
+ int rlen = 0;
+
+ length = luaL_checknumber(L, 1);
+
+ if ((length > 0) && (length <= sizeof(buffer)))
+ {
+ /* receive data */
+ rlen = uh_raw_recv(fd, buffer, length, to);
+
+ /* data read */
+ if (rlen > 0)
+ {
+ lua_pushnumber(L, rlen);
+ lua_pushlstring(L, buffer, rlen);
+ return 2;
+ }
+
+ /* eof */
+ else if (rlen == 0)
+ {
+ lua_pushnumber(L, 0);
+ return 1;
+ }
+
+ /* no, timeout and actually no data */
+ else
+ {
+ lua_pushnumber(L, -1);
+ return 1;
+ }
+ }
+
+ /* parameter error */
+ lua_pushnumber(L, -2);
+ return 1;
+}
+
+static int uh_lua_send_common(lua_State *L, bool chunked)
+{
+ size_t length;
+
+ char chunk[16];
+ const char *buffer;
+
+ int rv;
+ int to = 1;
+ int fd = fileno(stdout);
+ int slen = 0;
+
+ buffer = luaL_checklstring(L, 1, &length);
+
+ if (chunked)
+ {
+ if (length > 0)
+ {
+ snprintf(chunk, sizeof(chunk), "%X\r\n", length);
+
+ ensure_out(rv = uh_raw_send(fd, chunk, strlen(chunk), to));
+ slen += rv;
+
+ ensure_out(rv = uh_raw_send(fd, buffer, length, to));
+ slen += rv;
+
+ ensure_out(rv = uh_raw_send(fd, "\r\n", 2, to));
+ slen += rv;
+ }
+ else
+ {
+ slen = uh_raw_send(fd, "0\r\n\r\n", 5, to);
+ }
+ }
+ else
+ {
+ slen = uh_raw_send(fd, buffer, length, to);
+ }
+
+out:
+ lua_pushnumber(L, slen);
+ return 1;
+}
+
+static int uh_lua_send(lua_State *L)
+{
+ return uh_lua_send_common(L, false);
+}
+
+static int uh_lua_sendc(lua_State *L)
+{
+ return uh_lua_send_common(L, true);
+}
+
+static int uh_lua_str2str(lua_State *L, int (*xlate_func) (char *, int, const char *, int))
+{
+ size_t inlen;
+ int outlen;
+ const char *inbuf;
+ char outbuf[UH_LIMIT_MSGHEAD];
+
+ inbuf = luaL_checklstring(L, 1, &inlen);
+ outlen = (* xlate_func)(outbuf, sizeof(outbuf), inbuf, inlen);
+ if (outlen < 0)
+ luaL_error(L, "%s on URL-encode codec",
+ (outlen==-1) ? "buffer overflow" : "malformed string");
+
+ lua_pushlstring(L, outbuf, outlen);
+ return 1;
+}
+
+static int uh_lua_urldecode(lua_State *L)
+{
+ return uh_lua_str2str( L, uh_urldecode );
+}
+
+
+static int uh_lua_urlencode(lua_State *L)
+{
+ return uh_lua_str2str( L, uh_urlencode );
+}
+
+
+lua_State * uh_lua_init(const struct config *conf)
+{
+ lua_State *L = lua_open();
+ const char *err_str = NULL;
+
+ /* Load standard libaries */
+ luaL_openlibs(L);
+
+ /* build uhttpd api table */
+ lua_newtable(L);
+
+ /* register global send and receive functions */
+ lua_pushcfunction(L, uh_lua_recv);
+ lua_setfield(L, -2, "recv");
+
+ lua_pushcfunction(L, uh_lua_send);
+ lua_setfield(L, -2, "send");
+
+ lua_pushcfunction(L, uh_lua_sendc);
+ lua_setfield(L, -2, "sendc");
+
+ lua_pushcfunction(L, uh_lua_urldecode);
+ lua_setfield(L, -2, "urldecode");
+
+ lua_pushcfunction(L, uh_lua_urlencode);
+ lua_setfield(L, -2, "urlencode");
+
+ /* Pass the document-root to the Lua handler by placing it in
+ ** uhttpd.docroot. It could alternatively be placed in env.DOCUMENT_ROOT
+ ** which would more closely resemble the CGI protocol; but would mean that
+ ** it is not available at the time when the handler-chunk is loaded but
+ ** rather not until the handler is called, without any code savings. */
+ lua_pushstring(L, conf->docroot);
+ lua_setfield(L, -2, "docroot");
+
+ /* _G.uhttpd = { ... } */
+ lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd");
+
+
+ /* load Lua handler */
+ switch (luaL_loadfile(L, conf->lua_handler))
+ {
+ case LUA_ERRSYNTAX:
+ fprintf(stderr,
+ "Lua handler contains syntax errors, unable to continue\n");
+ exit(1);
+
+ case LUA_ERRMEM:
+ fprintf(stderr,
+ "Lua handler ran out of memory, unable to continue\n");
+ exit(1);
+
+ case LUA_ERRFILE:
+ fprintf(stderr,
+ "Lua cannot open the handler script, unable to continue\n");
+ exit(1);
+
+ default:
+ /* compile Lua handler */
+ switch (lua_pcall(L, 0, 0, 0))
+ {
+ case LUA_ERRRUN:
+ err_str = luaL_checkstring(L, -1);
+ fprintf(stderr,
+ "Lua handler had runtime error, "
+ "unable to continue\n"
+ "Error: %s\n", err_str);
+ exit(1);
+
+ case LUA_ERRMEM:
+ err_str = luaL_checkstring(L, -1);
+ fprintf(stderr,
+ "Lua handler ran out of memory, "
+ "unable to continue\n"
+ "Error: %s\n", err_str);
+ exit(1);
+
+ default:
+ /* test handler function */
+ lua_getglobal(L, UH_LUA_CALLBACK);
+
+ if (! lua_isfunction(L, -1))
+ {
+ fprintf(stderr,
+ "Lua handler provides no "UH_LUA_CALLBACK"(), "
+ "unable to continue\n");
+ exit(1);
+ }
+
+ lua_pop(L, 1);
+ break;
+ }
+
+ break;
+ }
+
+ return L;
+}
+
+static void uh_lua_shutdown(struct uh_lua_state *state)
+{
+ free(state);
+}
+
+static bool uh_lua_socket_cb(struct client *cl)
+{
+ int len;
+ char buf[UH_LIMIT_MSGHEAD];
+
+ struct uh_lua_state *state = (struct uh_lua_state *)cl->priv;
+
+ /* there is unread post data waiting */
+ while (state->content_length > 0)
+ {
+ /* remaining data in http head buffer ... */
+ if (cl->httpbuf.len > 0)
+ {
+ len = min(state->content_length, cl->httpbuf.len);
+
+ D("Lua: Child(%d) feed %d HTTP buffer bytes\n", cl->proc.pid, len);
+
+ memcpy(buf, cl->httpbuf.ptr, len);
+
+ cl->httpbuf.len -= len;
+ cl->httpbuf.ptr += len;
+ }
+
+ /* read it from socket ... */
+ else
+ {
+ len = uh_tcp_recv(cl, buf, min(state->content_length, sizeof(buf)));
+
+ if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
+ break;
+
+ D("Lua: Child(%d) feed %d/%d TCP socket bytes\n",
+ cl->proc.pid, len, min(state->content_length, sizeof(buf)));
+ }
+
+ if (len)
+ state->content_length -= len;
+ else
+ state->content_length = 0;
+
+ /* ... write to Lua process */
+ len = uh_raw_send(cl->wpipe.fd, buf, len,
+ cl->server->conf->script_timeout);
+
+ /* explicit EOF notification for the child */
+ if (state->content_length <= 0)
+ uh_ufd_remove(&cl->wpipe);
+ }
+
+ /* try to read data from child */
+ while ((len = uh_raw_recv(cl->rpipe.fd, buf, sizeof(buf), -1)) > 0)
+ {
+ /* pass through buffer to socket */
+ D("Lua: Child(%d) relaying %d normal bytes\n", cl->proc.pid, len);
+ ensure_out(uh_tcp_send(cl, buf, len));
+ state->data_sent = true;
+ }
+
+ /* got EOF or read error from child */
+ if ((len == 0) ||
+ ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1)))
+ {
+ D("Lua: Child(%d) presumed dead [%s]\n",
+ cl->proc.pid, strerror(errno));
+
+ goto out;
+ }
+
+ return true;
+
+out:
+ if (!state->data_sent)
+ {
+ if (cl->timeout.pending)
+ uh_http_sendhf(cl, 502, "Bad Gateway",
+ "The Lua process did not produce any response\n");
+ else
+ uh_http_sendhf(cl, 504, "Gateway Timeout",
+ "The Lua process took too long to produce a "
+ "response\n");
+ }
+
+ uh_lua_shutdown(state);
+ return false;
+}
+
+bool uh_lua_request(struct client *cl, lua_State *L)
+{
+ int i;
+ char *query_string;
+ const char *prefix = cl->server->conf->lua_prefix;
+ const char *err_str = NULL;
+
+ int rfd[2] = { 0, 0 };
+ int wfd[2] = { 0, 0 };
+
+ pid_t child;
+
+ struct uh_lua_state *state;
+ struct http_request *req = &cl->request;
+
+ int content_length = cl->httpbuf.len;
+
+
+ /* allocate state */
+ if (!(state = malloc(sizeof(*state))))
+ {
+ uh_client_error(cl, 500, "Internal Server Error", "Out of memory");
+ return false;
+ }
+
+ /* spawn pipes for me->child, child->me */
+ if ((pipe(rfd) < 0) || (pipe(wfd) < 0))
+ {
+ if (rfd[0] > 0) close(rfd[0]);
+ if (rfd[1] > 0) close(rfd[1]);
+ if (wfd[0] > 0) close(wfd[0]);
+ if (wfd[1] > 0) close(wfd[1]);
+
+ uh_client_error(cl, 500, "Internal Server Error",
+ "Failed to create pipe: %s", strerror(errno));
+
+ return false;
+ }
+
+
+ switch ((child = fork()))
+ {
+ case -1:
+ uh_client_error(cl, 500, "Internal Server Error",
+ "Failed to fork child: %s", strerror(errno));
+
+ return false;
+
+ case 0:
+#ifdef DEBUG
+ sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0"));
+#endif
+
+ /* do not leak parent epoll descriptor */
+ uloop_done();
+
+ /* close loose pipe ends */
+ close(rfd[0]);
+ close(wfd[1]);
+
+ /* patch stdout and stdin to pipes */
+ dup2(rfd[1], 1);
+ dup2(wfd[0], 0);
+
+ /* avoid leaking our pipe into child-child processes */
+ fd_cloexec(rfd[1]);
+ fd_cloexec(wfd[0]);
+
+ /* put handler callback on stack */
+ lua_getglobal(L, UH_LUA_CALLBACK);
+
+ /* build env table */
+ lua_newtable(L);
+
+ /* request method */
+ lua_pushstring(L, http_methods[req->method]);
+ lua_setfield(L, -2, "REQUEST_METHOD");
+
+ /* request url */
+ lua_pushstring(L, req->url);
+ lua_setfield(L, -2, "REQUEST_URI");
+
+ /* script name */
+ lua_pushstring(L, cl->server->conf->lua_prefix);
+ lua_setfield(L, -2, "SCRIPT_NAME");
+
+ /* query string, path info */
+ if ((query_string = strchr(req->url, '?')) != NULL)
+ {
+ lua_pushstring(L, query_string + 1);
+ lua_setfield(L, -2, "QUERY_STRING");
+
+ if ((int)(query_string - req->url) > strlen(prefix))
+ {
+ lua_pushlstring(L,
+ &req->url[strlen(prefix)],
+ (int)(query_string - req->url) - strlen(prefix)
+ );
+
+ lua_setfield(L, -2, "PATH_INFO");
+ }
+ }
+ else if (strlen(req->url) > strlen(prefix))
+ {
+ lua_pushstring(L, &req->url[strlen(prefix)]);
+ lua_setfield(L, -2, "PATH_INFO");
+ }
+
+ /* http protcol version */
+ lua_pushnumber(L, 0.9 + (req->version / 10.0));
+ lua_setfield(L, -2, "HTTP_VERSION");
+
+ lua_pushstring(L, http_versions[req->version]);
+ lua_setfield(L, -2, "SERVER_PROTOCOL");
+
+
+ /* address information */
+ lua_pushstring(L, sa_straddr(&cl->peeraddr));
+ lua_setfield(L, -2, "REMOTE_ADDR");
+
+ lua_pushinteger(L, sa_port(&cl->peeraddr));
+ lua_setfield(L, -2, "REMOTE_PORT");
+
+ lua_pushstring(L, sa_straddr(&cl->servaddr));
+ lua_setfield(L, -2, "SERVER_ADDR");
+
+ lua_pushinteger(L, sa_port(&cl->servaddr));
+ lua_setfield(L, -2, "SERVER_PORT");
+
+ /* essential env vars */
+ foreach_header(i, req->headers)
+ {
+ if (!strcasecmp(req->headers[i], "Content-Length"))
+ {
+ content_length = atoi(req->headers[i+1]);
+ }
+ else if (!strcasecmp(req->headers[i], "Content-Type"))
+ {
+ lua_pushstring(L, req->headers[i+1]);
+ lua_setfield(L, -2, "CONTENT_TYPE");
+ }
+ }
+
+ lua_pushnumber(L, content_length);
+ lua_setfield(L, -2, "CONTENT_LENGTH");
+
+ /* misc. headers */
+ lua_newtable(L);
+
+ foreach_header(i, req->headers)
+ {
+ if( strcasecmp(req->headers[i], "Content-Length") &&
+ strcasecmp(req->headers[i], "Content-Type"))
+ {
+ lua_pushstring(L, req->headers[i+1]);
+ lua_setfield(L, -2, req->headers[i]);
+ }
+ }
+
+ lua_setfield(L, -2, "headers");
+
+
+ /* call */
+ switch (lua_pcall(L, 1, 0, 0))
+ {
+ case LUA_ERRMEM:
+ case LUA_ERRRUN:
+ err_str = luaL_checkstring(L, -1);
+
+ if (! err_str)
+ err_str = "Unknown error";
+
+ printf("%s 500 Internal Server Error\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %i\r\n\r\n"
+ "Lua raised a runtime error:\n %s\n",
+ http_versions[req->version],
+ 31 + strlen(err_str), err_str);
+
+ break;
+
+ default:
+ break;
+ }
+
+ close(wfd[0]);
+ close(rfd[1]);
+ exit(0);
+
+ break;
+
+ /* parent; handle I/O relaying */
+ default:
+ memset(state, 0, sizeof(*state));
+
+ cl->rpipe.fd = rfd[0];
+ cl->wpipe.fd = wfd[1];
+ cl->proc.pid = child;
+
+ /* make pipe non-blocking */
+ fd_nonblock(cl->rpipe.fd);
+ fd_nonblock(cl->wpipe.fd);
+
+ /* close unneeded pipe ends */
+ close(rfd[1]);
+ close(wfd[0]);
+
+ D("Lua: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]);
+
+ state->content_length = cl->httpbuf.len;
+
+ /* find content length */
+ if (req->method == UH_HTTP_MSG_POST)
+ {
+ foreach_header(i, req->headers)
+ {
+ if (!strcasecmp(req->headers[i], "Content-Length"))
+ {
+ state->content_length = atoi(req->headers[i+1]);
+ break;
+ }
+ }
+ }
+
+ cl->cb = uh_lua_socket_cb;
+ cl->priv = state;
+
+ break;
+ }
+
+ return true;
+}
+
+void uh_lua_close(lua_State *L)
+{
+ lua_close(L);
+}
diff --git a/package/network/services/uhttpd/src/uhttpd-lua.h b/package/network/services/uhttpd/src/uhttpd-lua.h
new file mode 100644
index 0000000000..780d845d87
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-lua.h
@@ -0,0 +1,44 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Lua header
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_LUA_
+
+#include <math.h> /* floor() */
+#include <errno.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#define UH_LUA_CALLBACK "handle_request"
+
+#define UH_LUA_ERR_TIMEOUT -1
+#define UH_LUA_ERR_TOOBIG -2
+#define UH_LUA_ERR_PARAM -3
+
+
+struct uh_lua_state {
+ int content_length;
+ bool data_sent;
+};
+
+lua_State * uh_lua_init(const struct config *conf);
+bool uh_lua_request(struct client *cl, lua_State *L);
+void uh_lua_close(lua_State *L);
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-mimetypes.h b/package/network/services/uhttpd/src/uhttpd-mimetypes.h
new file mode 100644
index 0000000000..21717c0003
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-mimetypes.h
@@ -0,0 +1,86 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - MIME type definitions
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_MIMETYPES_
+
+static 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" },
+
+ { "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/package/network/services/uhttpd/src/uhttpd-tls.c b/package/network/services/uhttpd/src/uhttpd-tls.c
new file mode 100644
index 0000000000..9c6eb81db3
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-tls.c
@@ -0,0 +1,170 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - TLS helper
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uhttpd.h"
+#include "uhttpd-tls.h"
+#include "uhttpd-utils.h"
+
+#include <syslog.h>
+#define dbg(...) syslog(LOG_INFO, __VA_ARGS__)
+
+SSL_CTX * uh_tls_ctx_init(void)
+{
+ SSL_CTX *c;
+
+ SSL_load_error_strings();
+ SSL_library_init();
+
+#if TLS_IS_OPENSSL
+ if ((c = SSL_CTX_new(SSLv23_server_method())) != NULL)
+#else
+ if ((c = SSL_CTX_new(TLSv1_server_method())) != NULL)
+#endif
+ SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL);
+
+ return c;
+}
+
+int uh_tls_ctx_cert(SSL_CTX *c, const char *file)
+{
+ int rv;
+
+ if( (rv = SSL_CTX_use_certificate_file(c, file, SSL_FILETYPE_PEM)) < 1 )
+ rv = SSL_CTX_use_certificate_file(c, file, SSL_FILETYPE_ASN1);
+
+ return rv;
+}
+
+int uh_tls_ctx_key(SSL_CTX *c, const char *file)
+{
+ int rv;
+
+ if( (rv = SSL_CTX_use_PrivateKey_file(c, file, SSL_FILETYPE_PEM)) < 1 )
+ rv = SSL_CTX_use_PrivateKey_file(c, file, SSL_FILETYPE_ASN1);
+
+ return rv;
+}
+
+void uh_tls_ctx_free(struct listener *l)
+{
+ SSL_CTX_free(l->tls);
+}
+
+
+int uh_tls_client_accept(struct client *c)
+{
+ int rv, err;
+ int fd = c->fd.fd;
+
+ if (!c->server || !c->server->tls)
+ {
+ c->tls = NULL;
+ return 1;
+ }
+
+ if ((c->tls = SSL_new(c->server->tls)))
+ {
+ if ((rv = SSL_set_fd(c->tls, fd)) < 1)
+ {
+ SSL_free(c->tls);
+ c->tls = NULL;
+ }
+ else
+ {
+ while (true)
+ {
+ rv = SSL_accept(c->tls);
+ err = SSL_get_error(c->tls, rv);
+
+ if ((rv != 1) &&
+ (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE))
+ {
+ if (uh_socket_wait(fd, c->server->conf->network_timeout,
+ (err == SSL_ERROR_WANT_WRITE)))
+ {
+ D("TLS: accept(%d) = retry\n", fd);
+ continue;
+ }
+
+ D("TLS: accept(%d) = timeout\n", fd);
+ }
+ else if (rv == 1)
+ {
+ D("TLS: accept(%d) = %p\n", fd, c->tls);
+ return 1;
+ }
+
+#ifdef TLS_IS_OPENSSL
+ D("TLS: accept(%d) = failed: %s\n",
+ fd, ERR_error_string(ERR_get_error(), NULL));
+#endif
+
+ SSL_free(c->tls);
+ c->tls = NULL;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int uh_tls_client_recv(struct client *c, char *buf, int len)
+{
+ int rv = SSL_read(c->tls, buf, len);
+ int err = SSL_get_error(c->tls, 0);
+
+ if ((rv == -1) && (err == SSL_ERROR_WANT_READ))
+ {
+ D("TLS: recv(%d, %d) = retry\n", c->fd.fd, len);
+ errno = EAGAIN;
+ return -1;
+ }
+
+ D("TLS: recv(%d, %d) = %d\n", c->fd.fd, len, rv);
+ return rv;
+}
+
+int uh_tls_client_send(struct client *c, const char *buf, int len)
+{
+ int rv = SSL_write(c->tls, buf, len);
+ int err = SSL_get_error(c->tls, 0);
+
+ if ((rv == -1) && (err == SSL_ERROR_WANT_WRITE))
+ {
+ D("TLS: send(%d, %d) = retry\n", c->fd.fd, len);
+ errno = EAGAIN;
+ return -1;
+ }
+
+ D("TLS: send(%d, %d) = %d\n", c->fd.fd, len, rv);
+ return rv;
+}
+
+void uh_tls_client_close(struct client *c)
+{
+ if (c->tls)
+ {
+ D("TLS: close(%d)\n", c->fd.fd);
+
+ SSL_shutdown(c->tls);
+ SSL_free(c->tls);
+
+ c->tls = NULL;
+ }
+}
diff --git a/package/network/services/uhttpd/src/uhttpd-tls.h b/package/network/services/uhttpd/src/uhttpd-tls.h
new file mode 100644
index 0000000000..8644c2ac5d
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-tls.h
@@ -0,0 +1,36 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - TLS header
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_TLS_
+
+#include <openssl/ssl.h>
+#ifdef TLS_IS_OPENSSL
+#include <openssl/err.h>
+#endif
+
+SSL_CTX * uh_tls_ctx_init();
+int uh_tls_ctx_cert(SSL_CTX *c, const char *file);
+int uh_tls_ctx_key(SSL_CTX *c, const char *file);
+void uh_tls_ctx_free(struct listener *l);
+
+int uh_tls_client_accept(struct client *c);
+int uh_tls_client_recv(struct client *c, char *buf, int len);
+int uh_tls_client_send(struct client *c, const char *buf, int len);
+void uh_tls_client_close(struct client *c);
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-ubus.c b/package/network/services/uhttpd/src/uhttpd-ubus.c
new file mode 100644
index 0000000000..20781629ba
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-ubus.c
@@ -0,0 +1,957 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - ubus handler
+ *
+ * Copyright (C) 2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+#include "uhttpd-ubus.h"
+
+
+enum {
+ UH_UBUS_SN_TIMEOUT,
+ __UH_UBUS_SN_MAX,
+};
+
+static const struct blobmsg_policy new_policy[__UH_UBUS_SN_MAX] = {
+ [UH_UBUS_SN_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 },
+};
+
+
+enum {
+ UH_UBUS_SI_SID,
+ __UH_UBUS_SI_MAX,
+};
+
+static const struct blobmsg_policy sid_policy[__UH_UBUS_SI_MAX] = {
+ [UH_UBUS_SI_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+enum {
+ UH_UBUS_SS_SID,
+ UH_UBUS_SS_VALUES,
+ __UH_UBUS_SS_MAX,
+};
+
+static const struct blobmsg_policy set_policy[__UH_UBUS_SS_MAX] = {
+ [UH_UBUS_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+ [UH_UBUS_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE },
+};
+
+
+enum {
+ UH_UBUS_SG_SID,
+ UH_UBUS_SG_KEYS,
+ __UH_UBUS_SG_MAX,
+};
+
+static const struct blobmsg_policy get_policy[__UH_UBUS_SG_MAX] = {
+ [UH_UBUS_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+ [UH_UBUS_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+
+enum {
+ UH_UBUS_SA_SID,
+ UH_UBUS_SA_OBJECTS,
+ __UH_UBUS_SA_MAX,
+};
+
+static const struct blobmsg_policy acl_policy[__UH_UBUS_SA_MAX] = {
+ [UH_UBUS_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+ [UH_UBUS_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+
+static bool
+uh_ubus_strmatch(const char *str, const char *pat)
+{
+ while (*pat)
+ {
+ if (*pat == '?')
+ {
+ if (!*str)
+ return false;
+
+ str++;
+ pat++;
+ }
+ else if (*pat == '*')
+ {
+ if (uh_ubus_strmatch(str, pat+1))
+ return true;
+
+ if (*str && uh_ubus_strmatch(str+1, pat))
+ return true;
+
+ return false;
+ }
+ else if (*str++ != *pat++)
+ {
+ return false;
+ }
+ }
+
+ return (!*str && !*pat);
+}
+
+static int
+uh_ubus_avlcmp(const void *k1, const void *k2, void *ptr)
+{
+ return strcmp((char *)k1, (char *)k2);
+}
+
+static void
+uh_ubus_random(char *dest)
+{
+ int i;
+ unsigned char buf[16] = { 0 };
+ FILE *f;
+
+ if ((f = fopen("/dev/urandom", "r")) != NULL)
+ {
+ fread(buf, 1, sizeof(buf), f);
+ fclose(f);
+ }
+
+ for (i = 0; i < sizeof(buf); i++)
+ sprintf(dest + (i<<1), "%02x", buf[i]);
+}
+
+static void
+uh_ubus_session_dump_data(struct uh_ubus_session *ses, struct blob_buf *b)
+{
+ struct uh_ubus_session_data *d;
+
+ avl_for_each_element(&ses->data, d, avl)
+ {
+ blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr),
+ blobmsg_data(d->attr), blobmsg_data_len(d->attr));
+ }
+}
+
+static void
+uh_ubus_session_dump_acls(struct uh_ubus_session *ses, struct blob_buf *b)
+{
+ struct uh_ubus_session_acl *acl;
+ const char *lastobj = NULL;
+ void *c = NULL;
+
+ avl_for_each_element(&ses->acls, acl, avl)
+ {
+ if (!lastobj || strcmp(acl->object, lastobj))
+ {
+ if (c) blobmsg_close_array(b, c);
+ c = blobmsg_open_array(b, acl->object);
+ }
+
+ blobmsg_add_string(b, NULL, acl->function);
+ lastobj = acl->object;
+ }
+
+ if (c) blobmsg_close_array(b, c);
+}
+
+static void
+uh_ubus_session_dump(struct uh_ubus_session *ses,
+ struct ubus_context *ctx,
+ struct ubus_request_data *req)
+{
+ void *c;
+ struct blob_buf b;
+
+ memset(&b, 0, sizeof(b));
+ blob_buf_init(&b, 0);
+
+ blobmsg_add_string(&b, "sid", ses->id);
+ blobmsg_add_u32(&b, "timeout", ses->timeout);
+ blobmsg_add_u32(&b, "touched", ses->touched.tv_sec);
+
+ c = blobmsg_open_table(&b, "acls");
+ uh_ubus_session_dump_acls(ses, &b);
+ blobmsg_close_table(&b, c);
+
+ c = blobmsg_open_table(&b, "data");
+ uh_ubus_session_dump_data(ses, &b);
+ blobmsg_close_table(&b, c);
+
+ ubus_send_reply(ctx, req, b.head);
+ blob_buf_free(&b);
+}
+
+static struct uh_ubus_session *
+uh_ubus_session_create(struct uh_ubus_state *state, int timeout)
+{
+ struct uh_ubus_session *ses;
+
+ ses = malloc(sizeof(*ses));
+
+ /* failed to allocate memory... */
+ if (!ses)
+ return NULL;
+
+ memset(ses, 0, sizeof(*ses));
+
+ uh_ubus_random(ses->id);
+
+ ses->timeout = timeout;
+ ses->avl.key = ses->id;
+
+ avl_insert(&state->sessions, &ses->avl);
+ avl_init(&ses->acls, uh_ubus_avlcmp, true, NULL);
+ avl_init(&ses->data, uh_ubus_avlcmp, false, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &ses->touched);
+
+ return ses;
+}
+
+
+static struct uh_ubus_session *
+uh_ubus_session_get(struct uh_ubus_state *state, const char *id)
+{
+ struct uh_ubus_session *ses;
+
+ ses = avl_find_element(&state->sessions, id, ses, avl);
+
+ if (ses)
+ clock_gettime(CLOCK_MONOTONIC, &ses->touched);
+
+ return ses;
+}
+
+static void
+uh_ubus_session_destroy(struct uh_ubus_state *state,
+ struct uh_ubus_session *ses)
+{
+ struct uh_ubus_session_acl *acl, *nacl;
+ struct uh_ubus_session_data *data, *ndata;
+
+ avl_remove_all_elements(&ses->acls, acl, avl, nacl)
+ free(acl);
+
+ avl_remove_all_elements(&ses->data, data, avl, ndata)
+ free(data);
+
+ avl_delete(&state->sessions, &ses->avl);
+ free(ses);
+}
+
+static void
+uh_ubus_session_cleanup(struct uh_ubus_state *state)
+{
+ struct timespec now;
+ struct uh_ubus_session *ses, *nses;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ avl_for_each_element_safe(&state->sessions, ses, avl, nses)
+ {
+ if ((now.tv_sec - ses->touched.tv_sec) >= ses->timeout)
+ uh_ubus_session_destroy(state, ses);
+ }
+}
+
+
+static int
+uh_ubus_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct blob_attr *tb[__UH_UBUS_SN_MAX];
+
+ int timeout = state->timeout;
+
+ blobmsg_parse(new_policy, __UH_UBUS_SN_MAX, tb, blob_data(msg), blob_len(msg));
+
+ /* TODO: make this a uloop timeout */
+ uh_ubus_session_cleanup(state);
+
+ if (tb[UH_UBUS_SN_TIMEOUT])
+ timeout = *(uint32_t *)blobmsg_data(tb[UH_UBUS_SN_TIMEOUT]);
+
+ ses = uh_ubus_session_create(state, timeout);
+
+ if (ses)
+ uh_ubus_session_dump(ses, ctx, req);
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct blob_attr *tb[__UH_UBUS_SI_MAX];
+
+ blobmsg_parse(sid_policy, __UH_UBUS_SI_MAX, tb, blob_data(msg), blob_len(msg));
+
+ /* TODO: make this a uloop timeout */
+ uh_ubus_session_cleanup(state);
+
+ if (!tb[UH_UBUS_SI_SID])
+ {
+ avl_for_each_element(&state->sessions, ses, avl)
+ uh_ubus_session_dump(ses, ctx, req);
+ }
+ else
+ {
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SI_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ uh_ubus_session_dump(ses, ctx, req);
+ }
+
+ return 0;
+}
+
+
+static int
+uh_ubus_session_grant(struct uh_ubus_session *ses, struct ubus_context *ctx,
+ const char *object, const char *function)
+{
+ struct uh_ubus_session_acl *acl, *nacl;
+
+ acl = avl_find_element(&ses->acls, object, acl, avl);
+
+ if (acl)
+ {
+ avl_for_element_to_last(&ses->acls, acl, acl, avl)
+ {
+ if (!strcmp(acl->function, function))
+ return 1;
+ }
+ }
+
+ nacl = malloc(sizeof(*nacl) + strlen(object) + strlen(function) + 2);
+
+ if (nacl)
+ {
+ memset(nacl, 0, sizeof(*nacl));
+ nacl->function = nacl->object + 1;
+ nacl->function += sprintf(nacl->object, "%s", object);
+ sprintf(nacl->function, "%s", function);
+
+ nacl->avl.key = nacl->object;
+ avl_insert(&ses->acls, &nacl->avl);
+ }
+
+ return 0;
+}
+
+static int
+uh_ubus_session_revoke(struct uh_ubus_session *ses, struct ubus_context *ctx,
+ const char *object, const char *function)
+{
+ struct uh_ubus_session_acl *acl, *nacl;
+
+ if (!object && !function)
+ {
+ avl_remove_all_elements(&ses->acls, acl, avl, nacl)
+ free(acl);
+ }
+ else
+ {
+ avl_for_each_element_safe(&ses->acls, acl, avl, nacl)
+ {
+ if (uh_ubus_strmatch(acl->object, object) &&
+ uh_ubus_strmatch(acl->function, function))
+ {
+ avl_delete(&ses->acls, &acl->avl);
+ free(acl);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+uh_ubus_handle_grant(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+ struct blob_attr *attr, *sattr;
+ const char *object, *function;
+ int rem1, rem2;
+
+ blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SA_SID] || !tb[UH_UBUS_SA_OBJECTS])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SA_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1)
+ {
+ if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
+ continue;
+
+ object = NULL;
+ function = NULL;
+
+ blobmsg_for_each_attr(sattr, attr, rem2)
+ {
+ if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ if (!object)
+ object = blobmsg_data(sattr);
+ else if (!function)
+ function = blobmsg_data(sattr);
+ else
+ break;
+ }
+
+ if (object && function)
+ uh_ubus_session_grant(ses, ctx, object, function);
+ }
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_revoke(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+ struct blob_attr *attr, *sattr;
+ const char *object, *function;
+ int rem1, rem2;
+
+ blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SA_SID])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SA_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ if (!tb[UH_UBUS_SA_OBJECTS])
+ {
+ uh_ubus_session_revoke(ses, ctx, NULL, NULL);
+ }
+ else
+ {
+ blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1)
+ {
+ if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
+ continue;
+
+ object = NULL;
+ function = NULL;
+
+ blobmsg_for_each_attr(sattr, attr, rem2)
+ {
+ if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ if (!object)
+ object = blobmsg_data(sattr);
+ else if (!function)
+ function = blobmsg_data(sattr);
+ else
+ break;
+ }
+
+ if (object && function)
+ uh_ubus_session_revoke(ses, ctx, object, function);
+ }
+ }
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct uh_ubus_session_data *data;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+ struct blob_attr *attr;
+ int rem;
+
+ blobmsg_parse(set_policy, __UH_UBUS_SS_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SS_SID] || !tb[UH_UBUS_SS_VALUES])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SS_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ blobmsg_for_each_attr(attr, tb[UH_UBUS_SS_VALUES], rem)
+ {
+ if (!blobmsg_name(attr)[0])
+ continue;
+
+ data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
+
+ if (data)
+ {
+ avl_delete(&ses->data, &data->avl);
+ free(data);
+ }
+
+ data = malloc(sizeof(*data) + blob_pad_len(attr));
+
+ if (!data)
+ break;
+
+ memset(data, 0, sizeof(*data) + blob_pad_len(attr));
+ memcpy(data->attr, attr, blob_pad_len(attr));
+
+ data->avl.key = blobmsg_name(data->attr);
+ avl_insert(&ses->data, &data->avl);
+ }
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct uh_ubus_session_data *data;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+ struct blob_attr *attr;
+ struct blob_buf b;
+ void *c;
+ int rem;
+
+ blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SG_SID])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SG_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ memset(&b, 0, sizeof(b));
+ blob_buf_init(&b, 0);
+ c = blobmsg_open_table(&b, "values");
+
+ if (!tb[UH_UBUS_SG_KEYS])
+ {
+ uh_ubus_session_dump_data(ses, &b);
+ }
+ else
+ {
+ blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem)
+ {
+ if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
+
+ if (!data)
+ continue;
+
+ blobmsg_add_field(&b, blobmsg_type(data->attr),
+ blobmsg_name(data->attr),
+ blobmsg_data(data->attr),
+ blobmsg_data_len(data->attr));
+ }
+ }
+
+ blobmsg_close_table(&b, c);
+ ubus_send_reply(ctx, req, b.head);
+ blob_buf_free(&b);
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct uh_ubus_session_data *data, *ndata;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+ struct blob_attr *attr;
+ int rem;
+
+ blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SG_SID])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SG_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ if (!tb[UH_UBUS_SG_KEYS])
+ {
+ avl_remove_all_elements(&ses->data, data, avl, ndata)
+ free(data);
+ }
+ else
+ {
+ blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem)
+ {
+ if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
+
+ if (!data)
+ continue;
+
+ avl_delete(&ses->data, &data->avl);
+ free(data);
+ }
+ }
+
+ return 0;
+}
+
+static int
+uh_ubus_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus);
+ struct uh_ubus_session *ses;
+ struct blob_attr *tb[__UH_UBUS_SA_MAX];
+
+ blobmsg_parse(sid_policy, __UH_UBUS_SI_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UH_UBUS_SI_SID])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SI_SID]));
+
+ if (!ses)
+ return UBUS_STATUS_NOT_FOUND;
+
+ uh_ubus_session_destroy(state, ses);
+
+ return 0;
+}
+
+
+struct uh_ubus_state *
+uh_ubus_init(const struct config *conf)
+{
+ int rv;
+ struct uh_ubus_state *state;
+ struct ubus_object *session_object;
+
+ static struct ubus_method session_methods[] = {
+ UBUS_METHOD("create", uh_ubus_handle_create, new_policy),
+ UBUS_METHOD("list", uh_ubus_handle_list, sid_policy),
+ UBUS_METHOD("grant", uh_ubus_handle_grant, acl_policy),
+ UBUS_METHOD("revoke", uh_ubus_handle_revoke, acl_policy),
+ UBUS_METHOD("set", uh_ubus_handle_set, set_policy),
+ UBUS_METHOD("get", uh_ubus_handle_get, get_policy),
+ UBUS_METHOD("unset", uh_ubus_handle_unset, get_policy),
+ UBUS_METHOD("destroy", uh_ubus_handle_destroy, sid_policy),
+ };
+
+ static struct ubus_object_type session_type =
+ UBUS_OBJECT_TYPE("uhttpd", session_methods);
+
+ state = malloc(sizeof(*state));
+
+ if (!state)
+ {
+ fprintf(stderr, "Unable to allocate memory for ubus state\n");
+ exit(1);
+ }
+
+ memset(state, 0, sizeof(*state));
+ state->ctx = ubus_connect(conf->ubus_socket);
+ state->timeout = conf->script_timeout;
+
+ if (!state->ctx)
+ {
+ fprintf(stderr, "Unable to connect to ubus socket\n");
+ exit(1);
+ }
+
+ ubus_add_uloop(state->ctx);
+
+ session_object = &state->ubus;
+ session_object->name = "session";
+ session_object->type = &session_type;
+ session_object->methods = session_methods;
+ session_object->n_methods = ARRAY_SIZE(session_methods);
+
+ rv = ubus_add_object(state->ctx, &state->ubus);
+
+ if (rv)
+ {
+ fprintf(stderr, "Unable to publish ubus object: %s\n",
+ ubus_strerror(rv));
+ exit(1);
+ }
+
+ blob_buf_init(&state->buf, 0);
+ avl_init(&state->sessions, uh_ubus_avlcmp, false, NULL);
+
+ return state;
+}
+
+
+static bool
+uh_ubus_request_parse_url(struct client *cl, char **sid, char **obj, char **fun)
+{
+ char *url = cl->request.url + strlen(cl->server->conf->ubus_prefix);
+
+ for (; url && *url == '/'; *url++ = 0);
+ *sid = url;
+
+ for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0);
+ *obj = url;
+
+ for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0);
+ *fun = url;
+
+ for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0);
+ return (*sid && *obj && *fun);
+}
+
+static bool
+uh_ubus_request_parse_post(struct client *cl, int len, struct blob_buf *b)
+{
+ int rlen;
+ bool rv = false;
+ char buf[UH_LIMIT_MSGHEAD];
+
+ struct json_object *obj = NULL;
+ struct json_tokener *tok = NULL;
+
+ if (!len)
+ return NULL;
+
+ memset(b, 0, sizeof(*b));
+ blob_buf_init(b, 0);
+
+ tok = json_tokener_new();
+
+ while (len > 0)
+ {
+ /* remaining data in http head buffer ... */
+ if (cl->httpbuf.len > 0)
+ {
+ rlen = min(len, cl->httpbuf.len);
+
+ D("ubus: feed %d HTTP buffer bytes\n", rlen);
+
+ memcpy(buf, cl->httpbuf.ptr, rlen);
+
+ cl->httpbuf.len -= rlen;
+ cl->httpbuf.ptr += rlen;
+ }
+
+ /* read it from socket ... */
+ else
+ {
+ ensure_out(rlen = uh_tcp_recv(cl, buf, min(len, sizeof(buf))));
+
+ if ((rlen < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
+ break;
+
+ D("ubus: feed %d/%d TCP socket bytes\n",
+ rlen, min(len, sizeof(buf)));
+ }
+
+ obj = json_tokener_parse_ex(tok, buf, rlen);
+ len -= rlen;
+
+ if (tok->err != json_tokener_continue && !is_error(obj))
+ break;
+ }
+
+out:
+ if (!is_error(obj))
+ {
+ if (json_object_get_type(obj) == json_type_object)
+ {
+ rv = true;
+ json_object_object_foreach(obj, key, val)
+ {
+ if (!blobmsg_add_json_element(b, key, val))
+ {
+ rv = false;
+ break;
+ }
+ }
+ }
+
+ json_object_put(obj);
+ }
+
+ json_tokener_free(tok);
+
+ if (!rv)
+ blob_buf_free(b);
+
+ return rv;
+}
+
+static void
+uh_ubus_request_cb(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+ int len;
+ char *str;
+ struct client *cl = (struct client *)req->priv;
+
+ if (!msg)
+ {
+ uh_http_sendhf(cl, 204, "No content", "Function did not return data\n");
+ return;
+ }
+
+ str = blobmsg_format_json_indent(msg, true, 0);
+ len = strlen(str);
+
+ ensure_out(uh_http_sendf(cl, NULL, "HTTP/1.0 200 OK\r\n"));
+ ensure_out(uh_http_sendf(cl, NULL, "Content-Type: application/json\r\n"));
+ ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n\r\n", len));
+ ensure_out(uh_http_send(cl, NULL, str, len));
+
+out:
+ free(str);
+}
+
+bool
+uh_ubus_request(struct client *cl, struct uh_ubus_state *state)
+{
+ int i, len = 0;
+ bool access = false;
+ char *sid, *obj, *fun;
+
+ struct blob_buf buf;
+ struct uh_ubus_session *ses;
+ struct uh_ubus_session_acl *acl;
+
+ uint32_t obj_id;
+
+
+ memset(&buf, 0, sizeof(buf));
+ blob_buf_init(&buf, 0);
+
+ if (!uh_ubus_request_parse_url(cl, &sid, &obj, &fun))
+ {
+ uh_http_sendhf(cl, 400, "Bad Request", "Invalid Request\n");
+ goto out;
+ }
+
+ if (!(ses = uh_ubus_session_get(state, sid)))
+ {
+ uh_http_sendhf(cl, 404, "Not Found", "No such session\n");
+ goto out;
+ }
+
+ avl_for_each_element(&ses->acls, acl, avl)
+ {
+ if (uh_ubus_strmatch(obj, acl->object) &&
+ uh_ubus_strmatch(fun, acl->function))
+ {
+ access = true;
+ break;
+ }
+ }
+
+ if (!access)
+ {
+ uh_http_sendhf(cl, 403, "Denied", "Access to object denied\n");
+ goto out;
+ }
+
+ /* find content length */
+ if (cl->request.method == UH_HTTP_MSG_POST)
+ {
+ foreach_header(i, cl->request.headers)
+ {
+ if (!strcasecmp(cl->request.headers[i], "Content-Length"))
+ {
+ len = atoi(cl->request.headers[i+1]);
+ break;
+ }
+ }
+ }
+
+ if (len > UH_UBUS_MAX_POST_SIZE)
+ {
+ uh_http_sendhf(cl, 413, "Too Large", "Message too big\n");
+ goto out;
+ }
+
+ if (len && !uh_ubus_request_parse_post(cl, len, &buf))
+ {
+ uh_http_sendhf(cl, 400, "Bad Request", "Invalid JSON data\n");
+ goto out;
+ }
+
+ if (ubus_lookup_id(state->ctx, obj, &obj_id))
+ {
+ uh_http_sendhf(cl, 500, "Internal Error", "Unable to lookup object\n");
+ goto out;
+ }
+
+ if (ubus_invoke(state->ctx, obj_id, fun, buf.head,
+ uh_ubus_request_cb, cl, state->timeout * 1000))
+ {
+ uh_http_sendhf(cl, 500, "Internal Error", "Unable to invoke function\n");
+ goto out;
+ }
+
+out:
+ blob_buf_free(&buf);
+ return false;
+}
+
+void
+uh_ubus_close(struct uh_ubus_state *state)
+{
+ if (state->ctx)
+ ubus_free(state->ctx);
+
+ free(state);
+}
diff --git a/package/network/services/uhttpd/src/uhttpd-ubus.h b/package/network/services/uhttpd/src/uhttpd-ubus.h
new file mode 100644
index 0000000000..777ce27fd3
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-ubus.h
@@ -0,0 +1,70 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - ubus header
+ *
+ * Copyright (C) 2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_UBUS_
+
+#include <time.h>
+
+#include <libubus.h>
+#include <libubox/avl.h>
+#include <libubox/blobmsg_json.h>
+#include <json/json.h>
+
+
+#define UH_UBUS_MAX_POST_SIZE 4096
+
+
+struct uh_ubus_state {
+ struct ubus_context *ctx;
+ struct ubus_object ubus;
+ struct blob_buf buf;
+ struct avl_tree sessions;
+ int timeout;
+};
+
+struct uh_ubus_request_data {
+ const char *sid;
+ const char *object;
+ const char *function;
+};
+
+struct uh_ubus_session {
+ char id[33];
+ int timeout;
+ struct avl_node avl;
+ struct avl_tree data;
+ struct avl_tree acls;
+ struct timespec touched;
+};
+
+struct uh_ubus_session_data {
+ struct avl_node avl;
+ struct blob_attr attr[];
+};
+
+struct uh_ubus_session_acl {
+ struct avl_node avl;
+ char *function;
+ char object[];
+};
+
+struct uh_ubus_state * uh_ubus_init(const struct config *conf);
+bool uh_ubus_request(struct client *cl, struct uh_ubus_state *state);
+void uh_ubus_close(struct uh_ubus_state *state);
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-utils.c b/package/network/services/uhttpd/src/uhttpd-utils.c
new file mode 100644
index 0000000000..c8d3bb40f4
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-utils.c
@@ -0,0 +1,1081 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Utility functions
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _XOPEN_SOURCE 500 /* crypt() */
+#define _BSD_SOURCE /* strcasecmp(), strncasecmp() */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+
+#ifdef HAVE_TLS
+#include "uhttpd-tls.h"
+#endif
+
+
+static char *uh_index_files[] = {
+ "index.html",
+ "index.htm",
+ "default.html",
+ "default.htm"
+};
+
+
+const char * sa_straddr(void *sa)
+{
+ static char str[INET6_ADDRSTRLEN];
+ struct sockaddr_in *v4 = (struct sockaddr_in *)sa;
+ struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)sa;
+
+ if (v4->sin_family == AF_INET)
+ return inet_ntop(AF_INET, &(v4->sin_addr), str, sizeof(str));
+ else
+ return inet_ntop(AF_INET6, &(v6->sin6_addr), str, sizeof(str));
+}
+
+const char * sa_strport(void *sa)
+{
+ static char str[6];
+ snprintf(str, sizeof(str), "%i", sa_port(sa));
+ return str;
+}
+
+int sa_port(void *sa)
+{
+ return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+}
+
+int sa_rfc1918(void *sa)
+{
+ struct sockaddr_in *v4 = (struct sockaddr_in *)sa;
+ unsigned long a = htonl(v4->sin_addr.s_addr);
+
+ if (v4->sin_family == AF_INET)
+ {
+ return ((a >= 0x0A000000) && (a <= 0x0AFFFFFF)) ||
+ ((a >= 0xAC100000) && (a <= 0xAC1FFFFF)) ||
+ ((a >= 0xC0A80000) && (a <= 0xC0A8FFFF));
+ }
+
+ return 0;
+}
+
+/* Simple strstr() like function that takes len arguments for both haystack and needle. */
+char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
+{
+ int match = 0;
+ int i, j;
+
+ for (i = 0; i < hslen; i++)
+ {
+ if (haystack[i] == needle[0])
+ {
+ match = ((ndlen == 1) || ((i + ndlen) <= hslen));
+
+ for (j = 1; (j < ndlen) && ((i + j) < hslen); j++)
+ {
+ if (haystack[i+j] != needle[j])
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ if (match)
+ return &haystack[i];
+ }
+ }
+
+ return NULL;
+}
+
+bool uh_socket_wait(int fd, int sec, bool write)
+{
+ int rv;
+ struct timeval timeout;
+
+ fd_set fds;
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ timeout.tv_sec = sec;
+ timeout.tv_usec = 0;
+
+ while (((rv = select(fd+1, write ? NULL : &fds, write ? &fds : NULL,
+ NULL, &timeout)) < 0) && (errno == EINTR))
+ {
+ D("IO: FD(%d) select interrupted: %s\n",
+ fd, strerror(errno));
+
+ continue;
+ }
+
+ if (rv <= 0)
+ {
+ D("IO: FD(%d) appears dead (rv=%d)\n", fd, rv);
+ return false;
+ }
+
+ return true;
+}
+
+static int __uh_raw_send(struct client *cl, const char *buf, int len, int sec,
+ int (*wfn) (struct client *, const char *, int))
+{
+ ssize_t rv;
+ int fd = cl->fd.fd;
+
+ while (true)
+ {
+ if ((rv = wfn(cl, buf, len)) < 0)
+ {
+ if (errno == EINTR)
+ {
+ D("IO: FD(%d) interrupted\n", cl->fd.fd);
+ continue;
+ }
+ else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ if (!uh_socket_wait(fd, sec, true))
+ return -1;
+ }
+ else
+ {
+ D("IO: FD(%d) write error: %s\n", fd, strerror(errno));
+ return -1;
+ }
+ }
+ /*
+ * It is not entirely clear whether rv = 0 on nonblocking sockets
+ * is an error. In real world fuzzing tests, not handling it as close
+ * led to tight infinite loops in this send procedure, so treat it as
+ * closed and break out.
+ */
+ else if (rv == 0)
+ {
+ D("IO: FD(%d) appears closed\n", fd);
+ return 0;
+ }
+ else if (rv < len)
+ {
+ D("IO: FD(%d) short write %d/%d bytes\n", fd, rv, len);
+ len -= rv;
+ buf += rv;
+ continue;
+ }
+ else
+ {
+ D("IO: FD(%d) sent %d/%d bytes\n", fd, rv, len);
+ return rv;
+ }
+ }
+}
+
+int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len)
+{
+ return write(cl->fd.fd, buf, len);
+}
+
+int uh_raw_send(int fd, const char *buf, int len, int sec)
+{
+ struct client_light cl = { .fd = { .fd = fd } };
+ return __uh_raw_send((struct client *)&cl, buf, len, sec,
+ uh_tcp_send_lowlevel);
+}
+
+int uh_tcp_send(struct client *cl, const char *buf, int len)
+{
+ int seconds = cl->server->conf->network_timeout;
+#ifdef HAVE_TLS
+ if (cl->tls)
+ return __uh_raw_send(cl, buf, len, seconds,
+ cl->server->conf->tls_send);
+#endif
+ return __uh_raw_send(cl, buf, len, seconds, uh_tcp_send_lowlevel);
+}
+
+static int __uh_raw_recv(struct client *cl, char *buf, int len, int sec,
+ int (*rfn) (struct client *, char *, int))
+{
+ ssize_t rv;
+ int fd = cl->fd.fd;
+
+ while (true)
+ {
+ if ((rv = rfn(cl, buf, len)) < 0)
+ {
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ if (!uh_socket_wait(fd, sec, false))
+ return -1;
+ }
+ else
+ {
+ D("IO: FD(%d) read error: %s\n", fd, strerror(errno));
+ return -1;
+ }
+ }
+ else if (rv == 0)
+ {
+ D("IO: FD(%d) appears closed\n", fd);
+ return 0;
+ }
+ else
+ {
+ D("IO: FD(%d) read %d bytes\n", fd, rv);
+ return rv;
+ }
+ }
+}
+
+int uh_tcp_recv_lowlevel(struct client *cl, char *buf, int len)
+{
+ return read(cl->fd.fd, buf, len);
+}
+
+int uh_raw_recv(int fd, char *buf, int len, int sec)
+{
+ struct client_light cl = { .fd = { .fd = fd } };
+ return __uh_raw_recv((struct client *)&cl, buf, len, sec,
+ uh_tcp_recv_lowlevel);
+}
+
+int uh_tcp_recv(struct client *cl, char *buf, int len)
+{
+ int seconds = cl->server->conf->network_timeout;
+#ifdef HAVE_TLS
+ if (cl->tls)
+ return __uh_raw_recv(cl, buf, len, seconds,
+ cl->server->conf->tls_recv);
+#endif
+ return __uh_raw_recv(cl, buf, len, seconds, uh_tcp_recv_lowlevel);
+}
+
+
+int uh_http_sendhf(struct client *cl, int code, const char *summary,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ char buffer[UH_LIMIT_MSGHEAD];
+ int len;
+
+ len = snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 %03i %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n",
+ code, summary
+ );
+
+ ensure_ret(uh_tcp_send(cl, buffer, len));
+
+ va_start(ap, fmt);
+ len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ ensure_ret(uh_http_sendc(cl, buffer, len));
+ ensure_ret(uh_http_sendc(cl, NULL, 0));
+
+ return 0;
+}
+
+
+int uh_http_sendc(struct client *cl, const char *data, int len)
+{
+ char chunk[8];
+ int clen;
+
+ if (len == -1)
+ len = strlen(data);
+
+ if (len > 0)
+ {
+ clen = snprintf(chunk, sizeof(chunk), "%X\r\n", len);
+ ensure_ret(uh_tcp_send(cl, chunk, clen));
+ ensure_ret(uh_tcp_send(cl, data, len));
+ ensure_ret(uh_tcp_send(cl, "\r\n", 2));
+ }
+ else
+ {
+ ensure_ret(uh_tcp_send(cl, "0\r\n\r\n", 5));
+ }
+
+ return 0;
+}
+
+int uh_http_sendf(struct client *cl, struct http_request *req,
+ const char *fmt, ...)
+{
+ va_list ap;
+ char buffer[UH_LIMIT_MSGHEAD];
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ if ((req != NULL) && (req->version > UH_HTTP_VER_1_0))
+ ensure_ret(uh_http_sendc(cl, buffer, len));
+ else if (len > 0)
+ ensure_ret(uh_tcp_send(cl, buffer, len));
+
+ return 0;
+}
+
+int uh_http_send(struct client *cl, struct http_request *req,
+ const char *buf, int len)
+{
+ if (len < 0)
+ len = strlen(buf);
+
+ if ((req != NULL) && (req->version > UH_HTTP_VER_1_0))
+ ensure_ret(uh_http_sendc(cl, buf, len));
+ else if (len > 0)
+ ensure_ret(uh_tcp_send(cl, buf, len));
+
+ return 0;
+}
+
+
+/* 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] == '%')
+ {
+ if (((i+2) < slen) && isxdigit(src[i+1]) && isxdigit(src[i+2]))
+ {
+ buf[len++] = (char)(16 * hex(src[i+1]) + hex(src[i+2]));
+ i += 2;
+ }
+ else
+ {
+ /* Encoding error: it's hard to think of a
+ ** scenario in which returning an incorrect
+ ** 'decoding' of the malformed string is
+ ** preferable to signaling an error condition. */
+ #if 0 /* WORSE_IS_BETTER */
+ buf[len++] = '%';
+ #else
+ return -2;
+ #endif
+ }
+ }
+ else
+ {
+ buf[len++] = src[i];
+ }
+ }
+
+ 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;
+ 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 unsigned char *src, int slen)
+{
+ int i = 0;
+ int len = 0;
+
+ unsigned int cin = 0;
+ unsigned int cout = 0;
+
+
+ for (i = 0; (i <= slen) && (src[i] != 0); i++)
+ {
+ cin = src[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)
+ {
+ if ((len + 3) < blen)
+ {
+ buf[len++] = (char)(cout >> 16);
+ buf[len++] = (char)(cout >> 8);
+ buf[len++] = (char)(cout);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ buf[len++] = 0;
+ return len;
+}
+
+static char * canonpath(const char *path, char *path_resolved)
+{
+ char path_copy[PATH_MAX];
+ char *path_cpy = path_copy;
+ char *path_res = path_resolved;
+
+ struct stat s;
+
+
+ /* relative -> absolute */
+ if (*path != '/')
+ {
+ getcwd(path_copy, PATH_MAX);
+ strncat(path_copy, "/", PATH_MAX - strlen(path_copy));
+ strncat(path_copy, path, PATH_MAX - strlen(path_copy));
+ }
+ else
+ {
+ strncpy(path_copy, path, PATH_MAX);
+ }
+
+ /* normalize */
+ while ((*path_cpy != '\0') && (path_cpy < (path_copy + PATH_MAX - 2)))
+ {
+ if (*path_cpy == '/')
+ {
+ /* skip repeating / */
+ if (path_cpy[1] == '/')
+ {
+ path_cpy++;
+ continue;
+ }
+
+ /* /./ or /../ */
+ else if (path_cpy[1] == '.')
+ {
+ /* skip /./ */
+ if ((path_cpy[2] == '/') || (path_cpy[2] == '\0'))
+ {
+ path_cpy += 2;
+ continue;
+ }
+
+ /* collapse /x/../ */
+ else if ((path_cpy[2] == '.') &&
+ ((path_cpy[3] == '/') || (path_cpy[3] == '\0')))
+ {
+ while ((path_res > path_resolved) && (*--path_res != '/'))
+ ;
+
+ path_cpy += 3;
+ continue;
+ }
+ }
+ }
+
+ *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';
+
+ /* test access */
+ if (!stat(path_resolved, &s) && (s.st_mode & S_IROTH))
+ return path_resolved;
+
+ return NULL;
+}
+
+/* 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;
+
+ char buffer[UH_LIMIT_MSGHEAD];
+ char *docroot = cl->server->conf->docroot;
+ char *pathptr = NULL;
+
+ int slash = 0;
+ int no_sym = cl->server->conf->no_symlinks;
+ int i = 0;
+ struct stat s;
+
+ /* back out early if url is undefined */
+ if (url == NULL)
+ return NULL;
+
+ memset(path_phys, 0, sizeof(path_phys));
+ memset(path_info, 0, sizeof(path_info));
+ memset(buffer, 0, sizeof(buffer));
+ memset(&p, 0, sizeof(p));
+
+ /* copy docroot */
+ memcpy(buffer, docroot,
+ min(strlen(docroot), sizeof(buffer) - 1));
+
+ /* 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(&buffer[strlen(docroot)],
+ sizeof(buffer) - strlen(docroot) - 1,
+ url, pathptr - url ) < 0)
+ {
+ return NULL; /* bad URL */
+ }
+ }
+ }
+
+ /* no query string, decode all of url */
+ else
+ {
+ if (uh_urldecode(&buffer[strlen(docroot)],
+ sizeof(buffer) - strlen(docroot) - 1,
+ url, strlen(url) ) < 0)
+ {
+ return NULL; /* bad URL */
+ }
+ }
+
+ /* create canon path */
+ for (i = strlen(buffer), slash = (buffer[max(0, i-1)] == '/'); i >= 0; i--)
+ {
+ if ((buffer[i] == 0) || (buffer[i] == '/'))
+ {
+ memset(path_info, 0, sizeof(path_info));
+ memcpy(path_info, buffer, min(i + 1, sizeof(path_info) - 1));
+
+ if (no_sym ? realpath(path_info, path_phys)
+ : canonpath(path_info, path_phys))
+ {
+ memset(path_info, 0, sizeof(path_info));
+ memcpy(path_info, &buffer[i],
+ min(strlen(buffer) - i, sizeof(path_info) - 1));
+
+ break;
+ }
+ }
+ }
+
+ /* check whether found path is within docroot */
+ if (strncmp(path_phys, docroot, strlen(docroot)) ||
+ ((path_phys[strlen(docroot)] != 0) &&
+ (path_phys[strlen(docroot)] != '/')))
+ {
+ return NULL;
+ }
+
+ /* test current path */
+ if (!stat(path_phys, &p.stat))
+ {
+ /* is a regular file */
+ if (p.stat.st_mode & S_IFREG)
+ {
+ p.root = docroot;
+ p.phys = path_phys;
+ p.name = &path_phys[strlen(docroot)];
+ p.info = path_info[0] ? path_info : NULL;
+ }
+
+ /* is a directory */
+ else if ((p.stat.st_mode & S_IFDIR) && !strlen(path_info))
+ {
+ /* ensure trailing slash */
+ if (path_phys[strlen(path_phys)-1] != '/')
+ path_phys[strlen(path_phys)] = '/';
+
+ /* try to locate index file */
+ memset(buffer, 0, sizeof(buffer));
+ memcpy(buffer, path_phys, sizeof(buffer));
+ pathptr = &buffer[strlen(buffer)];
+
+ /* 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_sendf(cl, NULL,
+ "HTTP/1.1 302 Found\r\n"
+ "Location: %s%s%s\r\n"
+ "Connection: close\r\n\r\n",
+ &path_phys[strlen(docroot)],
+ p.query ? "?" : "",
+ p.query ? p.query : ""
+ );
+
+ p.redirected = 1;
+ }
+ else if (cl->server->conf->index_file)
+ {
+ strncat(buffer, cl->server->conf->index_file, sizeof(buffer));
+
+ if (!stat(buffer, &s) && (s.st_mode & S_IFREG))
+ {
+ memcpy(path_phys, buffer, sizeof(path_phys));
+ memcpy(&p.stat, &s, sizeof(p.stat));
+ }
+ }
+ else
+ {
+ for (i = 0; i < array_size(uh_index_files); i++)
+ {
+ strncat(buffer, uh_index_files[i], sizeof(buffer));
+
+ if (!stat(buffer, &s) && (s.st_mode & S_IFREG))
+ {
+ memcpy(path_phys, buffer, sizeof(path_phys));
+ memcpy(&p.stat, &s, sizeof(p.stat));
+ break;
+ }
+
+ *pathptr = 0;
+ }
+ }
+
+ p.root = docroot;
+ p.phys = path_phys;
+ p.name = &path_phys[strlen(docroot)];
+ }
+ }
+
+ return p.phys ? &p : NULL;
+}
+
+
+static struct auth_realm *uh_realms = NULL;
+
+struct auth_realm * uh_auth_add(char *path, char *user, char *pass)
+{
+ struct auth_realm *new = NULL;
+ struct passwd *pwd;
+
+#ifdef HAVE_SHADOW
+ struct spwd *spwd;
+#endif
+
+ if((new = (struct auth_realm *)malloc(sizeof(struct auth_realm))) != NULL)
+ {
+ memset(new, 0, sizeof(struct auth_realm));
+
+ memcpy(new->path, path,
+ min(strlen(path), sizeof(new->path) - 1));
+
+ memcpy(new->user, user,
+ min(strlen(user), sizeof(new->user) - 1));
+
+ /* given password refers to a passwd entry */
+ if ((strlen(pass) > 3) && !strncmp(pass, "$p$", 3))
+ {
+#ifdef HAVE_SHADOW
+ /* try to resolve shadow entry */
+ if (((spwd = getspnam(&pass[3])) != NULL) && spwd->sp_pwdp)
+ {
+ memcpy(new->pass, spwd->sp_pwdp,
+ min(strlen(spwd->sp_pwdp), sizeof(new->pass) - 1));
+ }
+
+ else
+#endif
+
+ /* try to resolve passwd entry */
+ if (((pwd = getpwnam(&pass[3])) != NULL) && pwd->pw_passwd &&
+ (pwd->pw_passwd[0] != '!') && (pwd->pw_passwd[0] != 0))
+ {
+ memcpy(new->pass, pwd->pw_passwd,
+ min(strlen(pwd->pw_passwd), sizeof(new->pass) - 1));
+ }
+ }
+
+ /* ordinary pwd */
+ else
+ {
+ memcpy(new->pass, pass,
+ min(strlen(pass), sizeof(new->pass) - 1));
+ }
+
+ if (new->pass[0])
+ {
+ new->next = uh_realms;
+ uh_realms = new;
+
+ return new;
+ }
+
+ free(new);
+ }
+
+ return NULL;
+}
+
+int uh_auth_check(struct client *cl, struct http_request *req,
+ struct path_info *pi)
+{
+ int i, plen, rlen, protected;
+ char buffer[UH_LIMIT_MSGHEAD];
+ char *user = NULL;
+ char *pass = NULL;
+
+ struct auth_realm *realm = NULL;
+
+ plen = strlen(pi->name);
+ protected = 0;
+
+ /* check whether at least one realm covers the requested url */
+ for (realm = uh_realms; realm; realm = realm->next)
+ {
+ rlen = strlen(realm->path);
+
+ if ((plen >= rlen) && !strncasecmp(pi->name, realm->path, rlen))
+ {
+ req->realm = realm;
+ protected = 1;
+ break;
+ }
+ }
+
+ /* requested resource is covered by a realm */
+ if (protected)
+ {
+ /* try to get client auth info */
+ foreach_header(i, req->headers)
+ {
+ if (!strcasecmp(req->headers[i], "Authorization") &&
+ (strlen(req->headers[i+1]) > 6) &&
+ !strncasecmp(req->headers[i+1], "Basic ", 6))
+ {
+ memset(buffer, 0, sizeof(buffer));
+ uh_b64decode(buffer, sizeof(buffer) - 1,
+ (unsigned char *) &req->headers[i+1][6],
+ strlen(req->headers[i+1]) - 6);
+
+ if ((pass = strchr(buffer, ':')) != NULL)
+ {
+ user = buffer;
+ *pass++ = 0;
+ }
+
+ break;
+ }
+ }
+
+ /* have client auth */
+ if (user && pass)
+ {
+ /* find matching realm */
+ for (realm = uh_realms; realm; realm = realm->next)
+ {
+ rlen = strlen(realm->path);
+
+ if ((plen >= rlen) &&
+ !strncasecmp(pi->name, realm->path, rlen) &&
+ !strcmp(user, realm->user))
+ {
+ req->realm = realm;
+ break;
+ }
+ }
+
+ /* found a realm matching the username */
+ if (realm)
+ {
+ /* check user pass */
+ if (!strcmp(pass, realm->pass) ||
+ !strcmp(crypt(pass, realm->pass), realm->pass))
+ return 1;
+ }
+ }
+
+ /* 401 */
+ uh_http_sendf(cl, NULL,
+ "%s 401 Authorization Required\r\n"
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 23\r\n\r\n"
+ "Authorization Required\n",
+ http_versions[req->version],
+ cl->server->conf->realm);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static struct listener *uh_listeners = NULL;
+static struct client *uh_clients = NULL;
+
+struct listener * uh_listener_add(int sock, struct config *conf)
+{
+ struct listener *new = NULL;
+ socklen_t sl;
+
+ if ((new = (struct listener *)malloc(sizeof(struct listener))) != NULL)
+ {
+ memset(new, 0, sizeof(struct listener));
+
+ new->fd.fd = sock;
+ new->conf = conf;
+
+
+ /* get local endpoint addr */
+ sl = sizeof(struct sockaddr_in6);
+ memset(&(new->addr), 0, sl);
+ getsockname(sock, (struct sockaddr *) &(new->addr), &sl);
+
+ new->next = uh_listeners;
+ uh_listeners = new;
+
+ return new;
+ }
+
+ return NULL;
+}
+
+struct listener * uh_listener_lookup(int sock)
+{
+ struct listener *cur = NULL;
+
+ for (cur = uh_listeners; cur; cur = cur->next)
+ if (cur->fd.fd == sock)
+ return cur;
+
+ return NULL;
+}
+
+
+struct client * uh_client_add(int sock, struct listener *serv,
+ struct sockaddr_in6 *peer)
+{
+ struct client *new = NULL;
+ socklen_t sl;
+
+ if ((new = (struct client *)malloc(sizeof(struct client))) != NULL)
+ {
+ memset(new, 0, sizeof(struct client));
+ memcpy(&new->peeraddr, peer, sizeof(new->peeraddr));
+
+ new->fd.fd = sock;
+ new->server = serv;
+
+ new->rpipe.fd = -1;
+ new->wpipe.fd = -1;
+
+ /* get local endpoint addr */
+ sl = sizeof(struct sockaddr_in6);
+ getsockname(sock, (struct sockaddr *) &(new->servaddr), &sl);
+
+ new->next = uh_clients;
+ uh_clients = new;
+
+ serv->n_clients++;
+
+ D("IO: Client(%d) allocated\n", new->fd.fd);
+ }
+
+ return new;
+}
+
+struct client * uh_client_lookup(int sock)
+{
+ struct client *cur = NULL;
+
+ for (cur = uh_clients; cur; cur = cur->next)
+ if (cur->fd.fd == sock)
+ return cur;
+
+ return NULL;
+}
+
+void uh_client_shutdown(struct client *cl)
+{
+#ifdef HAVE_TLS
+ /* free client tls context */
+ if (cl->server && cl->server->conf->tls)
+ cl->server->conf->tls_close(cl);
+#endif
+
+ /* remove from global client list */
+ uh_client_remove(cl);
+}
+
+void uh_client_remove(struct client *cl)
+{
+ struct client *cur = NULL;
+ struct client *prv = NULL;
+
+ for (cur = uh_clients; cur; prv = cur, cur = cur->next)
+ {
+ if (cur == cl)
+ {
+ if (prv)
+ prv->next = cur->next;
+ else
+ uh_clients = cur->next;
+
+ if (cur->timeout.pending)
+ uloop_timeout_cancel(&cur->timeout);
+
+ if (cur->proc.pid)
+ uloop_process_delete(&cur->proc);
+
+ D("IO: Client(%d) freeing\n", cur->fd.fd);
+
+ uh_ufd_remove(&cur->rpipe);
+ uh_ufd_remove(&cur->wpipe);
+ uh_ufd_remove(&cur->fd);
+
+ cur->server->n_clients--;
+
+ free(cur);
+ break;
+ }
+ }
+}
+
+
+void uh_ufd_add(struct uloop_fd *u, uloop_fd_handler h, unsigned int ev)
+{
+ if (h != NULL)
+ {
+ u->cb = h;
+ uloop_fd_add(u, ev);
+ D("IO: FD(%d) added to uloop\n", u->fd);
+ }
+}
+
+void uh_ufd_remove(struct uloop_fd *u)
+{
+ if (u->cb != NULL)
+ {
+ uloop_fd_delete(u);
+ D("IO: FD(%d) removed from uloop\n", u->fd);
+ u->cb = NULL;
+ }
+
+ if (u->fd > -1)
+ {
+ close(u->fd);
+ D("IO: FD(%d) closed\n", u->fd);
+ u->fd = -1;
+ }
+}
+
+
+#ifdef HAVE_CGI
+static struct interpreter *uh_interpreters = NULL;
+
+struct interpreter * uh_interpreter_add(const char *extn, const char *path)
+{
+ struct interpreter *new = NULL;
+
+ if ((new = (struct interpreter *)malloc(sizeof(struct interpreter))) != NULL)
+ {
+ memset(new, 0, sizeof(struct interpreter));
+
+ memcpy(new->extn, extn, min(strlen(extn), sizeof(new->extn)-1));
+ memcpy(new->path, path, min(strlen(path), sizeof(new->path)-1));
+
+ new->next = uh_interpreters;
+ uh_interpreters = new;
+
+ return new;
+ }
+
+ return NULL;
+}
+
+struct interpreter * uh_interpreter_lookup(const char *path)
+{
+ struct interpreter *cur = NULL;
+ const char *e;
+
+ for (cur = uh_interpreters; cur; cur = cur->next)
+ {
+ e = &path[max(strlen(path) - strlen(cur->extn), 0)];
+
+ if (!strcmp(e, cur->extn))
+ return cur;
+ }
+
+ return NULL;
+}
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd-utils.h b/package/network/services/uhttpd/src/uhttpd-utils.h
new file mode 100644
index 0000000000..9de9191948
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd-utils.h
@@ -0,0 +1,140 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Utility header
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_UTILS_
+
+#include <stdarg.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include <libubox/uloop.h>
+
+
+#ifdef HAVE_SHADOW
+#include <shadow.h>
+#endif
+
+#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 foreach_header(i, h) \
+ for( i = 0; (i + 1) < (sizeof(h) / sizeof(h[0])) && h[i]; i += 2 )
+
+#define fd_cloexec(fd) \
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)
+
+#define fd_nonblock(fd) \
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)
+
+#define ensure_out(x) \
+ do { if((x) < 0) goto out; } while(0)
+
+#define ensure_ret(x) \
+ do { if((x) < 0) return -1; } while(0)
+
+
+struct path_info {
+ char *root;
+ char *phys;
+ char *name;
+ char *info;
+ char *query;
+ int redirected;
+ struct stat stat;
+};
+
+
+const char * sa_straddr(void *sa);
+const char * sa_strport(void *sa);
+int sa_port(void *sa);
+int sa_rfc1918(void *sa);
+
+char *strfind(char *haystack, int hslen, const char *needle, int ndlen);
+
+bool uh_socket_wait(int fd, int sec, bool write);
+
+int uh_raw_send(int fd, const char *buf, int len, int seconds);
+int uh_raw_recv(int fd, char *buf, int len, int seconds);
+int uh_tcp_send(struct client *cl, const char *buf, int len);
+int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len);
+int uh_tcp_recv(struct client *cl, char *buf, int len);
+int uh_tcp_recv_lowlevel(struct client *cl, char *buf, int len);
+
+int uh_http_sendhf(struct client *cl, int code, const char *summary,
+ const char *fmt, ...);
+
+#define uh_http_response(cl, code, message) \
+ uh_http_sendhf(cl, code, message, message)
+
+int uh_http_sendc(struct client *cl, const char *data, int len);
+
+int uh_http_sendf(
+ struct client *cl, struct http_request *req,
+ const char *fmt, ...
+);
+
+int uh_http_send(
+ struct client *cl, struct http_request *req,
+ const char *buf, int len
+);
+
+
+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 unsigned char *src, int slen);
+
+
+struct auth_realm * uh_auth_add(char *path, char *user, char *pass);
+
+int uh_auth_check(
+ struct client *cl, struct http_request *req, struct path_info *pi
+);
+
+
+struct path_info * uh_path_lookup(struct client *cl, const char *url);
+
+struct listener * uh_listener_add(int sock, struct config *conf);
+struct listener * uh_listener_lookup(int sock);
+
+struct client * uh_client_add(int sock, struct listener *serv,
+ struct sockaddr_in6 *peer);
+
+struct client * uh_client_lookup(int sock);
+
+#define uh_client_error(cl, code, status, ...) do { \
+ uh_http_sendhf(cl, code, status, __VA_ARGS__); \
+ uh_client_shutdown(cl); \
+} while(0)
+
+void uh_client_shutdown(struct client *cl);
+void uh_client_remove(struct client *cl);
+
+void uh_ufd_add(struct uloop_fd *u, uloop_fd_handler h, unsigned int ev);
+void uh_ufd_remove(struct uloop_fd *u);
+
+
+#ifdef HAVE_CGI
+struct interpreter * uh_interpreter_add(const char *extn, const char *path);
+struct interpreter * uh_interpreter_lookup(const char *path);
+#endif
+
+#endif
diff --git a/package/network/services/uhttpd/src/uhttpd.c b/package/network/services/uhttpd/src/uhttpd.c
new file mode 100644
index 0000000000..1efcbf0f51
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd.c
@@ -0,0 +1,1288 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Main component
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _XOPEN_SOURCE 500 /* crypt() */
+
+#include "uhttpd.h"
+#include "uhttpd-utils.h"
+#include "uhttpd-file.h"
+
+#ifdef HAVE_CGI
+#include "uhttpd-cgi.h"
+#endif
+
+#ifdef HAVE_LUA
+#include "uhttpd-lua.h"
+#endif
+
+#ifdef HAVE_TLS
+#include "uhttpd-tls.h"
+#endif
+
+
+const char * http_methods[] = { "GET", "POST", "HEAD", };
+const char * http_versions[] = { "HTTP/0.9", "HTTP/1.0", "HTTP/1.1", };
+
+static int run = 1;
+
+static void uh_sigterm(int sig)
+{
+ run = 0;
+}
+
+static void uh_config_parse(struct config *conf)
+{
+ FILE *c;
+ char line[512];
+ char *col1 = NULL;
+ char *col2 = NULL;
+ char *eol = NULL;
+
+ const char *path = conf->file ? conf->file : "/etc/httpd.conf";
+
+
+ if ((c = fopen(path, "r")) != NULL)
+ {
+ 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;
+ }
+
+ if (!uh_auth_add(line, col1, col2))
+ {
+ fprintf(stderr,
+ "Notice: No password set for user %s, ignoring "
+ "authentication on %s\n", col1, line
+ );
+ }
+ }
+ else if (!strncmp(line, "I:", 2))
+ {
+ if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
+ !(eol = strchr(col1, '\n')) || (*eol++ = 0))
+ {
+ continue;
+ }
+
+ conf->index_file = 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);
+ }
+#ifdef HAVE_CGI
+ 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;
+ }
+
+ if (!uh_interpreter_add(col1, col2))
+ {
+ fprintf(stderr,
+ "Unable to add interpreter %s for extension %s: "
+ "Out of memory\n", col2, col1
+ );
+ }
+ }
+#endif
+ }
+
+ fclose(c);
+ }
+}
+
+static void uh_listener_cb(struct uloop_fd *u, unsigned int events);
+
+static int uh_socket_bind(const char *host, const char *port,
+ struct addrinfo *hints, int do_tls,
+ struct config *conf)
+{
+ int sock = -1;
+ int yes = 1;
+ int status;
+ int bound = 0;
+
+ int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt;
+
+ struct listener *l = NULL;
+ struct addrinfo *addrs = NULL, *p = NULL;
+
+ if ((status = getaddrinfo(host, port, hints, &addrs)) != 0)
+ {
+ fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
+ }
+
+ /* try to bind a new socket to each found address */
+ for (p = addrs; p; p = p->ai_next)
+ {
+ /* get the socket */
+ if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
+ {
+ perror("socket()");
+ goto error;
+ }
+
+ /* "address already in use" */
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
+ {
+ perror("setsockopt()");
+ goto error;
+ }
+
+ /* TCP keep-alive */
+ if (conf->tcp_keepalive > 0)
+ {
+ tcp_ka_idl = 1;
+ tcp_ka_cnt = 3;
+ tcp_ka_int = conf->tcp_keepalive;
+
+ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) ||
+ 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)))
+ {
+ fprintf(stderr, "Notice: Unable to enable TCP keep-alive: %s\n",
+ strerror(errno));
+ }
+ }
+
+ /* required to get parallel v4 + v6 working */
+ if (p->ai_family == AF_INET6)
+ {
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1)
+ {
+ perror("setsockopt()");
+ goto error;
+ }
+ }
+
+ /* bind */
+ if (bind(sock, p->ai_addr, p->ai_addrlen) == -1)
+ {
+ perror("bind()");
+ goto error;
+ }
+
+ /* listen */
+ if (listen(sock, UH_LIMIT_CLIENTS) == -1)
+ {
+ perror("listen()");
+ goto error;
+ }
+
+ /* add listener to global list */
+ if (!(l = uh_listener_add(sock, conf)))
+ {
+ fprintf(stderr, "uh_listener_add(): Failed to allocate memory\n");
+ goto error;
+ }
+
+#ifdef HAVE_TLS
+ /* init TLS */
+ l->tls = do_tls ? conf->tls : NULL;
+#endif
+
+ /* add socket to uloop */
+ fd_cloexec(sock);
+ uh_ufd_add(&l->fd, uh_listener_cb, ULOOP_READ);
+
+ bound++;
+ continue;
+
+ error:
+ if (sock > 0)
+ close(sock);
+ }
+
+ freeaddrinfo(addrs);
+
+ return bound;
+}
+
+static struct http_request * uh_http_header_parse(struct client *cl,
+ char *buffer, int buflen)
+{
+ char *method = buffer;
+ char *path = NULL;
+ char *version = NULL;
+
+ char *headers = NULL;
+ char *hdrname = NULL;
+ char *hdrdata = NULL;
+
+ int i;
+ int hdrcount = 0;
+
+ struct http_request *req = &cl->request;
+
+
+ /* terminate initial header line */
+ if ((headers = strfind(buffer, buflen, "\r\n", 2)) != NULL)
+ {
+ buffer[buflen-1] = 0;
+
+ *headers++ = 0;
+ *headers++ = 0;
+
+ /* find request path */
+ if ((path = strchr(buffer, ' ')) != NULL)
+ *path++ = 0;
+
+ /* find http version */
+ if ((path != NULL) && ((version = strchr(path, ' ')) != NULL))
+ *version++ = 0;
+
+
+ /* check method */
+ if (method && !strcmp(method, "GET"))
+ req->method = UH_HTTP_MSG_GET;
+ else if (method && !strcmp(method, "POST"))
+ req->method = UH_HTTP_MSG_POST;
+ else if (method && !strcmp(method, "HEAD"))
+ req->method = UH_HTTP_MSG_HEAD;
+ else
+ {
+ /* invalid method */
+ uh_http_response(cl, 405, "Method Not Allowed");
+ return NULL;
+ }
+
+ /* check path */
+ if (!path || !strlen(path))
+ {
+ /* malformed request */
+ uh_http_response(cl, 400, "Bad Request");
+ return NULL;
+ }
+ else
+ {
+ req->url = path;
+ }
+
+ /* check version */
+ if (version && !strcmp(version, "HTTP/0.9"))
+ req->version = UH_HTTP_VER_0_9;
+ else if (version && !strcmp(version, "HTTP/1.0"))
+ req->version = UH_HTTP_VER_1_0;
+ else if (version && !strcmp(version, "HTTP/1.1"))
+ req->version = UH_HTTP_VER_1_1;
+ else
+ {
+ /* unsupported version */
+ uh_http_response(cl, 400, "Bad Request");
+ return NULL;
+ }
+
+ D("SRV: %s %s %s\n",
+ http_methods[req->method], req->url, http_versions[req->version]);
+
+ /* process header fields */
+ for (i = (int)(headers - buffer); i < buflen; i++)
+ {
+ /* found eol and have name + value, push out header tuple */
+ if (hdrname && hdrdata && (buffer[i] == '\r' || buffer[i] == '\n'))
+ {
+ buffer[i] = 0;
+
+ /* store */
+ if ((hdrcount + 1) < array_size(req->headers))
+ {
+ D("SRV: HTTP: %s: %s\n", hdrname, hdrdata);
+
+ req->headers[hdrcount++] = hdrname;
+ req->headers[hdrcount++] = hdrdata;
+
+ hdrname = hdrdata = NULL;
+ }
+
+ /* too large */
+ else
+ {
+ D("SRV: HTTP: header too big (too many headers)\n");
+ uh_http_response(cl, 413, "Request Entity Too Large");
+ return NULL;
+ }
+ }
+
+ /* have name but no value and found a colon, start of value */
+ else if (hdrname && !hdrdata &&
+ ((i+1) < buflen) && (buffer[i] == ':'))
+ {
+ buffer[i] = 0;
+ hdrdata = &buffer[i+1];
+
+ while ((hdrdata + 1) < (buffer + buflen) && *hdrdata == ' ')
+ hdrdata++;
+ }
+
+ /* have no name and found [A-Za-z], start of name */
+ else if (!hdrname && isalpha(buffer[i]))
+ {
+ hdrname = &buffer[i];
+ }
+ }
+
+ /* valid enough */
+ req->redirect_status = 200;
+ return req;
+ }
+
+ /* Malformed request */
+ uh_http_response(cl, 400, "Bad Request");
+ return NULL;
+}
+
+
+static struct http_request * uh_http_header_recv(struct client *cl)
+{
+ char *bufptr = cl->httpbuf.buf;
+ char *idxptr = NULL;
+
+ ssize_t blen = sizeof(cl->httpbuf.buf)-1;
+ ssize_t rlen = 0;
+
+ memset(bufptr, 0, sizeof(cl->httpbuf.buf));
+
+ while (blen > 0)
+ {
+ /* receive data */
+ ensure_out(rlen = uh_tcp_recv(cl, bufptr, blen));
+ D("SRV: Client(%d) peek(%d) = %d\n", cl->fd.fd, blen, rlen);
+
+ if (rlen <= 0)
+ {
+ D("SRV: Client(%d) dead [%s]\n", cl->fd.fd, strerror(errno));
+ return NULL;
+ }
+
+ blen -= rlen;
+ bufptr += rlen;
+
+ if ((idxptr = strfind(cl->httpbuf.buf, sizeof(cl->httpbuf.buf),
+ "\r\n\r\n", 4)))
+ {
+ /* header read complete ... */
+ cl->httpbuf.ptr = idxptr + 4;
+ cl->httpbuf.len = bufptr - cl->httpbuf.ptr;
+
+ return uh_http_header_parse(cl, cl->httpbuf.buf,
+ (cl->httpbuf.ptr - cl->httpbuf.buf));
+ }
+ }
+
+ /* request entity too large */
+ D("SRV: HTTP: header too big (buffer exceeded)\n");
+ uh_http_response(cl, 413, "Request Entity Too Large");
+
+out:
+ return NULL;
+}
+
+#if defined(HAVE_LUA) || defined(HAVE_CGI)
+static int uh_path_match(const char *prefix, const char *url)
+{
+ if ((strstr(url, prefix) == url) &&
+ ((prefix[strlen(prefix)-1] == '/') ||
+ (strlen(url) == strlen(prefix)) ||
+ (url[strlen(prefix)] == '/')))
+ {
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+static bool uh_dispatch_request(struct client *cl, struct http_request *req)
+{
+ struct path_info *pin;
+ struct interpreter *ipr = NULL;
+ struct config *conf = cl->server->conf;
+
+#ifdef HAVE_LUA
+ /* Lua request? */
+ if (conf->lua_state &&
+ uh_path_match(conf->lua_prefix, req->url))
+ {
+ return conf->lua_request(cl, conf->lua_state);
+ }
+ else
+#endif
+
+#ifdef HAVE_UBUS
+ /* ubus request? */
+ if (conf->ubus_state &&
+ uh_path_match(conf->ubus_prefix, req->url))
+ {
+ return conf->ubus_request(cl, conf->ubus_state);
+ }
+ else
+#endif
+
+ /* dispatch request */
+ if ((pin = uh_path_lookup(cl, req->url)) != NULL)
+ {
+ /* auth ok? */
+ if (!pin->redirected && uh_auth_check(cl, req, pin))
+ {
+#ifdef HAVE_CGI
+ if (uh_path_match(conf->cgi_prefix, pin->name) ||
+ (ipr = uh_interpreter_lookup(pin->phys)) != NULL)
+ {
+ return uh_cgi_request(cl, pin, ipr);
+ }
+#endif
+ return uh_file_request(cl, pin);
+ }
+ }
+
+ /* 404 - pass 1 */
+ else
+ {
+ /* Try to invoke an error handler */
+ if ((pin = uh_path_lookup(cl, conf->error_handler)) != NULL)
+ {
+ /* auth ok? */
+ if (uh_auth_check(cl, req, pin))
+ {
+ req->redirect_status = 404;
+#ifdef HAVE_CGI
+ if (uh_path_match(conf->cgi_prefix, pin->name) ||
+ (ipr = uh_interpreter_lookup(pin->phys)) != NULL)
+ {
+ return uh_cgi_request(cl, pin, ipr);
+ }
+#endif
+ return uh_file_request(cl, pin);
+ }
+ }
+
+ /* 404 - pass 2 */
+ else
+ {
+ uh_http_sendhf(cl, 404, "Not Found", "No such file or directory");
+ }
+ }
+
+ return false;
+}
+
+static void uh_socket_cb(struct uloop_fd *u, unsigned int events);
+
+static void uh_listener_cb(struct uloop_fd *u, unsigned int events)
+{
+ int new_fd;
+ struct listener *serv;
+ struct client *cl;
+ struct config *conf;
+
+ struct sockaddr_in6 sa;
+ socklen_t sl = sizeof(sa);
+
+ serv = container_of(u, struct listener, fd);
+ conf = serv->conf;
+
+ /* defer client if maximum number of requests is exceeded */
+ if (serv->n_clients >= conf->max_requests)
+ return;
+
+ /* handle new connections */
+ if ((new_fd = accept(u->fd, (struct sockaddr *)&sa, &sl)) != -1)
+ {
+ D("SRV: Server(%d) accept => Client(%d)\n", u->fd, new_fd);
+
+ /* add to global client list */
+ if ((cl = uh_client_add(new_fd, serv, &sa)) != NULL)
+ {
+ /* add client socket to global fdset */
+ uh_ufd_add(&cl->fd, uh_socket_cb, ULOOP_READ);
+ fd_cloexec(cl->fd.fd);
+
+#ifdef HAVE_TLS
+ /* setup client tls context */
+ if (conf->tls)
+ {
+ if (conf->tls_accept(cl) < 1)
+ {
+ D("SRV: Client(%d) SSL handshake failed, drop\n", new_fd);
+
+ /* remove from global client list */
+ uh_client_remove(cl);
+ return;
+ }
+ }
+#endif
+ }
+
+ /* insufficient resources */
+ else
+ {
+ fprintf(stderr, "uh_client_add(): Cannot allocate memory\n");
+ close(new_fd);
+ }
+ }
+}
+
+static void uh_client_cb(struct client *cl, unsigned int events);
+
+static void uh_rpipe_cb(struct uloop_fd *u, unsigned int events)
+{
+ struct client *cl = container_of(u, struct client, rpipe);
+
+ D("SRV: Client(%d) rpipe readable\n", cl->fd.fd);
+
+ uh_client_cb(cl, ULOOP_WRITE);
+}
+
+static void uh_socket_cb(struct uloop_fd *u, unsigned int events)
+{
+ struct client *cl = container_of(u, struct client, fd);
+
+ D("SRV: Client(%d) socket readable\n", cl->fd.fd);
+
+ uh_client_cb(cl, ULOOP_READ);
+}
+
+static void uh_child_cb(struct uloop_process *p, int rv)
+{
+ struct client *cl = container_of(p, struct client, proc);
+
+ D("SRV: Client(%d) child(%d) dead\n", cl->fd.fd, cl->proc.pid);
+
+ uh_client_cb(cl, ULOOP_READ | ULOOP_WRITE);
+}
+
+static void uh_kill9_cb(struct uloop_timeout *t)
+{
+ struct client *cl = container_of(t, struct client, timeout);
+
+ if (!kill(cl->proc.pid, 0))
+ {
+ D("SRV: Client(%d) child(%d) kill(SIGKILL)...\n",
+ cl->fd.fd, cl->proc.pid);
+
+ kill(cl->proc.pid, SIGKILL);
+ }
+}
+
+static void uh_timeout_cb(struct uloop_timeout *t)
+{
+ struct client *cl = container_of(t, struct client, timeout);
+
+ D("SRV: Client(%d) child(%d) timed out\n", cl->fd.fd, cl->proc.pid);
+
+ if (!kill(cl->proc.pid, 0))
+ {
+ D("SRV: Client(%d) child(%d) kill(SIGTERM)...\n",
+ cl->fd.fd, cl->proc.pid);
+
+ kill(cl->proc.pid, SIGTERM);
+
+ cl->timeout.cb = uh_kill9_cb;
+ uloop_timeout_set(&cl->timeout, 1000);
+ }
+}
+
+static void uh_client_cb(struct client *cl, unsigned int events)
+{
+ int i;
+ struct config *conf;
+ struct http_request *req;
+
+ conf = cl->server->conf;
+
+ D("SRV: Client(%d) enter callback\n", cl->fd.fd);
+
+ /* undispatched yet */
+ if (!cl->dispatched)
+ {
+ /* we have no headers yet and this was a write event, ignore... */
+ if (!(events & ULOOP_READ))
+ {
+ D("SRV: Client(%d) ignoring write event before headers\n", cl->fd.fd);
+ return;
+ }
+
+ /* attempt to receive and parse headers */
+ if (!(req = uh_http_header_recv(cl)))
+ {
+ D("SRV: Client(%d) failed to receive header\n", cl->fd.fd);
+ uh_client_shutdown(cl);
+ return;
+ }
+
+ /* process expect headers */
+ foreach_header(i, req->headers)
+ {
+ if (strcasecmp(req->headers[i], "Expect"))
+ continue;
+
+ if (strcasecmp(req->headers[i+1], "100-continue"))
+ {
+ D("SRV: Client(%d) unknown expect header (%s)\n",
+ cl->fd.fd, req->headers[i+1]);
+
+ uh_http_response(cl, 417, "Precondition Failed");
+ uh_client_shutdown(cl);
+ return;
+ }
+ else
+ {
+ D("SRV: Client(%d) sending HTTP/1.1 100 Continue\n", cl->fd.fd);
+
+ uh_http_sendf(cl, NULL, "HTTP/1.1 100 Continue\r\n\r\n");
+ cl->httpbuf.len = 0; /* client will re-send the body */
+ break;
+ }
+ }
+
+ /* RFC1918 filtering */
+ if (conf->rfc1918_filter &&
+ sa_rfc1918(&cl->peeraddr) && !sa_rfc1918(&cl->servaddr))
+ {
+ uh_http_sendhf(cl, 403, "Forbidden",
+ "Rejected request from RFC1918 IP "
+ "to public server address");
+
+ uh_client_shutdown(cl);
+ return;
+ }
+
+ /* dispatch request */
+ if (!uh_dispatch_request(cl, req))
+ {
+ D("SRV: Client(%d) failed to dispach request\n", cl->fd.fd);
+ uh_client_shutdown(cl);
+ return;
+ }
+
+ /* request handler spawned a pipe, register handler */
+ if (cl->rpipe.fd > -1)
+ {
+ D("SRV: Client(%d) pipe(%d) spawned\n", cl->fd.fd, cl->rpipe.fd);
+
+ uh_ufd_add(&cl->rpipe, uh_rpipe_cb, ULOOP_READ);
+ }
+
+ /* request handler spawned a child, register handler */
+ if (cl->proc.pid)
+ {
+ D("SRV: Client(%d) child(%d) spawned\n", cl->fd.fd, cl->proc.pid);
+
+ cl->proc.cb = uh_child_cb;
+ uloop_process_add(&cl->proc);
+
+ cl->timeout.cb = uh_timeout_cb;
+ uloop_timeout_set(&cl->timeout, conf->script_timeout * 1000);
+ }
+
+ /* header processing complete */
+ D("SRV: Client(%d) dispatched\n", cl->fd.fd);
+ cl->dispatched = true;
+ }
+
+ if (!cl->cb(cl))
+ {
+ D("SRV: Client(%d) response callback signalized EOF\n", cl->fd.fd);
+ uh_client_shutdown(cl);
+ return;
+ }
+}
+
+#ifdef HAVE_TLS
+static inline int uh_inittls(struct config *conf)
+{
+ /* library handle */
+ void *lib;
+
+ /* already loaded */
+ if (conf->tls != NULL)
+ return 0;
+
+ /* load TLS plugin */
+ if (!(lib = dlopen("uhttpd_tls.so", RTLD_LAZY | RTLD_GLOBAL)))
+ {
+ fprintf(stderr,
+ "Notice: Unable to load TLS plugin - disabling SSL support! "
+ "(Reason: %s)\n", dlerror()
+ );
+
+ return 1;
+ }
+ else
+ {
+ /* resolve functions */
+ if (!(conf->tls_init = dlsym(lib, "uh_tls_ctx_init")) ||
+ !(conf->tls_cert = dlsym(lib, "uh_tls_ctx_cert")) ||
+ !(conf->tls_key = dlsym(lib, "uh_tls_ctx_key")) ||
+ !(conf->tls_free = dlsym(lib, "uh_tls_ctx_free")) ||
+ !(conf->tls_accept = dlsym(lib, "uh_tls_client_accept")) ||
+ !(conf->tls_close = dlsym(lib, "uh_tls_client_close")) ||
+ !(conf->tls_recv = dlsym(lib, "uh_tls_client_recv")) ||
+ !(conf->tls_send = dlsym(lib, "uh_tls_client_send")))
+ {
+ fprintf(stderr,
+ "Error: Failed to lookup required symbols "
+ "in TLS plugin: %s\n", dlerror()
+ );
+ exit(1);
+ }
+
+ /* init SSL context */
+ if (!(conf->tls = conf->tls_init()))
+ {
+ fprintf(stderr, "Error: Failed to initalize SSL context\n");
+ exit(1);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+int main (int argc, char **argv)
+{
+ /* working structs */
+ struct addrinfo hints;
+ struct sigaction sa;
+ struct config conf;
+
+ /* maximum file descriptor number */
+ int cur_fd = 0;
+
+#ifdef HAVE_TLS
+ int tls = 0;
+ int keys = 0;
+#endif
+
+ int bound = 0;
+ int nofork = 0;
+
+ /* args */
+ int opt;
+ char addr[128];
+ char *port = NULL;
+
+#if defined(HAVE_LUA) || defined(HAVE_TLS) || defined(HAVE_UBUS)
+ /* library handle */
+ void *lib;
+#endif
+
+ /* handle SIGPIPE, SIGINT, SIGTERM */
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, NULL);
+
+ sa.sa_handler = uh_sigterm;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ /* prepare addrinfo hints */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ /* parse args */
+ memset(&conf, 0, sizeof(conf));
+
+ uloop_init();
+
+ while ((opt = getopt(argc, argv,
+ "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:n:x:i:t:T:A:u:U:")) > 0)
+ {
+ switch(opt)
+ {
+ /* [addr:]port */
+ case 'p':
+ case 's':
+ memset(addr, 0, sizeof(addr));
+
+ if ((port = strrchr(optarg, ':')) != NULL)
+ {
+ if ((optarg[0] == '[') && (port > optarg) && (port[-1] == ']'))
+ memcpy(addr, optarg + 1,
+ min(sizeof(addr), (int)(port - optarg) - 2));
+ else
+ memcpy(addr, optarg,
+ min(sizeof(addr), (int)(port - optarg)));
+
+ port++;
+ }
+ else
+ {
+ port = optarg;
+ }
+
+#ifdef HAVE_TLS
+ if (opt == 's')
+ {
+ if (uh_inittls(&conf))
+ {
+ fprintf(stderr,
+ "Notice: TLS support is disabled, "
+ "ignoring '-s %s'\n", optarg
+ );
+ continue;
+ }
+
+ tls = 1;
+ }
+#endif
+
+ /* bind sockets */
+ bound += uh_socket_bind(addr[0] ? addr : NULL, port, &hints,
+ (opt == 's'), &conf);
+ break;
+
+#ifdef HAVE_TLS
+ /* certificate */
+ case 'C':
+ if (!uh_inittls(&conf))
+ {
+ if (conf.tls_cert(conf.tls, optarg) < 1)
+ {
+ fprintf(stderr,
+ "Error: Invalid certificate file given\n");
+ exit(1);
+ }
+
+ keys++;
+ }
+
+ break;
+
+ /* key */
+ case 'K':
+ if (!uh_inittls(&conf))
+ {
+ if (conf.tls_key(conf.tls, optarg) < 1)
+ {
+ fprintf(stderr,
+ "Error: Invalid private key file given\n");
+ exit(1);
+ }
+
+ keys++;
+ }
+
+ break;
+#else
+ case 'C':
+ case 'K':
+ fprintf(stderr,
+ "Notice: TLS support not compiled, ignoring -%c\n",
+ opt);
+ break;
+#endif
+
+ /* docroot */
+ case 'h':
+ if (! realpath(optarg, conf.docroot))
+ {
+ fprintf(stderr, "Error: Invalid directory %s: %s\n",
+ optarg, strerror(errno));
+ exit(1);
+ }
+ break;
+
+ /* error handler */
+ case 'E':
+ if ((strlen(optarg) == 0) || (optarg[0] != '/'))
+ {
+ fprintf(stderr, "Error: Invalid error handler: %s\n",
+ optarg);
+ exit(1);
+ }
+ conf.error_handler = optarg;
+ break;
+
+ /* index file */
+ case 'I':
+ if ((strlen(optarg) == 0) || (optarg[0] == '/'))
+ {
+ fprintf(stderr, "Error: Invalid index page: %s\n",
+ optarg);
+ exit(1);
+ }
+ conf.index_file = optarg;
+ break;
+
+ /* don't follow symlinks */
+ case 'S':
+ conf.no_symlinks = 1;
+ break;
+
+ /* don't list directories */
+ case 'D':
+ conf.no_dirlists = 1;
+ break;
+
+ case 'R':
+ conf.rfc1918_filter = 1;
+ break;
+
+ case 'n':
+ conf.max_requests = atoi(optarg);
+ break;
+
+#ifdef HAVE_CGI
+ /* cgi prefix */
+ case 'x':
+ conf.cgi_prefix = optarg;
+ break;
+
+ /* interpreter */
+ case 'i':
+ if ((optarg[0] == '.') && (port = strchr(optarg, '=')))
+ {
+ *port++ = 0;
+ uh_interpreter_add(optarg, port);
+ }
+ else
+ {
+ fprintf(stderr, "Error: Invalid interpreter: %s\n",
+ optarg);
+ exit(1);
+ }
+ break;
+#else
+ case 'x':
+ case 'i':
+ fprintf(stderr,
+ "Notice: CGI support not compiled, ignoring -%c\n",
+ opt);
+ break;
+#endif
+
+#ifdef HAVE_LUA
+ /* lua prefix */
+ case 'l':
+ conf.lua_prefix = optarg;
+ break;
+
+ /* lua handler */
+ case 'L':
+ conf.lua_handler = optarg;
+ break;
+#else
+ case 'l':
+ case 'L':
+ fprintf(stderr,
+ "Notice: Lua support not compiled, ignoring -%c\n",
+ opt);
+ break;
+#endif
+
+#ifdef HAVE_UBUS
+ /* ubus prefix */
+ case 'u':
+ conf.ubus_prefix = optarg;
+ break;
+
+ /* ubus socket */
+ case 'U':
+ conf.ubus_socket = optarg;
+ break;
+#else
+ case 'u':
+ case 'U':
+ fprintf(stderr,
+ "Notice: UBUS support not compiled, ignoring -%c\n",
+ opt);
+ break;
+#endif
+
+#if defined(HAVE_CGI) || defined(HAVE_LUA)
+ /* script timeout */
+ case 't':
+ conf.script_timeout = atoi(optarg);
+ break;
+#endif
+
+ /* network timeout */
+ case 'T':
+ conf.network_timeout = atoi(optarg);
+ break;
+
+ /* tcp keep-alive */
+ case 'A':
+ conf.tcp_keepalive = atoi(optarg);
+ break;
+
+ /* no fork */
+ case 'f':
+ nofork = 1;
+ break;
+
+ /* urldecode */
+ case 'd':
+ if ((port = malloc(strlen(optarg)+1)) != NULL)
+ {
+ /* "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 */
+ memset(port, 0, opt+1);
+ if (uh_urldecode(port, opt, optarg, opt) < 0)
+ fprintf(stderr, "uhttpd: invalid encoding\n");
+
+ printf("%s", port);
+ free(port);
+ exit(0);
+ }
+ break;
+
+ /* basic auth realm */
+ case 'r':
+ conf.realm = optarg;
+ break;
+
+ /* md5 crypt */
+ case 'm':
+ printf("%s\n", crypt(optarg, "$1$"));
+ exit(0);
+ break;
+
+ /* config file */
+ case 'c':
+ conf.file = optarg;
+ break;
+
+ default:
+ 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"
+#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 page for directories\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 requests\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 HTTP/JSON handler\n"
+ " -U file Override ubus socket path\n"
+#endif
+#ifdef HAVE_CGI
+ " -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
+ " -i .ext=path Use interpreter at path for files with the given extension\n"
+#endif
+#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
+ " -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n"
+#endif
+ " -T seconds Network timeout in seconds, default is 30\n"
+ " -d string URL decode given string\n"
+ " -r string Specify basic auth realm\n"
+ " -m string MD5 crypt given string\n"
+ "\n", argv[0]
+ );
+
+ exit(1);
+ }
+ }
+
+#ifdef HAVE_TLS
+ if ((tls == 1) && (keys < 2))
+ {
+ fprintf(stderr, "Error: Missing private key or certificate file\n");
+ exit(1);
+ }
+#endif
+
+ if (bound < 1)
+ {
+ fprintf(stderr, "Error: No sockets bound, unable to continue\n");
+ exit(1);
+ }
+
+ /* default docroot */
+ if (!conf.docroot[0] && !realpath(".", conf.docroot))
+ {
+ fprintf(stderr, "Error: Can not determine default document root: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* default realm */
+ if (!conf.realm)
+ conf.realm = "Protected Area";
+
+ /* config file */
+ uh_config_parse(&conf);
+
+ /* default max requests */
+ if (conf.max_requests <= 0)
+ conf.max_requests = 3;
+
+ /* default network timeout */
+ if (conf.network_timeout <= 0)
+ conf.network_timeout = 30;
+
+#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
+ /* default script timeout */
+ if (conf.script_timeout <= 0)
+ conf.script_timeout = 60;
+#endif
+
+#ifdef HAVE_CGI
+ /* default cgi prefix */
+ if (!conf.cgi_prefix)
+ conf.cgi_prefix = "/cgi-bin";
+#endif
+
+#ifdef HAVE_LUA
+ /* load Lua plugin */
+ if (!(lib = dlopen("uhttpd_lua.so", RTLD_LAZY | RTLD_GLOBAL)))
+ {
+ fprintf(stderr,
+ "Notice: Unable to load Lua plugin - disabling Lua support! "
+ "(Reason: %s)\n", dlerror());
+ }
+ else
+ {
+ /* resolve functions */
+ if (!(conf.lua_init = dlsym(lib, "uh_lua_init")) ||
+ !(conf.lua_close = dlsym(lib, "uh_lua_close")) ||
+ !(conf.lua_request = dlsym(lib, "uh_lua_request")))
+ {
+ fprintf(stderr,
+ "Error: Failed to lookup required symbols "
+ "in Lua plugin: %s\n", dlerror()
+ );
+ exit(1);
+ }
+
+ /* init Lua runtime if handler is specified */
+ if (conf.lua_handler)
+ {
+ /* default lua prefix */
+ if (!conf.lua_prefix)
+ conf.lua_prefix = "/lua";
+
+ conf.lua_state = conf.lua_init(&conf);
+ }
+ }
+#endif
+
+#ifdef HAVE_UBUS
+ /* load ubus plugin */
+ if (!(lib = dlopen("uhttpd_ubus.so", RTLD_LAZY | RTLD_GLOBAL)))
+ {
+ fprintf(stderr,
+ "Notice: Unable to load ubus plugin - disabling ubus support! "
+ "(Reason: %s)\n", dlerror());
+ }
+ else if (conf.ubus_prefix)
+ {
+ /* resolve functions */
+ if (!(conf.ubus_init = dlsym(lib, "uh_ubus_init")) ||
+ !(conf.ubus_close = dlsym(lib, "uh_ubus_close")) ||
+ !(conf.ubus_request = dlsym(lib, "uh_ubus_request")))
+ {
+ fprintf(stderr,
+ "Error: Failed to lookup required symbols "
+ "in ubus plugin: %s\n", dlerror()
+ );
+ exit(1);
+ }
+
+ /* initialize ubus */
+ conf.ubus_state = conf.ubus_init(&conf);
+ }
+#endif
+
+ /* fork (if not disabled) */
+ if (!nofork)
+ {
+ switch (fork())
+ {
+ case -1:
+ perror("fork()");
+ exit(1);
+
+ case 0:
+ /* daemon setup */
+ if (chdir("/"))
+ perror("chdir()");
+
+ if ((cur_fd = open("/dev/null", O_WRONLY)) > -1)
+ dup2(cur_fd, 0);
+
+ if ((cur_fd = open("/dev/null", O_RDONLY)) > -1)
+ dup2(cur_fd, 1);
+
+ if ((cur_fd = open("/dev/null", O_RDONLY)) > -1)
+ dup2(cur_fd, 2);
+
+ break;
+
+ default:
+ exit(0);
+ }
+ }
+
+ /* server main loop */
+ uloop_run();
+
+#ifdef HAVE_LUA
+ /* destroy the Lua state */
+ if (conf.lua_state != NULL)
+ conf.lua_close(conf.lua_state);
+#endif
+
+#ifdef HAVE_UBUS
+ /* destroy the ubus state */
+ if (conf.ubus_state != NULL)
+ conf.ubus_close(conf.ubus_state);
+#endif
+
+ return 0;
+}
diff --git a/package/network/services/uhttpd/src/uhttpd.h b/package/network/services/uhttpd/src/uhttpd.h
new file mode 100644
index 0000000000..f6982db323
--- /dev/null
+++ b/package/network/services/uhttpd/src/uhttpd.h
@@ -0,0 +1,214 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Main header
+ *
+ * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UHTTPD_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <linux/limits.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include <libubox/list.h>
+#include <libubox/uloop.h>
+
+
+#ifdef HAVE_LUA
+#include <lua.h>
+#endif
+
+#ifdef HAVE_TLS
+#include <openssl/ssl.h>
+#endif
+
+/* uClibc... */
+#ifndef SOL_TCP
+#define SOL_TCP 6
+#endif
+
+#ifdef DEBUG
+#define D(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define D(...)
+#endif
+
+
+#define UH_LIMIT_MSGHEAD 4096
+#define UH_LIMIT_HEADERS 64
+#define UH_LIMIT_CLIENTS 64
+
+
+struct listener;
+struct client;
+struct interpreter;
+struct http_request;
+struct uh_ubus_state;
+
+struct config {
+ char docroot[PATH_MAX];
+ char *realm;
+ char *file;
+ char *index_file;
+ char *error_handler;
+ int no_symlinks;
+ int no_dirlists;
+ int network_timeout;
+ int rfc1918_filter;
+ int tcp_keepalive;
+ int max_requests;
+#ifdef HAVE_CGI
+ char *cgi_prefix;
+#endif
+#ifdef HAVE_LUA
+ char *lua_prefix;
+ char *lua_handler;
+ lua_State *lua_state;
+ lua_State * (*lua_init) (const struct config *conf);
+ void (*lua_close) (lua_State *L);
+ bool (*lua_request) (struct client *cl, lua_State *L);
+#endif
+#ifdef HAVE_UBUS
+ char *ubus_prefix;
+ char *ubus_socket;
+ void *ubus_state;
+ struct uh_ubus_state * (*ubus_init) (const struct config *conf);
+ void (*ubus_close) (struct uh_ubus_state *state);
+ bool (*ubus_request) (struct client *cl, struct uh_ubus_state *state);
+#endif
+#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
+ int script_timeout;
+#endif
+#ifdef HAVE_TLS
+ char *cert;
+ char *key;
+ SSL_CTX *tls;
+ SSL_CTX * (*tls_init) (void);
+ int (*tls_cert) (SSL_CTX *c, const char *file);
+ int (*tls_key) (SSL_CTX *c, const char *file);
+ void (*tls_free) (struct listener *l);
+ int (*tls_accept) (struct client *c);
+ void (*tls_close) (struct client *c);
+ int (*tls_recv) (struct client *c, char *buf, int len);
+ int (*tls_send) (struct client *c, const char *buf, int len);
+#endif
+};
+
+enum http_method {
+ UH_HTTP_MSG_GET,
+ UH_HTTP_MSG_POST,
+ UH_HTTP_MSG_HEAD,
+};
+
+extern const char *http_methods[];
+
+enum http_version {
+ UH_HTTP_VER_0_9,
+ UH_HTTP_VER_1_0,
+ UH_HTTP_VER_1_1,
+};
+
+extern const char *http_versions[];
+
+struct http_request {
+ enum http_method method;
+ enum http_version version;
+ int redirect_status;
+ char *url;
+ char *headers[UH_LIMIT_HEADERS];
+ struct auth_realm *realm;
+};
+
+struct http_response {
+ int statuscode;
+ char *statusmsg;
+ char *headers[UH_LIMIT_HEADERS];
+};
+
+struct listener {
+ struct uloop_fd fd;
+ int socket;
+ int n_clients;
+ struct sockaddr_in6 addr;
+ struct config *conf;
+#ifdef HAVE_TLS
+ SSL_CTX *tls;
+#endif
+ struct listener *next;
+};
+
+struct client {
+#ifdef HAVE_TLS
+ SSL *tls;
+#endif
+ struct uloop_fd fd;
+ struct uloop_fd rpipe;
+ struct uloop_fd wpipe;
+ struct uloop_process proc;
+ struct uloop_timeout timeout;
+ bool (*cb)(struct client *);
+ void *priv;
+ bool dispatched;
+ struct {
+ char buf[UH_LIMIT_MSGHEAD];
+ char *ptr;
+ int len;
+ } httpbuf;
+ struct listener *server;
+ struct http_request request;
+ struct http_response response;
+ struct sockaddr_in6 servaddr;
+ struct sockaddr_in6 peeraddr;
+ struct client *next;
+};
+
+struct client_light {
+#ifdef HAVE_TLS
+ SSL *tls;
+#endif
+ struct uloop_fd fd;
+};
+
+struct auth_realm {
+ char path[PATH_MAX];
+ char user[32];
+ char pass[128];
+ struct auth_realm *next;
+};
+
+#ifdef HAVE_CGI
+struct interpreter {
+ char path[PATH_MAX];
+ char extn[32];
+ struct interpreter *next;
+};
+#endif
+
+#endif