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 #if 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 fcgi_close_backend(f);
387 void
388 fcgi_close_backend(struct fcgi *f)
390 event_del(&f->e);
391 close(f->fd);
392 f->fd = -1;
393 f->pending = 0;
394 f->s = FCGI_OFF;
397 void
398 handle_fcgi(int sock, short event, void *d)
400 struct fcgi *f = d;
401 struct fcgi_header h;
402 struct fcgi_end_req_body end;
403 struct client *c;
404 struct mbuf *mbuf;
405 size_t len;
407 if (fcgi_read_header(sock, &h) == -1)
408 goto err;
410 c = try_client_by_id(recid(&h));
411 if (c == NULL || c->fcgi != f->id)
412 goto err;
414 len = reclen(&h);
416 switch (h.type) {
417 case FCGI_END_REQUEST:
418 if (len != sizeof(end))
419 goto err;
420 if (must_read(sock, (char*)&end, sizeof(end)) == -1)
421 goto err;
422 /* TODO: do something with the status? */
424 f->pending--;
425 c->fcgi = -1;
426 c->next = close_conn;
427 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
428 break;
430 case FCGI_STDERR:
431 /* discard stderr (for now) */
432 if (!consume(sock, len))
433 goto err;
434 break;
436 case FCGI_STDOUT:
437 if ((mbuf = calloc(1, sizeof(*mbuf) + len)) == NULL)
438 fatal("calloc");
439 mbuf->len = len;
440 if (must_read(sock, mbuf->data, len) == -1) {
441 free(mbuf);
442 goto err;
445 if (TAILQ_EMPTY(&c->mbufhead)) {
446 TAILQ_INSERT_HEAD(&c->mbufhead, mbuf, mbufs);
447 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
448 } else
449 TAILQ_INSERT_TAIL(&c->mbufhead, mbuf, mbufs);
450 break;
452 default:
453 log_err(NULL, "got invalid fcgi record (type=%d)", h.type);
454 goto err;
457 if (!consume(sock, h.padding))
458 goto err;
460 if (f->pending == 0 && shutting_down)
461 fcgi_close_backend(f);
463 return;
465 err:
466 close_all(f);
469 void
470 send_fcgi_req(struct fcgi *f, struct client *c)
472 char addr[NI_MAXHOST], buf[22];
473 int e;
474 time_t tim;
475 struct tm tminfo;
476 struct envlist *p;
478 f->pending++;
480 e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
481 addr, sizeof(addr),
482 NULL, 0,
483 NI_NUMERICHOST);
484 if (e != 0)
485 fatal("getnameinfo failed");
487 c->next = NULL;
489 fcgi_begin_request(f->fd, c->id);
490 fcgi_send_param(f->fd, c->id, "GATEWAY_INTERFACE", "CGI/1.1");
491 fcgi_send_param(f->fd, c->id, "GEMINI_URL_PATH", c->iri.path);
492 fcgi_send_param(f->fd, c->id, "QUERY_STRING", c->iri.query);
493 fcgi_send_param(f->fd, c->id, "REMOTE_ADDR", addr);
494 fcgi_send_param(f->fd, c->id, "REMOTE_HOST", addr);
495 fcgi_send_param(f->fd, c->id, "REQUEST_METHOD", "");
496 fcgi_send_param(f->fd, c->id, "SERVER_NAME", c->iri.host);
497 fcgi_send_param(f->fd, c->id, "SERVER_PROTOCOL", "GEMINI");
498 fcgi_send_param(f->fd, c->id, "SERVER_SOFTWARE", GMID_VERSION);
500 if (tls_peer_cert_provided(c->ctx)) {
501 fcgi_send_param(f->fd, c->id, "AUTH_TYPE", "CERTIFICATE");
502 fcgi_send_param(f->fd, c->id, "REMOTE_USER",
503 tls_peer_cert_subject(c->ctx));
504 fcgi_send_param(f->fd, c->id, "TLS_CLIENT_ISSUER",
505 tls_peer_cert_issuer(c->ctx));
506 fcgi_send_param(f->fd, c->id, "TLS_CLIENT_HASH",
507 tls_peer_cert_hash(c->ctx));
508 fcgi_send_param(f->fd, c->id, "TLS_VERSION",
509 tls_conn_version(c->ctx));
510 fcgi_send_param(f->fd, c->id, "TLS_CIPHER",
511 tls_conn_cipher(c->ctx));
513 snprintf(buf, sizeof(buf), "%d",
514 tls_conn_cipher_strength(c->ctx));
515 fcgi_send_param(f->fd, c->id, "TLS_CIPHER_STRENGTH", buf);
517 tim = tls_peer_cert_notbefore(c->ctx);
518 strftime(buf, sizeof(buf), "%FT%TZ",
519 gmtime_r(&tim, &tminfo));
520 fcgi_send_param(f->fd, c->id, "TLS_CLIENT_NOT_BEFORE", buf);
522 tim = tls_peer_cert_notafter(c->ctx);
523 strftime(buf, sizeof(buf), "%FT%TZ",
524 gmtime_r(&tim, &tminfo));
525 fcgi_send_param(f->fd, c->id, "TLS_CLIENT_NOT_AFTER", buf);
527 TAILQ_FOREACH(p, &c->host->params, envs) {
528 fcgi_send_param(f->fd, c->id, p->name, p->value);
530 } else
531 fcgi_send_param(f->fd, c->id, "AUTH_TYPE", "");
533 if (fcgi_end_param(f->fd, c->id) == -1)
534 close_all(f);