From 43d7585dd007e91621b71e83727e5b9c2d804adb Mon Sep 17 00:00:00 2001 From: reyk Date: Tue, 29 Jul 2014 16:17:28 +0000 Subject: [PATCH] Add extended directory index options: "[no] index" and "[no] auto index". The option "directory auto index" implements basic directory listing and is turned off by default. ok deraadt@ --- etc/examples/httpd.conf | 3 +- usr.sbin/httpd/httpd.conf.5 | 17 +++- usr.sbin/httpd/httpd.h | 14 ++- usr.sbin/httpd/parse.y | 50 +++++++++- usr.sbin/httpd/server_file.c | 178 +++++++++++++++++++++++++++++++++-- 5 files changed, 245 insertions(+), 17 deletions(-) diff --git a/etc/examples/httpd.conf b/etc/examples/httpd.conf index ab76a53fe1c..826f0260f92 100644 --- a/etc/examples/httpd.conf +++ b/etc/examples/httpd.conf @@ -1,4 +1,4 @@ -# $OpenBSD: httpd.conf,v 1.2 2014/07/26 10:27:19 reyk Exp $ +# $OpenBSD: httpd.conf,v 1.3 2014/07/29 16:17:28 reyk Exp $ # Macros ext_addr="egress" @@ -20,6 +20,7 @@ server "www.example.com" { # Another server on a different internal IPv4 address server "intranet.example.com" { listen on 10.0.0.1 port 80 + directory { auto index, index "default.htm" } root "/htdocs/internet.example.com" } diff --git a/usr.sbin/httpd/httpd.conf.5 b/usr.sbin/httpd/httpd.conf.5 index 2cb442f2c86..7877f8769ca 100644 --- a/usr.sbin/httpd/httpd.conf.5 +++ b/usr.sbin/httpd/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.7 2014/07/25 17:49:11 reyk Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.8 2014/07/29 16:17:28 reyk Exp $ .\" .\" Copyright (c) 2014 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 25 2014 $ +.Dd $Mdocdate: July 29 2014 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -111,8 +111,19 @@ runs 3 server processes by default. .Sh SERVERS The configured web servers. .Pp -The following general table options are available: +The following general server options are available: .Bl -tag -width Ds +.It Ic directory Oo Ic no Oc Ic auto index +If no index file is found, automatically generate a directory listing. +This is disabled by default. +.It Ic directory Ic index Ar string +Set the directory index file. +If not specified, it defaults to +.Pa index.html . +.It Ic directory no index +Disable the directory index. +.Nm httpd +will neither display nor generate a directory index. .It Ic listen on Ar address Ic port Ar number Set the listen address and port. .It Ic root Ar directory diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h index 7e195336f58..081e6387231 100644 --- a/usr.sbin/httpd/httpd.h +++ b/usr.sbin/httpd/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.16 2014/07/29 12:16:36 reyk Exp $ */ +/* $OpenBSD: httpd.h,v 1.17 2014/07/29 16:17:28 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -272,6 +272,14 @@ struct client { }; SPLAY_HEAD(client_tree, client); +#define SRVFLAG_INDEX 0x01 +#define SRVFLAG_NO_INDEX 0x02 +#define SRVFLAG_AUTO_INDEX 0x04 +#define SRVFLAG_NO_AUTO_INDEX 0x08 + +#define SRVFLAG_BITS \ + "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" + #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 #define TCPFLAG_SACK 0x04 @@ -288,14 +296,16 @@ SPLAY_HEAD(client_tree, client); struct server_config { u_int32_t id; - u_int32_t flags; char name[MAXHOSTNAMELEN]; char docroot[MAXPATHLEN]; + char index[NAME_MAX]; + in_port_t port; struct sockaddr_storage ss; int prefixlen; struct timeval timeout; + u_int8_t flags; u_int8_t tcpflags; int tcpbufsiz; int tcpbacklog; diff --git a/usr.sbin/httpd/parse.y b/usr.sbin/httpd/parse.y index 1105e55c2a2..97485e31a12 100644 --- a/usr.sbin/httpd/parse.y +++ b/usr.sbin/httpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.7 2014/07/25 17:04:47 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.8 2014/07/29 16:17:28 reyk Exp $ */ /* * Copyright (c) 2007 - 2014 Reyk Floeter @@ -126,8 +126,9 @@ typedef struct { %} -%token ALL PORT LISTEN PREFORK ROOT SERVER ERROR LOG VERBOSE ON TYPES -%token UPDATES INCLUDE +%token ALL AUTO DIRECTORY INDEX LISTEN LOG NO ON PORT PREFORK ROOT SERVER +%token TYPES UPDATES VERBOSE +%token ERROR INCLUDE %token STRING %token NUMBER %type loglevel @@ -217,6 +218,8 @@ server : SERVER STRING { strlcpy(s->srv_conf.docroot, HTTPD_DOCROOT, sizeof(s->srv_conf.docroot)); + strlcpy(s->srv_conf.index, HTTPD_INDEX, + sizeof(s->srv_conf.index)); s->srv_conf.id = ++last_server_id; s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT; @@ -277,6 +280,38 @@ serveroptsl : LISTEN ON STRING port { } free($2); } + | DIRECTORY dirflags + | DIRECTORY '{' dirflags_l '}' + ; + +dirflags_l : dirflags comma dirflags_l + | dirflags + ; + +dirflags : INDEX STRING { + if (strlcpy(srv->srv_conf.index, $2, + sizeof(srv->srv_conf.index)) >= + sizeof(srv->srv_conf.index)) { + yyerror("index file too long"); + free($2); + YYERROR; + } + srv->srv_conf.flags &= ~SRVFLAG_NO_INDEX; + srv->srv_conf.flags |= SRVFLAG_INDEX; + free($2); + } + | NO INDEX { + srv->srv_conf.flags &= ~SRVFLAG_INDEX; + srv->srv_conf.flags |= SRVFLAG_NO_INDEX; + } + | AUTO INDEX { + srv->srv_conf.flags &= ~SRVFLAG_NO_AUTO_INDEX; + srv->srv_conf.flags |= SRVFLAG_AUTO_INDEX; + } + | NO AUTO INDEX { + srv->srv_conf.flags &= ~SRVFLAG_AUTO_INDEX; + srv->srv_conf.flags |= SRVFLAG_NO_AUTO_INDEX; + } ; types : TYPES '{' optnl mediaopts_l '}' @@ -364,6 +399,11 @@ loglevel : UPDATES { $$ = HTTPD_OPT_LOGUPDATE; } | ALL { $$ = HTTPD_OPT_LOGALL; } ; +comma : ',' + | nl + | /* empty */ + ; + optnl : '\n' optnl | ; @@ -406,9 +446,13 @@ lookup(char *s) /* this has to be sorted always */ static const struct keywords keywords[] = { { "all", ALL }, + { "auto", AUTO }, + { "directory", DIRECTORY }, { "include", INCLUDE }, + { "index", INDEX }, { "listen", LISTEN }, { "log", LOG }, + { "no", NO }, { "on", ON }, { "port", PORT }, { "prefork", PREFORK }, diff --git a/usr.sbin/httpd/server_file.c b/usr.sbin/httpd/server_file.c index e19adb08da4..3d364ed7ed2 100644 --- a/usr.sbin/httpd/server_file.c +++ b/usr.sbin/httpd/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.17 2014/07/26 22:38:38 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.18 2014/07/29 16:17:28 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include @@ -46,15 +48,20 @@ #include "httpd.h" #include "http.h" -int server_file_access(struct http_descriptor *, char *, size_t, +int server_file_access(struct client *, char *, size_t, struct stat *); +int server_file_index(struct httpd *, struct client *); void server_file_error(struct bufferevent *, short, void *); int -server_file_access(struct http_descriptor *desc, char *path, size_t len, +server_file_access(struct client *clt, char *path, size_t len, struct stat *st) { - char *newpath; + struct http_descriptor *desc = clt->clt_desc; + struct server_config *srv_conf = clt->clt_srv_conf; + struct stat stb; + char *newpath; + errno = 0; if (access(path, R_OK) == -1) { @@ -62,7 +69,11 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, } else if (stat(path, st) == -1) { goto fail; } else if (S_ISDIR(st->st_mode)) { - /* XXX Should we support directory listing? */ + /* Deny access if directory indexing is disabled */ + if (srv_conf->flags & SRVFLAG_NO_INDEX) { + errno = EACCES; + goto fail; + } if (!len) { /* Recursion - the index "file" is a directory? */ @@ -83,13 +94,27 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, } /* Otherwise append the default index file */ - if (strlcat(path, HTTPD_INDEX, len) >= len) { + if (strlcat(path, srv_conf->index, len) >= len) { errno = EACCES; goto fail; } /* Check again but set len to 0 to avoid recursion */ - return (server_file_access(desc, path, 0, st)); + if (server_file_access(clt, path, 0, &stb) == 404) { + /* + * Index file not found; fail if auto-indexing is + * not enabled, otherwise return success but + * indicate directory with S_ISDIR of the previous + * stat. + */ + if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { + errno = EACCES; + goto fail; + } + } 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; @@ -131,11 +156,16 @@ server_file(struct httpd *env, struct client *clt) } /* Returns HTTP status code on error */ - if ((ret = server_file_access(desc, path, sizeof(path), &st)) != 0) { + if ((ret = server_file_access(clt, path, sizeof(path), &st)) != 0) { server_abort_http(clt, ret, desc->http_path); 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; @@ -180,6 +210,138 @@ server_file(struct httpd *env, struct client *clt) return (-1); } +int +server_file_index(struct httpd *env, struct client *clt) +{ + char path[MAXPATHLEN]; + char tmstr[21]; + 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, namewidth, skip; + struct evbuffer *evb = NULL; + struct media_type *media; + const char *style; + struct stat st; + struct tm tm; + time_t t; + + /* 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; + + /* Indicate failure but continue going through the list */ + skip = 0; + + /* 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 */ + if (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) == -1)
+		skip = 1;
+
+	for (i = 0; i < namesize; i++) {
+		dp = namelist[i];
+
+		if (skip ||
+		    fstatat(fd, dp->d_name, &st, 0) == -1) {
+			free(dp);
+			continue;
+		}
+
+		t = st.st_mtime;
+		localtime_r(&t, &tm);			
+		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
+		namewidth = 51 - strlen(dp->d_name);
+
+		if (dp->d_name[0] == '.' &&
+		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
+			/* ignore hidden files starting with a dot */
+		} else if (dp->d_type == DT_DIR) {
+			namewidth -= 1; /* trailing slash */
+			if (evbuffer_add_printf(evb,
+			    "%s/%*s%s%20s\n",
+			    dp->d_name, dp->d_name,
+			    MAX(namewidth, 0), " ", tmstr, "-") == -1)
+				skip = 1;
+		} else if (dp->d_type == DT_REG) {
+			if (evbuffer_add_printf(evb,
+			    "%s%*s%s%20llu\n",
+			    dp->d_name, dp->d_name,
+			    MAX(namewidth, 0), " ", tmstr, st.st_size) == -1)
+				skip = 1;
+		}
+		free(dp);
+	}
+	free(namelist);
+
+	if (skip ||
+	    evbuffer_add_printf(evb,
+	    "
\n
\n\n\n") == -1) + goto fail; + + 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); +} + void server_file_error(struct bufferevent *bev, short error, void *arg) { -- 2.20.1