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