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:
Omar Polo 2022-09-06 16:11:09 +00:00
parent 5df699d1ab
commit d29a2ee224
11 changed files with 186 additions and 1167 deletions

View File

@ -63,7 +63,6 @@ COMPATS = compat/err.c \
compat/vasprintf.c
GMID_SRCS = dirs.c \
ex.c \
fcgi.c \
gmid.c \
iri.c \

View File

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

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

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

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

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

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

View File

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

View File

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

View File

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

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