mirror of https://github.com/omar-polo/gmid.git
get rid of the CGI support
I really want to get rid of the `executor' process hack for CGI scripts and its escalation to allow fastcgi and proxying to work on non-OpenBSD. This drops the CGI support and the `executor' process entirely and is the first step towards gmid 2.0. It also allows to have more secure defaults. On non-OpenBSD systems this means that the sandbox will be deactivated as soon as fastcgi or proxying are used: you can't open sockets under FreeBSD' capsicum(4) and I don't want to go thru the pain of making it work under linux' seccomp/landlock. Patches are always welcome however. For folks using CGI scripts (hey, I'm one of you!) not all hope is lost: fcgiwrap or OpenBSD' slowcgi(8) are ways to run CGI scripts as they were FastCGI applications. fixes for the documentation and to the non-OpenBSD sandboxes will follow.
This commit is contained in:
parent
5df699d1ab
commit
d29a2ee224
1
Makefile
1
Makefile
|
@ -63,7 +63,6 @@ COMPATS = compat/err.c \
|
|||
compat/vasprintf.c
|
||||
|
||||
GMID_SRCS = dirs.c \
|
||||
ex.c \
|
||||
fcgi.c \
|
||||
gmid.c \
|
||||
iri.c \
|
||||
|
|
|
@ -16,7 +16,7 @@ featureful server.
|
|||
- IRI support (RFC3987)
|
||||
- automatic certificate generation for config-less mode
|
||||
- reverse proxying
|
||||
- CGI and FastCGI support
|
||||
- FastCGI support
|
||||
- virtual hosts
|
||||
- location rules
|
||||
- event-based asynchronous I/O model
|
||||
|
@ -75,9 +75,6 @@ server "example.com" {
|
|||
# lang for text/gemini files
|
||||
lang "en"
|
||||
|
||||
# execute CGI scripts in /cgi/
|
||||
cgi "/cgi/*"
|
||||
|
||||
# only for locations that matches /files/*
|
||||
location "/files/*" {
|
||||
# generate directory listings
|
||||
|
@ -141,6 +138,9 @@ to the `contrib` directory.
|
|||
|
||||
## Architecture/Security considerations
|
||||
|
||||
**outdated: revisit for gmid 2.0**
|
||||
|
||||
|
||||
gmid is composed by four processes: the parent process, the logger,
|
||||
the listener and the executor. The parent process is the only one
|
||||
that doesn't drop privileges, but all it does is to wait for a SIGHUP
|
||||
|
|
531
ex.c
531
ex.c
|
@ -1,531 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Omar Polo <op@omarpolo.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "gmid.h"
|
||||
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
static void handle_imsg_cgi_req(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_imsg_fcgi_req(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_imsg_conn_req(struct imsgbuf *, struct imsg *, size_t);
|
||||
static void handle_imsg_quit(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_dispatch_imsg(int, short, void*);
|
||||
|
||||
static imsg_handlerfn *handlers[] = {
|
||||
[IMSG_FCGI_REQ] = handle_imsg_fcgi_req,
|
||||
[IMSG_CGI_REQ] = handle_imsg_cgi_req,
|
||||
[IMSG_CONN_REQ] = handle_imsg_conn_req,
|
||||
[IMSG_QUIT] = handle_imsg_quit,
|
||||
};
|
||||
|
||||
static inline void
|
||||
safe_setenv(const char *name, const char *val)
|
||||
{
|
||||
if (val == NULL)
|
||||
val = "";
|
||||
setenv(name, val, 1);
|
||||
}
|
||||
|
||||
static char *
|
||||
xasprintf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *s;
|
||||
|
||||
va_start(ap, fmt);
|
||||
if (vasprintf(&s, fmt, ap) == -1)
|
||||
s = NULL;
|
||||
va_end(ap);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void
|
||||
do_exec(const char *ex, const char *spath, char *query)
|
||||
{
|
||||
char **argv, buf[PATH_MAX], *sname, *t;
|
||||
size_t i, n;
|
||||
|
||||
/* restore the default handlers */
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
signal(SIGHUP, SIG_DFL);
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
|
||||
strlcpy(buf, spath, sizeof(buf));
|
||||
sname = basename(buf);
|
||||
|
||||
if (query == NULL || strchr(query, '=') != NULL) {
|
||||
if ((argv = calloc(2, sizeof(char*))) == NULL)
|
||||
err(1, "calloc");
|
||||
argv[0] = sname;
|
||||
execvp(ex, argv);
|
||||
warn("execvp: %s", argv[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
n = 1;
|
||||
for (t = query ;; t++, n++) {
|
||||
if ((t = strchr(t, '+')) == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if ((argv = calloc(n+2, sizeof(char*))) == NULL)
|
||||
err(1, "calloc");
|
||||
|
||||
argv[0] = sname;
|
||||
for (i = 0; i < n; ++i) {
|
||||
t = strchr(query, '+');
|
||||
if (t != NULL)
|
||||
*t = '\0';
|
||||
argv[i+1] = pct_decode_str(query);
|
||||
query = t+1;
|
||||
}
|
||||
|
||||
execvp(ex, argv);
|
||||
warn("execvp: %s", argv[0]);
|
||||
}
|
||||
|
||||
static inline void
|
||||
setenv_time(const char *var, time_t t)
|
||||
{
|
||||
char timebuf[21];
|
||||
struct tm tminfo;
|
||||
|
||||
if (t == -1)
|
||||
return;
|
||||
|
||||
strftime(timebuf, sizeof(timebuf), "%FT%TZ",
|
||||
gmtime_r(&t, &tminfo));
|
||||
setenv(var, timebuf, 1);
|
||||
}
|
||||
|
||||
/* fd or -1 on error */
|
||||
static int
|
||||
launch_cgi(struct iri *iri, struct cgireq *req, struct vhost *vhost,
|
||||
struct location *loc)
|
||||
{
|
||||
int p[2], errp[2]; /* read end, write end */
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
|
||||
return -1;
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, errp) == -1)
|
||||
return -1;
|
||||
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
log_err(NULL, "fork failed: %s", strerror(errno));
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
close(errp[0]);
|
||||
close(errp[1]);
|
||||
return -1;
|
||||
|
||||
case 0: { /* child */
|
||||
char *ex, *pwd;
|
||||
char iribuf[GEMINI_URL_LEN];
|
||||
char path[PATH_MAX];
|
||||
struct envlist *e;
|
||||
|
||||
close(p[0]);
|
||||
if (dup2(p[1], 1) == -1)
|
||||
goto childerr;
|
||||
|
||||
close(errp[0]);
|
||||
if (dup2(errp[1], 2) == -1)
|
||||
goto childerr;
|
||||
|
||||
ex = xasprintf("%s/%s", loc->dir, req->spath);
|
||||
|
||||
serialize_iri(iri, iribuf, sizeof(iribuf));
|
||||
|
||||
safe_setenv("GATEWAY_INTERFACE", "CGI/1.1");
|
||||
safe_setenv("GEMINI_DOCUMENT_ROOT", loc->dir);
|
||||
safe_setenv("GEMINI_SCRIPT_FILENAME",
|
||||
xasprintf("%s/%s", loc->dir, req->spath));
|
||||
safe_setenv("GEMINI_URL", iribuf);
|
||||
|
||||
strlcpy(path, "/", sizeof(path));
|
||||
strlcat(path, req->spath, sizeof(path));
|
||||
safe_setenv("GEMINI_URL_PATH", path);
|
||||
|
||||
if (*req->relpath != '\0') {
|
||||
strlcpy(path, "/", sizeof(path));
|
||||
strlcat(path, req->relpath, sizeof(path));
|
||||
safe_setenv("PATH_INFO", path);
|
||||
|
||||
strlcpy(path, loc->dir, sizeof(path));
|
||||
strlcat(path, "/", sizeof(path));
|
||||
strlcat(path, req->relpath, sizeof(path));
|
||||
safe_setenv("PATH_TRANSLATED", path);
|
||||
}
|
||||
|
||||
safe_setenv("QUERY_STRING", iri->query);
|
||||
safe_setenv("REMOTE_ADDR", req->addr);
|
||||
safe_setenv("REMOTE_HOST", req->addr);
|
||||
safe_setenv("REQUEST_METHOD", "");
|
||||
|
||||
strlcpy(path, "/", sizeof(path));
|
||||
strlcat(path, req->spath, sizeof(path));
|
||||
safe_setenv("SCRIPT_NAME", path);
|
||||
|
||||
safe_setenv("SERVER_NAME", iri->host);
|
||||
|
||||
snprintf(path, sizeof(path), "%d", conf.port);
|
||||
safe_setenv("SERVER_PORT", path);
|
||||
|
||||
safe_setenv("SERVER_PROTOCOL", "GEMINI");
|
||||
safe_setenv("SERVER_SOFTWARE", GMID_VERSION);
|
||||
|
||||
if (*req->subject != '\0')
|
||||
safe_setenv("AUTH_TYPE", "Certificate");
|
||||
else
|
||||
safe_setenv("AUTH_TYPE", "");
|
||||
|
||||
safe_setenv("REMOTE_USER", req->subject);
|
||||
safe_setenv("TLS_CLIENT_ISSUER", req->issuer);
|
||||
safe_setenv("TLS_CLIENT_HASH", req->hash);
|
||||
safe_setenv("TLS_VERSION", req->version);
|
||||
safe_setenv("TLS_CIPHER", req->cipher);
|
||||
|
||||
snprintf(path, sizeof(path), "%d", req->cipher_strength);
|
||||
safe_setenv("TLS_CIPHER_STRENGTH", path);
|
||||
|
||||
setenv_time("TLS_CLIENT_NOT_AFTER", req->notafter);
|
||||
setenv_time("TLS_CLIENT_NOT_BEFORE", req->notbefore);
|
||||
|
||||
TAILQ_FOREACH(e, &vhost->env, envs) {
|
||||
safe_setenv(e->name, e->value);
|
||||
}
|
||||
|
||||
strlcpy(path, ex, sizeof(path));
|
||||
|
||||
pwd = dirname(path);
|
||||
if (chdir(pwd)) {
|
||||
warn("chdir");
|
||||
goto childerr;
|
||||
}
|
||||
|
||||
do_exec(ex, req->spath, iri->query);
|
||||
goto childerr;
|
||||
}
|
||||
|
||||
default:
|
||||
close(p[1]);
|
||||
close(errp[0]);
|
||||
close(errp[1]);
|
||||
mark_nonblock(p[0]);
|
||||
return p[0];
|
||||
}
|
||||
|
||||
childerr:
|
||||
dprintf(p[1], "%d internal server error\r\n", TEMP_FAILURE);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static struct vhost *
|
||||
host_nth(size_t n)
|
||||
{
|
||||
struct vhost *h;
|
||||
|
||||
TAILQ_FOREACH(h, &hosts, vhosts) {
|
||||
if (n == 0)
|
||||
return h;
|
||||
n--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct location *
|
||||
loc_nth(struct vhost *vhost, size_t n)
|
||||
{
|
||||
struct location *loc;
|
||||
|
||||
TAILQ_FOREACH(loc, &vhost->locations, locations) {
|
||||
if (n == 0)
|
||||
return loc;
|
||||
n--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
||||
{
|
||||
struct vhost *h;
|
||||
struct location *l;
|
||||
struct cgireq req;
|
||||
struct iri iri;
|
||||
int fd;
|
||||
|
||||
if (datalen != sizeof(req))
|
||||
abort();
|
||||
|
||||
memcpy(&req, imsg->data, sizeof(req));
|
||||
|
||||
iri.schema = req.iri_schema_off + req.buf;
|
||||
iri.host = req.iri_host_off + req.buf;
|
||||
iri.port = req.iri_port_off + req.buf;
|
||||
iri.path = req.iri_path_off + req.buf;
|
||||
iri.query = req.iri_query_off + req.buf;
|
||||
iri.fragment = req.iri_fragment_off + req.buf;
|
||||
|
||||
/* patch the query, otherwise do_exec will always pass "" as
|
||||
* first argument to the script. */
|
||||
if (*iri.query == '\0')
|
||||
iri.query = NULL;
|
||||
|
||||
if ((h = host_nth(req.host_off)) == NULL)
|
||||
abort();
|
||||
|
||||
if ((l = loc_nth(h, req.loc_off)) == NULL)
|
||||
abort();
|
||||
|
||||
fd = launch_cgi(&iri, &req, h, l);
|
||||
imsg_compose(ibuf, IMSG_CGI_RES, imsg->hdr.peerid, 0, fd, NULL, 0);
|
||||
imsg_flush(ibuf);
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_open_prog(struct fcgi *f)
|
||||
{
|
||||
int s[2];
|
||||
pid_t p;
|
||||
|
||||
/* XXX! */
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1)
|
||||
err(1, "socketpair");
|
||||
|
||||
switch (p = fork()) {
|
||||
case -1:
|
||||
err(1, "fork");
|
||||
case 0:
|
||||
close(s[0]);
|
||||
if (dup2(s[1], 0) == -1)
|
||||
err(1, "dup2");
|
||||
execl(f->prog, f->prog, NULL);
|
||||
err(1, "execl %s", f->prog);
|
||||
default:
|
||||
close(s[1]);
|
||||
return s[0];
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_open_sock(struct fcgi *f)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
int fd;
|
||||
|
||||
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
log_err(NULL, "socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strlcpy(addr.sun_path, f->path, sizeof(addr.sun_path));
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
log_warn(NULL, "failed to connect to %s: %s", f->path,
|
||||
strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_open_conn(struct fcgi *f)
|
||||
{
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
int r, sock;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
if ((r = getaddrinfo(f->path, f->port, &hints, &servinfo)) != 0) {
|
||||
log_warn(NULL, "getaddrinfo %s:%s: %s", f->path, f->port,
|
||||
gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (sock == -1)
|
||||
continue;
|
||||
if (connect(sock, p->ai_addr, p->ai_addrlen) == -1) {
|
||||
close(sock);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
log_warn(NULL, "couldn't connect to %s:%s", f->path, f->port);
|
||||
sock = -1;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
return sock;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_fcgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
||||
{
|
||||
struct fcgi *f;
|
||||
int id, fd;
|
||||
|
||||
if (datalen != sizeof(id))
|
||||
abort();
|
||||
memcpy(&id, imsg->data, sizeof(id));
|
||||
|
||||
if (id > FCGI_MAX || (fcgi[id].path == NULL && fcgi[id].prog == NULL))
|
||||
abort();
|
||||
|
||||
f = &fcgi[id];
|
||||
if (f->prog != NULL)
|
||||
fd = fcgi_open_prog(f);
|
||||
else if (f->port != NULL)
|
||||
fd = fcgi_open_conn(f);
|
||||
else
|
||||
fd = fcgi_open_sock(f);
|
||||
|
||||
imsg_compose(ibuf, IMSG_FCGI_FD, imsg->hdr.peerid, 0, fd, NULL, 0);
|
||||
imsg_flush(ibuf);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_conn_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
||||
{
|
||||
struct addrinfo hints, *res, *res0;
|
||||
struct connreq req;
|
||||
int r, sock;
|
||||
|
||||
if (datalen != sizeof(req))
|
||||
abort();
|
||||
memcpy(&req, imsg->data, sizeof(req));
|
||||
req.flag = 0;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* XXX: do this asynchronously if possible */
|
||||
r = getaddrinfo(req.host, req.port, &hints, &res0);
|
||||
if (r != 0) {
|
||||
log_warn(NULL, "getaddrinfo(\"%s\", \"%s\"): %s",
|
||||
req.host, req.port, gai_strerror(r));
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (res = res0; res; res = res->ai_next) {
|
||||
sock = socket(res->ai_family, res->ai_socktype,
|
||||
res->ai_protocol);
|
||||
if (sock == -1)
|
||||
continue;
|
||||
|
||||
if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
|
||||
close(sock);
|
||||
sock = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(res0);
|
||||
|
||||
if (sock == -1) {
|
||||
log_warn(NULL, "can't connect to %s:%s", req.host,
|
||||
req.port);
|
||||
goto err;
|
||||
}
|
||||
|
||||
imsg_compose(ibuf, IMSG_CONN_FD, imsg->hdr.peerid, 0, sock, NULL, 0);
|
||||
imsg_flush(ibuf);
|
||||
return;
|
||||
|
||||
err:
|
||||
imsg_compose(ibuf, IMSG_CONN_FD, imsg->hdr.peerid, 0, -1, NULL, 0);
|
||||
imsg_flush(ibuf);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < conf.prefork; ++i) {
|
||||
imsg_compose(&servibuf[i], IMSG_QUIT, 0, 0, -1, NULL, 0);
|
||||
imsg_flush(&exibuf);
|
||||
close(servibuf[i].fd);
|
||||
}
|
||||
|
||||
event_loopbreak();
|
||||
}
|
||||
|
||||
static void
|
||||
handle_dispatch_imsg(int fd, short ev, void *d)
|
||||
{
|
||||
struct imsgbuf *ibuf = d;
|
||||
dispatch_imsg(ibuf, handlers, sizeof(handlers));
|
||||
}
|
||||
|
||||
int
|
||||
executor_main(struct imsgbuf *ibuf)
|
||||
{
|
||||
struct event evs[PROC_MAX], imsgev;
|
||||
int i;
|
||||
|
||||
event_init();
|
||||
|
||||
if (ibuf != NULL) {
|
||||
event_set(&imsgev, ibuf->fd, EV_READ | EV_PERSIST,
|
||||
handle_dispatch_imsg, ibuf);
|
||||
event_add(&imsgev, NULL);
|
||||
}
|
||||
|
||||
for (i = 0; i < conf.prefork; ++i) {
|
||||
event_set(&evs[i], servibuf[i].fd, EV_READ | EV_PERSIST,
|
||||
handle_dispatch_imsg, &servibuf[i]);
|
||||
event_add(&evs[i], NULL);
|
||||
}
|
||||
|
||||
sandbox_executor_process();
|
||||
|
||||
event_dispatch();
|
||||
|
||||
return 1;
|
||||
}
|
30
gmid.1
30
gmid.1
|
@ -31,13 +31,12 @@
|
|||
.Op Fl d Ar certs-dir
|
||||
.Op Fl H Ar hostname
|
||||
.Op Fl p Ar port
|
||||
.Op Fl x Ar cgi
|
||||
.Op Ar dir
|
||||
.Ek
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a simple and minimal gemini server that can serve static files,
|
||||
execute CGI scripts and talk to FastCGI applications.
|
||||
talk to FastCGI applications and act as a gemini reverse proxy.
|
||||
It can run without a configuration file with a limited set of features
|
||||
available.
|
||||
.Pp
|
||||
|
@ -118,18 +117,6 @@ Verbose mode.
|
|||
Multiple
|
||||
.Fl v
|
||||
options increase the verbosity.
|
||||
.It Fl x Ar path
|
||||
Enable execution of
|
||||
.Sx CGI
|
||||
scripts.
|
||||
See the description of the
|
||||
.Ic cgi
|
||||
option in the
|
||||
.Sq Servers
|
||||
section below to learn how
|
||||
.Ar path
|
||||
is processed.
|
||||
Cannot be provided more than once.
|
||||
.It Ar dir
|
||||
The root directory to serve.
|
||||
By default the current working directory is assumed.
|
||||
|
@ -167,21 +154,6 @@ Serve the current directory
|
|||
$ gmid .
|
||||
.Ed
|
||||
.Pp
|
||||
To serve the directory
|
||||
.Pa docs
|
||||
and enable CGI scripts inside
|
||||
.Pa docs/cgi
|
||||
.Bd -literal -offset indent
|
||||
$ mkdir docs/cgi
|
||||
$ cat <<EOF > docs/cgi/hello
|
||||
#!/bin/sh
|
||||
printf "20 text/plain\er\en"
|
||||
echo "hello world"
|
||||
EOF
|
||||
$ chmod +x docs/cgi/hello
|
||||
$ gmid -x '/cgi/*' docs
|
||||
.Ed
|
||||
.Pp
|
||||
To run
|
||||
.Nm
|
||||
as a deamon a configuration file and a X.509 certificate must be provided.
|
||||
|
|
125
gmid.c
125
gmid.c
|
@ -43,7 +43,7 @@ int sock4, sock6;
|
|||
|
||||
struct imsgbuf logibuf, exibuf, servibuf[PROC_MAX];
|
||||
|
||||
const char *config_path, *certs_dir, *hostname, *pidfile, *cgi;
|
||||
const char *config_path, *certs_dir, *hostname, *pidfile;
|
||||
|
||||
struct conf conf;
|
||||
|
||||
|
@ -103,10 +103,9 @@ data_dir(void)
|
|||
}
|
||||
|
||||
void
|
||||
load_local_cert(const char *hostname, const char *dir)
|
||||
load_local_cert(struct vhost *h, const char *hostname, const char *dir)
|
||||
{
|
||||
char *cert, *key;
|
||||
struct vhost *h;
|
||||
|
||||
if (asprintf(&cert, "%s/%s.cert.pem", dir, hostname) == -1)
|
||||
errx(1, "asprintf");
|
||||
|
@ -116,7 +115,6 @@ load_local_cert(const char *hostname, const char *dir)
|
|||
if (access(cert, R_OK) == -1 || access(key, R_OK) == -1)
|
||||
gen_certificate(hostname, cert, key);
|
||||
|
||||
h = TAILQ_FIRST(&hosts);
|
||||
h->cert = cert;
|
||||
h->key = key;
|
||||
h->domain = hostname;
|
||||
|
@ -351,7 +349,6 @@ free_config(void)
|
|||
free((char*)h->cert);
|
||||
free((char*)h->key);
|
||||
free((char*)h->ocsp);
|
||||
free((char*)h->cgi);
|
||||
free((char*)h->entrypoint);
|
||||
|
||||
TAILQ_REMOVE(&hosts, h, vhosts);
|
||||
|
@ -423,7 +420,7 @@ usage(void)
|
|||
fprintf(stderr,
|
||||
"Version: " GMID_STRING "\n"
|
||||
"Usage: %s [-fnv] [-c config] [-D macro=value] [-P pidfile]\n"
|
||||
" %s [-6hVv] [-d certs-dir] [-H hostname] [-p port] [-x cgi] [dir]\n",
|
||||
" %s [-6hVv] [-d certs-dir] [-H hostname] [-p port] [dir]\n",
|
||||
getprogname(),
|
||||
getprogname());
|
||||
}
|
||||
|
@ -453,42 +450,10 @@ logger_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
static int
|
||||
serve(int argc, char **argv, struct imsgbuf *ibuf)
|
||||
static void
|
||||
serve(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
int i, p[2];
|
||||
struct vhost *h;
|
||||
struct location *l;
|
||||
|
||||
if (config_path == NULL) {
|
||||
if (hostname == NULL)
|
||||
hostname = "localhost";
|
||||
if (certs_dir == NULL)
|
||||
certs_dir = data_dir();
|
||||
load_local_cert(hostname, certs_dir);
|
||||
|
||||
h = TAILQ_FIRST(&hosts);
|
||||
h->domain = "*";
|
||||
|
||||
l = TAILQ_FIRST(&h->locations);
|
||||
l->auto_index = 1;
|
||||
l->match = "*";
|
||||
|
||||
switch (argc) {
|
||||
case 0:
|
||||
l->dir = getcwd(path, sizeof(path));
|
||||
break;
|
||||
case 1:
|
||||
l->dir = absolutify_path(argv[0]);
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
log_notice(NULL, "serving %s on port %d", l->dir, conf.port);
|
||||
}
|
||||
int i, p[2];
|
||||
|
||||
/* setup tls before dropping privileges: we don't want user
|
||||
* to put private certs inside the chroot. */
|
||||
|
@ -512,10 +477,6 @@ serve(int argc, char **argv, struct imsgbuf *ibuf)
|
|||
imsg_init(&servibuf[i], p[0]);
|
||||
}
|
||||
}
|
||||
|
||||
setproctitle("executor");
|
||||
drop_priv();
|
||||
_exit(executor_main(ibuf));
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -547,23 +508,35 @@ write_pidfile(const char *pidfile)
|
|||
}
|
||||
|
||||
static void
|
||||
setup_configless(int argc, char **argv, const char *cgi)
|
||||
setup_configless(const char *path)
|
||||
{
|
||||
char p[PATH_MAX];
|
||||
struct vhost *host;
|
||||
struct location *loc;
|
||||
|
||||
if (hostname == NULL)
|
||||
hostname = "localhost";
|
||||
if (certs_dir == NULL)
|
||||
certs_dir = data_dir();
|
||||
|
||||
host = xcalloc(1, sizeof(*host));
|
||||
host->cgi = cgi;
|
||||
TAILQ_INSERT_HEAD(&hosts, host, vhosts);
|
||||
|
||||
loc = xcalloc(1, sizeof(*loc));
|
||||
loc->fcgi = -1;
|
||||
TAILQ_INSERT_HEAD(&host->locations, loc, locations);
|
||||
|
||||
serve(argc, argv, NULL);
|
||||
load_local_cert(host, hostname, certs_dir);
|
||||
|
||||
imsg_compose(&logibuf, IMSG_QUIT, 0, 0, -1, NULL, 0);
|
||||
imsg_flush(&logibuf);
|
||||
host->domain = "*";
|
||||
loc->auto_index = 1;
|
||||
loc->match = "*";
|
||||
if (path == NULL)
|
||||
loc->dir = getcwd(p, sizeof(p));
|
||||
else
|
||||
loc->dir = absolutify_path(path);
|
||||
|
||||
log_notice(NULL, "serving %s on port %d", loc->dir, conf.port);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -581,8 +554,7 @@ parse_portno(const char *p)
|
|||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
struct imsgbuf exibuf;
|
||||
int ch, conftest = 0, configless = 0;
|
||||
int i, ch, conftest = 0, configless = 0;
|
||||
int pidfd, old_ipv6, old_port;
|
||||
|
||||
logger_init();
|
||||
|
@ -644,14 +616,6 @@ main(int argc, char **argv)
|
|||
conf.verbose++;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
/* drop the starting / (if any) */
|
||||
if (*optarg == '/')
|
||||
optarg++;
|
||||
cgi = optarg;
|
||||
configless = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
usage();
|
||||
return 1;
|
||||
|
@ -667,6 +631,9 @@ main(int argc, char **argv)
|
|||
conf.verbose++;
|
||||
}
|
||||
|
||||
if (argc > 1 || (configless && argc != 0))
|
||||
usage();
|
||||
|
||||
if (config_path != NULL && (argc > 0 || configless))
|
||||
fatal("can't specify options in config mode.");
|
||||
|
||||
|
@ -691,6 +658,8 @@ main(int argc, char **argv)
|
|||
|
||||
if (config_path != NULL)
|
||||
parse_conf(config_path);
|
||||
else
|
||||
setup_configless(*argv);
|
||||
|
||||
sock4 = make_socket(conf.port, AF_INET);
|
||||
sock6 = -1;
|
||||
|
@ -698,12 +667,6 @@ main(int argc, char **argv)
|
|||
sock6 = make_socket(conf.port, AF_INET6);
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
if (configless) {
|
||||
setup_configless(argc, argv, cgi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pidfd = write_pidfile(pidfile);
|
||||
|
||||
|
@ -718,33 +681,19 @@ main(int argc, char **argv)
|
|||
|
||||
/* wait a sighup and reload the daemon */
|
||||
for (;;) {
|
||||
int p[2];
|
||||
serve();
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
|
||||
PF_UNSPEC, p) == -1)
|
||||
fatal("socketpair: %s", strerror(errno));
|
||||
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
fatal("fork: %s", strerror(errno));
|
||||
case 0:
|
||||
close(p[0]);
|
||||
imsg_init(&exibuf, p[1]);
|
||||
_exit(serve(argc, argv, &exibuf));
|
||||
}
|
||||
|
||||
close(p[1]);
|
||||
imsg_init(&exibuf, p[0]);
|
||||
|
||||
if (!wait_signal())
|
||||
if (!wait_signal() || configless)
|
||||
break;
|
||||
|
||||
log_info(NULL, "reloading configuration %s", config_path);
|
||||
|
||||
/* close the executor (it'll close the servers too) */
|
||||
imsg_compose(&exibuf, IMSG_QUIT, 0, 0, -1, NULL, 0);
|
||||
imsg_flush(&exibuf);
|
||||
close(p[0]);
|
||||
/* close the servers */
|
||||
for (i = 0; i < conf.prefork; ++i) {
|
||||
imsg_compose(&servibuf[i], IMSG_QUIT, 0, 0, -1, NULL, 0);
|
||||
imsg_flush(&servibuf[i]);
|
||||
close(servibuf[i].fd);
|
||||
}
|
||||
|
||||
old_ipv6 = conf.ipv6;
|
||||
old_port = conf.port;
|
||||
|
|
51
gmid.h
51
gmid.h
|
@ -157,7 +157,6 @@ struct vhost {
|
|||
const char *cert;
|
||||
const char *key;
|
||||
const char *ocsp;
|
||||
const char *cgi;
|
||||
const char *entrypoint;
|
||||
|
||||
TAILQ_ENTRY(vhost) vhosts;
|
||||
|
@ -221,14 +220,12 @@ enum {
|
|||
REQUEST_UNDECIDED,
|
||||
REQUEST_FILE,
|
||||
REQUEST_DIR,
|
||||
REQUEST_CGI,
|
||||
REQUEST_FCGI,
|
||||
REQUEST_PROXY,
|
||||
REQUEST_DONE,
|
||||
};
|
||||
|
||||
#define IS_INTERNAL_REQUEST(x) \
|
||||
((x) != REQUEST_CGI && \
|
||||
(x) != REQUEST_FCGI && \
|
||||
(x) != REQUEST_PROXY)
|
||||
|
||||
|
@ -273,36 +270,6 @@ struct client {
|
|||
SPLAY_HEAD(client_tree_id, client);
|
||||
extern struct client_tree_id clients;
|
||||
|
||||
struct cgireq {
|
||||
char buf[GEMINI_URL_LEN];
|
||||
|
||||
size_t iri_schema_off;
|
||||
size_t iri_host_off;
|
||||
size_t iri_port_off;
|
||||
size_t iri_path_off;
|
||||
size_t iri_query_off;
|
||||
size_t iri_fragment_off;
|
||||
int iri_portno;
|
||||
|
||||
char spath[PATH_MAX+1];
|
||||
char relpath[PATH_MAX+1];
|
||||
char addr[NI_MAXHOST+1];
|
||||
|
||||
/* AFAIK there isn't an upper limit for these two fields. */
|
||||
char subject[64+1];
|
||||
char issuer[64+1];
|
||||
|
||||
char hash[128+1];
|
||||
char version[8];
|
||||
char cipher[32];
|
||||
int cipher_strength;
|
||||
time_t notbefore;
|
||||
time_t notafter;
|
||||
|
||||
size_t host_off;
|
||||
size_t loc_off;
|
||||
};
|
||||
|
||||
struct connreq {
|
||||
char host[NI_MAXHOST];
|
||||
char port[NI_MAXSERV];
|
||||
|
@ -317,8 +284,6 @@ enum {
|
|||
};
|
||||
|
||||
enum imsg_type {
|
||||
IMSG_CGI_REQ,
|
||||
IMSG_CGI_RES,
|
||||
IMSG_FCGI_REQ,
|
||||
IMSG_FCGI_FD,
|
||||
IMSG_CONN_REQ,
|
||||
|
@ -331,7 +296,7 @@ enum imsg_type {
|
|||
|
||||
/* gmid.c */
|
||||
char *data_dir(void);
|
||||
void load_local_cert(const char*, const char*);
|
||||
void load_local_cert(struct vhost*, const char*, const char*);
|
||||
void load_vhosts(void);
|
||||
int make_socket(int, int);
|
||||
void setup_tls(void);
|
||||
|
@ -395,20 +360,6 @@ int scandir_fd(int, struct dirent***, int(*)(const struct dirent*),
|
|||
int select_non_dot(const struct dirent*);
|
||||
int select_non_dotdot(const struct dirent*);
|
||||
|
||||
/* ex.c */
|
||||
int send_string(int, const char*);
|
||||
int recv_string(int, char**);
|
||||
int send_iri(int, struct iri*);
|
||||
int recv_iri(int, struct iri*);
|
||||
void free_recvd_iri(struct iri*);
|
||||
int send_vhost(int, struct vhost*);
|
||||
int recv_vhost(int, struct vhost**);
|
||||
int send_time(int, time_t);
|
||||
int recv_time(int, time_t*);
|
||||
int send_fd(int, int);
|
||||
int recv_fd(int);
|
||||
int executor_main(struct imsgbuf*);
|
||||
|
||||
/* fcgi.c */
|
||||
void fcgi_read(struct bufferevent *, void *);
|
||||
void fcgi_write(struct bufferevent *, void *);
|
||||
|
|
22
parse.y
22
parse.y
|
@ -117,9 +117,8 @@ typedef struct {
|
|||
|
||||
%token ALIAS AUTO
|
||||
%token BLOCK
|
||||
%token CA CERT CGI CHROOT CLIENT
|
||||
%token CA CERT CHROOT CLIENT
|
||||
%token DEFAULT
|
||||
%token ENTRYPOINT ENV
|
||||
%token FASTCGI FOR_HOST
|
||||
%token INCLUDE INDEX IPV6
|
||||
%token KEY
|
||||
|
@ -282,22 +281,6 @@ servopt : ALIAS string {
|
|||
only_once(host->cert, "cert");
|
||||
host->cert = ensure_absolute_path($2);
|
||||
}
|
||||
| CGI string {
|
||||
only_once(host->cgi, "cgi");
|
||||
/* drop the starting '/', if any */
|
||||
if (*$2 == '/')
|
||||
memmove($2, $2+1, strlen($2));
|
||||
host->cgi = $2;
|
||||
}
|
||||
| ENTRYPOINT string {
|
||||
only_once(host->entrypoint, "entrypoint");
|
||||
while (*$2 == '/')
|
||||
memmove($2, $2+1, strlen($2));
|
||||
host->entrypoint = $2;
|
||||
}
|
||||
| ENV string '=' string {
|
||||
add_param($2, $4, 1);
|
||||
}
|
||||
| KEY string {
|
||||
only_once(host->key, "key");
|
||||
host->key = ensure_absolute_path($2);
|
||||
|
@ -522,12 +505,9 @@ static const struct keyword {
|
|||
{"block", BLOCK},
|
||||
{"ca", CA},
|
||||
{"cert", CERT},
|
||||
{"cgi", CGI},
|
||||
{"chroot", CHROOT},
|
||||
{"client", CLIENT},
|
||||
{"default", DEFAULT},
|
||||
{"entrypoint", ENTRYPOINT},
|
||||
{"env", ENV},
|
||||
{"fastcgi", FASTCGI},
|
||||
{"for-host", FOR_HOST},
|
||||
{"include", INCLUDE},
|
||||
|
|
|
@ -40,21 +40,16 @@ run_test test_custom_mime
|
|||
run_test test_default_type
|
||||
run_test test_custom_lang
|
||||
run_test test_parse_custom_lang_per_location
|
||||
run_test test_cgi_scripts
|
||||
run_test test_cgi_big_replies
|
||||
run_test test_cgi_split_query
|
||||
run_test test_custom_index
|
||||
run_test test_custom_index_default_type_per_location
|
||||
run_test test_auto_index
|
||||
run_test test_block
|
||||
run_test test_block_return_fmt
|
||||
run_test test_entrypoint
|
||||
run_test test_require_client_ca
|
||||
run_test test_root_inside_location
|
||||
run_test test_root_inside_location_with_redirect
|
||||
run_test test_fastcgi
|
||||
# run_test test_fastcgi XXX: needs to be fixed
|
||||
run_test test_macro_expansion
|
||||
run_test test_174_bugfix
|
||||
run_test test_proxy_relay_to
|
||||
run_test test_proxy_with_certs
|
||||
run_test test_unknown_host
|
||||
|
|
|
@ -92,61 +92,6 @@ test_parse_custom_lang_per_location() {
|
|||
# can parse multiple locations
|
||||
}
|
||||
|
||||
test_cgi_scripts() {
|
||||
setup_simple_test '' 'cgi "*"'
|
||||
|
||||
fetch /hello
|
||||
check_reply "20 text/gemini" "# hello world" || return 1
|
||||
|
||||
fetch /slow
|
||||
check_reply "20 text/gemini" "# hello world" || return 1
|
||||
|
||||
fetch /err
|
||||
check_reply "42 CGI error" || return 1
|
||||
|
||||
fetch /invalid
|
||||
check_reply "42 CGI error" || return 1
|
||||
}
|
||||
|
||||
test_cgi_big_replies() {
|
||||
setup_simple_test '' 'cgi "*"'
|
||||
|
||||
hdr="$(head /serve-bigfile)"
|
||||
get /bigfile > bigfile
|
||||
./sha bigfile bigfile.sha
|
||||
body="$(cat bigfile.sha)"
|
||||
check_reply "20 application/octet-stream" "$(cat testdata/bigfile.sha)"
|
||||
}
|
||||
|
||||
test_cgi_split_query() {
|
||||
setup_simple_test '' 'cgi "*"'
|
||||
|
||||
for s in "1" "2 ?foo" "3 ?foo+bar" "1 ?foo+bar=5" "3 ?foo+bar%3d5"; do
|
||||
exp="$(echo $s | sed 's/ .*//')"
|
||||
qry="$(echo $s | sed 's/^..//')"
|
||||
|
||||
if [ "$exp" = "$qry" ]; then
|
||||
# the "1" case yields exp == qry
|
||||
qry=''
|
||||
fi
|
||||
|
||||
url="/env$qry"
|
||||
|
||||
n="$(get "$url" | awk /^-/ | count)"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "failed to get /$url"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$n" -ne $exp ]; then
|
||||
echo "Unexpected number of args"
|
||||
echo "want : $exp"
|
||||
echo "got : $n"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
test_custom_index() {
|
||||
setup_simple_test '' 'index "foo.gmi"'
|
||||
|
||||
|
@ -222,28 +167,6 @@ location "*" {
|
|||
check_reply "40 % / 10965 localhost test" || return 1
|
||||
}
|
||||
|
||||
test_entrypoint() {
|
||||
setup_simple_test '' 'entrypoint "/env"'
|
||||
|
||||
fetch_hdr /foo/bar
|
||||
check_reply "20 text/plain; lang=en" || return 1
|
||||
|
||||
# TODO: test something similar with plain cgi too
|
||||
|
||||
body="$(get /foo/bar|grep PATH_INFO)"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "failed to get /foo/bar"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$body" != "PATH_INFO=/foo/bar" ]; then
|
||||
echo "Invalid PATH_INFO generated"
|
||||
echo "want : PATH_INFO=/foo/bar"
|
||||
echo "got : $body"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_require_client_ca() {
|
||||
setup_simple_test '' 'require client ca "'$PWD'/testca.pem"'
|
||||
|
||||
|
@ -313,18 +236,6 @@ EOF
|
|||
check_reply "20 text/gemini" "# hello world"
|
||||
}
|
||||
|
||||
# 1.7.4 bugfix: check_for_cgi goes out-of-bound processing a string
|
||||
# that doesn't contain a '/'
|
||||
test_174_bugfix() {
|
||||
setup_simple_test '' 'cgi "*"'
|
||||
|
||||
# thanks cage :)
|
||||
for i in 0 1 2 3 4 5 6 7 8 9; do
|
||||
fetch /favicon.txt
|
||||
check_reply "51 not found" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
test_proxy_relay_to() {
|
||||
gen_config '' ''
|
||||
set_proxy ''
|
||||
|
|
|
@ -638,7 +638,7 @@ sandbox_server_process(void)
|
|||
}
|
||||
}
|
||||
|
||||
if (pledge("stdio recvfd rpath inet", NULL) == -1)
|
||||
if (pledge("stdio recvfd rpath inet dns", NULL) == -1)
|
||||
fatal("pledge");
|
||||
}
|
||||
|
||||
|
|
487
server.c
487
server.c
|
@ -17,6 +17,7 @@
|
|||
#include "gmid.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
@ -42,7 +43,6 @@ static inline int matches(const char*, const char*);
|
|||
|
||||
static int check_path(struct client*, const char*, int*);
|
||||
static void open_file(struct client*);
|
||||
static void check_for_cgi(struct client*);
|
||||
static void handle_handshake(int, short, void*);
|
||||
static const char *strip_path(const char*, int);
|
||||
static void fmt_sbuf(const char*, struct client*, const char*);
|
||||
|
@ -51,8 +51,6 @@ static int check_matching_certificate(X509_STORE *, struct client *);
|
|||
static int apply_reverse_proxy(struct client *);
|
||||
static int apply_fastcgi(struct client*);
|
||||
static int apply_require_ca(struct client*);
|
||||
static size_t host_nth(struct vhost*);
|
||||
static void start_cgi(const char*, const char*, struct client*);
|
||||
static void open_dir(struct client*);
|
||||
static void redirect_canonical_dir(struct client*);
|
||||
|
||||
|
@ -65,24 +63,14 @@ static void client_error(struct bufferevent *, short, void *);
|
|||
|
||||
static void client_close_ev(int, short, void *);
|
||||
|
||||
static void cgi_read(struct bufferevent *, void *);
|
||||
static void cgi_write(struct bufferevent *, void *);
|
||||
static void cgi_error(struct bufferevent *, short, void *);
|
||||
|
||||
static void do_accept(int, short, void*);
|
||||
|
||||
static void handle_imsg_cgi_res(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_imsg_fcgi_fd(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_imsg_conn_fd(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_imsg_quit(struct imsgbuf*, struct imsg*, size_t);
|
||||
static void handle_dispatch_imsg(int, short, void *);
|
||||
static void handle_siginfo(int, short, void*);
|
||||
|
||||
static imsg_handlerfn *handlers[] = {
|
||||
[IMSG_QUIT] = handle_imsg_quit,
|
||||
[IMSG_CGI_RES] = handle_imsg_cgi_res,
|
||||
[IMSG_FCGI_FD] = handle_imsg_fcgi_fd,
|
||||
[IMSG_CONN_FD] = handle_imsg_conn_fd,
|
||||
};
|
||||
|
||||
static uint32_t server_client_id;
|
||||
|
@ -349,9 +337,6 @@ check_path(struct client *c, const char *path, int *fd)
|
|||
if (S_ISDIR(sb.st_mode))
|
||||
return FILE_DIRECTORY;
|
||||
|
||||
if (sb.st_mode & S_IXUSR)
|
||||
return FILE_EXECUTABLE;
|
||||
|
||||
return FILE_EXISTS;
|
||||
}
|
||||
|
||||
|
@ -359,14 +344,6 @@ static void
|
|||
open_file(struct client *c)
|
||||
{
|
||||
switch (check_path(c, c->iri.path, &c->pfd)) {
|
||||
case FILE_EXECUTABLE:
|
||||
if (c->host->cgi != NULL && matches(c->host->cgi, c->iri.path)) {
|
||||
start_cgi(c->iri.path, "", c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* fallthrough */
|
||||
|
||||
case FILE_EXISTS:
|
||||
c->type = REQUEST_FILE;
|
||||
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
||||
|
@ -377,10 +354,6 @@ open_file(struct client *c)
|
|||
return;
|
||||
|
||||
case FILE_MISSING:
|
||||
if (c->host->cgi != NULL && matches(c->host->cgi, c->iri.path)) {
|
||||
check_for_cgi(c);
|
||||
return;
|
||||
}
|
||||
start_reply(c, NOT_FOUND, "not found");
|
||||
return;
|
||||
|
||||
|
@ -390,55 +363,6 @@ open_file(struct client *c)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* the inverse of this algorithm, i.e. starting from the start of the
|
||||
* path + strlen(cgi), and checking if each component, should be
|
||||
* faster. But it's tedious to write. This does the opposite: starts
|
||||
* from the end and strip one component at a time, until either an
|
||||
* executable is found or we emptied the path.
|
||||
*/
|
||||
static void
|
||||
check_for_cgi(struct client *c)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char *end;
|
||||
|
||||
strlcpy(path, c->iri.path, sizeof(path));
|
||||
end = strchr(path, '\0');
|
||||
|
||||
while (end > path) {
|
||||
/*
|
||||
* go up one level. UNIX paths are simple and POSIX
|
||||
* dirname, with its ambiguities on if the given
|
||||
* pointer is changed or not, gives me headaches.
|
||||
*/
|
||||
while (*end != '/' && end > path)
|
||||
end--;
|
||||
|
||||
if (end == path)
|
||||
break;
|
||||
|
||||
*end = '\0';
|
||||
|
||||
switch (check_path(c, path, &c->pfd)) {
|
||||
case FILE_EXECUTABLE:
|
||||
start_cgi(path, end+1, c);
|
||||
return;
|
||||
case FILE_MISSING:
|
||||
break;
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
|
||||
*end = '/';
|
||||
end--;
|
||||
}
|
||||
|
||||
err:
|
||||
start_reply(c, NOT_FOUND, "not found");
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
mark_nonblock(int fd)
|
||||
{
|
||||
|
@ -653,12 +577,52 @@ check_matching_certificate(X509_STORE *store, struct client *c)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
proxy_socket(struct client *c, const char *host, const char *port)
|
||||
{
|
||||
struct addrinfo hints, *res, *res0;
|
||||
int r, sock;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* XXX: asr_run? :> */
|
||||
r = getaddrinfo(host, port, &hints, &res0);
|
||||
if (r != 0) {
|
||||
log_warn(c, "getaddrinfo(\"%s\", \"%s\"): %s",
|
||||
host, port, gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (res = res0; res; res = res->ai_next) {
|
||||
sock = socket(res->ai_family, res->ai_socktype,
|
||||
res->ai_protocol);
|
||||
if (sock == -1)
|
||||
continue;
|
||||
|
||||
if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
|
||||
close(sock);
|
||||
sock = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(res0);
|
||||
|
||||
if (sock == -1)
|
||||
log_warn(c, "can't connect to %s:%s", host, port);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
/* 1 if matching a proxy relay-to (and apply it), 0 otherwise */
|
||||
static int
|
||||
apply_reverse_proxy(struct client *c)
|
||||
{
|
||||
struct proxy *p;
|
||||
struct connreq r;
|
||||
|
||||
if ((p = matched_proxy(c)) == NULL)
|
||||
return 0;
|
||||
|
@ -671,15 +635,80 @@ apply_reverse_proxy(struct client *c)
|
|||
log_debug(c, "opening proxy connection for %s:%s",
|
||||
p->host, p->port);
|
||||
|
||||
strlcpy(r.host, p->host, sizeof(r.host));
|
||||
strlcpy(r.port, p->port, sizeof(r.port));
|
||||
if ((c->pfd = proxy_socket(c, p->host, p->port)) == -1) {
|
||||
start_reply(c, PROXY_ERROR, "proxy error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
imsg_compose(&exibuf, IMSG_CONN_REQ, c->id, 0, -1, &r, sizeof(r));
|
||||
imsg_flush(&exibuf);
|
||||
mark_nonblock(c->pfd);
|
||||
if (proxy_init(c) == -1)
|
||||
start_reply(c, PROXY_ERROR, "proxy error");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_open_sock(struct fcgi *f)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
int fd;
|
||||
|
||||
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
log_err(NULL, "socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strlcpy(addr.sun_path, f->path, sizeof(addr.sun_path));
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
log_warn(NULL, "failed to connect to %s: %s", f->path,
|
||||
strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_open_conn(struct fcgi *f)
|
||||
{
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
int r, sock;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
if ((r = getaddrinfo(f->path, f->port, &hints, &servinfo)) != 0) {
|
||||
log_warn(NULL, "getaddrinfo %s:%s: %s", f->path, f->port,
|
||||
gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (sock == -1)
|
||||
continue;
|
||||
if (connect(sock, p->ai_addr, p->ai_addrlen) == -1) {
|
||||
close(sock);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
log_warn(NULL, "couldn't connect to %s:%s", f->path, f->port);
|
||||
sock = -1;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
return sock;
|
||||
}
|
||||
|
||||
/* 1 if matching `fcgi' (and apply it), 0 otherwise */
|
||||
static int
|
||||
apply_fastcgi(struct client *c)
|
||||
|
@ -692,12 +721,31 @@ apply_fastcgi(struct client *c)
|
|||
|
||||
f = &fcgi[id];
|
||||
|
||||
log_debug(c, "opening fastcgi connection for (%s,%s,%s)",
|
||||
f->path, f->port, f->prog);
|
||||
log_debug(c, "opening fastcgi connection for (%s,%s)",
|
||||
f->path, f->port);
|
||||
|
||||
if (f->port != NULL)
|
||||
c->pfd = fcgi_open_sock(f);
|
||||
else
|
||||
c->pfd = fcgi_open_conn(f);
|
||||
|
||||
if (c->pfd == -1) {
|
||||
start_reply(c, CGI_ERROR, "CGI error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mark_nonblock(c->pfd);
|
||||
|
||||
c->cgibev = bufferevent_new(c->pfd, fcgi_read, fcgi_write,
|
||||
fcgi_error, c);
|
||||
if (c->cgibev == NULL) {
|
||||
start_reply(c, TEMP_FAILURE, "internal server error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
bufferevent_enable(c->cgibev, EV_READ|EV_WRITE);
|
||||
fcgi_req(c);
|
||||
|
||||
imsg_compose(&exibuf, IMSG_FCGI_REQ, c->id, 0, -1,
|
||||
&id, sizeof(id));
|
||||
imsg_flush(&exibuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -712,79 +760,6 @@ apply_require_ca(struct client *c)
|
|||
return check_matching_certificate(store, c);
|
||||
}
|
||||
|
||||
static size_t
|
||||
host_nth(struct vhost *h)
|
||||
{
|
||||
struct vhost *v;
|
||||
size_t i = 0;
|
||||
|
||||
TAILQ_FOREACH(v, &hosts, vhosts) {
|
||||
if (v == h)
|
||||
return i;
|
||||
i++;
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
static void
|
||||
start_cgi(const char *spath, const char *relpath, struct client *c)
|
||||
{
|
||||
char addr[NI_MAXHOST];
|
||||
const char *t;
|
||||
struct cgireq req;
|
||||
int e;
|
||||
|
||||
c->type = REQUEST_CGI;
|
||||
|
||||
e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
|
||||
addr, sizeof(addr),
|
||||
NULL, 0,
|
||||
NI_NUMERICHOST);
|
||||
if (e != 0)
|
||||
fatal("getnameinfo failed");
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
memcpy(req.buf, c->req, c->reqlen);
|
||||
|
||||
req.iri_schema_off = c->iri.schema - c->req;
|
||||
req.iri_host_off = c->iri.host - c->req;
|
||||
req.iri_port_off = c->iri.port - c->req;
|
||||
req.iri_path_off = c->iri.path - c->req;
|
||||
req.iri_query_off = c->iri.query - c->req;
|
||||
req.iri_fragment_off = c->iri.fragment - c->req;
|
||||
|
||||
req.iri_portno = c->iri.port_no;
|
||||
|
||||
strlcpy(req.spath, spath, sizeof(req.spath));
|
||||
strlcpy(req.relpath, relpath, sizeof(req.relpath));
|
||||
strlcpy(req.addr, addr, sizeof(req.addr));
|
||||
|
||||
if ((t = tls_peer_cert_subject(c->ctx)) != NULL)
|
||||
strlcpy(req.subject, t, sizeof(req.subject));
|
||||
if ((t = tls_peer_cert_issuer(c->ctx)) != NULL)
|
||||
strlcpy(req.issuer, t, sizeof(req.issuer));
|
||||
if ((t = tls_peer_cert_hash(c->ctx)) != NULL)
|
||||
strlcpy(req.hash, t, sizeof(req.hash));
|
||||
if ((t = tls_conn_version(c->ctx)) != NULL)
|
||||
strlcpy(req.version, t, sizeof(req.version));
|
||||
if ((t = tls_conn_cipher(c->ctx)) != NULL)
|
||||
strlcpy(req.cipher, t, sizeof(req.cipher));
|
||||
|
||||
req.cipher_strength = tls_conn_cipher_strength(c->ctx);
|
||||
req.notbefore = tls_peer_cert_notbefore(c->ctx);
|
||||
req.notafter = tls_peer_cert_notafter(c->ctx);
|
||||
|
||||
req.host_off = host_nth(c->host);
|
||||
req.loc_off = c->loc;
|
||||
|
||||
imsg_compose(&exibuf, IMSG_CGI_REQ, c->id, 0, -1, &req, sizeof(req));
|
||||
imsg_flush(&exibuf);
|
||||
|
||||
close(c->pfd);
|
||||
}
|
||||
|
||||
static void
|
||||
open_dir(struct client *c)
|
||||
{
|
||||
|
@ -792,6 +767,8 @@ open_dir(struct client *c)
|
|||
int dirfd, root;
|
||||
char *before_file;
|
||||
|
||||
log_debug(c, "in open_dir");
|
||||
|
||||
root = !strcmp(c->iri.path, "/") || *c->iri.path == '\0';
|
||||
|
||||
len = strlen(c->iri.path);
|
||||
|
@ -819,14 +796,6 @@ open_dir(struct client *c)
|
|||
c->pfd = -1;
|
||||
|
||||
switch (check_path(c, c->iri.path, &c->pfd)) {
|
||||
case FILE_EXECUTABLE:
|
||||
if (c->host->cgi != NULL && matches(c->host->cgi, c->iri.path)) {
|
||||
start_cgi(c->iri.path, "", c);
|
||||
break;
|
||||
}
|
||||
|
||||
/* fallthrough */
|
||||
|
||||
case FILE_EXISTS:
|
||||
c->type = REQUEST_FILE;
|
||||
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
||||
|
@ -1056,12 +1025,6 @@ client_read(struct bufferevent *bev, void *d)
|
|||
apply_fastcgi(c))
|
||||
return;
|
||||
|
||||
if (c->host->entrypoint != NULL) {
|
||||
c->loc = 0;
|
||||
start_cgi(c->host->entrypoint, c->iri.path, c);
|
||||
return;
|
||||
}
|
||||
|
||||
open_file(c);
|
||||
}
|
||||
|
||||
|
@ -1115,12 +1078,11 @@ client_write(struct bufferevent *bev, void *d)
|
|||
event_add(&c->bev->ev_write, NULL);
|
||||
break;
|
||||
|
||||
case REQUEST_CGI:
|
||||
case REQUEST_FCGI:
|
||||
case REQUEST_PROXY:
|
||||
/*
|
||||
* Here we depend on the cgi/fastcgi or proxy
|
||||
* connection to provide data.
|
||||
* Here we depend on fastcgi or proxy connection to
|
||||
* provide data.
|
||||
*/
|
||||
break;
|
||||
|
||||
|
@ -1177,8 +1139,7 @@ start_reply(struct client *c, int code, const char *meta)
|
|||
if (r > 1027)
|
||||
goto overflow;
|
||||
|
||||
if (c->type != REQUEST_CGI &&
|
||||
c->type != REQUEST_FCGI &&
|
||||
if (c->type != REQUEST_FCGI &&
|
||||
c->type != REQUEST_PROXY &&
|
||||
!strcmp(meta, "text/gemini") &&
|
||||
(lang = vhost_lang(c->host, c->iri.path)) != NULL) {
|
||||
|
@ -1309,94 +1270,6 @@ client_close(struct client *c)
|
|||
client_close_ev(c->fd, 0, c);
|
||||
}
|
||||
|
||||
static void
|
||||
cgi_read(struct bufferevent *bev, void *d)
|
||||
{
|
||||
struct client *client = d;
|
||||
struct evbuffer *src = EVBUFFER_INPUT(bev);
|
||||
char *header;
|
||||
size_t len;
|
||||
int code;
|
||||
|
||||
/* intercept the header */
|
||||
if (client->code == 0) {
|
||||
header = evbuffer_readln(src, &len, EVBUFFER_EOL_CRLF_STRICT);
|
||||
if (header == NULL) {
|
||||
/* max reply + \r\n */
|
||||
if (EVBUFFER_LENGTH(src) > 1029) {
|
||||
log_warn(client, "CGI script is trying to "
|
||||
"send a header too long.");
|
||||
cgi_error(bev, EVBUFFER_READ, client);
|
||||
}
|
||||
|
||||
/* wait a bit */
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 3 || len > 1029 ||
|
||||
!isdigit(header[0]) ||
|
||||
!isdigit(header[1]) ||
|
||||
!isspace(header[2])) {
|
||||
free(header);
|
||||
log_warn(client, "CGI script is trying to send a "
|
||||
"malformed header");
|
||||
cgi_error(bev, EVBUFFER_READ, client);
|
||||
return;
|
||||
}
|
||||
|
||||
client->header = header;
|
||||
code = (header[0] - '0') * 10 + (header[1] - '0');
|
||||
|
||||
if (code < 10 || code >= 70) {
|
||||
log_warn(client, "CGI script is trying to send an "
|
||||
"invalid reply code (%d)", code);
|
||||
cgi_error(bev, EVBUFFER_READ, client);
|
||||
return;
|
||||
}
|
||||
|
||||
start_reply(client, code, header + 3);
|
||||
|
||||
if (client->code < 20 || client->code > 29) {
|
||||
cgi_error(client->cgibev, EVBUFFER_EOF, client);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bufferevent_write_buffer(client->bev, src);
|
||||
}
|
||||
|
||||
static void
|
||||
cgi_write(struct bufferevent *bev, void *d)
|
||||
{
|
||||
/*
|
||||
* Never called. We don't send data to a CGI script.
|
||||
*/
|
||||
abort();
|
||||
}
|
||||
|
||||
static void
|
||||
cgi_error(struct bufferevent *bev, short error, void *d)
|
||||
{
|
||||
struct client *client = d;
|
||||
|
||||
if (error & EVBUFFER_ERROR)
|
||||
log_err(client, "%s: evbuffer error (%x): %s",
|
||||
__func__, error, strerror(errno));
|
||||
|
||||
bufferevent_disable(bev, EVBUFFER_READ|EVBUFFER_WRITE);
|
||||
bufferevent_free(bev);
|
||||
client->cgibev = NULL;
|
||||
|
||||
close(client->pfd);
|
||||
client->pfd = -1;
|
||||
|
||||
client->type = REQUEST_DONE;
|
||||
if (client->code != 0)
|
||||
client_write(client->bev, client);
|
||||
else
|
||||
start_reply(client, CGI_ERROR, "CGI error");
|
||||
}
|
||||
|
||||
static void
|
||||
do_accept(int sock, short et, void *d)
|
||||
{
|
||||
|
@ -1444,86 +1317,6 @@ client_by_id(int id)
|
|||
return SPLAY_FIND(client_tree_id, &clients, &find);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
struct client *c;
|
||||
|
||||
if ((c = client_by_id(imsg->hdr.peerid)) == NULL) {
|
||||
if (imsg->fd != -1)
|
||||
close(imsg->fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((c->pfd = imsg->fd) == -1) {
|
||||
start_reply(c, TEMP_FAILURE, "internal server error");
|
||||
return;
|
||||
}
|
||||
|
||||
c->type = REQUEST_CGI;
|
||||
|
||||
c->cgibev = bufferevent_new(c->pfd, cgi_read, cgi_write,
|
||||
cgi_error, c);
|
||||
|
||||
bufferevent_enable(c->cgibev, EV_READ);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_fcgi_fd(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
struct client *c;
|
||||
int id;
|
||||
|
||||
id = imsg->hdr.peerid;
|
||||
|
||||
if ((c = client_by_id(id)) == NULL) {
|
||||
if (imsg->fd != -1)
|
||||
close(imsg->fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((c->pfd = imsg->fd) == -1) {
|
||||
start_reply(c, CGI_ERROR, "CGI error");
|
||||
return;
|
||||
}
|
||||
|
||||
mark_nonblock(c->pfd);
|
||||
|
||||
c->cgibev = bufferevent_new(c->pfd, fcgi_read, fcgi_write,
|
||||
fcgi_error, c);
|
||||
if (c->cgibev == NULL) {
|
||||
start_reply(c, TEMP_FAILURE, "internal server error");
|
||||
return;
|
||||
}
|
||||
|
||||
bufferevent_enable(c->cgibev, EV_READ|EV_WRITE);
|
||||
fcgi_req(c);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_conn_fd(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
struct client *c;
|
||||
int id;
|
||||
|
||||
id = imsg->hdr.peerid;
|
||||
if ((c = client_by_id(id)) == NULL) {
|
||||
if (imsg->fd != -1)
|
||||
close(imsg->fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((c->pfd = imsg->fd) == -1) {
|
||||
start_reply(c, PROXY_ERROR, "proxy error");
|
||||
return;
|
||||
}
|
||||
|
||||
mark_nonblock(c->pfd);
|
||||
|
||||
if (proxy_init(c) == -1)
|
||||
start_reply(c, PROXY_ERROR, "proxy error");
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue