Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2022 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 : * USA.
19 : */
20 : /* Implementation of a (subset of) FastCGI according to
21 : * https://fastcgi-archives.github.io/FastCGI_Specification.html
22 : * Some things are not fully implemented, but this seems enough to get
23 : * Lwan to proxy PHP-FPM through FastCGI.
24 : */
25 : /* FIXME: not a lot of the private APIs that needed to be added to
26 : * support this module have a good API. Revisit this someday. */
27 :
28 : #define _GNU_SOURCE
29 : #include <arpa/inet.h>
30 : #include <ctype.h>
31 : #include <errno.h>
32 : #include <fcntl.h>
33 : #include <limits.h>
34 : #include <netinet/in.h>
35 : #include <stdlib.h>
36 : #include <string.h>
37 : #include <sys/socket.h>
38 : #include <sys/stat.h>
39 : #include <sys/uio.h>
40 : #include <sys/un.h>
41 : #include <unistd.h>
42 :
43 : #include "lwan-private.h"
44 :
45 : #include "int-to-str.h"
46 : #include "patterns.h"
47 : #include "realpathat.h"
48 : #include "lwan-cache.h"
49 : #include "lwan-mod-fastcgi.h"
50 : #include "lwan-strbuf.h"
51 :
52 : #define FASTCGI_ROLE_RESPONDER 1
53 :
54 : /* FIXME: use this to pool connections later? */
55 : #define FASTCGI_FLAGS_KEEP_CONN 1
56 :
57 : #define FASTCGI_TYPE_BEGIN_REQUEST 1
58 : #define FASTCGI_TYPE_END_REQUEST 3
59 : #define FASTCGI_TYPE_PARAMS 4
60 : #define FASTCGI_TYPE_STDIN 5
61 : #define FASTCGI_TYPE_STDOUT 6
62 : #define FASTCGI_TYPE_STDERR 7
63 :
64 : struct private_data {
65 : union {
66 : struct sockaddr_un un_addr;
67 : struct sockaddr_in in_addr;
68 : struct sockaddr_in6 in6_addr;
69 : struct sockaddr_storage sock_addr;
70 : };
71 : sa_family_t addr_family;
72 : socklen_t addr_size;
73 :
74 : struct cache *script_name_cache;
75 :
76 : struct lwan_value default_index;
77 :
78 : char *script_path;
79 : int script_path_fd;
80 : };
81 :
82 : struct record {
83 : uint8_t version;
84 : uint8_t type;
85 : uint16_t id;
86 : uint16_t len_content;
87 : uint8_t len_padding;
88 : uint8_t reserved;
89 : } __attribute__((packed));
90 :
91 : static_assert(sizeof(struct record) == 8, "Sane record size");
92 :
93 : struct begin_request_body {
94 : uint16_t role;
95 : uint8_t flags;
96 : uint8_t padding[5];
97 : } __attribute__((packed));
98 :
99 : static_assert(sizeof(struct begin_request_body) == 8,
100 : "Sane begin_request_body size");
101 :
102 : struct request_header {
103 : struct record begin_request;
104 : struct begin_request_body begin_request_body;
105 : struct record begin_params;
106 : } __attribute__((packed));
107 :
108 : struct script_name_cache_entry {
109 : struct cache_entry base;
110 : char *script_name;
111 : char *script_filename;
112 : };
113 :
114 0 : static void close_fd(void *data)
115 : {
116 0 : int fd = (int)(intptr_t)data;
117 :
118 0 : close(fd);
119 0 : }
120 :
121 0 : static void add_param_len(struct lwan_strbuf *strbuf,
122 : const char *key,
123 : size_t len_key,
124 : const char *value,
125 : size_t len_value)
126 : {
127 : /* FIXME: these should be enabled for release builds, too! */
128 0 : assert(len_key <= INT_MAX);
129 0 : assert(len_value <= INT_MAX);
130 :
131 0 : if (len_key <= 127) {
132 0 : lwan_strbuf_append_char(strbuf, (char)len_key);
133 : } else {
134 0 : uint32_t len_net = htonl((uint32_t)len_key) | 1u << 31;
135 0 : lwan_strbuf_append_str(strbuf, (char *)&len_net, 4);
136 : }
137 :
138 0 : if (len_value <= 127) {
139 0 : lwan_strbuf_append_char(strbuf, (char)len_value);
140 : } else {
141 0 : uint32_t len_net = htonl((uint32_t)len_value) | 1u << 31;
142 0 : lwan_strbuf_append_str(strbuf, (char *)&len_net, 4);
143 : }
144 :
145 0 : lwan_strbuf_append_str(strbuf, key, len_key);
146 0 : lwan_strbuf_append_str(strbuf, value, len_value);
147 0 : }
148 :
149 : static inline void
150 0 : add_param(struct lwan_strbuf *strbuf, const char *key, const char *value)
151 : {
152 0 : return add_param_len(strbuf, key, strlen(key), value, strlen(value));
153 : }
154 :
155 : static inline void
156 0 : add_int_param(struct lwan_strbuf *strbuf, const char *key, ssize_t value)
157 : {
158 : size_t len;
159 : char buffer[INT_TO_STR_BUFFER_SIZE];
160 0 : char *p = int_to_string(value, buffer, &len);
161 :
162 0 : return add_param_len(strbuf, key, strlen(key), p, len);
163 : }
164 :
165 0 : static struct cache_entry *create_script_name(const void *keyptr, void *context)
166 : {
167 0 : struct private_data *pd = context;
168 : struct script_name_cache_entry *entry;
169 0 : const struct lwan_value *url = keyptr;
170 : int r;
171 :
172 0 : entry = malloc(sizeof(*entry));
173 0 : if (!entry)
174 0 : return NULL;
175 :
176 0 : if (!url->len)
177 0 : url = &pd->default_index;
178 :
179 : /* SCRIPT_NAME */
180 0 : r = asprintf(&entry->script_name, "/%.*s", (int)url->len, url->value);
181 0 : if (r < 0)
182 0 : goto free_entry;
183 :
184 : /* SCRIPT_FILENAME */
185 : char temp[PATH_MAX];
186 0 : r = snprintf(temp, sizeof(temp), "%s/%.*s", pd->script_path, (int)url->len,
187 0 : url->value);
188 0 : if (r < 0 || r >= (int)sizeof(temp))
189 0 : goto free_script_name;
190 :
191 0 : entry->script_filename =
192 0 : realpathat(pd->script_path_fd, pd->script_path, temp, NULL);
193 0 : if (!entry->script_filename)
194 0 : goto free_script_name;
195 :
196 0 : if (strncmp(entry->script_filename, pd->script_path, strlen(pd->script_path)))
197 0 : goto free_script_filename;
198 :
199 0 : return &entry->base;
200 :
201 0 : free_script_filename:
202 0 : free(entry->script_filename);
203 0 : free_script_name:
204 0 : free(entry->script_name);
205 0 : free_entry:
206 0 : free(entry);
207 :
208 0 : return NULL;
209 : }
210 :
211 0 : static void destroy_script_name(struct cache_entry *entry, void *context)
212 : {
213 0 : struct script_name_cache_entry *snce =
214 : (struct script_name_cache_entry *)entry;
215 :
216 0 : free(snce->script_name);
217 0 : free(snce->script_filename);
218 0 : free(snce);
219 0 : }
220 :
221 0 : static enum lwan_http_status add_script_paths(const struct private_data *pd,
222 : struct lwan_request *request,
223 : struct lwan_response *response)
224 : {
225 : struct script_name_cache_entry *snce =
226 0 : (struct script_name_cache_entry *)cache_coro_get_and_ref_entry(
227 0 : pd->script_name_cache, request->conn->coro, &request->url);
228 :
229 0 : if (snce) {
230 0 : add_param(response->buffer, "SCRIPT_NAME", snce->script_name);
231 0 : add_param(response->buffer, "SCRIPT_FILENAME", snce->script_filename);
232 0 : return HTTP_OK;
233 : }
234 :
235 0 : return HTTP_NOT_FOUND;
236 : }
237 :
238 0 : static void add_header_to_strbuf(const char *header,
239 : size_t header_len,
240 : const char *value,
241 : size_t value_len,
242 : void *user_data)
243 : {
244 0 : struct lwan_strbuf *strbuf = user_data;
245 0 : return add_param_len(strbuf, header, header_len, value, value_len);
246 : }
247 :
248 0 : static enum lwan_http_status add_params(const struct private_data *pd,
249 : struct lwan_request *request,
250 : struct lwan_response *response)
251 : {
252 0 : const struct lwan_request_parser_helper *request_helper = request->helper;
253 0 : struct lwan_strbuf *strbuf = response->buffer;
254 :
255 : char remote_addr[INET6_ADDRSTRLEN];
256 : uint16_t remote_port;
257 :
258 : /* FIXME: let's use some hardcoded values for now so that we can
259 : * verify that the implementation is working first */
260 :
261 : /* Very compliant. Much CGI. Wow. */
262 0 : add_param(strbuf, "GATEWAY_INTERFACE", "CGI/1.1");
263 :
264 0 : add_param(strbuf, "REMOTE_ADDR",
265 : lwan_request_get_remote_address_and_port(request, remote_addr,
266 : &remote_port));
267 0 : add_int_param(strbuf, "REMOTE_PORT", remote_port);
268 :
269 0 : add_param(strbuf, "SERVER_ADDR", "127.0.0.1");
270 :
271 : /* FIXME: get the actual port from thread->listen_fd or
272 : * thread->tls_listen_fd */
273 0 : if (request->conn->flags & CONN_TLS) {
274 0 : add_param(strbuf, "SERVER_PORT", "0");
275 0 : add_param(strbuf, "HTTPS", "on");
276 : } else {
277 0 : add_param(strbuf, "SERVER_PORT", "0");
278 0 : add_param(strbuf, "HTTPS", "");
279 : }
280 :
281 0 : add_param(strbuf, "SERVER_SOFTWARE", "Lwan");
282 0 : add_param(strbuf, "SERVER_PROTOCOL",
283 0 : request->flags & REQUEST_IS_HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1");
284 :
285 0 : add_param(strbuf, "REQUEST_METHOD", lwan_request_get_method_str(request));
286 :
287 : /* FIXME: Should we support PATH_INFO? This is pretty shady. See
288 : * e.g. https://httpd.apache.org/docs/2.4/mod/core.html#acceptpathinfo */
289 0 : add_param(strbuf, "PATH_INFO", "");
290 :
291 0 : add_param(strbuf, "DOCUMENT_URI", request->original_url.value);
292 0 : add_param(strbuf, "DOCUMENT_ROOT", pd->script_path);
293 :
294 0 : const char *query_string = request_helper->query_string.value;
295 0 : if (query_string) {
296 : char *temp;
297 :
298 : /* FIXME: maybe we should add something that lets us
299 : * forward a va_arg to strbuf_append_printf() instead? */
300 0 : if (asprintf(&temp, "%s?%s", request->original_url.value,
301 : query_string) < 0) {
302 0 : return HTTP_INTERNAL_ERROR;
303 : }
304 :
305 0 : add_param(strbuf, "QUERY_STRING", query_string ? query_string : "");
306 0 : add_param(strbuf, "REQUEST_URI", temp);
307 :
308 0 : free(temp);
309 : } else {
310 0 : add_param(strbuf, "QUERY_STRING", "");
311 0 : add_param(strbuf, "REQUEST_URI", request->original_url.value);
312 : }
313 :
314 0 : lwan_request_foreach_header_for_cgi(request, add_header_to_strbuf, strbuf);
315 :
316 0 : return HTTP_OK;
317 : }
318 :
319 : static bool
320 0 : handle_stdout(struct lwan_request *request, const struct record *record, int fd)
321 : {
322 0 : size_t to_read = record->len_content;
323 0 : char *buffer = lwan_strbuf_extend_unsafe(request->response.buffer, to_read);
324 :
325 0 : if (!buffer)
326 0 : return false;
327 :
328 0 : while (to_read) {
329 0 : ssize_t r = lwan_request_async_read(request, fd, buffer, to_read);
330 :
331 0 : if (r < 0)
332 0 : return false;
333 :
334 0 : to_read -= (size_t)r;
335 0 : buffer += r;
336 : }
337 :
338 0 : if (record->len_padding) {
339 : char padding[256];
340 0 : lwan_request_async_read_flags(request, fd, padding,
341 0 : (size_t)record->len_padding, MSG_TRUNC);
342 : }
343 :
344 0 : return true;
345 : }
346 :
347 : static bool
348 0 : handle_stderr(struct lwan_request *request, const struct record *record, int fd)
349 : {
350 0 : size_t to_read = record->len_content;
351 0 : char *buffer = malloc(to_read);
352 :
353 0 : if (!buffer)
354 0 : return false;
355 :
356 0 : coro_deferred buffer_free_defer = coro_defer(request->conn->coro, free, buffer);
357 :
358 0 : for (char *p = buffer; to_read;) {
359 0 : ssize_t r = lwan_request_async_read(request, fd, p, to_read);
360 :
361 0 : if (r < 0)
362 0 : return false;
363 :
364 0 : p += r;
365 0 : to_read -= (size_t)r;
366 : }
367 :
368 0 : lwan_status_error("FastCGI stderr output: %.*s",
369 : (int)record->len_content, buffer);
370 :
371 0 : coro_defer_fire_and_disarm(request->conn->coro, buffer_free_defer);
372 :
373 0 : if (record->len_padding) {
374 : char padding[256];
375 0 : lwan_request_async_read_flags(request, fd, padding,
376 0 : (size_t)record->len_padding,
377 : MSG_TRUNC);
378 : }
379 :
380 0 : return true;
381 : }
382 :
383 0 : static bool discard_unknown_record(struct lwan_request *request,
384 : const struct record *record,
385 : int fd)
386 : {
387 : char buffer[256];
388 0 : size_t to_read = record->len_content + record->len_padding;
389 :
390 0 : if (record->type > 11) {
391 : /* Per the spec, 11 is the maximum (unknown type), so anything
392 : * above it is unspecified. */
393 0 : lwan_status_warning(
394 : "FastCGI server sent unknown/invalid record type %d", record->type);
395 0 : return false;
396 : }
397 :
398 0 : lwan_status_debug("Discarding record of type %d (%zu bytes incl. padding)",
399 : record->type, to_read);
400 :
401 0 : while (to_read) {
402 : ssize_t r;
403 :
404 0 : r = lwan_request_async_read_flags(
405 0 : request, fd, buffer, LWAN_MIN(sizeof(buffer), to_read), MSG_TRUNC);
406 0 : if (r < 0)
407 0 : return false;
408 :
409 0 : to_read -= (size_t)r;
410 : }
411 :
412 0 : return true;
413 : }
414 :
415 0 : DEFINE_ARRAY_TYPE_INLINEFIRST(header_array, struct lwan_key_value)
416 :
417 0 : static void reset_additional_header(void *data)
418 : {
419 0 : struct header_array *array = data;
420 0 : header_array_reset(array);
421 0 : }
422 :
423 : static enum lwan_http_status
424 0 : try_initiating_chunked_response(struct lwan_request *request)
425 : {
426 0 : struct lwan_response *response = &request->response;
427 : char *header_start[N_HEADER_START];
428 : char *next_request;
429 0 : enum lwan_http_status status_code = HTTP_OK;
430 0 : struct lwan_value buffer = {
431 0 : .value = lwan_strbuf_get_buffer(response->buffer),
432 0 : .len = lwan_strbuf_get_length(response->buffer),
433 : };
434 :
435 0 : assert(!(request->flags &
436 : (RESPONSE_CHUNKED_ENCODING | RESPONSE_SENT_HEADERS)));
437 :
438 0 : if (!memmem(buffer.value, buffer.len, "\r\n\r\n", 4))
439 0 : return HTTP_OK;
440 :
441 0 : ssize_t n_headers = lwan_find_headers(header_start, &buffer, &next_request);
442 0 : if (n_headers < 0)
443 0 : return HTTP_BAD_REQUEST;
444 :
445 : struct header_array additional_headers;
446 :
447 0 : header_array_init(&additional_headers);
448 0 : coro_deferred additional_headers_reset = coro_defer(request->conn->coro,
449 : reset_additional_header, &additional_headers);
450 :
451 0 : for (ssize_t i = 0; i < n_headers; i++) {
452 0 : char *begin = header_start[i];
453 0 : char *end = header_start[i + 1];
454 : char *p;
455 :
456 0 : p = strchr(begin, ':');
457 0 : if (!p) /* Shouldn't happen, but... */
458 0 : continue;
459 0 : *p = '\0';
460 :
461 0 : *(end - 2) = '\0';
462 :
463 0 : char *key = begin;
464 0 : char *value = p + 2;
465 :
466 0 : if (strcaseequal_neutral(key, "X-Powered-By")) {
467 : /* This is set by PHP-FPM. Do not advertise this for privacy
468 : * reasons. */
469 0 : } else if (strcaseequal_neutral(key, "Content-Type")) {
470 0 : response->mime_type = coro_strdup(request->conn->coro, value);
471 0 : } else if (strcaseequal_neutral(key, "Status")) {
472 0 : if (strlen(value) < 3) {
473 0 : status_code = HTTP_INTERNAL_ERROR;
474 0 : continue;
475 : }
476 :
477 0 : value[3] = '\0';
478 0 : int status_as_int = parse_int(value, -1);
479 :
480 0 : if (status_as_int < 100 || status_as_int >= 600)
481 0 : status_code = HTTP_INTERNAL_ERROR;
482 : else
483 0 : status_code = (enum lwan_http_status)status_as_int;
484 : } else {
485 0 : struct lwan_key_value *header = header_array_append(&additional_headers);
486 :
487 0 : if (!header)
488 0 : goto free_array_and_disarm;
489 :
490 0 : *header = (struct lwan_key_value){.key = key, .value = value};
491 : }
492 : }
493 :
494 0 : struct lwan_key_value *header = header_array_append(&additional_headers);
495 0 : if (!header)
496 0 : goto free_array_and_disarm;
497 0 : *header = (struct lwan_key_value){};
498 :
499 0 : if (!lwan_response_set_chunked_full(request, status_code,
500 0 : header_array_get_array(&additional_headers))) {
501 0 : goto free_array_and_disarm;
502 : }
503 :
504 0 : coro_defer_fire_and_disarm(request->conn->coro, additional_headers_reset);
505 :
506 0 : char *chunk_start = header_start[n_headers];
507 0 : size_t chunk_len = buffer.len - (size_t)(chunk_start - buffer.value);
508 :
509 0 : if (chunk_len) {
510 : struct lwan_strbuf first_chunk;
511 :
512 0 : lwan_strbuf_init(&first_chunk);
513 0 : lwan_strbuf_set_static(&first_chunk, chunk_start, chunk_len);
514 0 : lwan_response_send_chunk_full(request, &first_chunk);
515 :
516 : /* We sent the chunk using a temporary strbuf, so reset the
517 : * actual buffer that had the headers+first chunk */
518 0 : lwan_strbuf_reset(response->buffer);
519 : }
520 :
521 0 : return HTTP_OK;
522 :
523 0 : free_array_and_disarm:
524 0 : coro_defer_fire_and_disarm(request->conn->coro, additional_headers_reset);
525 0 : return HTTP_INTERNAL_ERROR;
526 : }
527 :
528 0 : DEFINE_ARRAY_TYPE_INLINEFIRST(iovec_array, struct iovec)
529 0 : DEFINE_ARRAY_TYPE_INLINEFIRST(record_array, struct record)
530 :
531 0 : static bool build_stdin_records(struct lwan_request *request,
532 : struct iovec_array *iovec_array,
533 : struct record_array *record_array,
534 : int fcgi_fd,
535 : const struct lwan_value *body_data)
536 : {
537 0 : if (!body_data)
538 0 : return true;
539 :
540 0 : size_t to_send = body_data->len;
541 0 : char *buffer = body_data->value;
542 :
543 0 : while (to_send) {
544 : struct record *record;
545 : struct iovec *iovec;
546 0 : size_t block_size = LWAN_MIN(0xffffull, to_send);
547 :
548 0 : record = record_array_append(record_array);
549 0 : if (UNLIKELY(!record))
550 0 : return false;
551 0 : *record = (struct record){
552 : .version = 1,
553 : .type = FASTCGI_TYPE_STDIN,
554 0 : .id = htons(1),
555 0 : .len_content = htons((uint16_t)block_size),
556 : };
557 :
558 0 : iovec = iovec_array_append(iovec_array);
559 0 : if (UNLIKELY(!iovec))
560 0 : return false;
561 0 : *iovec = (struct iovec){.iov_base = record, .iov_len = sizeof(*record)};
562 :
563 0 : iovec = iovec_array_append(iovec_array);
564 0 : if (UNLIKELY(!iovec))
565 0 : return false;
566 0 : *iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size};
567 :
568 0 : if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) {
569 0 : lwan_request_async_writev(request, fcgi_fd,
570 : iovec_array_get_array(iovec_array),
571 0 : (int)iovec_array_len(iovec_array));
572 0 : iovec_array_reset(iovec_array);
573 0 : record_array_reset(record_array);
574 : }
575 :
576 0 : to_send -= block_size;
577 0 : buffer += block_size;
578 : }
579 :
580 0 : return true;
581 : }
582 :
583 0 : static enum lwan_http_status send_request(struct private_data *pd,
584 : struct lwan_request *request,
585 : struct lwan_response *response,
586 : int fcgi_fd)
587 : {
588 : enum lwan_http_status status;
589 :
590 0 : status = add_params(pd, request, response);
591 0 : if (status != HTTP_OK)
592 0 : return status;
593 :
594 0 : status = add_script_paths(pd, request, response);
595 0 : if (status != HTTP_OK)
596 0 : return status;
597 :
598 0 : if (UNLIKELY(lwan_strbuf_get_length(response->buffer) > 0xffffu)) {
599 : /* Should not happen because DEFAULT_BUFFER_SIZE is a lot smaller
600 : * than 65535, but check anyway. (If anything, we could send multiple
601 : * PARAMS records, but that's very unlikely to happen anyway until
602 : * we change how request headers are read.) */
603 : static_assert(DEFAULT_BUFFER_SIZE <= 0xffffu,
604 : "only needs one PARAMS record");
605 0 : return HTTP_TOO_LARGE;
606 : }
607 :
608 : struct iovec_array iovec_array;
609 : struct record_array record_array;
610 : struct iovec *iovec;
611 :
612 : /* These arrays should never go beyond the inlinefirst threshold, so they
613 : * shouldn't leak -- thus requiring no defer to reset them. */
614 0 : record_array_init(&record_array);
615 0 : iovec_array_init(&iovec_array);
616 :
617 0 : iovec = iovec_array_append(&iovec_array);
618 0 : if (UNLIKELY(!iovec))
619 0 : return HTTP_INTERNAL_ERROR;
620 0 : *iovec = (struct iovec){
621 : .iov_base =
622 0 : &(struct request_header){
623 : .begin_request = {.version = 1,
624 : .type = FASTCGI_TYPE_BEGIN_REQUEST,
625 0 : .id = htons(1),
626 0 : .len_content = htons(
627 : (uint16_t)sizeof(struct begin_request_body))},
628 0 : .begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)},
629 : .begin_params = {.version = 1,
630 : .type = FASTCGI_TYPE_PARAMS,
631 0 : .id = htons(1),
632 : .len_content =
633 0 : htons((uint16_t)lwan_strbuf_get_length(
634 0 : response->buffer))}},
635 : .iov_len = sizeof(struct request_header),
636 : };
637 :
638 0 : iovec = iovec_array_append(&iovec_array);
639 0 : if (UNLIKELY(!iovec))
640 0 : return HTTP_INTERNAL_ERROR;
641 0 : *iovec =
642 0 : (struct iovec){.iov_base = lwan_strbuf_get_buffer(response->buffer),
643 0 : .iov_len = lwan_strbuf_get_length(response->buffer)};
644 :
645 0 : iovec = iovec_array_append(&iovec_array);
646 0 : if (UNLIKELY(!iovec))
647 0 : return HTTP_INTERNAL_ERROR;
648 0 : *iovec = (struct iovec){
649 0 : .iov_base = &(struct record){.version = 1,
650 : .type = FASTCGI_TYPE_PARAMS,
651 0 : .id = htons(1)},
652 : .iov_len = sizeof(struct record),
653 : };
654 :
655 0 : if (!build_stdin_records(request, &iovec_array, &record_array, fcgi_fd,
656 : lwan_request_get_request_body(request))) {
657 0 : return HTTP_INTERNAL_ERROR;
658 : }
659 :
660 0 : iovec = iovec_array_append(&iovec_array);
661 0 : if (UNLIKELY(!iovec))
662 0 : return HTTP_INTERNAL_ERROR;
663 0 : *iovec = (struct iovec){
664 0 : .iov_base = &(struct record){.version = 1,
665 : .type = FASTCGI_TYPE_STDIN,
666 0 : .id = htons(1)},
667 : .iov_len = sizeof(struct record),
668 : };
669 :
670 0 : lwan_request_async_writev(request, fcgi_fd,
671 : iovec_array_get_array(&iovec_array),
672 0 : (int)iovec_array_len(&iovec_array));
673 0 : iovec_array_reset(&iovec_array);
674 0 : record_array_reset(&record_array);
675 :
676 0 : lwan_strbuf_reset(response->buffer);
677 :
678 0 : return HTTP_OK;
679 : }
680 :
681 0 : static bool try_connect(struct lwan_request *request,
682 : int sock_fd,
683 : struct sockaddr *sockaddr,
684 : socklen_t socklen)
685 : {
686 0 : if (LIKELY(!connect(sock_fd, sockaddr, socklen)))
687 0 : return true;
688 :
689 : /* Since socket has been created in non-blocking mode, connection
690 : * might not be completed immediately. Depending on the socket type,
691 : * connect() might return EAGAIN or EINPROGRESS. */
692 0 : if (errno != EAGAIN && errno != EINPROGRESS)
693 0 : return false;
694 :
695 : /* If we get any of the above errors, try checking for socket error
696 : * codes and loop until we get no errors. We await for writing here
697 : * because that's what the Linux man page for connect(2) says we should
698 : * do in this case. */
699 0 : for (int try = 0; try < 10; try++) {
700 0 : socklen_t sockerrnolen = (socklen_t)sizeof(int);
701 : int sockerrno;
702 :
703 0 : lwan_request_await_write(request, sock_fd);
704 :
705 0 : if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &sockerrno,
706 : &sockerrnolen) < 0) {
707 0 : break;
708 : }
709 :
710 0 : switch (sockerrno) {
711 0 : case EISCONN:
712 : case 0:
713 0 : return true;
714 :
715 0 : case EAGAIN:
716 : case EINPROGRESS:
717 : case EINTR:
718 0 : continue;
719 :
720 0 : default:
721 0 : return false;
722 : }
723 : }
724 :
725 0 : return false;
726 : }
727 :
728 : static enum lwan_http_status
729 0 : fastcgi_handle_request(struct lwan_request *request,
730 : struct lwan_response *response,
731 : void *instance)
732 : {
733 0 : struct private_data *pd = instance;
734 : enum lwan_http_status status;
735 0 : int remaining_tries_for_chunked = 10;
736 : int fcgi_fd;
737 :
738 : fcgi_fd =
739 0 : socket(pd->addr_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
740 0 : if (fcgi_fd < 0)
741 0 : return HTTP_INTERNAL_ERROR;
742 :
743 0 : coro_defer(request->conn->coro, close_fd, (void *)(intptr_t)fcgi_fd);
744 :
745 0 : if (!try_connect(request, fcgi_fd, (struct sockaddr *)&pd->sock_addr,
746 : pd->addr_size)) {
747 0 : return HTTP_UNAVAILABLE;
748 : }
749 :
750 0 : status = send_request(pd, request, response, fcgi_fd);
751 0 : if (status != HTTP_OK)
752 0 : return status;
753 :
754 : /* FIXME: the header parser starts at the \r from an usual
755 : * HTTP request with the verb line, etc. */
756 0 : lwan_strbuf_append_char(response->buffer, '\r');
757 :
758 0 : request->flags |= RESPONSE_NO_EXPIRES;
759 :
760 0 : while (true) {
761 : struct record record;
762 : ssize_t r;
763 :
764 0 : r = lwan_request_async_read(request, fcgi_fd, &record, sizeof(record));
765 0 : if (r < 0)
766 0 : return HTTP_UNAVAILABLE;
767 0 : if (r != (ssize_t)sizeof(record))
768 0 : return HTTP_INTERNAL_ERROR;
769 :
770 0 : record.len_content = ntohs(record.len_content);
771 0 : record.id = htons(record.id);
772 :
773 0 : switch (record.type) {
774 0 : case FASTCGI_TYPE_STDOUT:
775 0 : if (!handle_stdout(request, &record, fcgi_fd))
776 0 : return HTTP_INTERNAL_ERROR;
777 :
778 : /* Fallthrough */
779 :
780 : case FASTCGI_TYPE_END_REQUEST:
781 0 : if (request->flags & RESPONSE_CHUNKED_ENCODING) {
782 0 : if (lwan_strbuf_get_length(response->buffer) != 0) {
783 : /* Avoid buffering all the records from the FastCGI
784 : * server and send chunks as we get them if we know we're
785 : * already responding with chunked encoding. */
786 0 : lwan_response_send_chunk(request);
787 : }
788 : } else {
789 : /* See if we can parse the headers at this point; if we can,
790 : * then we can also send the first chunk with the additional
791 : * headers we just parsed from FastCGI. */
792 0 : remaining_tries_for_chunked--;
793 0 : if (!remaining_tries_for_chunked)
794 0 : return HTTP_UNAVAILABLE;
795 :
796 0 : status = try_initiating_chunked_response(request);
797 0 : if (status != HTTP_OK)
798 0 : return status;
799 : }
800 :
801 0 : if (record.type == FASTCGI_TYPE_END_REQUEST)
802 0 : return HTTP_OK;
803 :
804 0 : break;
805 :
806 0 : case FASTCGI_TYPE_STDERR:
807 0 : if (!handle_stderr(request, &record, fcgi_fd))
808 0 : return HTTP_INTERNAL_ERROR;
809 0 : break;
810 :
811 0 : default:
812 0 : if (!discard_unknown_record(request, &record, fcgi_fd))
813 0 : return HTTP_INTERNAL_ERROR;
814 0 : break;
815 : }
816 : }
817 :
818 : __builtin_unreachable();
819 : }
820 :
821 0 : static void *fastcgi_create(const char *prefix __attribute__((unused)),
822 : void *user_settings)
823 : {
824 0 : struct lwan_fastcgi_settings *settings = user_settings;
825 : struct private_data *pd;
826 :
827 0 : if (!settings->address) {
828 0 : lwan_status_error("FastCGI: `address` not specified");
829 0 : return NULL;
830 : }
831 0 : if (!settings->script_path) {
832 0 : lwan_status_error("FastCGI: `script_path` not specified");
833 0 : return NULL;
834 : }
835 :
836 0 : if (!settings->default_index)
837 0 : settings->default_index = "index.php";
838 :
839 0 : pd = malloc(sizeof(*pd));
840 0 : if (!pd) {
841 0 : lwan_status_perror("FastCGI: Could not allocate memory for module");
842 0 : return NULL;
843 : }
844 :
845 0 : pd->script_name_cache =
846 0 : cache_create(create_script_name, destroy_script_name, pd, 60);
847 0 : if (!pd->script_name_cache) {
848 0 : lwan_status_error("FastCGI: could not create cache for script_name");
849 0 : goto free_pd;
850 : }
851 :
852 0 : pd->default_index = (struct lwan_value){
853 0 : .value = strdup(settings->default_index),
854 0 : .len = strlen(settings->default_index),
855 : };
856 0 : if (!pd->default_index.value) {
857 0 : lwan_status_error("FastCGI: could not copy default_address for module");
858 0 : goto destroy_cache;
859 : }
860 :
861 0 : pd->script_path = realpath(settings->script_path, NULL);
862 0 : if (!pd->script_path) {
863 0 : lwan_status_perror("FastCGI: `script_path` of '%s' is invalid",
864 : settings->script_path);
865 0 : goto free_default_index;
866 : }
867 :
868 0 : pd->script_path_fd = open(pd->script_path, O_PATH | O_DIRECTORY | O_CLOEXEC);
869 0 : if (pd->script_path_fd < 0) {
870 0 : lwan_status_perror("FastCGI: Could not open `script_path` at '%s'",
871 : pd->script_path);
872 0 : goto free_script_path;
873 : }
874 :
875 0 : if (*settings->address == '/') {
876 : struct stat st;
877 :
878 0 : if (stat(settings->address, &st) < 0) {
879 0 : lwan_status_perror("FastCGI: `address` not found: %s",
880 : settings->address);
881 0 : goto close_script_path_fd;
882 : }
883 :
884 0 : if (!(st.st_mode & S_IFSOCK)) {
885 0 : lwan_status_error("FastCGI: `address` is not a socket: %s",
886 : settings->address);
887 0 : goto close_script_path_fd;
888 : }
889 :
890 0 : if (strlen(settings->address) >= sizeof(pd->un_addr.sun_path)) {
891 0 : lwan_status_error(
892 : "FastCGI: `address` is too long for a sockaddr_un: %s",
893 : settings->address);
894 0 : goto close_script_path_fd;
895 : }
896 :
897 0 : pd->addr_family = AF_UNIX;
898 0 : pd->un_addr = (struct sockaddr_un){.sun_family = AF_UNIX};
899 0 : pd->addr_size = sizeof(pd->un_addr);
900 0 : memcpy(pd->un_addr.sun_path, settings->address,
901 0 : strlen(settings->address) + 1);
902 0 : return pd;
903 : }
904 :
905 : char *node, *port, *address_copy;
906 :
907 0 : address_copy = strdup(settings->address);
908 0 : if (!address_copy)
909 0 : goto free_address_copy;
910 :
911 0 : pd->addr_family = lwan_socket_parse_address(address_copy, &node, &port);
912 :
913 0 : int int_port = parse_int(port, -1);
914 0 : if (int_port < 0 || int_port > 0xffff) {
915 0 : lwan_status_error("FastCGI: Port %d is not in valid range [0-65535]",
916 : int_port);
917 0 : goto free_address_copy;
918 : }
919 :
920 0 : switch (pd->addr_family) {
921 0 : case AF_MAX:
922 0 : lwan_status_error("FastCGI: Could not parse '%s' as 'address:port'",
923 : settings->address);
924 0 : goto free_address_copy;
925 :
926 0 : case AF_INET: {
927 : struct in_addr in_addr;
928 :
929 0 : if (inet_pton(AF_INET, node, &in_addr) < 0) {
930 0 : lwan_status_perror("FastCGI: Could not parse IPv4 address '%s'",
931 : node);
932 0 : goto free_address_copy;
933 : }
934 :
935 0 : pd->in_addr =
936 : (struct sockaddr_in){.sin_family = AF_INET,
937 : .sin_addr = in_addr,
938 0 : .sin_port = htons((uint16_t)int_port)};
939 0 : pd->addr_size = sizeof(in_addr);
940 0 : free(address_copy);
941 0 : return pd;
942 : }
943 :
944 0 : case AF_INET6: {
945 : struct in6_addr in6_addr;
946 :
947 0 : if (inet_pton(AF_INET6, node, &in6_addr) < 0) {
948 0 : lwan_status_perror("FastCGI: Could not parse IPv6 address '%s'",
949 : node);
950 0 : goto free_address_copy;
951 : }
952 :
953 0 : pd->in6_addr =
954 : (struct sockaddr_in6){.sin6_family = AF_INET6,
955 : .sin6_addr = in6_addr,
956 0 : .sin6_port = htons((uint16_t)int_port)};
957 0 : pd->addr_size = sizeof(in6_addr);
958 0 : free(address_copy);
959 0 : return pd;
960 : }
961 : }
962 :
963 0 : lwan_status_error("FastCGI: Address '%s' isn't a valid Unix Domain Socket, IPv4, or IPv6 address",
964 : settings->address);
965 :
966 0 : free_address_copy:
967 0 : free(address_copy);
968 0 : close_script_path_fd:
969 0 : close(pd->script_path_fd);
970 0 : free_script_path:
971 0 : free(pd->script_path);
972 0 : free_default_index:
973 0 : free(pd->default_index.value);
974 0 : destroy_cache:
975 0 : cache_destroy(pd->script_name_cache);
976 0 : free_pd:
977 0 : free(pd);
978 0 : return NULL;
979 : }
980 :
981 0 : static void fastcgi_destroy(void *instance)
982 : {
983 0 : struct private_data *pd = instance;
984 :
985 0 : cache_destroy(pd->script_name_cache);
986 0 : free(pd->default_index.value);
987 0 : close(pd->script_path_fd);
988 0 : free(pd->script_path);
989 0 : free(pd);
990 0 : }
991 :
992 0 : static void *fastcgi_create_from_hash(const char *prefix,
993 : const struct hash *hash)
994 : {
995 0 : struct lwan_fastcgi_settings settings = {
996 0 : .address = hash_find(hash, "address"),
997 0 : .script_path = hash_find(hash, "script_path"),
998 0 : .default_index = hash_find(hash, "default_index"),
999 : };
1000 0 : return fastcgi_create(prefix, &settings);
1001 : }
1002 :
1003 : static const struct lwan_module module = {
1004 : .create = fastcgi_create,
1005 : .create_from_hash = fastcgi_create_from_hash,
1006 : .destroy = fastcgi_destroy,
1007 : .handle_request = fastcgi_handle_request,
1008 : };
1009 :
1010 : LWAN_REGISTER_MODULE(fastcgi, &module);
|