aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--ex.c117
-rw-r--r--fcgi.c469
-rw-r--r--gmid.c12
-rw-r--r--gmid.h54
-rw-r--r--lex.l3
-rw-r--r--parse.y67
-rw-r--r--sandbox.c23
-rw-r--r--server.c117
9 files changed, 851 insertions, 13 deletions
diff --git a/Makefile b/Makefile
index 16f3bbc..521c5b2 100644
--- a/Makefile
+++ b/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}
diff --git a/ex.c b/ex.c
index 6a5effe..0dcad61 100644
--- a/ex.c
+++ b/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)
{
diff --git a/fcgi.c b/fcgi.c
new file mode 100644
index 0000000..fe4db2f
--- /dev/null
+++ b/fcgi.c
@@ -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, &copy_mbuf, c, NULL);
+ return;
+ case TLS_WANT_POLLOUT:
+ event_once(c->fd, EV_WRITE, &copy_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, &copy_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, &copy_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);
+}
diff --git a/gmid.c b/gmid.c
index bdf5bd9..e04e172 100644
--- a/gmid.c
+++ b/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);
}
diff --git a/gmid.h b/gmid.h
index 752653a..c97fd23 100644
--- a/gmid.h
+++ b/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);
diff --git a/lex.l b/lex.l
index 1aa87f2..eadbb18 100644
--- a/lex.l
+++ b/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;
diff --git a/parse.y b/parse.y
index 692037a..e3eff65 100644
--- a/parse.y
+++ b/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;
+}
diff --git a/sandbox.c b/sandbox.c
index 4e10739..d2236d7 100644
--- a/sandbox.c
+++ b/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");
}
diff --git a/server.c b/server.c
index 79c7d9c..7bc783b 100644
--- a/server.c
+++ b/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
@@ -203,6 +203,25 @@ vhost_block_return(struct vhost *v, const char *path, int *code, const char **fm
}
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)
{
struct location *loc;
@@ -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)
{
@@ -1133,6 +1201,39 @@ handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg *imsg, size_t len)
}
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)
{
(void)imsg;