LCOV - code coverage report
Current view: top level - lib - lwan-mod-fastcgi.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 459 0.0 %
Date: 2023-04-18 16:19:03 Functions: 0 33 0.0 %

          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);

Generated by: LCOV version 1.15-2-gb9d6727