httpd.c revision 1.3.4.1 1 /* $NetBSD: httpd.c,v 1.3.4.1 2024/02/29 12:35:00 martin Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16 /*! \file */
17
18 #include <ctype.h>
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <string.h>
22
23 #include <isc/buffer.h>
24 #include <isc/httpd.h>
25 #include <isc/mem.h>
26 #include <isc/netmgr.h>
27 #include <isc/print.h>
28 #include <isc/refcount.h>
29 #include <isc/sockaddr.h>
30 #include <isc/string.h>
31 #include <isc/time.h>
32 #include <isc/url.h>
33 #include <isc/util.h>
34
35 #include "netmgr/netmgr-int.h"
36 #include "picohttpparser.h"
37
38 #ifdef HAVE_ZLIB
39 #include <zlib.h>
40 #endif /* ifdef HAVE_ZLIB */
41
42 #define CHECK(m) \
43 do { \
44 result = (m); \
45 if (result != ISC_R_SUCCESS) { \
46 goto cleanup; \
47 } \
48 } while (0)
49
50 /*
51 * Size the recv buffer to hold at maximum two full buffers from isc_nm_read(),
52 * so we don't have to handle the truncation.
53 */
54 #define HTTP_RECVLEN ISC_NETMGR_TCP_RECVBUF_SIZE * 2
55 #define HTTP_SENDLEN ISC_NETMGR_TCP_RECVBUF_SIZE
56 #define HTTP_HEADERS_NUM 100
57 #define HTTP_MAX_REQUEST_LEN 4096
58
59 typedef enum httpd_flags {
60 CONNECTION_CLOSE = 1 << 0, /* connection must close */
61 CONNECTION_KEEP_ALIVE = 1 << 1, /* response needs a keep-alive header */
62 ACCEPT_DEFLATE = 1 << 2, /* response can be compressed */
63 } httpd_flags_t;
64
65 #define HTTPD_MAGIC ISC_MAGIC('H', 't', 'p', 'd')
66 #define VALID_HTTPD(m) ISC_MAGIC_VALID(m, HTTPD_MAGIC)
67
68 #define HTTPDMGR_MAGIC ISC_MAGIC('H', 'p', 'd', 'm')
69 #define VALID_HTTPDMGR(m) ISC_MAGIC_VALID(m, HTTPDMGR_MAGIC)
70
71 /*%
72 * HTTP methods.
73 */
74 typedef enum { METHOD_UNKNOWN = 0, METHOD_GET = 1, METHOD_POST = 2 } method_t;
75
76 /*%
77 * HTTP urls. These are the URLs we manage, and the function to call to
78 * provide the data for it. We pass in the base url (so the same function
79 * can handle multiple requests), and a structure to fill in to return a
80 * result to the client. We also pass in a pointer to be filled in for
81 * the data cleanup function.
82 */
83 struct isc_httpdurl {
84 char *url;
85 isc_httpdaction_t *action;
86 void *action_arg;
87 bool isstatic;
88 isc_time_t loadtime;
89 ISC_LINK(isc_httpdurl_t) link;
90 };
91
92 /*% http client */
93 struct isc_httpd {
94 unsigned int magic; /* HTTPD_MAGIC */
95
96 isc_httpdmgr_t *mgr; /*%< our parent */
97 ISC_LINK(isc_httpd_t) link;
98
99 isc_nmhandle_t *handle; /* Permanent pointer to handle */
100 isc_nmhandle_t *readhandle; /* Waiting for a read callback */
101
102 /*%
103 * Received data state.
104 */
105 char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
106 size_t recvlen; /*%< length recv'd */
107 size_t consume; /*%< length of last command */
108
109 method_t method;
110 int minor_version;
111 httpd_flags_t flags;
112 const char *path;
113 isc_url_parser_t up;
114 isc_time_t if_modified_since;
115 };
116
117 struct isc_httpdmgr {
118 unsigned int magic; /* HTTPDMGR_MAGIC */
119 isc_refcount_t references;
120 isc_mem_t *mctx;
121 isc_nmsocket_t *sock;
122
123 isc_httpdclientok_t *client_ok; /*%< client validator */
124 isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */
125 void *cb_arg; /*%< argument for the above */
126
127 unsigned int flags;
128 ISC_LIST(isc_httpd_t) running; /*%< running clients */
129
130 isc_mutex_t lock;
131
132 ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */
133 isc_httpdaction_t *render_404;
134 isc_httpdaction_t *render_500;
135 };
136
137 typedef struct isc_httpd_sendreq {
138 isc_mem_t *mctx;
139 isc_httpd_t *httpd;
140 isc_nmhandle_t *handle;
141
142 /*%
143 * Transmit data state.
144 *
145 * This is the data buffer we will transmit.
146 *
147 * This free function pointer is filled in by the rendering function
148 * we call. The free function is called after the data is transmitted
149 * to the client.
150 *
151 * We currently use three buffers total:
152 *
153 * sendbuffer - gets filled as we gather the data
154 *
155 * bodybuffer - for the client to fill in (which it manages, it provides
156 * the space for it, etc) -- we will pass that buffer structure back to
157 * the caller, who is responsible for managing the space it may have
158 * allocated as backing store for it. we only allocate the buffer
159 * itself, not the backing store.
160 *
161 * compbuffer - managed by us, that contains the compressed HTTP data,
162 * if compression is used.
163 */
164 isc_buffer_t *sendbuffer;
165 isc_buffer_t *compbuffer;
166
167 isc_buffer_t bodybuffer;
168
169 const char *mimetype;
170 unsigned int retcode;
171 const char *retmsg;
172 isc_httpdfree_t *freecb;
173 void *freecb_arg;
174
175 } isc_httpd_sendreq_t;
176
177 static isc_result_t
178 httpd_newconn(isc_nmhandle_t *, isc_result_t, void *);
179 static void
180 httpd_request(isc_nmhandle_t *, isc_result_t, isc_region_t *, void *);
181 static void
182 httpd_senddone(isc_nmhandle_t *, isc_result_t, void *);
183 static void
184 httpd_reset(void *);
185 static void
186 httpd_put(void *);
187
188 static void
189 httpd_addheader(isc_httpd_sendreq_t *, const char *, const char *);
190 static void
191 httpd_addheaderuint(isc_httpd_sendreq_t *, const char *, int);
192 static void
193 httpd_endheaders(isc_httpd_sendreq_t *);
194 static void
195 httpd_response(isc_httpd_t *, isc_httpd_sendreq_t *);
196
197 static isc_result_t
198 process_request(isc_httpd_t *, size_t);
199
200 static isc_httpdaction_t render_404;
201 static isc_httpdaction_t render_500;
202
203 #if ENABLE_AFL
204 static void (*finishhook)(void) = NULL;
205 #endif /* ENABLE_AFL */
206
207 static void
208 destroy_httpdmgr(isc_httpdmgr_t *);
209
210 static void
211 httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **);
212 static void
213 httpdmgr_detach(isc_httpdmgr_t **);
214
215 isc_result_t
216 isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr,
217 isc_httpdclientok_t *client_ok,
218 isc_httpdondestroy_t *ondestroy, void *cb_arg,
219 isc_httpdmgr_t **httpdmgrp) {
220 isc_result_t result;
221 isc_httpdmgr_t *httpdmgr = NULL;
222
223 REQUIRE(nm != NULL);
224 REQUIRE(mctx != NULL);
225 REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
226
227 httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
228 *httpdmgr = (isc_httpdmgr_t){ .client_ok = client_ok,
229 .ondestroy = ondestroy,
230 .cb_arg = cb_arg,
231 .render_404 = render_404,
232 .render_500 = render_500 };
233
234 isc_mutex_init(&httpdmgr->lock);
235 isc_mem_attach(mctx, &httpdmgr->mctx);
236
237 ISC_LIST_INIT(httpdmgr->running);
238 ISC_LIST_INIT(httpdmgr->urls);
239
240 isc_refcount_init(&httpdmgr->references, 1);
241
242 CHECK(isc_nm_listentcp(nm, addr, httpd_newconn, httpdmgr,
243 sizeof(isc_httpd_t), 5, NULL, &httpdmgr->sock));
244
245 httpdmgr->magic = HTTPDMGR_MAGIC;
246 *httpdmgrp = httpdmgr;
247
248 return (ISC_R_SUCCESS);
249
250 cleanup:
251 httpdmgr->magic = 0;
252 isc_refcount_decrementz(&httpdmgr->references);
253 isc_refcount_destroy(&httpdmgr->references);
254 isc_mem_detach(&httpdmgr->mctx);
255 isc_mutex_destroy(&httpdmgr->lock);
256 isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
257
258 return (result);
259 }
260
261 static void
262 httpdmgr_attach(isc_httpdmgr_t *source, isc_httpdmgr_t **targetp) {
263 REQUIRE(VALID_HTTPDMGR(source));
264 REQUIRE(targetp != NULL && *targetp == NULL);
265
266 isc_refcount_increment(&source->references);
267
268 *targetp = source;
269 }
270
271 static void
272 httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) {
273 isc_httpdmgr_t *httpdmgr = NULL;
274
275 REQUIRE(httpdmgrp != NULL);
276 REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
277
278 httpdmgr = *httpdmgrp;
279 *httpdmgrp = NULL;
280
281 if (isc_refcount_decrement(&httpdmgr->references) == 1) {
282 destroy_httpdmgr(httpdmgr);
283 }
284 }
285
286 static void
287 destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) {
288 isc_httpdurl_t *url;
289
290 isc_refcount_destroy(&httpdmgr->references);
291
292 LOCK(&httpdmgr->lock);
293
294 REQUIRE((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0);
295 REQUIRE(ISC_LIST_EMPTY(httpdmgr->running));
296
297 httpdmgr->magic = 0;
298
299 if (httpdmgr->sock != NULL) {
300 isc_nmsocket_close(&httpdmgr->sock);
301 }
302
303 /*
304 * Clear out the list of all actions we know about. Just free the
305 * memory.
306 */
307 url = ISC_LIST_HEAD(httpdmgr->urls);
308 while (url != NULL) {
309 isc_mem_free(httpdmgr->mctx, url->url);
310 ISC_LIST_UNLINK(httpdmgr->urls, url, link);
311 isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t));
312 url = ISC_LIST_HEAD(httpdmgr->urls);
313 }
314
315 UNLOCK(&httpdmgr->lock);
316 isc_mutex_destroy(&httpdmgr->lock);
317
318 if (httpdmgr->ondestroy != NULL) {
319 (httpdmgr->ondestroy)(httpdmgr->cb_arg);
320 }
321 isc_mem_putanddetach(&httpdmgr->mctx, httpdmgr, sizeof(isc_httpdmgr_t));
322 }
323
324 static bool
325 name_match(const struct phr_header *header, const char *match) {
326 size_t match_len = strlen(match);
327 if (match_len != header->name_len) {
328 return (false);
329 }
330 return (strncasecmp(header->name, match, match_len) == 0);
331 }
332
333 static bool
334 value_match(const struct phr_header *header, const char *match) {
335 size_t match_len = strlen(match);
336 size_t limit;
337
338 if (match_len > header->value_len) {
339 return (false);
340 }
341
342 limit = header->value_len - match_len + 1;
343
344 for (size_t i = 0; i < limit; i++) {
345 if (isspace((unsigned char)header->value[i])) {
346 while (i < limit &&
347 isspace((unsigned char)header->value[i]))
348 {
349 i++;
350 }
351 continue;
352 }
353
354 if (strncasecmp(&header->value[i], match, match_len) == 0) {
355 i += match_len;
356 /*
357 * Sanity check; f.e. for 'deflate' match only
358 * 'deflate[,;]', but not 'deflateyou'
359 */
360 if (i == header->value_len || header->value[i] == ',' ||
361 header->value[i] == ';')
362 {
363 return (true);
364 }
365 }
366
367 while (i < limit && header->value[i] != ',') {
368 i++;
369 }
370 }
371 return (false);
372 }
373
374 static isc_result_t
375 process_request(isc_httpd_t *httpd, size_t last_len) {
376 int pret;
377 const char *method = NULL;
378 size_t method_len = 0;
379 const char *path;
380 size_t path_len = 0;
381 struct phr_header headers[HTTP_HEADERS_NUM];
382 size_t num_headers;
383 isc_result_t result;
384
385 num_headers = ARRAY_SIZE(headers);
386
387 pret = phr_parse_request(httpd->recvbuf, httpd->recvlen, &method,
388 &method_len, &path, &path_len,
389 &httpd->minor_version, headers, &num_headers,
390 last_len);
391
392 if (pret == -1) {
393 /* Parse Error */
394 return (ISC_R_UNEXPECTED);
395 } else if (pret == -2) {
396 /* Need more data */
397 return (ISC_R_NOMORE);
398 }
399
400 INSIST(pret > 0);
401
402 if (pret > HTTP_MAX_REQUEST_LEN) {
403 return (ISC_R_RANGE);
404 }
405
406 httpd->consume = pret;
407
408 /*
409 * Determine if this is a POST or GET method. Any other values will
410 * cause an error to be returned.
411 */
412 if (strncmp(method, "GET ", method_len) == 0) {
413 httpd->method = METHOD_GET;
414 } else if (strncmp(method, "POST ", method_len) == 0) {
415 httpd->method = METHOD_POST;
416 } else {
417 return (ISC_R_RANGE);
418 }
419
420 /*
421 * Parse the URL
422 */
423 result = isc_url_parse(path, path_len, 0, &httpd->up);
424 if (result != ISC_R_SUCCESS) {
425 return (result);
426 }
427 httpd->path = path;
428
429 /*
430 * Examine headers that can affect this request's response
431 */
432 httpd->flags = 0;
433
434 size_t content_len = 0;
435 bool keep_alive = false;
436 bool host_header = false;
437
438 isc_time_set(&httpd->if_modified_since, 0, 0);
439
440 for (size_t i = 0; i < num_headers; i++) {
441 struct phr_header *header = &headers[i];
442
443 if (name_match(header, "Content-Length")) {
444 char *endptr;
445 long val = strtol(header->value, &endptr, 10);
446
447 errno = 0;
448
449 /* ensure we consumed all digits */
450 if ((header->value + header->value_len) != endptr) {
451 return (ISC_R_BADNUMBER);
452 }
453 /* ensure there was no minus sign */
454 if (val < 0) {
455 return (ISC_R_BADNUMBER);
456 }
457 /* ensure it did not overflow */
458 if (errno != 0) {
459 return (ISC_R_RANGE);
460 }
461 content_len = val;
462 } else if (name_match(header, "Connection")) {
463 /* multiple fields in a connection header are allowed */
464 if (value_match(header, "close")) {
465 httpd->flags |= CONNECTION_CLOSE;
466 }
467 if (value_match(header, "keep-alive")) {
468 keep_alive = true;
469 }
470 } else if (name_match(header, "Host")) {
471 host_header = true;
472 } else if (name_match(header, "Accept-Encoding")) {
473 if (value_match(header, "deflate")) {
474 httpd->flags |= ACCEPT_DEFLATE;
475 }
476 } else if (name_match(header, "If-Modified-Since") &&
477 header->value_len < ISC_FORMATHTTPTIMESTAMP_SIZE)
478 {
479 char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1];
480 memmove(timestamp, header->value, header->value_len);
481 timestamp[header->value_len] = 0;
482
483 /* Ignore the value if it can't be parsed */
484 (void)isc_time_parsehttptimestamp(
485 timestamp, &httpd->if_modified_since);
486 }
487 }
488
489 /*
490 * The Content-Length is optional in an HTTP request.
491 * For a GET the length must be zero.
492 */
493 if (httpd->method == METHOD_GET && content_len != 0) {
494 return (ISC_R_BADNUMBER);
495 }
496
497 if (content_len >= HTTP_MAX_REQUEST_LEN) {
498 return (ISC_R_RANGE);
499 }
500
501 size_t consume = httpd->consume + content_len;
502 if (consume > httpd->recvlen) {
503 /* The request data isn't complete yet. */
504 return (ISC_R_NOMORE);
505 }
506
507 /* Consume the request's data, which we do not use. */
508 httpd->consume = consume;
509
510 switch (httpd->minor_version) {
511 case 0:
512 /*
513 * RFC 9112 section 9.3 says close takes priority if
514 * keep-alive is also present
515 */
516 if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) {
517 httpd->flags |= CONNECTION_KEEP_ALIVE;
518 } else {
519 httpd->flags |= CONNECTION_CLOSE;
520 }
521 break;
522 case 1:
523 if (!host_header) {
524 return (ISC_R_RANGE);
525 }
526 break;
527 default:
528 return (ISC_R_UNEXPECTED);
529 }
530
531 /*
532 * Looks like a a valid request, so now we know we won't have
533 * to process this buffer again. We can NULL-terminate the
534 * URL for the caller's benefit, and set recvlen to 0 so
535 * the next read will overwrite this one instead of appending
536 * to the buffer.
537 */
538
539 return (ISC_R_SUCCESS);
540 }
541
542 static void
543 httpd_reset(void *arg) {
544 isc_httpd_t *httpd = (isc_httpd_t *)arg;
545 isc_httpdmgr_t *httpdmgr = NULL;
546
547 REQUIRE(VALID_HTTPD(httpd));
548
549 httpdmgr = httpd->mgr;
550
551 REQUIRE(VALID_HTTPDMGR(httpdmgr));
552
553 LOCK(&httpdmgr->lock);
554 ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
555 UNLOCK(&httpdmgr->lock);
556
557 httpd->recvbuf[0] = 0;
558 httpd->recvlen = 0;
559 httpd->consume = 0;
560 httpd->method = METHOD_UNKNOWN;
561 httpd->flags = 0;
562
563 httpd->minor_version = -1;
564 httpd->path = NULL;
565 httpd->up = (isc_url_parser_t){ 0 };
566 isc_time_set(&httpd->if_modified_since, 0, 0);
567 }
568
569 static void
570 isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) {
571 /* Clean up buffers */
572
573 isc_buffer_free(&req->sendbuffer);
574
575 isc_mem_putanddetach(&req->mctx, req, sizeof(*req));
576 }
577
578 static isc_httpd_sendreq_t *
579 isc__httpd_sendreq_new(isc_httpd_t *httpd) {
580 isc_httpdmgr_t *httpdmgr = httpd->mgr;
581 isc_httpd_sendreq_t *req;
582
583 REQUIRE(VALID_HTTPDMGR(httpdmgr));
584
585 req = isc_mem_get(httpdmgr->mctx, sizeof(*req));
586 *req = (isc_httpd_sendreq_t){ 0 };
587
588 isc_mem_attach(httpdmgr->mctx, &req->mctx);
589
590 /*
591 * Initialize the buffer for our headers.
592 */
593 isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN);
594 isc_buffer_clear(req->sendbuffer);
595 isc_buffer_setautorealloc(req->sendbuffer, true);
596
597 isc_buffer_initnull(&req->bodybuffer);
598
599 return (req);
600 }
601
602 static void
603 httpd_put(void *arg) {
604 isc_httpd_t *httpd = (isc_httpd_t *)arg;
605 isc_httpdmgr_t *mgr = NULL;
606
607 REQUIRE(VALID_HTTPD(httpd));
608
609 mgr = httpd->mgr;
610 REQUIRE(VALID_HTTPDMGR(mgr));
611
612 httpd->magic = 0;
613 httpd->mgr = NULL;
614
615 isc_mem_put(mgr->mctx, httpd, sizeof(*httpd));
616
617 httpdmgr_detach(&mgr);
618
619 #if ENABLE_AFL
620 if (finishhook != NULL) {
621 finishhook();
622 }
623 #endif /* ENABLE_AFL */
624 }
625
626 static void
627 new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) {
628 isc_httpd_t *httpd = NULL;
629
630 REQUIRE(VALID_HTTPDMGR(httpdmgr));
631
632 httpd = isc_nmhandle_getdata(handle);
633 if (httpd == NULL) {
634 httpd = isc_mem_get(httpdmgr->mctx, sizeof(*httpd));
635 *httpd = (isc_httpd_t){ .handle = NULL };
636 httpdmgr_attach(httpdmgr, &httpd->mgr);
637 }
638
639 if (httpd->handle == NULL) {
640 isc_nmhandle_setdata(handle, httpd, httpd_reset, httpd_put);
641 httpd->handle = handle;
642 } else {
643 INSIST(httpd->handle == handle);
644 }
645
646 ISC_LINK_INIT(httpd, link);
647
648 httpd->magic = HTTPD_MAGIC;
649
650 LOCK(&httpdmgr->lock);
651 ISC_LIST_APPEND(httpdmgr->running, httpd, link);
652 UNLOCK(&httpdmgr->lock);
653
654 isc_nmhandle_attach(httpd->handle, &httpd->readhandle);
655 isc_nm_read(handle, httpd_request, httpdmgr);
656 }
657
658 static isc_result_t
659 httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
660 isc_httpdmgr_t *httpdmgr = (isc_httpdmgr_t *)arg;
661 isc_sockaddr_t peeraddr;
662
663 REQUIRE(VALID_HTTPDMGR(httpdmgr));
664
665 if ((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) {
666 return (ISC_R_CANCELED);
667 } else if (result == ISC_R_CANCELED) {
668 isc_httpdmgr_shutdown(&httpdmgr);
669 return (result);
670 } else if (result != ISC_R_SUCCESS) {
671 return (result);
672 }
673
674 peeraddr = isc_nmhandle_peeraddr(handle);
675 if (httpdmgr->client_ok != NULL &&
676 !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg))
677 {
678 return (ISC_R_FAILURE);
679 }
680
681 new_httpd(httpdmgr, handle);
682
683 return (ISC_R_SUCCESS);
684 }
685
686 static isc_result_t
687 render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
688 unsigned int *retcode, const char **retmsg, const char **mimetype,
689 isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
690 static char msg[] = "No such URL.\r\n";
691
692 UNUSED(httpd);
693 UNUSED(urlinfo);
694 UNUSED(arg);
695
696 *retcode = 404;
697 *retmsg = "No such URL";
698 *mimetype = "text/plain";
699 isc_buffer_reinit(b, msg, strlen(msg));
700 isc_buffer_add(b, strlen(msg));
701 *freecb = NULL;
702 *freecb_args = NULL;
703
704 return (ISC_R_SUCCESS);
705 }
706
707 static isc_result_t
708 render_500(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
709 unsigned int *retcode, const char **retmsg, const char **mimetype,
710 isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
711 static char msg[] = "Internal server failure.\r\n";
712
713 UNUSED(httpd);
714 UNUSED(urlinfo);
715 UNUSED(arg);
716
717 *retcode = 500;
718 *retmsg = "Internal server failure";
719 *mimetype = "text/plain";
720 isc_buffer_reinit(b, msg, strlen(msg));
721 isc_buffer_add(b, strlen(msg));
722 *freecb = NULL;
723 *freecb_args = NULL;
724
725 return (ISC_R_SUCCESS);
726 }
727
728 #ifdef HAVE_ZLIB
729 /*%<
730 * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
731 * if necessary.
732 *
733 * Requires:
734 *\li httpd a valid isc_httpd_t object
735 *
736 * Returns:
737 *\li #ISC_R_SUCCESS -- all is well.
738 *\li #ISC_R_NOMEMORY -- not enough memory to compress data
739 *\li #ISC_R_FAILURE -- error during compression or compressed
740 * data would be larger than input data
741 */
742 static isc_result_t
743 httpd_compress(isc_httpd_sendreq_t *req) {
744 z_stream zstr;
745 int ret, inputlen;
746
747 /*
748 * We're setting output buffer size to input size so it fails if the
749 * compressed data size would be bigger than the input size.
750 */
751 inputlen = isc_buffer_usedlength(&req->bodybuffer);
752 if (inputlen == 0) {
753 return (ISC_R_FAILURE);
754 }
755
756 isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen);
757 isc_buffer_clear(req->compbuffer);
758
759 zstr = (z_stream){
760 .total_in = inputlen,
761 .avail_out = inputlen,
762 .avail_in = inputlen,
763 .next_in = isc_buffer_base(&req->bodybuffer),
764 .next_out = isc_buffer_base(req->compbuffer),
765 };
766
767 ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
768 if (ret == Z_OK) {
769 ret = deflate(&zstr, Z_FINISH);
770 }
771 deflateEnd(&zstr);
772 if (ret == Z_STREAM_END) {
773 isc_buffer_add(req->compbuffer, zstr.total_out);
774 return (ISC_R_SUCCESS);
775 } else {
776 isc_buffer_free(&req->compbuffer);
777 return (ISC_R_FAILURE);
778 }
779 }
780 #endif /* ifdef HAVE_ZLIB */
781
782 static void
783 prepare_response(isc_httpdmgr_t *mgr, isc_httpd_t *httpd,
784 isc_httpd_sendreq_t **reqp) {
785 isc_httpd_sendreq_t *req = NULL;
786 isc_time_t now;
787 char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
788 const char *path = "/";
789 size_t path_len = 1;
790 bool is_compressed = false;
791 isc_httpdurl_t *url = NULL;
792 isc_result_t result;
793
794 REQUIRE(VALID_HTTPD(httpd));
795 REQUIRE(reqp != NULL && *reqp == NULL);
796
797 isc_time_now(&now);
798 isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
799
800 if (httpd->up.field_set & (1 << ISC_UF_PATH)) {
801 path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off];
802 path_len = httpd->up.field_data[ISC_UF_PATH].len;
803 }
804
805 LOCK(&mgr->lock);
806 url = ISC_LIST_HEAD(mgr->urls);
807 while (url != NULL) {
808 if (strncmp(path, url->url, path_len) == 0) {
809 break;
810 }
811 url = ISC_LIST_NEXT(url, link);
812 }
813 UNLOCK(&mgr->lock);
814
815 req = isc__httpd_sendreq_new(httpd);
816
817 if (url == NULL) {
818 result = mgr->render_404(httpd, NULL, NULL, &req->retcode,
819 &req->retmsg, &req->mimetype,
820 &req->bodybuffer, &req->freecb,
821 &req->freecb_arg);
822 } else {
823 result = url->action(httpd, url, url->action_arg, &req->retcode,
824 &req->retmsg, &req->mimetype,
825 &req->bodybuffer, &req->freecb,
826 &req->freecb_arg);
827 }
828 if (result != ISC_R_SUCCESS) {
829 result = mgr->render_500(httpd, url, NULL, &req->retcode,
830 &req->retmsg, &req->mimetype,
831 &req->bodybuffer, &req->freecb,
832 &req->freecb_arg);
833 RUNTIME_CHECK(result == ISC_R_SUCCESS);
834 }
835
836 #ifdef HAVE_ZLIB
837 if ((httpd->flags & ACCEPT_DEFLATE) != 0) {
838 result = httpd_compress(req);
839 if (result == ISC_R_SUCCESS) {
840 is_compressed = true;
841 }
842 }
843 #endif /* ifdef HAVE_ZLIB */
844
845 httpd_response(httpd, req);
846 /* RFC 9112 9.6: SHOULD send Connection: close in last response */
847 if ((httpd->flags & CONNECTION_CLOSE) != 0) {
848 httpd_addheader(req, "Connection", "close");
849 } else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) {
850 httpd_addheader(req, "Connection", "Keep-Alive");
851 }
852 httpd_addheader(req, "Content-Type", req->mimetype);
853 httpd_addheader(req, "Date", datebuf);
854 httpd_addheader(req, "Expires", datebuf);
855
856 if (url != NULL && url->isstatic) {
857 char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
858 isc_time_formathttptimestamp(&url->loadtime, loadbuf,
859 sizeof(loadbuf));
860 httpd_addheader(req, "Last-Modified", loadbuf);
861 httpd_addheader(req, "Cache-Control: public", NULL);
862 } else {
863 httpd_addheader(req, "Last-Modified", datebuf);
864 httpd_addheader(req, "Pragma: no-cache", NULL);
865 httpd_addheader(req, "Cache-Control: no-cache", NULL);
866 }
867
868 httpd_addheader(req, "Server: libisc", NULL);
869
870 if (is_compressed) {
871 httpd_addheader(req, "Content-Encoding", "deflate");
872 httpd_addheaderuint(req, "Content-Length",
873 isc_buffer_usedlength(req->compbuffer));
874 } else {
875 httpd_addheaderuint(req, "Content-Length",
876 isc_buffer_usedlength(&req->bodybuffer));
877 }
878
879 httpd_endheaders(req); /* done */
880
881 /*
882 * Append either the compressed or the non-compressed response body to
883 * the response headers and store the result in httpd->sendbuffer.
884 */
885 if (is_compressed) {
886 isc_buffer_putmem(req->sendbuffer,
887 isc_buffer_base(req->compbuffer),
888 isc_buffer_usedlength(req->compbuffer));
889 isc_buffer_free(&req->compbuffer);
890 } else {
891 isc_buffer_putmem(req->sendbuffer,
892 isc_buffer_base(&req->bodybuffer),
893 isc_buffer_usedlength(&req->bodybuffer));
894 }
895
896 /* Free the bodybuffer */
897 if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) {
898 req->freecb(&req->bodybuffer, req->freecb_arg);
899 }
900
901 /* Consume the request from the recv buffer. */
902 INSIST(httpd->consume != 0);
903 INSIST(httpd->consume <= httpd->recvlen);
904 if (httpd->consume < httpd->recvlen) {
905 memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume,
906 httpd->recvlen - httpd->consume);
907 }
908 httpd->recvlen -= httpd->consume;
909 httpd->consume = 0;
910
911 /*
912 * We don't need to attach to httpd here because it gets only cleaned
913 * when the last handle has been detached
914 */
915 req->httpd = httpd;
916
917 *reqp = req;
918 }
919
920 static void
921 httpd_request(isc_nmhandle_t *handle, isc_result_t eresult,
922 isc_region_t *region, void *arg) {
923 isc_result_t result;
924 isc_httpdmgr_t *mgr = arg;
925 isc_httpd_t *httpd = NULL;
926 isc_httpd_sendreq_t *req = NULL;
927 isc_region_t r;
928 size_t last_len = 0;
929
930 httpd = isc_nmhandle_getdata(handle);
931
932 REQUIRE(VALID_HTTPD(httpd));
933
934 REQUIRE(httpd->handle == handle);
935
936 if (httpd->readhandle == NULL) {
937 /* The channel has been already closed, just bail out */
938 return;
939 }
940
941 if (eresult != ISC_R_SUCCESS) {
942 goto close_readhandle;
943 }
944
945 REQUIRE(httpd->readhandle == handle);
946
947 isc_nm_pauseread(httpd->readhandle);
948
949 /*
950 * If we are being called from httpd_senddone(), the last HTTP request
951 * was processed successfully, reset the last_len to 0, even if there's
952 * data in the httpd->recvbuf.
953 */
954 last_len = (region == NULL) ? 0 : httpd->recvlen;
955
956 /* Store the received data into the recvbuf */
957 if (region != NULL) {
958 if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) {
959 goto close_readhandle;
960 }
961
962 memmove(httpd->recvbuf + httpd->recvlen, region->base,
963 region->length);
964 httpd->recvlen += region->length;
965 }
966
967 result = process_request(httpd, last_len);
968
969 if (result == ISC_R_NOMORE) {
970 if (httpd->recvlen > HTTP_MAX_REQUEST_LEN) {
971 goto close_readhandle;
972 }
973
974 /* Wait for more data, the readhandle is still attached */
975 isc_nm_resumeread(httpd->readhandle);
976 return;
977 }
978
979 /* XXXFANF it would be more polite to reply 400 bad request */
980 if (result != ISC_R_SUCCESS) {
981 goto close_readhandle;
982 }
983
984 prepare_response(mgr, httpd, &req);
985
986 /*
987 * Determine total response size.
988 */
989 isc_buffer_usedregion(req->sendbuffer, &r);
990
991 isc_nmhandle_attach(httpd->handle, &req->handle);
992 isc_nm_send(httpd->handle, &r, httpd_senddone, req);
993 return;
994
995 close_readhandle:
996 isc_nm_pauseread(httpd->readhandle);
997 isc_nmhandle_detach(&httpd->readhandle);
998 }
999
1000 void
1001 isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
1002 isc_httpdmgr_t *httpdmgr;
1003 isc_httpd_t *httpd;
1004
1005 REQUIRE(httpdmgrp != NULL);
1006 REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
1007
1008 httpdmgr = *httpdmgrp;
1009 *httpdmgrp = NULL;
1010
1011 isc_nm_stoplistening(httpdmgr->sock);
1012
1013 LOCK(&httpdmgr->lock);
1014 httpdmgr->flags |= ISC_HTTPDMGR_SHUTTINGDOWN;
1015
1016 httpd = ISC_LIST_HEAD(httpdmgr->running);
1017 while (httpd != NULL) {
1018 isc_nm_cancelread(httpd->readhandle);
1019 httpd = ISC_LIST_NEXT(httpd, link);
1020 }
1021 UNLOCK(&httpdmgr->lock);
1022
1023 isc_nmsocket_close(&httpdmgr->sock);
1024
1025 httpdmgr_detach(&httpdmgr);
1026 }
1027
1028 static void
1029 httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) {
1030 isc_result_t result;
1031
1032 result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n",
1033 httpd->minor_version, req->retcode,
1034 req->retmsg);
1035
1036 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1037 }
1038
1039 static void
1040 httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) {
1041 isc_result_t result;
1042
1043 if (val != NULL) {
1044 result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name,
1045 val);
1046 } else {
1047 result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name);
1048 }
1049
1050 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1051 }
1052
1053 static void
1054 httpd_endheaders(isc_httpd_sendreq_t *req) {
1055 isc_result_t result;
1056
1057 result = isc_buffer_printf(req->sendbuffer, "\r\n");
1058
1059 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1060 }
1061
1062 static void
1063 httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) {
1064 isc_result_t result;
1065
1066 result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val);
1067
1068 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1069 }
1070
1071 static void
1072 httpd_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
1073 isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg;
1074 isc_httpd_t *httpd = req->httpd;
1075
1076 REQUIRE(VALID_HTTPD(httpd));
1077
1078 if (httpd->readhandle == NULL) {
1079 goto detach;
1080 }
1081
1082 if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) == 0)
1083 {
1084 /*
1085 * Calling httpd_request() with region NULL restarts
1086 * reading.
1087 */
1088 httpd_request(handle, ISC_R_SUCCESS, NULL, httpd->mgr);
1089 } else {
1090 isc_nm_cancelread(httpd->readhandle);
1091 }
1092
1093 detach:
1094 isc_nmhandle_detach(&handle);
1095
1096 isc__httpd_sendreq_free(req);
1097 }
1098
1099 isc_result_t
1100 isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
1101 isc_httpdaction_t *func, void *arg) {
1102 isc_httpdurl_t *item;
1103
1104 REQUIRE(VALID_HTTPDMGR(httpdmgr));
1105
1106 if (url == NULL) {
1107 httpdmgr->render_404 = func;
1108 return (ISC_R_SUCCESS);
1109 }
1110
1111 item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
1112
1113 item->url = isc_mem_strdup(httpdmgr->mctx, url);
1114
1115 item->action = func;
1116 item->action_arg = arg;
1117 item->isstatic = isstatic;
1118 isc_time_now(&item->loadtime);
1119
1120 ISC_LINK_INIT(item, link);
1121
1122 LOCK(&httpdmgr->lock);
1123 ISC_LIST_APPEND(httpdmgr->urls, item, link);
1124 UNLOCK(&httpdmgr->lock);
1125
1126 return (ISC_R_SUCCESS);
1127 }
1128
1129 void
1130 isc_httpd_setfinishhook(void (*fn)(void)) {
1131 #if ENABLE_AFL
1132 finishhook = fn;
1133 #else /* ENABLE_AFL */
1134 UNUSED(fn);
1135 #endif /* ENABLE_AFL */
1136 }
1137
1138 bool
1139 isc_httpdurl_isstatic(const isc_httpdurl_t *url) {
1140 return (url->isstatic);
1141 }
1142
1143 const isc_time_t *
1144 isc_httpdurl_loadtime(const isc_httpdurl_t *url) {
1145 return (&url->loadtime);
1146 }
1147
1148 const isc_time_t *
1149 isc_httpd_if_modified_since(const isc_httpd_t *httpd) {
1150 return ((const isc_time_t *)&httpd->if_modified_since);
1151 }
1152