server_fcgi.c revision 1.40
1/*	$OpenBSD: server_fcgi.c,v 1.40 2014/10/25 03:23:49 lteo Exp $	*/
2
3/*
4 * Copyright (c) 2014 Florian Obser <florian@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/queue.h>
21#include <sys/time.h>
22#include <sys/stat.h>
23#include <sys/socket.h>
24#include <sys/un.h>
25#include <sys/tree.h>
26#include <sys/hash.h>
27
28#include <net/if.h>
29#include <netinet/in.h>
30#include <netinet/ip.h>
31#include <netinet/tcp.h>
32#include <arpa/inet.h>
33
34#include <errno.h>
35#include <fcntl.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39#include <stdio.h>
40#include <ctype.h>
41#include <err.h>
42#include <event.h>
43
44#include "httpd.h"
45#include "http.h"
46
47#define FCGI_PADDING_SIZE	 255
48#define FCGI_RECORD_SIZE	 \
49    (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
50
51#define FCGI_BEGIN_REQUEST	 1
52#define FCGI_ABORT_REQUEST	 2
53#define FCGI_END_REQUEST	 3
54#define FCGI_PARAMS		 4
55#define FCGI_STDIN		 5
56#define FCGI_STDOUT		 6
57#define FCGI_STDERR		 7
58#define FCGI_DATA		 8
59#define FCGI_GET_VALUES		 9
60#define FCGI_GET_VALUES_RESULT	10
61#define FCGI_UNKNOWN_TYPE	11
62#define FCGI_MAXTYPE		(FCGI_UNKNOWN_TYPE)
63
64#define FCGI_RESPONDER		 1
65
66struct fcgi_record_header {
67	uint8_t		version;
68	uint8_t		type;
69	uint16_t	id;
70	uint16_t	content_len;
71	uint8_t		padding_len;
72	uint8_t		reserved;
73} __packed;
74
75struct fcgi_begin_request_body {
76	uint16_t	role;
77	uint8_t		flags;
78	uint8_t		reserved[5];
79} __packed;
80
81struct server_fcgi_param {
82	int		total_len;
83	uint8_t		buf[FCGI_RECORD_SIZE];
84};
85
86int	server_fcgi_header(struct client *, u_int);
87void	server_fcgi_read(struct bufferevent *, void *);
88int	server_fcgi_writeheader(struct client *, struct kv *, void *);
89int	server_fcgi_writechunk(struct client *);
90int	server_fcgi_getheaders(struct client *);
91int	fcgi_add_param(struct server_fcgi_param *, const char *, const char *,
92	    struct client *);
93
94int
95server_fcgi(struct httpd *env, struct client *clt)
96{
97	struct server_fcgi_param	 param;
98	struct server_config		*srv_conf = clt->clt_srv_conf;
99	struct http_descriptor		*desc = clt->clt_descreq;
100	struct fcgi_record_header	*h;
101	struct fcgi_begin_request_body	*begin;
102	char				 hbuf[MAXHOSTNAMELEN];
103	size_t				 scriptlen;
104	int				 pathlen;
105	int				 fd = -1, ret;
106	const char			*errstr = NULL;
107	char				*str, *p, *script = NULL;
108
109	if (srv_conf->socket[0] == ':') {
110		struct sockaddr_storage	 ss;
111		in_port_t		 port;
112
113		p = srv_conf->socket + 1;
114
115		port = strtonum(p, 0, 0xffff, &errstr);
116		if (errstr != NULL) {
117			log_warn("%s: strtonum %s, %s", __func__, p, errstr);
118			goto fail;
119		}
120		memset(&ss, 0, sizeof(ss));
121		ss.ss_family = AF_INET;
122		((struct sockaddr_in *)
123		    &ss)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
124		port = htons(port);
125
126		if ((fd = server_socket_connect(&ss, port, srv_conf)) == -1)
127			goto fail;
128	} else {
129		struct sockaddr_un	 sun;
130		size_t			 len;
131
132		if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
133			goto fail;
134
135		memset(&sun, 0, sizeof(sun));
136		sun.sun_family = AF_UNIX;
137		len = strlcpy(sun.sun_path,
138		    srv_conf->socket, sizeof(sun.sun_path));
139		if (len >= sizeof(sun.sun_path)) {
140			errstr = "socket path to long";
141			goto fail;
142		}
143		sun.sun_len = len;
144
145		if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
146			goto fail;
147	}
148
149	socket_set_blockmode(fd, BM_NONBLOCK);
150
151	memset(hbuf, 0, sizeof(hbuf));
152	clt->clt_fcgi_state = FCGI_READ_HEADER;
153	clt->clt_fcgi_toread = sizeof(struct fcgi_record_header);
154
155	if (clt->clt_srvevb != NULL)
156		evbuffer_free(clt->clt_srvevb);
157
158	clt->clt_srvevb = evbuffer_new();
159	if (clt->clt_srvevb == NULL) {
160		errstr = "failed to allocate evbuffer";
161		goto fail;
162	}
163
164	clt->clt_fd = fd;
165	if (clt->clt_srvbev != NULL)
166		bufferevent_free(clt->clt_srvbev);
167
168	clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read,
169	    NULL, server_file_error, clt);
170	if (clt->clt_srvbev == NULL) {
171		errstr = "failed to allocate fcgi buffer event";
172		goto fail;
173	}
174
175	memset(&param, 0, sizeof(param));
176
177	h = (struct fcgi_record_header *)&param.buf;
178	h->version = 1;
179	h->type = FCGI_BEGIN_REQUEST;
180	h->id = htons(1);
181	h->content_len = htons(sizeof(struct fcgi_begin_request_body));
182	h->padding_len = 0;
183
184	begin = (struct fcgi_begin_request_body *)&param.buf[sizeof(struct
185	    fcgi_record_header)];
186	begin->role = htons(FCGI_RESPONDER);
187
188	bufferevent_write(clt->clt_srvbev, &param.buf,
189	    sizeof(struct fcgi_record_header) +
190	    sizeof(struct fcgi_begin_request_body));
191
192	h->type = FCGI_PARAMS;
193	h->content_len = param.total_len = 0;
194
195	if ((pathlen = asprintf(&script, "%s%s", srv_conf->root,
196	    desc->http_path_alias != NULL ?
197	    desc->http_path_alias : desc->http_path)) == -1) {
198		errstr = "failed to get script name";
199		goto fail;
200	}
201
202	scriptlen = path_info(script);
203	/*
204	 * no part of root should show up in PATH_INFO.
205	 * therefore scriptlen should be >= strlen(root)
206	 */
207	if (scriptlen < strlen(srv_conf->root))
208		scriptlen = strlen(srv_conf->root);
209	if ((int)scriptlen < pathlen) {
210		if (fcgi_add_param(&param, "PATH_INFO",
211		    script + scriptlen, clt) == -1) {
212			errstr = "failed to encode param";
213			goto fail;
214		}
215		script[scriptlen] = '\0';
216	}
217
218	if (fcgi_add_param(&param, "SCRIPT_NAME",
219	    script + strlen(srv_conf->root), clt) == -1) {
220		errstr = "failed to encode param";
221		goto fail;
222	}
223	if (fcgi_add_param(&param, "SCRIPT_FILENAME", script, clt) == -1) {
224		errstr = "failed to encode param";
225		goto fail;
226	}
227
228	if (desc->http_query)
229		if (fcgi_add_param(&param, "QUERY_STRING", desc->http_query,
230		    clt) == -1) {
231			errstr = "failed to encode param";
232			goto fail;
233		}
234
235	if (fcgi_add_param(&param, "DOCUMENT_ROOT", srv_conf->root,
236	    clt) == -1) {
237		errstr = "failed to encode param";
238		goto fail;
239	}
240	if (fcgi_add_param(&param, "DOCUMENT_URI", desc->http_path,
241	    clt) == -1) {
242		errstr = "failed to encode param";
243		goto fail;
244	}
245	if (fcgi_add_param(&param, "GATEWAY_INTERFACE", "CGI/1.1",
246	    clt) == -1) {
247		errstr = "failed to encode param";
248		goto fail;
249	}
250
251	/* Add HTTP_* headers */
252	if (server_headers(clt, desc, server_fcgi_writeheader, &param) == -1) {
253		errstr = "failed to encode param";
254		goto fail;
255	}
256
257	if (srv_conf->flags & SRVFLAG_SSL)
258		if (fcgi_add_param(&param, "HTTPS", "on", clt) == -1) {
259			errstr = "failed to encode param";
260			goto fail;
261		}
262
263	(void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf));
264	if (fcgi_add_param(&param, "REMOTE_ADDR", hbuf, clt) == -1) {
265		errstr = "failed to encode param";
266		goto fail;
267	}
268
269	(void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port));
270	if (fcgi_add_param(&param, "REMOTE_PORT", hbuf, clt) == -1) {
271		errstr = "failed to encode param";
272		goto fail;
273	}
274
275	if (fcgi_add_param(&param, "REQUEST_METHOD",
276	    server_httpmethod_byid(desc->http_method), clt) == -1) {
277		errstr = "failed to encode param";
278		goto fail;
279	}
280
281	if (!desc->http_query) {
282		if (fcgi_add_param(&param, "REQUEST_URI", desc->http_path,
283		    clt) == -1) {
284			errstr = "failed to encode param";
285			goto fail;
286		}
287	} else if (asprintf(&str, "%s?%s", desc->http_path,
288	    desc->http_query) != -1) {
289		ret = fcgi_add_param(&param, "REQUEST_URI", str, clt);
290		free(str);
291		if (ret == -1) {
292			errstr = "failed to encode param";
293			goto fail;
294		}
295	}
296
297	(void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf));
298	if (fcgi_add_param(&param, "SERVER_ADDR", hbuf, clt) == -1) {
299		errstr = "failed to encode param";
300		goto fail;
301	}
302
303	(void)snprintf(hbuf, sizeof(hbuf), "%d",
304	    ntohs(server_socket_getport(&clt->clt_srv_ss)));
305	if (fcgi_add_param(&param, "SERVER_PORT", hbuf, clt) == -1) {
306		errstr = "failed to encode param";
307		goto fail;
308	}
309
310	if (fcgi_add_param(&param, "SERVER_NAME", srv_conf->name,
311	    clt) == -1) {
312		errstr = "failed to encode param";
313		goto fail;
314	}
315
316	if (fcgi_add_param(&param, "SERVER_PROTOCOL", desc->http_version,
317	    clt) == -1) {
318		errstr = "failed to encode param";
319		goto fail;
320	}
321
322	if (fcgi_add_param(&param, "SERVER_SOFTWARE", HTTPD_SERVERNAME,
323	    clt) == -1) {
324		errstr = "failed to encode param";
325		goto fail;
326	}
327
328	if (param.total_len != 0) {	/* send last params record */
329		bufferevent_write(clt->clt_srvbev, &param.buf,
330		    sizeof(struct fcgi_record_header) +
331		    ntohs(h->content_len));
332	}
333
334	/* send "no more params" message */
335	h->content_len = 0;
336	bufferevent_write(clt->clt_srvbev, &param.buf,
337	    sizeof(struct fcgi_record_header));
338
339	bufferevent_settimeout(clt->clt_srvbev,
340	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
341	bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE);
342	if (clt->clt_toread != 0) {
343		server_read_httpcontent(clt->clt_bev, clt);
344		bufferevent_enable(clt->clt_bev, EV_READ);
345	} else {
346		bufferevent_disable(clt->clt_bev, EV_READ);
347		fcgi_add_stdin(clt, NULL);
348	}
349
350	if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
351		clt->clt_fcgi_chunked = 1;
352	} else {
353		/* HTTP/1.0 does not support chunked encoding */
354		clt->clt_fcgi_chunked = 0;
355		clt->clt_persist = 0;
356	}
357	clt->clt_fcgi_end = 0;
358	clt->clt_done = 0;
359
360	free(script);
361	return (0);
362 fail:
363	free(script);
364	if (errstr == NULL)
365		errstr = strerror(errno);
366	server_abort_http(clt, 500, errstr);
367	return (-1);
368}
369
370int
371fcgi_add_stdin(struct client *clt, struct evbuffer *evbuf)
372{
373	struct fcgi_record_header	h;
374
375	memset(&h, 0, sizeof(h));
376	h.version = 1;
377	h.type = FCGI_STDIN;
378	h.id = htons(1);
379	h.padding_len = 0;
380
381	if (evbuf == NULL) {
382		h.content_len = 0;
383		return bufferevent_write(clt->clt_srvbev, &h,
384		    sizeof(struct fcgi_record_header));
385	} else {
386		h.content_len = htons(EVBUFFER_LENGTH(evbuf));
387		if (bufferevent_write(clt->clt_srvbev, &h,
388		    sizeof(struct fcgi_record_header)) == -1)
389			return -1;
390		return bufferevent_write_buffer(clt->clt_srvbev, evbuf);
391	}
392	return (0);
393}
394
395int
396fcgi_add_param(struct server_fcgi_param *p, const char *key,
397    const char *val, struct client *clt)
398{
399	struct fcgi_record_header	*h;
400	int				 len = 0;
401	int				 key_len = strlen(key);
402	int				 val_len = strlen(val);
403	uint8_t				*param;
404
405	len += key_len + val_len;
406	len += key_len > 127 ? 4 : 1;
407	len += val_len > 127 ? 4 : 1;
408
409	DPRINTF("%s: %s[%d] => %s[%d], total_len: %d", __func__, key, key_len,
410	    val, val_len, p->total_len);
411
412	if (len > FCGI_CONTENT_SIZE)
413		return (-1);
414
415	if (p->total_len + len > FCGI_CONTENT_SIZE) {
416		bufferevent_write(clt->clt_srvbev, p->buf,
417		    sizeof(struct fcgi_record_header) + p->total_len);
418		p->total_len = 0;
419	}
420
421	h = (struct fcgi_record_header *)p->buf;
422	param = p->buf + sizeof(*h) + p->total_len;
423
424	if (key_len > 127) {
425		*param++ = ((key_len >> 24) & 0xff) | 0x80;
426		*param++ = ((key_len >> 16) & 0xff);
427		*param++ = ((key_len >> 8) & 0xff);
428		*param++ = (key_len & 0xff);
429	} else
430		*param++ = key_len;
431
432	if (val_len > 127) {
433		*param++ = ((val_len >> 24) & 0xff) | 0x80;
434		*param++ = ((val_len >> 16) & 0xff);
435		*param++ = ((val_len >> 8) & 0xff);
436		*param++ = (val_len & 0xff);
437	} else
438		*param++ = val_len;
439
440	memcpy(param, key, key_len);
441	param += key_len;
442	memcpy(param, val, val_len);
443
444	p->total_len += len;
445
446	h->content_len = htons(p->total_len);
447	return (0);
448}
449
450void
451server_fcgi_read(struct bufferevent *bev, void *arg)
452{
453	uint8_t				 buf[FCGI_RECORD_SIZE];
454	struct client			*clt = (struct client *) arg;
455	struct fcgi_record_header	*h;
456	size_t				 len;
457	char				*ptr;
458
459	do {
460		len = bufferevent_read(bev, buf, clt->clt_fcgi_toread);
461		/* XXX error handling */
462		evbuffer_add(clt->clt_srvevb, buf, len);
463		clt->clt_fcgi_toread -= len;
464		DPRINTF("%s: len: %lu toread: %d state: %d type: %d",
465		    __func__, len, clt->clt_fcgi_toread,
466		    clt->clt_fcgi_state, clt->clt_fcgi_type);
467
468		if (clt->clt_fcgi_toread != 0)
469			return;
470
471		switch (clt->clt_fcgi_state) {
472		case FCGI_READ_HEADER:
473			clt->clt_fcgi_state = FCGI_READ_CONTENT;
474			h = (struct fcgi_record_header *)
475			    EVBUFFER_DATA(clt->clt_srvevb);
476			DPRINTF("%s: record header: version %d type %d id %d "
477			    "content len %d padding %d", __func__,
478			    h->version, h->type, ntohs(h->id),
479			    ntohs(h->content_len), h->padding_len);
480			clt->clt_fcgi_type = h->type;
481			clt->clt_fcgi_toread = ntohs(h->content_len);
482			clt->clt_fcgi_padding_len = h->padding_len;
483			evbuffer_drain(clt->clt_srvevb,
484			    EVBUFFER_LENGTH(clt->clt_srvevb));
485			if (clt->clt_fcgi_toread != 0)
486				break;
487			else if (clt->clt_fcgi_type == FCGI_STDOUT &&
488			    !clt->clt_chunk) {
489				server_abort_http(clt, 500, "empty stdout");
490				return;
491			}
492
493			/* fallthrough if content_len == 0 */
494		case FCGI_READ_CONTENT:
495			switch (clt->clt_fcgi_type) {
496			case FCGI_STDERR:
497				if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 &&
498				    (ptr = get_string(
499				    EVBUFFER_DATA(clt->clt_srvevb),
500				    EVBUFFER_LENGTH(clt->clt_srvevb)))
501				    != NULL) {
502					server_sendlog(clt->clt_srv_conf,
503					    IMSG_LOG_ERROR, "%s", ptr);
504					free(ptr);
505				}
506				break;
507			case FCGI_STDOUT:
508				if (++clt->clt_chunk == 1) {
509					if (server_fcgi_header(clt,
510					    server_fcgi_getheaders(clt))
511					    == -1) {
512						server_abort_http(clt, 500,
513						    "malformed fcgi headers");
514						return;
515					}
516					if (!EVBUFFER_LENGTH(clt->clt_srvevb))
517						break;
518				}
519				/* FALLTHROUGH */
520			case FCGI_END_REQUEST:
521				if (server_fcgi_writechunk(clt) == -1) {
522					server_abort_http(clt, 500,
523					    "encoding error");
524					return;
525				}
526				break;
527			}
528			evbuffer_drain(clt->clt_srvevb,
529			    EVBUFFER_LENGTH(clt->clt_srvevb));
530			if (!clt->clt_fcgi_padding_len) {
531				clt->clt_fcgi_state = FCGI_READ_HEADER;
532				clt->clt_fcgi_toread =
533				    sizeof(struct fcgi_record_header);
534			} else {
535				clt->clt_fcgi_state = FCGI_READ_PADDING;
536				clt->clt_fcgi_toread =
537				    clt->clt_fcgi_padding_len;
538			}
539			break;
540		case FCGI_READ_PADDING:
541			evbuffer_drain(clt->clt_srvevb,
542			    EVBUFFER_LENGTH(clt->clt_srvevb));
543			clt->clt_fcgi_state = FCGI_READ_HEADER;
544			clt->clt_fcgi_toread =
545			    sizeof(struct fcgi_record_header);
546			break;
547		}
548	} while (len > 0);
549}
550
551int
552server_fcgi_header(struct client *clt, u_int code)
553{
554	struct http_descriptor	*desc = clt->clt_descreq;
555	struct http_descriptor	*resp = clt->clt_descresp;
556	const char		*error;
557	char			 tmbuf[32];
558	struct kv		*kv, key;
559
560	if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
561		return (-1);
562
563	if (server_log_http(clt, code, 0) == -1)
564		return (-1);
565
566	/* Add error codes */
567	if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
568	    kv_set(&resp->http_pathquery, "%s", error) == -1)
569		return (-1);
570
571	/* Add headers */
572	if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
573		return (-1);
574
575	/* Set chunked encoding */
576	if (clt->clt_fcgi_chunked) {
577		/* XXX Should we keep and handle Content-Length instead? */
578		key.kv_key = "Content-Length";
579		if ((kv = kv_find(&resp->http_headers, &key)) != NULL)
580			kv_delete(&resp->http_headers, kv);
581
582		/*
583		 * XXX What if the FastCGI added some kind of Transfer-Encoding?
584		 * XXX like gzip, deflate or even "chunked"?
585		 */
586		if (kv_add(&resp->http_headers,
587		    "Transfer-Encoding", "chunked") == NULL)
588			return (-1);
589	}
590
591	/* Is it a persistent connection? */
592	if (clt->clt_persist) {
593		if (kv_add(&resp->http_headers,
594		    "Connection", "keep-alive") == NULL)
595			return (-1);
596	} else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
597		return (-1);
598
599	/* Date header is mandatory and should be added as late as possible */
600	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
601	    kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
602		return (-1);
603
604	/* Write initial header (fcgi might append more) */
605	if (server_writeresponse_http(clt) == -1 ||
606	    server_bufferevent_print(clt, "\r\n") == -1 ||
607	    server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
608	    server_bufferevent_print(clt, "\r\n") == -1)
609		return (-1);
610
611	return (0);
612}
613
614int
615server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg)
616{
617	struct server_fcgi_param	*param = arg;
618	char				*val, *name, *p;
619	const char			*key;
620	int				 ret;
621
622	if (hdr->kv_flags & KV_FLAG_INVALID)
623		return (0);
624
625	/* The key might have been updated in the parent */
626	if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
627		key = hdr->kv_parent->kv_key;
628	else
629		key = hdr->kv_key;
630
631	val = hdr->kv_value;
632
633	if (strcasecmp(key, "Content-Length") == 0 ||
634	    strcasecmp(key, "Content-Type") == 0) {
635		if ((name = strdup(key)) == NULL)
636			return (-1);
637	} else {
638		if (asprintf(&name, "HTTP_%s", key) == -1)
639			return (-1);
640	}
641
642	for (p = name; *p != '\0'; p++) {
643		if (isalpha((unsigned char)*p))
644			*p = toupper((unsigned char)*p);
645		else
646			*p = '_';
647	}
648
649	ret = fcgi_add_param(param, name, val, clt);
650	free(name);
651
652	return (ret);
653}
654
655int
656server_fcgi_writechunk(struct client *clt)
657{
658	struct evbuffer *evb = clt->clt_srvevb;
659	size_t		 len;
660
661	if (clt->clt_fcgi_type == FCGI_END_REQUEST) {
662		len = 0;
663	} else
664		len = EVBUFFER_LENGTH(evb);
665
666	/* If len is 0, make sure to write the end marker only once */
667	if (len == 0 && clt->clt_fcgi_end++)
668		return (0);
669
670	if (clt->clt_fcgi_chunked) {
671		if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 ||
672		    server_bufferevent_write_chunk(clt, evb, len) == -1 ||
673		    server_bufferevent_print(clt, "\r\n") == -1)
674			return (-1);
675	} else
676		return (server_bufferevent_write_buffer(clt, evb));
677
678	return (0);
679}
680
681int
682server_fcgi_getheaders(struct client *clt)
683{
684	struct http_descriptor	*resp = clt->clt_descresp;
685	struct evbuffer		*evb = clt->clt_srvevb;
686	int			 code = 200;
687	char			*line, *key, *value;
688	const char		*errstr;
689
690	while ((line = evbuffer_getline(evb)) != NULL && *line != '\0') {
691		key = line;
692
693		if ((value = strchr(key, ':')) == NULL)
694			break;
695		if (*value == ':') {
696			*value++ = '\0';
697			value += strspn(value, " \t");
698		} else {
699			*value++ = '\0';
700		}
701
702		DPRINTF("%s: %s: %s", __func__, key, value);
703
704		if (strcasecmp("Status", key) == 0) {
705			value[strcspn(value, " \t")] = '\0';
706			code = (int)strtonum(value, 100, 600, &errstr);
707			if (errstr != NULL || server_httperror_byid(
708			    code) == NULL)
709				code = 200;
710		} else {
711			(void)kv_add(&resp->http_headers, key, value);
712		}
713		free(line);
714	}
715
716	return (code);
717}
718