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(struct bufferevent *bev, 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 (bufferevent_write(bev, &r, sizeof(r)) == -1)
169 return -1;
170 return 0;
173 static int
174 fcgi_send_param(struct bufferevent *bev, int id, const char *name,
175 const char *value)
177 struct fcgi_header h;
178 uint32_t namlen, vallen, padlen;
179 uint8_t s[8];
180 size_t size;
181 char padding[8] = { 0 };
183 namlen = strlen(name);
184 vallen = strlen(value);
185 size = namlen + vallen + 8; /* 4 for the sizes */
186 padlen = (8 - (size & 0x7)) & 0x7;
188 s[0] = ( namlen >> 24) | 0x80;
189 s[1] = ((namlen >> 16) & 0xFF);
190 s[2] = ((namlen >> 8) & 0xFF);
191 s[3] = ( namlen & 0xFF);
193 s[4] = ( vallen >> 24) | 0x80;
194 s[5] = ((vallen >> 16) & 0xFF);
195 s[6] = ((vallen >> 8) & 0xFF);
196 s[7] = ( vallen & 0xFF);
198 prepare_header(&h, FCGI_PARAMS, id, size, padlen);
200 if (bufferevent_write(bev, &h, sizeof(h)) == -1 ||
201 bufferevent_write(bev, s, sizeof(s)) == -1 ||
202 bufferevent_write(bev, name, namlen) == -1 ||
203 bufferevent_write(bev, value, vallen) == -1 ||
204 bufferevent_write(bev, padding, padlen) == -1)
205 return -1;
207 return 0;
210 static int
211 fcgi_end_param(struct bufferevent *bev, int id)
213 struct fcgi_header h;
215 prepare_header(&h, FCGI_PARAMS, id, 0, 0);
216 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
217 return -1;
219 prepare_header(&h, FCGI_STDIN, id, 0, 0);
220 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
221 return -1;
223 return 0;
226 static int
227 fcgi_abort_request(struct bufferevent *bev, int id)
229 struct fcgi_header h;
231 prepare_header(&h, FCGI_ABORT_REQUEST, id, 0, 0);
232 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
233 return -1;
235 return 0;
238 static inline int
239 recid(struct fcgi_header *h)
241 return h->req_id0 + (h->req_id1 << 8) - 1;
244 static inline int
245 reclen(struct fcgi_header *h)
247 return h->content_len0 + (h->content_len1 << 8);
250 static void
251 copy_mbuf(int fd, short ev, void *d)
253 struct client *c = d;
254 struct mbuf *mbuf;
255 size_t len;
256 ssize_t r;
257 char *data;
259 for (;;) {
260 mbuf = TAILQ_FIRST(&c->mbufhead);
261 if (mbuf == NULL)
262 break;
264 len = mbuf->len - mbuf->off;
265 data = mbuf->data + mbuf->off;
266 switch (r = tls_write(c->ctx, data, len)) {
267 case -1:
268 /*
269 * Can't close_conn here. The application
270 * needs to be informed first, otherwise it
271 * can interfere with future connections.
272 * Check also that we're not doing recursion
273 * (copy_mbuf -> handle_fcgi -> copy_mbuf ...)
274 */
275 if (c->next != NULL)
276 goto end;
277 fcgi_abort_request(0, c->id);
278 return;
279 case TLS_WANT_POLLIN:
280 event_once(c->fd, EV_READ, &copy_mbuf, c, NULL);
281 return;
282 case TLS_WANT_POLLOUT:
283 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
284 return;
286 mbuf->off += r;
288 if (mbuf->off == mbuf->len) {
289 TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs);
290 free(mbuf);
294 end:
295 if (c->next != NULL)
296 c->next(0, 0, c);
299 void
300 fcgi_close_backend(struct fcgi *f)
302 bufferevent_free(f->bev);
303 f->bev = NULL;
304 close(fcgi->fd);
305 fcgi->fd = -1;
306 fcgi->pending = 0;
307 fcgi->s = FCGI_OFF;
310 void
311 fcgi_read(struct bufferevent *bev, void *d)
313 struct fcgi *fcgi = d;
314 struct evbuffer *src = EVBUFFER_INPUT(bev);
315 struct fcgi_header hdr;
316 struct fcgi_end_req_body end;
317 struct client *c;
318 struct mbuf *mbuf;
319 size_t len;
321 #if DEBUG_FCGI
322 if (debug_socket == -1) {
323 struct sockaddr_un addr;
325 if ((debug_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
326 err(1, "socket");
328 memset(&addr, 0, sizeof(addr));
329 addr.sun_family = AF_UNIX;
330 strlcpy(addr.sun_path, "./debug.sock", sizeof(addr.sun_path));
331 if (connect(debug_socket, (struct sockaddr*)&addr, sizeof(addr))
332 == -1)
333 err(1, "connect");
335 #endif
337 for (;;) {
338 if (EVBUFFER_LENGTH(src) < sizeof(hdr))
339 return;
341 memcpy(&hdr, EVBUFFER_DATA(src), sizeof(hdr));
343 c = try_client_by_id(recid(&hdr));
344 if (c == NULL) {
345 log_err(NULL,
346 "got invalid client id from fcgi backend %d",
347 recid(&hdr));
348 goto err;
351 len = reclen(&hdr);
353 if (EVBUFFER_LENGTH(src) < sizeof(hdr) + len + hdr.padding)
354 return;
356 #if DEBUG_FCGI
357 write(debug_soocket, EVBUFFER_DATA(src),
358 sizeof(hdr) + len + hdr.padding);
359 #endif
361 evbuffer_drain(src, sizeof(hdr));
363 switch (hdr.type) {
364 case FCGI_END_REQUEST:
365 if (len != sizeof(end)) {
366 log_err(NULL,
367 "got invalid end request record size");
368 goto err;
370 bufferevent_read(bev, &end, sizeof(end));
372 /* TODO: do something with the status? */
373 fcgi->pending--;
374 c->fcgi = -1;
375 c->next = close_conn;
376 event_once(c->fd, EV_WRITE, &copy_mbuf, c, NULL);
377 break;
379 case FCGI_STDERR:
380 /* discard stderr (for now) */
381 evbuffer_drain(src, len);
382 break;
384 case FCGI_STDOUT:
385 if ((mbuf = calloc(1, sizeof(*mbuf) + len)) == NULL)
386 fatal("calloc");
387 mbuf->len = len;
388 bufferevent_read(bev, mbuf->data, len);
390 if (TAILQ_EMPTY(&c->mbufhead))
391 event_once(c->fd, EV_WRITE, &copy_mbuf,
392 c, NULL);
394 TAILQ_INSERT_TAIL(&c->mbufhead, mbuf, mbufs);
395 break;
397 default:
398 log_err(NULL, "got invalid fcgi record (type=%d)",
399 hdr.type);
400 goto err;
403 evbuffer_drain(src, hdr.padding);
405 if (fcgi->pending == 0 && shutting_down) {
406 fcgi_error(bev, EVBUFFER_EOF, fcgi);
407 return;
411 err:
412 fcgi_error(bev, EVBUFFER_ERROR, fcgi);
415 void
416 fcgi_write(struct bufferevent *bev, void *d)
418 /*
419 * There's no much use for the write callback.
420 */
421 return;
424 void
425 fcgi_error(struct bufferevent *bev, short err, void *d)
427 struct fcgi *fcgi = d;
428 struct client *c;
429 size_t i;
431 if (!(err & EVBUFFER_ERROR) ||
432 !(err & EVBUFFER_EOF))
433 log_warn(NULL, "unknown event error (%x)",
434 err);
436 for (i = 0; i < MAX_USERS; ++i) {
437 c = &clients[i];
439 if (c->fcgi != fcgi->id)
440 continue;
442 if (c->code != 0)
443 close_conn(0, 0, 0);
444 else
445 start_reply(c, CGI_ERROR, "CGI error");
448 fcgi_close_backend(fcgi);
451 void
452 fcgi_req(struct fcgi *f, struct client *c)
454 char addr[NI_MAXHOST], buf[22];
455 int e;
456 time_t tim;
457 struct tm tminfo;
458 struct envlist *p;
460 f->pending++;
462 e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
463 addr, sizeof(addr),
464 NULL, 0,
465 NI_NUMERICHOST);
466 if (e != 0)
467 fatal("getnameinfo failed");
469 c->next = NULL;
471 fcgi_begin_request(f->bev, c->id);
472 fcgi_send_param(f->bev, c->id, "GATEWAY_INTERFACE", "CGI/1.1");
473 fcgi_send_param(f->bev, c->id, "GEMINI_URL_PATH", c->iri.path);
474 fcgi_send_param(f->bev, c->id, "QUERY_STRING", c->iri.query);
475 fcgi_send_param(f->bev, c->id, "REMOTE_ADDR", addr);
476 fcgi_send_param(f->bev, c->id, "REMOTE_HOST", addr);
477 fcgi_send_param(f->bev, c->id, "REQUEST_METHOD", "");
478 fcgi_send_param(f->bev, c->id, "SERVER_NAME", c->iri.host);
479 fcgi_send_param(f->bev, c->id, "SERVER_PROTOCOL", "GEMINI");
480 fcgi_send_param(f->bev, c->id, "SERVER_SOFTWARE", GMID_VERSION);
482 if (tls_peer_cert_provided(c->ctx)) {
483 fcgi_send_param(f->bev, c->id, "AUTH_TYPE", "CERTIFICATE");
484 fcgi_send_param(f->bev, c->id, "REMOTE_USER",
485 tls_peer_cert_subject(c->ctx));
486 fcgi_send_param(f->bev, c->id, "TLS_CLIENT_ISSUER",
487 tls_peer_cert_issuer(c->ctx));
488 fcgi_send_param(f->bev, c->id, "TLS_CLIENT_HASH",
489 tls_peer_cert_hash(c->ctx));
490 fcgi_send_param(f->bev, c->id, "TLS_VERSION",
491 tls_conn_version(c->ctx));
492 fcgi_send_param(f->bev, c->id, "TLS_CIPHER",
493 tls_conn_cipher(c->ctx));
495 snprintf(buf, sizeof(buf), "%d",
496 tls_conn_cipher_strength(c->ctx));
497 fcgi_send_param(f->bev, c->id, "TLS_CIPHER_STRENGTH", buf);
499 tim = tls_peer_cert_notbefore(c->ctx);
500 strftime(buf, sizeof(buf), "%FT%TZ",
501 gmtime_r(&tim, &tminfo));
502 fcgi_send_param(f->bev, c->id, "TLS_CLIENT_NOT_BEFORE", buf);
504 tim = tls_peer_cert_notafter(c->ctx);
505 strftime(buf, sizeof(buf), "%FT%TZ",
506 gmtime_r(&tim, &tminfo));
507 fcgi_send_param(f->bev, c->id, "TLS_CLIENT_NOT_AFTER", buf);
509 TAILQ_FOREACH(p, &c->host->params, envs) {
510 fcgi_send_param(f->bev, c->id, p->name, p->value);
512 } else
513 fcgi_send_param(f->bev, c->id, "AUTH_TYPE", "");
515 if (fcgi_end_param(f->bev, c->id) == -1)
516 fcgi_error(f->bev, EVBUFFER_ERROR, f);