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> 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 * parse.y (vhost): added support for location blocks
* server.c (send_dir): make the directory index customizable * 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. Set the directory index file.
If not specified, it defaults to If not specified, it defaults to
.Pa index.gmi .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 ... .It Ic location Pa path Brq ...
Specify server configuration rules for a specific location. Specify server configuration rules for a specific location.
The The

3
gmid.c
View File

@ -156,6 +156,9 @@ starts_with(const char *str, const char *prefix)
{ {
size_t i; size_t i;
if (prefix == NULL)
return 0;
for (i = 0; prefix[i] != '\0'; ++i) for (i = 0; prefix[i] != '\0'; ++i)
if (str[i] != prefix[i]) if (str[i] != prefix[i])
return 0; return 0;

15
gmid.h
View File

@ -17,10 +17,13 @@
#ifndef GMID_H #ifndef GMID_H
#define GMID_H #define GMID_H
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h>
#include <dirent.h>
#include <poll.h> #include <poll.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -62,6 +65,7 @@ struct location {
char *lang; char *lang;
char *default_mime; char *default_mime;
char *index; char *index;
int auto_index; /* 0 auto, -1 off, 1 on */
}; };
struct vhost { struct vhost {
@ -119,6 +123,7 @@ enum {
S_OPEN, S_OPEN,
S_INITIALIZING, S_INITIALIZING,
S_SENDING_FILE, S_SENDING_FILE,
S_SENDING_DIR,
S_SENDING_CGI, S_SENDING_CGI,
S_CLOSING, S_CLOSING,
}; };
@ -134,6 +139,7 @@ struct client {
char sbuf[1024]; /* static buffer */ char sbuf[1024]; /* static buffer */
void *buf, *i; /* mmap buffer */ void *buf, *i; /* mmap buffer */
ssize_t len, off; /* mmap/static buffer */ ssize_t len, off; /* mmap/static buffer */
DIR *dir;
struct sockaddr_storage addr; struct sockaddr_storage addr;
struct vhost *host; /* host she's talking to */ 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_lang(struct vhost*, const char*);
const char *vhost_default_mime(struct vhost*, const char*); const char *vhost_default_mime(struct vhost*, const char*);
const char *vhost_index(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*); int check_path(struct client*, const char*, int*);
void open_file(struct pollfd*, struct client*); 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 check_for_cgi(char *, char*, struct pollfd*, struct client*);
void mark_nonblock(int); void mark_nonblock(int);
void handle_handshake(struct pollfd*, struct client*); 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_reply(struct pollfd*, struct client*, int, const char*);
void start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*); void start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*);
void send_file(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_child(struct pollfd*, struct client*);
void cgi_poll_on_client(struct pollfd*, struct client*); void cgi_poll_on_client(struct pollfd*, struct client*);
void handle_cgi(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; cgi return TCGI;
lang return TLANG; lang return TLANG;
index return TINDEX; index return TINDEX;
auto return TAUTO;
[{}] return *yytext; [{}] return *yytext;

View File

@ -46,7 +46,7 @@ extern void yyerror(const char*);
} }
%token TDAEMON TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TSERVER %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 TERR
%token <str> TSTRING %token <str> TSTRING
@ -138,4 +138,5 @@ locopt : TDEFAULT TTYPE TSTRING {
free(loc->index); free(loc->index);
loc->index = $2; 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 /" eq "$(head /dir/hello)" "20 text/plain" "Unexpected head for /"
echo OK GET /dir/hello with location and default type 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/" eq "$(get /dir/|tail -1)" 'echo "# hello world"' "Unexpected body for /dir/"
echo OK GET /dir/ with location and custom index echo OK GET /dir/ with location and custom index
check "should be running" check "should be running"
quit 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

@ -25,3 +25,19 @@ server "it.example.com" {
# optional # optional
lang "it" 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
}
}

227
server.c
View File

@ -77,11 +77,28 @@ vhost_index(struct vhost *v, const char *path)
return index; 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 int
check_path(struct client *c, const char *path, int *fd) check_path(struct client *c, const char *path, int *fd)
{ {
struct stat sb; struct stat sb;
const char *p; const char *p;
int flags;
assert(path != NULL); assert(path != NULL);
@ -94,9 +111,10 @@ check_path(struct client *c, const char *path, int *fd)
else else
p = path; 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; return FILE_MISSING;
}
if (fstat(*fd, &sb) == -1) { if (fstat(*fd, &sb) == -1) {
LOGN(c, "failed stat for %s: %s", path, strerror(errno)); 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)) { switch (check_path(c, c->iri.path, &c->fd)) {
case FILE_EXECUTABLE: 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); start_cgi(c->iri.path, "", c->iri.query, fds, c);
return; return;
} }
@ -125,27 +143,11 @@ open_file(struct pollfd *fds, struct client *c)
/* fallthrough */ /* fallthrough */
case FILE_EXISTS: case FILE_EXISTS:
if ((c->len = filesize(c->fd)) == -1) { load_file(fds, c);
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));
return; return;
case FILE_DIRECTORY: case FILE_DIRECTORY:
close(c->fd); open_dir(fds, c);
c->fd = -1;
send_dir(fds, c);
return; return;
case FILE_MISSING: 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 * 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 void
send_dir(struct pollfd *fds, struct client *c) open_dir(struct pollfd *fds, struct client *c)
{ {
size_t len; size_t len;
int dirfd;
char *before_file;
/* guard against a re-entrant call: open_file -> send_dir -> len = strlen(c->iri.path);
* open_file -> send_dir. This can happen only if: if (len > 0 && !ends_with(c->iri.path, "/")) {
* redirect_canonical_dir(fds, c);
* - 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);
return; return;
} }
strlcpy(c->sbuf, "/", sizeof(c->sbuf)); 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)); 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; 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)); strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf));
len = strlcat(c->sbuf, "/", 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));
if (len >= sizeof(c->sbuf)) { if (len >= sizeof(c->sbuf)) {
start_reply(fds, c, TEMP_FAILURE, "internal server error"); start_reply(fds, c, TEMP_FAILURE, "internal server error");
return; return;
} }
close(c->fd); start_reply(fds, c, TEMP_REDIRECT, c->sbuf);
c->iri.path = c->sbuf; }
open_file(fds, c);
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 void
@ -624,6 +753,9 @@ close_conn(struct pollfd *pfd, struct client *c)
if (c->fd != -1) if (c->fd != -1)
close(c->fd); close(c->fd);
if (c->dir != NULL)
closedir(c->dir);
close(pfd->fd); close(pfd->fd);
pfd->fd = -1; 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].fd = -1;
clients[i].waiting_on_child = 0; clients[i].waiting_on_child = 0;
clients[i].buf = MAP_FAILED; clients[i].buf = MAP_FAILED;
clients[i].dir = NULL;
clients[i].addr = addr; clients[i].addr = addr;
connected_clients++; connected_clients++;
@ -688,6 +821,10 @@ handle(struct pollfd *fds, struct client *client)
send_file(fds, client); send_file(fds, client);
break; break;
case S_SENDING_DIR:
send_directory_listing(fds, client);
break;
case S_SENDING_CGI: case S_SENDING_CGI:
handle_cgi(fds, client); handle_cgi(fds, client);
break; break;