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 */
68 struct 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 */
79 struct 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 */
137 struct 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
212 static void isc_httpd_accept(isc_task_t *, isc_event_t *);
213 static void isc_httpd_recvdone(isc_task_t *, isc_event_t *);
214 static void isc_httpd_senddone(isc_task_t *, isc_event_t *);
215 static void destroy_client(isc_httpd_t **);
216 static isc_result_t process_request(isc_httpd_t *, int);
217 static void httpdmgr_destroy(isc_httpdmgr_t *);
218 static isc_result_t grow_headerspace(isc_httpd_t *);
219 static void reset_client(isc_httpd_t *httpd);
220 static 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 **);
225 static 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
231 static void
destroy_client(isc_httpd_t ** httpdp)232 destroy_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
255 isc_result_t
isc_httpdmgr_create(isc_mem_t * mctx,isc_socket_t * sock,isc_task_t * task,isc_httpdclientok_t * client_ok,isc_httpdondestroy_t * ondestroy,void * cb_arg,isc_timermgr_t * tmgr,isc_httpdmgr_t ** httpdp)256 isc_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
323 static void
httpdmgr_destroy(isc_httpdmgr_t * httpdmgr)324 httpdmgr_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
381 static isc_result_t
process_request(isc_httpd_t * httpd,int length)382 process_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
515 static void
isc_httpd_accept(isc_task_t * task,isc_event_t * ev)516 isc_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
611 static isc_result_t
render_404(const char * url,const char * querystring,void * arg,unsigned int * retcode,const char ** retmsg,const char ** mimetype,isc_buffer_t * b,isc_httpdfree_t ** freecb,void ** freecb_args)612 render_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
635 static isc_result_t
render_500(const char * url,const char * querystring,void * arg,unsigned int * retcode,const char ** retmsg,const char ** mimetype,isc_buffer_t * b,isc_httpdfree_t ** freecb,void ** freecb_args)636 render_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
659 static void
isc_httpd_recvdone(isc_task_t * task,isc_event_t * ev)660 isc_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
768 void
isc_httpdmgr_shutdown(isc_httpdmgr_t ** httpdmgrp)769 isc_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
796 static isc_result_t
grow_headerspace(isc_httpd_t * httpd)797 grow_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
818 isc_result_t
isc_httpd_response(isc_httpd_t * httpd)819 isc_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
841 isc_result_t
isc_httpd_addheader(isc_httpd_t * httpd,const char * name,const char * val)842 isc_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
871 isc_result_t
isc_httpd_endheaders(isc_httpd_t * httpd)872 isc_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
888 isc_result_t
isc_httpd_addheaderuint(isc_httpd_t * httpd,const char * name,int val)889 isc_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
914 static void
isc_httpd_senddone(isc_task_t * task,isc_event_t * ev)915 isc_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
973 out:
974 isc_event_free(&ev);
975 EXIT("senddone");
976 }
977
978 static void
reset_client(isc_httpd_t * httpd)979 reset_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
1001 isc_result_t
isc_httpdmgr_addurl(isc_httpdmgr_t * httpdmgr,const char * url,isc_httpdaction_t * func,void * arg)1002 isc_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