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