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