mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
1211 lines
26 KiB
C
1211 lines
26 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (c) 2014--2016, 2019 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
*
|
|
* 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 "config.h"
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "kcgiregress.h"
|
|
|
|
#define FCGI_HDR_SIZE 8
|
|
|
|
/*
|
|
* The FastCGI header.
|
|
* This is duplicated elsewhere in the code, but is harmless as it isn't
|
|
* really going to change.
|
|
*/
|
|
struct fcgi_hdr {
|
|
uint8_t version;
|
|
uint8_t type;
|
|
#define FCGI_HDR_END 3
|
|
#define FCGI_HDR_HEAD 4
|
|
#define FCGI_HDR_DATA_IN 5
|
|
#define FCGI_HDR_DATA_OUT 6
|
|
uint16_t requestId;
|
|
uint16_t contentLength;
|
|
uint8_t paddingLength;
|
|
uint8_t reserved;
|
|
};
|
|
|
|
/*
|
|
* Non-blocking read.
|
|
* Returns <0 on failure (system error) or bytes read, which may be
|
|
* zero in the event of EOF.
|
|
*/
|
|
static ssize_t
|
|
nb_read(int fd, void *buf, size_t bufsz)
|
|
{
|
|
struct pollfd pfd;
|
|
int rc;
|
|
|
|
assert(buf != NULL && bufsz);
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
|
|
if ((rc = poll(&pfd, 1, -1)) < 0) {
|
|
perror("poll");
|
|
return (-1);
|
|
} else if (rc == 0) {
|
|
fprintf(stderr, "poll: timeout!?\n");
|
|
return (-1);
|
|
}
|
|
|
|
return read(fd, buf, bufsz);
|
|
}
|
|
|
|
/*
|
|
* Non-blocking write of "bufsz" bytes in "buf".
|
|
* Return TRUE on success, FALSE on failure (system error).
|
|
*/
|
|
static int
|
|
nb_write(int fd, const void *buf, size_t bufsz)
|
|
{
|
|
ssize_t ssz;
|
|
size_t sz;
|
|
struct pollfd pfd;
|
|
int rc;
|
|
|
|
if (buf == NULL || bufsz == 0)
|
|
return 1;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLOUT;
|
|
|
|
for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
|
|
if ((rc = poll(&pfd, 1, -1)) < 0)
|
|
perror("poll");
|
|
else if (rc == 0)
|
|
fprintf(stderr, "poll: timeout!?\n");
|
|
else if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0)
|
|
perror("write");
|
|
else if (sz > SIZE_MAX - (size_t)ssz)
|
|
fprintf(stderr, "write: overflow: "
|
|
"%zu, %zd", sz, ssz);
|
|
else
|
|
continue;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Blocking write of "sz" bytes in "buf".
|
|
* Return TRUE on success, FALSE on failure (system error).
|
|
*/
|
|
static int
|
|
b_write(int fd, const void *buf, size_t sz)
|
|
{
|
|
ssize_t ssz;
|
|
size_t wsz = 0;
|
|
|
|
while (sz > 0) {
|
|
if ((ssz = write(fd, buf + wsz, sz)) == -1) {
|
|
perror("write");
|
|
return 0;
|
|
}
|
|
sz -= (size_t)ssz;
|
|
wsz += (size_t)ssz;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Blocking read (discarding bytes).
|
|
* Discards "sz" bytes and returns TRUE on success, FALSE on failure
|
|
* (system error or EOF).
|
|
*/
|
|
static int
|
|
b_ignore(int fd, size_t sz)
|
|
{
|
|
ssize_t ssz;
|
|
char buf;
|
|
|
|
while (sz > 0) {
|
|
if ((ssz = read(fd, &buf, 1)) == -1) {
|
|
perror("read");
|
|
return 0;
|
|
} else if (ssz == 0) {
|
|
fputs("read: unexpected EOF\n", stderr);
|
|
return 0;
|
|
}
|
|
sz--;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Blocking read.
|
|
* Reads "sz" bytes into buf and returns TRUE on success, FALSE on
|
|
* failure (system error or EOF).
|
|
*/
|
|
static int
|
|
b_read(int fd, void *buf, size_t sz)
|
|
{
|
|
ssize_t ssz;
|
|
size_t rsz = 0;
|
|
|
|
while (sz > 0) {
|
|
if ((ssz = read(fd, buf + rsz, sz)) == -1) {
|
|
perror("read");
|
|
return 0;
|
|
} else if (ssz == 0) {
|
|
fputs("read: unexpected EOF\n", stderr);
|
|
return 0;
|
|
}
|
|
sz -= (size_t)ssz;
|
|
rsz += (size_t)ssz;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Write a entire FastCGI header to the child.
|
|
* Note that the members need to be in network-byte order, so perform
|
|
* your htons and so on before invoking this function.
|
|
*/
|
|
static int
|
|
fcgi_hdr_write(int fd, const struct fcgi_hdr *hdr)
|
|
{
|
|
|
|
if (!b_write(fd, &hdr->version, 1))
|
|
fprintf(stderr, "%s: version\n", __func__);
|
|
else if (!b_write(fd, &hdr->type, 1))
|
|
fprintf(stderr, "%s: type\n", __func__);
|
|
else if (!b_write(fd, &hdr->requestId, 2))
|
|
fprintf(stderr, "%s: requestId\n", __func__);
|
|
else if (!b_write(fd, &hdr->contentLength, 2))
|
|
fprintf(stderr, "%s: data length\n", __func__);
|
|
else if (!b_write(fd, &hdr->paddingLength, 1))
|
|
fprintf(stderr, "%s: pad length\n", __func__);
|
|
else if (!b_write(fd, &hdr->reserved, 1))
|
|
fprintf(stderr, "%s: reserved\n", __func__);
|
|
else
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
fcgi_hdr_read(int fd, struct fcgi_hdr *hdr)
|
|
{
|
|
char buf[FCGI_HDR_SIZE];
|
|
|
|
if (!b_read(fd, buf, sizeof(buf))) {
|
|
fprintf(stderr, "%s: header\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
hdr->version = buf[0];
|
|
hdr->type = buf[1];
|
|
hdr->requestId = ntohs(*(uint16_t *)&buf[2]);
|
|
hdr->contentLength = ntohs(*(uint16_t *)&buf[4]);
|
|
hdr->paddingLength = buf[6];
|
|
hdr->reserved = buf[7];
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
fcgi_data_write(int fd, const void *buf, size_t sz)
|
|
{
|
|
struct fcgi_hdr hdr;
|
|
|
|
hdr.version = 1;
|
|
hdr.type = FCGI_HDR_DATA_IN;
|
|
hdr.requestId = htons(1);
|
|
hdr.contentLength = htons(sz);
|
|
hdr.paddingLength = 0;
|
|
hdr.reserved = 0;
|
|
|
|
if (!fcgi_hdr_write(fd, &hdr))
|
|
fprintf(stderr, "%s: header\n", __func__);
|
|
else if (!b_write(fd, buf, sz))
|
|
fprintf(stderr, "%s: data\n", __func__);
|
|
else
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
fcgi_end_read(int fd, int *status)
|
|
{
|
|
uint32_t st;
|
|
uint8_t pst;
|
|
uint8_t res[3];
|
|
|
|
if (!b_read(fd, &st, 4)) {
|
|
fprintf(stderr, "%s: status\n", __func__);
|
|
return 0;
|
|
} else if (!b_read(fd, &pst, 1)) {
|
|
fprintf(stderr, "%s: flags\n", __func__);
|
|
return 0;
|
|
} else if (!b_read(fd, res, sizeof(res))) {
|
|
fprintf(stderr, "%s: reserved\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We use EXIT_SUCCESS for our status message.
|
|
* See output.c for where this is set.
|
|
*/
|
|
|
|
*status = ntohl(st) == EXIT_SUCCESS ? 1 : 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
fcgi_begin_write(int fd)
|
|
{
|
|
struct fcgi_hdr hdr;
|
|
uint16_t role = htons(1);
|
|
uint8_t flags = 0;
|
|
uint8_t res[5];
|
|
|
|
hdr.version = 1;
|
|
hdr.type = 1;
|
|
hdr.requestId = htons(1);
|
|
hdr.contentLength = htons(8);
|
|
hdr.paddingLength = 0;
|
|
hdr.reserved = 0;
|
|
|
|
memset(res, 0, sizeof(res));
|
|
|
|
if (!fcgi_hdr_write(fd, &hdr))
|
|
fprintf(stderr, "%s: header\n", __func__);
|
|
else if (!b_write(fd, &role, 2))
|
|
fprintf(stderr, "%s: role\n", __func__);
|
|
else if (!b_write(fd, &flags, 1))
|
|
fprintf(stderr, "%s: flags\n", __func__);
|
|
else if (!b_write(fd, res, sizeof(res)))
|
|
fprintf(stderr, "%s: reserved\n", __func__);
|
|
else
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Set the environment variable for a CGI script.
|
|
* This involves actually setting the environment variable.
|
|
*/
|
|
static int
|
|
dochild_params_cgi(const char *key, const char *val, void *arg)
|
|
{
|
|
|
|
setenv(key, val, 1);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Set the environment variable for a FastCGI script.
|
|
* We'll need to bundle it into a FastCGI request and write that to the
|
|
* child.
|
|
*/
|
|
static int
|
|
dochild_params_fcgi(const char *key, const char *val, void *arg)
|
|
{
|
|
int fd = *(int *)arg;
|
|
struct fcgi_hdr hdr;
|
|
uint32_t lenl;
|
|
uint8_t lens;
|
|
size_t sz;
|
|
|
|
sz = strlen(key) + (strlen(key) > 127 ? 4 : 1) +
|
|
strlen(val) + (strlen(val) > 127 ? 4 : 1);
|
|
|
|
/* Start with the FastCGI header. */
|
|
|
|
hdr.version = 1;
|
|
hdr.type = FCGI_HDR_HEAD;
|
|
hdr.requestId = htons(1);
|
|
hdr.contentLength = htons(sz);
|
|
hdr.paddingLength = 0;
|
|
hdr.reserved = 0;
|
|
|
|
if (!fcgi_hdr_write(fd, &hdr)) {
|
|
fprintf(stderr, "%s: header\n", __func__);
|
|
return(0);
|
|
}
|
|
|
|
/* Key and value lengths. */
|
|
|
|
if ((sz = strlen(key)) > 127) {
|
|
lenl = htonl(sz);
|
|
if (!b_write(fd, &lenl, 4)) {
|
|
fprintf(stderr, "%s: key length", __func__);
|
|
return 0;
|
|
}
|
|
} else {
|
|
lens = sz;
|
|
if (!b_write(fd, &lens, 1)) {
|
|
fprintf(stderr, "%s: key length", __func__);
|
|
return 0;
|
|
}
|
|
}
|
|
if ((sz = strlen(val)) > 127) {
|
|
lenl = htonl(sz);
|
|
if (!b_write(fd, &lenl, 4)) {
|
|
fprintf(stderr, "%s: val length", __func__);
|
|
return 0;
|
|
}
|
|
} else {
|
|
lens = sz;
|
|
if (!b_write(fd, &lens, 1)) {
|
|
fprintf(stderr, "%s: val length", __func__);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Key and value data. */
|
|
if (!b_write(fd, key, strlen(key))) {
|
|
fprintf(stderr, "%s: key", __func__);
|
|
return 0;
|
|
} else if (!b_write(fd, val, strlen(val))) {
|
|
fprintf(stderr, "%s: val", __func__);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Parse HTTP header lines from input.
|
|
* This obviously isn't the best way of doing things, but it's simple
|
|
* and easy to fix for the purposes of this regression suite.
|
|
* This will continually parse lines from `fd' until it reaches a blank
|
|
* line, at which point it will return control.
|
|
* It returns zero on failure (read failure, or possibly write failure
|
|
* when serialising to the CGI context) or non-zero on success.
|
|
*/
|
|
static int
|
|
dochild_params(int fd, void *arg, size_t *length,
|
|
int (*fp)(const char *, const char *, void *))
|
|
{
|
|
int first;
|
|
char head[BUFSIZ], buf[BUFSIZ];
|
|
ssize_t ssz;
|
|
size_t sz;
|
|
char *cp, *path, *query, *key, *val;
|
|
char c;
|
|
extern char *__progname;
|
|
|
|
if (length != NULL)
|
|
*length = 0;
|
|
|
|
if (!fp("SCRIPT_NAME", __progname, arg))
|
|
return 0;
|
|
|
|
/*
|
|
* Read header lines without buffering and clobbering the data
|
|
* yet to be read on the wire.
|
|
* Process them as the environment input to our CGI.
|
|
*/
|
|
|
|
for (first = 1; ; ) {
|
|
/*
|
|
* Start by reading the header itself into a
|
|
* fixed-length buffer, making sure to respect that the
|
|
* descriptor is non-blocking.
|
|
*/
|
|
|
|
for (sz = 0; sz < BUFSIZ; ) {
|
|
ssz = nb_read(fd, &c, 1);
|
|
if (ssz < 0) {
|
|
perror("read");
|
|
return 0;
|
|
} else if (ssz == 0 || (head[sz++] = c) == '\n')
|
|
break;
|
|
}
|
|
|
|
/* Strip CRLF. */
|
|
|
|
if (sz < 2 || sz == BUFSIZ) {
|
|
fprintf(stderr, "Bad HTTP header\n");
|
|
return 0;
|
|
} else if (head[sz - 2] != '\r') {
|
|
fprintf(stderr, "Bad HTTP header CRLF\n");
|
|
return 0;
|
|
}
|
|
head[sz - 2] = '\0';
|
|
|
|
/* Empty line: now we're at the CGI document. */
|
|
|
|
if (head[0] == '\0')
|
|
break;
|
|
|
|
/* Process our header. */
|
|
|
|
if (first) {
|
|
/* Snarf the first GET/POST line. */
|
|
|
|
if (strncmp(head, "GET ", 4) == 0) {
|
|
if (!fp("REQUEST_METHOD", "GET", arg))
|
|
return 0;
|
|
path = head + 4;
|
|
} else if (strncmp(head, "POST ", 5) == 0) {
|
|
if (!fp("REQUEST_METHOD", "POST", arg))
|
|
return(0);
|
|
path = head + 5;
|
|
} else {
|
|
fprintf(stderr, "Unknown HTTP "
|
|
"first line: %s\n", head);
|
|
return 0;
|
|
}
|
|
|
|
/* Split this into the path and query. */
|
|
|
|
cp = path;
|
|
while (*cp != '\0' && !isspace((unsigned char)*cp))
|
|
cp++;
|
|
*cp = '\0';
|
|
if ((query = strchr(path, '?')) != NULL)
|
|
*query++ = '\0';
|
|
|
|
first = 0;
|
|
if (!fp("PATH_INFO", path, arg))
|
|
return 0;
|
|
if (query != NULL)
|
|
if (!fp("QUERY_STRING", query, arg))
|
|
return 0;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Split headers into key/value parts.
|
|
* Strip the leading spaces on the latter.
|
|
* Let baddies (no value) just go by.
|
|
*/
|
|
|
|
key = head;
|
|
if ((val = strchr(key, ':')) == NULL)
|
|
continue;
|
|
*val++ = '\0';
|
|
while (*val != '\0' && isspace((unsigned char)*val))
|
|
val++;
|
|
|
|
/* Recognise some attributes... */
|
|
|
|
if (strcmp(key, "Content-Length") == 0) {
|
|
if (length != NULL)
|
|
*length = atoi(val);
|
|
if (!fp("CONTENT_LENGTH", val, arg))
|
|
return 0;
|
|
continue;
|
|
} else if (strcmp(key, "Content-Type") == 0) {
|
|
if (!fp("CONTENT_TYPE", val, arg))
|
|
return 0;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Now we have "regular" attributes that we want to cast
|
|
* directly as HTTP attributes in the CGI environment.
|
|
*/
|
|
|
|
strlcpy(buf, "HTTP_", sizeof(buf));
|
|
sz = strlcat(buf, key, sizeof(buf));
|
|
assert(sz < sizeof(buf));
|
|
for (cp = buf; *cp != '\0'; cp++)
|
|
if (*cp == '-')
|
|
*cp = '_';
|
|
else if (isalpha((unsigned char)*cp))
|
|
*cp = toupper((unsigned char)*cp);
|
|
|
|
if (!fp(buf, val, arg))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* First, read regress().
|
|
* This is the "child" portion of that description.
|
|
* Bind to the local port over which we'll directly accept test requests
|
|
* from our regression suite.
|
|
* This port is acting as an ad hoc web server.
|
|
* Return the file descriptor on success or -1 on failure.
|
|
*/
|
|
static int
|
|
dochild_prepare(void)
|
|
{
|
|
int s, in, opt = 1;
|
|
struct sockaddr_in ad, rem;
|
|
socklen_t len;
|
|
|
|
memset(&ad, 0, sizeof(struct sockaddr_in));
|
|
|
|
/*
|
|
* Bind and listen to our reusable testing socket.
|
|
* We pretty much just choose a random port for this.
|
|
*/
|
|
|
|
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
|
|
perror("socket");
|
|
return (-1);
|
|
} else if (setsockopt(s, SOL_SOCKET,
|
|
SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
|
|
perror("setsockopt");
|
|
close(s);
|
|
return (-1);
|
|
}
|
|
|
|
ad.sin_family = AF_INET;
|
|
ad.sin_port = htons(KCGI_REGRESS_PORT);
|
|
ad.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
if (bind(s, (struct sockaddr *)&ad, sizeof(ad)) == -1) {
|
|
perror("bind");
|
|
close(s);
|
|
return (-1);
|
|
} else if (listen(s, 1) == -1) {
|
|
perror("listen");
|
|
close(s);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Tell our parent that we've bound to our socket and are ready
|
|
* to receive data.
|
|
* They'll do a waitpid() to see when we're sleeping, then wake
|
|
* us up when we're already ready to go.
|
|
*/
|
|
|
|
kill(getpid(), SIGSTOP);
|
|
|
|
/*
|
|
* Wait for a single testing connection.
|
|
* When we accept it, immediately note as non-blocking: kcgi(3)
|
|
* uses polling, so it will need a non-blocking descriptor.
|
|
*/
|
|
|
|
len = sizeof(struct sockaddr_in);
|
|
if ((in = accept(s, (struct sockaddr *)&rem, &len)) == -1) {
|
|
perror("accept");
|
|
close(s);
|
|
return (-1);
|
|
} else if (fcntl(in, F_SETFL, O_NONBLOCK) == -1) {
|
|
perror("fcntl: O_NONBLOCK");
|
|
close(s);
|
|
close(in);
|
|
return 0;
|
|
}
|
|
|
|
close(s);
|
|
return in;
|
|
}
|
|
|
|
/*
|
|
* Broker a FastCGI process child.
|
|
* Return zero on failure and non-zero on success.
|
|
*/
|
|
static int
|
|
dochild_fcgi(kcgi_regress_server child, void *carg)
|
|
{
|
|
int in, rc = 0, fd;
|
|
size_t sz, len, vecsz = 0, headsz;
|
|
pid_t pid;
|
|
struct sockaddr_un un;
|
|
struct sockaddr *ss;
|
|
ssize_t ssz;
|
|
extern char *__progname;
|
|
const char *end = NULL, *start, *msg, *ovec, *cp;
|
|
char sfn[22], buf[BUFSIZ];
|
|
char *vec = NULL;
|
|
void *pp;
|
|
struct fcgi_hdr hdr;
|
|
mode_t mode;
|
|
|
|
/*
|
|
* Create a temporary file, close it, then unlink it.
|
|
* The child will recreate this as a socket.
|
|
*/
|
|
|
|
strlcpy(sfn, "/tmp/kfcgi.XXXXXXXXXX", sizeof(sfn));
|
|
|
|
/* This shuts up Coverity. */
|
|
|
|
mode = umask(S_IXUSR | S_IRWXG | S_IRWXO);
|
|
if ((fd = mkstemp(sfn)) == -1) {
|
|
perror(sfn);
|
|
return 0;
|
|
} else if (close(fd) == -1 || unlink(sfn) == -1) {
|
|
perror(sfn);
|
|
return 0;
|
|
}
|
|
umask(mode);
|
|
|
|
/* Do the usual dance to set up UNIX sockets. */
|
|
|
|
ss = (struct sockaddr *)&un;
|
|
memset(&un, 0, sizeof(un));
|
|
un.sun_family = AF_UNIX;
|
|
sz = strlcpy(un.sun_path, sfn, sizeof(un.sun_path));
|
|
if (sz >= sizeof(un.sun_path)) {
|
|
fprintf(stderr, "socket path to long\n");
|
|
return 0;
|
|
}
|
|
#if !defined(__linux__) && !defined(__sun)
|
|
un.sun_len = sz;
|
|
#endif
|
|
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
perror("socket");
|
|
return 0;
|
|
} else if (bind(fd, ss, sizeof(un)) == -1) {
|
|
perror(sfn);
|
|
close(fd);
|
|
return 0;
|
|
} else if (listen(fd, 5) == -1) {
|
|
perror(sfn);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now fork the FastCGI process.
|
|
* We need to use a separate process because we're going to
|
|
* package the request as a FastCGI request and ship it into the
|
|
* UNIX socket.
|
|
*/
|
|
|
|
if ((pid = fork()) == -1) {
|
|
perror("fork");
|
|
unlink(sfn);
|
|
close(fd);
|
|
return 0;
|
|
} else if (pid == 0) {
|
|
if (dup2(fd, STDIN_FILENO) == -1)
|
|
_exit(EXIT_FAILURE);
|
|
close(fd);
|
|
return child(carg);
|
|
}
|
|
|
|
/*
|
|
* Close the socket, as we're going to connect to it.
|
|
* The child has a reference to the object (via its dup2), so
|
|
* we're not going to totally remove the file.
|
|
*/
|
|
|
|
close(fd);
|
|
fd = -1;
|
|
|
|
/* Get the next incoming connection and FILE-ise it. */
|
|
|
|
if ((in = dochild_prepare()) == -1)
|
|
goto out;
|
|
|
|
/*
|
|
* Open a new socket to the FastCGI object and connect to it,
|
|
* reusing the prior socket address.
|
|
* Then remove the object, as nobody needs it any more.
|
|
*/
|
|
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
perror(sfn);
|
|
goto out;
|
|
} else if (connect(fd, ss, sizeof(un)) == -1) {
|
|
perror(sfn);
|
|
goto out;
|
|
} else if (unlink(sfn) == -1) {
|
|
perror(sfn);
|
|
goto out;
|
|
}
|
|
sfn[0] = '\0';
|
|
|
|
/* Write the request, its parameters, and all data. */
|
|
|
|
if (!fcgi_begin_write(fd))
|
|
goto out;
|
|
if (!dochild_params(in, &fd, &len, dochild_params_fcgi))
|
|
goto out;
|
|
|
|
/* Forward any data from the test's parent. */
|
|
|
|
while (len > 0) {
|
|
ssz = nb_read(in, buf,
|
|
len < sizeof(buf) ? len : sizeof(buf));
|
|
if (ssz < 0) {
|
|
perror("read");
|
|
goto out;
|
|
} else if (ssz == 0)
|
|
break;
|
|
|
|
if (!fcgi_data_write(fd, buf, ssz)) {
|
|
fprintf(stderr, "%s: stdout\n", __func__);
|
|
goto out;
|
|
}
|
|
len -= ssz;
|
|
}
|
|
|
|
/* Indicate end of input. */
|
|
|
|
if (!fcgi_data_write(fd, NULL, 0)) {
|
|
fprintf(stderr, "%s: stdout (FIN)\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* Read and buffer into "vec" til EOF or end of headers. */
|
|
|
|
while (fcgi_hdr_read(fd, &hdr)) {
|
|
if (hdr.type == FCGI_HDR_END) {
|
|
if (!fcgi_end_read(fd, &rc)) {
|
|
fprintf(stderr, "%s: bad fin\n", __func__);
|
|
goto out;
|
|
}
|
|
break;
|
|
} else if (hdr.type != FCGI_HDR_DATA_OUT) {
|
|
fprintf(stderr, "%s: bad type: %"
|
|
PRIu8 "\n", __func__, hdr.type);
|
|
goto out;
|
|
}
|
|
if (hdr.contentLength > 0) {
|
|
pp = realloc(vec, vecsz + hdr.contentLength);
|
|
if (pp == NULL) {
|
|
perror(NULL);
|
|
goto out;
|
|
}
|
|
vec = pp;
|
|
if (!b_read(fd, vec + vecsz, hdr.contentLength)) {
|
|
fprintf(stderr, "%s: bad read\n", __func__);
|
|
goto out;
|
|
}
|
|
vecsz += hdr.contentLength;
|
|
}
|
|
if (b_ignore(fd, hdr.paddingLength) == 0) {
|
|
fprintf(stderr, "%s: bad ignore\n", __func__);
|
|
goto out;
|
|
}
|
|
if (vecsz == 0)
|
|
continue;
|
|
end = memmem(vec, vecsz, "\r\n\r\n", 4);
|
|
if (end != NULL)
|
|
break;
|
|
}
|
|
|
|
/* Uh-oh: we read the whole message and no headers. */
|
|
|
|
if (end == NULL) {
|
|
fprintf(stderr, "FastCGI script did "
|
|
"not terminate headers\n");
|
|
rc = 1;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* No "status" is ok: convert it to a 200.
|
|
* If we do have a status, print it with the HTTP type.
|
|
*/
|
|
|
|
headsz = (size_t)(end - vec);
|
|
|
|
if ((start = memmem(vec, headsz, "Status:", 7)) == NULL) {
|
|
msg = "HTTP/1.1 200 OK\r\n";
|
|
if (!b_write(in, msg, strlen(msg)))
|
|
goto out;
|
|
fprintf(stderr, "FastCGI script did "
|
|
"not specify status\n");
|
|
ovec = vec;
|
|
} else {
|
|
msg = "HTTP/1.1";
|
|
if (!nb_write(in, msg, strlen(msg)))
|
|
goto out;
|
|
cp = start + 7;
|
|
while (cp < end) {
|
|
if (!nb_write(in, cp, 1))
|
|
goto out;
|
|
cp++;
|
|
if (cp[-1] == '\n')
|
|
break;
|
|
}
|
|
if (!nb_write(in, vec, (size_t)(start - vec)))
|
|
goto out;
|
|
vecsz -= (cp - vec);
|
|
ovec = cp;
|
|
}
|
|
|
|
/* Print remaining buffered data. */
|
|
|
|
if (!nb_write(in, ovec, vecsz))
|
|
goto out;
|
|
|
|
/* Forward remaining script output. */
|
|
|
|
while (fcgi_hdr_read(fd, &hdr)) {
|
|
if (hdr.type == FCGI_HDR_END) {
|
|
if (!fcgi_end_read(fd, &rc)) {
|
|
fprintf(stderr, "%s: bad fin\n", __func__);
|
|
goto out;
|
|
}
|
|
rc = 1;
|
|
break;
|
|
} else if (hdr.type != FCGI_HDR_DATA_OUT) {
|
|
fprintf(stderr, "%s: bad type: %"
|
|
PRIu8 "\n", __func__, hdr.type);
|
|
goto out;
|
|
}
|
|
while (hdr.contentLength > 0) {
|
|
vecsz = hdr.contentLength > sizeof(buf) ?
|
|
sizeof(buf) : hdr.contentLength;
|
|
if (!b_read(fd, buf, vecsz)) {
|
|
fprintf(stderr, "%s: bad read\n", __func__);
|
|
goto out;
|
|
} else if (!nb_write(in, buf, vecsz)) {
|
|
fprintf(stderr, "%s: bad write\n", __func__);
|
|
goto out;
|
|
}
|
|
hdr.contentLength -= vecsz;
|
|
}
|
|
if (b_ignore(fd, hdr.paddingLength) == 0) {
|
|
fprintf(stderr, "%s: bad ignore\n", __func__);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (rc == 0)
|
|
fprintf(stderr, "%s: no fin\n", __func__);
|
|
out:
|
|
/*
|
|
* Begin by asking the child to exit.
|
|
* Then close all of our comm channels.
|
|
*/
|
|
|
|
kill(pid, SIGTERM);
|
|
if (in != -1)
|
|
close(in);
|
|
if (sfn[0] != '\0')
|
|
unlink(sfn);
|
|
if (fd != -1)
|
|
close(fd);
|
|
|
|
/*
|
|
* Now mandate that the child dies and reap its resources.
|
|
* FIXME: we might kill the process before it's done actually
|
|
* terminating, which is unfair and will raise spurious
|
|
* warnings elsewhere.
|
|
*/
|
|
|
|
if (waitpid(pid, NULL, 0) == -1)
|
|
perror("waitpid");
|
|
free(vec);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Broker a CGI process child.
|
|
* Return zero on failure and non-zero on success.
|
|
*/
|
|
static int
|
|
dochild_cgi(kcgi_regress_server child, void *carg)
|
|
{
|
|
int in, fd[2], rc;
|
|
const char *msg;
|
|
pid_t pid;
|
|
char *vec, *end, *start, *cp, *ovec;
|
|
size_t vecsz, headsz;
|
|
void *pp;
|
|
char buf[BUFSIZ];
|
|
ssize_t ssz;
|
|
|
|
if ((in = dochild_prepare()) == -1)
|
|
return 0;
|
|
|
|
/*
|
|
* We need to do some filtering from the CGI script's output
|
|
* (just its Status message), so create a socketpair which we'll
|
|
* use to scrub its output.
|
|
* This is because the CGI protocol is stupid: it would have
|
|
* been a lot easier to just require an HTTP status message, but
|
|
* I guess the intent was to make the web server worry about
|
|
* formatting for various versions of HTTP.
|
|
* Whatever.
|
|
*/
|
|
|
|
if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) == -1) {
|
|
perror("socketpair");
|
|
close(in);
|
|
return 0;
|
|
}
|
|
|
|
/* Launch the actual CGI process. */
|
|
|
|
if ((pid = fork()) == -1) {
|
|
perror("fork");
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
close(in);
|
|
return 0;
|
|
} else if (pid == 0) {
|
|
close(fd[1]);
|
|
/*
|
|
* First, we suck down the HTTP headeres into our CGI
|
|
* environment and run the child.
|
|
* Next, re-assign our stdin to be the server's file
|
|
* descriptor "in".
|
|
* Then assign our stdout to be fd[0].
|
|
*/
|
|
|
|
if (!dochild_params
|
|
(in, NULL, NULL, dochild_params_cgi)) {
|
|
close(in);
|
|
close(fd[0]);
|
|
return(0);
|
|
}
|
|
|
|
if (dup2(in, STDIN_FILENO) != STDIN_FILENO) {
|
|
perror("dup2");
|
|
close(in);
|
|
close(fd[0]);
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
close(in);
|
|
|
|
if (dup2(fd[0], STDOUT_FILENO) != STDOUT_FILENO) {
|
|
perror("dup2");
|
|
close(fd[0]);
|
|
return 0;
|
|
}
|
|
close(fd[0]);
|
|
|
|
child(carg);
|
|
_exit(in ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* We're in the parent.
|
|
* Read and buffer the output of the CGI process until we get
|
|
* its Status value.
|
|
* Do so by copying through a static buffer into a growable
|
|
* vector that we'll scan for the Status message to re-write.
|
|
* Of course, if the CGI process is ill-designed, we'll consume
|
|
* all of our memory and die.
|
|
*/
|
|
|
|
close(fd[0]);
|
|
vecsz = 0;
|
|
vec = end = NULL;
|
|
rc = 0;
|
|
|
|
while ((ssz = read(fd[1], buf, sizeof(buf))) > 0) {
|
|
pp = realloc(vec, vecsz + ssz);
|
|
if (pp == NULL) {
|
|
perror("realloc");
|
|
goto out;
|
|
}
|
|
vec = pp;
|
|
memcpy(vec + vecsz, buf, ssz);
|
|
vecsz += ssz;
|
|
end = memmem(vec, vecsz, "\r\n\r\n", 4);
|
|
if (end != NULL)
|
|
break;
|
|
}
|
|
|
|
if (ssz < 0) {
|
|
perror("read");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Now all our headers are in vec plus extra; of if it's NULL,
|
|
* we reached the end of input without getting headers.
|
|
* Now we start to scan for the status line or dump.
|
|
*/
|
|
|
|
if (end != NULL) {
|
|
/* Look for the status field. */
|
|
|
|
headsz = (size_t)(end - vec);
|
|
start = memmem(vec, headsz, "Status:", 7);
|
|
if (start == NULL) {
|
|
/*
|
|
* No status field.
|
|
* This is Ok (according to CGI).
|
|
* However, we do not provide a valid status, so
|
|
* do what others do and 200 it.
|
|
*/
|
|
|
|
msg = "HTTP/1.1 200 OK\r\n";
|
|
if (!nb_write(in, msg, strlen(msg)))
|
|
goto out;
|
|
fprintf(stderr, "CGI script did "
|
|
"not specify status\n");
|
|
ovec = vec;
|
|
} else {
|
|
/*
|
|
* We found the status.
|
|
* Print it out now, then everything that came
|
|
* before it.
|
|
*/
|
|
|
|
msg = "HTTP/1.1";
|
|
if (!nb_write(in, msg, strlen(msg)))
|
|
goto out;
|
|
cp = start + 7;
|
|
while (cp < end) {
|
|
if (!nb_write(in, cp, 1))
|
|
goto out;
|
|
cp++;
|
|
if (cp[-1] == '\n')
|
|
break;
|
|
}
|
|
if (!nb_write(in, vec, (size_t)(start - vec)))
|
|
goto out;
|
|
vecsz -= (cp - vec);
|
|
ovec = cp;
|
|
}
|
|
|
|
/*
|
|
* Print everything else in our vector array, then poll
|
|
* on the CGI script til its dry.
|
|
*/
|
|
|
|
if (!nb_write(in, ovec, vecsz))
|
|
goto out;
|
|
|
|
while ((ssz = read(fd[1], buf, sizeof(buf))) > 0)
|
|
if ( ! nb_write(in, buf, ssz))
|
|
goto out;
|
|
|
|
if (ssz < 0) {
|
|
perror("read");
|
|
goto out;
|
|
}
|
|
} else {
|
|
if (!nb_write(in, vec, vecsz))
|
|
goto out;
|
|
fprintf(stderr, "CGI script did "
|
|
"not terminate headers\n");
|
|
}
|
|
|
|
rc = 1;
|
|
out:
|
|
if (waitpid(pid, NULL, 0) == -1)
|
|
perror("waitpid");
|
|
|
|
free(vec);
|
|
close(in);
|
|
close(fd[1]);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* This is the real beginning of the regression system.
|
|
* Create a child process first, and wait for it to go to sleep.
|
|
* When the child has gone to sleep, we know that it has started up and
|
|
* bound to a socket, so we can wake it up (SIGCONT) and test against
|
|
* the open socket.
|
|
*/
|
|
static int
|
|
regress(int fastcgi,
|
|
kcgi_regress_client parent, void *parg,
|
|
kcgi_regress_server child, void *carg)
|
|
{
|
|
pid_t chld, pid;
|
|
int rc, st;
|
|
|
|
/*
|
|
* Create our "test framework" child.
|
|
* The child will return EXIT_SUCCESS or EXIT_FAILURE in the
|
|
* usual way.
|
|
*/
|
|
|
|
if ((chld = fork()) == -1) {
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
} else if (chld == 0) {
|
|
rc = fastcgi ?
|
|
dochild_fcgi(child, carg) :
|
|
dochild_cgi(child, carg);
|
|
exit(rc ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Wait for it to sleep.
|
|
* We do this to prevent the "parent" from trying to access the
|
|
* web application over a socket that hasn't been opened yet.
|
|
*/
|
|
|
|
do {
|
|
pid = waitpid(chld, &st, WUNTRACED);
|
|
} while (pid == -1 && errno == EINTR);
|
|
|
|
/*
|
|
* Once the child sleeps, we know that it has bound itself to
|
|
* the listening socket.
|
|
* So simply wake it up and continue our work.
|
|
*/
|
|
|
|
if (pid == -1) {
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
} else if (!WIFSTOPPED(st)) {
|
|
fprintf(stderr, "child not sleeping\n");
|
|
exit(EXIT_FAILURE);
|
|
} else if (kill(chld, SIGCONT) == -1) {
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
rc = parent(parg);
|
|
|
|
if (waitpid(pid, &st, 0) == -1) {
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return(rc && WIFEXITED(st) &&
|
|
EXIT_SUCCESS == WEXITSTATUS(st));
|
|
}
|
|
|
|
int
|
|
kcgi_regress_cgi(kcgi_regress_client parent, void *parg,
|
|
kcgi_regress_server child, void *carg)
|
|
{
|
|
|
|
return regress(0, parent, parg, child, carg);
|
|
}
|
|
|
|
int
|
|
kcgi_regress_fcgi(kcgi_regress_client parent, void *parg,
|
|
kcgi_regress_server child, void *carg)
|
|
{
|
|
|
|
return regress(1, parent, parg, child, carg);
|
|
}
|