]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/bind9/lib/isc/httpd.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / bind9 / lib / isc / httpd.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }