server_fcgi.c revision 1.4
1/*	$OpenBSD: server_fcgi.c,v 1.4 2014/07/31 17:55:09 reyk 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_systm.h>
30#include <netinet/in.h>
31#include <netinet/ip.h>
32#include <netinet/tcp.h>
33#include <arpa/inet.h>
34
35#include <errno.h>
36#include <fcntl.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40#include <stdio.h>
41#include <err.h>
42#include <event.h>
43
44#include <openssl/ssl.h>
45
46#include "httpd.h"
47#include "http.h"
48
49#define FCGI_CONTENT_SIZE	 65535
50#define FCGI_PADDING_SIZE	 255
51#define FCGI_RECORD_SIZE	 \
52    (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
53
54#define FCGI_BEGIN_REQUEST	 1
55#define FCGI_ABORT_REQUEST	 2
56#define FCGI_END_REQUEST	 3
57#define FCGI_PARAMS		 4
58#define FCGI_STDIN		 5
59#define FCGI_STDOUT		 6
60#define FCGI_STDERR		 7
61#define FCGI_DATA		 8
62#define FCGI_GET_VALUES		 9
63#define FCGI_GET_VALUES_RESULT	10
64#define FCGI_UNKNOWN_TYPE	11
65#define FCGI_MAXTYPE		(FCGI_UNKNOWN_TYPE)
66
67#define FCGI_RESPONDER		 1
68
69struct fcgi_record_header {
70	uint8_t		version;
71	uint8_t		type;
72	uint16_t	id;
73	uint16_t	content_len;
74	uint8_t		padding_len;
75	uint8_t		reserved;
76} __packed;
77
78struct fcgi_begin_request_body {
79	uint16_t	role;
80	uint8_t		flags;
81	uint8_t		reserved[5];
82} __packed;
83
84int	server_fcgi_header(struct client *, u_int);
85void	server_fcgi_read(struct bufferevent *, void *);
86int	fcgi_add_param(uint8_t *, char *, char *, int);
87
88int
89server_fcgi(struct httpd *env, struct client *clt)
90{
91	struct server_config		*srv_conf = clt->clt_srv_conf;
92	struct http_descriptor		*desc	= clt->clt_desc;
93	struct sockaddr_un		 sun;
94	struct fcgi_record_header 	*h;
95	struct fcgi_begin_request_body	*begin;
96	size_t				 len, total_len;
97	int				 fd;
98	const char			*errstr = NULL;
99	uint8_t				 buf[FCGI_RECORD_SIZE];
100	uint8_t				*params;
101
102	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
103		goto fail;
104
105	bzero(&sun, sizeof(sun));
106	sun.sun_family = AF_UNIX;
107	len = strlcpy(sun.sun_path, srv_conf->path, sizeof(sun.sun_path));
108	if (len >= sizeof(sun.sun_path)) {
109		errstr = "socket path to long";
110		goto fail;
111	}
112	sun.sun_len = len;
113
114	if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
115		goto fail;
116
117	if (clt->clt_srvbev != NULL)
118		bufferevent_free(clt->clt_srvbev);
119
120	clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read,
121	    NULL, server_file_error, clt);
122	if (clt->clt_srvbev == NULL) {
123		errstr = "failed to allocate fcgi buffer event";
124		goto fail;
125	}
126
127	bzero(&buf, sizeof(buf));
128
129	h = (struct fcgi_record_header *) &buf;
130	h->version = 1;
131	h->type = FCGI_BEGIN_REQUEST;
132	h->id = htons(1);
133	h->content_len = htons(sizeof(struct fcgi_begin_request_body));
134	h->padding_len = 0;
135
136	begin = (struct fcgi_begin_request_body *) &buf[sizeof(struct
137	    fcgi_record_header)];
138	begin->role = htons(FCGI_RESPONDER);
139
140	bufferevent_write(clt->clt_srvbev, &buf,
141	    sizeof(struct fcgi_record_header) +
142	    sizeof(struct fcgi_begin_request_body));
143
144	h->type = FCGI_PARAMS;
145	h->content_len = 0;
146	params = &buf[sizeof(struct fcgi_record_header)];
147
148	total_len = 0;
149
150	len = fcgi_add_param(params, "SCRIPT_NAME", desc->http_path,
151	    FCGI_CONTENT_SIZE);
152	params += len;
153	total_len += len;
154
155	if (desc->http_query) {
156		len = fcgi_add_param(params, "QUERY_STRING", desc->http_query,
157		    FCGI_CONTENT_SIZE);
158		params += len;
159		total_len += len;
160	}
161
162	h->content_len = htons(total_len);
163
164	bufferevent_write(clt->clt_srvbev, &buf,
165	    sizeof(struct fcgi_record_header) +
166	    ntohs(h->content_len));
167
168	h->content_len = 0;
169
170	bufferevent_write(clt->clt_srvbev, &buf,
171	    sizeof(struct fcgi_record_header));
172
173	h->type = FCGI_STDIN;
174
175	bufferevent_write(clt->clt_srvbev, &buf,
176	    sizeof(struct fcgi_record_header));
177
178	bufferevent_settimeout(clt->clt_srvbev,
179	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
180	bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE);
181	bufferevent_disable(clt->clt_bev, EV_READ);
182
183	/*
184	 * persist is not supported yet because we don't get the
185	 * Content-Length from slowcgi and don't support chunked encoding.
186	 */
187	clt->clt_persist = 0;
188	clt->clt_done = 0;
189
190	return (0);
191 fail:
192	if (errstr == NULL)
193		errstr = strerror(errno);
194	server_abort_http(clt, 500, errstr);
195	return (-1);
196}
197
198int
199fcgi_add_param(uint8_t *buf, char *key, char *val, int size)
200{
201	int len = 0;
202	DPRINTF("%s: %s => %s", __func__, key, val);
203	buf[0] = strlen(key);
204	len++;
205	buf[1] = strlen(val);
206	len++;
207	len += strlcpy(buf + len, key, size - len);
208	len += strlcpy(buf + len, val, size - len);
209
210	return len;
211}
212
213void
214server_fcgi_read(struct bufferevent *bev, void *arg)
215{
216	struct client *clt = (struct client *) arg;
217	struct fcgi_record_header 	*h;
218	uint8_t	 buf[FCGI_RECORD_SIZE];
219	size_t	 len;
220
221	len = bufferevent_read(bev, &buf, FCGI_RECORD_SIZE);
222	DPRINTF("%s: %lu", __func__, len);
223
224	h = (struct fcgi_record_header *) &buf;
225	DPRINTF("%s: record header: version %d type %d id %d content len %d",
226	    __func__, h->version, h->type, ntohs(h->id),
227	    ntohs(h->content_len));
228
229	if (h->type == FCGI_STDOUT && ntohs(h->content_len) > 0) {
230		DPRINTF("%s", (char *) &buf +
231                    sizeof(struct fcgi_record_header));
232
233		server_fcgi_header(clt, 200);
234		server_bufferevent_write(clt, (char *)&buf +
235		    sizeof(struct fcgi_record_header),
236		    len - sizeof(struct fcgi_record_header));
237	}
238}
239
240int
241server_fcgi_header(struct client *clt, u_int code)
242{
243	struct http_descriptor	*desc = clt->clt_desc;
244	const char		*error;
245	char			 tmbuf[32];
246
247	if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
248		return (-1);
249
250	kv_purge(&desc->http_headers);
251
252	/* Add error codes */
253	if (kv_setkey(&desc->http_pathquery, "%lu", code) == -1 ||
254	    kv_set(&desc->http_pathquery, "%s", error) == -1)
255		return (-1);
256
257	/* Add headers */
258	if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
259		return (-1);
260
261	/* Is it a persistent connection? */
262	if (clt->clt_persist) {
263		if (kv_add(&desc->http_headers,
264		    "Connection", "keep-alive") == NULL)
265			return (-1);
266	} else if (kv_add(&desc->http_headers, "Connection", "close") == NULL)
267		return (-1);
268
269	/* Date header is mandatory and should be added last */
270	server_http_date(tmbuf, sizeof(tmbuf));
271	if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL)
272		return (-1);
273
274	/* Write initial header (fcgi might append more) */
275	if (server_writeresponse_http(clt) == -1 ||
276	    server_bufferevent_print(clt, "\r\n") == -1 ||
277	    server_writeheader_http(clt) == -1)
278		return (-1);
279
280	return (0);
281}
282