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