lighttpd 1.4.x
https://www.lighttpd.net/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
12 KiB
374 lines
12 KiB
/* |
|
* 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) |
|
*/ |
|
|
|
#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> |
|
|
|
#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); |
|
|
|
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; |
|
}
|
|
|