123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- /*
- * simple and trivial FastCGI server w/ hard-coded results for use in unit tests
- * - processes a single FastCGI request at a time (serially)
- * - listens on FCGI_LISTENSOCK_FILENO
- * (socket on FCGI_LISTENSOCK_FILENO must be set up by invoker)
- * expects to be started w/ listening socket already on FCGI_LISTENSOCK_FILENO
- * - expect recv data for request headers every 10ms or less
- * - no read timeouts for request body; might block reading request body
- * - no write timeouts; might block writing response
- *
- * Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
- * License: BSD 3-clause (same as lighttpd)
- */
- #if defined(__sun)
- #define __EXTENSIONS__
- #endif
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <assert.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <poll.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <string.h>
- #include <unistd.h>
- #ifdef HAVE_SIGNAL /* XXX: must be defined; config.h not included here */
- #include <signal.h>
- #endif
- #ifndef MSG_DONTWAIT
- #define MSG_DONTWAIT 0
- #endif
- #include "../src/compat/fastcgi.h"
- static int finished;
- static unsigned char buf[65536];
- static void
- fcgi_header (FCGI_Header * const header, const unsigned char type, const int request_id, const int contentLength, const unsigned char paddingLength)
- {
- /*force_assert(contentLength <= FCGI_MAX_LENGTH);*/
- header->version = FCGI_VERSION_1;
- header->type = type;
- header->requestIdB1 = (request_id >> 8) & 0xff;
- header->requestIdB0 = request_id & 0xff;
- header->contentLengthB1 = (contentLength >> 8) & 0xff;
- header->contentLengthB0 = contentLength & 0xff;
- header->paddingLength = paddingLength;
- header->reserved = 0;
- }
- static void
- fcgi_unknown_type_rec (FCGI_UnknownTypeRecord * const rec, const int req_id, const unsigned char type)
- {
- fcgi_header(&rec->header, FCGI_UNKNOWN_TYPE, req_id, sizeof(rec->header), 0);
- memset(&rec->body.reserved, 0, sizeof(rec->body.reserved));
- rec->body.type = type;
- }
- static void
- fcgi_end_request_rec (FCGI_EndRequestRecord * const rec, const int req_id, const uint32_t appStatus, const unsigned char protocolStatus)
- {
- fcgi_header(&rec->header, FCGI_END_REQUEST, req_id, sizeof(rec->header), 0);
- rec->body.appStatusB3 = (appStatus >> 24) & 0xff;
- rec->body.appStatusB2 = (appStatus >> 16) & 0xff;
- rec->body.appStatusB1 = (appStatus >> 8) & 0xff;
- rec->body.appStatusB0 = appStatus & 0xff;
- rec->body.protocolStatus = protocolStatus;
- rec->body.reserved[0] = 0;
- rec->body.reserved[1] = 0;
- rec->body.reserved[2] = 0;
- }
- static int
- fcgi_puts (const int req_id, const char * const str, size_t len, FILE * const stream)
- {
- if (NULL == str) return -1;
- FCGI_Header header;
- for (size_t offset = 0, part; offset != len; offset += part) {
- part = len - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : len - offset;
- fcgi_header(&header, FCGI_STDOUT, req_id, part, 0);
- if (1 != fwrite(&header, sizeof(header), 1, stream))
- return -1;
- if (part != fwrite(str+offset, 1, part, stream))
- return -1;
- }
- return 0;
- }
- static const char *
- fcgi_getenv(const unsigned char * const r, const uint32_t rlen, const char * const name, int nlen, int *len)
- {
- /* simple search;
- * if many lookups are done, then should use more efficient data structure*/
- for (uint32_t i = 0; i < rlen; ) {
- int klen = r[i];
- if (!(r[i] & 0x80))
- ++i;
- else {
- klen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
- i += 4;
- }
- int vlen = r[i];
- if (!(r[i] & 0x80))
- ++i;
- else {
- vlen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
- i += 4;
- }
- if (klen == nlen && 0 == memcmp(r+i, name, klen)) {
- *len = vlen;
- return (const char *)r+i+klen;
- }
- i += klen + vlen;
- }
- char s[256];
- if (nlen > (int)sizeof(s)-1)
- return NULL;
- memcpy(s, name, nlen);
- s[nlen] = '\0';
- char *e = getenv(s);
- if (e) *len = strlen(e);
- return e;
- }
- static int
- fcgi_process_params (FILE * const stream, int req_id, int role, unsigned char * const r, uint32_t rlen)
- {
- const char *p = NULL;
- int len;
- /* (FCGI_STDIN currently ignored in these FastCGI unit test responses, so
- * generate response here based on query string values (indicating test) */
- const char *cdata = NULL;
- if (NULL != (p = fcgi_getenv(r, rlen, "QUERY_STRING", 12, &len))) {
- if (2 == len && 0 == memcmp(p, "lf", 2))
- cdata = "Status: 200 OK\n\n";
- else if (4 == len && 0 == memcmp(p, "crlf", 4))
- cdata = "Status: 200 OK\r\n\r\n";
- else if (7 == len && 0 == memcmp(p, "slow-lf", 7)) {
- cdata = "Status: 200 OK\n";
- if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
- return -1;
- fflush(stdout);
- cdata = "\n";
- }
- else if (9 == len && 0 == memcmp(p, "slow-crlf", 9)) {
- cdata = "Status: 200 OK\r\n";
- if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
- return -1;
- fflush(stdout);
- cdata = "\r\n";
- }
- else if (10 == len && 0 == memcmp(p, "die-at-end", 10)) {
- cdata = "Status: 200 OK\r\n\r\n";
- finished = 1;
- }
- else if (role == FCGI_AUTHORIZER
- && len >= 5 && 0 == memcmp(p, "auth-", 5)) {
- if (7 == len && 0 == memcmp(p, "auth-ok", 7))
- cdata = "Status: 200 OK\r\n\r\n";
- else if (8 == len && 0 == memcmp(p, "auth-var", 8)) {
- /* Status: 200 OK to allow access is implied
- * if Status header is not included in response */
- cdata = "Variable-X-LIGHTTPD-FCGI-AUTH: "
- "LighttpdTestContent\r\n\r\n";
- p = NULL;
- }
- else {
- cdata = "Status: 403 Forbidden\r\n\r\n";
- p = NULL;
- }
- }
- else
- cdata = "Status: 200 OK\r\n\r\n";
- }
- else {
- cdata = "Status: 500 Internal Foo\r\n\r\n";
- p = NULL;
- }
- if (cdata && 0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
- return -1;
- if (NULL == p)
- cdata = NULL;
- else if (len > 4 && 0 == memcmp(p, "env=", 4))
- cdata = fcgi_getenv(r, rlen, p+4, len-4, &len);
- else if (8 == len && 0 == memcmp(p, "auth-var", 8))
- cdata = fcgi_getenv(r, rlen, "X_LIGHTTPD_FCGI_AUTH", 20, &len);
- else {
- cdata = "test123";
- len = sizeof("test123")-1;
- }
- if (cdata && 0 != fcgi_puts(req_id, cdata, (size_t)len, stream))
- return -1;
- /*(XXX: always sending appStatus 0)*/
- FCGI_EndRequestRecord endrec;
- fcgi_end_request_rec(&endrec, req_id, 0, FCGI_REQUEST_COMPLETE);
- if (1 != fwrite(&endrec, sizeof(endrec), 1, stream))
- return -1; /* error writing FCGI_END_REQUEST; ignore */
- return -2; /* done */
- }
- static int
- fcgi_dispatch_packet (FILE *stream, ssize_t offset, uint32_t len)
- {
- FCGI_Header * const h = (FCGI_Header *)(buf+offset);
- int req_id = (h->requestIdB1 << 8) | h->requestIdB0;
- int type = h->type;
- if (type > FCGI_MAXTYPE) {
- FCGI_UnknownTypeRecord unkrec;
- fcgi_unknown_type_rec(&unkrec, req_id, type);
- if (1 != fwrite(&unkrec, sizeof(unkrec), 1, stream))
- return -1;
- return 0;
- }
- if (0 == req_id) {
- /* not implemented: FCGI_GET_VALUES
- * FCGI_GET_VALUES_RESULT
- * FCGI_MAX_CONNS: 1
- * FCGI_MAX_REQS: 1
- * FCGI_MPXS_CONNS: 0
- * ...
- */
- return 0;
- }
- /* XXX: save role from FCGI_BEGIN_REQUEST; should keep independent state */
- static int role;
- switch (type) {
- case FCGI_BEGIN_REQUEST:
- role = (buf[offset+FCGI_HEADER_LEN] << 8)
- | buf[offset+FCGI_HEADER_LEN+1];
- return 0; /* ignore; could save req_id and match further packets */
- case FCGI_ABORT_REQUEST:
- return -2; /* done */
- case FCGI_END_REQUEST:
- return -1; /* unexpected; this server is not sending FastCGI requests */
- case FCGI_PARAMS:
- return fcgi_process_params(stream, req_id, role,
- buf+offset+FCGI_HEADER_LEN, len);
- case FCGI_STDIN:
- /* XXX: TODO read and discard request body
- * (currently ignored in these FastCGI unit tests)
- * (make basic effort to read body; ignore any timeouts or errors) */
- return -1; /* unexpected; this server is not expecting request body */
- case FCGI_STDOUT:
- return -1; /* unexpected; this server is not sending FastCGI requests */
- case FCGI_STDERR:
- return -1; /* unexpected; this server is not sending FastCGI requests */
- case FCGI_DATA:
- return -1; /* unexpected; this server is not sending FastCGI requests */
- case FCGI_GET_VALUES:
- return 0; /* ignore; not implemented */
- case FCGI_GET_VALUES_RESULT:
- return 0; /* ignore; not implemented */
- default:
- return -1; /* unexpected */
- }
- }
- static ssize_t
- fcgi_recv_packet (FILE * const stream, ssize_t sz)
- {
- ssize_t offset = 0;
- while (sz - offset >= (ssize_t)FCGI_HEADER_LEN) {
- FCGI_Header * const h = (FCGI_Header *)(buf+offset);
- uint32_t pad = h->paddingLength;
- uint32_t len = (h->contentLengthB1 << 8) | h->contentLengthB0;
- if (sz - offset < (ssize_t)(FCGI_HEADER_LEN + len + pad))
- break;
- int rc = fcgi_dispatch_packet(stream, offset, len);
- if (rc < 0)
- return rc;
- offset += (ssize_t)(FCGI_HEADER_LEN + len + pad);
- }
- return offset;
- }
- static int
- fcgi_recv (const int fd, FILE * const stream)
- {
- ssize_t rd = 0, offset = 0;
- /* XXX: remain blocking */
- /*fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);*/
- do {
- struct pollfd pfd = { fd, POLLIN, 0 };
- switch (poll(&pfd, 1, 10)) { /* 10ms timeout */
- default: /* 1; the only pfd has revents */
- break;
- case -1: /* error */
- case 0: /* timeout */
- pfd.revents |= POLLERR;
- break;
- }
- if (!(pfd.revents & POLLIN))
- break;
- do {
- rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT);
- } while (rd < 0 && errno == EINTR);
- if (rd > 0) {
- offset += rd;
- rd = fcgi_recv_packet(stream, offset);
- if (rd < 0)
- return (-2 == rd) ? 0 : -1; /*(-2 indicates done)*/
- if (rd > 0) {
- offset -= rd;
- if (offset)
- memmove(buf, buf+rd, offset);
- }
- }
- else if (0 == rd || (errno != EAGAIN && errno != EWOULDBLOCK))
- break;
- } while (offset < (ssize_t)sizeof(buf));
- return -1;
- }
- int
- main (void)
- {
- int fd;
- fcntl(FCGI_LISTENSOCK_FILENO, F_SETFL,
- fcntl(FCGI_LISTENSOCK_FILENO, F_GETFL) & ~O_NONBLOCK);
- #ifdef HAVE_SIGNAL
- signal(SIGINT, SIG_IGN);
- signal(SIGUSR1, SIG_IGN);
- #endif
- do {
- fd = accept(FCGI_LISTENSOCK_FILENO, NULL, NULL);
- if (fd < 0)
- continue;
- /* XXX: skip checking FCGI_WEB_SERVER_ADDRS; not implemented */
- /* uses stdio to retain prior behavior of output buffering (default)
- * and flushing with fflush() at specific points */
- FILE *stream = fdopen(fd, "r+");
- if (NULL == stream) {
- close(fd);
- continue;
- }
- fcgi_recv(fd, stream);
- fflush(stream);
- fclose(stream);
- } while (fd > 0 ? !finished : errno == EINTR);
- return 0;
- }
|