diff options
Diffstat (limited to 'package/network/services/uhttpd/src')
-rw-r--r-- | package/network/services/uhttpd/src/Makefile | 89 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-cgi.c | 556 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-cgi.h | 43 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-file.c | 438 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-file.h | 36 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-lua.c | 579 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-lua.h | 44 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-mimetypes.h | 86 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-tls.c | 170 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-tls.h | 36 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-ubus.c | 957 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-ubus.h | 70 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-utils.c | 1081 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-utils.h | 140 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd.c | 1288 | ||||
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd.h | 214 |
16 files changed, 5827 insertions, 0 deletions
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 |