httpd.c revision 290001
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 */
68struct 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 */
79struct 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 */
137struct 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
212static void isc_httpd_accept(isc_task_t *, isc_event_t *);
213static void isc_httpd_recvdone(isc_task_t *, isc_event_t *);
214static void isc_httpd_senddone(isc_task_t *, isc_event_t *);
215static void destroy_client(isc_httpd_t **);
216static isc_result_t process_request(isc_httpd_t *, int);
217static void httpdmgr_destroy(isc_httpdmgr_t *);
218static isc_result_t grow_headerspace(isc_httpd_t *);
219static void reset_client(isc_httpd_t *httpd);
220static 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 **);
225static 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
231static void
232destroy_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
255isc_result_t
256isc_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
323static void
324httpdmgr_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
381static isc_result_t
382process_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
515static void
516isc_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
611static isc_result_t
612render_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
635static isc_result_t
636render_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
659static void
660isc_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
768void
769isc_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
796static isc_result_t
797grow_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
818isc_result_t
819isc_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
841isc_result_t
842isc_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
871isc_result_t
872isc_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
888isc_result_t
889isc_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
914static void
915isc_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
973out:
974	isc_event_free(&ev);
975	EXIT("senddone");
976}
977
978static void
979reset_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
1001isc_result_t
1002isc_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