mirror of https://github.com/omar-polo/gmid.git
fastcgi: a first implementation
Not production-ready yet, but it's a start. This adds a third ``backend'' for gmid: until now there it served local files or CGI scripts, now FastCGI applications too. FastCGI is meant to be an improvement over CGI: instead of exec'ing a script for every request, it allows to open a single connection to an ``application'' and send the requests/receive the responses over that socket using a simple binary protocol. At the moment gmid supports three different methods of opening a fastcgi connection: - local unix sockets, with: fastcgi "/path/to/sock" - network sockets, with: fastcgi tcp "host" [port] port defaults to 9000 and can be either a string or a number - subprocess, with: fastcgi spawn "/path/to/program" the fastcgi protocol is done over the executed program stdin of these, the last is only for testing and may be removed in the future. P.S.: the fastcgi rule is per-location of course :)
This commit is contained in:
parent
50310aff33
commit
8ad1c57024
2
Makefile
2
Makefile
|
@ -14,7 +14,7 @@ y.tab.c: parse.y
|
|||
${YACC} -b y -d parse.y
|
||||
|
||||
SRCS = gmid.c iri.c utf8.c ex.c server.c sandbox.c mime.c puny.c \
|
||||
utils.c log.c dirs.c
|
||||
utils.c log.c dirs.c fcgi.c
|
||||
OBJS = ${SRCS:.c=.o} lex.yy.o y.tab.o ${COMPAT}
|
||||
|
||||
gmid: ${OBJS}
|
||||
|
|
117
ex.c
117
ex.c
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include "gmid.h"
|
||||
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
@ -28,10 +30,12 @@
|
|||
#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_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_QUIT] = handle_imsg_quit,
|
||||
};
|
||||
|
@ -294,6 +298,119 @@ handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
|||
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_UNIX, 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, datalen);
|
||||
|
||||
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, id, 0, fd, NULL, 0);
|
||||
imsg_flush(ibuf);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* 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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Sometimes it can be useful to inspect the fastcgi traffic as
|
||||
* received by gmid.
|
||||
*
|
||||
* This will make gmid connect to a `debug.sock' socket (that must
|
||||
* exists) in the current directory and send there a copy of what gets
|
||||
* read. The socket can be created and monitored e.g. with
|
||||
*
|
||||
* rm -f debug.sock ; nc -Ulk ./debug.sock | hexdump -C
|
||||
*
|
||||
* NB: the sandbox must be disabled for this to work.
|
||||
*/
|
||||
#define DEBUG_FCGI 0
|
||||
|
||||
#ifdef DEBUG_FCGI
|
||||
# include <sys/un.h>
|
||||
static int debug_socket = -1;
|
||||
#endif
|
||||
|
||||
struct fcgi_header {
|
||||
unsigned char version;
|
||||
unsigned char type;
|
||||
unsigned char req_id1;
|
||||
unsigned char req_id0;
|
||||
unsigned char content_len1;
|
||||
unsigned char content_len0;
|
||||
unsigned char padding;
|
||||
unsigned char reserved;
|
||||
};
|
||||
|
||||
/*
|
||||
* number of bytes in a FCGI_HEADER. Future version of the protocol
|
||||
* will not reduce this number.
|
||||
*/
|
||||
#define FCGI_HEADER_LEN 8
|
||||
|
||||
/*
|
||||
* values for the version component
|
||||
*/
|
||||
#define FCGI_VERSION_1 1
|
||||
|
||||
/*
|
||||
* values for the type component
|
||||
*/
|
||||
#define FCGI_BEGIN_REQUEST 1
|
||||
#define FCGI_ABORT_REQUEST 2
|
||||
#define FCGI_END_REQUEST 3
|
||||
#define FCGI_PARAMS 4
|
||||
#define FCGI_STDIN 5
|
||||
#define FCGI_STDOUT 6
|
||||
#define FCGI_STDERR 7
|
||||
#define FCGI_DATA 8
|
||||
#define FCGI_GET_VALUES 9
|
||||
#define FCGI_GET_VALUES_RESULT 10
|
||||
#define FCGI_UNKNOWN_TYPE 11
|
||||
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
|
||||
|
||||
struct fcgi_begin_req {
|
||||
unsigned char role1;
|
||||
unsigned char role0;
|
||||
unsigned char flags;
|
||||
unsigned char reserved[5];
|
||||
};
|
||||
|
||||
struct fcgi_begin_req_record {
|
||||
struct fcgi_header header;
|
||||
struct fcgi_begin_req body;
|
||||
};
|
||||
|
||||
/*
|
||||
* mask for flags;
|
||||
*/
|
||||
#define FCGI_KEEP_CONN 1
|
||||
|
||||
/*
|
||||
* values for the role
|
||||
*/
|
||||
#define FCGI_RESPONDER 1
|
||||
#define FCGI_AUTHORIZER 2
|
||||
#define FCGI_FILTER 3
|
||||
|
||||
struct fcgi_end_req_body {
|
||||
unsigned char app_status3;
|
||||
unsigned char app_status2;
|
||||
unsigned char app_status1;
|
||||
unsigned char app_status0;
|
||||
unsigned char proto_status;
|
||||
unsigned char reserved[3];
|
||||
};
|
||||
|
||||
/*
|
||||
* values for proto_status
|
||||
*/
|
||||
#define FCGI_REQUEST_COMPLETE 0
|
||||
#define FCGI_CANT_MPX_CONN 1
|
||||
#define FCGI_OVERLOADED 2
|
||||
#define FCGI_UNKNOWN_ROLE 3
|
||||
|
||||
/*
|
||||
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
|
||||
* records.
|
||||
*/
|
||||
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
|
||||
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
|
||||
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
|
||||
|
||||
static int
|
||||
prepare_header(struct fcgi_header *h, int type, int id, size_t size,
|
||||
size_t padding)
|
||||
{
|
||||
memset(h, 0, sizeof(*h));
|
||||
|
||||
/*
|
||||
* id=0 is reserved for status messages.
|
||||
*/
|
||||
id++;
|
||||
|
||||
h->version = FCGI_VERSION_1;
|
||||
h->type = type;
|
||||
h->req_id1 = (id >> 8);
|
||||
h->req_id0 = (id & 0xFF);
|
||||
h->content_len1 = (size >> 8);
|
||||
h->content_len0 = (size & 0xFF);
|
||||
h->padding = padding;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_begin_request(int sock, int id)
|
||||
{
|
||||
struct fcgi_begin_req_record r;
|
||||
|
||||
if (id > UINT16_MAX)
|
||||
return -1;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
prepare_header(&r.header, FCGI_BEGIN_REQUEST, id,
|
||||
sizeof(r.body), 0);
|
||||
assert(sizeof(r.body) == FCGI_HEADER_LEN);
|
||||
|
||||
r.body.role1 = 0;
|
||||
r.body.role0 = FCGI_RESPONDER;
|
||||
r.body.flags = FCGI_KEEP_CONN;
|
||||
|
||||
if (write(sock, &r, sizeof(r)) != sizeof(r))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_send_param(int sock, int id, const char *name, const char *value)
|
||||
{
|
||||
struct fcgi_header h;
|
||||
uint32_t namlen, vallen, padlen;
|
||||
uint8_t s[8];
|
||||
size_t size;
|
||||
char padding[8] = { 0 };
|
||||
|
||||
namlen = strlen(name);
|
||||
vallen = strlen(value);
|
||||
size = namlen + vallen + 8; /* 4 for the sizes */
|
||||
padlen = (8 - (size & 0x7)) & 0x7;
|
||||
|
||||
s[0] = ( namlen >> 24) | 0x80;
|
||||
s[1] = ((namlen >> 16) & 0xFF);
|
||||
s[2] = ((namlen >> 8) & 0xFF);
|
||||
s[3] = ( namlen & 0xFF);
|
||||
|
||||
s[4] = ( vallen >> 24) | 0x80;
|
||||
s[5] = ((vallen >> 16) & 0xFF);
|
||||
s[6] = ((vallen >> 8) & 0xFF);
|
||||
s[7] = ( vallen & 0xFF);
|
||||
|
||||
prepare_header(&h, FCGI_PARAMS, id, size, padlen);
|
||||
|
||||
if (write(sock, &h, sizeof(h)) != sizeof(h) ||
|
||||
write(sock, s, sizeof(s)) != sizeof(s) ||
|
||||
write(sock, name, namlen) != namlen ||
|
||||
write(sock, value, vallen) != vallen ||
|
||||
write(sock, padding, padlen) != padlen)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_end_param(int sock, int id)
|
||||
{
|
||||
struct fcgi_header h;
|
||||
|
||||
prepare_header(&h, FCGI_PARAMS, id, 0, 0);
|
||||
if (write(sock, &h, sizeof(h)) != sizeof(h))
|
||||
return -1;
|
||||
|
||||
prepare_header(&h, FCGI_STDIN, id, 0, 0);
|
||||
if (write(sock, &h, sizeof(h)) != sizeof(h))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_abort_request(int sock, int id)
|
||||
{
|
||||
struct fcgi_header h;
|
||||
|
||||
prepare_header(&h, FCGI_ABORT_REQUEST, id, 0, 0);
|
||||
if (write(sock, &h, sizeof(h)) != sizeof(h))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
must_read(int sock, char *d, size_t len)
|
||||
{
|
||||
ssize_t r;
|
||||
|
||||
#if DEBUG_FCGI
|
||||
if (debug_socket == -1) {
|
||||
struct sockaddr_un addr;
|
||||
|
||||
if ((debug_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
|
||||
err(1, "socket");
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strlcpy(addr.sun_path, "./debug.sock", sizeof(addr.sun_path));
|
||||
if (connect(debug_socket, (struct sockaddr*)&addr, sizeof(addr))
|
||||
== -1)
|
||||
err(1, "connect");
|
||||
}
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
switch (r = read(sock, d, len)) {
|
||||
case -1:
|
||||
case 0:
|
||||
return -1;
|
||||
default:
|
||||
#if DEBUG_FCGI
|
||||
write(debug_socket, d, r);
|
||||
#endif
|
||||
|
||||
if (r == (ssize_t)len)
|
||||
return 0;
|
||||
len -= r;
|
||||
d += r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fcgi_read_header(int sock, struct fcgi_header *h)
|
||||
{
|
||||
if (must_read(sock, (char*)h, sizeof(*h)) == -1)
|
||||
return -1;
|
||||
if (h->version != FCGI_VERSION_1) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
recid(struct fcgi_header *h)
|
||||
{
|
||||
return h->req_id0 + (h->req_id1 << 8) - 1;
|
||||
}
|
||||
|
||||
static inline int
|
||||
reclen(struct fcgi_header *h)
|
||||
{
|
||||
return h->content_len0 + (h->content_len1 << 8);
|
||||
}
|
||||
|
||||
static void
|
||||
copy_mbuf(int fd, short ev, void *d)
|
||||
{
|
||||
struct client *c = d;
|
||||
struct mbuf *mbuf;
|
||||
size_t len;
|
||||
ssize_t r;
|
||||
char *data;
|
||||
|
||||
for (;;) {
|
||||
mbuf = TAILQ_FIRST(&c->mbufhead);
|
||||
if (mbuf == NULL)
|
||||
break;
|
||||
|
||||
len = mbuf->len - mbuf->off;
|
||||
data = mbuf->data + mbuf->off;
|
||||
switch (r = tls_write(c->ctx, data, len)) {
|
||||
case -1:
|
||||
/*
|
||||
* Can't close_conn here. The application
|
||||
* needs to be informed first, otherwise it
|
||||
* can interfere with future connections.
|
||||
* Check also that we're not doing recursion
|
||||
* (copy_mbuf -> handle_fcgi -> copy_mbuf ...)
|
||||
*/
|
||||
if (c->next != NULL)
|
||||
goto end;
|
||||
fcgi_abort_request(0, c->id);
|
||||
return;
|
||||
case TLS_WANT_POLLIN:
|
||||
event_once(c->fd, EV_READ, ©_mbuf, c, NULL);
|
||||
return;
|
||||
case TLS_WANT_POLLOUT:
|
||||
event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL);
|
||||
return;
|
||||
}
|
||||
mbuf->off += r;
|
||||
|
||||
if (mbuf->off == mbuf->len) {
|
||||
TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs);
|
||||
free(mbuf);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
if (c->next != NULL)
|
||||
c->next(0, 0, c);
|
||||
}
|
||||
|
||||
static int
|
||||
consume(int fd, size_t len)
|
||||
{
|
||||
size_t l;
|
||||
char buf[64];
|
||||
|
||||
while (len != 0) {
|
||||
if ((l = len) > sizeof(buf))
|
||||
l = sizeof(buf);
|
||||
if (must_read(fd, buf, l) == -1)
|
||||
return 0;
|
||||
len -= l;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
close_all(struct fcgi *f)
|
||||
{
|
||||
size_t i;
|
||||
struct client *c;
|
||||
|
||||
for (i = 0; i < MAX_USERS; i++) {
|
||||
c = &clients[i];
|
||||
|
||||
if (c->fcgi != f->id)
|
||||
continue;
|
||||
|
||||
if (c->code != 0)
|
||||
close_conn(0, 0, c);
|
||||
else
|
||||
start_reply(c, CGI_ERROR, "CGI error");
|
||||
}
|
||||
|
||||
event_del(&f->e);
|
||||
close(f->fd);
|
||||
f->fd = -1;
|
||||
f->s = FCGI_OFF;
|
||||
}
|
||||
|
||||
void
|
||||
handle_fcgi(int sock, short event, void *d)
|
||||
{
|
||||
struct fcgi *f = d;
|
||||
struct fcgi_header h;
|
||||
struct fcgi_end_req_body end;
|
||||
struct client *c;
|
||||
struct mbuf *mbuf;
|
||||
size_t len;
|
||||
|
||||
if (fcgi_read_header(sock, &h) == -1)
|
||||
goto err;
|
||||
|
||||
c = try_client_by_id(recid(&h));
|
||||
if (c == NULL || c->fcgi != f->id)
|
||||
goto err;
|
||||
|
||||
len = reclen(&h);
|
||||
|
||||
switch (h.type) {
|
||||
case FCGI_END_REQUEST:
|
||||
if (len != sizeof(end))
|
||||
goto err;
|
||||
if (must_read(sock, (char*)&end, sizeof(end)) == -1)
|
||||
goto err;
|
||||
/* TODO: do something with the status? */
|
||||
c->fcgi = -1;
|
||||
c->next = close_conn;
|
||||
event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL);
|
||||
break;
|
||||
|
||||
case FCGI_STDERR:
|
||||
/* discard stderr (for now) */
|
||||
if (!consume(sock, len))
|
||||
goto err;
|
||||
break;
|
||||
|
||||
case FCGI_STDOUT:
|
||||
if ((mbuf = calloc(1, sizeof(*mbuf) + len)) == NULL)
|
||||
fatal("calloc");
|
||||
mbuf->len = len;
|
||||
if (must_read(sock, mbuf->data, len) == -1) {
|
||||
free(mbuf);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&c->mbufhead)) {
|
||||
TAILQ_INSERT_HEAD(&c->mbufhead, mbuf, mbufs);
|
||||
event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL);
|
||||
} else
|
||||
TAILQ_INSERT_TAIL(&c->mbufhead, mbuf, mbufs);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_err(NULL, "got invalid fcgi record (type=%d)", h.type);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!consume(sock, h.padding))
|
||||
goto err;
|
||||
return;
|
||||
|
||||
err:
|
||||
close_all(f);
|
||||
}
|
||||
|
||||
void
|
||||
send_fcgi_req(struct fcgi *f, struct client *c)
|
||||
{
|
||||
c->next = NULL;
|
||||
|
||||
fcgi_begin_request(f->fd, c->id);
|
||||
fcgi_send_param(f->fd, c->id, "QUERY_STRING", c->iri.query);
|
||||
fcgi_send_param(f->fd, c->id, "GEMINI_URL_PATH", c->iri.path);
|
||||
fcgi_send_param(f->fd, c->id, "SERVER_SOFTWARE", "gmid/1.7");
|
||||
/* ... */
|
||||
|
||||
if (fcgi_end_param(f->fd, c->id) == -1)
|
||||
close_all(f);
|
||||
}
|
12
gmid.c
12
gmid.c
|
@ -26,6 +26,8 @@
|
|||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
struct fcgi fcgi[FCGI_MAX];
|
||||
|
||||
struct vhosthead hosts;
|
||||
|
||||
int sock4, sock6;
|
||||
|
@ -251,7 +253,7 @@ free_config(void)
|
|||
struct location *l, *tl;
|
||||
struct envlist *e, *te;
|
||||
struct alist *a, *ta;
|
||||
int v;
|
||||
int v, i;
|
||||
|
||||
v = conf.verbose;
|
||||
|
||||
|
@ -299,6 +301,14 @@ free_config(void)
|
|||
free(h);
|
||||
}
|
||||
|
||||
for (i = 0; i < FCGI_MAX; ++i) {
|
||||
if (fcgi[i].path == NULL && fcgi[i].prog == NULL)
|
||||
break;
|
||||
free(fcgi[i].path);
|
||||
free(fcgi[i].port);
|
||||
free(fcgi[i].prog);
|
||||
}
|
||||
|
||||
tls_free(ctx);
|
||||
tls_config_free(tlsconf);
|
||||
}
|
||||
|
|
54
gmid.h
54
gmid.h
|
@ -26,6 +26,7 @@
|
|||
#include <netinet/in.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <event.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
|
@ -55,8 +56,24 @@
|
|||
#define DOMAIN_NAME_LEN (253+1)
|
||||
#define LABEL_LEN (63+1)
|
||||
|
||||
#define FCGI_MAX 32
|
||||
#define PROC_MAX 16
|
||||
|
||||
struct fcgi {
|
||||
int id;
|
||||
char *path;
|
||||
char *port;
|
||||
char *prog;
|
||||
int fd;
|
||||
struct event e;
|
||||
|
||||
#define FCGI_OFF 0
|
||||
#define FCGI_INFLIGHT 1
|
||||
#define FCGI_READY 2
|
||||
int s;
|
||||
};
|
||||
extern struct fcgi fcgi[FCGI_MAX];
|
||||
|
||||
TAILQ_HEAD(lochead, location);
|
||||
struct location {
|
||||
const char *match;
|
||||
|
@ -69,6 +86,7 @@ struct location {
|
|||
int strip;
|
||||
X509_STORE *reqca;
|
||||
int disable_log;
|
||||
int fcgi;
|
||||
|
||||
const char *dir;
|
||||
int dirfd;
|
||||
|
@ -157,6 +175,14 @@ struct parser {
|
|||
const char *err;
|
||||
};
|
||||
|
||||
struct mbuf {
|
||||
size_t len;
|
||||
size_t off;
|
||||
TAILQ_ENTRY(mbuf) mbufs;
|
||||
char data[];
|
||||
};
|
||||
TAILQ_HEAD(mbufhead, mbuf);
|
||||
|
||||
struct client;
|
||||
|
||||
typedef void (imsg_handlerfn)(struct imsgbuf*, struct imsg*, size_t);
|
||||
|
@ -171,6 +197,7 @@ typedef void (*statefn)(int, short, void*);
|
|||
* handle_handshake -> close_conn // on err
|
||||
*
|
||||
* handle_open_conn -> handle_cgi_reply // via open_file/dir/...
|
||||
* handle_open_conn -> send_fcgi_req // via apply_fastcgi, IMSG_FCGI_FD
|
||||
* handle_open_conn -> handle_dirlist // ...same
|
||||
* handle_open_conn -> send_file // ...same
|
||||
* handle_open_conn -> start_reply // on error
|
||||
|
@ -180,6 +207,10 @@ typedef void (*statefn)(int, short, void*);
|
|||
*
|
||||
* handle_cgi -> close_conn
|
||||
*
|
||||
* send_fcgi_req -> copy_mbuf // via handle_fcgi
|
||||
* handle_fcgi -> close_all // on error
|
||||
* copy_mbuf -> close_conn // on success/error
|
||||
*
|
||||
* handle_dirlist -> send_directory_listing
|
||||
* handle_dirlist -> close_conn // on error
|
||||
*
|
||||
|
@ -193,21 +224,33 @@ struct client {
|
|||
char req[GEMINI_URL_LEN];
|
||||
struct iri iri;
|
||||
char domain[DOMAIN_NAME_LEN];
|
||||
|
||||
/*
|
||||
* start_reply uses this to know what function call after the
|
||||
* reply. It's also used as sentinel value in fastcgi to know
|
||||
* if the server has closed the request.
|
||||
*/
|
||||
statefn next;
|
||||
|
||||
int code;
|
||||
const char *meta;
|
||||
int fd, pfd;
|
||||
struct dirent **dir;
|
||||
int dirlen, diroff;
|
||||
int fcgi;
|
||||
|
||||
/* big enough to store STATUS + SPACE + META + CRLF */
|
||||
char sbuf[1029];
|
||||
ssize_t len, off;
|
||||
|
||||
struct mbufhead mbufhead;
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
struct vhost *host; /* host they're talking to */
|
||||
};
|
||||
|
||||
extern struct client clients[MAX_USERS];
|
||||
|
||||
struct cgireq {
|
||||
char buf[GEMINI_URL_LEN];
|
||||
|
||||
|
@ -248,6 +291,8 @@ enum {
|
|||
enum imsg_type {
|
||||
IMSG_CGI_REQ,
|
||||
IMSG_CGI_RES,
|
||||
IMSG_FCGI_REQ,
|
||||
IMSG_FCGI_FD,
|
||||
IMSG_LOG,
|
||||
IMSG_QUIT,
|
||||
};
|
||||
|
@ -298,11 +343,16 @@ const char *vhost_default_mime(struct vhost*, const char*);
|
|||
const char *vhost_index(struct vhost*, const char*);
|
||||
int vhost_auto_index(struct vhost*, const char*);
|
||||
int vhost_block_return(struct vhost*, const char*, int*, const char**);
|
||||
int vhost_fastcgi(struct vhost*, const char*);
|
||||
int vhost_dirfd(struct vhost*, const char*);
|
||||
int vhost_strip(struct vhost*, const char*);
|
||||
X509_STORE *vhost_require_ca(struct vhost*, const char*);
|
||||
int vhost_disable_log(struct vhost*, const char*);
|
||||
|
||||
void mark_nonblock(int);
|
||||
void start_reply(struct client*, int, const char*);
|
||||
void close_conn(int, short, void*);
|
||||
struct client *try_client_by_id(int);
|
||||
void loop(struct tls*, int, int, struct imsgbuf*);
|
||||
|
||||
/* dirs.c */
|
||||
|
@ -325,6 +375,10 @@ int send_fd(int, int);
|
|||
int recv_fd(int);
|
||||
int executor_main(struct imsgbuf*);
|
||||
|
||||
/* fcgi.c */
|
||||
void handle_fcgi(int, short, void*);
|
||||
void send_fcgi_req(struct fcgi*, struct client*);
|
||||
|
||||
/* sandbox.c */
|
||||
void sandbox_server_process(void);
|
||||
void sandbox_executor_process(void);
|
||||
|
|
3
lex.l
3
lex.l
|
@ -62,6 +62,7 @@ client return TCLIENT;
|
|||
default return TDEFAULT;
|
||||
entrypoint return TENTRYPOINT;
|
||||
env return TENV;
|
||||
fastcgi return TFASTCGI;
|
||||
index return TINDEX;
|
||||
ipv6 return TIPV6;
|
||||
key return TKEY;
|
||||
|
@ -76,7 +77,9 @@ require return TREQUIRE;
|
|||
return return TRETURN;
|
||||
root return TROOT;
|
||||
server return TSERVER;
|
||||
spawn return TSPAWN;
|
||||
strip return TSTRIP;
|
||||
tcp return TTCP;
|
||||
type return TTYPE;
|
||||
user return TUSER;
|
||||
|
||||
|
|
67
parse.y
67
parse.y
|
@ -47,6 +47,8 @@ int check_strip_no(int);
|
|||
int check_prefork_num(int);
|
||||
void advance_loc(void);
|
||||
void only_once(const void*, const char*);
|
||||
void only_oncei(int, const char*);
|
||||
int fastcgi_conf(char *, char *, char *);
|
||||
|
||||
%}
|
||||
|
||||
|
@ -60,7 +62,8 @@ void only_once(const void*, const char*);
|
|||
|
||||
%token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
|
||||
%token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
|
||||
%token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS
|
||||
%token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
|
||||
%token TFASTCGI TSPAWN
|
||||
|
||||
%token TERR
|
||||
|
||||
|
@ -203,6 +206,29 @@ locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
|
|||
only_once(loc->default_mime, "default type");
|
||||
loc->default_mime = $3;
|
||||
}
|
||||
| TFASTCGI TSPAWN TSTRING {
|
||||
only_oncei(loc->fcgi, "fastcgi");
|
||||
loc->fcgi = fastcgi_conf(NULL, NULL, $3);
|
||||
}
|
||||
| TFASTCGI TSTRING {
|
||||
only_oncei(loc->fcgi, "fastcgi");
|
||||
loc->fcgi = fastcgi_conf($2, NULL, NULL);
|
||||
}
|
||||
| TFASTCGI TTCP TSTRING TNUM {
|
||||
char *c;
|
||||
if (asprintf(&c, "%d", $4) == -1)
|
||||
err(1, "asprintf");
|
||||
only_oncei(loc->fcgi, "fastcgi");
|
||||
loc->fcgi = fastcgi_conf($3, c, NULL);
|
||||
}
|
||||
| TFASTCGI TTCP TSTRING {
|
||||
only_oncei(loc->fcgi, "fastcgi");
|
||||
loc->fcgi = fastcgi_conf($3, xstrdup("9000"), NULL);
|
||||
}
|
||||
| TFASTCGI TTCP TSTRING TSTRING {
|
||||
only_oncei(loc->fcgi, "fastcgi");
|
||||
loc->fcgi = fastcgi_conf($3, $4, NULL);
|
||||
}
|
||||
| TINDEX TSTRING {
|
||||
only_once(loc->index, "index");
|
||||
loc->index = $2;
|
||||
|
@ -241,6 +267,7 @@ new_location(void)
|
|||
|
||||
l = xcalloc(1, sizeof(*l));
|
||||
l->dirfd = -1;
|
||||
l->fcgi = -1;
|
||||
return l;
|
||||
}
|
||||
|
||||
|
@ -354,3 +381,41 @@ only_once(const void *ptr, const char *name)
|
|||
if (ptr != NULL)
|
||||
yyerror("`%s' specified more than once", name);
|
||||
}
|
||||
|
||||
void
|
||||
only_oncei(int i, const char *name)
|
||||
{
|
||||
if (i != -1)
|
||||
yyerror("`%s' specified more than once", name);
|
||||
}
|
||||
|
||||
int
|
||||
fastcgi_conf(char *path, char *port, char *prog)
|
||||
{
|
||||
struct fcgi *f;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FCGI_MAX; ++i) {
|
||||
f = &fcgi[i];
|
||||
|
||||
if (f->path == NULL) {
|
||||
f->id = i;
|
||||
f->path = path;
|
||||
f->port = port;
|
||||
f->prog = prog;
|
||||
return i;
|
||||
}
|
||||
|
||||
/* XXX: what to do with prog? */
|
||||
if (!strcmp(f->path, path) &&
|
||||
((port == NULL && f->port == NULL) ||
|
||||
!strcmp(f->port, port))) {
|
||||
free(path);
|
||||
free(port);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
yyerror("too much `fastcgi' rules defined.");
|
||||
return -1;
|
||||
}
|
||||
|
|
23
sandbox.c
23
sandbox.c
|
@ -304,6 +304,8 @@ sandbox_executor_process(void)
|
|||
{
|
||||
struct vhost *h;
|
||||
struct location *l;
|
||||
struct fcgi *f;
|
||||
size_t i;
|
||||
|
||||
TAILQ_FOREACH(h, &hosts, vhosts) {
|
||||
TAILQ_FOREACH(l, &h->locations, locations) {
|
||||
|
@ -317,8 +319,25 @@ sandbox_executor_process(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* rpath to chdir into the correct directory */
|
||||
if (pledge("stdio rpath sendfd proc exec", NULL))
|
||||
for (i = 0; i < FCGI_MAX; i++) {
|
||||
f = &fcgi[i];
|
||||
if (f->path != NULL) {
|
||||
if (unveil(f->path, "rw") == -1)
|
||||
fatal("unveil %s", f->path);
|
||||
}
|
||||
|
||||
if (f->prog != NULL) {
|
||||
if (unveil(f->prog, "rx") == -1)
|
||||
fatal("unveil %s", f->prog);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* rpath: to chdir into the correct directory
|
||||
* proc exec: CGI
|
||||
* dns inet unix: FastCGI
|
||||
*/
|
||||
if (pledge("stdio rpath sendfd proc exec dns inet unix", NULL))
|
||||
err(1, "pledge");
|
||||
}
|
||||
|
||||
|
|
117
server.c
117
server.c
|
@ -26,7 +26,8 @@
|
|||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
static struct client clients[MAX_USERS];
|
||||
struct client clients[MAX_USERS];
|
||||
|
||||
static struct tls *ctx;
|
||||
|
||||
static struct event e4, e6, imsgev, siginfo, sigusr2;
|
||||
|
@ -48,7 +49,6 @@ static void fmt_sbuf(const char*, struct client*, const char*);
|
|||
static int apply_block_return(struct client*);
|
||||
static int apply_require_ca(struct client*);
|
||||
static void handle_open_conn(int, short, void*);
|
||||
static void start_reply(struct client*, int, const char*);
|
||||
static void handle_start_reply(int, short, void*);
|
||||
static size_t host_nth(struct vhost*);
|
||||
static void start_cgi(const char*, const char*, struct client*);
|
||||
|
@ -60,16 +60,16 @@ static int read_next_dir_entry(struct client*);
|
|||
static void send_directory_listing(int, short, void*);
|
||||
static void handle_cgi_reply(int, short, void*);
|
||||
static void handle_copy(int, short, void*);
|
||||
static void close_conn(int, short, void*);
|
||||
static void do_accept(int, short, void*);
|
||||
struct client *client_by_id(int);
|
||||
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_quit(struct imsgbuf*, struct imsg*, size_t);
|
||||
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,
|
||||
};
|
||||
|
||||
static inline int
|
||||
|
@ -202,6 +202,25 @@ vhost_block_return(struct vhost *v, const char *path, int *code, const char **fm
|
|||
return loc->block_code != 0;
|
||||
}
|
||||
|
||||
int
|
||||
vhost_fastcgi(struct vhost *v, const char *path)
|
||||
{
|
||||
struct location *loc;
|
||||
|
||||
if (v == NULL || path == NULL)
|
||||
return -1;
|
||||
|
||||
loc = TAILQ_FIRST(&v->locations);
|
||||
while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
|
||||
if (loc->fcgi != -1)
|
||||
if (matches(loc->match, path))
|
||||
return loc->fcgi;
|
||||
}
|
||||
|
||||
loc = TAILQ_FIRST(&v->locations);
|
||||
return loc->fcgi;
|
||||
}
|
||||
|
||||
int
|
||||
vhost_dirfd(struct vhost *v, const char *path)
|
||||
{
|
||||
|
@ -556,6 +575,37 @@ apply_block_return(struct client *c)
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* 1 if matching `fcgi' (and apply it), 0 otherwise */
|
||||
static int
|
||||
apply_fastcgi(struct client *c)
|
||||
{
|
||||
int id;
|
||||
struct fcgi *f;
|
||||
|
||||
if ((id = vhost_fastcgi(c->host, c->iri.path)) == -1)
|
||||
return 0;
|
||||
|
||||
switch ((f = &fcgi[id])->s) {
|
||||
case FCGI_OFF:
|
||||
f->s = FCGI_INFLIGHT;
|
||||
log_info(c, "opening fastcgi connection for (%s,%s,%s)",
|
||||
f->path, f->port, f->prog);
|
||||
imsg_compose(&exibuf, IMSG_FCGI_REQ, 0, 0, -1,
|
||||
&id, sizeof(id));
|
||||
imsg_flush(&exibuf);
|
||||
/* fallthrough */
|
||||
case FCGI_INFLIGHT:
|
||||
c->fcgi = id;
|
||||
break;
|
||||
case FCGI_READY:
|
||||
c->fcgi = id;
|
||||
send_fcgi_req(f, c);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 1 if matching `require client ca' fails (and apply it), 0 otherwise */
|
||||
static int
|
||||
apply_require_ca(struct client *c)
|
||||
|
@ -627,6 +677,9 @@ handle_open_conn(int fd, short ev, void *d)
|
|||
if (apply_block_return(c))
|
||||
return;
|
||||
|
||||
if (apply_fastcgi(c))
|
||||
return;
|
||||
|
||||
if (c->host->entrypoint != NULL) {
|
||||
start_cgi(c->host->entrypoint, c->iri.path, c);
|
||||
return;
|
||||
|
@ -635,7 +688,7 @@ handle_open_conn(int fd, short ev, void *d)
|
|||
open_file(c);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
start_reply(struct client *c, int code, const char *meta)
|
||||
{
|
||||
c->code = code;
|
||||
|
@ -1039,10 +1092,11 @@ end:
|
|||
close_conn(c->fd, ev, d);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
close_conn(int fd, short ev, void *d)
|
||||
{
|
||||
struct client *c = d;
|
||||
struct client *c = d;
|
||||
struct mbuf *mbuf;
|
||||
|
||||
switch (tls_close(c->ctx)) {
|
||||
case TLS_WANT_POLLIN:
|
||||
|
@ -1055,6 +1109,11 @@ close_conn(int fd, short ev, void *d)
|
|||
|
||||
connected_clients--;
|
||||
|
||||
while ((mbuf = TAILQ_FIRST(&c->mbufhead)) != NULL) {
|
||||
TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs);
|
||||
free(mbuf);
|
||||
}
|
||||
|
||||
tls_free(c->ctx);
|
||||
c->ctx = NULL;
|
||||
|
||||
|
@ -1101,6 +1160,7 @@ do_accept(int sock, short et, void *d)
|
|||
c->pfd = -1;
|
||||
c->dir = NULL;
|
||||
c->addr = addr;
|
||||
c->fcgi = -1;
|
||||
|
||||
yield_read(fd, c, &handle_handshake);
|
||||
connected_clients++;
|
||||
|
@ -1111,7 +1171,7 @@ do_accept(int sock, short et, void *d)
|
|||
close(fd);
|
||||
}
|
||||
|
||||
struct client *
|
||||
static struct client *
|
||||
client_by_id(int id)
|
||||
{
|
||||
if ((size_t)id > sizeof(clients)/sizeof(clients[0]))
|
||||
|
@ -1119,6 +1179,14 @@ client_by_id(int id)
|
|||
return &clients[id];
|
||||
}
|
||||
|
||||
struct client *
|
||||
try_client_by_id(int id)
|
||||
{
|
||||
if ((size_t)id > sizeof(clients)/sizeof(clients[0]))
|
||||
return NULL;
|
||||
return &clients[id];
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
|
@ -1132,6 +1200,39 @@ handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
|||
yield_read(c->pfd, c, &handle_cgi_reply);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_fcgi_fd(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
struct client *c;
|
||||
struct fcgi *f;
|
||||
int i, id;
|
||||
|
||||
id = imsg->hdr.peerid;
|
||||
f = &fcgi[id];
|
||||
|
||||
if ((f->fd = imsg->fd) != -1) {
|
||||
event_set(&f->e, imsg->fd, EV_READ | EV_PERSIST, &handle_fcgi,
|
||||
&fcgi[id]);
|
||||
event_add(&f->e, NULL);
|
||||
} else {
|
||||
f->s = FCGI_OFF;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_USERS; ++i) {
|
||||
c = &clients[i];
|
||||
if (c->fd == -1)
|
||||
continue;
|
||||
if (c->fcgi != id)
|
||||
continue;
|
||||
|
||||
if (f->fd == -1) {
|
||||
c->fcgi = -1;
|
||||
start_reply(c, TEMP_FAILURE, "internal server error");
|
||||
} else
|
||||
send_fcgi_req(f, c);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue