2 * Copyright (c) 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
4 * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
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.
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.
19 #include "got_compat.h"
21 #include <arpa/inet.h>
22 #include <sys/queue.h>
23 #include <sys/socket.h>
24 #include <sys/types.h>
37 #include "got_error.h"
38 #include "got_reference.h"
44 size_t fcgi_parse_record(uint8_t *, size_t, struct request
*);
45 void fcgi_parse_begin_request(uint8_t *, uint16_t, struct request
*,
47 void fcgi_parse_params(uint8_t *, uint16_t, struct request
*, uint16_t);
48 int fcgi_send_response(struct request
*, int, const void *, size_t);
50 void dump_fcgi_record_header(const char *, struct fcgi_record_header
*);
51 void dump_fcgi_begin_request_body(const char *,
52 struct fcgi_begin_request_body
*);
53 void dump_fcgi_end_request_body(const char *,
54 struct fcgi_end_request_body
*);
56 extern int cgi_inflight
;
57 extern volatile int client_cnt
;
60 fcgi_request(int fd
, short events
, void *arg
)
62 struct request
*c
= arg
;
66 n
= read(fd
, c
->buf
+ c
->buf_pos
+ c
->buf_len
,
67 FCGI_RECORD_SIZE
- c
->buf_pos
-c
->buf_len
);
81 log_debug("closed connection");
90 * Parse the records as they are received. Per the FastCGI
91 * specification, the server need only receive the FastCGI
92 * parameter records in full; it is free to begin execution
93 * at that point, which is what happens here.
96 parsed
= fcgi_parse_record(c
->buf
+ c
->buf_pos
, c
->buf_len
, c
);
102 /* drop the parsed record */
103 if (parsed
!= 0 && c
->buf_len
> 0) {
104 bcopy(c
->buf
+ c
->buf_pos
, c
->buf
, c
->buf_len
);
107 } while (parsed
> 0 && c
->buf_len
> 0);
111 fcgi_cleanup_request(c
);
115 fcgi_parse_record(uint8_t *buf
, size_t n
, struct request
*c
)
117 struct fcgi_record_header
*h
;
119 if (n
< sizeof(struct fcgi_record_header
))
122 h
= (struct fcgi_record_header
*) buf
;
124 dump_fcgi_record("", h
);
126 if (n
< sizeof(struct fcgi_record_header
) + ntohs(h
->content_len
)
131 log_warn("wrong version");
134 case FCGI_BEGIN_REQUEST
:
135 fcgi_parse_begin_request(buf
+
136 sizeof(struct fcgi_record_header
),
137 ntohs(h
->content_len
), c
, ntohs(h
->id
));
140 fcgi_parse_params(buf
+ sizeof(struct fcgi_record_header
),
141 ntohs(h
->content_len
), c
, ntohs(h
->id
));
144 case FCGI_ABORT_REQUEST
:
145 if (c
->sock
->client_status
!= CLIENT_DISCONNECT
&&
146 c
->outbuf_len
!= 0) {
147 fcgi_send_response(c
, FCGI_STDOUT
, c
->outbuf
,
151 fcgi_create_end_record(c
);
152 fcgi_cleanup_request(c
);
155 log_warn("unimplemented type %d", h
->type
);
159 return (sizeof(struct fcgi_record_header
) + ntohs(h
->content_len
)
164 fcgi_parse_begin_request(uint8_t *buf
, uint16_t n
,
165 struct request
*c
, uint16_t id
)
167 /* XXX -- FCGI_CANT_MPX_CONN */
168 if (c
->request_started
) {
169 log_warn("unexpected FCGI_BEGIN_REQUEST, ignoring");
173 if (n
!= sizeof(struct fcgi_begin_request_body
)) {
174 log_warn("wrong size %d != %lu", n
,
175 sizeof(struct fcgi_begin_request_body
));
179 c
->request_started
= 1;
184 fcgi_parse_params(uint8_t *buf
, uint16_t n
, struct request
*c
, uint16_t id
)
186 uint32_t name_len
, val_len
;
189 if (!c
->request_started
) {
190 log_warn("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring");
195 log_warn("unexpected id, ignoring");
200 gotweb_process_request(c
);
205 if (buf
[0] >> 7 == 0) {
211 name_len
= ((buf
[0] & 0x7f) << 24) +
212 (buf
[1] << 16) + (buf
[2] << 8) + buf
[3];
222 if (buf
[0] >> 7 == 0) {
228 val_len
= ((buf
[0] & 0x7f) << 24) +
229 (buf
[1] << 16) + (buf
[2] << 8) +
237 if (n
< name_len
+ val_len
)
240 val
= buf
+ name_len
;
242 if (c
->querystring
[0] == '\0' &&
243 val_len
< MAX_QUERYSTRING
&&
245 strncmp(buf
, "QUERY_STRING", 12) == 0) {
246 memcpy(c
->querystring
, val
, val_len
);
247 c
->querystring
[val_len
] = '\0';
250 if (c
->http_host
[0] == '\0' &&
251 val_len
< GOTWEBD_MAXTEXT
&&
253 strncmp(buf
, "HTTP_HOST", 9) == 0) {
254 memcpy(c
->http_host
, val
, val_len
);
255 c
->http_host
[val_len
] = '\0';
258 * lazily get subdomain
259 * will only get domain if no subdomain exists
260 * this can still work if gotweb server name is the same
262 sd
= strchr(c
->http_host
, '.');
267 if (c
->document_uri
[0] == '\0' &&
268 val_len
< MAX_DOCUMENT_URI
&&
270 strncmp(buf
, "DOCUMENT_URI", 12) == 0) {
271 memcpy(c
->document_uri
, val
, val_len
);
272 c
->document_uri
[val_len
] = '\0';
275 if (c
->server_name
[0] == '\0' &&
276 val_len
< MAX_SERVER_NAME
&&
278 strncmp(buf
, "SERVER_NAME", 11) == 0) {
279 memcpy(c
->server_name
, val
, val_len
);
280 c
->server_name
[val_len
] = '\0';
284 strncmp(buf
, "HTTPS", 5) == 0)
287 buf
+= name_len
+ val_len
;
288 n
-= name_len
- val_len
;
293 fcgi_timeout(int fd
, short events
, void *arg
)
295 fcgi_cleanup_request((struct request
*) arg
);
299 fcgi_puts(struct template *tp
, const char *str
)
303 return fcgi_gen_binary_response(tp
->tp_arg
, str
, strlen(str
));
307 fcgi_putc(struct template *tp
, int ch
)
310 return fcgi_gen_binary_response(tp
->tp_arg
, &c
, 1);
314 fcgi_vprintf(struct request
*c
, const char *fmt
, va_list ap
)
319 r
= vasprintf(&str
, fmt
, ap
);
321 log_warn("%s: asprintf", __func__
);
325 r
= fcgi_gen_binary_response(c
, str
, r
);
331 fcgi_printf(struct request
*c
, const char *fmt
, ...)
337 r
= fcgi_vprintf(c
, fmt
, ap
);
344 fcgi_gen_binary_response(struct request
*c
, const uint8_t *data
, int len
)
348 if (c
->sock
->client_status
== CLIENT_DISCONNECT
)
351 if (data
== NULL
|| len
== 0)
355 * special case: send big replies -like blobs- directly
358 if (len
> sizeof(c
->outbuf
)) {
359 if (c
->outbuf_len
> 0) {
360 fcgi_send_response(c
, FCGI_STDOUT
,
361 c
->outbuf
, c
->outbuf_len
);
364 return fcgi_send_response(c
, FCGI_STDOUT
, data
, len
);
367 if (len
< sizeof(c
->outbuf
) - c
->outbuf_len
) {
368 memcpy(c
->outbuf
+ c
->outbuf_len
, data
, len
);
369 c
->outbuf_len
+= len
;
373 r
= fcgi_send_response(c
, FCGI_STDOUT
, c
->outbuf
, c
->outbuf_len
);
377 memcpy(c
->outbuf
, data
, len
);
383 send_response(struct request
*c
, int type
, const uint8_t *data
,
386 static const uint8_t padding
[FCGI_PADDING_SIZE
];
387 struct fcgi_record_header header
;
391 size_t padded_len
, tot
;
392 int i
, err
= 0, th
= 2000;
397 memset(&header
, 0, sizeof(header
));
400 header
.id
= htons(c
->id
);
401 header
.content_len
= htons(len
);
403 /* The FastCGI spec suggests to align the output buffer */
404 tot
= sizeof(header
) + len
;
405 padded_len
= FCGI_ALIGN(tot
);
406 if (padded_len
> tot
) {
407 header
.padding_len
= padded_len
- tot
;
408 tot
+= header
.padding_len
;
411 iov
[0].iov_base
= &header
;
412 iov
[0].iov_len
= sizeof(header
);
414 iov
[1].iov_base
= (void *)data
;
415 iov
[1].iov_len
= len
;
417 iov
[2].iov_base
= (void *)padding
;
418 iov
[2].iov_len
= header
.padding_len
;
420 dump_fcgi_record("resp ", &header
);
423 * XXX: add some simple write heuristics here
424 * On slower VMs, spotty connections, etc., we don't want to go right to
425 * disconnect. Let's at least try to write the data a few times before
429 nw
= writev(c
->fd
, iov
, nitems(iov
));
431 c
->sock
->client_status
= CLIENT_DISCONNECT
;
436 if (errno
== EAGAIN
&& err
< th
) {
437 nanosleep(&ts
, NULL
);
440 log_warn("%s: write failure", __func__
);
441 c
->sock
->client_status
= CLIENT_DISCONNECT
;
446 log_debug("%s: partial write: %zu vs %zu", __func__
,
450 for (i
= 0; i
< nitems(iov
); ++i
) {
451 if (nw
< iov
[i
].iov_len
) {
452 iov
[i
].iov_base
+= nw
;
453 iov
[i
].iov_len
-= nw
;
456 nw
-= iov
[i
].iov_len
;
465 fcgi_send_response(struct request
*c
, int type
, const void *data
,
468 if (c
->sock
->client_status
== CLIENT_DISCONNECT
)
471 while (len
> FCGI_CONTENT_SIZE
) {
472 if (send_response(c
, type
, data
, len
) == -1)
475 data
+= FCGI_CONTENT_SIZE
;
476 len
-= FCGI_CONTENT_SIZE
;
482 return send_response(c
, type
, data
, len
);
486 fcgi_create_end_record(struct request
*c
)
488 struct fcgi_end_request_body end_request
;
490 memset(&end_request
, 0, sizeof(end_request
));
491 end_request
.app_status
= htonl(0); /* script status */
492 end_request
.protocol_status
= FCGI_REQUEST_COMPLETE
;
494 fcgi_send_response(c
, FCGI_END_REQUEST
, &end_request
,
495 sizeof(end_request
));
499 fcgi_cleanup_request(struct request
*c
)
504 evtimer_del(&c
->tmo
);
505 if (event_initialized(&c
->ev
))
509 template_free(c
->tp
);
511 gotweb_free_transport(c
->t
);
516 dump_fcgi_record(const char *p
, struct fcgi_record_header
*h
)
518 dump_fcgi_record_header(p
, h
);
520 if (h
->type
== FCGI_BEGIN_REQUEST
)
521 dump_fcgi_begin_request_body(p
,
522 (struct fcgi_begin_request_body
*)(h
+ 1));
523 else if (h
->type
== FCGI_END_REQUEST
)
524 dump_fcgi_end_request_body(p
,
525 (struct fcgi_end_request_body
*)(h
+ 1));
529 dump_fcgi_record_header(const char* p
, struct fcgi_record_header
*h
)
531 log_debug("%sversion: %d", p
, h
->version
);
532 log_debug("%stype: %d", p
, h
->type
);
533 log_debug("%srequestId: %d", p
, ntohs(h
->id
));
534 log_debug("%scontentLength: %d", p
, ntohs(h
->content_len
));
535 log_debug("%spaddingLength: %d", p
, h
->padding_len
);
536 log_debug("%sreserved: %d", p
, h
->reserved
);
540 dump_fcgi_begin_request_body(const char *p
, struct fcgi_begin_request_body
*b
)
542 log_debug("%srole %d", p
, ntohs(b
->role
));
543 log_debug("%sflags %d", p
, b
->flags
);
547 dump_fcgi_end_request_body(const char *p
, struct fcgi_end_request_body
*b
)
549 log_debug("%sappStatus: %d", p
, ntohl(b
->app_status
));
550 log_debug("%sprotocolStatus: %d", p
, b
->protocol_status
);