httpd.c revision 1.3 1 /* $NetBSD: httpd.c,v 1.3 2019/01/09 16:55:14 christos Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14
15 /*! \file */
16
17 #include <config.h>
18
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <string.h>
22
23 #include <isc/buffer.h>
24 #include <isc/httpd.h>
25 #include <isc/mem.h>
26 #include <isc/print.h>
27 #include <isc/socket.h>
28 #include <isc/string.h>
29 #include <isc/task.h>
30 #include <isc/time.h>
31 #include <isc/util.h>
32
33 #ifdef HAVE_ZLIB
34 #include <zlib.h>
35 #endif
36
37 /*%
38 * TODO:
39 *
40 * o Put in better checks to make certain things are passed in correctly.
41 * This includes a magic number for externally-visible structures,
42 * checking for NULL-ness before dereferencing, etc.
43 * o Make the URL processing external functions which will fill-in a buffer
44 * structure we provide, or return an error and we will render a generic
45 * page and close the client.
46 */
47
48 #define MSHUTTINGDOWN(cm) ((cm->flags & ISC_HTTPDMGR_FLAGSHUTTINGDOWN) != 0)
49 #define MSETSHUTTINGDOWN(cm) (cm->flags |= ISC_HTTPDMGR_FLAGSHUTTINGDOWN)
50
51 #ifdef DEBUG_HTTPD
52 #define ENTER(x) do { fprintf(stderr, "ENTER %s\n", (x)); } while (/*CONSTCOND*/0)
53 #define EXIT(x) do { fprintf(stderr, "EXIT %s\n", (x)); } while (/*CONSTCOND*/0)
54 #define NOTICE(x) do { fprintf(stderr, "NOTICE %s\n", (x)); } while (/*CONSTCOND*/0)
55 #else
56 #define ENTER(x) do { } while(/*CONSTCOND*/0)
57 #define EXIT(x) do { } while(/*CONSTCOND*/0)
58 #define NOTICE(x) do { } while(/*CONSTCOND*/0)
59 #endif
60
61 #define HTTP_RECVLEN 1024
62 #define HTTP_SENDGROW 1024
63 #define HTTP_SEND_MAXLEN 10240
64
65 #define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */
66 #define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */
67 #define HTTPD_KEEPALIVE 0x0004 /* Got a Connection: Keep-Alive */
68 #define HTTPD_ACCEPT_DEFLATE 0x0008
69
70 /*% http client */
71 struct isc_httpd {
72 isc_httpdmgr_t *mgr; /*%< our parent */
73 ISC_LINK(isc_httpd_t) link;
74 unsigned int state;
75 isc_socket_t *sock;
76
77 /*%
78 * Received data state.
79 */
80 char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
81 uint32_t recvlen; /*%< length recv'd */
82 char *headers; /*%< set in process_request() */
83 unsigned int method;
84 char *url;
85 char *querystring;
86 char *protocol;
87
88 /*
89 * Flags on the httpd client.
90 */
91 int flags;
92
93 /*%
94 * Transmit data state.
95 *
96 * This is the data buffer we will transmit.
97 *
98 * This free function pointer is filled in by the rendering function
99 * we call. The free function is called after the data is transmitted
100 * to the client.
101 *
102 * The bufflist is the list of buffers we are currently transmitting.
103 * The headerbuffer is where we render our headers to. If we run out of
104 * space when rendering a header, we will change the size of our
105 * buffer. We will not free it until we are finished, and will
106 * allocate an additional HTTP_SENDGROW bytes per header space grow.
107 *
108 * We currently use three buffers total, one for the headers (which
109 * we manage), another for the client to fill in (which it manages,
110 * it provides the space for it, etc) -- we will pass that buffer
111 * structure back to the caller, who is responsible for managing the
112 * space it may have allocated as backing store for it. This second
113 * buffer is bodybuffer, and we only allocate the buffer itself, not
114 * the backing store.
115 * The third buffer is compbuffer, managed by us, that contains the
116 * compressed HTTP data, if compression is used.
117 *
118 */
119 isc_buffer_t headerbuffer;
120 isc_buffer_t compbuffer;
121 isc_buffer_t *sendbuffer;
122
123 const char *mimetype;
124 unsigned int retcode;
125 const char *retmsg;
126 isc_buffer_t bodybuffer;
127 isc_httpdfree_t *freecb;
128 void *freecb_arg;
129 };
130
131 /*% lightweight socket manager for httpd output */
132 struct isc_httpdmgr {
133 isc_mem_t *mctx;
134 isc_socket_t *sock; /*%< listening socket */
135 isc_task_t *task; /*%< owning task */
136 isc_timermgr_t *timermgr;
137
138 isc_httpdclientok_t *client_ok; /*%< client validator */
139 isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */
140 void *cb_arg; /*%< argument for the above */
141
142 unsigned int flags;
143 ISC_LIST(isc_httpd_t) running; /*%< running clients */
144
145 isc_mutex_t lock;
146
147 ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */
148 isc_httpdaction_t *render_404;
149 isc_httpdaction_t *render_500;
150 };
151
152 /*%
153 * HTTP methods.
154 */
155 #define ISC_HTTPD_METHODUNKNOWN 0
156 #define ISC_HTTPD_METHODGET 1
157 #define ISC_HTTPD_METHODPOST 2
158
159 /*%
160 * Client states.
161 *
162 * _IDLE The client is not doing anything at all. This state should
163 * only occur just after creation, and just before being
164 * destroyed.
165 *
166 * _RECV The client is waiting for data after issuing a socket recv().
167 *
168 * _RECVDONE Data has been received, and is being processed.
169 *
170 * _SEND All data for a response has completed, and a reply was
171 * sent via a socket send() call.
172 *
173 * _SENDDONE Send is completed.
174 *
175 * Badly formatted state table:
176 *
177 * IDLE -> RECV when client has a recv() queued.
178 *
179 * RECV -> RECVDONE when recvdone event received.
180 *
181 * RECVDONE -> SEND if the data for a reply is at hand.
182 *
183 * SEND -> RECV when a senddone event was received.
184 *
185 * At any time -> RECV on error. If RECV fails, the client will
186 * self-destroy, closing the socket and freeing memory.
187 */
188 #define ISC_HTTPD_STATEIDLE 0
189 #define ISC_HTTPD_STATERECV 1
190 #define ISC_HTTPD_STATERECVDONE 2
191 #define ISC_HTTPD_STATESEND 3
192 #define ISC_HTTPD_STATESENDDONE 4
193
194 #define ISC_HTTPD_ISRECV(c) ((c)->state == ISC_HTTPD_STATERECV)
195 #define ISC_HTTPD_ISRECVDONE(c) ((c)->state == ISC_HTTPD_STATERECVDONE)
196 #define ISC_HTTPD_ISSEND(c) ((c)->state == ISC_HTTPD_STATESEND)
197 #define ISC_HTTPD_ISSENDDONE(c) ((c)->state == ISC_HTTPD_STATESENDDONE)
198
199 /*%
200 * Overall magic test that means we're not idle.
201 */
202 #define ISC_HTTPD_SETRECV(c) ((c)->state = ISC_HTTPD_STATERECV)
203 #define ISC_HTTPD_SETRECVDONE(c) ((c)->state = ISC_HTTPD_STATERECVDONE)
204 #define ISC_HTTPD_SETSEND(c) ((c)->state = ISC_HTTPD_STATESEND)
205 #define ISC_HTTPD_SETSENDDONE(c) ((c)->state = ISC_HTTPD_STATESENDDONE)
206
207 static void isc_httpd_accept(isc_task_t *, isc_event_t *);
208 static void isc_httpd_recvdone(isc_task_t *, isc_event_t *);
209 static void isc_httpd_senddone(isc_task_t *, isc_event_t *);
210 static void destroy_client(isc_httpd_t **);
211 static isc_result_t process_request(isc_httpd_t *, int);
212 static void httpdmgr_destroy(isc_httpdmgr_t *);
213 static isc_result_t grow_headerspace(isc_httpd_t *);
214 static void reset_client(isc_httpd_t *httpd);
215
216 static isc_httpdaction_t render_404;
217 static isc_httpdaction_t render_500;
218
219 static void (*finishhook)(void) = NULL;
220
221 static void
222 destroy_client(isc_httpd_t **httpdp) {
223 isc_httpd_t *httpd = *httpdp;
224 isc_httpdmgr_t *httpdmgr = httpd->mgr;
225 isc_region_t r;
226
227 *httpdp = NULL;
228
229 LOCK(&httpdmgr->lock);
230
231 isc_socket_detach(&httpd->sock);
232 ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
233
234 isc_buffer_region(&httpd->headerbuffer, &r);
235 if (r.length > 0) {
236 isc_mem_put(httpdmgr->mctx, r.base, r.length);
237 }
238
239 isc_buffer_region(&httpd->compbuffer, &r);
240 if (r.length > 0) {
241 isc_mem_put(httpdmgr->mctx, r.base, r.length);
242 }
243
244 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t));
245
246 UNLOCK(&httpdmgr->lock);
247
248 if (finishhook != NULL)
249 finishhook();
250
251 httpdmgr_destroy(httpdmgr);
252 }
253
254 isc_result_t
255 isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task,
256 isc_httpdclientok_t *client_ok,
257 isc_httpdondestroy_t *ondestroy, void *cb_arg,
258 isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdmgrp)
259 {
260 isc_result_t result;
261 isc_httpdmgr_t *httpdmgr;
262
263 REQUIRE(mctx != NULL);
264 REQUIRE(sock != NULL);
265 REQUIRE(task != NULL);
266 REQUIRE(tmgr != NULL);
267 REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
268
269 httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
270 if (httpdmgr == NULL)
271 return (ISC_R_NOMEMORY);
272
273 isc_mutex_init(&httpdmgr->lock);
274 httpdmgr->mctx = NULL;
275 isc_mem_attach(mctx, &httpdmgr->mctx);
276 httpdmgr->sock = NULL;
277 isc_socket_attach(sock, &httpdmgr->sock);
278 httpdmgr->task = NULL;
279 isc_task_attach(task, &httpdmgr->task);
280 httpdmgr->timermgr = tmgr; /* XXXMLG no attach function? */
281 httpdmgr->client_ok = client_ok;
282 httpdmgr->ondestroy = ondestroy;
283 httpdmgr->cb_arg = cb_arg;
284 httpdmgr->flags = 0;
285
286 ISC_LIST_INIT(httpdmgr->running);
287 ISC_LIST_INIT(httpdmgr->urls);
288
289 /* XXXMLG ignore errors on isc_socket_listen() */
290 result = isc_socket_listen(sock, SOMAXCONN);
291 if (result != ISC_R_SUCCESS) {
292 UNEXPECTED_ERROR(__FILE__, __LINE__,
293 "isc_socket_listen() failed: %s",
294 isc_result_totext(result));
295 goto cleanup;
296 }
297
298 (void)isc_socket_filter(sock, "httpready");
299
300 result = isc_socket_accept(sock, task, isc_httpd_accept, httpdmgr);
301 if (result != ISC_R_SUCCESS)
302 goto cleanup;
303
304 httpdmgr->render_404 = render_404;
305 httpdmgr->render_500 = render_500;
306
307 *httpdmgrp = httpdmgr;
308 return (ISC_R_SUCCESS);
309
310 cleanup:
311 isc_task_detach(&httpdmgr->task);
312 isc_socket_detach(&httpdmgr->sock);
313 isc_mem_detach(&httpdmgr->mctx);
314 isc_mutex_destroy(&httpdmgr->lock);
315 isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
316 return (result);
317 }
318
319 static void
320 httpdmgr_destroy(isc_httpdmgr_t *httpdmgr) {
321 isc_mem_t *mctx;
322 isc_httpdurl_t *url;
323
324 ENTER("httpdmgr_destroy");
325
326 LOCK(&httpdmgr->lock);
327
328 if (!MSHUTTINGDOWN(httpdmgr)) {
329 NOTICE("httpdmgr_destroy not shutting down yet");
330 UNLOCK(&httpdmgr->lock);
331 return;
332 }
333
334 /*
335 * If all clients are not shut down, don't do anything yet.
336 */
337 if (!ISC_LIST_EMPTY(httpdmgr->running)) {
338 NOTICE("httpdmgr_destroy clients still active");
339 UNLOCK(&httpdmgr->lock);
340 return;
341 }
342
343 NOTICE("httpdmgr_destroy detaching socket, task, and timermgr");
344
345 isc_socket_detach(&httpdmgr->sock);
346 isc_task_detach(&httpdmgr->task);
347 httpdmgr->timermgr = NULL;
348
349 /*
350 * Clear out the list of all actions we know about. Just free the
351 * memory.
352 */
353 url = ISC_LIST_HEAD(httpdmgr->urls);
354 while (url != NULL) {
355 isc_mem_free(httpdmgr->mctx, url->url);
356 ISC_LIST_UNLINK(httpdmgr->urls, url, link);
357 isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t));
358 url = ISC_LIST_HEAD(httpdmgr->urls);
359 }
360
361 UNLOCK(&httpdmgr->lock);
362 isc_mutex_destroy(&httpdmgr->lock);
363
364 if (httpdmgr->ondestroy != NULL)
365 (httpdmgr->ondestroy)(httpdmgr->cb_arg);
366
367 mctx = httpdmgr->mctx;
368 isc_mem_putanddetach(&mctx, httpdmgr, sizeof(isc_httpdmgr_t));
369
370 EXIT("httpdmgr_destroy");
371 }
372
373 #define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen)
374 #define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN)
375
376 /*
377 * Look for the given header in headers.
378 * If value is specified look for it terminated with a character in eov.
379 */
380 static bool
381 have_header(isc_httpd_t *httpd, const char *header, const char *value,
382 const char *eov)
383 {
384 char *cr, *nl, *h;
385 size_t hlen, vlen = 0;
386
387 h = httpd->headers;
388 hlen = strlen(header);
389 if (value != NULL) {
390 INSIST(eov != NULL);
391 vlen = strlen(value);
392 }
393
394 for (;;) {
395 if (strncasecmp(h, header, hlen) != 0) {
396 /*
397 * Skip to next line;
398 */
399 cr = strchr(h, '\r');
400 if (cr != NULL && cr[1] == '\n')
401 cr++;
402 nl = strchr(h, '\n');
403
404 /* last header? */
405 h = cr;
406 if (h == NULL || (nl != NULL && nl < h))
407 h = nl;
408 if (h == NULL)
409 return (false);
410 h++;
411 continue;
412 }
413
414 if (value == NULL)
415 return (true);
416
417 /*
418 * Skip optional leading white space.
419 */
420 h += hlen;
421 while (*h == ' ' || *h == '\t')
422 h++;
423 /*
424 * Terminate token search on NULL or EOL.
425 */
426 while (*h != 0 && *h != '\r' && *h != '\n') {
427 if (strncasecmp(h, value, vlen) == 0)
428 if (strchr(eov, h[vlen]) != NULL)
429 return (true);
430 /*
431 * Skip to next token.
432 */
433 h += strcspn(h, eov);
434 if (h[0] == '\r' && h[1] == '\n')
435 h++;
436 if (h[0] != 0)
437 h++;
438 }
439 return (false);
440 }
441 }
442
443 static isc_result_t
444 process_request(isc_httpd_t *httpd, int length) {
445 char *s;
446 char *p;
447 int delim;
448
449 ENTER("request");
450
451 httpd->recvlen += length;
452
453 httpd->recvbuf[httpd->recvlen] = 0;
454 httpd->headers = NULL;
455
456 /*
457 * If we don't find a blank line in our buffer, return that we need
458 * more data.
459 */
460 s = strstr(httpd->recvbuf, "\r\n\r\n");
461 delim = 2;
462 if (s == NULL) {
463 s = strstr(httpd->recvbuf, "\n\n");
464 delim = 1;
465 }
466 if (s == NULL)
467 return (ISC_R_NOTFOUND);
468
469 /*
470 * NUL terminate request at the blank line.
471 */
472 s[delim] = 0;
473
474 /*
475 * Determine if this is a POST or GET method. Any other values will
476 * cause an error to be returned.
477 */
478 if (strncmp(httpd->recvbuf, "GET ", 4) == 0) {
479 httpd->method = ISC_HTTPD_METHODGET;
480 p = httpd->recvbuf + 4;
481 } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) {
482 httpd->method = ISC_HTTPD_METHODPOST;
483 p = httpd->recvbuf + 5;
484 } else {
485 return (ISC_R_RANGE);
486 }
487
488 /*
489 * From now on, p is the start of our buffer.
490 */
491
492 /*
493 * Extract the URL.
494 */
495 s = p;
496 while (LENGTHOK(s) && BUFLENOK(s) &&
497 (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' '))
498 s++;
499 if (!LENGTHOK(s))
500 return (ISC_R_NOTFOUND);
501 if (!BUFLENOK(s))
502 return (ISC_R_NOMEMORY);
503 *s = 0;
504
505 /*
506 * Make the URL relative.
507 */
508 if ((strncmp(p, "http:/", 6) == 0)
509 || (strncmp(p, "https:/", 7) == 0)) {
510 /* Skip first / */
511 while (*p != '/' && *p != 0)
512 p++;
513 if (*p == 0)
514 return (ISC_R_RANGE);
515 p++;
516 /* Skip second / */
517 while (*p != '/' && *p != 0)
518 p++;
519 if (*p == 0)
520 return (ISC_R_RANGE);
521 p++;
522 /* Find third / */
523 while (*p != '/' && *p != 0)
524 p++;
525 if (*p == 0) {
526 p--;
527 *p = '/';
528 }
529 }
530
531 httpd->url = p;
532 p = s + 1;
533 s = p;
534
535 /*
536 * Now, see if there is a ? mark in the URL. If so, this is
537 * part of the query string, and we will split it from the URL.
538 */
539 httpd->querystring = strchr(httpd->url, '?');
540 if (httpd->querystring != NULL) {
541 *(httpd->querystring) = 0;
542 httpd->querystring++;
543 }
544
545 /*
546 * Extract the HTTP/1.X protocol. We will bounce on anything but
547 * HTTP/1.0 or HTTP/1.1 for now.
548 */
549 while (LENGTHOK(s) && BUFLENOK(s) &&
550 (*s != '\n' && *s != '\r' && *s != '\0'))
551 s++;
552 if (!LENGTHOK(s))
553 return (ISC_R_NOTFOUND);
554 if (!BUFLENOK(s))
555 return (ISC_R_NOMEMORY);
556 /*
557 * Check that we have the expected eol delimiter.
558 */
559 if (strncmp(s, delim == 1 ? "\n" : "\r\n", delim) != 0)
560 return (ISC_R_RANGE);
561 *s = 0;
562 if ((strncmp(p, "HTTP/1.0", 8) != 0)
563 && (strncmp(p, "HTTP/1.1", 8) != 0))
564 return (ISC_R_RANGE);
565 httpd->protocol = p;
566 p = s + delim; /* skip past eol */
567 s = p;
568
569 httpd->headers = s;
570
571 if (have_header(httpd, "Connection:", "close", ", \t\r\n"))
572 httpd->flags |= HTTPD_CLOSE;
573
574 if (have_header(httpd, "Host:", NULL, NULL))
575 httpd->flags |= HTTPD_FOUNDHOST;
576
577 if (strncmp(httpd->protocol, "HTTP/1.0", 8) == 0) {
578 if (have_header(httpd, "Connection:", "Keep-Alive",
579 ", \t\r\n"))
580 httpd->flags |= HTTPD_KEEPALIVE;
581 else
582 httpd->flags |= HTTPD_CLOSE;
583 }
584
585 /*
586 * Check for Accept-Encoding:
587 */
588 #ifdef HAVE_ZLIB
589 if (have_header(httpd, "Accept-Encoding:", "deflate", ";, \t\r\n"))
590 httpd->flags |= HTTPD_ACCEPT_DEFLATE;
591 #endif
592
593 /*
594 * Standards compliance hooks here.
595 */
596 if (strcmp(httpd->protocol, "HTTP/1.1") == 0
597 && ((httpd->flags & HTTPD_FOUNDHOST) == 0))
598 return (ISC_R_RANGE);
599
600 EXIT("request");
601
602 return (ISC_R_SUCCESS);
603 }
604
605 static void
606 isc_httpd_accept(isc_task_t *task, isc_event_t *ev) {
607 isc_result_t result;
608 isc_httpdmgr_t *httpdmgr = ev->ev_arg;
609 isc_httpd_t *httpd;
610 isc_region_t r;
611 isc_socket_newconnev_t *nev = (isc_socket_newconnev_t *)ev;
612 isc_sockaddr_t peeraddr;
613 char *headerdata;
614
615 ENTER("accept");
616
617 LOCK(&httpdmgr->lock);
618 if (MSHUTTINGDOWN(httpdmgr)) {
619 NOTICE("accept shutting down, goto out");
620 goto out;
621 }
622
623 if (nev->result == ISC_R_CANCELED) {
624 NOTICE("accept canceled, goto out");
625 goto out;
626 }
627
628 if (nev->result != ISC_R_SUCCESS) {
629 /* XXXMLG log failure */
630 NOTICE("accept returned failure, goto requeue");
631 goto requeue;
632 }
633
634 (void)isc_socket_getpeername(nev->newsocket, &peeraddr);
635 if (httpdmgr->client_ok != NULL &&
636 !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg)) {
637 isc_socket_detach(&nev->newsocket);
638 goto requeue;
639 }
640
641 httpd = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpd_t));
642 if (httpd == NULL) {
643 /* XXXMLG log failure */
644 NOTICE("accept failed to allocate memory, goto requeue");
645 isc_socket_detach(&nev->newsocket);
646 goto requeue;
647 }
648
649 httpd->mgr = httpdmgr;
650 ISC_LINK_INIT(httpd, link);
651 ISC_LIST_APPEND(httpdmgr->running, httpd, link);
652 ISC_HTTPD_SETRECV(httpd);
653 httpd->sock = nev->newsocket;
654 isc_socket_setname(httpd->sock, "httpd", NULL);
655 httpd->flags = 0;
656
657 /*
658 * Initialize the buffer for our headers.
659 */
660 headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW);
661 if (headerdata == NULL) {
662 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t));
663 isc_socket_detach(&nev->newsocket);
664 goto requeue;
665 }
666 isc_buffer_init(&httpd->headerbuffer, headerdata, HTTP_SENDGROW);
667
668 isc_buffer_initnull(&httpd->compbuffer);
669 isc_buffer_initnull(&httpd->bodybuffer);
670 httpd->sendbuffer = NULL;
671 reset_client(httpd);
672
673 r.base = (unsigned char *)httpd->recvbuf;
674 r.length = HTTP_RECVLEN - 1;
675 result = isc_socket_recv(httpd->sock, &r, 1, task, isc_httpd_recvdone,
676 httpd);
677 /* FIXME!!! */
678 POST(result);
679 NOTICE("accept queued recv on socket");
680
681 requeue:
682 result = isc_socket_accept(httpdmgr->sock, task, isc_httpd_accept,
683 httpdmgr);
684 if (result != ISC_R_SUCCESS) {
685 /* XXXMLG what to do? Log failure... */
686 NOTICE("accept could not reaccept due to failure");
687 }
688
689 out:
690 UNLOCK(&httpdmgr->lock);
691
692 httpdmgr_destroy(httpdmgr);
693
694 isc_event_free(&ev);
695
696 EXIT("accept");
697 }
698
699 static isc_result_t
700 render_404(const char *url, isc_httpdurl_t *urlinfo,
701 const char *querystring, const char *headers, void *arg,
702 unsigned int *retcode, const char **retmsg,
703 const char **mimetype, isc_buffer_t *b,
704 isc_httpdfree_t **freecb, void **freecb_args)
705 {
706 static char msg[] = "No such URL.\r\n";
707
708 UNUSED(url);
709 UNUSED(urlinfo);
710 UNUSED(querystring);
711 UNUSED(headers);
712 UNUSED(arg);
713
714 *retcode = 404;
715 *retmsg = "No such URL";
716 *mimetype = "text/plain";
717 isc_buffer_reinit(b, msg, strlen(msg));
718 isc_buffer_add(b, strlen(msg));
719 *freecb = NULL;
720 *freecb_args = NULL;
721
722 return (ISC_R_SUCCESS);
723 }
724
725 static isc_result_t
726 render_500(const char *url, isc_httpdurl_t *urlinfo,
727 const char *querystring, const char *headers, void *arg,
728 unsigned int *retcode, const char **retmsg,
729 const char **mimetype, isc_buffer_t *b,
730 isc_httpdfree_t **freecb, void **freecb_args)
731 {
732 static char msg[] = "Internal server failure.\r\n";
733
734 UNUSED(url);
735 UNUSED(urlinfo);
736 UNUSED(querystring);
737 UNUSED(headers);
738 UNUSED(arg);
739
740 *retcode = 500;
741 *retmsg = "Internal server failure";
742 *mimetype = "text/plain";
743 isc_buffer_reinit(b, msg, strlen(msg));
744 isc_buffer_add(b, strlen(msg));
745 *freecb = NULL;
746 *freecb_args = NULL;
747
748 return (ISC_R_SUCCESS);
749 }
750
751 #ifdef HAVE_ZLIB
752 /*%<
753 * Reallocates compbuffer to size, does nothing if compbuffer is already
754 * larger than size.
755 *
756 * Requires:
757 *\li httpd a valid isc_httpd_t object
758 *
759 * Returns:
760 *\li #ISC_R_SUCCESS -- all is well.
761 *\li #ISC_R_NOMEMORY -- not enough memory to extend buffer
762 */
763 static isc_result_t
764 alloc_compspace(isc_httpd_t *httpd, unsigned int size) {
765 char *newspace;
766 isc_region_t r;
767
768 isc_buffer_region(&httpd->compbuffer, &r);
769 if (size < r.length) {
770 return (ISC_R_SUCCESS);
771 }
772
773 newspace = isc_mem_get(httpd->mgr->mctx, size);
774 if (newspace == NULL)
775 return (ISC_R_NOMEMORY);
776 isc_buffer_reinit(&httpd->compbuffer, newspace, size);
777
778 if (r.base != NULL) {
779 isc_mem_put(httpd->mgr->mctx, r.base, r.length);
780 }
781
782 return (ISC_R_SUCCESS);
783 }
784
785 /*%<
786 * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
787 * if necessary.
788 *
789 * Requires:
790 *\li httpd a valid isc_httpd_t object
791 *
792 * Returns:
793 *\li #ISC_R_SUCCESS -- all is well.
794 *\li #ISC_R_NOMEMORY -- not enough memory to compress data
795 *\li #ISC_R_FAILURE -- error during compression or compressed
796 * data would be larger than input data
797 */
798 static isc_result_t
799 isc_httpd_compress(isc_httpd_t *httpd) {
800 z_stream zstr;
801 isc_region_t r;
802 isc_result_t result;
803 int ret;
804 int inputlen;
805
806 inputlen = isc_buffer_usedlength(&httpd->bodybuffer);
807 result = alloc_compspace(httpd, inputlen);
808 if (result != ISC_R_SUCCESS) {
809 return result;
810 }
811 isc_buffer_region(&httpd->compbuffer, &r);
812
813 /*
814 * We're setting output buffer size to input size so it fails if the
815 * compressed data size would be bigger than the input size.
816 */
817 memset(&zstr, 0, sizeof(zstr));
818 zstr.total_in = zstr.avail_in =
819 zstr.total_out = zstr.avail_out = inputlen;
820
821 zstr.next_in = isc_buffer_base(&httpd->bodybuffer);
822 zstr.next_out = r.base;
823
824 ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
825 if (ret == Z_OK) {
826 ret = deflate(&zstr, Z_FINISH);
827 }
828 deflateEnd(&zstr);
829 if (ret == Z_STREAM_END) {
830 isc_buffer_add(&httpd->compbuffer, inputlen - zstr.avail_out);
831 return (ISC_R_SUCCESS);
832 } else {
833 return (ISC_R_FAILURE);
834 }
835 }
836 #endif
837
838 static void
839 isc_httpd_recvdone(isc_task_t *task, isc_event_t *ev) {
840 isc_result_t result;
841 isc_httpd_t *httpd = ev->ev_arg;
842 isc_socketevent_t *sev = (isc_socketevent_t *)ev;
843 isc_buffer_t *databuffer;
844 isc_httpdurl_t *url;
845 isc_time_t now;
846 isc_region_t r;
847 bool is_compressed = false;
848 char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
849
850 ENTER("recv");
851
852 INSIST(ISC_HTTPD_ISRECV(httpd));
853
854 if (sev->result != ISC_R_SUCCESS) {
855 NOTICE("recv destroying client");
856 destroy_client(&httpd);
857 goto out;
858 }
859
860 result = process_request(httpd, sev->n);
861 if (result == ISC_R_NOTFOUND) {
862 if (httpd->recvlen >= HTTP_RECVLEN - 1) {
863 destroy_client(&httpd);
864 goto out;
865 }
866 r.base = (unsigned char *)httpd->recvbuf + httpd->recvlen;
867 r.length = HTTP_RECVLEN - httpd->recvlen - 1;
868 /* check return code? */
869 (void)isc_socket_recv(httpd->sock, &r, 1, task,
870 isc_httpd_recvdone, httpd);
871 goto out;
872 } else if (result != ISC_R_SUCCESS) {
873 destroy_client(&httpd);
874 goto out;
875 }
876
877 ISC_HTTPD_SETSEND(httpd);
878
879 /*
880 * XXXMLG Call function here. Provide an add-header function
881 * which will append the common headers to a response we generate.
882 */
883 isc_buffer_initnull(&httpd->bodybuffer);
884 isc_time_now(&now);
885 isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
886 url = ISC_LIST_HEAD(httpd->mgr->urls);
887 while (url != NULL) {
888 if (strcmp(httpd->url, url->url) == 0)
889 break;
890 url = ISC_LIST_NEXT(url, link);
891 }
892 if (url == NULL)
893 result = httpd->mgr->render_404(httpd->url, NULL,
894 httpd->querystring,
895 NULL, NULL,
896 &httpd->retcode,
897 &httpd->retmsg,
898 &httpd->mimetype,
899 &httpd->bodybuffer,
900 &httpd->freecb,
901 &httpd->freecb_arg);
902 else
903 result = url->action(httpd->url, url,
904 httpd->querystring,
905 httpd->headers,
906 url->action_arg,
907 &httpd->retcode, &httpd->retmsg,
908 &httpd->mimetype, &httpd->bodybuffer,
909 &httpd->freecb, &httpd->freecb_arg);
910 if (result != ISC_R_SUCCESS) {
911 result = httpd->mgr->render_500(httpd->url, url,
912 httpd->querystring,
913 NULL, NULL,
914 &httpd->retcode,
915 &httpd->retmsg,
916 &httpd->mimetype,
917 &httpd->bodybuffer,
918 &httpd->freecb,
919 &httpd->freecb_arg);
920 RUNTIME_CHECK(result == ISC_R_SUCCESS);
921 }
922
923 #ifdef HAVE_ZLIB
924 if ((httpd->flags & HTTPD_ACCEPT_DEFLATE) != 0) {
925 result = isc_httpd_compress(httpd);
926 if (result == ISC_R_SUCCESS) {
927 is_compressed = true;
928 }
929 }
930 #endif
931
932 isc_httpd_response(httpd);
933 if ((httpd->flags & HTTPD_KEEPALIVE) != 0)
934 isc_httpd_addheader(httpd, "Connection", "Keep-Alive");
935 isc_httpd_addheader(httpd, "Content-Type", httpd->mimetype);
936 isc_httpd_addheader(httpd, "Date", datebuf);
937 isc_httpd_addheader(httpd, "Expires", datebuf);
938
939 if (url != NULL && url->isstatic) {
940 char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
941 isc_time_formathttptimestamp(&url->loadtime,
942 loadbuf, sizeof(loadbuf));
943 isc_httpd_addheader(httpd, "Last-Modified", loadbuf);
944 isc_httpd_addheader(httpd, "Cache-Control: public", NULL);
945 } else {
946 isc_httpd_addheader(httpd, "Last-Modified", datebuf);
947 isc_httpd_addheader(httpd, "Pragma: no-cache", NULL);
948 isc_httpd_addheader(httpd, "Cache-Control: no-cache", NULL);
949 }
950
951 isc_httpd_addheader(httpd, "Server: libisc", NULL);
952
953 if (is_compressed == true) {
954 isc_httpd_addheader(httpd, "Content-Encoding", "deflate");
955 isc_httpd_addheaderuint(httpd, "Content-Length",
956 isc_buffer_usedlength(&httpd->compbuffer));
957 } else {
958 isc_httpd_addheaderuint(httpd, "Content-Length",
959 isc_buffer_usedlength(&httpd->bodybuffer));
960 }
961
962 isc_httpd_endheaders(httpd); /* done */
963
964 /*
965 * Append either the compressed or the non-compressed response body to
966 * the response headers and store the result in httpd->sendbuffer.
967 */
968 isc_buffer_dup(httpd->mgr->mctx,
969 &httpd->sendbuffer, &httpd->headerbuffer);
970 isc_buffer_setautorealloc(httpd->sendbuffer, true);
971 databuffer = (is_compressed ? &httpd->compbuffer : &httpd->bodybuffer);
972 isc_buffer_usedregion(databuffer, &r);
973 result = isc_buffer_copyregion(httpd->sendbuffer, &r);
974 RUNTIME_CHECK(result == ISC_R_SUCCESS);
975 /*
976 * Determine total response size.
977 */
978 isc_buffer_usedregion(httpd->sendbuffer, &r);
979
980 /* check return code? */
981 (void)isc_socket_send(httpd->sock, &r, task,
982 isc_httpd_senddone, httpd);
983
984 out:
985 isc_event_free(&ev);
986 EXIT("recv");
987 }
988
989 void
990 isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
991 isc_httpdmgr_t *httpdmgr;
992 isc_httpd_t *httpd;
993 httpdmgr = *httpdmgrp;
994 *httpdmgrp = NULL;
995
996 ENTER("isc_httpdmgr_shutdown");
997
998 LOCK(&httpdmgr->lock);
999
1000 MSETSHUTTINGDOWN(httpdmgr);
1001
1002 isc_socket_cancel(httpdmgr->sock, httpdmgr->task, ISC_SOCKCANCEL_ALL);
1003
1004 httpd = ISC_LIST_HEAD(httpdmgr->running);
1005 while (httpd != NULL) {
1006 isc_socket_cancel(httpd->sock, httpdmgr->task,
1007 ISC_SOCKCANCEL_ALL);
1008 httpd = ISC_LIST_NEXT(httpd, link);
1009 }
1010
1011 UNLOCK(&httpdmgr->lock);
1012
1013 EXIT("isc_httpdmgr_shutdown");
1014 }
1015
1016 static isc_result_t
1017 grow_headerspace(isc_httpd_t *httpd) {
1018 char *newspace;
1019 unsigned int newlen;
1020 isc_region_t r;
1021
1022 isc_buffer_region(&httpd->headerbuffer, &r);
1023 newlen = r.length + HTTP_SENDGROW;
1024 if (newlen > HTTP_SEND_MAXLEN)
1025 return (ISC_R_NOSPACE);
1026
1027 newspace = isc_mem_get(httpd->mgr->mctx, newlen);
1028 if (newspace == NULL)
1029 return (ISC_R_NOMEMORY);
1030
1031 isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen);
1032
1033 isc_mem_put(httpd->mgr->mctx, r.base, r.length);
1034
1035 return (ISC_R_SUCCESS);
1036 }
1037
1038 isc_result_t
1039 isc_httpd_response(isc_httpd_t *httpd) {
1040 isc_result_t result;
1041 unsigned int needlen;
1042
1043 needlen = strlen(httpd->protocol) + 1; /* protocol + space */
1044 needlen += 3 + 1; /* room for response code, always 3 bytes */
1045 needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */
1046
1047 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1048 result = grow_headerspace(httpd);
1049 if (result != ISC_R_SUCCESS)
1050 return (result);
1051 }
1052
1053 return (isc_buffer_printf(&httpd->headerbuffer, "%s %03u %s\r\n",
1054 httpd->protocol, httpd->retcode,
1055 httpd->retmsg));
1056 }
1057
1058 isc_result_t
1059 isc_httpd_addheader(isc_httpd_t *httpd, const char *name,
1060 const char *val)
1061 {
1062 isc_result_t result;
1063 unsigned int needlen;
1064
1065 needlen = strlen(name); /* name itself */
1066 if (val != NULL)
1067 needlen += 2 + strlen(val); /* :<space> and val */
1068 needlen += 2; /* CRLF */
1069
1070 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1071 result = grow_headerspace(httpd);
1072 if (result != ISC_R_SUCCESS)
1073 return (result);
1074 }
1075
1076 if (val != NULL) {
1077 return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n",
1078 name, val));
1079 } else {
1080 return (isc_buffer_printf(&httpd->headerbuffer, "%s\r\n",
1081 name));
1082 }
1083 }
1084
1085 isc_result_t
1086 isc_httpd_endheaders(isc_httpd_t *httpd) {
1087 isc_result_t result;
1088
1089 while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) {
1090 result = grow_headerspace(httpd);
1091 if (result != ISC_R_SUCCESS)
1092 return (result);
1093 }
1094
1095 return (isc_buffer_printf(&httpd->headerbuffer, "\r\n"));
1096 }
1097
1098 isc_result_t
1099 isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) {
1100 isc_result_t result;
1101 unsigned int needlen;
1102 char buf[sizeof "18446744073709551616"];
1103
1104 snprintf(buf, sizeof(buf), "%d", val);
1105
1106 needlen = strlen(name); /* name itself */
1107 needlen += 2 + strlen(buf); /* :<space> and val */
1108 needlen += 2; /* CRLF */
1109
1110 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1111 result = grow_headerspace(httpd);
1112 if (result != ISC_R_SUCCESS)
1113 return (result);
1114 }
1115
1116 return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", name,
1117 buf));
1118 }
1119
1120 static void
1121 isc_httpd_senddone(isc_task_t *task, isc_event_t *ev) {
1122 isc_httpd_t *httpd = ev->ev_arg;
1123 isc_region_t r;
1124 isc_socketevent_t *sev = (isc_socketevent_t *)ev;
1125
1126 ENTER("senddone");
1127 INSIST(ISC_HTTPD_ISSEND(httpd));
1128
1129 isc_buffer_free(&httpd->sendbuffer);
1130
1131 /*
1132 * We will always want to clean up our receive buffer, even if we
1133 * got an error on send or we are shutting down.
1134 *
1135 * We will pass in the buffer only if there is data in it. If
1136 * there is no data, we will pass in a NULL.
1137 */
1138 if (httpd->freecb != NULL) {
1139 isc_buffer_t *b = NULL;
1140 if (isc_buffer_length(&httpd->bodybuffer) > 0) {
1141 b = &httpd->bodybuffer;
1142 httpd->freecb(b, httpd->freecb_arg);
1143 }
1144 NOTICE("senddone free callback performed");
1145 }
1146
1147 if (sev->result != ISC_R_SUCCESS) {
1148 destroy_client(&httpd);
1149 goto out;
1150 }
1151
1152 if ((httpd->flags & HTTPD_CLOSE) != 0) {
1153 destroy_client(&httpd);
1154 goto out;
1155 }
1156
1157 ISC_HTTPD_SETRECV(httpd);
1158
1159 NOTICE("senddone restarting recv on socket");
1160
1161 reset_client(httpd);
1162
1163 r.base = (unsigned char *)httpd->recvbuf;
1164 r.length = HTTP_RECVLEN - 1;
1165 /* check return code? */
1166 (void)isc_socket_recv(httpd->sock, &r, 1, task,
1167 isc_httpd_recvdone, httpd);
1168
1169 out:
1170 isc_event_free(&ev);
1171 EXIT("senddone");
1172 }
1173
1174 static void
1175 reset_client(isc_httpd_t *httpd) {
1176 /*
1177 * Catch errors here. We MUST be in RECV mode, and we MUST NOT have
1178 * any outstanding buffers. If we have buffers, we have a leak.
1179 */
1180 INSIST(ISC_HTTPD_ISRECV(httpd));
1181 INSIST(!ISC_LINK_LINKED(&httpd->headerbuffer, link));
1182 INSIST(!ISC_LINK_LINKED(&httpd->bodybuffer, link));
1183
1184 httpd->recvbuf[0] = 0;
1185 httpd->recvlen = 0;
1186 httpd->headers = NULL;
1187 httpd->method = ISC_HTTPD_METHODUNKNOWN;
1188 httpd->url = NULL;
1189 httpd->querystring = NULL;
1190 httpd->protocol = NULL;
1191 httpd->flags = 0;
1192
1193 isc_buffer_clear(&httpd->headerbuffer);
1194 isc_buffer_clear(&httpd->compbuffer);
1195 isc_buffer_invalidate(&httpd->bodybuffer);
1196 }
1197
1198 isc_result_t
1199 isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url,
1200 isc_httpdaction_t *func, void *arg)
1201 {
1202 return (isc_httpdmgr_addurl2(httpdmgr, url, false, func, arg));
1203 }
1204
1205 isc_result_t
1206 isc_httpdmgr_addurl2(isc_httpdmgr_t *httpdmgr, const char *url,
1207 bool isstatic,
1208 isc_httpdaction_t *func, void *arg)
1209 {
1210 isc_httpdurl_t *item;
1211
1212 if (url == NULL) {
1213 httpdmgr->render_404 = func;
1214 return (ISC_R_SUCCESS);
1215 }
1216
1217 item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
1218 if (item == NULL)
1219 return (ISC_R_NOMEMORY);
1220
1221 item->url = isc_mem_strdup(httpdmgr->mctx, url);
1222 if (item->url == NULL) {
1223 isc_mem_put(httpdmgr->mctx, item, sizeof(isc_httpdurl_t));
1224 return (ISC_R_NOMEMORY);
1225 }
1226
1227 item->action = func;
1228 item->action_arg = arg;
1229 item->isstatic = isstatic;
1230 isc_time_now(&item->loadtime);
1231
1232 ISC_LINK_INIT(item, link);
1233 ISC_LIST_APPEND(httpdmgr->urls, item, link);
1234
1235 return (ISC_R_SUCCESS);
1236 }
1237
1238 void
1239 isc_httpd_setfinishhook(void (*fn)(void))
1240 {
1241 finishhook = fn;
1242 }
1243