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
|
compat/vasprintf.c
|
||||||
|
|
||||||
GMID_SRCS = dirs.c \
|
GMID_SRCS = dirs.c \
|
||||||
ex.c \
|
|
||||||
fcgi.c \
|
fcgi.c \
|
||||||
gmid.c \
|
gmid.c \
|
||||||
iri.c \
|
iri.c \
|
||||||
|
|
|
@ -16,7 +16,7 @@ featureful server.
|
||||||
- IRI support (RFC3987)
|
- IRI support (RFC3987)
|
||||||
- automatic certificate generation for config-less mode
|
- automatic certificate generation for config-less mode
|
||||||
- reverse proxying
|
- reverse proxying
|
||||||
- CGI and FastCGI support
|
- FastCGI support
|
||||||
- virtual hosts
|
- virtual hosts
|
||||||
- location rules
|
- location rules
|
||||||
- event-based asynchronous I/O model
|
- event-based asynchronous I/O model
|
||||||
|
@ -75,9 +75,6 @@ server "example.com" {
|
||||||
# lang for text/gemini files
|
# lang for text/gemini files
|
||||||
lang "en"
|
lang "en"
|
||||||
|
|
||||||
# execute CGI scripts in /cgi/
|
|
||||||
cgi "/cgi/*"
|
|
||||||
|
|
||||||
# only for locations that matches /files/*
|
# only for locations that matches /files/*
|
||||||
location "/files/*" {
|
location "/files/*" {
|
||||||
# generate directory listings
|
# generate directory listings
|
||||||
|
@ -141,6 +138,9 @@ to the `contrib` directory.
|
||||||
|
|
||||||
## Architecture/Security considerations
|
## Architecture/Security considerations
|
||||||
|
|
||||||
|
**outdated: revisit for gmid 2.0**
|
||||||
|
|
||||||
|
|
||||||
gmid is composed by four processes: the parent process, the logger,
|
gmid is composed by four processes: the parent process, the logger,
|
||||||
the listener and the executor. The parent process is the only one
|
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
|
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 d Ar certs-dir
|
||||||
.Op Fl H Ar hostname
|
.Op Fl H Ar hostname
|
||||||
.Op Fl p Ar port
|
.Op Fl p Ar port
|
||||||
.Op Fl x Ar cgi
|
|
||||||
.Op Ar dir
|
.Op Ar dir
|
||||||
.Ek
|
.Ek
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.Nm
|
||||||
is a simple and minimal gemini server that can serve static files,
|
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
|
It can run without a configuration file with a limited set of features
|
||||||
available.
|
available.
|
||||||
.Pp
|
.Pp
|
||||||
|
@ -118,18 +117,6 @@ Verbose mode.
|
||||||
Multiple
|
Multiple
|
||||||
.Fl v
|
.Fl v
|
||||||
options increase the verbosity.
|
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
|
.It Ar dir
|
||||||
The root directory to serve.
|
The root directory to serve.
|
||||||
By default the current working directory is assumed.
|
By default the current working directory is assumed.
|
||||||
|
@ -167,21 +154,6 @@ Serve the current directory
|
||||||
$ gmid .
|
$ gmid .
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.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
|
To run
|
||||||
.Nm
|
.Nm
|
||||||
as a deamon a configuration file and a X.509 certificate must be provided.
|
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];
|
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;
|
struct conf conf;
|
||||||
|
|
||||||
|
@ -103,10 +103,9 @@ data_dir(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
char *cert, *key;
|
||||||
struct vhost *h;
|
|
||||||
|
|
||||||
if (asprintf(&cert, "%s/%s.cert.pem", dir, hostname) == -1)
|
if (asprintf(&cert, "%s/%s.cert.pem", dir, hostname) == -1)
|
||||||
errx(1, "asprintf");
|
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)
|
if (access(cert, R_OK) == -1 || access(key, R_OK) == -1)
|
||||||
gen_certificate(hostname, cert, key);
|
gen_certificate(hostname, cert, key);
|
||||||
|
|
||||||
h = TAILQ_FIRST(&hosts);
|
|
||||||
h->cert = cert;
|
h->cert = cert;
|
||||||
h->key = key;
|
h->key = key;
|
||||||
h->domain = hostname;
|
h->domain = hostname;
|
||||||
|
@ -351,7 +349,6 @@ free_config(void)
|
||||||
free((char*)h->cert);
|
free((char*)h->cert);
|
||||||
free((char*)h->key);
|
free((char*)h->key);
|
||||||
free((char*)h->ocsp);
|
free((char*)h->ocsp);
|
||||||
free((char*)h->cgi);
|
|
||||||
free((char*)h->entrypoint);
|
free((char*)h->entrypoint);
|
||||||
|
|
||||||
TAILQ_REMOVE(&hosts, h, vhosts);
|
TAILQ_REMOVE(&hosts, h, vhosts);
|
||||||
|
@ -423,7 +420,7 @@ usage(void)
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Version: " GMID_STRING "\n"
|
"Version: " GMID_STRING "\n"
|
||||||
"Usage: %s [-fnv] [-c config] [-D macro=value] [-P pidfile]\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(),
|
||||||
getprogname());
|
getprogname());
|
||||||
}
|
}
|
||||||
|
@ -453,42 +450,10 @@ logger_init(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
serve(int argc, char **argv, struct imsgbuf *ibuf)
|
serve(void)
|
||||||
{
|
{
|
||||||
char path[PATH_MAX];
|
int i, p[2];
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* setup tls before dropping privileges: we don't want user
|
/* setup tls before dropping privileges: we don't want user
|
||||||
* to put private certs inside the chroot. */
|
* 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]);
|
imsg_init(&servibuf[i], p[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setproctitle("executor");
|
|
||||||
drop_priv();
|
|
||||||
_exit(executor_main(ibuf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -547,23 +508,35 @@ write_pidfile(const char *pidfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_configless(int argc, char **argv, const char *cgi)
|
setup_configless(const char *path)
|
||||||
{
|
{
|
||||||
|
char p[PATH_MAX];
|
||||||
struct vhost *host;
|
struct vhost *host;
|
||||||
struct location *loc;
|
struct location *loc;
|
||||||
|
|
||||||
|
if (hostname == NULL)
|
||||||
|
hostname = "localhost";
|
||||||
|
if (certs_dir == NULL)
|
||||||
|
certs_dir = data_dir();
|
||||||
|
|
||||||
host = xcalloc(1, sizeof(*host));
|
host = xcalloc(1, sizeof(*host));
|
||||||
host->cgi = cgi;
|
|
||||||
TAILQ_INSERT_HEAD(&hosts, host, vhosts);
|
TAILQ_INSERT_HEAD(&hosts, host, vhosts);
|
||||||
|
|
||||||
loc = xcalloc(1, sizeof(*loc));
|
loc = xcalloc(1, sizeof(*loc));
|
||||||
loc->fcgi = -1;
|
loc->fcgi = -1;
|
||||||
TAILQ_INSERT_HEAD(&host->locations, loc, locations);
|
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);
|
host->domain = "*";
|
||||||
imsg_flush(&logibuf);
|
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
|
static int
|
||||||
|
@ -581,8 +554,7 @@ parse_portno(const char *p)
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
struct imsgbuf exibuf;
|
int i, ch, conftest = 0, configless = 0;
|
||||||
int ch, conftest = 0, configless = 0;
|
|
||||||
int pidfd, old_ipv6, old_port;
|
int pidfd, old_ipv6, old_port;
|
||||||
|
|
||||||
logger_init();
|
logger_init();
|
||||||
|
@ -644,14 +616,6 @@ main(int argc, char **argv)
|
||||||
conf.verbose++;
|
conf.verbose++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'x':
|
|
||||||
/* drop the starting / (if any) */
|
|
||||||
if (*optarg == '/')
|
|
||||||
optarg++;
|
|
||||||
cgi = optarg;
|
|
||||||
configless = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
usage();
|
usage();
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -667,6 +631,9 @@ main(int argc, char **argv)
|
||||||
conf.verbose++;
|
conf.verbose++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (argc > 1 || (configless && argc != 0))
|
||||||
|
usage();
|
||||||
|
|
||||||
if (config_path != NULL && (argc > 0 || configless))
|
if (config_path != NULL && (argc > 0 || configless))
|
||||||
fatal("can't specify options in config mode.");
|
fatal("can't specify options in config mode.");
|
||||||
|
|
||||||
|
@ -691,6 +658,8 @@ main(int argc, char **argv)
|
||||||
|
|
||||||
if (config_path != NULL)
|
if (config_path != NULL)
|
||||||
parse_conf(config_path);
|
parse_conf(config_path);
|
||||||
|
else
|
||||||
|
setup_configless(*argv);
|
||||||
|
|
||||||
sock4 = make_socket(conf.port, AF_INET);
|
sock4 = make_socket(conf.port, AF_INET);
|
||||||
sock6 = -1;
|
sock6 = -1;
|
||||||
|
@ -698,12 +667,6 @@ main(int argc, char **argv)
|
||||||
sock6 = make_socket(conf.port, AF_INET6);
|
sock6 = make_socket(conf.port, AF_INET6);
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
signal(SIGCHLD, SIG_IGN);
|
|
||||||
|
|
||||||
if (configless) {
|
|
||||||
setup_configless(argc, argv, cgi);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pidfd = write_pidfile(pidfile);
|
pidfd = write_pidfile(pidfile);
|
||||||
|
|
||||||
|
@ -718,33 +681,19 @@ main(int argc, char **argv)
|
||||||
|
|
||||||
/* wait a sighup and reload the daemon */
|
/* wait a sighup and reload the daemon */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int p[2];
|
serve();
|
||||||
|
|
||||||
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
|
if (!wait_signal() || configless)
|
||||||
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())
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
log_info(NULL, "reloading configuration %s", config_path);
|
log_info(NULL, "reloading configuration %s", config_path);
|
||||||
|
|
||||||
/* close the executor (it'll close the servers too) */
|
/* close the servers */
|
||||||
imsg_compose(&exibuf, IMSG_QUIT, 0, 0, -1, NULL, 0);
|
for (i = 0; i < conf.prefork; ++i) {
|
||||||
imsg_flush(&exibuf);
|
imsg_compose(&servibuf[i], IMSG_QUIT, 0, 0, -1, NULL, 0);
|
||||||
close(p[0]);
|
imsg_flush(&servibuf[i]);
|
||||||
|
close(servibuf[i].fd);
|
||||||
|
}
|
||||||
|
|
||||||
old_ipv6 = conf.ipv6;
|
old_ipv6 = conf.ipv6;
|
||||||
old_port = conf.port;
|
old_port = conf.port;
|
||||||
|
|
51
gmid.h
51
gmid.h
|
@ -157,7 +157,6 @@ struct vhost {
|
||||||
const char *cert;
|
const char *cert;
|
||||||
const char *key;
|
const char *key;
|
||||||
const char *ocsp;
|
const char *ocsp;
|
||||||
const char *cgi;
|
|
||||||
const char *entrypoint;
|
const char *entrypoint;
|
||||||
|
|
||||||
TAILQ_ENTRY(vhost) vhosts;
|
TAILQ_ENTRY(vhost) vhosts;
|
||||||
|
@ -221,14 +220,12 @@ enum {
|
||||||
REQUEST_UNDECIDED,
|
REQUEST_UNDECIDED,
|
||||||
REQUEST_FILE,
|
REQUEST_FILE,
|
||||||
REQUEST_DIR,
|
REQUEST_DIR,
|
||||||
REQUEST_CGI,
|
|
||||||
REQUEST_FCGI,
|
REQUEST_FCGI,
|
||||||
REQUEST_PROXY,
|
REQUEST_PROXY,
|
||||||
REQUEST_DONE,
|
REQUEST_DONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define IS_INTERNAL_REQUEST(x) \
|
#define IS_INTERNAL_REQUEST(x) \
|
||||||
((x) != REQUEST_CGI && \
|
|
||||||
(x) != REQUEST_FCGI && \
|
(x) != REQUEST_FCGI && \
|
||||||
(x) != REQUEST_PROXY)
|
(x) != REQUEST_PROXY)
|
||||||
|
|
||||||
|
@ -273,36 +270,6 @@ struct client {
|
||||||
SPLAY_HEAD(client_tree_id, client);
|
SPLAY_HEAD(client_tree_id, client);
|
||||||
extern struct client_tree_id clients;
|
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 {
|
struct connreq {
|
||||||
char host[NI_MAXHOST];
|
char host[NI_MAXHOST];
|
||||||
char port[NI_MAXSERV];
|
char port[NI_MAXSERV];
|
||||||
|
@ -317,8 +284,6 @@ enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum imsg_type {
|
enum imsg_type {
|
||||||
IMSG_CGI_REQ,
|
|
||||||
IMSG_CGI_RES,
|
|
||||||
IMSG_FCGI_REQ,
|
IMSG_FCGI_REQ,
|
||||||
IMSG_FCGI_FD,
|
IMSG_FCGI_FD,
|
||||||
IMSG_CONN_REQ,
|
IMSG_CONN_REQ,
|
||||||
|
@ -331,7 +296,7 @@ enum imsg_type {
|
||||||
|
|
||||||
/* gmid.c */
|
/* gmid.c */
|
||||||
char *data_dir(void);
|
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);
|
void load_vhosts(void);
|
||||||
int make_socket(int, int);
|
int make_socket(int, int);
|
||||||
void setup_tls(void);
|
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_dot(const struct dirent*);
|
||||||
int select_non_dotdot(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 */
|
/* fcgi.c */
|
||||||
void fcgi_read(struct bufferevent *, void *);
|
void fcgi_read(struct bufferevent *, void *);
|
||||||
void fcgi_write(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 ALIAS AUTO
|
||||||
%token BLOCK
|
%token BLOCK
|
||||||
%token CA CERT CGI CHROOT CLIENT
|
%token CA CERT CHROOT CLIENT
|
||||||
%token DEFAULT
|
%token DEFAULT
|
||||||
%token ENTRYPOINT ENV
|
|
||||||
%token FASTCGI FOR_HOST
|
%token FASTCGI FOR_HOST
|
||||||
%token INCLUDE INDEX IPV6
|
%token INCLUDE INDEX IPV6
|
||||||
%token KEY
|
%token KEY
|
||||||
|
@ -282,22 +281,6 @@ servopt : ALIAS string {
|
||||||
only_once(host->cert, "cert");
|
only_once(host->cert, "cert");
|
||||||
host->cert = ensure_absolute_path($2);
|
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 {
|
| KEY string {
|
||||||
only_once(host->key, "key");
|
only_once(host->key, "key");
|
||||||
host->key = ensure_absolute_path($2);
|
host->key = ensure_absolute_path($2);
|
||||||
|
@ -522,12 +505,9 @@ static const struct keyword {
|
||||||
{"block", BLOCK},
|
{"block", BLOCK},
|
||||||
{"ca", CA},
|
{"ca", CA},
|
||||||
{"cert", CERT},
|
{"cert", CERT},
|
||||||
{"cgi", CGI},
|
|
||||||
{"chroot", CHROOT},
|
{"chroot", CHROOT},
|
||||||
{"client", CLIENT},
|
{"client", CLIENT},
|
||||||
{"default", DEFAULT},
|
{"default", DEFAULT},
|
||||||
{"entrypoint", ENTRYPOINT},
|
|
||||||
{"env", ENV},
|
|
||||||
{"fastcgi", FASTCGI},
|
{"fastcgi", FASTCGI},
|
||||||
{"for-host", FOR_HOST},
|
{"for-host", FOR_HOST},
|
||||||
{"include", INCLUDE},
|
{"include", INCLUDE},
|
||||||
|
|
|
@ -40,21 +40,16 @@ run_test test_custom_mime
|
||||||
run_test test_default_type
|
run_test test_default_type
|
||||||
run_test test_custom_lang
|
run_test test_custom_lang
|
||||||
run_test test_parse_custom_lang_per_location
|
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
|
||||||
run_test test_custom_index_default_type_per_location
|
run_test test_custom_index_default_type_per_location
|
||||||
run_test test_auto_index
|
run_test test_auto_index
|
||||||
run_test test_block
|
run_test test_block
|
||||||
run_test test_block_return_fmt
|
run_test test_block_return_fmt
|
||||||
run_test test_entrypoint
|
|
||||||
run_test test_require_client_ca
|
run_test test_require_client_ca
|
||||||
run_test test_root_inside_location
|
run_test test_root_inside_location
|
||||||
run_test test_root_inside_location_with_redirect
|
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_macro_expansion
|
||||||
run_test test_174_bugfix
|
|
||||||
run_test test_proxy_relay_to
|
run_test test_proxy_relay_to
|
||||||
run_test test_proxy_with_certs
|
run_test test_proxy_with_certs
|
||||||
run_test test_unknown_host
|
run_test test_unknown_host
|
||||||
|
|
|
@ -92,61 +92,6 @@ test_parse_custom_lang_per_location() {
|
||||||
# can parse multiple locations
|
# 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() {
|
test_custom_index() {
|
||||||
setup_simple_test '' 'index "foo.gmi"'
|
setup_simple_test '' 'index "foo.gmi"'
|
||||||
|
|
||||||
|
@ -222,28 +167,6 @@ location "*" {
|
||||||
check_reply "40 % / 10965 localhost test" || return 1
|
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() {
|
test_require_client_ca() {
|
||||||
setup_simple_test '' 'require client ca "'$PWD'/testca.pem"'
|
setup_simple_test '' 'require client ca "'$PWD'/testca.pem"'
|
||||||
|
|
||||||
|
@ -313,18 +236,6 @@ EOF
|
||||||
check_reply "20 text/gemini" "# hello world"
|
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() {
|
test_proxy_relay_to() {
|
||||||
gen_config '' ''
|
gen_config '' ''
|
||||||
set_proxy ''
|
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");
|
fatal("pledge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
487
server.c
487
server.c
|
@ -17,6 +17,7 @@
|
||||||
#include "gmid.h"
|
#include "gmid.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.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 int check_path(struct client*, const char*, int*);
|
||||||
static void open_file(struct client*);
|
static void open_file(struct client*);
|
||||||
static void check_for_cgi(struct client*);
|
|
||||||
static void handle_handshake(int, short, void*);
|
static void handle_handshake(int, short, void*);
|
||||||
static const char *strip_path(const char*, int);
|
static const char *strip_path(const char*, int);
|
||||||
static void fmt_sbuf(const char*, struct client*, const char*);
|
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_reverse_proxy(struct client *);
|
||||||
static int apply_fastcgi(struct client*);
|
static int apply_fastcgi(struct client*);
|
||||||
static int apply_require_ca(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 open_dir(struct client*);
|
||||||
static void redirect_canonical_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 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 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_imsg_quit(struct imsgbuf*, struct imsg*, size_t);
|
||||||
static void handle_dispatch_imsg(int, short, void *);
|
static void handle_dispatch_imsg(int, short, void *);
|
||||||
static void handle_siginfo(int, short, void*);
|
static void handle_siginfo(int, short, void*);
|
||||||
|
|
||||||
static imsg_handlerfn *handlers[] = {
|
static imsg_handlerfn *handlers[] = {
|
||||||
[IMSG_QUIT] = handle_imsg_quit,
|
[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;
|
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))
|
if (S_ISDIR(sb.st_mode))
|
||||||
return FILE_DIRECTORY;
|
return FILE_DIRECTORY;
|
||||||
|
|
||||||
if (sb.st_mode & S_IXUSR)
|
|
||||||
return FILE_EXECUTABLE;
|
|
||||||
|
|
||||||
return FILE_EXISTS;
|
return FILE_EXISTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,14 +344,6 @@ static void
|
||||||
open_file(struct client *c)
|
open_file(struct client *c)
|
||||||
{
|
{
|
||||||
switch (check_path(c, c->iri.path, &c->pfd)) {
|
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:
|
case FILE_EXISTS:
|
||||||
c->type = REQUEST_FILE;
|
c->type = REQUEST_FILE;
|
||||||
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
||||||
|
@ -377,10 +354,6 @@ open_file(struct client *c)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case FILE_MISSING:
|
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");
|
start_reply(c, NOT_FOUND, "not found");
|
||||||
return;
|
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
|
void
|
||||||
mark_nonblock(int fd)
|
mark_nonblock(int fd)
|
||||||
{
|
{
|
||||||
|
@ -653,12 +577,52 @@ check_matching_certificate(X509_STORE *store, struct client *c)
|
||||||
return 0;
|
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 */
|
/* 1 if matching a proxy relay-to (and apply it), 0 otherwise */
|
||||||
static int
|
static int
|
||||||
apply_reverse_proxy(struct client *c)
|
apply_reverse_proxy(struct client *c)
|
||||||
{
|
{
|
||||||
struct proxy *p;
|
struct proxy *p;
|
||||||
struct connreq r;
|
|
||||||
|
|
||||||
if ((p = matched_proxy(c)) == NULL)
|
if ((p = matched_proxy(c)) == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -671,15 +635,80 @@ apply_reverse_proxy(struct client *c)
|
||||||
log_debug(c, "opening proxy connection for %s:%s",
|
log_debug(c, "opening proxy connection for %s:%s",
|
||||||
p->host, p->port);
|
p->host, p->port);
|
||||||
|
|
||||||
strlcpy(r.host, p->host, sizeof(r.host));
|
if ((c->pfd = proxy_socket(c, p->host, p->port)) == -1) {
|
||||||
strlcpy(r.port, p->port, sizeof(r.port));
|
start_reply(c, PROXY_ERROR, "proxy error");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
imsg_compose(&exibuf, IMSG_CONN_REQ, c->id, 0, -1, &r, sizeof(r));
|
mark_nonblock(c->pfd);
|
||||||
imsg_flush(&exibuf);
|
if (proxy_init(c) == -1)
|
||||||
|
start_reply(c, PROXY_ERROR, "proxy error");
|
||||||
|
|
||||||
return 1;
|
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 */
|
/* 1 if matching `fcgi' (and apply it), 0 otherwise */
|
||||||
static int
|
static int
|
||||||
apply_fastcgi(struct client *c)
|
apply_fastcgi(struct client *c)
|
||||||
|
@ -692,12 +721,31 @@ apply_fastcgi(struct client *c)
|
||||||
|
|
||||||
f = &fcgi[id];
|
f = &fcgi[id];
|
||||||
|
|
||||||
log_debug(c, "opening fastcgi connection for (%s,%s,%s)",
|
log_debug(c, "opening fastcgi connection for (%s,%s)",
|
||||||
f->path, f->port, f->prog);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,79 +760,6 @@ apply_require_ca(struct client *c)
|
||||||
return check_matching_certificate(store, 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
|
static void
|
||||||
open_dir(struct client *c)
|
open_dir(struct client *c)
|
||||||
{
|
{
|
||||||
|
@ -792,6 +767,8 @@ open_dir(struct client *c)
|
||||||
int dirfd, root;
|
int dirfd, root;
|
||||||
char *before_file;
|
char *before_file;
|
||||||
|
|
||||||
|
log_debug(c, "in open_dir");
|
||||||
|
|
||||||
root = !strcmp(c->iri.path, "/") || *c->iri.path == '\0';
|
root = !strcmp(c->iri.path, "/") || *c->iri.path == '\0';
|
||||||
|
|
||||||
len = strlen(c->iri.path);
|
len = strlen(c->iri.path);
|
||||||
|
@ -819,14 +796,6 @@ open_dir(struct client *c)
|
||||||
c->pfd = -1;
|
c->pfd = -1;
|
||||||
|
|
||||||
switch (check_path(c, c->iri.path, &c->pfd)) {
|
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:
|
case FILE_EXISTS:
|
||||||
c->type = REQUEST_FILE;
|
c->type = REQUEST_FILE;
|
||||||
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
start_reply(c, SUCCESS, mime(c->host, c->iri.path));
|
||||||
|
@ -1056,12 +1025,6 @@ client_read(struct bufferevent *bev, void *d)
|
||||||
apply_fastcgi(c))
|
apply_fastcgi(c))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (c->host->entrypoint != NULL) {
|
|
||||||
c->loc = 0;
|
|
||||||
start_cgi(c->host->entrypoint, c->iri.path, c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_file(c);
|
open_file(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1115,12 +1078,11 @@ client_write(struct bufferevent *bev, void *d)
|
||||||
event_add(&c->bev->ev_write, NULL);
|
event_add(&c->bev->ev_write, NULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REQUEST_CGI:
|
|
||||||
case REQUEST_FCGI:
|
case REQUEST_FCGI:
|
||||||
case REQUEST_PROXY:
|
case REQUEST_PROXY:
|
||||||
/*
|
/*
|
||||||
* Here we depend on the cgi/fastcgi or proxy
|
* Here we depend on fastcgi or proxy connection to
|
||||||
* connection to provide data.
|
* provide data.
|
||||||
*/
|
*/
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1177,8 +1139,7 @@ start_reply(struct client *c, int code, const char *meta)
|
||||||
if (r > 1027)
|
if (r > 1027)
|
||||||
goto overflow;
|
goto overflow;
|
||||||
|
|
||||||
if (c->type != REQUEST_CGI &&
|
if (c->type != REQUEST_FCGI &&
|
||||||
c->type != REQUEST_FCGI &&
|
|
||||||
c->type != REQUEST_PROXY &&
|
c->type != REQUEST_PROXY &&
|
||||||
!strcmp(meta, "text/gemini") &&
|
!strcmp(meta, "text/gemini") &&
|
||||||
(lang = vhost_lang(c->host, c->iri.path)) != NULL) {
|
(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);
|
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
|
static void
|
||||||
do_accept(int sock, short et, void *d)
|
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);
|
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
|
static void
|
||||||
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue