Blob


1 /*
2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include "gmid.h"
19 #include <assert.h>
20 #include <errno.h>
21 #include <string.h>
23 /*
24 * Sometimes it can be useful to inspect the fastcgi traffic as
25 * received by gmid.
26 *
27 * This will make gmid connect to a `debug.sock' socket (that must
28 * exists) in the current directory and send there a copy of what gets
29 * read. The socket can be created and monitored e.g. with
30 *
31 * rm -f debug.sock ; nc -Ulk ./debug.sock | hexdump -C
32 *
33 * NB: the sandbox must be disabled for this to work.
34 */
35 #define DEBUG_FCGI 0
37 #ifdef DEBUG_FCGI
38 # include <sys/un.h>
39 static int debug_socket = -1;
40 #endif
42 struct fcgi_header {
43 unsigned char version;
44 unsigned char type;
45 unsigned char req_id1;
46 unsigned char req_id0;
47 unsigned char content_len1;
48 unsigned char content_len0;
49 unsigned char padding;
50 unsigned char reserved;
51 };
53 /*
54 * number of bytes in a FCGI_HEADER. Future version of the protocol
55 * will not reduce this number.
56 */
57 #define FCGI_HEADER_LEN 8
59 /*
60 * values for the version component
61 */
62 #define FCGI_VERSION_1 1
64 /*
65 * values for the type component
66 */
67 #define FCGI_BEGIN_REQUEST 1
68 #define FCGI_ABORT_REQUEST 2
69 #define FCGI_END_REQUEST 3
70 #define FCGI_PARAMS 4
71 #define FCGI_STDIN 5
72 #define FCGI_STDOUT 6
73 #define FCGI_STDERR 7
74 #define FCGI_DATA 8
75 #define FCGI_GET_VALUES 9
76 #define FCGI_GET_VALUES_RESULT 10
77 #define FCGI_UNKNOWN_TYPE 11
78 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
80 struct fcgi_begin_req {
81 unsigned char role1;
82 unsigned char role0;
83 unsigned char flags;
84 unsigned char reserved[5];
85 };
87 struct fcgi_begin_req_record {
88 struct fcgi_header header;
89 struct fcgi_begin_req body;
90 };
92 /*
93 * mask for flags;
94 */
95 #define FCGI_KEEP_CONN 1
97 /*
98 * values for the role
99 */
100 #define FCGI_RESPONDER 1
101 #define FCGI_AUTHORIZER 2
102 #define FCGI_FILTER 3
104 struct fcgi_end_req_body {
105 unsigned char app_status3;
106 unsigned char app_status2;
107 unsigned char app_status1;
108 unsigned char app_status0;
109 unsigned char proto_status;
110 unsigned char reserved[3];
111 };
113 /*
114 * values for proto_status
115 */
116 #define FCGI_REQUEST_COMPLETE 0
117 #define FCGI_CANT_MPX_CONN 1
118 #define FCGI_OVERLOADED 2
119 #define FCGI_UNKNOWN_ROLE 3
121 /*
122 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
123 * records.
124 */
125 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
126 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
127 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
129 static int
130 prepare_header(struct fcgi_header *h, int type, int id, size_t size,
131 size_t padding)
133 memset(h, 0, sizeof(*h));
135 /*
136 * id=0 is reserved for status messages.
137 */
138 id++;
140 h->version = FCGI_VERSION_1;
141 h->type = type;
142 h->req_id1 = (id >> 8);
143 h->req_id0 = (id & 0xFF);
144 h->content_len1 = (size >> 8);
145 h->content_len0 = (size & 0xFF);
146 h->padding = padding;
148 return 0;
151 static int
152 fcgi_begin_request(int sock, int id)
154 struct fcgi_begin_req_record r;
156 if (id > UINT16_MAX)
157 return -1;
159 memset(&r, 0, sizeof(r));
160 prepare_header(&r.header, FCGI_BEGIN_REQUEST, id,
161 sizeof(r.body), 0);
162 assert(sizeof(r.body) == FCGI_HEADER_LEN);
164 r.body.role1 = 0;
165 r.body.role0 = FCGI_RESPONDER;
166 r.body.flags = FCGI_KEEP_CONN;
168 if (write(sock, &r, sizeof(r)) != sizeof(r))
169 return -1;
170 return 0;
173 static int
174 fcgi_send_param(int sock, int id, const char *name, const char *value)
176 struct fcgi_header h;
177 uint32_t namlen, vallen, padlen;
178 uint8_t s[8];
179 size_t size;
180 char padding[8] = { 0 };
182 namlen = strlen(name);
183 vallen = strlen(value);
184 size = namlen + vallen + 8; /* 4 for the sizes */
185 padlen = (8 - (size & 0x7)) & 0x7;
187 s[0] = ( namlen >> 24) | 0x80;
188 s[1] = ((namlen >> 16) & 0xFF);
189 s[2] = ((namlen >> 8) & 0xFF);
190 s[3] = ( namlen & 0xFF);
192 s[4] = ( vallen >> 24) | 0x80;
193 s[5] = ((vallen >> 16) & 0xFF);
194 s[6] = ((vallen >> 8) & 0xFF);
195 s[7] = ( vallen & 0xFF);
197 prepare_header(&h, FCGI_PARAMS, id, size, padlen);
199 if (write(sock, &h, sizeof(h)) != sizeof(h) ||
200 write(sock, s, sizeof(s)) != sizeof(s) ||
201 write(sock, name, namlen) != namlen ||
202 write(sock, value, vallen) != vallen ||
203 write(sock, padding, padlen) != padlen)
204 return -1;
206 return 0;
209 static int
210 fcgi_end_param(int sock, int id)
212 struct fcgi_header h;
214 prepare_header(&h, FCGI_PARAMS, id, 0, 0);
215 if (write(sock, &h, sizeof(h)) != sizeof(h))
216 return -1;
218 prepare_header(&h, FCGI_STDIN, id, 0, 0);
219 if (write(sock, &h, sizeof(h)) != sizeof(h))
220 return -1;
222 return 0;
225 static int
226 fcgi_abort_request(int sock, int id)
228 struct fcgi_header h;
230 prepare_header(&h, FCGI_ABORT_REQUEST, id, 0, 0);
231 if (write(sock, &h, sizeof(h)) != sizeof(h))
232 return -1;
234 return 0;
237 static int
238 must_read(int sock, char *d, size_t len)
240 ssize_t r;
242 #if DEBUG_FCGI
243 if (debug_socket == -1) {
244 struct sockaddr_un addr;
246 if ((debug_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
247 err(1, "socket");
249 memset(&addr, 0, sizeof(addr));
250 addr.sun_family = AF_UNIX;
251 strlcpy(addr.sun_path, "./debug.sock", sizeof(addr.sun_path));
252 if (connect(debug_socket, (struct sockaddr*)&addr, sizeof(addr))
253 == -1)
254 err(1, "connect");
256 #endif
258 for (;;) {
259 switch (r = read(sock, d, len)) {
260 case -1:
261 case 0:
262 return -1;
263 default:
264 #if DEBUG_FCGI
265 write(debug_socket, d, r);
266 #endif
268 if (r == (ssize_t)len)
269 return 0;
270 len -= r;
271 d += r;
276 static int
277 fcgi_read_header(int sock, struct fcgi_header *h)
279 if (must_read(sock, (char*)h, sizeof(*h)) == -1)
280 return -1;
281 if (h->version != FCGI_VERSION_1) {
282 errno = EINVAL;
283 return -1;
285 return 0;
288 static inline int
289 recid(struct fcgi_header *h)
291 return h->req_id0 + (h->req_id1 << 8) - 1;
294 static inline int
295 reclen(struct fcgi_header *h)
297 return h->content_len0 + (h->content_len1 << 8);
300 static void
301 copy_mbuf(int fd, short ev, void *d)
303 struct client *c = d;
304 struct mbuf *mbuf;
305 size_t len;
306 ssize_t r;
307 char *data;
309 for (;;) {
310 mbuf = TAILQ_FIRST(&c->mbufhead);
311 if (mbuf == NULL)
312 break;
314 len = mbuf->len - mbuf->off;
315 data = mbuf->data + mbuf->off;
316 switch (r = tls_write(c->ctx, data, len)) {
317 case -1:
318 /*
319 * Can't close_conn here. The application
320 * needs to be informed first, otherwise it
321 * can interfere with future connections.
322 * Check also that we're not doing recursion
323 * (copy_mbuf -> handle_fcgi -> copy_mbuf ...)
324 */
325 if (c->next != NULL)
326 goto end;
327 fcgi_abort_request(0, c->id);
328 return;
329 case TLS_WANT_POLLIN:
330 event_once(c->fd, EV_READ, &copy_mbuf, c, NULL);
331 return;
332 case TLS_WANT_POLLOUT:
333 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
334 return;
336 mbuf->off += r;
338 if (mbuf->off == mbuf->len) {
339 TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs);
340 free(mbuf);
344 end:
345 if (c->next != NULL)
346 c->next(0, 0, c);
349 static int
350 consume(int fd, size_t len)
352 size_t l;
353 char buf[64];
355 while (len != 0) {
356 if ((l = len) > sizeof(buf))
357 l = sizeof(buf);
358 if (must_read(fd, buf, l) == -1)
359 return 0;
360 len -= l;
363 return 1;
366 static void
367 close_all(struct fcgi *f)
369 size_t i;
370 struct client *c;
372 for (i = 0; i < MAX_USERS; i++) {
373 c = &clients[i];
375 if (c->fcgi != f->id)
376 continue;
378 if (c->code != 0)
379 close_conn(0, 0, c);
380 else
381 start_reply(c, CGI_ERROR, "CGI error");
384 event_del(&f->e);
385 close(f->fd);
386 f->fd = -1;
387 f->s = FCGI_OFF;
390 void
391 handle_fcgi(int sock, short event, void *d)
393 struct fcgi *f = d;
394 struct fcgi_header h;
395 struct fcgi_end_req_body end;
396 struct client *c;
397 struct mbuf *mbuf;
398 size_t len;
400 if (fcgi_read_header(sock, &h) == -1)
401 goto err;
403 c = try_client_by_id(recid(&h));
404 if (c == NULL || c->fcgi != f->id)
405 goto err;
407 len = reclen(&h);
409 switch (h.type) {
410 case FCGI_END_REQUEST:
411 if (len != sizeof(end))
412 goto err;
413 if (must_read(sock, (char*)&end, sizeof(end)) == -1)
414 goto err;
415 /* TODO: do something with the status? */
416 c->fcgi = -1;
417 c->next = close_conn;
418 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
419 break;
421 case FCGI_STDERR:
422 /* discard stderr (for now) */
423 if (!consume(sock, len))
424 goto err;
425 break;
427 case FCGI_STDOUT:
428 if ((mbuf = calloc(1, sizeof(*mbuf) + len)) == NULL)
429 fatal("calloc");
430 mbuf->len = len;
431 if (must_read(sock, mbuf->data, len) == -1) {
432 free(mbuf);
433 goto err;
436 if (TAILQ_EMPTY(&c->mbufhead)) {
437 TAILQ_INSERT_HEAD(&c->mbufhead, mbuf, mbufs);
438 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
439 } else
440 TAILQ_INSERT_TAIL(&c->mbufhead, mbuf, mbufs);
441 break;
443 default:
444 log_err(NULL, "got invalid fcgi record (type=%d)", h.type);
445 goto err;
448 if (!consume(sock, h.padding))
449 goto err;
450 return;
452 err:
453 close_all(f);
456 void
457 send_fcgi_req(struct fcgi *f, struct client *c)
459 c->next = NULL;
461 fcgi_begin_request(f->fd, c->id);
462 fcgi_send_param(f->fd, c->id, "QUERY_STRING", c->iri.query);
463 fcgi_send_param(f->fd, c->id, "GEMINI_URL_PATH", c->iri.path);
464 fcgi_send_param(f->fd, c->id, "SERVER_SOFTWARE", "gmid/1.7");
465 /* ... */
467 if (fcgi_end_param(f->fd, c->id) == -1)
468 close_all(f);