1 /*
2 * simple and trivial FastCGI server w/ hard-coded results for use in unit tests
3 * - processes a single FastCGI request at a time (serially)
4 * - listens on FCGI_LISTENSOCK_FILENO
5 * (socket on FCGI_LISTENSOCK_FILENO must be set up by invoker)
6 * expects to be started w/ listening socket already on FCGI_LISTENSOCK_FILENO
7 * - expect recv data for request headers every 10ms or less
8 * - no read timeouts for request body; might block reading request body
9 * - no write timeouts; might block writing response
10 *
11 * Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
12 * License: BSD 3-clause (same as lighttpd)
13 */
14 #if defined(__sun)
15 #define __EXTENSIONS__
16 #endif
17
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <poll.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #ifdef HAVE_SIGNAL /* XXX: must be defined; config.h not included here */
32 #include <signal.h>
33 #endif
34
35 #ifndef MSG_DONTWAIT
36 #define MSG_DONTWAIT 0
37 #endif
38
39 #include "../src/compat/fastcgi.h"
40
41 static int finished;
42 static unsigned char buf[65536];
43
44
45 static void
fcgi_header(FCGI_Header * const header,const unsigned char type,const int request_id,const int contentLength,const unsigned char paddingLength)46 fcgi_header (FCGI_Header * const header, const unsigned char type, const int request_id, const int contentLength, const unsigned char paddingLength)
47 {
48 /*force_assert(contentLength <= FCGI_MAX_LENGTH);*/
49
50 header->version = FCGI_VERSION_1;
51 header->type = type;
52 header->requestIdB1 = (request_id >> 8) & 0xff;
53 header->requestIdB0 = request_id & 0xff;
54 header->contentLengthB1 = (contentLength >> 8) & 0xff;
55 header->contentLengthB0 = contentLength & 0xff;
56 header->paddingLength = paddingLength;
57 header->reserved = 0;
58 }
59
60
61 static void
fcgi_unknown_type_rec(FCGI_UnknownTypeRecord * const rec,const int req_id,const unsigned char type)62 fcgi_unknown_type_rec (FCGI_UnknownTypeRecord * const rec, const int req_id, const unsigned char type)
63 {
64 fcgi_header(&rec->header, FCGI_UNKNOWN_TYPE, req_id, sizeof(rec->header), 0);
65 memset(&rec->body.reserved, 0, sizeof(rec->body.reserved));
66 rec->body.type = type;
67 }
68
69
70 static void
fcgi_end_request_rec(FCGI_EndRequestRecord * const rec,const int req_id,const uint32_t appStatus,const unsigned char protocolStatus)71 fcgi_end_request_rec (FCGI_EndRequestRecord * const rec, const int req_id, const uint32_t appStatus, const unsigned char protocolStatus)
72 {
73 fcgi_header(&rec->header, FCGI_END_REQUEST, req_id, sizeof(rec->header), 0);
74 rec->body.appStatusB3 = (appStatus >> 24) & 0xff;
75 rec->body.appStatusB2 = (appStatus >> 16) & 0xff;
76 rec->body.appStatusB1 = (appStatus >> 8) & 0xff;
77 rec->body.appStatusB0 = appStatus & 0xff;
78 rec->body.protocolStatus = protocolStatus;
79 rec->body.reserved[0] = 0;
80 rec->body.reserved[1] = 0;
81 rec->body.reserved[2] = 0;
82 }
83
84
85 static int
fcgi_puts(const int req_id,const char * const str,size_t len,FILE * const stream)86 fcgi_puts (const int req_id, const char * const str, size_t len, FILE * const stream)
87 {
88 if (NULL == str) return -1;
89
90 FCGI_Header header;
91
92 for (size_t offset = 0, part; offset != len; offset += part) {
93 part = len - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : len - offset;
94 fcgi_header(&header, FCGI_STDOUT, req_id, part, 0);
95 if (1 != fwrite(&header, sizeof(header), 1, stream))
96 return -1;
97 if (part != fwrite(str+offset, 1, part, stream))
98 return -1;
99 }
100
101 return 0;
102 }
103
104
105 static const char *
fcgi_getenv(const unsigned char * const r,const uint32_t rlen,const char * const name,int nlen,int * len)106 fcgi_getenv(const unsigned char * const r, const uint32_t rlen, const char * const name, int nlen, int *len)
107 {
108 /* simple search;
109 * if many lookups are done, then should use more efficient data structure*/
110 for (uint32_t i = 0; i < rlen; ) {
111 int klen = r[i];
112 if (!(r[i] & 0x80))
113 ++i;
114 else {
115 klen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
116 i += 4;
117 }
118 int vlen = r[i];
119 if (!(r[i] & 0x80))
120 ++i;
121 else {
122 vlen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
123 i += 4;
124 }
125
126 if (klen == nlen && 0 == memcmp(r+i, name, klen)) {
127 *len = vlen;
128 return (const char *)r+i+klen;
129 }
130
131 i += klen + vlen;
132 }
133
134 char s[256];
135 if (nlen > (int)sizeof(s)-1)
136 return NULL;
137 memcpy(s, name, nlen);
138 s[nlen] = '\0';
139 char *e = getenv(s);
140 if (e) *len = strlen(e);
141 return e;
142 }
143
144
145 static int
fcgi_process_params(FILE * const stream,int req_id,int role,unsigned char * const r,uint32_t rlen)146 fcgi_process_params (FILE * const stream, int req_id, int role, unsigned char * const r, uint32_t rlen)
147 {
148 const char *p = NULL;
149 int len;
150
151 /* (FCGI_STDIN currently ignored in these FastCGI unit test responses, so
152 * generate response here based on query string values (indicating test) */
153
154 const char *cdata = NULL;
155
156 if (NULL != (p = fcgi_getenv(r, rlen, "QUERY_STRING", 12, &len))) {
157 if (2 == len && 0 == memcmp(p, "lf", 2))
158 cdata = "Status: 200 OK\n\n";
159 else if (4 == len && 0 == memcmp(p, "crlf", 4))
160 cdata = "Status: 200 OK\r\n\r\n";
161 else if (7 == len && 0 == memcmp(p, "slow-lf", 7)) {
162 cdata = "Status: 200 OK\n";
163 if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
164 return -1;
165 fflush(stdout);
166 cdata = "\n";
167 }
168 else if (9 == len && 0 == memcmp(p, "slow-crlf", 9)) {
169 cdata = "Status: 200 OK\r\n";
170 if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
171 return -1;
172 fflush(stdout);
173 cdata = "\r\n";
174 }
175 else if (10 == len && 0 == memcmp(p, "die-at-end", 10)) {
176 cdata = "Status: 200 OK\r\n\r\n";
177 finished = 1;
178 }
179 else if (role == FCGI_AUTHORIZER
180 && len >= 5 && 0 == memcmp(p, "auth-", 5)) {
181 if (7 == len && 0 == memcmp(p, "auth-ok", 7))
182 cdata = "Status: 200 OK\r\n\r\n";
183 else if (8 == len && 0 == memcmp(p, "auth-var", 8)) {
184 /* Status: 200 OK to allow access is implied
185 * if Status header is not included in response */
186 cdata = "Variable-X-LIGHTTPD-FCGI-AUTH: "
187 "LighttpdTestContent\r\n\r\n";
188 p = NULL;
189 }
190 else {
191 cdata = "Status: 403 Forbidden\r\n\r\n";
192 p = NULL;
193 }
194 }
195 else
196 cdata = "Status: 200 OK\r\n\r\n";
197 }
198 else {
199 cdata = "Status: 500 Internal Foo\r\n\r\n";
200 p = NULL;
201 }
202
203 if (cdata && 0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
204 return -1;
205
206 if (NULL == p)
207 cdata = NULL;
208 else if (len > 4 && 0 == memcmp(p, "env=", 4))
209 cdata = fcgi_getenv(r, rlen, p+4, len-4, &len);
210 else if (8 == len && 0 == memcmp(p, "auth-var", 8))
211 cdata = fcgi_getenv(r, rlen, "X_LIGHTTPD_FCGI_AUTH", 20, &len);
212 else {
213 cdata = "test123";
214 len = sizeof("test123")-1;
215 }
216
217 if (cdata && 0 != fcgi_puts(req_id, cdata, (size_t)len, stream))
218 return -1;
219
220 /*(XXX: always sending appStatus 0)*/
221 FCGI_EndRequestRecord endrec;
222 fcgi_end_request_rec(&endrec, req_id, 0, FCGI_REQUEST_COMPLETE);
223 if (1 != fwrite(&endrec, sizeof(endrec), 1, stream))
224 return -1; /* error writing FCGI_END_REQUEST; ignore */
225
226 return -2; /* done */
227 }
228
229
230 static int
fcgi_dispatch_packet(FILE * stream,ssize_t offset,uint32_t len)231 fcgi_dispatch_packet (FILE *stream, ssize_t offset, uint32_t len)
232 {
233 FCGI_Header * const h = (FCGI_Header *)(buf+offset);
234 int req_id = (h->requestIdB1 << 8) | h->requestIdB0;
235 int type = h->type;
236
237 if (type > FCGI_MAXTYPE) {
238 FCGI_UnknownTypeRecord unkrec;
239 fcgi_unknown_type_rec(&unkrec, req_id, type);
240 if (1 != fwrite(&unkrec, sizeof(unkrec), 1, stream))
241 return -1;
242 return 0;
243 }
244
245 if (0 == req_id) {
246 /* not implemented: FCGI_GET_VALUES
247 * FCGI_GET_VALUES_RESULT
248 * FCGI_MAX_CONNS: 1
249 * FCGI_MAX_REQS: 1
250 * FCGI_MPXS_CONNS: 0
251 * ...
252 */
253 return 0;
254 }
255
256 /* XXX: save role from FCGI_BEGIN_REQUEST; should keep independent state */
257 static int role;
258
259 switch (type) {
260 case FCGI_BEGIN_REQUEST:
261 role = (buf[offset+FCGI_HEADER_LEN] << 8)
262 | buf[offset+FCGI_HEADER_LEN+1];
263 return 0; /* ignore; could save req_id and match further packets */
264 case FCGI_ABORT_REQUEST:
265 return -2; /* done */
266 case FCGI_END_REQUEST:
267 return -1; /* unexpected; this server is not sending FastCGI requests */
268 case FCGI_PARAMS:
269 return fcgi_process_params(stream, req_id, role,
270 buf+offset+FCGI_HEADER_LEN, len);
271 case FCGI_STDIN:
272 /* XXX: TODO read and discard request body
273 * (currently ignored in these FastCGI unit tests)
274 * (make basic effort to read body; ignore any timeouts or errors) */
275 return -1; /* unexpected; this server is not expecting request body */
276 case FCGI_STDOUT:
277 return -1; /* unexpected; this server is not sending FastCGI requests */
278 case FCGI_STDERR:
279 return -1; /* unexpected; this server is not sending FastCGI requests */
280 case FCGI_DATA:
281 return -1; /* unexpected; this server is not sending FastCGI requests */
282 case FCGI_GET_VALUES:
283 return 0; /* ignore; not implemented */
284 case FCGI_GET_VALUES_RESULT:
285 return 0; /* ignore; not implemented */
286 default:
287 return -1; /* unexpected */
288 }
289 }
290
291
292 static ssize_t
fcgi_recv_packet(FILE * const stream,ssize_t sz)293 fcgi_recv_packet (FILE * const stream, ssize_t sz)
294 {
295 ssize_t offset = 0;
296 while (sz - offset >= (ssize_t)FCGI_HEADER_LEN) {
297 FCGI_Header * const h = (FCGI_Header *)(buf+offset);
298 uint32_t pad = h->paddingLength;
299 uint32_t len = (h->contentLengthB1 << 8) | h->contentLengthB0;
300 if (sz - offset < (ssize_t)(FCGI_HEADER_LEN + len + pad))
301 break;
302 int rc = fcgi_dispatch_packet(stream, offset, len);
303 if (rc < 0)
304 return rc;
305 offset += (ssize_t)(FCGI_HEADER_LEN + len + pad);
306 }
307 return offset;
308 }
309
310
311 static int
fcgi_recv(const int fd,FILE * const stream)312 fcgi_recv (const int fd, FILE * const stream)
313 {
314 ssize_t rd = 0, offset = 0;
315
316 /* XXX: remain blocking */
317 /*fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);*/
318
319 do {
320 struct pollfd pfd = { fd, POLLIN, 0 };
321 switch (poll(&pfd, 1, 10)) { /* 10ms timeout */
322 default: /* 1; the only pfd has revents */
323 break;
324 case -1: /* error */
325 case 0: /* timeout */
326 pfd.revents |= POLLERR;
327 break;
328 }
329 if (!(pfd.revents & POLLIN))
330 break;
331
332 do {
333 rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT);
334 } while (rd < 0 && errno == EINTR);
335
336 if (rd > 0) {
337 offset += rd;
338 rd = fcgi_recv_packet(stream, offset);
339 if (rd < 0)
340 return (-2 == rd) ? 0 : -1; /*(-2 indicates done)*/
341 if (rd > 0) {
342 offset -= rd;
343 if (offset)
344 memmove(buf, buf+rd, offset);
345 }
346 }
347 else if (0 == rd || (errno != EAGAIN && errno != EWOULDBLOCK))
348 break;
349 } while (offset < (ssize_t)sizeof(buf));
350
351 return -1;
352 }
353
354
355 int
main(void)356 main (void)
357 {
358 int fd;
359 fcntl(FCGI_LISTENSOCK_FILENO, F_SETFL,
360 fcntl(FCGI_LISTENSOCK_FILENO, F_GETFL) & ~O_NONBLOCK);
361
362 #ifdef HAVE_SIGNAL
363 signal(SIGINT, SIG_IGN);
364 signal(SIGUSR1, SIG_IGN);
365 #endif
366
367 do {
368 fd = accept(FCGI_LISTENSOCK_FILENO, NULL, NULL);
369 if (fd < 0)
370 continue;
371 /* XXX: skip checking FCGI_WEB_SERVER_ADDRS; not implemented */
372
373 /* uses stdio to retain prior behavior of output buffering (default)
374 * and flushing with fflush() at specific points */
375 FILE *stream = fdopen(fd, "r+");
376 if (NULL == stream) {
377 close(fd);
378 continue;
379 }
380 fcgi_recv(fd, stream);
381 fflush(stream);
382 fclose(stream);
383 } while (fd > 0 ? !finished : errno == EINTR);
384
385 return 0;
386 }
387