Blob


1 /*
2 * Copyright (c) 2021, 2022, 2023 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 #include "log.h"
25 /*
26 * Sometimes it can be useful to inspect the fastcgi traffic as
27 * received by gmid.
28 *
29 * This will make gmid connect to a `debug.sock' socket (that must
30 * exists) in the current directory and send there a copy of what gets
31 * read. The socket can be created and monitored e.g. with
32 *
33 * rm -f debug.sock ; nc -Ulk ./debug.sock | hexdump -C
34 *
35 * NB: the sandbox must be disabled for this to work.
36 */
37 #define DEBUG_FCGI 0
39 #if DEBUG_FCGI
40 # include <sys/un.h>
41 static int debug_socket = -1;
42 #endif
44 struct fcgi_header {
45 unsigned char version;
46 unsigned char type;
47 unsigned char req_id1;
48 unsigned char req_id0;
49 unsigned char content_len1;
50 unsigned char content_len0;
51 unsigned char padding;
52 unsigned char reserved;
53 };
55 /*
56 * number of bytes in a FCGI_HEADER. Future version of the protocol
57 * will not reduce this number.
58 */
59 #define FCGI_HEADER_LEN 8
61 /*
62 * values for the version component
63 */
64 #define FCGI_VERSION_1 1
66 /*
67 * values for the type component
68 */
69 #define FCGI_BEGIN_REQUEST 1
70 #define FCGI_ABORT_REQUEST 2
71 #define FCGI_END_REQUEST 3
72 #define FCGI_PARAMS 4
73 #define FCGI_STDIN 5
74 #define FCGI_STDOUT 6
75 #define FCGI_STDERR 7
76 #define FCGI_DATA 8
77 #define FCGI_GET_VALUES 9
78 #define FCGI_GET_VALUES_RESULT 10
79 #define FCGI_UNKNOWN_TYPE 11
80 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
82 struct fcgi_begin_req {
83 unsigned char role1;
84 unsigned char role0;
85 unsigned char flags;
86 unsigned char reserved[5];
87 };
89 struct fcgi_begin_req_record {
90 struct fcgi_header header;
91 struct fcgi_begin_req body;
92 };
94 /*
95 * mask for flags;
96 */
97 #define FCGI_KEEP_CONN 1
99 /*
100 * values for the role
101 */
102 #define FCGI_RESPONDER 1
103 #define FCGI_AUTHORIZER 2
104 #define FCGI_FILTER 3
106 struct fcgi_end_req_body {
107 unsigned char app_status3;
108 unsigned char app_status2;
109 unsigned char app_status1;
110 unsigned char app_status0;
111 unsigned char proto_status;
112 unsigned char reserved[3];
113 };
115 /*
116 * values for proto_status
117 */
118 #define FCGI_REQUEST_COMPLETE 0
119 #define FCGI_CANT_MPX_CONN 1
120 #define FCGI_OVERLOADED 2
121 #define FCGI_UNKNOWN_ROLE 3
123 /*
124 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
125 * records.
126 */
127 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
128 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
129 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
131 static int
132 prepare_header(struct fcgi_header *h, int type, size_t size,
133 size_t padding)
135 int id = 1;
137 memset(h, 0, sizeof(*h));
139 h->version = FCGI_VERSION_1;
140 h->type = type;
141 h->req_id1 = (id >> 8);
142 h->req_id0 = (id & 0xFF);
143 h->content_len1 = (size >> 8);
144 h->content_len0 = (size & 0xFF);
145 h->padding = padding;
147 return 0;
150 static int
151 fcgi_begin_request(struct bufferevent *bev)
153 struct fcgi_begin_req_record r;
155 memset(&r, 0, sizeof(r));
156 prepare_header(&r.header, FCGI_BEGIN_REQUEST, sizeof(r.body), 0);
157 assert(sizeof(r.body) == FCGI_HEADER_LEN);
159 r.body.role1 = 0;
160 r.body.role0 = FCGI_RESPONDER;
161 r.body.flags = FCGI_KEEP_CONN;
163 if (bufferevent_write(bev, &r, sizeof(r)) == -1)
164 return -1;
165 return 0;
168 static int
169 fcgi_send_param(struct bufferevent *bev, const char *name,
170 const char *value)
172 struct fcgi_header h;
173 uint32_t namlen, vallen, padlen;
174 uint8_t s[8];
175 size_t size;
176 const char padding[8] = { 0 };
178 namlen = strlen(name);
179 vallen = strlen(value);
180 size = namlen + vallen + 8; /* 4 for the sizes */
181 padlen = (8 - (size & 0x7)) & 0x7;
183 s[0] = ( namlen >> 24) | 0x80;
184 s[1] = ((namlen >> 16) & 0xFF);
185 s[2] = ((namlen >> 8) & 0xFF);
186 s[3] = ( namlen & 0xFF);
188 s[4] = ( vallen >> 24) | 0x80;
189 s[5] = ((vallen >> 16) & 0xFF);
190 s[6] = ((vallen >> 8) & 0xFF);
191 s[7] = ( vallen & 0xFF);
193 prepare_header(&h, FCGI_PARAMS, size, padlen);
195 if (bufferevent_write(bev, &h, sizeof(h)) == -1 ||
196 bufferevent_write(bev, s, sizeof(s)) == -1 ||
197 bufferevent_write(bev, name, namlen) == -1 ||
198 bufferevent_write(bev, value, vallen) == -1 ||
199 bufferevent_write(bev, padding, padlen) == -1)
200 return -1;
202 return 0;
205 static int
206 fcgi_end_param(struct bufferevent *bev)
208 struct fcgi_header h;
210 prepare_header(&h, FCGI_PARAMS, 0, 0);
211 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
212 return -1;
214 prepare_header(&h, FCGI_STDIN, 0, 0);
215 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
216 return -1;
218 return 0;
221 static inline int
222 recid(struct fcgi_header *h)
224 return h->req_id0 + (h->req_id1 << 8);
227 static inline int
228 reclen(struct fcgi_header *h)
230 return h->content_len0 + (h->content_len1 << 8);
233 void
234 fcgi_read(struct bufferevent *bev, void *d)
236 struct client *c = d;
237 struct evbuffer *src = EVBUFFER_INPUT(bev);
238 struct fcgi_header hdr;
239 struct fcgi_end_req_body end;
240 size_t len;
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 fatal("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 fatal("connect");
256 #endif
258 for (;;) {
259 if (EVBUFFER_LENGTH(src) < sizeof(hdr))
260 return;
262 memcpy(&hdr, EVBUFFER_DATA(src), sizeof(hdr));
264 if (recid(&hdr) != 1) {
265 log_warnx("got invalid client id %d from fcgi backend",
266 recid(&hdr));
267 goto err;
270 len = reclen(&hdr);
272 if (EVBUFFER_LENGTH(src) < sizeof(hdr) + len + hdr.padding)
273 return;
275 #if DEBUG_FCGI
276 write(debug_socket, EVBUFFER_DATA(src),
277 sizeof(hdr) + len + hdr.padding);
278 #endif
280 evbuffer_drain(src, sizeof(hdr));
282 switch (hdr.type) {
283 case FCGI_END_REQUEST:
284 if (len != sizeof(end)) {
285 log_warnx("got invalid end request"
286 " record size");
287 goto err;
289 bufferevent_read(bev, &end, sizeof(end));
291 /* TODO: do something with the status? */
292 c->type = REQUEST_DONE;
293 client_write(c->bev, c);
294 return;
296 case FCGI_STDERR:
297 /* discard stderr (for now) */
298 evbuffer_drain(src, len);
299 break;
301 case FCGI_STDOUT:
302 bufferevent_write(c->bev, EVBUFFER_DATA(src), len);
303 evbuffer_drain(src, len);
304 break;
306 default:
307 log_warnx("got invalid fcgi record (type=%d)",
308 hdr.type);
309 goto err;
312 evbuffer_drain(src, hdr.padding);
315 err:
316 fcgi_error(bev, EVBUFFER_ERROR, c);
319 void
320 fcgi_write(struct bufferevent *bev, void *d)
322 /*
323 * There's no much use for the write callback.
324 */
325 return;
328 void
329 fcgi_error(struct bufferevent *bev, short err, void *d)
331 struct client *c = d;
333 if (!(err & (EVBUFFER_ERROR|EVBUFFER_EOF)))
334 log_warn("unknown event error (%x)", err);
336 c->type = REQUEST_DONE;
337 if (c->code != 0)
338 client_close(c);
339 else
340 start_reply(c, CGI_ERROR, "CGI error");
343 void
344 fcgi_req(struct client *c)
346 char addr[NI_MAXHOST], buf[22];
347 char *qs;
348 int e;
349 time_t tim;
350 struct tm tminfo;
351 struct envlist *p;
353 e = getnameinfo((struct sockaddr*)&c->raddr, c->raddrlen,
354 addr, sizeof(addr),
355 NULL, 0,
356 NI_NUMERICHOST);
357 if (e != 0)
358 fatalx("getnameinfo failed: %s", gai_strerror(e));
360 fcgi_begin_request(c->cgibev);
361 fcgi_send_param(c->cgibev, "GATEWAY_INTERFACE", "CGI/1.1");
362 fcgi_send_param(c->cgibev, "GEMINI_URL_PATH", c->iri.path);
363 fcgi_send_param(c->cgibev, "QUERY_STRING", c->iri.query);
364 fcgi_send_param(c->cgibev, "REMOTE_ADDR", addr);
365 fcgi_send_param(c->cgibev, "REMOTE_HOST", addr);
366 fcgi_send_param(c->cgibev, "REQUEST_METHOD", "");
367 fcgi_send_param(c->cgibev, "SERVER_NAME", c->iri.host);
368 fcgi_send_param(c->cgibev, "SERVER_PROTOCOL", "GEMINI");
369 fcgi_send_param(c->cgibev, "SERVER_SOFTWARE", GMID_VERSION);
371 if (*c->iri.query != '\0' &&
372 strchr(c->iri.query, '=') == NULL &&
373 (qs = strdup(c->iri.query)) != NULL) {
374 pct_decode_str(qs);
375 fcgi_send_param(c->cgibev, "GEMINI_SEARCH_STRING", qs);
376 free(qs);
379 TAILQ_FOREACH(p, &c->host->params, envs) {
380 fcgi_send_param(c->cgibev, p->name, p->value);
383 if (tls_peer_cert_provided(c->ctx)) {
384 fcgi_send_param(c->cgibev, "AUTH_TYPE", "CERTIFICATE");
385 fcgi_send_param(c->cgibev, "REMOTE_USER",
386 tls_peer_cert_subject(c->ctx));
387 fcgi_send_param(c->cgibev, "TLS_CLIENT_ISSUER",
388 tls_peer_cert_issuer(c->ctx));
389 fcgi_send_param(c->cgibev, "TLS_CLIENT_HASH",
390 tls_peer_cert_hash(c->ctx));
391 fcgi_send_param(c->cgibev, "TLS_VERSION",
392 tls_conn_version(c->ctx));
393 fcgi_send_param(c->cgibev, "TLS_CIPHER",
394 tls_conn_cipher(c->ctx));
396 snprintf(buf, sizeof(buf), "%d",
397 tls_conn_cipher_strength(c->ctx));
398 fcgi_send_param(c->cgibev, "TLS_CIPHER_STRENGTH", buf);
400 tim = tls_peer_cert_notbefore(c->ctx);
401 strftime(buf, sizeof(buf), "%FT%TZ",
402 gmtime_r(&tim, &tminfo));
403 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_BEFORE", buf);
405 tim = tls_peer_cert_notafter(c->ctx);
406 strftime(buf, sizeof(buf), "%FT%TZ",
407 gmtime_r(&tim, &tminfo));
408 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_AFTER", buf);
410 } else
411 fcgi_send_param(c->cgibev, "AUTH_TYPE", "");
413 if (fcgi_end_param(c->cgibev) == -1)
414 fcgi_error(c->cgibev, EVBUFFER_ERROR, c);