From b9ef26a6926417277e59cfd3c2b58df0596bab3a Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 27 Jul 2014 00:28:45 +0200 Subject: Add directory index --- config.c | 4 +- http.h | 10 +++-- httpd.c | 4 +- httpd.h | 7 ++- server.c | 30 +++++++++---- server_file.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- server_http.c | 94 ++++++++++++++++++++++++++++++++------- 7 files changed, 251 insertions(+), 39 deletions(-) diff --git a/config.c b/config.c index eb9e003..762a7a3 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.4 2014/07/25 16:23:19 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.5 2014/07/25 23:30:58 reyk Exp $ */ /* * Copyright (c) 2011 - 2014 Reyk Floeter @@ -249,7 +249,7 @@ config_getserver(struct httpd *env, struct imsg *imsg) /* Check if server with matching listening socket already exists */ if ((srv = server_byaddr((struct sockaddr *) - &srv_conf.ss)) != NULL) { + &srv_conf.ss, srv_conf.port)) != NULL) { /* Add "host" to existing listening server */ close(imsg->fd); return (config_getserver_config(env, diff --git a/http.h b/http.h index af2627f..994e98b 100644 --- a/http.h +++ b/http.h @@ -1,4 +1,4 @@ -/* $OpenBSD: http.h,v 1.3 2014/07/13 15:11:23 reyk Exp $ */ +/* $OpenBSD: http.h,v 1.4 2014/07/25 23:23:39 reyk Exp $ */ /* * Copyright (c) 2012 - 2014 Reyk Floeter @@ -19,6 +19,9 @@ #ifndef _HTTP_H #define _HTTP_H +#define HTTP_PORT 80 +#define HTTPS_PORT 443 + enum httpmethod { HTTP_METHOD_NONE = 0, @@ -148,13 +151,14 @@ struct http_descriptor { #define query_key http_matchquery.kv_key #define query_val http_matchquery.kv_value - char *http_version; + char http_host[MAXHOSTNAMELEN]; enum httpmethod http_method; int http_chunked; + char *http_version; /* A tree of headers and attached lists for repeated headers. */ - struct kvtree http_headers; struct kv *http_lastheader; + struct kvtree http_headers; }; #endif /* _HTTP_H */ diff --git a/httpd.c b/httpd.c index a1a8558..520ae5e 100644 --- a/httpd.c +++ b/httpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.c,v 1.8 2014/07/25 16:23:19 reyk Exp $ */ +/* $OpenBSD: httpd.c,v 1.10 2014/07/26 09:59:14 reyk Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -654,7 +654,7 @@ prefixlen2mask6(u_int8_t prefixlen, u_int32_t *mask) if (prefixlen > 128) prefixlen = 128; - bzero(&s6, sizeof(s6)); + memset(&s6, 0, sizeof(s6)); for (i = 0; i < prefixlen / 8; i++) s6.s6_addr[i] = 0xff; i = prefixlen % 8; diff --git a/httpd.h b/httpd.h index f955fe6..df34b47 100644 --- a/httpd.h +++ b/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.12 2014/07/25 16:23:19 reyk Exp $ */ +/* $OpenBSD: httpd.h,v 1.15 2014/07/25 23:30:58 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -256,6 +256,7 @@ struct client { void *clt_srv; void *clt_srv_conf; u_int32_t clt_srv_id; + struct sockaddr_storage clt_srv_ss; int clt_s; in_port_t clt_port; @@ -387,7 +388,7 @@ int server_bufferevent_add(struct event *, int); int server_bufferevent_write(struct client *, void *, size_t); void server_inflight_dec(struct client *, const char *); struct server * - server_byaddr(struct sockaddr *); + server_byaddr(struct sockaddr *, in_port_t); SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp); @@ -412,6 +413,8 @@ int server_response_http(struct client *, u_int, struct media_type *, void server_reset_http(struct client *); void server_close_http(struct client *); int server_response(struct httpd *, struct client *); +const char * + server_http_host(struct sockaddr_storage *, char *, size_t); /* server_file.c */ int server_file(struct httpd *, struct client *); diff --git a/server.c b/server.c index 21ccff2..3ef1401 100644 --- a/server.c +++ b/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.11 2014/07/25 16:23:19 reyk Exp $ */ +/* $OpenBSD: server.c,v 1.13 2014/07/25 23:30:58 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -182,12 +182,13 @@ server_purge(struct server *srv) } struct server * -server_byaddr(struct sockaddr *addr) +server_byaddr(struct sockaddr *addr, in_port_t port) { struct server *srv; TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { - if (sockaddr_cmp((struct sockaddr *)&srv->srv_conf.ss, + if (port == srv->srv_conf.port && + sockaddr_cmp((struct sockaddr *)&srv->srv_conf.ss, addr, srv->srv_conf.prefixlen) == 0) return (srv); } @@ -503,6 +504,19 @@ server_accept(int fd, short event, void *arg) clt->clt_srv_id = srv->srv_conf.id; clt->clt_pid = getpid(); clt->clt_inflight = 1; + + /* get local address */ + slen = sizeof(clt->clt_srv_ss); + if (getsockname(s, (struct sockaddr *)&clt->clt_srv_ss, + &slen) == -1) { + server_close(clt, "listen address lookup failed"); + return; + } + + /* get client address */ + memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss)); + + /* get ports */ switch (ss.ss_family) { case AF_INET: clt->clt_port = ((struct sockaddr_in *)&ss)->sin_port; @@ -511,7 +525,6 @@ server_accept(int fd, short event, void *arg) clt->clt_port = ((struct sockaddr_in6 *)&ss)->sin6_port; break; } - memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss)); getmonotime(&clt->clt_tv_start); memcpy(&clt->clt_tv_last, &clt->clt_tv_start, sizeof(clt->clt_tv_last)); @@ -571,7 +584,8 @@ server_inflight_dec(struct client *clt, const char *why) void server_close(struct client *clt, const char *msg) { - char ibuf[128], obuf[128], *ptr = NULL; + char ibuf[MAXHOSTNAMELEN], obuf[MAXHOSTNAMELEN]; + char *ptr = NULL; struct server *srv = clt->clt_srv; struct server_config *srv_conf = clt->clt_srv_conf; @@ -590,14 +604,14 @@ server_close(struct client *clt, const char *msg) memset(&ibuf, 0, sizeof(ibuf)); memset(&obuf, 0, sizeof(obuf)); (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf)); - (void)print_host(&srv_conf->ss, obuf, sizeof(obuf)); + (void)server_http_host(&clt->clt_srv_ss, obuf, sizeof(obuf)); if (EVBUFFER_LENGTH(clt->clt_log) && evbuffer_add_printf(clt->clt_log, "\r\n") != -1) ptr = evbuffer_readline(clt->clt_log); log_info("server %s, " - "client %d (%d active), %s -> %s:%d, " + "client %d (%d active), %s:%u -> %s, " "%s%s%s", srv_conf->name, clt->clt_id, server_clients, - ibuf, obuf, ntohs(clt->clt_port), msg, + ibuf, ntohs(clt->clt_port), obuf, msg, ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr); if (ptr != NULL) free(ptr); diff --git a/server_file.c b/server_file.c index a21c48c..e31dc82 100644 --- a/server_file.c +++ b/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.14 2014/07/25 20:13:06 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.16 2014/07/25 23:23:39 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -54,7 +55,9 @@ int server_file_access(struct http_descriptor *desc, char *path, size_t len, struct stat *st) { - char *newpath; + struct stat stb; + char *newpath; + errno = 0; if (access(path, R_OK) == -1) { @@ -72,7 +75,8 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, /* Redirect to path with trailing "/" */ if (path[strlen(path) - 1] != '/') { - if (asprintf(&newpath, "%s/", desc->http_path) == -1) + if (asprintf(&newpath, "http://%s%s/", + desc->http_host, desc->http_path) == -1) return (500); free(desc->http_path); desc->http_path = newpath; @@ -88,7 +92,15 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, } /* Check again but set len to 0 to avoid recursion */ - return (server_file_access(desc, path, 0, st)); + if (server_file_access(desc, path, 0, &stb) == 404) { + /* + * Index file not found, return success but + * indicate directory with S_ISDIR. + */ + } else { + /* return updated stat from index file */ + memcpy(st, &stb, sizeof(*st)); + } } else if (!S_ISREG(st->st_mode)) { /* Don't follow symlinks and ignore special files */ errno = EACCES; @@ -110,19 +122,131 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, /* NOTREACHED */ } +static int +server_file_index(struct httpd *env, struct client *clt) +{ + char path[MAXPATHLEN]; + struct http_descriptor *desc = clt->clt_desc; + struct server_config *srv_conf = clt->clt_srv_conf; + struct dirent **namelist, *dp; + int namesize, i, ret, fd = -1, width; + struct evbuffer *evb = NULL; + struct media_type *media; + const char *style; + struct stat st; + + /* Request path is already canonicalized */ + if ((size_t)snprintf(path, sizeof(path), "%s%s", + srv_conf->docroot, desc->http_path) >= sizeof(path)) + goto fail; + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto fail; + + /* File descriptor is opened, decrement inflight counter */ + server_inflight_dec(clt, __func__); + + if ((evb = evbuffer_new()) == NULL) + goto fail; + + if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) + goto fail; + + /* A CSS stylesheet allows minimal customization by the user */ + style = "body { background-color: white; color: black; font-family: " + "sans-serif; }"; + /* Generate simple HTML index document */ + evbuffer_add_printf(evb, + "\n" + "\n" + "\n" + "Index of %s\n" + "\n" + "\n" + "\n" + "

Index of %s

\n" + "
\n
\n",
+	    desc->http_path, style, desc->http_path);
+
+	for (i = 0; i < namesize; i++) {
+		dp = namelist[i];
+		if (dp->d_name[0] == '.' && dp->d_name[1] == '\0') {
+			/* ignore */
+		} else if (dp->d_type == DT_DIR) {
+			evbuffer_add_printf(evb,
+			    "%s/\n",
+			    dp->d_name, dp->d_name);
+		} else if (dp->d_type == DT_REG) {
+			if (fstatat(fd, dp->d_name, &st, 0) == -1) {
+				free(dp);
+				continue;
+			}
+			
+			width = 50 - strlen(dp->d_name);
+			evbuffer_add_printf(evb,
+			    "%s%*s%llu bytes\n",
+			    dp->d_name, dp->d_name,
+			    width < 0 ? 50 : width, "", st.st_size);
+		}
+		free(dp);
+	}
+	free(namelist);
+
+	evbuffer_add_printf(evb,
+	    "
\n
\n\n\n"); + + close(fd); + + media = media_find(env->sc_mediatypes, "index.html"); + ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb)); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + evbuffer_free(evb); + return (0); + default: + break; + } + + if (server_bufferevent_write_buffer(clt, evb) == -1) + goto fail; + evbuffer_free(evb); + + 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); + + fail: + if (fd != -1) + close(fd); + if (evb != NULL) + evbuffer_free(evb); + server_abort_http(clt, 500, desc->http_path); + return (-1); +} + int server_file(struct httpd *env, struct client *clt) { + char path[MAXPATHLEN]; struct http_descriptor *desc = clt->clt_desc; struct server_config *srv_conf = clt->clt_srv_conf; struct media_type *media; const char *errstr = NULL; int fd = -1, ret; - char path[MAXPATHLEN]; struct stat st; /* Request path is already canonicalized */ - if ((size_t)snprintf(path, sizeof(path), "%s/%s", + if ((size_t)snprintf(path, sizeof(path), "%s%s", srv_conf->docroot, desc->http_path) >= sizeof(path)) { /* Do not echo the uncanonicalized path */ server_abort_http(clt, 500, desc->http_path); @@ -135,6 +259,11 @@ server_file(struct httpd *env, struct client *clt) return (-1); } + if (S_ISDIR(st.st_mode)) { + /* list directory index */ + return (server_file_index(env, clt)); + } + /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) goto fail; diff --git a/server_http.c b/server_http.c index 58080a4..bfc5272 100644 --- a/server_http.c +++ b/server_http.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_http.c,v 1.14 2014/07/25 16:23:19 reyk Exp $ */ +/* $OpenBSD: server_http.c,v 1.19 2014/07/25 23:25:38 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -48,6 +48,7 @@ #include "httpd.h" #include "http.h" +static void server_http_date(char *, size_t); 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 *); @@ -505,6 +506,7 @@ void server_reset_http(struct client *clt) { struct http_descriptor *desc = clt->clt_desc; + struct server *srv = clt->clt_srv; server_httpdesc_free(desc); desc->http_method = 0; @@ -513,6 +515,51 @@ server_reset_http(struct client *clt) clt->clt_line = 0; clt->clt_done = 0; clt->clt_bev->readcb = server_read_http; + clt->clt_srv_conf = &srv->srv_conf; +} + +static void +server_http_date(char *tmbuf, size_t len) +{ + time_t t; + struct tm tm; + + /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ + time(&t); + gmtime_r(&t, &tm); + 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[MAXHOSTNAMELEN]; + 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); } void @@ -522,8 +569,6 @@ server_abort_http(struct client *clt, u_int code, const char *msg) struct bufferevent *bev = clt->clt_bev; const char *httperr = NULL, *text = ""; char *httpmsg, *extraheader = NULL; - time_t t; - struct tm *lt; char tmbuf[32], hbuf[128]; const char *style; @@ -537,12 +582,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg) if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL) goto done; - /* RFC 2616 "tolerates" asctime() */ - time(&t); - lt = localtime(&t); - tmbuf[0] = '\0'; - if (asctime_r(lt, tmbuf) != NULL) - tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */ + server_http_date(tmbuf, sizeof(tmbuf)); /* Do not send details of the Internal Server Error */ switch (code) { @@ -630,8 +670,8 @@ server_response(struct httpd *httpd, struct client *clt) char path[MAXPATHLEN]; struct http_descriptor *desc = clt->clt_desc; struct server *srv = clt->clt_srv; - struct server_config *srv_conf; - struct kv *kv, key; + struct server_config *srv_conf = &srv->srv_conf; + struct kv *kv, key, *host; int ret; /* Canonicalize the request path */ @@ -642,10 +682,14 @@ server_response(struct httpd *httpd, struct client *clt) 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 */ - key.kv_key = "Host"; - if ((kv = kv_find(&desc->http_headers, &key)) == NULL) + if (host == NULL) goto fail; /* Is the connection persistent? */ @@ -669,19 +713,31 @@ server_response(struct httpd *httpd, struct client *clt) * Do we have a Host header and matching configuration? * XXX the Host can also appear in the URL path. */ - key.kv_key = "Host"; - if ((kv = kv_find(&desc->http_headers, &key)) != NULL) { + if (host != NULL) { /* XXX maybe better to turn srv_hosts into a tree */ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { - if (fnmatch(srv_conf->name, kv->kv_value, + if (fnmatch(srv_conf->name, host->kv_value, FNM_CASEFOLD) == 0) { /* 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, desc->http_host, + sizeof(desc->http_host)) == NULL) + goto fail; + } else { + /* Host header was valid and found */ + if (strlcpy(desc->http_host, host->kv_value, + sizeof(desc->http_host)) >= sizeof(desc->http_host)) + goto fail; + } + if ((ret = server_file(httpd, clt)) == -1) return (-1); @@ -700,6 +756,7 @@ server_response_http(struct client *clt, u_int code, struct http_descriptor *desc = clt->clt_desc; const char *error; struct kv *ct, *cl; + char tmbuf[32]; if (desc == NULL || (error = server_httperror_byid(code)) == NULL) return (-1); @@ -736,6 +793,11 @@ server_response_http(struct client *clt, u_int code, kv_set(cl, "%ld", size) == -1)) return (-1); + /* Date header is mandatory and should be added last */ + server_http_date(tmbuf, sizeof(tmbuf)); + if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL) + return (-1); + /* Write completed header */ if (server_writeresponse_http(clt) == -1 || server_bufferevent_print(clt, "\r\n") == -1 || -- cgit v1.2.3-54-g00ecf