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, size_t size,
131 size_t padding)
133 int id = 1;
135 memset(h, 0, sizeof(*h));
137 h->version = FCGI_VERSION_1;
138 h->type = type;
139 h->req_id1 = (id >> 8);
140 h->req_id0 = (id & 0xFF);
141 h->content_len1 = (size >> 8);
142 h->content_len0 = (size & 0xFF);
143 h->padding = padding;
145 return 0;
148 static int
149 fcgi_begin_request(struct bufferevent *bev)
151 struct fcgi_begin_req_record r;
153 memset(&r, 0, sizeof(r));
154 prepare_header(&r.header, FCGI_BEGIN_REQUEST, sizeof(r.body), 0);
155 assert(sizeof(r.body) == FCGI_HEADER_LEN);
157 r.body.role1 = 0;
158 r.body.role0 = FCGI_RESPONDER;
159 r.body.flags = FCGI_KEEP_CONN;
161 if (bufferevent_write(bev, &r, sizeof(r)) == -1)
162 return -1;
163 return 0;
166 static int
167 fcgi_send_param(struct bufferevent *bev, const char *name,
168 const char *value)
170 struct fcgi_header h;
171 uint32_t namlen, vallen, padlen;
172 uint8_t s[8];
173 size_t size;
174 const char padding[8] = { 0 };
176 namlen = strlen(name);
177 vallen = strlen(value);
178 size = namlen + vallen + 8; /* 4 for the sizes */
179 padlen = (8 - (size & 0x7)) & 0x7;
181 s[0] = ( namlen >> 24) | 0x80;
182 s[1] = ((namlen >> 16) & 0xFF);
183 s[2] = ((namlen >> 8) & 0xFF);
184 s[3] = ( namlen & 0xFF);
186 s[4] = ( vallen >> 24) | 0x80;
187 s[5] = ((vallen >> 16) & 0xFF);
188 s[6] = ((vallen >> 8) & 0xFF);
189 s[7] = ( vallen & 0xFF);
191 prepare_header(&h, FCGI_PARAMS, size, padlen);
193 if (bufferevent_write(bev, &h, sizeof(h)) == -1 ||
194 bufferevent_write(bev, s, sizeof(s)) == -1 ||
195 bufferevent_write(bev, name, namlen) == -1 ||
196 bufferevent_write(bev, value, vallen) == -1 ||
197 bufferevent_write(bev, padding, padlen) == -1)
198 return -1;
200 return 0;
203 static int
204 fcgi_end_param(struct bufferevent *bev)
206 struct fcgi_header h;
208 prepare_header(&h, FCGI_PARAMS, 0, 0);
209 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
210 return -1;
212 prepare_header(&h, FCGI_STDIN, 0, 0);
213 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
214 return -1;
216 return 0;
219 static inline int
220 recid(struct fcgi_header *h)
222 return h->req_id0 + (h->req_id1 << 8);
225 static inline int
226 reclen(struct fcgi_header *h)
228 return h->content_len0 + (h->content_len1 << 8);
231 void
232 fcgi_read(struct bufferevent *bev, void *d)
234 struct client *c = d;
235 struct evbuffer *src = EVBUFFER_INPUT(bev);
236 struct fcgi_header hdr;
237 struct fcgi_end_req_body end;
238 size_t len;
240 #if DEBUG_FCGI
241 if (debug_socket == -1) {
242 struct sockaddr_un addr;
244 if ((debug_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
245 err(1, "socket");
247 memset(&addr, 0, sizeof(addr));
248 addr.sun_family = AF_UNIX;
249 strlcpy(addr.sun_path, "./debug.sock", sizeof(addr.sun_path));
250 if (connect(debug_socket, (struct sockaddr*)&addr, sizeof(addr))
251 == -1)
252 err(1, "connect");
254 #endif
256 for (;;) {
257 if (EVBUFFER_LENGTH(src) < sizeof(hdr))
258 return;
260 memcpy(&hdr, EVBUFFER_DATA(src), sizeof(hdr));
262 if (recid(&hdr) != 1) {
263 log_err(NULL,
264 "got invalid client id %d from fcgi backend",
265 recid(&hdr));
266 goto err;
269 len = reclen(&hdr);
271 if (EVBUFFER_LENGTH(src) < sizeof(hdr) + len + hdr.padding)
272 return;
274 #if DEBUG_FCGI
275 write(debug_socket, EVBUFFER_DATA(src),
276 sizeof(hdr) + len + hdr.padding);
277 #endif
279 evbuffer_drain(src, sizeof(hdr));
281 switch (hdr.type) {
282 case FCGI_END_REQUEST:
283 if (len != sizeof(end)) {
284 log_err(NULL,
285 "got invalid end request record size");
286 goto err;
288 bufferevent_read(bev, &end, sizeof(end));
290 /* TODO: do something with the status? */
291 c->type = REQUEST_DONE;
292 client_write(c->bev, c);
293 break;
295 case FCGI_STDERR:
296 /* discard stderr (for now) */
297 evbuffer_drain(src, len);
298 break;
300 case FCGI_STDOUT:
301 bufferevent_write(c->bev, EVBUFFER_DATA(src), len);
302 evbuffer_drain(src, len);
303 break;
305 default:
306 log_err(NULL, "got invalid fcgi record (type=%d)",
307 hdr.type);
308 goto err;
311 evbuffer_drain(src, hdr.padding);
314 err:
315 fcgi_error(bev, EVBUFFER_ERROR, c);
318 void
319 fcgi_write(struct bufferevent *bev, void *d)
321 /*
322 * There's no much use for the write callback.
323 */
324 return;
327 void
328 fcgi_error(struct bufferevent *bev, short err, void *d)
330 struct client *c = d;
332 if (!(err & (EVBUFFER_ERROR|EVBUFFER_EOF)))
333 log_warn(NULL, "unknown event error (%x): %s",
334 err, strerror(errno));
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 int e;
348 time_t tim;
349 struct tm tminfo;
350 struct envlist *p;
352 e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
353 addr, sizeof(addr),
354 NULL, 0,
355 NI_NUMERICHOST);
356 if (e != 0)
357 fatal("getnameinfo failed: %s (%s)",
358 gai_strerror(e), strerror(errno));
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 TAILQ_FOREACH(p, &c->host->params, envs) {
372 fcgi_send_param(c->cgibev, p->name, p->value);
375 if (tls_peer_cert_provided(c->ctx)) {
376 fcgi_send_param(c->cgibev, "AUTH_TYPE", "CERTIFICATE");
377 fcgi_send_param(c->cgibev, "REMOTE_USER",
378 tls_peer_cert_subject(c->ctx));
379 fcgi_send_param(c->cgibev, "TLS_CLIENT_ISSUER",
380 tls_peer_cert_issuer(c->ctx));
381 fcgi_send_param(c->cgibev, "TLS_CLIENT_HASH",
382 tls_peer_cert_hash(c->ctx));
383 fcgi_send_param(c->cgibev, "TLS_VERSION",
384 tls_conn_version(c->ctx));
385 fcgi_send_param(c->cgibev, "TLS_CIPHER",
386 tls_conn_cipher(c->ctx));
388 snprintf(buf, sizeof(buf), "%d",
389 tls_conn_cipher_strength(c->ctx));
390 fcgi_send_param(c->cgibev, "TLS_CIPHER_STRENGTH", buf);
392 tim = tls_peer_cert_notbefore(c->ctx);
393 strftime(buf, sizeof(buf), "%FT%TZ",
394 gmtime_r(&tim, &tminfo));
395 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_BEFORE", buf);
397 tim = tls_peer_cert_notafter(c->ctx);
398 strftime(buf, sizeof(buf), "%FT%TZ",
399 gmtime_r(&tim, &tminfo));
400 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_AFTER", buf);
402 } else
403 fcgi_send_param(c->cgibev, "AUTH_TYPE", "");
405 if (fcgi_end_param(c->cgibev) == -1)
406 fcgi_error(c->cgibev, EVBUFFER_ERROR, c);