aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog2
-rw-r--r--gmid.13
-rw-r--r--gmid.c3
-rw-r--r--gmid.h15
-rw-r--r--lex.l1
-rw-r--r--parse.y3
-rwxr-xr-xregress/runtime18
-rw-r--r--sample.conf22
-rw-r--r--server.c225
9 files changed, 241 insertions, 51 deletions
diff --git a/ChangeLog b/ChangeLog
index b7f0515..1626a63 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,7 @@
2021-01-24 Omar Polo <op@omarpolo.com>
+ * server.c (open_dir): add directory listing (disabled by default)
+
* parse.y (vhost): added support for location blocks
* server.c (send_dir): make the directory index customizable
diff --git a/gmid.1 b/gmid.1
index 70ece21..ad0aeba 100644
--- a/gmid.1
+++ b/gmid.1
@@ -181,6 +181,9 @@ parameter will be added in the response.
Set the directory index file.
If not specified, it defaults to
.Pa index.gmi
+.It Ic auto Ic index Ar bool
+If no index file is found, automatically generate a directory listing.
+It's disabled by default.
.It Ic location Pa path Brq ...
Specify server configuration rules for a specific location.
The
diff --git a/gmid.c b/gmid.c
index 5bccefc..3e1ed33 100644
--- a/gmid.c
+++ b/gmid.c
@@ -156,6 +156,9 @@ starts_with(const char *str, const char *prefix)
{
size_t i;
+ if (prefix == NULL)
+ return 0;
+
for (i = 0; prefix[i] != '\0'; ++i)
if (str[i] != prefix[i])
return 0;
diff --git a/gmid.h b/gmid.h
index 139338e..2364e7c 100644
--- a/gmid.h
+++ b/gmid.h
@@ -17,10 +17,13 @@
#ifndef GMID_H
#define GMID_H
+#include <sys/socket.h>
+#include <sys/types.h>
+
#include <arpa/inet.h>
#include <netinet/in.h>
-#include <sys/socket.h>
+#include <dirent.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
@@ -62,6 +65,7 @@ struct location {
char *lang;
char *default_mime;
char *index;
+ int auto_index; /* 0 auto, -1 off, 1 on */
};
struct vhost {
@@ -119,6 +123,7 @@ enum {
S_OPEN,
S_INITIALIZING,
S_SENDING_FILE,
+ S_SENDING_DIR,
S_SENDING_CGI,
S_CLOSING,
};
@@ -134,6 +139,7 @@ struct client {
char sbuf[1024]; /* static buffer */
void *buf, *i; /* mmap buffer */
ssize_t len, off; /* mmap/static buffer */
+ DIR *dir;
struct sockaddr_storage addr;
struct vhost *host; /* host she's talking to */
};
@@ -185,8 +191,10 @@ const char *mime(struct vhost*, const char*);
const char *vhost_lang(struct vhost*, const char*);
const char *vhost_default_mime(struct vhost*, const char*);
const char *vhost_index(struct vhost*, const char*);
+int vhost_auto_index(struct vhost*, const char*);
int check_path(struct client*, const char*, int*);
void open_file(struct pollfd*, struct client*);
+void load_file(struct pollfd*, struct client*);
void check_for_cgi(char *, char*, struct pollfd*, struct client*);
void mark_nonblock(int);
void handle_handshake(struct pollfd*, struct client*);
@@ -194,7 +202,10 @@ void handle_open_conn(struct pollfd*, struct client*);
void start_reply(struct pollfd*, struct client*, int, const char*);
void start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*);
void send_file(struct pollfd*, struct client*);
-void send_dir(struct pollfd*, struct client*);
+void open_dir(struct pollfd*, struct client*);
+void redirect_canonical_dir(struct pollfd*, struct client*);
+int read_next_dir_entry(struct client*);
+void send_directory_listing(struct pollfd*, struct client*);
void cgi_poll_on_child(struct pollfd*, struct client*);
void cgi_poll_on_client(struct pollfd*, struct client*);
void handle_cgi(struct pollfd*, struct client*);
diff --git a/lex.l b/lex.l
index b785796..240f7c4 100644
--- a/lex.l
+++ b/lex.l
@@ -67,6 +67,7 @@ root return TROOT;
cgi return TCGI;
lang return TLANG;
index return TINDEX;
+auto return TAUTO;
[{}] return *yytext;
diff --git a/parse.y b/parse.y
index e7883a9..5e7cb21 100644
--- a/parse.y
+++ b/parse.y
@@ -46,7 +46,7 @@ extern void yyerror(const char*);
}
%token TDAEMON TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TSERVER
-%token TLOCATION TCERT TKEY TROOT TCGI TLANG
+%token TLOCATION TCERT TKEY TROOT TCGI TLANG TINDEX TAUTO
%token TERR
%token <str> TSTRING
@@ -138,4 +138,5 @@ locopt : TDEFAULT TTYPE TSTRING {
free(loc->index);
loc->index = $2;
}
+ | TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
;
diff --git a/regress/runtime b/regress/runtime
index 8cf2afb..ca87701 100755
--- a/regress/runtime
+++ b/regress/runtime
@@ -199,9 +199,25 @@ run
eq "$(head /dir/hello)" "20 text/plain" "Unexpected head for /"
echo OK GET /dir/hello with location and default type
-eq "$(head /dir/)" "20 text/plain" "Unexpected head for /dir"
+eq "$(head /dir/)" "20 text/plain" "Unexpected head for /dir/"
eq "$(get /dir/|tail -1)" 'echo "# hello world"' "Unexpected body for /dir/"
echo OK GET /dir/ with location and custom index
check "should be running"
quit
+
+config '' 'location "/dir/" { auto index on }'
+checkconf
+run
+
+eq "$(head /)" "20 text/gemini" "Unexpected head for /"
+eq "$(get /)" "# hello world$ln" "Unexpected body for /"
+echo OK GET / with auto index
+
+eq "$(head /dir)" "30 /dir/" "Unexpected head for /dir"
+eq "$(head /dir/)" "20 text/gemini" "Unexpected head for /dir/"
+eq "$(get /dir/|wc -l|xargs)" "3" "Unexpected body for /dir/"
+echo OK GET /dir/ with auto index on
+
+check "should be running"
+quit
diff --git a/sample.conf b/sample.conf
index d9bf46f..490bd2c 100644
--- a/sample.conf
+++ b/sample.conf
@@ -19,9 +19,25 @@ server "it.example.com" {
key "/path/to/key.pem"
root "/var/gemini/example.com"
- # enable CGI scripts in /cgi-bin/
+ # enable CGI scripts in /cgi-bin/
cgi "/cgi-bin/"
- # optional
- lang "it"
+ # optional
+ lang "it"
+}
+
+# a server block with a location
+server "foo.com" {
+ cert "..."
+ key "..."
+ root "..."
+
+ location "/it/" {
+ lang "it"
+ }
+
+ location "/files" {
+ lang "en"
+ auto index on
+ }
}
diff --git a/server.c b/server.c
index 48a7701..3c6b6e7 100644
--- a/server.c
+++ b/server.c
@@ -78,10 +78,27 @@ vhost_index(struct vhost *v, const char *path)
}
int
+vhost_auto_index(struct vhost *v, const char *path)
+{
+ struct location *loc;
+ int auto_index = 0;
+
+ for (loc = v->locations; loc->match != NULL; ++loc) {
+ if (!fnmatch(loc->match, path, 0)) {
+ if (loc->auto_index)
+ auto_index = loc->auto_index;
+ }
+ }
+
+ return auto_index == 1;
+}
+
+int
check_path(struct client *c, const char *path, int *fd)
{
struct stat sb;
const char *p;
+ int flags;
assert(path != NULL);
@@ -94,9 +111,10 @@ check_path(struct client *c, const char *path, int *fd)
else
p = path;
- if ((*fd = openat(c->host->dirfd, p, O_RDONLY | O_NOFOLLOW)) == -1) {
+ flags = O_RDONLY | O_NOFOLLOW;
+
+ if (*fd == -1 && (*fd = openat(c->host->dirfd, p, flags)) == -1)
return FILE_MISSING;
- }
if (fstat(*fd, &sb) == -1) {
LOGN(c, "failed stat for %s: %s", path, strerror(errno));
@@ -117,7 +135,7 @@ open_file(struct pollfd *fds, struct client *c)
{
switch (check_path(c, c->iri.path, &c->fd)) {
case FILE_EXECUTABLE:
- if (c->host->cgi != NULL && starts_with(c->iri.path, c->host->cgi)) {
+ if (starts_with(c->iri.path, c->host->cgi)) {
start_cgi(c->iri.path, "", c->iri.query, fds, c);
return;
}
@@ -125,27 +143,11 @@ open_file(struct pollfd *fds, struct client *c)
/* fallthrough */
case FILE_EXISTS:
- if ((c->len = filesize(c->fd)) == -1) {
- LOGE(c, "failed to get file size for %s", c->iri.path);
- close_conn(fds, c);
- return;
- }
-
- if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE,
- c->fd, 0)) == MAP_FAILED) {
- LOGW(c, "mmap: %s: %s", c->iri.path, strerror(errno));
- close_conn(fds, c);
- return;
- }
- c->i = c->buf;
- c->next = S_SENDING_FILE;
- start_reply(fds, c, SUCCESS, mime(c->host, c->iri.path));
+ load_file(fds, c);
return;
case FILE_DIRECTORY:
- close(c->fd);
- c->fd = -1;
- send_dir(fds, c);
+ open_dir(fds, c);
return;
case FILE_MISSING:
@@ -162,6 +164,25 @@ open_file(struct pollfd *fds, struct client *c)
}
}
+void
+load_file(struct pollfd *fds, struct client *c)
+{
+ if ((c->len = filesize(c->fd)) == -1) {
+ LOGE(c, "failed to get file size for %s", c->iri.path);
+ start_reply(fds, c, TEMP_FAILURE, "internal server error");
+ return;
+ }
+
+ if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE,
+ c->fd, 0)) == MAP_FAILED) {
+ LOGW(c, "mmap: %s: %s", c->iri.path, strerror(errno));
+ start_reply(fds, c, TEMP_FAILURE, "internal server error");
+ return;
+ }
+ c->i = c->buf;
+ c->next = S_SENDING_FILE;
+ start_reply(fds, c, SUCCESS, mime(c->host, c->iri.path));
+}
/*
* the inverse of this algorithm, i.e. starting from the start of the
@@ -434,49 +455,157 @@ send_file(struct pollfd *fds, struct client *c)
}
void
-send_dir(struct pollfd *fds, struct client *c)
+open_dir(struct pollfd *fds, struct client *c)
{
size_t len;
+ int dirfd;
+ char *before_file;
- /* guard against a re-entrant call: open_file -> send_dir ->
- * open_file -> send_dir. This can happen only if:
- *
- * - user requested a dir, say foo/
- * - we try to serve foo/$INDEX
- * - foo/$INDEX is a directory.
- */
- if (c->iri.path == c->sbuf) {
- start_reply(fds, c, TEMP_REDIRECT, c->sbuf);
+ len = strlen(c->iri.path);
+ if (len > 0 && !ends_with(c->iri.path, "/")) {
+ redirect_canonical_dir(fds, c);
return;
}
strlcpy(c->sbuf, "/", sizeof(c->sbuf));
-
- len = strlen(c->iri.path);
- if (len > 0 && c->iri.path[len-1] != '/') {
- /* redirect to url with the trailing / */
- strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
+ strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
+ if (!ends_with(c->sbuf, "/"))
strlcat(c->sbuf, "/", sizeof(c->sbuf));
+ before_file = strchr(c->sbuf, '\0');
+ len = strlcat(c->sbuf, vhost_index(c->host, c->iri.path),
+ sizeof(c->sbuf));
+ if (len >= sizeof(c->sbuf)) {
+ start_reply(fds, c, TEMP_FAILURE, "internal server error");
+ return;
+ }
+
+ c->iri.path = c->sbuf;
+
+ /* close later unless we have to generate the dir listing */
+ dirfd = c->fd;
+ c->fd = -1;
+
+ switch (check_path(c, c->iri.path, &c->fd)) {
+ case FILE_EXECUTABLE:
+ if (starts_with(c->iri.path, c->host->cgi)) {
+ start_cgi(c->iri.path, "", c->iri.query, fds, c);
+ break;
+ }
+
+ /* fallthrough */
+
+ case FILE_EXISTS:
+ load_file(fds, c);
+ break;
+
+ case FILE_DIRECTORY:
start_reply(fds, c, TEMP_REDIRECT, c->sbuf);
+ break;
+
+ case FILE_MISSING:
+ *before_file = '\0';
+
+ if (!vhost_auto_index(c->host, c->iri.path)) {
+ start_reply(fds, c, NOT_FOUND, "not found");
+ break;
+ }
+
+ c->fd = dirfd;
+ c->next = S_SENDING_DIR;
+
+ if ((c->dir = fdopendir(c->fd)) == NULL) {
+ LOGE(c, "can't fdopendir(%d) (vhost:%s) %s: %s",
+ c->fd, c->host->domain, c->iri.path, strerror(errno));
+ start_reply(fds, c, TEMP_FAILURE, "internal server error");
+ return;
+ }
+ c->off = 0;
+
+ start_reply(fds, c, SUCCESS, "text/gemini");
return;
+
+ default:
+ /* unreachable */
+ abort();
}
- strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
+ close(dirfd);
+}
- if (!ends_with(c->sbuf, "/"))
- strlcat(c->sbuf, "/", sizeof(c->sbuf));
+void
+redirect_canonical_dir(struct pollfd *fds, struct client *c)
+{
+ size_t len;
- len = strlcat(c->sbuf, vhost_index(c->host, c->iri.path),
- sizeof(c->sbuf));
+ strlcpy(c->sbuf, "/", sizeof(c->sbuf));
+ strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
+ len = strlcat(c->sbuf, "/", sizeof(c->sbuf));
if (len >= sizeof(c->sbuf)) {
start_reply(fds, c, TEMP_FAILURE, "internal server error");
return;
}
- close(c->fd);
- c->iri.path = c->sbuf;
- open_file(fds, c);
+ start_reply(fds, c, TEMP_REDIRECT, c->sbuf);
+}
+
+int
+read_next_dir_entry(struct client *c)
+{
+ struct dirent *d;
+
+ do {
+ errno = 0;
+ if ((d = readdir(c->dir)) == NULL) {
+ if (errno != 0)
+ LOGE(c, "readdir: %s", strerror(errno));
+ return 0;
+ }
+ } while (!strcmp(d->d_name, "."));
+
+ /* XXX: url escape */
+ snprintf(c->sbuf, sizeof(c->sbuf), "=> %s %s\n",
+ d->d_name, d->d_name);
+ c->len = strlen(c->sbuf);
+ c->off = 0;
+
+ return 1;
+}
+
+void
+send_directory_listing(struct pollfd *fds, struct client *c)
+{
+ ssize_t r;
+
+ while (1) {
+ if (c->len == 0) {
+ if (!read_next_dir_entry(c))
+ goto end;
+ }
+
+ while (c->len > 0) {
+ switch (r = tls_write(c->ctx, c->sbuf + c->off, c->len)) {
+ case -1:
+ goto end;
+
+ case TLS_WANT_POLLOUT:
+ fds->events = POLLOUT;
+ return;
+
+ case TLS_WANT_POLLIN:
+ fds->events = POLLIN;
+ return;
+
+ default:
+ c->off += r;
+ c->len -= r;
+ break;
+ }
+ }
+ }
+
+end:
+ close_conn(fds, c);
}
void
@@ -624,6 +753,9 @@ close_conn(struct pollfd *pfd, struct client *c)
if (c->fd != -1)
close(c->fd);
+ if (c->dir != NULL)
+ closedir(c->dir);
+
close(pfd->fd);
pfd->fd = -1;
}
@@ -658,6 +790,7 @@ do_accept(int sock, struct tls *ctx, struct pollfd *fds, struct client *clients)
clients[i].fd = -1;
clients[i].waiting_on_child = 0;
clients[i].buf = MAP_FAILED;
+ clients[i].dir = NULL;
clients[i].addr = addr;
connected_clients++;
@@ -688,6 +821,10 @@ handle(struct pollfd *fds, struct client *client)
send_file(fds, client);
break;
+ case S_SENDING_DIR:
+ send_directory_listing(fds, client);
+ break;
+
case S_SENDING_CGI:
handle_cgi(fds, client);
break;