ksgi
/
kcgiregress.c
1210 строк · 25.8 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2014--2016, 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include "config.h"18
19#include <sys/socket.h>20#include <sys/wait.h>21#include <sys/stat.h>22#include <sys/un.h>23#include <netinet/in.h>24
25#include <assert.h>26#include <ctype.h>27#include <errno.h>28#include <fcntl.h>29#include <inttypes.h>30#include <poll.h>31#include <signal.h>32#include <stdio.h>33#include <stdint.h>34#include <stdlib.h>35#include <string.h>36#include <unistd.h>37
38#include "kcgiregress.h"39
40#define FCGI_HDR_SIZE 841
42/*
43* The FastCGI header.
44* This is duplicated elsewhere in the code, but is harmless as it isn't
45* really going to change.
46*/
47struct fcgi_hdr {48uint8_t version;49uint8_t type;50#define FCGI_HDR_END 351#define FCGI_HDR_HEAD 452#define FCGI_HDR_DATA_IN 553#define FCGI_HDR_DATA_OUT 654uint16_t requestId;55uint16_t contentLength;56uint8_t paddingLength;57uint8_t reserved;58};59
60/*
61* Non-blocking read.
62* Returns <0 on failure (system error) or bytes read, which may be
63* zero in the event of EOF.
64*/
65static ssize_t66nb_read(int fd, void *buf, size_t bufsz)67{
68struct pollfd pfd;69int rc;70
71assert(buf != NULL && bufsz);72
73pfd.fd = fd;74pfd.events = POLLIN;75
76if ((rc = poll(&pfd, 1, -1)) < 0) {77perror("poll");78return (-1);79} else if (rc == 0) {80fprintf(stderr, "poll: timeout!?\n");81return (-1);82}83
84return read(fd, buf, bufsz);85}
86
87/*
88* Non-blocking write of "bufsz" bytes in "buf".
89* Return TRUE on success, FALSE on failure (system error).
90*/
91static int92nb_write(int fd, const void *buf, size_t bufsz)93{
94ssize_t ssz;95size_t sz;96struct pollfd pfd;97int rc;98
99if (buf == NULL || bufsz == 0)100return 1;101
102pfd.fd = fd;103pfd.events = POLLOUT;104
105for (sz = 0; sz < bufsz; sz += (size_t)ssz) {106if ((rc = poll(&pfd, 1, -1)) < 0)107perror("poll");108else if (rc == 0)109fprintf(stderr, "poll: timeout!?\n");110else if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0)111perror("write");112else if (sz > SIZE_MAX - (size_t)ssz)113fprintf(stderr, "write: overflow: "114"%zu, %zd", sz, ssz);115else116continue;117return 0;118}119
120return 1;121}
122
123/*
124* Blocking write of "sz" bytes in "buf".
125* Return TRUE on success, FALSE on failure (system error).
126*/
127static int128b_write(int fd, const void *buf, size_t sz)129{
130ssize_t ssz;131size_t wsz = 0;132
133while (sz > 0) {134if ((ssz = write(fd, buf + wsz, sz)) == -1) {135perror("write");136return 0;137}138sz -= (size_t)ssz;139wsz += (size_t)ssz;140}141
142return 1;143}
144
145/*
146* Blocking read (discarding bytes).
147* Discards "sz" bytes and returns TRUE on success, FALSE on failure
148* (system error or EOF).
149*/
150static int151b_ignore(int fd, size_t sz)152{
153ssize_t ssz;154char buf;155
156while (sz > 0) {157if ((ssz = read(fd, &buf, 1)) == -1) {158perror("read");159return 0;160} else if (ssz == 0) {161fputs("read: unexpected EOF\n", stderr);162return 0;163}164sz--;165}166
167return 1;168}
169
170/*
171* Blocking read.
172* Reads "sz" bytes into buf and returns TRUE on success, FALSE on
173* failure (system error or EOF).
174*/
175static int176b_read(int fd, void *buf, size_t sz)177{
178ssize_t ssz;179size_t rsz = 0;180
181while (sz > 0) {182if ((ssz = read(fd, buf + rsz, sz)) == -1) {183perror("read");184return 0;185} else if (ssz == 0) {186fputs("read: unexpected EOF\n", stderr);187return 0;188}189sz -= (size_t)ssz;190rsz += (size_t)ssz;191}192
193return 1;194}
195
196/*
197* Write a entire FastCGI header to the child.
198* Note that the members need to be in network-byte order, so perform
199* your htons and so on before invoking this function.
200*/
201static int202fcgi_hdr_write(int fd, const struct fcgi_hdr *hdr)203{
204
205if (!b_write(fd, &hdr->version, 1))206fprintf(stderr, "%s: version\n", __func__);207else if (!b_write(fd, &hdr->type, 1))208fprintf(stderr, "%s: type\n", __func__);209else if (!b_write(fd, &hdr->requestId, 2))210fprintf(stderr, "%s: requestId\n", __func__);211else if (!b_write(fd, &hdr->contentLength, 2))212fprintf(stderr, "%s: data length\n", __func__);213else if (!b_write(fd, &hdr->paddingLength, 1))214fprintf(stderr, "%s: pad length\n", __func__);215else if (!b_write(fd, &hdr->reserved, 1))216fprintf(stderr, "%s: reserved\n", __func__);217else218return 1;219
220return 0;221}
222
223static int224fcgi_hdr_read(int fd, struct fcgi_hdr *hdr)225{
226char buf[FCGI_HDR_SIZE];227
228if (!b_read(fd, buf, sizeof(buf))) {229fprintf(stderr, "%s: header\n", __func__);230return 0;231}232
233hdr->version = buf[0];234hdr->type = buf[1];235hdr->requestId = ntohs(*(uint16_t *)&buf[2]);236hdr->contentLength = ntohs(*(uint16_t *)&buf[4]);237hdr->paddingLength = buf[6];238hdr->reserved = buf[7];239
240return 1;241}
242
243static int244fcgi_data_write(int fd, const void *buf, size_t sz)245{
246struct fcgi_hdr hdr;247
248hdr.version = 1;249hdr.type = FCGI_HDR_DATA_IN;250hdr.requestId = htons(1);251hdr.contentLength = htons(sz);252hdr.paddingLength = 0;253hdr.reserved = 0;254
255if (!fcgi_hdr_write(fd, &hdr))256fprintf(stderr, "%s: header\n", __func__);257else if (!b_write(fd, buf, sz))258fprintf(stderr, "%s: data\n", __func__);259else260return 1;261
262return 0;263}
264
265static int266fcgi_end_read(int fd, int *status)267{
268uint32_t st;269uint8_t pst;270uint8_t res[3];271
272if (!b_read(fd, &st, 4)) {273fprintf(stderr, "%s: status\n", __func__);274return 0;275} else if (!b_read(fd, &pst, 1)) {276fprintf(stderr, "%s: flags\n", __func__);277return 0;278} else if (!b_read(fd, res, sizeof(res))) {279fprintf(stderr, "%s: reserved\n", __func__);280return 0;281}282
283/*284* We use EXIT_SUCCESS for our status message.
285* See output.c for where this is set.
286*/
287
288*status = ntohl(st) == EXIT_SUCCESS ? 1 : 0;289return 1;290}
291
292static int293fcgi_begin_write(int fd)294{
295struct fcgi_hdr hdr;296uint16_t role = htons(1);297uint8_t flags = 0;298uint8_t res[5];299
300hdr.version = 1;301hdr.type = 1;302hdr.requestId = htons(1);303hdr.contentLength = htons(8);304hdr.paddingLength = 0;305hdr.reserved = 0;306
307memset(res, 0, sizeof(res));308
309if (!fcgi_hdr_write(fd, &hdr))310fprintf(stderr, "%s: header\n", __func__);311else if (!b_write(fd, &role, 2))312fprintf(stderr, "%s: role\n", __func__);313else if (!b_write(fd, &flags, 1))314fprintf(stderr, "%s: flags\n", __func__);315else if (!b_write(fd, res, sizeof(res)))316fprintf(stderr, "%s: reserved\n", __func__);317else318return 1;319
320return 0;321}
322
323/*
324* Set the environment variable for a CGI script.
325* This involves actually setting the environment variable.
326*/
327static int328dochild_params_cgi(const char *key, const char *val, void *arg)329{
330
331setenv(key, val, 1);332return 1;333}
334
335/*
336* Set the environment variable for a FastCGI script.
337* We'll need to bundle it into a FastCGI request and write that to the
338* child.
339*/
340static int341dochild_params_fcgi(const char *key, const char *val, void *arg)342{
343int fd = *(int *)arg;344struct fcgi_hdr hdr;345uint32_t lenl;346uint8_t lens;347size_t sz;348
349sz = strlen(key) + (strlen(key) > 127 ? 4 : 1) +350strlen(val) + (strlen(val) > 127 ? 4 : 1);351
352/* Start with the FastCGI header. */353
354hdr.version = 1;355hdr.type = FCGI_HDR_HEAD;356hdr.requestId = htons(1);357hdr.contentLength = htons(sz);358hdr.paddingLength = 0;359hdr.reserved = 0;360
361if (!fcgi_hdr_write(fd, &hdr)) {362fprintf(stderr, "%s: header\n", __func__);363return(0);364}365
366/* Key and value lengths. */367
368if ((sz = strlen(key)) > 127) {369lenl = htonl(sz);370if (!b_write(fd, &lenl, 4)) {371fprintf(stderr, "%s: key length", __func__);372return 0;373}374} else {375lens = sz;376if (!b_write(fd, &lens, 1)) {377fprintf(stderr, "%s: key length", __func__);378return 0;379}380}381if ((sz = strlen(val)) > 127) {382lenl = htonl(sz);383if (!b_write(fd, &lenl, 4)) {384fprintf(stderr, "%s: val length", __func__);385return 0;386}387} else {388lens = sz;389if (!b_write(fd, &lens, 1)) {390fprintf(stderr, "%s: val length", __func__);391return 0;392}393}394
395/* Key and value data. */396if (!b_write(fd, key, strlen(key))) {397fprintf(stderr, "%s: key", __func__);398return 0;399} else if (!b_write(fd, val, strlen(val))) {400fprintf(stderr, "%s: val", __func__);401return 0;402}403
404return 1;405}
406
407/*
408* Parse HTTP header lines from input.
409* This obviously isn't the best way of doing things, but it's simple
410* and easy to fix for the purposes of this regression suite.
411* This will continually parse lines from `fd' until it reaches a blank
412* line, at which point it will return control.
413* It returns zero on failure (read failure, or possibly write failure
414* when serialising to the CGI context) or non-zero on success.
415*/
416static int417dochild_params(int fd, void *arg, size_t *length,418int (*fp)(const char *, const char *, void *))419{
420int first;421char head[BUFSIZ], buf[BUFSIZ];422ssize_t ssz;423size_t sz;424char *cp, *path, *query, *key, *val;425char c;426extern char *__progname;427
428if (length != NULL)429*length = 0;430
431if (!fp("SCRIPT_NAME", __progname, arg))432return 0;433
434/*435* Read header lines without buffering and clobbering the data
436* yet to be read on the wire.
437* Process them as the environment input to our CGI.
438*/
439
440for (first = 1; ; ) {441/*442* Start by reading the header itself into a
443* fixed-length buffer, making sure to respect that the
444* descriptor is non-blocking.
445*/
446
447for (sz = 0; sz < BUFSIZ; ) {448ssz = nb_read(fd, &c, 1);449if (ssz < 0) {450perror("read");451return 0;452} else if (ssz == 0 || (head[sz++] = c) == '\n')453break;454}455
456/* Strip CRLF. */457
458if (sz < 2 || sz == BUFSIZ) {459fprintf(stderr, "Bad HTTP header\n");460return 0;461} else if (head[sz - 2] != '\r') {462fprintf(stderr, "Bad HTTP header CRLF\n");463return 0;464}465head[sz - 2] = '\0';466
467/* Empty line: now we're at the CGI document. */468
469if (head[0] == '\0')470break;471
472/* Process our header. */473
474if (first) {475/* Snarf the first GET/POST line. */476
477if (strncmp(head, "GET ", 4) == 0) {478if (!fp("REQUEST_METHOD", "GET", arg))479return 0;480path = head + 4;481} else if (strncmp(head, "POST ", 5) == 0) {482if (!fp("REQUEST_METHOD", "POST", arg))483return(0);484path = head + 5;485} else {486fprintf(stderr, "Unknown HTTP "487"first line: %s\n", head);488return 0;489}490
491/* Split this into the path and query. */492
493cp = path;494while (*cp != '\0' && !isspace((unsigned char)*cp))495cp++;496*cp = '\0';497if ((query = strchr(path, '?')) != NULL)498*query++ = '\0';499
500first = 0;501if (!fp("PATH_INFO", path, arg))502return 0;503if (query != NULL)504if (!fp("QUERY_STRING", query, arg))505return 0;506continue;507}508
509/*510* Split headers into key/value parts.
511* Strip the leading spaces on the latter.
512* Let baddies (no value) just go by.
513*/
514
515key = head;516if ((val = strchr(key, ':')) == NULL)517continue;518*val++ = '\0';519while (*val != '\0' && isspace((unsigned char)*val))520val++;521
522/* Recognise some attributes... */523
524if (strcmp(key, "Content-Length") == 0) {525if (length != NULL)526*length = atoi(val);527if (!fp("CONTENT_LENGTH", val, arg))528return 0;529continue;530} else if (strcmp(key, "Content-Type") == 0) {531if (!fp("CONTENT_TYPE", val, arg))532return 0;533continue;534}535
536/*537* Now we have "regular" attributes that we want to cast
538* directly as HTTP attributes in the CGI environment.
539*/
540
541strlcpy(buf, "HTTP_", sizeof(buf));542sz = strlcat(buf, key, sizeof(buf));543assert(sz < sizeof(buf));544for (cp = buf; *cp != '\0'; cp++)545if (*cp == '-')546*cp = '_';547else if (isalpha((unsigned char)*cp))548*cp = toupper((unsigned char)*cp);549
550if (!fp(buf, val, arg))551return 0;552}553
554return 1;555}
556
557/*
558* First, read regress().
559* This is the "child" portion of that description.
560* Bind to the local port over which we'll directly accept test requests
561* from our regression suite.
562* This port is acting as an ad hoc web server.
563* Return the file descriptor on success or -1 on failure.
564*/
565static int566dochild_prepare(void)567{
568int s, in, opt = 1;569struct sockaddr_in ad, rem;570socklen_t len;571
572memset(&ad, 0, sizeof(struct sockaddr_in));573
574/*575* Bind and listen to our reusable testing socket.
576* We pretty much just choose a random port for this.
577*/
578
579if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {580perror("socket");581return (-1);582} else if (setsockopt(s, SOL_SOCKET,583SO_REUSEADDR, &opt, sizeof(opt)) == -1) {584perror("setsockopt");585close(s);586return (-1);587}588
589ad.sin_family = AF_INET;590ad.sin_port = htons(KCGI_REGRESS_PORT);591ad.sin_addr.s_addr = htonl(INADDR_LOOPBACK);592
593if (bind(s, (struct sockaddr *)&ad, sizeof(ad)) == -1) {594perror("bind");595close(s);596return (-1);597} else if (listen(s, 1) == -1) {598perror("listen");599close(s);600return (-1);601}602
603/*604* Tell our parent that we've bound to our socket and are ready
605* to receive data.
606* They'll do a waitpid() to see when we're sleeping, then wake
607* us up when we're already ready to go.
608*/
609
610kill(getpid(), SIGSTOP);611
612/*613* Wait for a single testing connection.
614* When we accept it, immediately note as non-blocking: kcgi(3)
615* uses polling, so it will need a non-blocking descriptor.
616*/
617
618len = sizeof(struct sockaddr_in);619if ((in = accept(s, (struct sockaddr *)&rem, &len)) == -1) {620perror("accept");621close(s);622return (-1);623} else if (fcntl(in, F_SETFL, O_NONBLOCK) == -1) {624perror("fcntl: O_NONBLOCK");625close(s);626close(in);627return 0;628}629
630close(s);631return in;632}
633
634/*
635* Broker a FastCGI process child.
636* Return zero on failure and non-zero on success.
637*/
638static int639dochild_fcgi(kcgi_regress_server child, void *carg)640{
641int in, rc = 0, fd;642size_t sz, len, vecsz = 0, headsz;643pid_t pid;644struct sockaddr_un un;645struct sockaddr *ss;646ssize_t ssz;647extern char *__progname;648const char *end = NULL, *start, *msg, *ovec, *cp;649char sfn[22], buf[BUFSIZ];650char *vec = NULL;651void *pp;652struct fcgi_hdr hdr;653mode_t mode;654
655/*656* Create a temporary file, close it, then unlink it.
657* The child will recreate this as a socket.
658*/
659
660strlcpy(sfn, "/tmp/kfcgi.XXXXXXXXXX", sizeof(sfn));661
662/* This shuts up Coverity. */663
664mode = umask(S_IXUSR | S_IRWXG | S_IRWXO);665if ((fd = mkstemp(sfn)) == -1) {666perror(sfn);667return 0;668} else if (close(fd) == -1 || unlink(sfn) == -1) {669perror(sfn);670return 0;671}672umask(mode);673
674/* Do the usual dance to set up UNIX sockets. */675
676ss = (struct sockaddr *)&un;677memset(&un, 0, sizeof(un));678un.sun_family = AF_UNIX;679sz = strlcpy(un.sun_path, sfn, sizeof(un.sun_path));680if (sz >= sizeof(un.sun_path)) {681fprintf(stderr, "socket path to long\n");682return 0;683}684#if !defined(__linux__) && !defined(__sun)685un.sun_len = sz;686#endif687
688if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {689perror("socket");690return 0;691} else if (bind(fd, ss, sizeof(un)) == -1) {692perror(sfn);693close(fd);694return 0;695} else if (listen(fd, 5) == -1) {696perror(sfn);697close(fd);698return 0;699}700
701/*702* Now fork the FastCGI process.
703* We need to use a separate process because we're going to
704* package the request as a FastCGI request and ship it into the
705* UNIX socket.
706*/
707
708if ((pid = fork()) == -1) {709perror("fork");710unlink(sfn);711close(fd);712return 0;713} else if (pid == 0) {714if (dup2(fd, STDIN_FILENO) == -1)715_exit(EXIT_FAILURE);716close(fd);717return child(carg);718}719
720/*721* Close the socket, as we're going to connect to it.
722* The child has a reference to the object (via its dup2), so
723* we're not going to totally remove the file.
724*/
725
726close(fd);727fd = -1;728
729/* Get the next incoming connection and FILE-ise it. */730
731if ((in = dochild_prepare()) == -1)732goto out;733
734/*735* Open a new socket to the FastCGI object and connect to it,
736* reusing the prior socket address.
737* Then remove the object, as nobody needs it any more.
738*/
739
740if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {741perror(sfn);742goto out;743} else if (connect(fd, ss, sizeof(un)) == -1) {744perror(sfn);745goto out;746} else if (unlink(sfn) == -1) {747perror(sfn);748goto out;749}750sfn[0] = '\0';751
752/* Write the request, its parameters, and all data. */753
754if (!fcgi_begin_write(fd))755goto out;756if (!dochild_params(in, &fd, &len, dochild_params_fcgi))757goto out;758
759/* Forward any data from the test's parent. */760
761while (len > 0) {762ssz = nb_read(in, buf,763len < sizeof(buf) ? len : sizeof(buf));764if (ssz < 0) {765perror("read");766goto out;767} else if (ssz == 0)768break;769
770if (!fcgi_data_write(fd, buf, ssz)) {771fprintf(stderr, "%s: stdout\n", __func__);772goto out;773}774len -= ssz;775}776
777/* Indicate end of input. */778
779if (!fcgi_data_write(fd, NULL, 0)) {780fprintf(stderr, "%s: stdout (FIN)\n", __func__);781goto out;782}783
784/* Read and buffer into "vec" til EOF or end of headers. */785
786while (fcgi_hdr_read(fd, &hdr)) {787if (hdr.type == FCGI_HDR_END) {788if (!fcgi_end_read(fd, &rc)) {789fprintf(stderr, "%s: bad fin\n", __func__);790goto out;791}792break;793} else if (hdr.type != FCGI_HDR_DATA_OUT) {794fprintf(stderr, "%s: bad type: %"795PRIu8 "\n", __func__, hdr.type);796goto out;797}798if (hdr.contentLength > 0) {799pp = realloc(vec, vecsz + hdr.contentLength);800if (pp == NULL) {801perror(NULL);802goto out;803}804vec = pp;805if (!b_read(fd, vec + vecsz, hdr.contentLength)) {806fprintf(stderr, "%s: bad read\n", __func__);807goto out;808}809vecsz += hdr.contentLength;810}811if (b_ignore(fd, hdr.paddingLength) == 0) {812fprintf(stderr, "%s: bad ignore\n", __func__);813goto out;814}815if (vecsz == 0)816continue;817end = memmem(vec, vecsz, "\r\n\r\n", 4);818if (end != NULL)819break;820}821
822/* Uh-oh: we read the whole message and no headers. */823
824if (end == NULL) {825fprintf(stderr, "FastCGI script did "826"not terminate headers\n");827rc = 1;828goto out;829}830
831/*832* No "status" is ok: convert it to a 200.
833* If we do have a status, print it with the HTTP type.
834*/
835
836headsz = (size_t)(end - vec);837
838if ((start = memmem(vec, headsz, "Status:", 7)) == NULL) {839msg = "HTTP/1.1 200 OK\r\n";840if (!b_write(in, msg, strlen(msg)))841goto out;842fprintf(stderr, "FastCGI script did "843"not specify status\n");844ovec = vec;845} else {846msg = "HTTP/1.1";847if (!nb_write(in, msg, strlen(msg)))848goto out;849cp = start + 7;850while (cp < end) {851if (!nb_write(in, cp, 1))852goto out;853cp++;854if (cp[-1] == '\n')855break;856}857if (!nb_write(in, vec, (size_t)(start - vec)))858goto out;859vecsz -= (cp - vec);860ovec = cp;861}862
863/* Print remaining buffered data. */864
865if (!nb_write(in, ovec, vecsz))866goto out;867
868/* Forward remaining script output. */869
870while (fcgi_hdr_read(fd, &hdr)) {871if (hdr.type == FCGI_HDR_END) {872if (!fcgi_end_read(fd, &rc)) {873fprintf(stderr, "%s: bad fin\n", __func__);874goto out;875}876rc = 1;877break;878} else if (hdr.type != FCGI_HDR_DATA_OUT) {879fprintf(stderr, "%s: bad type: %"880PRIu8 "\n", __func__, hdr.type);881goto out;882}883while (hdr.contentLength > 0) {884vecsz = hdr.contentLength > sizeof(buf) ?885sizeof(buf) : hdr.contentLength;886if (!b_read(fd, buf, vecsz)) {887fprintf(stderr, "%s: bad read\n", __func__);888goto out;889} else if (!nb_write(in, buf, vecsz)) {890fprintf(stderr, "%s: bad write\n", __func__);891goto out;892}893hdr.contentLength -= vecsz;894}895if (b_ignore(fd, hdr.paddingLength) == 0) {896fprintf(stderr, "%s: bad ignore\n", __func__);897goto out;898}899}900
901if (rc == 0)902fprintf(stderr, "%s: no fin\n", __func__);903out:904/*905* Begin by asking the child to exit.
906* Then close all of our comm channels.
907*/
908
909kill(pid, SIGTERM);910if (in != -1)911close(in);912if (sfn[0] != '\0')913unlink(sfn);914if (fd != -1)915close(fd);916
917/*918* Now mandate that the child dies and reap its resources.
919* FIXME: we might kill the process before it's done actually
920* terminating, which is unfair and will raise spurious
921* warnings elsewhere.
922*/
923
924if (waitpid(pid, NULL, 0) == -1)925perror("waitpid");926free(vec);927return rc;928}
929
930/*
931* Broker a CGI process child.
932* Return zero on failure and non-zero on success.
933*/
934static int935dochild_cgi(kcgi_regress_server child, void *carg)936{
937int in, fd[2], rc;938const char *msg;939pid_t pid;940char *vec, *end, *start, *cp, *ovec;941size_t vecsz, headsz;942void *pp;943char buf[BUFSIZ];944ssize_t ssz;945
946if ((in = dochild_prepare()) == -1)947return 0;948
949/*950* We need to do some filtering from the CGI script's output
951* (just its Status message), so create a socketpair which we'll
952* use to scrub its output.
953* This is because the CGI protocol is stupid: it would have
954* been a lot easier to just require an HTTP status message, but
955* I guess the intent was to make the web server worry about
956* formatting for various versions of HTTP.
957* Whatever.
958*/
959
960if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) == -1) {961perror("socketpair");962close(in);963return 0;964}965
966/* Launch the actual CGI process. */967
968if ((pid = fork()) == -1) {969perror("fork");970close(fd[0]);971close(fd[1]);972close(in);973return 0;974} else if (pid == 0) {975close(fd[1]);976/*977* First, we suck down the HTTP headeres into our CGI
978* environment and run the child.
979* Next, re-assign our stdin to be the server's file
980* descriptor "in".
981* Then assign our stdout to be fd[0].
982*/
983
984if (!dochild_params985(in, NULL, NULL, dochild_params_cgi)) {986close(in);987close(fd[0]);988return(0);989}990
991if (dup2(in, STDIN_FILENO) != STDIN_FILENO) {992perror("dup2");993close(in);994close(fd[0]);995_exit(EXIT_FAILURE);996}997close(in);998
999if (dup2(fd[0], STDOUT_FILENO) != STDOUT_FILENO) {1000perror("dup2");1001close(fd[0]);1002return 0;1003}1004close(fd[0]);1005
1006child(carg);1007_exit(in ? EXIT_SUCCESS : EXIT_FAILURE);1008}1009
1010/*1011* We're in the parent.
1012* Read and buffer the output of the CGI process until we get
1013* its Status value.
1014* Do so by copying through a static buffer into a growable
1015* vector that we'll scan for the Status message to re-write.
1016* Of course, if the CGI process is ill-designed, we'll consume
1017* all of our memory and die.
1018*/
1019
1020close(fd[0]);1021vecsz = 0;1022vec = end = NULL;1023rc = 0;1024
1025while ((ssz = read(fd[1], buf, sizeof(buf))) > 0) {1026pp = realloc(vec, vecsz + ssz);1027if (pp == NULL) {1028perror("realloc");1029goto out;1030}1031vec = pp;1032memcpy(vec + vecsz, buf, ssz);1033vecsz += ssz;1034end = memmem(vec, vecsz, "\r\n\r\n", 4);1035if (end != NULL)1036break;1037}1038
1039if (ssz < 0) {1040perror("read");1041goto out;1042}1043
1044/*1045* Now all our headers are in vec plus extra; of if it's NULL,
1046* we reached the end of input without getting headers.
1047* Now we start to scan for the status line or dump.
1048*/
1049
1050if (end != NULL) {1051/* Look for the status field. */1052
1053headsz = (size_t)(end - vec);1054start = memmem(vec, headsz, "Status:", 7);1055if (start == NULL) {1056/*1057* No status field.
1058* This is Ok (according to CGI).
1059* However, we do not provide a valid status, so
1060* do what others do and 200 it.
1061*/
1062
1063msg = "HTTP/1.1 200 OK\r\n";1064if (!nb_write(in, msg, strlen(msg)))1065goto out;1066fprintf(stderr, "CGI script did "1067"not specify status\n");1068ovec = vec;1069} else {1070/*1071* We found the status.
1072* Print it out now, then everything that came
1073* before it.
1074*/
1075
1076msg = "HTTP/1.1";1077if (!nb_write(in, msg, strlen(msg)))1078goto out;1079cp = start + 7;1080while (cp < end) {1081if (!nb_write(in, cp, 1))1082goto out;1083cp++;1084if (cp[-1] == '\n')1085break;1086}1087if (!nb_write(in, vec, (size_t)(start - vec)))1088goto out;1089vecsz -= (cp - vec);1090ovec = cp;1091}1092
1093/*1094* Print everything else in our vector array, then poll
1095* on the CGI script til its dry.
1096*/
1097
1098if (!nb_write(in, ovec, vecsz))1099goto out;1100
1101while ((ssz = read(fd[1], buf, sizeof(buf))) > 0)1102if ( ! nb_write(in, buf, ssz))1103goto out;1104
1105if (ssz < 0) {1106perror("read");1107goto out;1108}1109} else {1110if (!nb_write(in, vec, vecsz))1111goto out;1112fprintf(stderr, "CGI script did "1113"not terminate headers\n");1114}1115
1116rc = 1;1117out:1118if (waitpid(pid, NULL, 0) == -1)1119perror("waitpid");1120
1121free(vec);1122close(in);1123close(fd[1]);1124return rc;1125}
1126
1127/*
1128* This is the real beginning of the regression system.
1129* Create a child process first, and wait for it to go to sleep.
1130* When the child has gone to sleep, we know that it has started up and
1131* bound to a socket, so we can wake it up (SIGCONT) and test against
1132* the open socket.
1133*/
1134static int1135regress(int fastcgi,1136kcgi_regress_client parent, void *parg,1137kcgi_regress_server child, void *carg)1138{
1139pid_t chld, pid;1140int rc, st;1141
1142/*1143* Create our "test framework" child.
1144* The child will return EXIT_SUCCESS or EXIT_FAILURE in the
1145* usual way.
1146*/
1147
1148if ((chld = fork()) == -1) {1149perror(NULL);1150exit(EXIT_FAILURE);1151} else if (chld == 0) {1152rc = fastcgi ?1153dochild_fcgi(child, carg) :1154dochild_cgi(child, carg);1155exit(rc ? EXIT_SUCCESS : EXIT_FAILURE);1156}1157
1158/*1159* Wait for it to sleep.
1160* We do this to prevent the "parent" from trying to access the
1161* web application over a socket that hasn't been opened yet.
1162*/
1163
1164do {1165pid = waitpid(chld, &st, WUNTRACED);1166} while (pid == -1 && errno == EINTR);1167
1168/*1169* Once the child sleeps, we know that it has bound itself to
1170* the listening socket.
1171* So simply wake it up and continue our work.
1172*/
1173
1174if (pid == -1) {1175perror(NULL);1176exit(EXIT_FAILURE);1177} else if (!WIFSTOPPED(st)) {1178fprintf(stderr, "child not sleeping\n");1179exit(EXIT_FAILURE);1180} else if (kill(chld, SIGCONT) == -1) {1181perror(NULL);1182exit(EXIT_FAILURE);1183}1184
1185rc = parent(parg);1186
1187if (waitpid(pid, &st, 0) == -1) {1188perror(NULL);1189exit(EXIT_FAILURE);1190}1191
1192return(rc && WIFEXITED(st) &&1193EXIT_SUCCESS == WEXITSTATUS(st));1194}
1195
1196int
1197kcgi_regress_cgi(kcgi_regress_client parent, void *parg,1198kcgi_regress_server child, void *carg)1199{
1200
1201return regress(0, parent, parg, child, carg);1202}
1203
1204int
1205kcgi_regress_fcgi(kcgi_regress_client parent, void *parg,1206kcgi_regress_server child, void *carg)1207{
1208
1209return regress(1, parent, parg, child, carg);1210}
1211