1*76ed9045Smillert /* $OpenBSD: server_fcgi.c,v 1.97 2023/11/08 19:19:10 millert Exp $ */
2ab07c989Sflorian
3ab07c989Sflorian /*
4ab07c989Sflorian * Copyright (c) 2014 Florian Obser <florian@openbsd.org>
5ab07c989Sflorian *
6ab07c989Sflorian * Permission to use, copy, modify, and distribute this software for any
7ab07c989Sflorian * purpose with or without fee is hereby granted, provided that the above
8ab07c989Sflorian * copyright notice and this permission notice appear in all copies.
9ab07c989Sflorian *
10ab07c989Sflorian * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11ab07c989Sflorian * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12ab07c989Sflorian * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13ab07c989Sflorian * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14ab07c989Sflorian * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15ab07c989Sflorian * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16ab07c989Sflorian * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17ab07c989Sflorian */
18ab07c989Sflorian
19ab07c989Sflorian #include <sys/types.h>
20ab07c989Sflorian #include <sys/time.h>
21ab07c989Sflorian #include <sys/socket.h>
22ab07c989Sflorian #include <sys/un.h>
23ab07c989Sflorian
24ab07c989Sflorian #include <netinet/in.h>
2586f952e4Sreyk #include <arpa/inet.h>
26ab07c989Sflorian
2786f952e4Sreyk #include <limits.h>
28ab07c989Sflorian #include <errno.h>
29ab07c989Sflorian #include <stdlib.h>
30ab07c989Sflorian #include <string.h>
31ab07c989Sflorian #include <stdio.h>
3286f952e4Sreyk #include <time.h>
334aa750c1Sreyk #include <ctype.h>
34ab07c989Sflorian #include <event.h>
357a0b27b8Sjung #include <unistd.h>
36ab07c989Sflorian
37ab07c989Sflorian #include "httpd.h"
38ab07c989Sflorian #include "http.h"
39ab07c989Sflorian
40ab07c989Sflorian #define FCGI_PADDING_SIZE 255
41ab07c989Sflorian #define FCGI_RECORD_SIZE \
42ab07c989Sflorian (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
43ab07c989Sflorian
44ab07c989Sflorian #define FCGI_BEGIN_REQUEST 1
45ab07c989Sflorian #define FCGI_ABORT_REQUEST 2
46ab07c989Sflorian #define FCGI_END_REQUEST 3
47ab07c989Sflorian #define FCGI_PARAMS 4
48ab07c989Sflorian #define FCGI_STDIN 5
49ab07c989Sflorian #define FCGI_STDOUT 6
50ab07c989Sflorian #define FCGI_STDERR 7
51ab07c989Sflorian #define FCGI_DATA 8
52ab07c989Sflorian #define FCGI_GET_VALUES 9
53ab07c989Sflorian #define FCGI_GET_VALUES_RESULT 10
54ab07c989Sflorian #define FCGI_UNKNOWN_TYPE 11
55ab07c989Sflorian #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
56ab07c989Sflorian
57ab07c989Sflorian #define FCGI_RESPONDER 1
58ab07c989Sflorian
59ab07c989Sflorian struct fcgi_record_header {
60ab07c989Sflorian uint8_t version;
61ab07c989Sflorian uint8_t type;
62ab07c989Sflorian uint16_t id;
63ab07c989Sflorian uint16_t content_len;
64ab07c989Sflorian uint8_t padding_len;
65ab07c989Sflorian uint8_t reserved;
66ab07c989Sflorian } __packed;
67ab07c989Sflorian
68ab07c989Sflorian struct fcgi_begin_request_body {
69ab07c989Sflorian uint16_t role;
70ab07c989Sflorian uint8_t flags;
71ab07c989Sflorian uint8_t reserved[5];
72ab07c989Sflorian } __packed;
73ab07c989Sflorian
744aa750c1Sreyk struct server_fcgi_param {
754aa750c1Sreyk int total_len;
764aa750c1Sreyk uint8_t buf[FCGI_RECORD_SIZE];
774aa750c1Sreyk };
784aa750c1Sreyk
794703e0faSreyk int server_fcgi_header(struct client *, unsigned int);
80193f358aSop void server_fcgi_error(struct bufferevent *, short, void *);
81ab07c989Sflorian void server_fcgi_read(struct bufferevent *, void *);
824aa750c1Sreyk int server_fcgi_writeheader(struct client *, struct kv *, void *);
832e7af781Sreyk int server_fcgi_writechunk(struct client *);
8457c60317Sreyk int server_fcgi_getheaders(struct client *);
854aa750c1Sreyk int fcgi_add_param(struct server_fcgi_param *, const char *, const char *,
86c94b2444Sflorian struct client *);
87ab07c989Sflorian
88ab07c989Sflorian int
server_fcgi(struct httpd * env,struct client * clt)89ab07c989Sflorian server_fcgi(struct httpd *env, struct client *clt)
90ab07c989Sflorian {
914aa750c1Sreyk struct server_fcgi_param param;
92ab07c989Sflorian struct server_config *srv_conf = clt->clt_srv_conf;
93d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
94ab07c989Sflorian struct fcgi_record_header *h;
95ab07c989Sflorian struct fcgi_begin_request_body *begin;
9603cb893cSpirofti struct fastcgi_param *fcgiparam;
97b9fc9a72Sderaadt char hbuf[HOST_NAME_MAX+1];
9820b0b871Schrisz size_t scriptlen;
9920b0b871Schrisz int pathlen;
1004aa750c1Sreyk int fd = -1, ret;
1019ea72f95Stracey const char *stripped, *alias, *errstr = NULL;
102a23b6848Stb char *query_alias, *str, *script = NULL;
103ab07c989Sflorian
1049ea72f95Stracey if ((fd = socket(srv_conf->fastcgi_ss.ss_family,
105838637bcSreyk SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1)
106ab07c989Sflorian goto fail;
1079ea72f95Stracey if ((connect(fd, (struct sockaddr *) &srv_conf->fastcgi_ss,
1089ea72f95Stracey srv_conf->fastcgi_ss.ss_len)) == -1) {
1099ea72f95Stracey if (errno != EINPROGRESS)
110ab07c989Sflorian goto fail;
11122049912Sreyk }
112ab07c989Sflorian
113876a82a2Sreyk memset(hbuf, 0, sizeof(hbuf));
1149917324bSflorian clt->clt_fcgi.state = FCGI_READ_HEADER;
1159917324bSflorian clt->clt_fcgi.toread = sizeof(struct fcgi_record_header);
1169917324bSflorian clt->clt_fcgi.status = 200;
1179917324bSflorian clt->clt_fcgi.headersdone = 0;
1185a7cb2b1Sflorian clt->clt_fcgi.headerssent = 0;
11919d2af9eSflorian
12019d2af9eSflorian if (clt->clt_srvevb != NULL)
12119d2af9eSflorian evbuffer_free(clt->clt_srvevb);
12219d2af9eSflorian
12319d2af9eSflorian clt->clt_srvevb = evbuffer_new();
12419d2af9eSflorian if (clt->clt_srvevb == NULL) {
12519d2af9eSflorian errstr = "failed to allocate evbuffer";
12619d2af9eSflorian goto fail;
12719d2af9eSflorian }
12819d2af9eSflorian
1297a0b27b8Sjung close(clt->clt_fd);
130fc896a69Sflorian clt->clt_fd = fd;
1317a0b27b8Sjung
132151a32cfSreyk if (clt->clt_srvbev != NULL)
133151a32cfSreyk bufferevent_free(clt->clt_srvbev);
134cebbf23cSreyk
135f3e6e694Sflorian clt->clt_srvbev_throttled = 0;
136151a32cfSreyk clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read,
137193f358aSop NULL, server_fcgi_error, clt);
138151a32cfSreyk if (clt->clt_srvbev == NULL) {
139ab07c989Sflorian errstr = "failed to allocate fcgi buffer event";
140ab07c989Sflorian goto fail;
141ab07c989Sflorian }
142ab07c989Sflorian
1434aa750c1Sreyk memset(¶m, 0, sizeof(param));
144ab07c989Sflorian
1454aa750c1Sreyk h = (struct fcgi_record_header *)¶m.buf;
146ab07c989Sflorian h->version = 1;
147ab07c989Sflorian h->type = FCGI_BEGIN_REQUEST;
148ab07c989Sflorian h->id = htons(1);
149ab07c989Sflorian h->content_len = htons(sizeof(struct fcgi_begin_request_body));
150ab07c989Sflorian h->padding_len = 0;
151ab07c989Sflorian
1524aa750c1Sreyk begin = (struct fcgi_begin_request_body *)¶m.buf[sizeof(struct
153ab07c989Sflorian fcgi_record_header)];
154ab07c989Sflorian begin->role = htons(FCGI_RESPONDER);
155ab07c989Sflorian
15678dd27f1Sblambert if (bufferevent_write(clt->clt_srvbev, ¶m.buf,
157ab07c989Sflorian sizeof(struct fcgi_record_header) +
15878dd27f1Sblambert sizeof(struct fcgi_begin_request_body)) == -1) {
15978dd27f1Sblambert errstr = "failed to write to evbuffer";
16078dd27f1Sblambert goto fail;
16178dd27f1Sblambert }
162ab07c989Sflorian
163ab07c989Sflorian h->type = FCGI_PARAMS;
1644aa750c1Sreyk h->content_len = param.total_len = 0;
165ab07c989Sflorian
1664624b10aSchrisz alias = desc->http_path_alias != NULL
1674624b10aSchrisz ? desc->http_path_alias
1684624b10aSchrisz : desc->http_path;
1694624b10aSchrisz
170a23b6848Stb query_alias = desc->http_query_alias != NULL
171a23b6848Stb ? desc->http_query_alias
172a23b6848Stb : desc->http_query;
173a23b6848Stb
1744624b10aSchrisz stripped = server_root_strip(alias, srv_conf->strip);
1754624b10aSchrisz if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped))
1764624b10aSchrisz == -1) {
177ef353f36Sreyk errstr = "failed to get script name";
178c94b2444Sflorian goto fail;
179cebbf23cSreyk }
180cebbf23cSreyk
18120b0b871Schrisz scriptlen = path_info(script);
18220b0b871Schrisz /*
18320b0b871Schrisz * no part of root should show up in PATH_INFO.
18420b0b871Schrisz * therefore scriptlen should be >= strlen(root)
18520b0b871Schrisz */
18620b0b871Schrisz if (scriptlen < strlen(srv_conf->root))
18720b0b871Schrisz scriptlen = strlen(srv_conf->root);
18820b0b871Schrisz if ((int)scriptlen < pathlen) {
189ef353f36Sreyk if (fcgi_add_param(¶m, "PATH_INFO",
190ef353f36Sreyk script + scriptlen, clt) == -1) {
191e46d51a0Sreyk errstr = "failed to encode param";
192e46d51a0Sreyk goto fail;
193e46d51a0Sreyk }
194ef353f36Sreyk script[scriptlen] = '\0';
195857e9b36Sreyk } else {
196857e9b36Sreyk /* RFC 3875 mandates that PATH_INFO is empty if not set */
197857e9b36Sreyk if (fcgi_add_param(¶m, "PATH_INFO", "", clt) == -1) {
198857e9b36Sreyk errstr = "failed to encode param";
199857e9b36Sreyk goto fail;
200857e9b36Sreyk }
201ef353f36Sreyk }
202ef353f36Sreyk
2034624b10aSchrisz /*
2044624b10aSchrisz * calculate length of http SCRIPT_NAME:
2054624b10aSchrisz * add length of stripped prefix,
2064624b10aSchrisz * subtract length of prepended local root
2074624b10aSchrisz */
2084624b10aSchrisz scriptlen += (stripped - alias) - strlen(srv_conf->root);
2094624b10aSchrisz if ((str = strndup(alias, scriptlen)) == NULL)
2104624b10aSchrisz goto fail;
2114624b10aSchrisz ret = fcgi_add_param(¶m, "SCRIPT_NAME", str, clt);
2124624b10aSchrisz free(str);
2134624b10aSchrisz if (ret == -1) {
214ef353f36Sreyk errstr = "failed to encode param";
215ef353f36Sreyk goto fail;
216ef353f36Sreyk }
2172cf74b7fSflorian if (fcgi_add_param(¶m, "SCRIPT_FILENAME", server_root_strip(script,
2182cf74b7fSflorian srv_conf->fcgistrip), clt) == -1) {
219ef353f36Sreyk errstr = "failed to encode param";
220ef353f36Sreyk goto fail;
221e46d51a0Sreyk }
222e46d51a0Sreyk
223a23b6848Stb if (query_alias) {
224a23b6848Stb if (fcgi_add_param(¶m, "QUERY_STRING", query_alias,
225c94b2444Sflorian clt) == -1) {
226c94b2444Sflorian errstr = "failed to encode param";
227c94b2444Sflorian goto fail;
228c94b2444Sflorian }
229b851590dSchrisz } else if (fcgi_add_param(¶m, "QUERY_STRING", "", clt) == -1) {
230b851590dSchrisz errstr = "failed to encode param";
231b851590dSchrisz goto fail;
232b851590dSchrisz }
233c94b2444Sflorian
2342cf74b7fSflorian if (fcgi_add_param(¶m, "DOCUMENT_ROOT", server_root_strip(
2352cf74b7fSflorian srv_conf->root, srv_conf->fcgistrip), clt) == -1) {
236ef353f36Sreyk errstr = "failed to encode param";
237ef353f36Sreyk goto fail;
238ef353f36Sreyk }
239a193fdd3Schrisz if (fcgi_add_param(¶m, "DOCUMENT_URI", alias,
2404aa750c1Sreyk clt) == -1) {
2414aa750c1Sreyk errstr = "failed to encode param";
2424aa750c1Sreyk goto fail;
2434aa750c1Sreyk }
2444aa750c1Sreyk if (fcgi_add_param(¶m, "GATEWAY_INTERFACE", "CGI/1.1",
2454aa750c1Sreyk clt) == -1) {
246c94b2444Sflorian errstr = "failed to encode param";
247c94b2444Sflorian goto fail;
248c94b2444Sflorian }
249c94b2444Sflorian
250602531d9Sreyk if (srv_conf->flags & SRVFLAG_AUTH) {
251e286121aSflorian if (fcgi_add_param(¶m, "REMOTE_USER",
252daa1b608Sflorian clt->clt_remote_user, clt) == -1) {
253e286121aSflorian errstr = "failed to encode param";
254e286121aSflorian goto fail;
255e286121aSflorian }
256e286121aSflorian }
257e286121aSflorian
2584aa750c1Sreyk /* Add HTTP_* headers */
259d08e4976Sreyk if (server_headers(clt, desc, server_fcgi_writeheader, ¶m) == -1) {
260c94b2444Sflorian errstr = "failed to encode param";
261c94b2444Sflorian goto fail;
262c94b2444Sflorian }
263c94b2444Sflorian
2641d0dc528Sjsing if (srv_conf->flags & SRVFLAG_TLS) {
2655e7cd613Sreyk if (fcgi_add_param(¶m, "HTTPS", "on", clt) == -1) {
2665e7cd613Sreyk errstr = "failed to encode param";
2675e7cd613Sreyk goto fail;
2685e7cd613Sreyk }
2691d0dc528Sjsing if (srv_conf->tls_flags != 0 && fcgi_add_param(¶m,
2701d0dc528Sjsing "TLS_PEER_VERIFY", printb_flags(srv_conf->tls_flags,
2711d0dc528Sjsing TLSFLAG_BITS), clt) == -1) {
2721d0dc528Sjsing errstr = "failed to encode param";
2731d0dc528Sjsing goto fail;
2741d0dc528Sjsing }
2751d0dc528Sjsing }
2765e7cd613Sreyk
27703cb893cSpirofti TAILQ_FOREACH(fcgiparam, &srv_conf->fcgiparams, entry) {
278e95f05c9Sreyk if (fcgi_add_param(¶m, fcgiparam->name, fcgiparam->value,
279e95f05c9Sreyk clt) == -1) {
28003cb893cSpirofti errstr = "failed to encode param";
28103cb893cSpirofti goto fail;
28203cb893cSpirofti }
28303cb893cSpirofti }
28403cb893cSpirofti
285c94b2444Sflorian (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf));
2864aa750c1Sreyk if (fcgi_add_param(¶m, "REMOTE_ADDR", hbuf, clt) == -1) {
287c94b2444Sflorian errstr = "failed to encode param";
288c94b2444Sflorian goto fail;
289c94b2444Sflorian }
290c94b2444Sflorian
291c94b2444Sflorian (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port));
2924aa750c1Sreyk if (fcgi_add_param(¶m, "REMOTE_PORT", hbuf, clt) == -1) {
293c94b2444Sflorian errstr = "failed to encode param";
294c94b2444Sflorian goto fail;
295c94b2444Sflorian }
296c94b2444Sflorian
2974aa750c1Sreyk if (fcgi_add_param(¶m, "REQUEST_METHOD",
2984aa750c1Sreyk server_httpmethod_byid(desc->http_method), clt) == -1) {
299c94b2444Sflorian errstr = "failed to encode param";
300c94b2444Sflorian goto fail;
301c94b2444Sflorian }
302c94b2444Sflorian
303c94b2444Sflorian if (!desc->http_query) {
30416a88448Syasuoka if (fcgi_add_param(¶m, "REQUEST_URI", desc->http_path_orig,
3054aa750c1Sreyk clt) == -1) {
306c94b2444Sflorian errstr = "failed to encode param";
307c94b2444Sflorian goto fail;
308c94b2444Sflorian }
3098fe5fc8cSblambert } else {
31016a88448Syasuoka if (asprintf(&str, "%s?%s", desc->http_path_orig,
3118fe5fc8cSblambert desc->http_query) == -1) {
3128fe5fc8cSblambert errstr = "failed to encode param";
3138fe5fc8cSblambert goto fail;
3148fe5fc8cSblambert }
3154aa750c1Sreyk ret = fcgi_add_param(¶m, "REQUEST_URI", str, clt);
316e46d51a0Sreyk free(str);
317e46d51a0Sreyk if (ret == -1) {
318c94b2444Sflorian errstr = "failed to encode param";
319c94b2444Sflorian goto fail;
320c94b2444Sflorian }
321c94b2444Sflorian }
322c94b2444Sflorian
323c94b2444Sflorian (void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf));
3244aa750c1Sreyk if (fcgi_add_param(¶m, "SERVER_ADDR", hbuf, clt) == -1) {
325c94b2444Sflorian errstr = "failed to encode param";
326c94b2444Sflorian goto fail;
327c94b2444Sflorian }
328c94b2444Sflorian
329c94b2444Sflorian (void)snprintf(hbuf, sizeof(hbuf), "%d",
330c94b2444Sflorian ntohs(server_socket_getport(&clt->clt_srv_ss)));
3314aa750c1Sreyk if (fcgi_add_param(¶m, "SERVER_PORT", hbuf, clt) == -1) {
332c94b2444Sflorian errstr = "failed to encode param";
333c94b2444Sflorian goto fail;
334c94b2444Sflorian }
335c94b2444Sflorian
3364aa750c1Sreyk if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name,
337c94b2444Sflorian clt) == -1) {
338c94b2444Sflorian errstr = "failed to encode param";
339c94b2444Sflorian goto fail;
340c94b2444Sflorian }
341c94b2444Sflorian
3424aa750c1Sreyk if (fcgi_add_param(¶m, "SERVER_PROTOCOL", desc->http_version,
343c94b2444Sflorian clt) == -1) {
344c94b2444Sflorian errstr = "failed to encode param";
345c94b2444Sflorian goto fail;
346c94b2444Sflorian }
347c94b2444Sflorian
3484aa750c1Sreyk if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME,
3494aa750c1Sreyk clt) == -1) {
3504aa750c1Sreyk errstr = "failed to encode param";
3514aa750c1Sreyk goto fail;
3524aa750c1Sreyk }
3534aa750c1Sreyk
3544aa750c1Sreyk if (param.total_len != 0) { /* send last params record */
35578dd27f1Sblambert if (bufferevent_write(clt->clt_srvbev, ¶m.buf,
356ab07c989Sflorian sizeof(struct fcgi_record_header) +
35778dd27f1Sblambert ntohs(h->content_len)) == -1) {
35878dd27f1Sblambert errstr = "failed to write to client evbuffer";
35978dd27f1Sblambert goto fail;
36078dd27f1Sblambert }
361c94b2444Sflorian }
362ab07c989Sflorian
363c94b2444Sflorian /* send "no more params" message */
364ab07c989Sflorian h->content_len = 0;
36578dd27f1Sblambert if (bufferevent_write(clt->clt_srvbev, ¶m.buf,
36678dd27f1Sblambert sizeof(struct fcgi_record_header)) == -1) {
36778dd27f1Sblambert errstr = "failed to write to client evbuffer";
36878dd27f1Sblambert goto fail;
36978dd27f1Sblambert }
370ab07c989Sflorian
371cebbf23cSreyk bufferevent_settimeout(clt->clt_srvbev,
372cebbf23cSreyk srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
373cebbf23cSreyk bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE);
374b6a65335Sflorian if (clt->clt_toread != 0) {
375ebfb0322Stb /*
376ebfb0322Stb * XXX - Work around UAF: server_read_httpcontent() can call
377*76ed9045Smillert * server_close(), normally freeing clt. If clt->clt_fcgi_count
378*76ed9045Smillert * reaches 0, call server_close() via server_abort_http().
379ebfb0322Stb */
380*76ed9045Smillert clt->clt_fcgi_count++;
381b6a65335Sflorian server_read_httpcontent(clt->clt_bev, clt);
382*76ed9045Smillert if (clt->clt_fcgi_count-- <= 0) {
383ebfb0322Stb errstr = clt->clt_fcgi_error;
384ebfb0322Stb goto fail;
385*76ed9045Smillert }
386b6a65335Sflorian bufferevent_enable(clt->clt_bev, EV_READ);
387b6a65335Sflorian } else {
388cebbf23cSreyk bufferevent_disable(clt->clt_bev, EV_READ);
389b6a65335Sflorian fcgi_add_stdin(clt, NULL);
390b6a65335Sflorian }
391cebbf23cSreyk
3922e7af781Sreyk if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
3939917324bSflorian clt->clt_fcgi.chunked = 1;
3942e7af781Sreyk } else {
3952e7af781Sreyk /* HTTP/1.0 does not support chunked encoding */
3969917324bSflorian clt->clt_fcgi.chunked = 0;
397cebbf23cSreyk clt->clt_persist = 0;
3982e7af781Sreyk }
3999917324bSflorian clt->clt_fcgi.end = 0;
400cebbf23cSreyk clt->clt_done = 0;
401cebbf23cSreyk
402ef353f36Sreyk free(script);
403ab07c989Sflorian return (0);
404ab07c989Sflorian fail:
405ef353f36Sreyk free(script);
406ab07c989Sflorian if (errstr == NULL)
407ab07c989Sflorian errstr = strerror(errno);
408702d5e8dSjsg if (fd != -1 && clt->clt_fd != fd)
409702d5e8dSjsg close(fd);
410ab07c989Sflorian server_abort_http(clt, 500, errstr);
411ab07c989Sflorian return (-1);
412ab07c989Sflorian }
413ab07c989Sflorian
414ab07c989Sflorian int
fcgi_add_stdin(struct client * clt,struct evbuffer * evbuf)415b6a65335Sflorian fcgi_add_stdin(struct client *clt, struct evbuffer *evbuf)
416b6a65335Sflorian {
417b6a65335Sflorian struct fcgi_record_header h;
418b6a65335Sflorian
4199bbd4a2bSreyk memset(&h, 0, sizeof(h));
420b6a65335Sflorian h.version = 1;
421b6a65335Sflorian h.type = FCGI_STDIN;
422b6a65335Sflorian h.id = htons(1);
423b6a65335Sflorian h.padding_len = 0;
424b6a65335Sflorian
425b6a65335Sflorian if (evbuf == NULL) {
426b6a65335Sflorian h.content_len = 0;
427b6a65335Sflorian return bufferevent_write(clt->clt_srvbev, &h,
428b6a65335Sflorian sizeof(struct fcgi_record_header));
429b6a65335Sflorian } else {
430b6a65335Sflorian h.content_len = htons(EVBUFFER_LENGTH(evbuf));
431b6a65335Sflorian if (bufferevent_write(clt->clt_srvbev, &h,
432b6a65335Sflorian sizeof(struct fcgi_record_header)) == -1)
433b6a65335Sflorian return -1;
434b6a65335Sflorian return bufferevent_write_buffer(clt->clt_srvbev, evbuf);
435b6a65335Sflorian }
436b6a65335Sflorian return (0);
437b6a65335Sflorian }
438b6a65335Sflorian
439b6a65335Sflorian int
fcgi_add_param(struct server_fcgi_param * p,const char * key,const char * val,struct client * clt)4404aa750c1Sreyk fcgi_add_param(struct server_fcgi_param *p, const char *key,
4414aa750c1Sreyk const char *val, struct client *clt)
442ab07c989Sflorian {
443c94b2444Sflorian struct fcgi_record_header *h;
444ab07c989Sflorian int len = 0;
445c94b2444Sflorian int key_len = strlen(key);
446c94b2444Sflorian int val_len = strlen(val);
447c94b2444Sflorian uint8_t *param;
448ab07c989Sflorian
449c94b2444Sflorian len += key_len + val_len;
450c94b2444Sflorian len += key_len > 127 ? 4 : 1;
451c94b2444Sflorian len += val_len > 127 ? 4 : 1;
452c94b2444Sflorian
453c94b2444Sflorian DPRINTF("%s: %s[%d] => %s[%d], total_len: %d", __func__, key, key_len,
4544aa750c1Sreyk val, val_len, p->total_len);
455c94b2444Sflorian
456c94b2444Sflorian if (len > FCGI_CONTENT_SIZE)
457c94b2444Sflorian return (-1);
458c94b2444Sflorian
4594aa750c1Sreyk if (p->total_len + len > FCGI_CONTENT_SIZE) {
46078dd27f1Sblambert if (bufferevent_write(clt->clt_srvbev, p->buf,
46178dd27f1Sblambert sizeof(struct fcgi_record_header) + p->total_len) == -1)
46278dd27f1Sblambert return (-1);
4634aa750c1Sreyk p->total_len = 0;
464c94b2444Sflorian }
465c94b2444Sflorian
4664aa750c1Sreyk h = (struct fcgi_record_header *)p->buf;
4674aa750c1Sreyk param = p->buf + sizeof(*h) + p->total_len;
468c94b2444Sflorian
469c94b2444Sflorian if (key_len > 127) {
470c94b2444Sflorian *param++ = ((key_len >> 24) & 0xff) | 0x80;
471c94b2444Sflorian *param++ = ((key_len >> 16) & 0xff);
472c94b2444Sflorian *param++ = ((key_len >> 8) & 0xff);
473c94b2444Sflorian *param++ = (key_len & 0xff);
474c94b2444Sflorian } else
475c94b2444Sflorian *param++ = key_len;
476c94b2444Sflorian
477c94b2444Sflorian if (val_len > 127) {
478c94b2444Sflorian *param++ = ((val_len >> 24) & 0xff) | 0x80;
479c94b2444Sflorian *param++ = ((val_len >> 16) & 0xff);
480c94b2444Sflorian *param++ = ((val_len >> 8) & 0xff);
481c94b2444Sflorian *param++ = (val_len & 0xff);
482c94b2444Sflorian } else
483c94b2444Sflorian *param++ = val_len;
484c94b2444Sflorian
485c94b2444Sflorian memcpy(param, key, key_len);
486c94b2444Sflorian param += key_len;
487c94b2444Sflorian memcpy(param, val, val_len);
488c94b2444Sflorian
4894aa750c1Sreyk p->total_len += len;
490c94b2444Sflorian
4914aa750c1Sreyk h->content_len = htons(p->total_len);
492c94b2444Sflorian return (0);
493ab07c989Sflorian }
494ab07c989Sflorian
495ab07c989Sflorian void
server_fcgi_error(struct bufferevent * bev,short error,void * arg)496193f358aSop server_fcgi_error(struct bufferevent *bev, short error, void *arg)
497193f358aSop {
498193f358aSop struct client *clt = arg;
49933b680dbSop struct http_descriptor *desc = clt->clt_descreq;
500193f358aSop
501193f358aSop if ((error & EVBUFFER_EOF) && !clt->clt_fcgi.headersdone) {
502193f358aSop server_abort_http(clt, 500, "malformed or no headers");
503193f358aSop return;
504193f358aSop }
505193f358aSop
506193f358aSop /* send the end marker if not already */
50733b680dbSop if (desc->http_method != HTTP_METHOD_HEAD && clt->clt_fcgi.chunked &&
50833b680dbSop !clt->clt_fcgi.end++)
509193f358aSop server_bufferevent_print(clt, "0\r\n\r\n");
510193f358aSop
511193f358aSop server_file_error(bev, error, arg);
512193f358aSop }
513193f358aSop
514193f358aSop void
server_fcgi_read(struct bufferevent * bev,void * arg)515ab07c989Sflorian server_fcgi_read(struct bufferevent *bev, void *arg)
516ab07c989Sflorian {
5175803884eSreyk uint8_t buf[FCGI_RECORD_SIZE];
518ab07c989Sflorian struct client *clt = (struct client *) arg;
519ab07c989Sflorian struct fcgi_record_header *h;
520ab07c989Sflorian size_t len;
52115893f91Sreyk char *ptr;
522ab07c989Sflorian
52311f8da53Sflorian do {
5249917324bSflorian len = bufferevent_read(bev, buf, clt->clt_fcgi.toread);
5255e91c641Sblambert if (evbuffer_add(clt->clt_srvevb, buf, len) == -1) {
5265e91c641Sblambert server_abort_http(clt, 500, "short write");
5275e91c641Sblambert return;
5285e91c641Sblambert }
5299917324bSflorian clt->clt_fcgi.toread -= len;
5302e7af781Sreyk DPRINTF("%s: len: %lu toread: %d state: %d type: %d",
5319917324bSflorian __func__, len, clt->clt_fcgi.toread,
5329917324bSflorian clt->clt_fcgi.state, clt->clt_fcgi.type);
533ab07c989Sflorian
5349917324bSflorian if (clt->clt_fcgi.toread != 0)
53519d2af9eSflorian return;
536ab07c989Sflorian
5379917324bSflorian switch (clt->clt_fcgi.state) {
53819d2af9eSflorian case FCGI_READ_HEADER:
5399917324bSflorian clt->clt_fcgi.state = FCGI_READ_CONTENT;
54019d2af9eSflorian h = (struct fcgi_record_header *)
54119d2af9eSflorian EVBUFFER_DATA(clt->clt_srvevb);
54219d2af9eSflorian DPRINTF("%s: record header: version %d type %d id %d "
5436d469730Sflorian "content len %d padding %d", __func__,
5446d469730Sflorian h->version, h->type, ntohs(h->id),
5456d469730Sflorian ntohs(h->content_len), h->padding_len);
5469917324bSflorian clt->clt_fcgi.type = h->type;
5479917324bSflorian clt->clt_fcgi.toread = ntohs(h->content_len);
5489917324bSflorian clt->clt_fcgi.padding_len = h->padding_len;
54919d2af9eSflorian evbuffer_drain(clt->clt_srvevb,
55019d2af9eSflorian EVBUFFER_LENGTH(clt->clt_srvevb));
5519917324bSflorian if (clt->clt_fcgi.toread != 0)
55219d2af9eSflorian break;
5539917324bSflorian else if (clt->clt_fcgi.type == FCGI_STDOUT &&
554b1450811Sflorian !clt->clt_chunk) {
555b1450811Sflorian server_abort_http(clt, 500, "empty stdout");
556b1450811Sflorian return;
557b1450811Sflorian }
558ab07c989Sflorian
55919d2af9eSflorian /* fallthrough if content_len == 0 */
56019d2af9eSflorian case FCGI_READ_CONTENT:
5619917324bSflorian switch (clt->clt_fcgi.type) {
5622e7af781Sreyk case FCGI_STDERR:
5632e7af781Sreyk if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 &&
5642e7af781Sreyk (ptr = get_string(
56515893f91Sreyk EVBUFFER_DATA(clt->clt_srvevb),
56615893f91Sreyk EVBUFFER_LENGTH(clt->clt_srvevb)))
56715893f91Sreyk != NULL) {
56815893f91Sreyk server_sendlog(clt->clt_srv_conf,
56915893f91Sreyk IMSG_LOG_ERROR, "%s", ptr);
57015893f91Sreyk free(ptr);
57115893f91Sreyk }
5722e7af781Sreyk break;
5732e7af781Sreyk case FCGI_STDOUT:
574e6c0e46bSflorian ++clt->clt_chunk;
5759917324bSflorian if (!clt->clt_fcgi.headersdone) {
5769917324bSflorian clt->clt_fcgi.headersdone =
577e6c0e46bSflorian server_fcgi_getheaders(clt);
5782e7af781Sreyk if (!EVBUFFER_LENGTH(clt->clt_srvevb))
5792e7af781Sreyk break;
5802e7af781Sreyk }
5812e7af781Sreyk /* FALLTHROUGH */
5822e7af781Sreyk case FCGI_END_REQUEST:
5835a7cb2b1Sflorian if (clt->clt_fcgi.headersdone &&
5845a7cb2b1Sflorian !clt->clt_fcgi.headerssent) {
5855a7cb2b1Sflorian if (server_fcgi_header(clt,
5865a7cb2b1Sflorian clt->clt_fcgi.status) == -1) {
5875a7cb2b1Sflorian server_abort_http(clt, 500,
5885a7cb2b1Sflorian "malformed fcgi headers");
5895a7cb2b1Sflorian return;
5905a7cb2b1Sflorian }
5915a7cb2b1Sflorian }
5925dfcea78Sbenno /* Don't send content for HEAD requests */
5935dfcea78Sbenno if (clt->clt_fcgi.headerssent &&
594873b6565Sclaudio clt->clt_descreq->http_method
5955dfcea78Sbenno == HTTP_METHOD_HEAD)
596b3aaf490Sclaudio /* nothing */ ;
597b3aaf490Sclaudio else if (server_fcgi_writechunk(clt) == -1) {
5982e7af781Sreyk server_abort_http(clt, 500,
5992e7af781Sreyk "encoding error");
6002e7af781Sreyk return;
6012e7af781Sreyk }
602b3aaf490Sclaudio if (clt->clt_fcgi.type == FCGI_END_REQUEST) {
603b3aaf490Sclaudio bufferevent_enable(clt->clt_bev,
604b3aaf490Sclaudio EV_READ|EV_WRITE);
605b3aaf490Sclaudio if (clt->clt_persist)
606b3aaf490Sclaudio clt->clt_toread =
607b3aaf490Sclaudio TOREAD_HTTP_HEADER;
608b3aaf490Sclaudio else
609b3aaf490Sclaudio clt->clt_toread =
610b3aaf490Sclaudio TOREAD_HTTP_NONE;
611b3aaf490Sclaudio clt->clt_done = 0;
612b3aaf490Sclaudio server_reset_http(clt);
613b3aaf490Sclaudio }
6142e7af781Sreyk break;
61519d2af9eSflorian }
61619d2af9eSflorian evbuffer_drain(clt->clt_srvevb,
61719d2af9eSflorian EVBUFFER_LENGTH(clt->clt_srvevb));
6189917324bSflorian if (!clt->clt_fcgi.padding_len) {
6199917324bSflorian clt->clt_fcgi.state = FCGI_READ_HEADER;
6209917324bSflorian clt->clt_fcgi.toread =
62119d2af9eSflorian sizeof(struct fcgi_record_header);
6226d469730Sflorian } else {
6239917324bSflorian clt->clt_fcgi.state = FCGI_READ_PADDING;
6249917324bSflorian clt->clt_fcgi.toread =
6259917324bSflorian clt->clt_fcgi.padding_len;
6266d469730Sflorian }
6276d469730Sflorian break;
6286d469730Sflorian case FCGI_READ_PADDING:
6296d469730Sflorian evbuffer_drain(clt->clt_srvevb,
6306d469730Sflorian EVBUFFER_LENGTH(clt->clt_srvevb));
6319917324bSflorian clt->clt_fcgi.state = FCGI_READ_HEADER;
6329917324bSflorian clt->clt_fcgi.toread =
6336d469730Sflorian sizeof(struct fcgi_record_header);
6346d469730Sflorian break;
635ab07c989Sflorian }
63611f8da53Sflorian } while (len > 0);
637ab07c989Sflorian }
638ab07c989Sflorian
639cebbf23cSreyk int
server_fcgi_header(struct client * clt,unsigned int code)6404703e0faSreyk server_fcgi_header(struct client *clt, unsigned int code)
641ab07c989Sflorian {
642d8ac5268Sflorian struct server_config *srv_conf = clt->clt_srv_conf;
643d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
644d08e4976Sreyk struct http_descriptor *resp = clt->clt_descresp;
645cebbf23cSreyk const char *error;
646cebbf23cSreyk char tmbuf[32];
647d8ac5268Sflorian struct kv *kv, *cl, key;
648cebbf23cSreyk
6495a7cb2b1Sflorian clt->clt_fcgi.headerssent = 1;
6505a7cb2b1Sflorian
651cebbf23cSreyk if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
652cebbf23cSreyk return (-1);
653cebbf23cSreyk
65474dc588cSreyk if (server_log_http(clt, code, 0) == -1)
65574dc588cSreyk return (-1);
65674dc588cSreyk
657cebbf23cSreyk /* Add error codes */
6583323ac76Sbenno if (kv_setkey(&resp->http_pathquery, "%u", code) == -1 ||
659d08e4976Sreyk kv_set(&resp->http_pathquery, "%s", error) == -1)
660cebbf23cSreyk return (-1);
661cebbf23cSreyk
662cebbf23cSreyk /* Add headers */
663d08e4976Sreyk if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
664cebbf23cSreyk return (-1);
665cebbf23cSreyk
6665a7cb2b1Sflorian if (clt->clt_fcgi.type == FCGI_END_REQUEST ||
6675a7cb2b1Sflorian EVBUFFER_LENGTH(clt->clt_srvevb) == 0) {
6685a7cb2b1Sflorian /* Can't chunk encode an empty body. */
6695a7cb2b1Sflorian clt->clt_fcgi.chunked = 0;
670933c26cbSflorian
6715dfcea78Sbenno /* But then we need a Content-Length unless method is HEAD... */
6725dfcea78Sbenno if (desc->http_method != HTTP_METHOD_HEAD) {
673933c26cbSflorian key.kv_key = "Content-Length";
674933c26cbSflorian if ((kv = kv_find(&resp->http_headers, &key)) == NULL) {
675933c26cbSflorian if (kv_add(&resp->http_headers,
676933c26cbSflorian "Content-Length", "0") == NULL)
677933c26cbSflorian return (-1);
678933c26cbSflorian }
6795a7cb2b1Sflorian }
6805dfcea78Sbenno }
6815a7cb2b1Sflorian
6825dfcea78Sbenno /* Send chunked encoding header */
6839917324bSflorian if (clt->clt_fcgi.chunked) {
6845dfcea78Sbenno /* but only if no Content-Length header is supplied */
6852e7af781Sreyk key.kv_key = "Content-Length";
6865dfcea78Sbenno if ((kv = kv_find(&resp->http_headers, &key)) != NULL) {
6875dfcea78Sbenno clt->clt_fcgi.chunked = 0;
6885dfcea78Sbenno } else {
6892e7af781Sreyk /*
6905dfcea78Sbenno * XXX What if the FastCGI added some kind of
6915dfcea78Sbenno * Transfer-Encoding, like gzip, deflate or even
6925dfcea78Sbenno * "chunked"?
6932e7af781Sreyk */
6942e7af781Sreyk if (kv_add(&resp->http_headers,
6952e7af781Sreyk "Transfer-Encoding", "chunked") == NULL)
6962e7af781Sreyk return (-1);
6972e7af781Sreyk }
6985dfcea78Sbenno }
6992e7af781Sreyk
700cebbf23cSreyk /* Is it a persistent connection? */
701cebbf23cSreyk if (clt->clt_persist) {
702d08e4976Sreyk if (kv_add(&resp->http_headers,
703cebbf23cSreyk "Connection", "keep-alive") == NULL)
704cebbf23cSreyk return (-1);
705d08e4976Sreyk } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
706cebbf23cSreyk return (-1);
707cebbf23cSreyk
708d8ac5268Sflorian /* HSTS header */
709ae30f9e2Sbentley if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
710ae30f9e2Sbentley srv_conf->flags & SRVFLAG_TLS) {
711d8ac5268Sflorian if ((cl =
712d8ac5268Sflorian kv_add(&resp->http_headers, "Strict-Transport-Security",
713d8ac5268Sflorian NULL)) == NULL ||
7143323ac76Sbenno kv_set(cl, "max-age=%d%s%s", srv_conf->hsts_max_age,
715d8ac5268Sflorian srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
716d8ac5268Sflorian "; includeSubDomains" : "",
717d8ac5268Sflorian srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
718d8ac5268Sflorian "; preload" : "") == -1)
719d8ac5268Sflorian return (-1);
720d8ac5268Sflorian }
721d8ac5268Sflorian
722be5ab2e6Schrisz /* Date header is mandatory and should be added as late as possible */
72393cf633bSians key.kv_key = "Date";
72446b94b0cSbenno if (kv_find(&resp->http_headers, &key) == NULL &&
72593cf633bSians (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
72693cf633bSians kv_add(&resp->http_headers, "Date", tmbuf) == NULL))
727cebbf23cSreyk return (-1);
728cebbf23cSreyk
729cebbf23cSreyk if (server_writeresponse_http(clt) == -1 ||
730cebbf23cSreyk server_bufferevent_print(clt, "\r\n") == -1 ||
73157c60317Sreyk server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
73257c60317Sreyk server_bufferevent_print(clt, "\r\n") == -1)
733cebbf23cSreyk return (-1);
734cebbf23cSreyk
735cebbf23cSreyk return (0);
736ab07c989Sflorian }
7374aa750c1Sreyk
7384aa750c1Sreyk int
server_fcgi_writeheader(struct client * clt,struct kv * hdr,void * arg)7394aa750c1Sreyk server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg)
7404aa750c1Sreyk {
7414aa750c1Sreyk struct server_fcgi_param *param = arg;
7424aa750c1Sreyk char *val, *name, *p;
7434aa750c1Sreyk const char *key;
7444aa750c1Sreyk int ret;
7454aa750c1Sreyk
7464aa750c1Sreyk /* The key might have been updated in the parent */
7474aa750c1Sreyk if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
7484aa750c1Sreyk key = hdr->kv_parent->kv_key;
7494aa750c1Sreyk else
7504aa750c1Sreyk key = hdr->kv_key;
7514aa750c1Sreyk
7524aa750c1Sreyk val = hdr->kv_value;
7534aa750c1Sreyk
754af746ff6Sflorian if (strcasecmp(key, "Content-Length") == 0 ||
755af746ff6Sflorian strcasecmp(key, "Content-Type") == 0) {
756af746ff6Sflorian if ((name = strdup(key)) == NULL)
757af746ff6Sflorian return (-1);
758af746ff6Sflorian } else {
7594aa750c1Sreyk if (asprintf(&name, "HTTP_%s", key) == -1)
7604aa750c1Sreyk return (-1);
761af746ff6Sflorian }
7624aa750c1Sreyk
763eec990a9Sflorian /*
764eec990a9Sflorian * RFC 7230 defines a header field-name as a "token" and a "token"
765eec990a9Sflorian * is defined as one or more characters for which isalpha or
766eec990a9Sflorian * isdigit is true plus a list of additional characters.
767eec990a9Sflorian * According to RFC 3875 a CGI environment variable is created
768eec990a9Sflorian * by converting all letters to upper case and replacing '-'
769eec990a9Sflorian * with '_'.
770eec990a9Sflorian */
7714aa750c1Sreyk for (p = name; *p != '\0'; p++) {
772b76c06cdSflorian if (isalpha((unsigned char)*p))
773b76c06cdSflorian *p = toupper((unsigned char)*p);
774eec990a9Sflorian else if (!(*p == '!' || *p == '#' || *p == '$' || *p == '%' ||
775eec990a9Sflorian *p == '&' || *p == '\'' || *p == '*' || *p == '+' ||
776eec990a9Sflorian *p == '.' || *p == '^' || *p == '_' || *p == '`' ||
777eec990a9Sflorian *p == '|' || *p == '~' || isdigit((unsigned char)*p)))
7784aa750c1Sreyk *p = '_';
7794aa750c1Sreyk }
7804aa750c1Sreyk
7814aa750c1Sreyk ret = fcgi_add_param(param, name, val, clt);
7824aa750c1Sreyk free(name);
7834aa750c1Sreyk
7844aa750c1Sreyk return (ret);
7854aa750c1Sreyk }
78627c77bdaSflorian
78727c77bdaSflorian int
server_fcgi_writechunk(struct client * clt)7882e7af781Sreyk server_fcgi_writechunk(struct client *clt)
7892e7af781Sreyk {
7902e7af781Sreyk struct evbuffer *evb = clt->clt_srvevb;
7912e7af781Sreyk size_t len;
7922e7af781Sreyk
7939917324bSflorian if (clt->clt_fcgi.type == FCGI_END_REQUEST) {
7942e7af781Sreyk len = 0;
7952e7af781Sreyk } else
7962e7af781Sreyk len = EVBUFFER_LENGTH(evb);
7972e7af781Sreyk
7989917324bSflorian if (clt->clt_fcgi.chunked) {
7992e7af781Sreyk /* If len is 0, make sure to write the end marker only once */
8009917324bSflorian if (len == 0 && clt->clt_fcgi.end++)
8012e7af781Sreyk return (0);
8022e7af781Sreyk if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 ||
8032e7af781Sreyk server_bufferevent_write_chunk(clt, evb, len) == -1 ||
8042e7af781Sreyk server_bufferevent_print(clt, "\r\n") == -1)
8052e7af781Sreyk return (-1);
80633b95b44Sreyk } else if (len)
8072e7af781Sreyk return (server_bufferevent_write_buffer(clt, evb));
8082e7af781Sreyk
8092e7af781Sreyk return (0);
8102e7af781Sreyk }
8112e7af781Sreyk
8122e7af781Sreyk int
server_fcgi_getheaders(struct client * clt)81357c60317Sreyk server_fcgi_getheaders(struct client *clt)
81427c77bdaSflorian {
81557c60317Sreyk struct http_descriptor *resp = clt->clt_descresp;
8161fb81db1Sreyk struct evbuffer *evb = clt->clt_srvevb;
8177f7ed8ccSpatrick int code, ret;
81857c60317Sreyk char *line, *key, *value;
81927c77bdaSflorian const char *errstr;
82027c77bdaSflorian
82157c60317Sreyk while ((line = evbuffer_getline(evb)) != NULL && *line != '\0') {
82257c60317Sreyk key = line;
82357c60317Sreyk
82457c60317Sreyk if ((value = strchr(key, ':')) == NULL)
82557c60317Sreyk break;
82611b123fbSpatrick
82757c60317Sreyk *value++ = '\0';
82857c60317Sreyk value += strspn(value, " \t");
82957c60317Sreyk
83057c60317Sreyk DPRINTF("%s: %s: %s", __func__, key, value);
83157c60317Sreyk
83257c60317Sreyk if (strcasecmp("Status", key) == 0) {
83357c60317Sreyk value[strcspn(value, " \t")] = '\0';
83457c60317Sreyk code = (int)strtonum(value, 100, 600, &errstr);
83527c77bdaSflorian if (errstr != NULL || server_httperror_byid(
83627c77bdaSflorian code) == NULL)
83727c77bdaSflorian code = 200;
8389917324bSflorian clt->clt_fcgi.status = code;
83957c60317Sreyk } else {
84057c60317Sreyk (void)kv_add(&resp->http_headers, key, value);
84127c77bdaSflorian }
84257c60317Sreyk free(line);
84327c77bdaSflorian }
84457c60317Sreyk
8457f7ed8ccSpatrick ret = (line != NULL && *line == '\0');
8467f7ed8ccSpatrick
8477f7ed8ccSpatrick free(line);
8487f7ed8ccSpatrick return ret;
84927c77bdaSflorian }
850