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