1 /* $NetBSD: httpd.c,v 1.9 2024/02/21 22:52:28 christos 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