Add extended directory index options: "[no] index" and "[no] auto index".
authorreyk <reyk@openbsd.org>
Tue, 29 Jul 2014 16:17:28 +0000 (16:17 +0000)
committerreyk <reyk@openbsd.org>
Tue, 29 Jul 2014 16:17:28 +0000 (16:17 +0000)
The option "directory auto index" implements basic directory listing
and is turned off by default.

ok deraadt@

etc/examples/httpd.conf
usr.sbin/httpd/httpd.conf.5
usr.sbin/httpd/httpd.h
usr.sbin/httpd/parse.y
usr.sbin/httpd/server_file.c

index ab76a53..826f026 100644 (file)
@@ -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"
 }
 
index 2cb442f..7877f87 100644 (file)
@@ -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 <reyk@openbsd.org>
 .\"
@@ -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
index 7e19533..081e638 100644 (file)
@@ -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 <reyk@openbsd.org>
@@ -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;
index 1105e55..97485e3 100644 (file)
@@ -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 <reyk@openbsd.org>
@@ -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 <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.number>      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 },
index e19adb0..3d364ed 100644 (file)
@@ -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 <reyk@openbsd.org>
@@ -38,6 +38,8 @@
 #include <string.h>
 #include <unistd.h>
 #include <stdio.h>
+#include <dirent.h>
+#include <time.h>
 #include <err.h>
 #include <event.h>
 
 #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,
+           "<!DOCTYPE HTML PUBLIC "
+           "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+           "<html>\n"
+           "<head>\n"
+           "<title>Index of %s</title>\n"
+           "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+           "</head>\n"
+           "<body>\n"
+           "<h1>Index of %s</h1>\n"
+           "<hr>\n<pre>\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,
+                           "<a href=\"%s\">%s/</a>%*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,
+                           "<a href=\"%s\">%s</a>%*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,
+           "</pre>\n<hr>\n</body>\n</html>\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)
 {