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