accf_http.c revision 129880
1202375Srdivacky/*
2202375Srdivacky * Copyright (c) 2000 Paycounter, Inc.
3202375Srdivacky * Author: Alfred Perlstein <alfred@paycounter.com>, <alfred@FreeBSD.org>
4202375Srdivacky * All rights reserved.
5202375Srdivacky *
6202375Srdivacky * Redistribution and use in source and binary forms, with or without
7202375Srdivacky * modification, are permitted provided that the following conditions
8202375Srdivacky * are met:
9202375Srdivacky * 1. Redistributions of source code must retain the above copyright
10202375Srdivacky *    notice, this list of conditions and the following disclaimer.
11202375Srdivacky * 2. Redistributions in binary form must reproduce the above copyright
12202375Srdivacky *    notice, this list of conditions and the following disclaimer in the
13202375Srdivacky *    documentation and/or other materials provided with the distribution.
14202375Srdivacky *
15203954Srdivacky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16202375Srdivacky * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17202375Srdivacky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18202375Srdivacky * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19202375Srdivacky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20202375Srdivacky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21202375Srdivacky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22202375Srdivacky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23202375Srdivacky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24202375Srdivacky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25202375Srdivacky * SUCH DAMAGE.
26202375Srdivacky *
27202375Srdivacky *	$FreeBSD: head/sys/netinet/accf_http.c 129880 2004-05-30 20:27:19Z phk $
28202375Srdivacky */
29202375Srdivacky
30202375Srdivacky#define ACCEPT_FILTER_MOD
31202375Srdivacky
32202375Srdivacky#include <sys/param.h>
33202375Srdivacky#include <sys/kernel.h>
34202375Srdivacky#include <sys/mbuf.h>
35202375Srdivacky#include <sys/module.h>
36202375Srdivacky#include <sys/signalvar.h>
37202375Srdivacky#include <sys/sysctl.h>
38202375Srdivacky#include <sys/socketvar.h>
39202375Srdivacky
40202375Srdivacky/* check for GET/HEAD */
41202375Srdivackystatic void sohashttpget(struct socket *so, void *arg, int waitflag);
42202375Srdivacky/* check for HTTP/1.0 or HTTP/1.1 */
43202375Srdivackystatic void soparsehttpvers(struct socket *so, void *arg, int waitflag);
44202375Srdivacky/* check for end of HTTP/1.x request */
45202375Srdivackystatic void soishttpconnected(struct socket *so, void *arg, int waitflag);
46202375Srdivacky/* strcmp on an mbuf chain */
47202375Srdivackystatic int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp);
48202375Srdivacky/* strncmp on an mbuf chain */
49202375Srdivackystatic int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset,
50202375Srdivacky	int max, char *cmp);
51202375Srdivacky/* socketbuffer is full */
52202375Srdivackystatic int sbfull(struct sockbuf *sb);
53202375Srdivacky
54202375Srdivackystatic struct accept_filter accf_http_filter = {
55202375Srdivacky	"httpready",
56202375Srdivacky	sohashttpget,
57202375Srdivacky	NULL,
58202375Srdivacky	NULL
59212904Sdim};
60212904Sdim
61212904Sdimstatic moduledata_t accf_http_mod = {
62212904Sdim	"accf_http",
63212904Sdim	accept_filt_generic_mod_event,
64212904Sdim	&accf_http_filter
65212904Sdim};
66212904Sdim
67212904SdimDECLARE_MODULE(accf_http, accf_http_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
68212904Sdim
69212904Sdimstatic int parse_http_version = 1;
70212904Sdim
71212904SdimSYSCTL_NODE(_net_inet_accf, OID_AUTO, http, CTLFLAG_RW, 0,
72212904Sdim"HTTP accept filter");
73212904SdimSYSCTL_INT(_net_inet_accf_http, OID_AUTO, parsehttpversion, CTLFLAG_RW,
74212904Sdim&parse_http_version, 1,
75212904Sdim"Parse http version so that non 1.x requests work");
76212904Sdim
77212904Sdim#ifdef ACCF_HTTP_DEBUG
78212904Sdim#define DPRINT(fmt, args...)						\
79212904Sdim	do {								\
80212904Sdim		printf("%s:%d: " fmt "\n", __func__, __LINE__, ##args);	\
81212904Sdim	} while (0)
82212904Sdim#else
83212904Sdim#define DPRINT(fmt, args...)
84212904Sdim#endif
85212904Sdim
86212904Sdimstatic int
87212904Sdimsbfull(struct sockbuf *sb)
88212904Sdim{
89212904Sdim
90212904Sdim	DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, "
91212904Sdim	    "mbcnt(%ld) >= mbmax(%ld): %d",
92212904Sdim	    sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat,
93212904Sdim	    sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax);
94212904Sdim	return (sb->sb_cc >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax);
95212904Sdim}
96212904Sdim
97212904Sdim/*
98212904Sdim * start at mbuf m, (must provide npkt if exists)
99212904Sdim * starting at offset in m compare characters in mbuf chain for 'cmp'
100212904Sdim */
101212904Sdimstatic int
102212904Sdimmbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp)
103212904Sdim{
104212904Sdim	struct mbuf *n;
105212904Sdim
106212904Sdim	for (; m != NULL; m = n) {
107212904Sdim		n = npkt;
108212904Sdim		if (npkt)
109212904Sdim			npkt = npkt->m_nextpkt;
110212904Sdim		for (; m; m = m->m_next) {
111212904Sdim			for (; offset < m->m_len; offset++, cmp++) {
112212904Sdim				if (*cmp == '\0')
113212904Sdim					return (1);
114212904Sdim				else if (*cmp != *(mtod(m, char *) + offset))
115212904Sdim					return (0);
116212904Sdim			}
117212904Sdim			if (*cmp == '\0')
118212904Sdim				return (1);
119212904Sdim			offset = 0;
120212904Sdim		}
121212904Sdim	}
122212904Sdim	return (0);
123212904Sdim}
124212904Sdim
125212904Sdim/*
126212904Sdim * start at mbuf m, (must provide npkt if exists)
127212904Sdim * starting at offset in m compare characters in mbuf chain for 'cmp'
128212904Sdim * stop at 'max' characters
129212904Sdim */
130212904Sdimstatic int
131212904Sdimmbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int max, char *cmp)
132212904Sdim{
133212904Sdim	struct mbuf *n;
134212904Sdim
135212904Sdim	for (; m != NULL; m = n) {
136212904Sdim		n = npkt;
137212904Sdim		if (npkt)
138212904Sdim			npkt = npkt->m_nextpkt;
139212904Sdim		for (; m; m = m->m_next) {
140212904Sdim			for (; offset < m->m_len; offset++, cmp++, max--) {
141212904Sdim				if (max == 0 || *cmp == '\0')
142212904Sdim					return (1);
143212904Sdim				else if (*cmp != *(mtod(m, char *) + offset))
144212904Sdim					return (0);
145212904Sdim			}
146212904Sdim			if (max == 0 || *cmp == '\0')
147212904Sdim				return (1);
148212904Sdim			offset = 0;
149212904Sdim		}
150212904Sdim	}
151212904Sdim	return (0);
152212904Sdim}
153212904Sdim
154212904Sdim#define STRSETUP(sptr, slen, str)					\
155212904Sdim	do {								\
156212904Sdim		sptr = str;						\
157212904Sdim		slen = sizeof(str) - 1;					\
158212904Sdim	} while(0)
159212904Sdim
160212904Sdimstatic void
161212904Sdimsohashttpget(struct socket *so, void *arg, int waitflag)
162212904Sdim{
163212904Sdim
164212904Sdim	if ((so->so_state & SS_CANTRCVMORE) == 0 && !sbfull(&so->so_rcv)) {
165212904Sdim		struct mbuf *m;
166212904Sdim		char *cmp;
167212904Sdim		int	cmplen, cc;
168212904Sdim
169212904Sdim		m = so->so_rcv.sb_mb;
170212904Sdim		cc = so->so_rcv.sb_cc - 1;
171212904Sdim		if (cc < 1)
172212904Sdim			return;
173212904Sdim		switch (*mtod(m, char *)) {
174212904Sdim		case 'G':
175212904Sdim			STRSETUP(cmp, cmplen, "ET ");
176212904Sdim			break;
177212904Sdim		case 'H':
178212904Sdim			STRSETUP(cmp, cmplen, "EAD ");
179212904Sdim			break;
180212904Sdim		default:
181212904Sdim			goto fallout;
182212904Sdim		}
183212904Sdim		if (cc < cmplen) {
184212904Sdim			if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) {
185212904Sdim				DPRINT("short cc (%d) but mbufstrncmp ok", cc);
186212904Sdim				return;
187212904Sdim			} else {
188212904Sdim				DPRINT("short cc (%d) mbufstrncmp failed", cc);
189212904Sdim				goto fallout;
190212904Sdim			}
191212904Sdim		}
192212904Sdim		if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) {
193212904Sdim			DPRINT("mbufstrcmp ok");
194212904Sdim			if (parse_http_version == 0)
195212904Sdim				soishttpconnected(so, arg, waitflag);
196212904Sdim			else
197212904Sdim				soparsehttpvers(so, arg, waitflag);
198212904Sdim			return;
199212904Sdim		}
200212904Sdim		DPRINT("mbufstrcmp bad");
201212904Sdim	}
202212904Sdim
203212904Sdimfallout:
204212904Sdim	DPRINT("fallout");
205212904Sdim	so->so_upcall = NULL;
206212904Sdim	so->so_rcv.sb_flags &= ~SB_UPCALL;
207212904Sdim	soisconnected(so);
208212904Sdim	return;
209212904Sdim}
210212904Sdim
211212904Sdimstatic void
212212904Sdimsoparsehttpvers(struct socket *so, void *arg, int waitflag)
213212904Sdim{
214212904Sdim	struct mbuf *m, *n;
215212904Sdim	int	i, cc, spaces, inspaces;
216212904Sdim
217212904Sdim	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
218212904Sdim		goto fallout;
219212904Sdim
220212904Sdim	m = so->so_rcv.sb_mb;
221212904Sdim	cc = so->so_rcv.sb_cc;
222212904Sdim	inspaces = spaces = 0;
223212904Sdim	for (m = so->so_rcv.sb_mb; m; m = n) {
224212904Sdim		n = m->m_nextpkt;
225212904Sdim		for (; m; m = m->m_next) {
226212904Sdim			for (i = 0; i < m->m_len; i++, cc--) {
227212904Sdim				switch (*(mtod(m, char *) + i)) {
228212904Sdim				case ' ':
229212904Sdim					/* tabs? '\t' */
230212904Sdim					if (!inspaces) {
231212904Sdim						spaces++;
232212904Sdim						inspaces = 1;
233212904Sdim					}
234212904Sdim					break;
235212904Sdim				case '\r':
236212904Sdim				case '\n':
237212904Sdim					DPRINT("newline");
238212904Sdim					goto fallout;
239212904Sdim				default:
240212904Sdim					if (spaces != 2) {
241212904Sdim						inspaces = 0;
242212904Sdim						break;
243212904Sdim					}
244212904Sdim
245212904Sdim					/*
246212904Sdim					 * if we don't have enough characters
247212904Sdim					 * left (cc < sizeof("HTTP/1.0") - 1)
248212904Sdim					 * then see if the remaining ones
249212904Sdim					 * are a request we can parse.
250212904Sdim					 */
251212904Sdim					if (cc < sizeof("HTTP/1.0") - 1) {
252212904Sdim						if (mbufstrncmp(m, n, i, cc,
253212904Sdim							"HTTP/1.") == 1) {
254212904Sdim							DPRINT("ok");
255212904Sdim							goto readmore;
256212904Sdim						} else {
257212904Sdim							DPRINT("bad");
258212904Sdim							goto fallout;
259212904Sdim						}
260212904Sdim					} else if (
261212904Sdim					    mbufstrcmp(m, n, i, "HTTP/1.0") ||
262212904Sdim					    mbufstrcmp(m, n, i, "HTTP/1.1")) {
263212904Sdim							DPRINT("ok");
264212904Sdim							soishttpconnected(so,
265212904Sdim							    arg, waitflag);
266212904Sdim							return;
267212904Sdim					} else {
268212904Sdim						DPRINT("bad");
269212904Sdim						goto fallout;
270212904Sdim					}
271212904Sdim				}
272212904Sdim			}
273212904Sdim		}
274212904Sdim	}
275212904Sdimreadmore:
276212904Sdim	DPRINT("readmore");
277212904Sdim	/*
278212904Sdim	 * if we hit here we haven't hit something
279212904Sdim	 * we don't understand or a newline, so try again
280212904Sdim	 */
281212904Sdim	so->so_upcall = soparsehttpvers;
282212904Sdim	so->so_rcv.sb_flags |= SB_UPCALL;
283212904Sdim	return;
284212904Sdim
285212904Sdimfallout:
286212904Sdim	DPRINT("fallout");
287212904Sdim	so->so_upcall = NULL;
288212904Sdim	so->so_rcv.sb_flags &= ~SB_UPCALL;
289212904Sdim	soisconnected(so);
290212904Sdim	return;
291212904Sdim}
292212904Sdim
293212904Sdim
294212904Sdim#define NCHRS 3
295212904Sdim
296212904Sdimstatic void
297212904Sdimsoishttpconnected(struct socket *so, void *arg, int waitflag)
298212904Sdim{
299212904Sdim	char a, b, c;
300212904Sdim	struct mbuf *m, *n;
301212904Sdim	int ccleft, copied;
302212904Sdim
303212904Sdim	DPRINT("start");
304212904Sdim	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
305212904Sdim		goto gotit;
306202375Srdivacky
307202375Srdivacky	/*
308202375Srdivacky	 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c
309212904Sdim	 * copied - how much we've copied so far
310212904Sdim	 * ccleft - how many bytes remaining in the socketbuffer
311212904Sdim	 * just loop over the mbufs subtracting from 'ccleft' until we only
312212904Sdim	 * have NCHRS left
313212904Sdim	 */
314212904Sdim	copied = 0;
315212904Sdim	ccleft = so->so_rcv.sb_cc;
316212904Sdim	if (ccleft < NCHRS)
317212904Sdim		goto readmore;
318212904Sdim	a = b = c = '\0';
319212904Sdim	for (m = so->so_rcv.sb_mb; m; m = n) {
320212904Sdim		n = m->m_nextpkt;
321212904Sdim		for (; m; m = m->m_next) {
322212904Sdim			ccleft -= m->m_len;
323202375Srdivacky			if (ccleft <= NCHRS) {
324202375Srdivacky				char *src;
325202375Srdivacky				int tocopy;
326202375Srdivacky
327202375Srdivacky				tocopy = (NCHRS - ccleft) - copied;
328202375Srdivacky				src = mtod(m, char *) + (m->m_len - tocopy);
329202375Srdivacky
330202375Srdivacky				while (tocopy--) {
331202375Srdivacky					switch (copied++) {
332202375Srdivacky					case 0:
333203954Srdivacky						a = *src++;
334203954Srdivacky						break;
335203954Srdivacky					case 1:
336202375Srdivacky						b = *src++;
337202375Srdivacky						break;
338202375Srdivacky					case 2:
339202375Srdivacky						c = *src++;
340202375Srdivacky						break;
341202375Srdivacky					}
342202375Srdivacky				}
343202375Srdivacky			}
344202375Srdivacky		}
345202375Srdivacky	}
346202375Srdivacky	if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) {
347202375Srdivacky		/* we have all request headers */
348202375Srdivacky		goto gotit;
349202375Srdivacky	}
350202375Srdivacky
351202375Srdivackyreadmore:
352202375Srdivacky	so->so_upcall = soishttpconnected;
353202375Srdivacky	so->so_rcv.sb_flags |= SB_UPCALL;
354202375Srdivacky	return;
355202375Srdivacky
356202375Srdivackygotit:
357202375Srdivacky	so->so_upcall = NULL;
358202375Srdivacky	so->so_rcv.sb_flags &= ~SB_UPCALL;
359202375Srdivacky	soisconnected(so);
360202375Srdivacky	return;
361202375Srdivacky}
362202375Srdivacky