added support for location blocks

This commit is contained in:
Omar Polo 2021-01-24 18:53:26 +00:00
parent c8b7433918
commit 252908e6bb
9 changed files with 243 additions and 53 deletions

View File

@ -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

3
gmid.1
View File

@ -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

3
gmid.c
View File

@ -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;

15
gmid.h
View File

@ -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*);

1
lex.l
View File

@ -67,6 +67,7 @@ root return TROOT;
cgi return TCGI;
lang return TLANG;
index return TINDEX;
auto return TAUTO;
[{}] return *yytext;

View File

@ -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; }
;

View File

@ -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

View File

@ -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
}
}

229
server.c
View File

@ -77,11 +77,28 @@ vhost_index(struct vhost *v, const char *path)
return index;
}
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));
start_reply(fds, c, TEMP_REDIRECT, 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();
}
close(dirfd);
}
void
redirect_canonical_dir(struct pollfd *fds, struct client *c)
{
size_t len;
strlcpy(c->sbuf, "/", sizeof(c->sbuf));
strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
if (!ends_with(c->sbuf, "/"))
strlcat(c->sbuf, "/", sizeof(c->sbuf));
len = strlcat(c->sbuf, vhost_index(c->host, 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;