1/* $NetBSD: httpd.c,v 1.5 2020/05/25 20:47:20 christos Exp $ */ 2 3/* 4 * Copyright (C) 2006-2008, 2010-2012 Internet Systems Consortium, Inc. ("ISC") 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 11 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 12 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 13 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 15 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 * PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19/* Id */ 20 21/*! \file */ 22 23#include <config.h> 24 25#include <isc/buffer.h> 26#include <isc/httpd.h> 27#include <isc/mem.h> 28#include <isc/socket.h> 29#include <isc/string.h> 30#include <isc/task.h> 31#include <isc/util.h> 32 33#include <string.h> 34 35/*% 36 * TODO: 37 * 38 * o Put in better checks to make certain things are passed in correctly. 39 * This includes a magic number for externally-visible structures, 40 * checking for NULL-ness before dereferencing, etc. 41 * o Make the URL processing external functions which will fill-in a buffer 42 * structure we provide, or return an error and we will render a generic 43 * page and close the client. 44 */ 45 46#define MSHUTTINGDOWN(cm) ((cm->flags & ISC_HTTPDMGR_FLAGSHUTTINGDOWN) != 0) 47#define MSETSHUTTINGDOWN(cm) (cm->flags |= ISC_HTTPDMGR_FLAGSHUTTINGDOWN) 48 49#ifdef DEBUG_HTTPD 50#define ENTER(x) do { fprintf(stderr, "ENTER %s\n", (x)); } while (0) 51#define EXIT(x) do { fprintf(stderr, "EXIT %s\n", (x)); } while (0) 52#define NOTICE(x) do { fprintf(stderr, "NOTICE %s\n", (x)); } while (0) 53#else 54#define ENTER(x) do { } while(0) 55#define EXIT(x) do { } while(0) 56#define NOTICE(x) do { } while(0) 57#endif 58 59#define HTTP_RECVLEN 1024 60#define HTTP_SENDGROW 1024 61#define HTTP_SEND_MAXLEN 10240 62 63/*% 64 * HTTP urls. These are the URLs we manage, and the function to call to 65 * provide the data for it. We pass in the base url (so the same function 66 * can handle multiple requests), and a structure to fill in to return a 67 * result to the client. We also pass in a pointer to be filled in for 68 * the data cleanup function. 69 */ 70struct isc_httpdurl { 71 char *url; 72 isc_httpdaction_t *action; 73 void *action_arg; 74 ISC_LINK(isc_httpdurl_t) link; 75}; 76 77#define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */ 78#define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */ 79 80/*% http client */ 81struct isc_httpd { 82 isc_httpdmgr_t *mgr; /*%< our parent */ 83 ISC_LINK(isc_httpd_t) link; 84 unsigned int state; 85 isc_socket_t *sock; 86 87 /*% 88 * Received data state. 89 */ 90 char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */ 91 isc_uint32_t recvlen; /*%< length recv'd */ 92 unsigned int method; 93 char *url; 94 char *querystring; 95 char *protocol; 96 97 /* 98 * Flags on the httpd client. 99 */ 100 int flags; 101 102 /*% 103 * Transmit data state. 104 * 105 * This is the data buffer we will transmit. 106 * 107 * This free function pointer is filled in by the rendering function 108 * we call. The free function is called after the data is transmitted 109 * to the client. 110 * 111 * The bufflist is the list of buffers we are currently transmitting. 112 * The headerdata is where we render our headers to. If we run out of 113 * space when rendering a header, we will change the size of our 114 * buffer. We will not free it until we are finished, and will 115 * allocate an additional HTTP_SENDGROW bytes per header space grow. 116 * 117 * We currently use two buffers total, one for the headers (which 118 * we manage) and another for the client to fill in (which it manages, 119 * it provides the space for it, etc) -- we will pass that buffer 120 * structure back to the caller, who is responsible for managing the 121 * space it may have allocated as backing store for it. This second 122 * buffer is bodybuffer, and we only allocate the buffer itself, not 123 * the backing store. 124 */ 125 isc_bufferlist_t bufflist; 126 char *headerdata; /*%< send header buf */ 127 unsigned int headerlen; /*%< current header buffer size */ 128 isc_buffer_t headerbuffer; 129 130 const char *mimetype; 131 unsigned int retcode; 132 const char *retmsg; 133 isc_buffer_t bodybuffer; 134 isc_httpdfree_t *freecb; 135 void *freecb_arg; 136}; 137 138/*% lightweight socket manager for httpd output */ 139struct isc_httpdmgr { 140 isc_mem_t *mctx; 141 isc_socket_t *sock; /*%< listening socket */ 142 isc_task_t *task; /*%< owning task */ 143 isc_timermgr_t *timermgr; 144 145 isc_httpdclientok_t *client_ok; /*%< client validator */ 146 isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */ 147 void *cb_arg; /*%< argument for the above */ 148 149 unsigned int flags; 150 ISC_LIST(isc_httpd_t) running; /*%< running clients */ 151 152 isc_mutex_t lock; 153 154 ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */ 155 isc_httpdaction_t *render_404; 156 isc_httpdaction_t *render_500; 157}; 158 159/*% 160 * HTTP methods. 161 */ 162#define ISC_HTTPD_METHODUNKNOWN 0 163#define ISC_HTTPD_METHODGET 1 164#define ISC_HTTPD_METHODPOST 2 165 166/*% 167 * Client states. 168 * 169 * _IDLE The client is not doing anything at all. This state should 170 * only occur just after creation, and just before being 171 * destroyed. 172 * 173 * _RECV The client is waiting for data after issuing a socket recv(). 174 * 175 * _RECVDONE Data has been received, and is being processed. 176 * 177 * _SEND All data for a response has completed, and a reply was 178 * sent via a socket send() call. 179 * 180 * _SENDDONE Send is completed. 181 * 182 * Badly formatted state table: 183 * 184 * IDLE -> RECV when client has a recv() queued. 185 * 186 * RECV -> RECVDONE when recvdone event received. 187 * 188 * RECVDONE -> SEND if the data for a reply is at hand. 189 * 190 * SEND -> RECV when a senddone event was received. 191 * 192 * At any time -> RECV on error. If RECV fails, the client will 193 * self-destroy, closing the socket and freeing memory. 194 */ 195#define ISC_HTTPD_STATEIDLE 0 196#define ISC_HTTPD_STATERECV 1 197#define ISC_HTTPD_STATERECVDONE 2 198#define ISC_HTTPD_STATESEND 3 199#define ISC_HTTPD_STATESENDDONE 4 200 201#define ISC_HTTPD_ISRECV(c) ((c)->state == ISC_HTTPD_STATERECV) 202#define ISC_HTTPD_ISRECVDONE(c) ((c)->state == ISC_HTTPD_STATERECVDONE) 203#define ISC_HTTPD_ISSEND(c) ((c)->state == ISC_HTTPD_STATESEND) 204#define ISC_HTTPD_ISSENDDONE(c) ((c)->state == ISC_HTTPD_STATESENDDONE) 205 206/*% 207 * Overall magic test that means we're not idle. 208 */ 209#define ISC_HTTPD_SETRECV(c) ((c)->state = ISC_HTTPD_STATERECV) 210#define ISC_HTTPD_SETRECVDONE(c) ((c)->state = ISC_HTTPD_STATERECVDONE) 211#define ISC_HTTPD_SETSEND(c) ((c)->state = ISC_HTTPD_STATESEND) 212#define ISC_HTTPD_SETSENDDONE(c) ((c)->state = ISC_HTTPD_STATESENDDONE) 213 214static void isc_httpd_accept(isc_task_t *, isc_event_t *); 215static void isc_httpd_recvdone(isc_task_t *, isc_event_t *); 216static void isc_httpd_senddone(isc_task_t *, isc_event_t *); 217static void destroy_client(isc_httpd_t **); 218static isc_result_t process_request(isc_httpd_t *, int); 219static void httpdmgr_destroy(isc_httpdmgr_t *); 220static isc_result_t grow_headerspace(isc_httpd_t *); 221static void reset_client(isc_httpd_t *httpd); 222static isc_result_t render_404(const char *, const char *, 223 void *, 224 unsigned int *, const char **, 225 const char **, isc_buffer_t *, 226 isc_httpdfree_t **, void **); 227static isc_result_t render_500(const char *, const char *, 228 void *, 229 unsigned int *, const char **, 230 const char **, isc_buffer_t *, 231 isc_httpdfree_t **, void **); 232 233static void 234destroy_client(isc_httpd_t **httpdp) 235{ 236 isc_httpd_t *httpd = *httpdp; 237 isc_httpdmgr_t *httpdmgr = httpd->mgr; 238 239 *httpdp = NULL; 240 241 LOCK(&httpdmgr->lock); 242 243 isc_socket_detach(&httpd->sock); 244 ISC_LIST_UNLINK(httpdmgr->running, httpd, link); 245 246 if (httpd->headerlen > 0) 247 isc_mem_put(httpdmgr->mctx, httpd->headerdata, 248 httpd->headerlen); 249 250 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t)); 251 252 UNLOCK(&httpdmgr->lock); 253 254 httpdmgr_destroy(httpdmgr); 255} 256 257isc_result_t 258isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task, 259 isc_httpdclientok_t *client_ok, 260 isc_httpdondestroy_t *ondestroy, void *cb_arg, 261 isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdp) 262{ 263 isc_result_t result; 264 isc_httpdmgr_t *httpd; 265 266 REQUIRE(mctx != NULL); 267 REQUIRE(sock != NULL); 268 REQUIRE(task != NULL); 269 REQUIRE(tmgr != NULL); 270 REQUIRE(httpdp != NULL && *httpdp == NULL); 271 272 httpd = isc_mem_get(mctx, sizeof(isc_httpdmgr_t)); 273 if (httpd == NULL) 274 return (ISC_R_NOMEMORY); 275 276 result = isc_mutex_init(&httpd->lock); 277 if (result != ISC_R_SUCCESS) { 278 isc_mem_put(mctx, httpd, sizeof(isc_httpdmgr_t)); 279 return (result); 280 } 281 httpd->mctx = NULL; 282 isc_mem_attach(mctx, &httpd->mctx); 283 httpd->sock = NULL; 284 isc_socket_attach(sock, &httpd->sock); 285 httpd->task = NULL; 286 isc_task_attach(task, &httpd->task); 287 httpd->timermgr = tmgr; /* XXXMLG no attach function? */ 288 httpd->client_ok = client_ok; 289 httpd->ondestroy = ondestroy; 290 httpd->cb_arg = cb_arg; 291 292 ISC_LIST_INIT(httpd->running); 293 ISC_LIST_INIT(httpd->urls); 294 295 /* XXXMLG ignore errors on isc_socket_listen() */ 296 result = isc_socket_listen(sock, SOMAXCONN); 297 if (result != ISC_R_SUCCESS) { 298 UNEXPECTED_ERROR(__FILE__, __LINE__, 299 "isc_socket_listen() failed: %s", 300 isc_result_totext(result)); 301 goto cleanup; 302 } 303 304 (void)isc_socket_filter(sock, "httpready"); 305 306 result = isc_socket_accept(sock, task, isc_httpd_accept, httpd); 307 if (result != ISC_R_SUCCESS) 308 goto cleanup; 309 310 httpd->render_404 = render_404; 311 httpd->render_500 = render_500; 312 313 *httpdp = httpd; 314 return (ISC_R_SUCCESS); 315 316 cleanup: 317 isc_task_detach(&httpd->task); 318 isc_socket_detach(&httpd->sock); 319 isc_mem_detach(&httpd->mctx); 320 (void)isc_mutex_destroy(&httpd->lock); 321 isc_mem_put(mctx, httpd, sizeof(isc_httpdmgr_t)); 322 return (result); 323} 324 325static void 326httpdmgr_destroy(isc_httpdmgr_t *httpdmgr) 327{ 328 isc_mem_t *mctx; 329 isc_httpdurl_t *url; 330 331 ENTER("httpdmgr_destroy"); 332 333 LOCK(&httpdmgr->lock); 334 335 if (!MSHUTTINGDOWN(httpdmgr)) { 336 NOTICE("httpdmgr_destroy not shutting down yet"); 337 UNLOCK(&httpdmgr->lock); 338 return; 339 } 340 341 /* 342 * If all clients are not shut down, don't do anything yet. 343 */ 344 if (!ISC_LIST_EMPTY(httpdmgr->running)) { 345 NOTICE("httpdmgr_destroy clients still active"); 346 UNLOCK(&httpdmgr->lock); 347 return; 348 } 349 350 NOTICE("httpdmgr_destroy detaching socket, task, and timermgr"); 351 352 isc_socket_detach(&httpdmgr->sock); 353 isc_task_detach(&httpdmgr->task); 354 httpdmgr->timermgr = NULL; 355 356 /* 357 * Clear out the list of all actions we know about. Just free the 358 * memory. 359 */ 360 url = ISC_LIST_HEAD(httpdmgr->urls); 361 while (url != NULL) { 362 isc_mem_free(httpdmgr->mctx, url->url); 363 ISC_LIST_UNLINK(httpdmgr->urls, url, link); 364 isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t)); 365 url = ISC_LIST_HEAD(httpdmgr->urls); 366 } 367 368 UNLOCK(&httpdmgr->lock); 369 (void)isc_mutex_destroy(&httpdmgr->lock); 370 371 if (httpdmgr->ondestroy != NULL) 372 (httpdmgr->ondestroy)(httpdmgr->cb_arg); 373 374 mctx = httpdmgr->mctx; 375 isc_mem_putanddetach(&mctx, httpdmgr, sizeof(isc_httpdmgr_t)); 376 377 EXIT("httpdmgr_destroy"); 378} 379 380#define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen) 381#define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN) 382 383static isc_result_t 384process_request(isc_httpd_t *httpd, int length) 385{ 386 char *s; 387 char *p; 388 int delim; 389 390 ENTER("request"); 391 392 httpd->recvlen += length; 393 394 httpd->recvbuf[httpd->recvlen] = 0; 395 396 /* 397 * If we don't find a blank line in our buffer, return that we need 398 * more data. 399 */ 400 s = strstr(httpd->recvbuf, "\r\n\r\n"); 401 delim = 1; 402 if (s == NULL) { 403 s = strstr(httpd->recvbuf, "\n\n"); 404 delim = 2; 405 } 406 if (s == NULL) 407 return (ISC_R_NOTFOUND); 408 409 /* 410 * Determine if this is a POST or GET method. Any other values will 411 * cause an error to be returned. 412 */ 413 if (strncmp(httpd->recvbuf, "GET ", 4) == 0) { 414 httpd->method = ISC_HTTPD_METHODGET; 415 p = httpd->recvbuf + 4; 416 } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) { 417 httpd->method = ISC_HTTPD_METHODPOST; 418 p = httpd->recvbuf + 5; 419 } else { 420 return (ISC_R_RANGE); 421 } 422 423 /* 424 * From now on, p is the start of our buffer. 425 */ 426 427 /* 428 * Extract the URL. 429 */ 430 s = p; 431 while (LENGTHOK(s) && BUFLENOK(s) && 432 (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' ')) 433 s++; 434 if (!LENGTHOK(s)) 435 return (ISC_R_NOTFOUND); 436 if (!BUFLENOK(s)) 437 return (ISC_R_NOMEMORY); 438 *s = 0; 439 440 /* 441 * Make the URL relative. 442 */ 443 if ((strncmp(p, "http:/", 6) == 0) 444 || (strncmp(p, "https:/", 7) == 0)) { 445 /* Skip first / */ 446 while (*p != '/' && *p != 0) 447 p++; 448 if (*p == 0) 449 return (ISC_R_RANGE); 450 p++; 451 /* Skip second / */ 452 while (*p != '/' && *p != 0) 453 p++; 454 if (*p == 0) 455 return (ISC_R_RANGE); 456 p++; 457 /* Find third / */ 458 while (*p != '/' && *p != 0) 459 p++; 460 if (*p == 0) { 461 p--; 462 *p = '/'; 463 } 464 } 465 466 httpd->url = p; 467 p = s + delim; 468 s = p; 469 470 /* 471 * Now, see if there is a ? mark in the URL. If so, this is 472 * part of the query string, and we will split it from the URL. 473 */ 474 httpd->querystring = strchr(httpd->url, '?'); 475 if (httpd->querystring != NULL) { 476 *(httpd->querystring) = 0; 477 httpd->querystring++; 478 } 479 480 /* 481 * Extract the HTTP/1.X protocol. We will bounce on anything but 482 * HTTP/1.1 for now. 483 */ 484 while (LENGTHOK(s) && BUFLENOK(s) && 485 (*s != '\n' && *s != '\r' && *s != '\0')) 486 s++; 487 if (!LENGTHOK(s)) 488 return (ISC_R_NOTFOUND); 489 if (!BUFLENOK(s)) 490 return (ISC_R_NOMEMORY); 491 *s = 0; 492 if ((strncmp(p, "HTTP/1.0", 8) != 0) 493 && (strncmp(p, "HTTP/1.1", 8) != 0)) 494 return (ISC_R_RANGE); 495 httpd->protocol = p; 496 p = s + 1; 497 s = p; 498 499 if (strstr(s, "Connection: close") != NULL) 500 httpd->flags |= HTTPD_CLOSE; 501 502 if (strstr(s, "Host: ") != NULL) 503 httpd->flags |= HTTPD_FOUNDHOST; 504 505 /* 506 * Standards compliance hooks here. 507 */ 508 if (strcmp(httpd->protocol, "HTTP/1.1") == 0 509 && ((httpd->flags & HTTPD_FOUNDHOST) == 0)) 510 return (ISC_R_RANGE); 511 512 EXIT("request"); 513 514 return (ISC_R_SUCCESS); 515} 516 517static void 518isc_httpd_accept(isc_task_t *task, isc_event_t *ev) 519{ 520 isc_result_t result; 521 isc_httpdmgr_t *httpdmgr = ev->ev_arg; 522 isc_httpd_t *httpd; 523 isc_region_t r; 524 isc_socket_newconnev_t *nev = (isc_socket_newconnev_t *)ev; 525 isc_sockaddr_t peeraddr; 526 527 ENTER("accept"); 528 529 LOCK(&httpdmgr->lock); 530 if (MSHUTTINGDOWN(httpdmgr)) { 531 NOTICE("accept shutting down, goto out"); 532 goto out; 533 } 534 535 if (nev->result == ISC_R_CANCELED) { 536 NOTICE("accept canceled, goto out"); 537 goto out; 538 } 539 540 if (nev->result != ISC_R_SUCCESS) { 541 /* XXXMLG log failure */ 542 NOTICE("accept returned failure, goto requeue"); 543 goto requeue; 544 } 545 546 (void)isc_socket_getpeername(nev->newsocket, &peeraddr); 547 if (httpdmgr->client_ok != NULL && 548 !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg)) { 549 isc_socket_detach(&nev->newsocket); 550 goto requeue; 551 } 552 553 httpd = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpd_t)); 554 if (httpd == NULL) { 555 /* XXXMLG log failure */ 556 NOTICE("accept failed to allocate memory, goto requeue"); 557 isc_socket_detach(&nev->newsocket); 558 goto requeue; 559 } 560 561 httpd->mgr = httpdmgr; 562 ISC_LINK_INIT(httpd, link); 563 ISC_LIST_APPEND(httpdmgr->running, httpd, link); 564 ISC_HTTPD_SETRECV(httpd); 565 httpd->sock = nev->newsocket; 566 isc_socket_setname(httpd->sock, "httpd", NULL); 567 httpd->flags = 0; 568 569 /* 570 * Initialize the buffer for our headers. 571 */ 572 httpd->headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW); 573 if (httpd->headerdata == NULL) { 574 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t)); 575 isc_socket_detach(&nev->newsocket); 576 goto requeue; 577 } 578 httpd->headerlen = HTTP_SENDGROW; 579 isc_buffer_init(&httpd->headerbuffer, httpd->headerdata, 580 httpd->headerlen); 581 582 ISC_LIST_INIT(httpd->bufflist); 583 584 isc_buffer_initnull(&httpd->bodybuffer); 585 reset_client(httpd); 586 587 r.base = (unsigned char *)httpd->recvbuf; 588 r.length = HTTP_RECVLEN - 1; 589 result = isc_socket_recv(httpd->sock, &r, 1, task, isc_httpd_recvdone, 590 httpd); 591 /* FIXME!!! */ 592 POST(result); 593 NOTICE("accept queued recv on socket"); 594 595 requeue: 596 result = isc_socket_accept(httpdmgr->sock, task, isc_httpd_accept, 597 httpdmgr); 598 if (result != ISC_R_SUCCESS) { 599 /* XXXMLG what to do? Log failure... */ 600 NOTICE("accept could not reaccept due to failure"); 601 } 602 603 out: 604 UNLOCK(&httpdmgr->lock); 605 606 httpdmgr_destroy(httpdmgr); 607 608 isc_event_free(&ev); 609 610 EXIT("accept"); 611} 612 613static isc_result_t 614render_404(const char *url, const char *querystring, 615 void *arg, 616 unsigned int *retcode, const char **retmsg, 617 const char **mimetype, isc_buffer_t *b, 618 isc_httpdfree_t **freecb, void **freecb_args) 619{ 620 static char msg[] = "No such URL."; 621 622 UNUSED(url); 623 UNUSED(querystring); 624 UNUSED(arg); 625 626 *retcode = 404; 627 *retmsg = "No such URL"; 628 *mimetype = "text/plain"; 629 isc_buffer_reinit(b, msg, strlen(msg)); 630 isc_buffer_add(b, strlen(msg)); 631 *freecb = NULL; 632 *freecb_args = NULL; 633 634 return (ISC_R_SUCCESS); 635} 636 637static isc_result_t 638render_500(const char *url, const char *querystring, 639 void *arg, 640 unsigned int *retcode, const char **retmsg, 641 const char **mimetype, isc_buffer_t *b, 642 isc_httpdfree_t **freecb, void **freecb_args) 643{ 644 static char msg[] = "Internal server failure."; 645 646 UNUSED(url); 647 UNUSED(querystring); 648 UNUSED(arg); 649 650 *retcode = 500; 651 *retmsg = "Internal server failure"; 652 *mimetype = "text/plain"; 653 isc_buffer_reinit(b, msg, strlen(msg)); 654 isc_buffer_add(b, strlen(msg)); 655 *freecb = NULL; 656 *freecb_args = NULL; 657 658 return (ISC_R_SUCCESS); 659} 660 661static void 662isc_httpd_recvdone(isc_task_t *task, isc_event_t *ev) 663{ 664 isc_region_t r; 665 isc_result_t result; 666 isc_httpd_t *httpd = ev->ev_arg; 667 isc_socketevent_t *sev = (isc_socketevent_t *)ev; 668 isc_httpdurl_t *url; 669 isc_time_t now; 670 char datebuf[32]; /* Only need 30, but safety first */ 671 672 ENTER("recv"); 673 674 INSIST(ISC_HTTPD_ISRECV(httpd)); 675 676 if (sev->result != ISC_R_SUCCESS) { 677 NOTICE("recv destroying client"); 678 destroy_client(&httpd); 679 goto out; 680 } 681 682 result = process_request(httpd, sev->n); 683 if (result == ISC_R_NOTFOUND) { 684 if (httpd->recvlen >= HTTP_RECVLEN - 1) { 685 destroy_client(&httpd); 686 goto out; 687 } 688 r.base = (unsigned char *)httpd->recvbuf + httpd->recvlen; 689 r.length = HTTP_RECVLEN - httpd->recvlen - 1; 690 /* check return code? */ 691 (void)isc_socket_recv(httpd->sock, &r, 1, task, 692 isc_httpd_recvdone, httpd); 693 goto out; 694 } else if (result != ISC_R_SUCCESS) { 695 destroy_client(&httpd); 696 goto out; 697 } 698 699 ISC_HTTPD_SETSEND(httpd); 700 701 /* 702 * XXXMLG Call function here. Provide an add-header function 703 * which will append the common headers to a response we generate. 704 */ 705 isc_buffer_initnull(&httpd->bodybuffer); 706 isc_time_now(&now); 707 isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf)); 708 url = ISC_LIST_HEAD(httpd->mgr->urls); 709 while (url != NULL) { 710 if (strcmp(httpd->url, url->url) == 0) 711 break; 712 url = ISC_LIST_NEXT(url, link); 713 } 714 if (url == NULL) 715 result = httpd->mgr->render_404(httpd->url, httpd->querystring, 716 NULL, 717 &httpd->retcode, 718 &httpd->retmsg, 719 &httpd->mimetype, 720 &httpd->bodybuffer, 721 &httpd->freecb, 722 &httpd->freecb_arg); 723 else 724 result = url->action(httpd->url, httpd->querystring, 725 url->action_arg, 726 &httpd->retcode, &httpd->retmsg, 727 &httpd->mimetype, &httpd->bodybuffer, 728 &httpd->freecb, &httpd->freecb_arg); 729 if (result != ISC_R_SUCCESS) { 730 result = httpd->mgr->render_500(httpd->url, httpd->querystring, 731 NULL, &httpd->retcode, 732 &httpd->retmsg, 733 &httpd->mimetype, 734 &httpd->bodybuffer, 735 &httpd->freecb, 736 &httpd->freecb_arg); 737 RUNTIME_CHECK(result == ISC_R_SUCCESS); 738 } 739 740 isc_httpd_response(httpd); 741 isc_httpd_addheader(httpd, "Content-Type", httpd->mimetype); 742 isc_httpd_addheader(httpd, "Date", datebuf); 743 isc_httpd_addheader(httpd, "Expires", datebuf); 744 isc_httpd_addheader(httpd, "Last-Modified", datebuf); 745 isc_httpd_addheader(httpd, "Pragma: no-cache", NULL); 746 isc_httpd_addheader(httpd, "Cache-Control: no-cache", NULL); 747 isc_httpd_addheader(httpd, "Server: libisc", NULL); 748 isc_httpd_addheaderuint(httpd, "Content-Length", 749 isc_buffer_usedlength(&httpd->bodybuffer)); 750 isc_httpd_endheaders(httpd); /* done */ 751 752 ISC_LIST_APPEND(httpd->bufflist, &httpd->headerbuffer, link); 753 /* 754 * Link the data buffer into our send queue, should we have any data 755 * rendered into it. If no data is present, we won't do anything 756 * with the buffer. 757 */ 758 if (isc_buffer_length(&httpd->bodybuffer) > 0) 759 ISC_LIST_APPEND(httpd->bufflist, &httpd->bodybuffer, link); 760 761 /* check return code? */ 762 (void)isc_socket_sendv(httpd->sock, &httpd->bufflist, task, 763 isc_httpd_senddone, httpd); 764 765 out: 766 isc_event_free(&ev); 767 EXIT("recv"); 768} 769 770void 771isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) 772{ 773 isc_httpdmgr_t *httpdmgr; 774 isc_httpd_t *httpd; 775 httpdmgr = *httpdmgrp; 776 *httpdmgrp = NULL; 777 778 ENTER("isc_httpdmgr_shutdown"); 779 780 LOCK(&httpdmgr->lock); 781 782 MSETSHUTTINGDOWN(httpdmgr); 783 784 isc_socket_cancel(httpdmgr->sock, httpdmgr->task, ISC_SOCKCANCEL_ALL); 785 786 httpd = ISC_LIST_HEAD(httpdmgr->running); 787 while (httpd != NULL) { 788 isc_socket_cancel(httpd->sock, httpdmgr->task, 789 ISC_SOCKCANCEL_ALL); 790 httpd = ISC_LIST_NEXT(httpd, link); 791 } 792 793 UNLOCK(&httpdmgr->lock); 794 795 EXIT("isc_httpdmgr_shutdown"); 796} 797 798static isc_result_t 799grow_headerspace(isc_httpd_t *httpd) 800{ 801 char *newspace; 802 unsigned int newlen; 803 isc_region_t r; 804 805 newlen = httpd->headerlen + HTTP_SENDGROW; 806 if (newlen > HTTP_SEND_MAXLEN) 807 return (ISC_R_NOSPACE); 808 809 newspace = isc_mem_get(httpd->mgr->mctx, newlen); 810 if (newspace == NULL) 811 return (ISC_R_NOMEMORY); 812 isc_buffer_region(&httpd->headerbuffer, &r); 813 isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen); 814 815 isc_mem_put(httpd->mgr->mctx, r.base, r.length); 816 817 return (ISC_R_SUCCESS); 818} 819 820isc_result_t 821isc_httpd_response(isc_httpd_t *httpd) 822{ 823 isc_result_t result; 824 unsigned int needlen; 825 826 needlen = strlen(httpd->protocol) + 1; /* protocol + space */ 827 needlen += 3 + 1; /* room for response code, always 3 bytes */ 828 needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */ 829 830 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { 831 result = grow_headerspace(httpd); 832 if (result != ISC_R_SUCCESS) 833 return (result); 834 } 835 836 sprintf(isc_buffer_used(&httpd->headerbuffer), "%s %03d %s\r\n", 837 httpd->protocol, httpd->retcode, httpd->retmsg); 838 isc_buffer_add(&httpd->headerbuffer, needlen); 839 840 return (ISC_R_SUCCESS); 841} 842 843isc_result_t 844isc_httpd_addheader(isc_httpd_t *httpd, const char *name, 845 const char *val) 846{ 847 isc_result_t result; 848 unsigned int needlen; 849 850 needlen = strlen(name); /* name itself */ 851 if (val != NULL) 852 needlen += 2 + strlen(val); /* :<space> and val */ 853 needlen += 2; /* CRLF */ 854 855 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { 856 result = grow_headerspace(httpd); 857 if (result != ISC_R_SUCCESS) 858 return (result); 859 } 860 861 if (val != NULL) 862 sprintf(isc_buffer_used(&httpd->headerbuffer), 863 "%s: %s\r\n", name, val); 864 else 865 sprintf(isc_buffer_used(&httpd->headerbuffer), 866 "%s\r\n", name); 867 868 isc_buffer_add(&httpd->headerbuffer, needlen); 869 870 return (ISC_R_SUCCESS); 871} 872 873isc_result_t 874isc_httpd_endheaders(isc_httpd_t *httpd) 875{ 876 isc_result_t result; 877 878 while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) { 879 result = grow_headerspace(httpd); 880 if (result != ISC_R_SUCCESS) 881 return (result); 882 } 883 884 sprintf(isc_buffer_used(&httpd->headerbuffer), "\r\n"); 885 isc_buffer_add(&httpd->headerbuffer, 2); 886 887 return (ISC_R_SUCCESS); 888} 889 890isc_result_t 891isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) { 892 isc_result_t result; 893 unsigned int needlen; 894 char buf[sizeof "18446744073709551616"]; 895 896 sprintf(buf, "%d", val); 897 898 needlen = strlen(name); /* name itself */ 899 needlen += 2 + strlen(buf); /* :<space> and val */ 900 needlen += 2; /* CRLF */ 901 902 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { 903 result = grow_headerspace(httpd); 904 if (result != ISC_R_SUCCESS) 905 return (result); 906 } 907 908 sprintf(isc_buffer_used(&httpd->headerbuffer), 909 "%s: %s\r\n", name, buf); 910 911 isc_buffer_add(&httpd->headerbuffer, needlen); 912 913 return (ISC_R_SUCCESS); 914} 915 916static void 917isc_httpd_senddone(isc_task_t *task, isc_event_t *ev) 918{ 919 isc_httpd_t *httpd = ev->ev_arg; 920 isc_region_t r; 921 isc_socketevent_t *sev = (isc_socketevent_t *)ev; 922 923 ENTER("senddone"); 924 INSIST(ISC_HTTPD_ISSEND(httpd)); 925 926 /* 927 * First, unlink our header buffer from the socket's bufflist. This 928 * is sort of an evil hack, since we know our buffer will be there, 929 * and we know it's address, so we can just remove it directly. 930 */ 931 NOTICE("senddone unlinked header"); 932 ISC_LIST_UNLINK(sev->bufferlist, &httpd->headerbuffer, link); 933 934 /* 935 * We will always want to clean up our receive buffer, even if we 936 * got an error on send or we are shutting down. 937 * 938 * We will pass in the buffer only if there is data in it. If 939 * there is no data, we will pass in a NULL. 940 */ 941 if (httpd->freecb != NULL) { 942 isc_buffer_t *b = NULL; 943 if (isc_buffer_length(&httpd->bodybuffer) > 0) 944 b = &httpd->bodybuffer; 945 httpd->freecb(b, httpd->freecb_arg); 946 NOTICE("senddone free callback performed"); 947 } 948 if (ISC_LINK_LINKED(&httpd->bodybuffer, link)) { 949 ISC_LIST_UNLINK(sev->bufferlist, &httpd->bodybuffer, link); 950 NOTICE("senddone body buffer unlinked"); 951 } 952 953 if (sev->result != ISC_R_SUCCESS) { 954 destroy_client(&httpd); 955 goto out; 956 } 957 958 if ((httpd->flags & HTTPD_CLOSE) != 0) { 959 destroy_client(&httpd); 960 goto out; 961 } 962 963 ISC_HTTPD_SETRECV(httpd); 964 965 NOTICE("senddone restarting recv on socket"); 966 967 reset_client(httpd); 968 969 r.base = (unsigned char *)httpd->recvbuf; 970 r.length = HTTP_RECVLEN - 1; 971 /* check return code? */ 972 (void)isc_socket_recv(httpd->sock, &r, 1, task, 973 isc_httpd_recvdone, httpd); 974 975out: 976 isc_event_free(&ev); 977 EXIT("senddone"); 978} 979 980static void 981reset_client(isc_httpd_t *httpd) 982{ 983 /* 984 * Catch errors here. We MUST be in RECV mode, and we MUST NOT have 985 * any outstanding buffers. If we have buffers, we have a leak. 986 */ 987 INSIST(ISC_HTTPD_ISRECV(httpd)); 988 INSIST(!ISC_LINK_LINKED(&httpd->headerbuffer, link)); 989 INSIST(!ISC_LINK_LINKED(&httpd->bodybuffer, link)); 990 991 httpd->recvbuf[0] = 0; 992 httpd->recvlen = 0; 993 httpd->method = ISC_HTTPD_METHODUNKNOWN; 994 httpd->url = NULL; 995 httpd->querystring = NULL; 996 httpd->protocol = NULL; 997 httpd->flags = 0; 998 999 isc_buffer_clear(&httpd->headerbuffer); 1000 isc_buffer_invalidate(&httpd->bodybuffer); 1001} 1002 1003isc_result_t 1004isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, 1005 isc_httpdaction_t *func, void *arg) 1006{ 1007 isc_httpdurl_t *item; 1008 1009 if (url == NULL) { 1010 httpdmgr->render_404 = func; 1011 return (ISC_R_SUCCESS); 1012 } 1013 1014 item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t)); 1015 if (item == NULL) 1016 return (ISC_R_NOMEMORY); 1017 1018 item->url = isc_mem_strdup(httpdmgr->mctx, url); 1019 if (item->url == NULL) { 1020 isc_mem_put(httpdmgr->mctx, item, sizeof(isc_httpdurl_t)); 1021 return (ISC_R_NOMEMORY); 1022 } 1023 1024 item->action = func; 1025 item->action_arg = arg; 1026 ISC_LINK_INIT(item, link); 1027 ISC_LIST_APPEND(httpdmgr->urls, item, link); 1028 1029 return (ISC_R_SUCCESS); 1030} 1031