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