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