aboutsummaryrefslogtreecommitdiff
path: root/httpd/server_http.c
diff options
context:
space:
mode:
Diffstat (limited to 'httpd/server_http.c')
-rw-r--r--httpd/server_http.c1425
1 files changed, 1425 insertions, 0 deletions
diff --git a/httpd/server_http.c b/httpd/server_http.c
new file mode 100644
index 0000000..b63fc22
--- /dev/null
+++ b/httpd/server_http.c
@@ -0,0 +1,1425 @@
+/* $OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdio.h>
+#include <time.h>
+#include <resolv.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include "httpd.h"
+#include "http.h"
+
+static int server_httpmethod_cmp(const void *, const void *);
+static int server_httperror_cmp(const void *, const void *);
+void server_httpdesc_free(struct http_descriptor *);
+int server_http_authenticate(struct server_config *,
+ struct client *);
+char *server_expand_http(struct client *, const char *,
+ char *, size_t);
+
+static struct httpd *env = NULL;
+
+static struct http_method http_methods[] = HTTP_METHODS;
+static struct http_error http_errors[] = HTTP_ERRORS;
+
+void
+server_http(struct httpd *x_env)
+{
+ if (x_env != NULL)
+ env = x_env;
+
+ DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
+
+ /* Sort the HTTP lookup arrays */
+ qsort(http_methods, sizeof(http_methods) /
+ sizeof(http_methods[0]) - 1,
+ sizeof(http_methods[0]), server_httpmethod_cmp);
+ qsort(http_errors, sizeof(http_errors) /
+ sizeof(http_errors[0]) - 1,
+ sizeof(http_errors[0]), server_httperror_cmp);
+}
+
+void
+server_http_init(struct server *srv)
+{
+ /* nothing */
+}
+
+int
+server_httpdesc_init(struct client *clt)
+{
+ struct http_descriptor *desc;
+
+ if ((desc = calloc(1, sizeof(*desc))) == NULL)
+ return (-1);
+ RB_INIT(&desc->http_headers);
+ clt->clt_descreq = desc;
+
+ if ((desc = calloc(1, sizeof(*desc))) == NULL) {
+ /* req will be cleaned up later */
+ return (-1);
+ }
+ RB_INIT(&desc->http_headers);
+ clt->clt_descresp = desc;
+
+ return (0);
+}
+
+void
+server_httpdesc_free(struct http_descriptor *desc)
+{
+ if (desc == NULL)
+ return;
+ if (desc->http_path != NULL) {
+ free(desc->http_path);
+ desc->http_path = NULL;
+ }
+ if (desc->http_path_alias != NULL) {
+ free(desc->http_path_alias);
+ desc->http_path_alias = NULL;
+ }
+ if (desc->http_query != NULL) {
+ free(desc->http_query);
+ desc->http_query = NULL;
+ }
+ if (desc->http_version != NULL) {
+ free(desc->http_version);
+ desc->http_version = NULL;
+ }
+ if (desc->http_host != NULL) {
+ free(desc->http_host);
+ desc->http_host = NULL;
+ }
+ kv_purge(&desc->http_headers);
+ desc->http_lastheader = NULL;
+ desc->http_method = 0;
+ desc->http_chunked = 0;
+}
+
+int
+server_http_authenticate(struct server_config *srv_conf, struct client *clt)
+{
+ char decoded[1024];
+ FILE *fp = NULL;
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct auth *auth = srv_conf->auth;
+ struct kv *ba, key;
+ size_t linesize = 0;
+ ssize_t linelen;
+ int ret = -1;
+ char *line = NULL, *user = NULL, *pass = NULL;
+ char *clt_user = NULL, *clt_pass = NULL;
+
+ memset(decoded, 0, sizeof(decoded));
+ key.kv_key = "Authorization";
+
+ if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
+ ba->kv_value == NULL)
+ goto done;
+
+ if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
+ goto done;
+
+ if (b64_pton(strchr(ba->kv_value, ' ') + 1, (u_int8_t *)decoded,
+ sizeof(decoded)) <= 0)
+ goto done;
+
+ if ((clt_pass = strchr(decoded, ':')) == NULL)
+ goto done;
+
+ clt_user = decoded;
+ *clt_pass++ = '\0';
+ if ((clt->clt_remote_user = strdup(clt_user)) == NULL)
+ goto done;
+
+ if (clt_pass == NULL)
+ goto done;
+
+ if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL)
+ goto done;
+
+ while ((linelen = getline(&line, &linesize, fp)) != -1) {
+ if (line[linelen - 1] == '\n')
+ line[linelen - 1] = '\0';
+ user = line;
+ pass = strchr(line, ':');
+
+ if (pass == NULL) {
+ explicit_bzero(line, linelen);
+ continue;
+ }
+
+ *pass++ = '\0';
+
+ if (strcmp(clt_user, user) != 0) {
+ explicit_bzero(line, linelen);
+ continue;
+ }
+
+ if (crypt_checkpass(clt_pass, pass) == 0) {
+ explicit_bzero(line, linelen);
+ ret = 0;
+ break;
+ }
+ }
+done:
+ if (fp != NULL)
+ fclose(fp);
+
+ if (ba != NULL && ba->kv_value != NULL) {
+ explicit_bzero(ba->kv_value, strlen(ba->kv_value));
+ explicit_bzero(decoded, sizeof(decoded));
+ }
+
+ return (ret);
+}
+
+void
+server_read_http(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char *line = NULL, *key, *value;
+ const char *errstr;
+ size_t size, linelen;
+ struct kv *hdr = NULL;
+
+ getmonotime(&clt->clt_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld",
+ __func__, clt->clt_id, size, clt->clt_toread);
+ if (!size) {
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ goto done;
+ }
+
+ while (!clt->clt_done && (line = evbuffer_readline(src)) != NULL) {
+ linelen = strlen(line);
+
+ /*
+ * An empty line indicates the end of the request.
+ * libevent already stripped the \r\n for us.
+ */
+ if (!linelen) {
+ clt->clt_done = 1;
+ free(line);
+ break;
+ }
+ key = line;
+
+ /* Limit the total header length minus \r\n */
+ clt->clt_headerlen += linelen;
+ if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) {
+ server_abort_http(clt, 413, "request too large");
+ goto abort;
+ }
+
+ /*
+ * The first line is the GET/POST/PUT/... request,
+ * subsequent lines are HTTP headers.
+ */
+ if (++clt->clt_line == 1)
+ value = strchr(key, ' ');
+ else if (*key == ' ' || *key == '\t')
+ /* Multiline headers wrap with a space or tab */
+ value = NULL;
+ else
+ value = strchr(key, ':');
+ if (value == NULL) {
+ if (clt->clt_line == 1) {
+ server_abort_http(clt, 400, "malformed");
+ goto abort;
+ }
+
+ /* Append line to the last header, if present */
+ if (kv_extend(&desc->http_headers,
+ desc->http_lastheader, line) == NULL)
+ goto fail;
+
+ free(line);
+ continue;
+ }
+ if (*value == ':') {
+ *value++ = '\0';
+ value += strspn(value, " \t\r\n");
+ } else {
+ *value++ = '\0';
+ }
+
+ DPRINTF("%s: session %d: header '%s: %s'", __func__,
+ clt->clt_id, key, value);
+
+ /*
+ * Identify and handle specific HTTP request methods
+ */
+ if (clt->clt_line == 1) {
+ if ((desc->http_method = server_httpmethod_byname(key))
+ == HTTP_METHOD_NONE) {
+ server_abort_http(clt, 400, "malformed");
+ goto abort;
+ }
+
+ /*
+ * Decode request path and query
+ */
+ desc->http_path = strdup(value);
+ if (desc->http_path == NULL)
+ goto fail;
+
+ desc->http_version = strchr(desc->http_path, ' ');
+ if (desc->http_version == NULL)
+ goto fail;
+
+ *desc->http_version++ = '\0';
+ desc->http_query = strchr(desc->http_path, '?');
+ if (desc->http_query != NULL)
+ *desc->http_query++ = '\0';
+
+ /*
+ * Have to allocate the strings because they could
+ * be changed independently by the filters later.
+ */
+ if ((desc->http_version =
+ strdup(desc->http_version)) == NULL)
+ goto fail;
+
+ if (desc->http_query != NULL &&
+ (desc->http_query =
+ strdup(desc->http_query)) == NULL)
+ goto fail;
+
+ } else if (desc->http_method != HTTP_METHOD_NONE &&
+ strcasecmp("Content-Length", key) == 0) {
+ if (desc->http_method == HTTP_METHOD_TRACE ||
+ desc->http_method == HTTP_METHOD_CONNECT) {
+ /*
+ * These method should not have a body
+ * and thus no Content-Length header.
+ */
+ server_abort_http(clt, 400, "malformed");
+ goto abort;
+ }
+
+ /*
+ * Need to read data from the client after the
+ * HTTP header.
+ * XXX What about non-standard clients not using
+ * the carriage return? And some browsers seem to
+ * include the line length in the content-length.
+ */
+ clt->clt_toread = strtonum(value, 0, LLONG_MAX,
+ &errstr);
+ if (errstr) {
+ server_abort_http(clt, 500, errstr);
+ goto abort;
+ }
+ if ((size_t)clt->clt_toread >
+ srv_conf->maxrequestbody) {
+ server_abort_http(clt, 413, NULL);
+ goto abort;
+ }
+ }
+
+ if (strcasecmp("Transfer-Encoding", key) == 0 &&
+ strcasecmp("chunked", value) == 0)
+ desc->http_chunked = 1;
+
+ if (clt->clt_line != 1) {
+ if ((hdr = kv_add(&desc->http_headers, key,
+ value)) == NULL)
+ goto fail;
+
+ desc->http_lastheader = hdr;
+ }
+
+ free(line);
+ }
+ if (clt->clt_done) {
+ if (desc->http_method == HTTP_METHOD_NONE) {
+ server_abort_http(clt, 406, "no method");
+ return;
+ }
+
+ switch (desc->http_method) {
+ case HTTP_METHOD_CONNECT:
+ /* Data stream */
+ clt->clt_toread = TOREAD_UNLIMITED;
+ bev->readcb = server_read;
+ break;
+ case HTTP_METHOD_DELETE:
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_HEAD:
+ case HTTP_METHOD_OPTIONS:
+ /* WebDAV methods */
+ case HTTP_METHOD_COPY:
+ clt->clt_toread = 0;
+ break;
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_PUT:
+ case HTTP_METHOD_RESPONSE:
+ /* WebDAV methods */
+ case HTTP_METHOD_PROPFIND:
+ case HTTP_METHOD_PROPPATCH:
+ case HTTP_METHOD_MKCOL:
+ case HTTP_METHOD_LOCK:
+ case HTTP_METHOD_UNLOCK:
+ case HTTP_METHOD_VERSION_CONTROL:
+ case HTTP_METHOD_REPORT:
+ case HTTP_METHOD_CHECKOUT:
+ case HTTP_METHOD_CHECKIN:
+ case HTTP_METHOD_UNCHECKOUT:
+ case HTTP_METHOD_MKWORKSPACE:
+ case HTTP_METHOD_UPDATE:
+ case HTTP_METHOD_LABEL:
+ case HTTP_METHOD_MERGE:
+ case HTTP_METHOD_BASELINE_CONTROL:
+ case HTTP_METHOD_MKACTIVITY:
+ case HTTP_METHOD_ORDERPATCH:
+ case HTTP_METHOD_ACL:
+ case HTTP_METHOD_MKREDIRECTREF:
+ case HTTP_METHOD_UPDATEREDIRECTREF:
+ case HTTP_METHOD_SEARCH:
+ case HTTP_METHOD_PATCH:
+ /* HTTP request payload */
+ if (clt->clt_toread > 0)
+ bev->readcb = server_read_httpcontent;
+
+ /* Single-pass HTTP body */
+ if (clt->clt_toread < 0) {
+ clt->clt_toread = TOREAD_UNLIMITED;
+ bev->readcb = server_read;
+ }
+ break;
+ default:
+ server_abort_http(clt, 405, "method not allowed");
+ return;
+ }
+ if (desc->http_chunked) {
+ /* Chunked transfer encoding */
+ clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
+ bev->readcb = server_read_httpchunks;
+ }
+
+ done:
+ if (clt->clt_toread != 0)
+ bufferevent_disable(bev, EV_READ);
+ server_response(env, clt);
+ return;
+ }
+ if (clt->clt_done) {
+ server_close(clt, "done");
+ return;
+ }
+ if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http)
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+ fail:
+ server_abort_http(clt, 500, strerror(errno));
+ abort:
+ free(line);
+}
+
+void
+server_read_httpcontent(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ size_t size;
+
+ getmonotime(&clt->clt_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+ clt->clt_id, size, clt->clt_toread);
+ if (!size)
+ return;
+
+ if (clt->clt_toread > 0) {
+ /* Read content data */
+ if ((off_t)size > clt->clt_toread) {
+ size = clt->clt_toread;
+ if (fcgi_add_stdin(clt, src) == -1)
+ goto fail;
+ clt->clt_toread = 0;
+ } else {
+ if (fcgi_add_stdin(clt, src) == -1)
+ goto fail;
+ clt->clt_toread -= size;
+ }
+ DPRINTF("%s: done, size %lu, to read %lld", __func__,
+ size, clt->clt_toread);
+ }
+ if (clt->clt_toread == 0) {
+ fcgi_add_stdin(clt, NULL);
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ bufferevent_disable(bev, EV_READ);
+ bev->readcb = server_read_http;
+ return;
+ }
+ if (clt->clt_done)
+ goto done;
+ if (bev->readcb != server_read_httpcontent)
+ bev->readcb(bev, arg);
+
+ return;
+ done:
+ return;
+ fail:
+ server_close(clt, strerror(errno));
+}
+
+void
+server_read_httpchunks(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char *line;
+ long long llval;
+ size_t size;
+
+ getmonotime(&clt->clt_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+ clt->clt_id, size, clt->clt_toread);
+ if (!size)
+ return;
+
+ if (clt->clt_toread > 0) {
+ /* Read chunk data */
+ if ((off_t)size > clt->clt_toread) {
+ size = clt->clt_toread;
+ if (server_bufferevent_write_chunk(clt, src, size)
+ == -1)
+ goto fail;
+ clt->clt_toread = 0;
+ } else {
+ if (server_bufferevent_write_buffer(clt, src) == -1)
+ goto fail;
+ clt->clt_toread -= size;
+ }
+ DPRINTF("%s: done, size %lu, to read %lld", __func__,
+ size, clt->clt_toread);
+ }
+ switch (clt->clt_toread) {
+ case TOREAD_HTTP_CHUNK_LENGTH:
+ line = evbuffer_readline(src);
+ if (line == NULL) {
+ /* Ignore empty line, continue */
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ if (strlen(line) == 0) {
+ free(line);
+ goto next;
+ }
+
+ /*
+ * Read prepended chunk size in hex, ignore the trailer.
+ * The returned signed value must not be negative.
+ */
+ if (sscanf(line, "%llx", &llval) != 1 || llval < 0) {
+ free(line);
+ server_close(clt, "invalid chunk size");
+ return;
+ }
+
+ if (server_bufferevent_print(clt, line) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ free(line);
+
+ if ((clt->clt_toread = llval) == 0) {
+ DPRINTF("%s: last chunk", __func__);
+ clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER;
+ }
+ break;
+ case TOREAD_HTTP_CHUNK_TRAILER:
+ /* Last chunk is 0 bytes followed by trailer and empty line */
+ line = evbuffer_readline(src);
+ if (line == NULL) {
+ /* Ignore empty line, continue */
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ if (server_bufferevent_print(clt, line) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ if (strlen(line) == 0) {
+ /* Switch to HTTP header mode */
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ bev->readcb = server_read_http;
+ }
+ free(line);
+ break;
+ case 0:
+ /* Chunk is terminated by an empty newline */
+ line = evbuffer_readline(src);
+ if (line != NULL)
+ free(line);
+ if (server_bufferevent_print(clt, "\r\n") == -1)
+ goto fail;
+ clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
+ break;
+ }
+
+ next:
+ if (clt->clt_done)
+ goto done;
+ if (EVBUFFER_LENGTH(src))
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+
+ done:
+ server_close(clt, "last http chunk read (done)");
+ return;
+ fail:
+ server_close(clt, strerror(errno));
+}
+
+void
+server_reset_http(struct client *clt)
+{
+ struct server *srv = clt->clt_srv;
+
+ server_log(clt, NULL);
+
+ server_httpdesc_free(clt->clt_descreq);
+ server_httpdesc_free(clt->clt_descresp);
+ clt->clt_headerlen = 0;
+ clt->clt_line = 0;
+ clt->clt_done = 0;
+ clt->clt_chunk = 0;
+ free(clt->clt_remote_user);
+ clt->clt_remote_user = NULL;
+ clt->clt_bev->readcb = server_read_http;
+ clt->clt_srv_conf = &srv->srv_conf;
+}
+
+ssize_t
+server_http_time(time_t t, char *tmbuf, size_t len)
+{
+ struct tm tm;
+
+ /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
+ if (t == -1 || gmtime_r(&t, &tm) == NULL)
+ return (-1);
+ else
+ return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
+}
+
+const char *
+server_http_host(struct sockaddr_storage *ss, char *buf, size_t len)
+{
+ char hbuf[HOST_NAME_MAX+1];
+ in_port_t port;
+
+ if (print_host(ss, buf, len) == NULL)
+ return (NULL);
+
+ port = ntohs(server_socket_getport(ss));
+ if (port == HTTP_PORT)
+ return (buf);
+
+ switch (ss->ss_family) {
+ case AF_INET:
+ if ((size_t)snprintf(hbuf, sizeof(hbuf),
+ "%s:%u", buf, port) >= sizeof(hbuf))
+ return (NULL);
+ break;
+ case AF_INET6:
+ if ((size_t)snprintf(hbuf, sizeof(hbuf),
+ "[%s]:%u", buf, port) >= sizeof(hbuf))
+ return (NULL);
+ break;
+ }
+
+ if (strlcpy(buf, hbuf, len) >= len)
+ return (NULL);
+
+ return (buf);
+}
+
+char *
+server_http_parsehost(char *host, char *buf, size_t len, int *portval)
+{
+ char *start, *end, *port;
+ const char *errstr = NULL;
+
+ if (strlcpy(buf, host, len) >= len) {
+ log_debug("%s: host name too long", __func__);
+ return (NULL);
+ }
+
+ start = buf;
+ end = port = NULL;
+
+ if (*start == '[' && (end = strchr(start, ']')) != NULL) {
+ /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
+ start++;
+ *end++ = '\0';
+ if ((port = strchr(end, ':')) == NULL || *port == '\0')
+ port = NULL;
+ else
+ port++;
+ memmove(buf, start, strlen(start) + 1);
+ } else if ((end = strchr(start, ':')) != NULL) {
+ /* Name or address with port, eg. www.example.com:80 */
+ *end++ = '\0';
+ port = end;
+ } else {
+ /* Name or address with default port, eg. www.example.com */
+ port = NULL;
+ }
+
+ if (port != NULL) {
+ /* Save the requested port */
+ *portval = strtonum(port, 0, 0xffff, &errstr);
+ if (errstr != NULL) {
+ log_debug("%s: invalid port: %s", __func__,
+ strerror(errno));
+ return (NULL);
+ }
+ *portval = htons(*portval);
+ } else {
+ /* Port not given, indicate the default port */
+ *portval = -1;
+ }
+
+ return (start);
+}
+
+void
+server_abort_http(struct client *clt, u_int code, const char *msg)
+{
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = &srv->srv_conf;
+ struct bufferevent *bev = clt->clt_bev;
+ struct http_descriptor *desc = clt->clt_descreq;
+ const char *httperr = NULL, *style;
+ char *httpmsg, *body = NULL, *extraheader = NULL;
+ char tmbuf[32], hbuf[128];
+ char buf[IBUF_READ_SIZE], *ptr = NULL;
+ int bodylen;
+
+ if (code == 0) {
+ server_close(clt, "dropped");
+ return;
+ }
+
+ if ((httperr = server_httperror_byid(code)) == NULL)
+ httperr = "Unknown Error";
+
+ if (bev == NULL)
+ goto done;
+
+ if (server_log_http(clt, code, 0) == -1)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
+ goto done;
+
+ /* Do not send details of the Internal Server Error */
+ switch (code) {
+ case 301:
+ case 302:
+ case 303:
+ if (msg == NULL)
+ break;
+ memset(buf, 0, sizeof(buf));
+ if ((ptr = server_expand_http(clt, msg,
+ buf, sizeof(buf))) == NULL)
+ goto done;
+ if ((ptr = url_encode(ptr)) == NULL)
+ goto done;
+ if (asprintf(&extraheader, "Location: %s\r\n", ptr) == -1) {
+ code = 500;
+ extraheader = NULL;
+ }
+ msg = ptr;
+ break;
+ case 401:
+ if (asprintf(&extraheader,
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n", msg) == -1) {
+ code = 500;
+ extraheader = NULL;
+ }
+ break;
+ default:
+ /*
+ * Do not send details of the error. Traditionally,
+ * web servers responsed with the request path on 40x
+ * errors which could be abused to inject JavaScript etc.
+ * Instead of sanitizing the path here, we just don't
+ * reprint it.
+ */
+ break;
+ }
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ style = "body { background-color: white; color: black; font-family: "
+ "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
+ "hr { border: 0; border-bottom: 1px dashed; }\n";
+
+ /* Generate simple HTML error document */
+ if ((bodylen = asprintf(&body,
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%03d %s</h1>\n"
+ "<hr>\n<address>%s</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1)
+ goto done;
+
+ /* Add basic HTTP headers */
+ if (asprintf(&httpmsg,
+ "HTTP/1.0 %03d %s\r\n"
+ "Date: %s\r\n"
+ "Server: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "Content-Length: %d\r\n"
+ "%s"
+ "\r\n"
+ "%s",
+ code, httperr, tmbuf, HTTPD_SERVERNAME, bodylen,
+ extraheader == NULL ? "" : extraheader,
+ desc->http_method == HTTP_METHOD_HEAD ? "" : body) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ server_dump(clt, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ free(body);
+ free(extraheader);
+ if (msg == NULL)
+ msg = "\"\"";
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
+ server_close(clt, msg);
+ } else {
+ server_close(clt, httpmsg);
+ free(httpmsg);
+ }
+ free(ptr);
+}
+
+void
+server_close_http(struct client *clt)
+{
+ struct http_descriptor *desc;
+
+ desc = clt->clt_descreq;
+ server_httpdesc_free(desc);
+ free(desc);
+ clt->clt_descreq = NULL;
+
+ desc = clt->clt_descresp;
+ server_httpdesc_free(desc);
+ free(desc);
+ clt->clt_descresp = NULL;
+ free(clt->clt_remote_user);
+ clt->clt_remote_user = NULL;
+}
+
+char *
+server_expand_http(struct client *clt, const char *val, char *buf,
+ size_t len)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ char ibuf[128], *str;
+
+ if (strlcpy(buf, val, len) >= len)
+ return (NULL);
+
+ if (strstr(val, "$DOCUMENT_URI") != NULL) {
+ if (expand_string(buf, len, "$DOCUMENT_URI",
+ desc->http_path) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$QUERY_STRING") != NULL) {
+ if (expand_string(buf, len, "$QUERY_STRING",
+ desc->http_query == NULL ? "" :
+ desc->http_query) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_") != NULL) {
+ if (strstr(val, "$REMOTE_ADDR") != NULL) {
+ if (print_host(&clt->clt_ss,
+ ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$REMOTE_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf),
+ "%u", ntohs(clt->clt_port));
+ if (expand_string(buf, len,
+ "$REMOTE_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_USER") != NULL) {
+ if ((srv_conf->flags & SRVFLAG_AUTH) &&
+ clt->clt_remote_user != NULL)
+ str = clt->clt_remote_user;
+ else
+ str = "";
+ if (expand_string(buf, len,
+ "$REMOTE_USER", str) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$REQUEST_URI") != NULL) {
+ if (desc->http_query == NULL) {
+ if ((str = strdup(desc->http_path)) == NULL)
+ return (NULL);
+ } else if (asprintf(&str, "%s?%s",
+ desc->http_path, desc->http_query) == -1)
+ return (NULL);
+ if (expand_string(buf, len, "$REQUEST_URI", str) != 0) {
+ free(str);
+ return (NULL);
+ }
+ free(str);
+ }
+ if (strstr(val, "$SERVER_") != NULL) {
+ if (strstr(val, "$SERVER_ADDR") != NULL) {
+ if (print_host(&srv_conf->ss,
+ ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$SERVER_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%u",
+ ntohs(srv_conf->port));
+ if (expand_string(buf, len,
+ "$SERVER_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_NAME") != NULL) {
+ if (expand_string(buf, len,
+ "$SERVER_NAME", srv_conf->name) != 0)
+ return (NULL);
+ }
+ }
+
+ return (buf);
+}
+
+int
+server_response(struct httpd *httpd, struct client *clt)
+{
+ char path[PATH_MAX];
+ char hostname[HOST_NAME_MAX+1];
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct http_descriptor *resp = clt->clt_descresp;
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = &srv->srv_conf;
+ struct kv *kv, key, *host;
+ int portval = -1;
+ char *hostval;
+
+ /* Canonicalize the request path */
+ if (desc->http_path == NULL ||
+ url_decode(desc->http_path) == NULL ||
+ canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
+ goto fail;
+ free(desc->http_path);
+ if ((desc->http_path = strdup(path)) == NULL)
+ goto fail;
+
+ key.kv_key = "Host";
+ if ((host = kv_find(&desc->http_headers, &key)) != NULL &&
+ host->kv_value == NULL)
+ host = NULL;
+
+ if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
+ /* Host header is mandatory */
+ if (host == NULL)
+ goto fail;
+
+ /* Is the connection persistent? */
+ key.kv_key = "Connection";
+ if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
+ strcasecmp("close", kv->kv_value) == 0)
+ clt->clt_persist = 0;
+ else
+ clt->clt_persist++;
+ } else {
+ /* Is the connection persistent? */
+ key.kv_key = "Connection";
+ if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
+ strcasecmp("keep-alive", kv->kv_value) == 0)
+ clt->clt_persist++;
+ else
+ clt->clt_persist = 0;
+ }
+
+ if (clt->clt_persist >= srv_conf->maxrequests)
+ clt->clt_persist = 0;
+
+ /*
+ * Do we have a Host header and matching configuration?
+ * XXX the Host can also appear in the URL path.
+ */
+ if (host != NULL) {
+ if ((hostval = server_http_parsehost(host->kv_value,
+ hostname, sizeof(hostname), &portval)) == NULL)
+ goto fail;
+
+ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
+#ifdef DEBUG
+ if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) {
+ DPRINTF("%s: virtual host \"%s:%u\""
+ " host \"%s\" (\"%s\")",
+ __func__, srv_conf->name,
+ ntohs(srv_conf->port), host->kv_value,
+ hostname);
+ }
+#endif
+ if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 &&
+ fnmatch(srv_conf->name, hostname,
+ FNM_CASEFOLD) == 0 &&
+ (portval == -1 ||
+ (portval != -1 && portval == srv_conf->port))) {
+ /* Replace host configuration */
+ clt->clt_srv_conf = srv_conf;
+ srv_conf = NULL;
+ break;
+ }
+ }
+ }
+
+ if (srv_conf != NULL) {
+ /* Use the actual server IP address */
+ if (server_http_host(&clt->clt_srv_ss, hostname,
+ sizeof(hostname)) == NULL)
+ goto fail;
+ } else {
+ /* Host header was valid and found */
+ if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
+ sizeof(hostname))
+ goto fail;
+ srv_conf = clt->clt_srv_conf;
+ }
+
+ if ((desc->http_host = strdup(hostname)) == NULL)
+ goto fail;
+
+ /* Now fill in the mandatory parts of the response descriptor */
+ resp->http_method = desc->http_method;
+ if ((resp->http_version = strdup(desc->http_version)) == NULL)
+ goto fail;
+
+ /* Now search for the location */
+ srv_conf = server_getlocation(clt, desc->http_path);
+
+ if (srv_conf->flags & SRVFLAG_BLOCK) {
+ server_abort_http(clt, srv_conf->return_code,
+ srv_conf->return_uri);
+ return (-1);
+ } else if (srv_conf->flags & SRVFLAG_AUTH &&
+ server_http_authenticate(srv_conf, clt) == -1) {
+ server_abort_http(clt, 401, srv_conf->auth_realm);
+ return (-1);
+ } else
+ return (server_file(httpd, clt));
+ fail:
+ server_abort_http(clt, 400, "bad request");
+ return (-1);
+}
+
+const char *
+server_root_strip(const char *path, int n)
+{
+ const char *p;
+
+ /* Strip strip leading directories. Leading '/' is ignored. */
+ for (; n > 0 && *path != '\0'; n--)
+ if ((p = strchr(++path, '/')) == NULL)
+ path = strchr(path, '\0');
+ else
+ path = p;
+
+ return (path);
+}
+
+struct server_config *
+server_getlocation(struct client *clt, const char *path)
+{
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = clt->clt_srv_conf, *location;
+
+ /* Now search for the location */
+ TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
+#ifdef DEBUG
+ if (location->flags & SRVFLAG_LOCATION) {
+ DPRINTF("%s: location \"%s\" path \"%s\"",
+ __func__, location->location, path);
+ }
+#endif
+ if ((location->flags & SRVFLAG_LOCATION) &&
+ location->parent_id == srv_conf->parent_id &&
+ fnmatch(location->location, path, FNM_CASEFOLD) == 0) {
+ /* Replace host configuration */
+ clt->clt_srv_conf = srv_conf = location;
+ break;
+ }
+ }
+
+ return (srv_conf);
+}
+
+int
+server_response_http(struct client *clt, u_int code,
+ struct media_type *media, size_t size, time_t mtime)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct http_descriptor *resp = clt->clt_descresp;
+ const char *error;
+ struct kv *ct, *cl;
+ char tmbuf[32];
+
+ if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
+ return (-1);
+
+ if (server_log_http(clt, code, size) == -1)
+ return (-1);
+
+ /* Add error codes */
+ if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
+ kv_set(&resp->http_pathquery, "%s", error) == -1)
+ return (-1);
+
+ /* Add headers */
+ if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
+ return (-1);
+
+ /* Is it a persistent connection? */
+ if (clt->clt_persist) {
+ if (kv_add(&resp->http_headers,
+ "Connection", "keep-alive") == NULL)
+ return (-1);
+ } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
+ return (-1);
+
+ /* Set media type */
+ if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
+ kv_set(ct, "%s/%s",
+ media == NULL ? "application" : media->media_type,
+ media == NULL ? "octet-stream" : media->media_subtype) == -1)
+ return (-1);
+
+ /* Set content length, if specified */
+ if ((cl =
+ kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
+ kv_set(cl, "%ld", size) == -1)
+ return (-1);
+
+ /* Set last modification time */
+ if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
+ kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
+ return (-1);
+
+ /* Date header is mandatory and should be added as late as possible */
+ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
+ kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
+ return (-1);
+
+ /* Write completed header */
+ if (server_writeresponse_http(clt) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1 ||
+ server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1)
+ return (-1);
+
+ if (size == 0 || resp->http_method == HTTP_METHOD_HEAD) {
+ bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+ if (clt->clt_persist)
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ else
+ clt->clt_toread = TOREAD_HTTP_NONE;
+ clt->clt_done = 0;
+ return (0);
+ }
+
+ return (1);
+}
+
+int
+server_writeresponse_http(struct client *clt)
+{
+ struct http_descriptor *desc = clt->clt_descresp;
+
+ DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
+ desc->http_rescode, desc->http_resmesg);
+
+ if (server_bufferevent_print(clt, desc->http_version) == -1 ||
+ server_bufferevent_print(clt, " ") == -1 ||
+ server_bufferevent_print(clt, desc->http_rescode) == -1 ||
+ server_bufferevent_print(clt, " ") == -1 ||
+ server_bufferevent_print(clt, desc->http_resmesg) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+server_writeheader_http(struct client *clt, struct kv *hdr, void *arg)
+{
+ char *ptr;
+ const char *key;
+
+ if (hdr->kv_flags & KV_FLAG_INVALID)
+ return (0);
+
+ /* The key might have been updated in the parent */
+ if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
+ key = hdr->kv_parent->kv_key;
+ else
+ key = hdr->kv_key;
+
+ ptr = hdr->kv_value;
+ if (server_bufferevent_print(clt, key) == -1 ||
+ (ptr != NULL &&
+ (server_bufferevent_print(clt, ": ") == -1 ||
+ server_bufferevent_print(clt, ptr) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1)))
+ return (-1);
+ DPRINTF("%s: %s: %s", __func__, key,
+ hdr->kv_value == NULL ? "" : hdr->kv_value);
+
+ return (0);
+}
+
+int
+server_headers(struct client *clt, void *descp,
+ int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
+{
+ struct kv *hdr, *kv;
+ struct http_descriptor *desc = descp;
+
+ RB_FOREACH(hdr, kvtree, &desc->http_headers) {
+ if ((hdr_cb)(clt, hdr, arg) == -1)
+ return (-1);
+ TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
+ if ((hdr_cb)(clt, kv, arg) == -1)
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+enum httpmethod
+server_httpmethod_byname(const char *name)
+{
+ enum httpmethod id = HTTP_METHOD_NONE;
+ struct http_method method, *res = NULL;
+
+ /* Set up key */
+ method.method_name = name;
+
+ if ((res = bsearch(&method, http_methods,
+ sizeof(http_methods) / sizeof(http_methods[0]) - 1,
+ sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL)
+ id = res->method_id;
+
+ return (id);
+}
+
+const char *
+server_httpmethod_byid(u_int id)
+{
+ const char *name = "<UNKNOWN>";
+ int i;
+
+ for (i = 0; http_methods[i].method_name != NULL; i++) {
+ if (http_methods[i].method_id == id) {
+ name = http_methods[i].method_name;
+ break;
+ }
+ }
+
+ return (name);
+}
+
+static int
+server_httpmethod_cmp(const void *a, const void *b)
+{
+ const struct http_method *ma = a;
+ const struct http_method *mb = b;
+
+ /*
+ * RFC 2616 section 5.1.1 says that the method is case
+ * sensitive so we don't do a strcasecmp here.
+ */
+ return (strcmp(ma->method_name, mb->method_name));
+}
+
+const char *
+server_httperror_byid(u_int id)
+{
+ struct http_error error, *res;
+
+ /* Set up key */
+ error.error_code = (int)id;
+
+ if ((res = bsearch(&error, http_errors,
+ sizeof(http_errors) / sizeof(http_errors[0]) - 1,
+ sizeof(http_errors[0]), server_httperror_cmp)) != NULL)
+ return (res->error_name);
+
+ return (NULL);
+}
+
+static int
+server_httperror_cmp(const void *a, const void *b)
+{
+ const struct http_error *ea = a;
+ const struct http_error *eb = b;
+ return (ea->error_code - eb->error_code);
+}
+
+int
+server_log_http(struct client *clt, u_int code, size_t len)
+{
+ static char tstamp[64];
+ static char ip[INET6_ADDRSTRLEN];
+ time_t t;
+ struct kv key, *agent, *referrer;
+ struct tm *tm;
+ struct server_config *srv_conf;
+ struct http_descriptor *desc;
+
+ if ((srv_conf = clt->clt_srv_conf) == NULL)
+ return (-1);
+ if ((srv_conf->flags & SRVFLAG_LOG) == 0)
+ return (0);
+ if ((desc = clt->clt_descreq) == NULL)
+ return (-1);
+
+ if ((t = time(NULL)) == -1)
+ return (-1);
+ if ((tm = localtime(&t)) == NULL)
+ return (-1);
+ if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0)
+ return (-1);
+
+ if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL)
+ return (-1);
+
+ /*
+ * For details on common log format, see:
+ * https://httpd.apache.org/docs/current/mod/mod_log_config.html
+ *
+ * httpd's format is similar to these Apache LogFormats:
+ * "%v %h %l %u %t \"%r\" %>s %B"
+ * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\""
+ */
+ switch (srv_conf->logformat) {
+ case LOG_FORMAT_COMMON:
+ if (evbuffer_add_printf(clt->clt_log,
+ "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
+ srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
+ clt->clt_remote_user, tstamp,
+ server_httpmethod_byid(desc->http_method),
+ desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_query == NULL ? "" : "?",
+ desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_version == NULL ? "" : " ",
+ desc->http_version == NULL ? "" : desc->http_version,
+ code, len) == -1)
+ return (-1);
+ break;
+
+ case LOG_FORMAT_COMBINED:
+ key.kv_key = "Referer"; /* sic */
+ if ((referrer = kv_find(&desc->http_headers, &key)) != NULL &&
+ referrer->kv_value == NULL)
+ referrer = NULL;
+
+ key.kv_key = "User-Agent";
+ if ((agent = kv_find(&desc->http_headers, &key)) != NULL &&
+ agent->kv_value == NULL)
+ agent = NULL;
+
+ if (evbuffer_add_printf(clt->clt_log,
+ "%s %s - %s [%s] \"%s %s%s%s%s%s\""
+ " %03d %zu \"%s\" \"%s\"\n",
+ srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
+ clt->clt_remote_user, tstamp,
+ server_httpmethod_byid(desc->http_method),
+ desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_query == NULL ? "" : "?",
+ desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_version == NULL ? "" : " ",
+ desc->http_version == NULL ? "" : desc->http_version,
+ code, len,
+ referrer == NULL ? "" : referrer->kv_value,
+ agent == NULL ? "" : agent->kv_value) == -1)
+ return (-1);
+ break;
+
+ case LOG_FORMAT_CONNECTION:
+ if (evbuffer_add_printf(clt->clt_log, " [%s]",
+ desc->http_path == NULL ? "" : desc->http_path) == -1)
+ return (-1);
+ break;
+ }
+
+ return (0);
+}