xref: /lighttpd1.4/tests/fcgi-responder.c (revision a931b1fc)
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