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