xref: /dragonfly/sys/net/accf_http/accf_http.c (revision 36a3d1d6)
1 /*
2  * Copyright (c) 2000 Paycounter, Inc.
3  * Author: Alfred Perlstein <alfred@paycounter.com>, <alfred@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  *	$FreeBSD: src/sys/netinet/accf_http.c,v 1.1.2.4 2002/05/01 08:34:37 alfred Exp $
28  *	$DragonFly: src/sys/net/accf_http/accf_http.c,v 1.4 2007/04/22 01:13:13 dillon Exp $
29  */
30 
31 #define ACCEPT_FILTER_MOD
32 
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/sysproto.h>
36 #include <sys/kernel.h>
37 #include <sys/proc.h>
38 #include <sys/malloc.h>
39 #include <sys/unistd.h>
40 #include <sys/file.h>
41 #include <sys/fcntl.h>
42 #include <sys/protosw.h>
43 #include <sys/sysctl.h>
44 #include <sys/socket.h>
45 #include <sys/socketvar.h>
46 #include <sys/stat.h>
47 #include <sys/mbuf.h>
48 #include <sys/resource.h>
49 #include <sys/sysent.h>
50 #include <sys/resourcevar.h>
51 
52 /* check for GET/HEAD */
53 static void sohashttpget(struct socket *so, void *arg, int waitflag);
54 /* check for HTTP/1.0 or HTTP/1.1 */
55 static void soparsehttpvers(struct socket *so, void *arg, int waitflag);
56 /* check for end of HTTP/1.x request */
57 static void soishttpconnected(struct socket *so, void *arg, int waitflag);
58 /* strcmp on an mbuf chain */
59 static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp);
60 /* strncmp on an mbuf chain */
61 static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset,
62 	int max, char *cmp);
63 /* socketbuffer is full */
64 static int sbfull(struct signalsockbuf *sb);
65 
66 static struct accept_filter accf_http_filter = {
67 	"httpready",
68 	sohashttpget,
69 	NULL,
70 	NULL
71 };
72 
73 static moduledata_t accf_http_mod = {
74 	"accf_http",
75 	accept_filt_generic_mod_event,
76 	&accf_http_filter
77 };
78 
79 DECLARE_MODULE(accf_http, accf_http_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
80 
81 static int parse_http_version = 1;
82 
83 SYSCTL_NODE(_net_inet_accf, OID_AUTO, http, CTLFLAG_RW, 0,
84 "HTTP accept filter");
85 SYSCTL_INT(_net_inet_accf_http, OID_AUTO, parsehttpversion, CTLFLAG_RW,
86 &parse_http_version, 1,
87 "Parse http version so that non 1.x requests work");
88 
89 #ifdef ACCF_HTTP_DEBUG
90 #define DPRINT(fmt, args...) \
91 	do {	\
92 		kprintf("%s:%d: " fmt "\n", __func__, __LINE__ , ##args);	\
93 	} while (0)
94 #else
95 #define DPRINT(fmt, args...)
96 #endif
97 
98 static int
99 sbfull(struct signalsockbuf *ssb)
100 {
101 
102 	DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, mbcnt(%ld) >= mbmax(%ld): %d",
103 	    ssb->ssb_cc, ssb->ssb_hiwat, ssb->ssb_cc >= ssb->ssb_hiwat,
104 	    ssb->ssb_mbcnt, ssb->ssb_mbmax, ssb->ssb_mbcnt >= ssb->ssb_mbmax);
105 	return(ssb->ssb_cc >= ssb->ssb_hiwat ||
106 	       ssb->ssb_mbcnt >= ssb->ssb_mbmax);
107 }
108 
109 /*
110  * start at mbuf m, (must provide npkt if exists)
111  * starting at offset in m compare characters in mbuf chain for 'cmp'
112  */
113 static int
114 mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp)
115 {
116 	struct mbuf *n;
117 
118 	for (;m != NULL; m = n) {
119 		n = npkt;
120 		if (npkt)
121 			npkt = npkt->m_nextpkt;
122 		for (; m; m = m->m_next) {
123 			for (; offset < m->m_len; offset++, cmp++) {
124 				if (*cmp == '\0') {
125 					return (1);
126 				} else if (*cmp != *(mtod(m, char *) + offset)) {
127 					return (0);
128 				}
129 			}
130 			if (*cmp == '\0')
131 				return (1);
132 			offset = 0;
133 		}
134 	}
135 	return (0);
136 }
137 
138 /*
139  * start at mbuf m, (must provide npkt if exists)
140  * starting at offset in m compare characters in mbuf chain for 'cmp'
141  * stop at 'max' characters
142  */
143 static int
144 mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int max, char *cmp)
145 {
146 	struct mbuf *n;
147 
148 	for (;m != NULL; m = n) {
149 		n = npkt;
150 		if (npkt)
151 			npkt = npkt->m_nextpkt;
152 		for (; m; m = m->m_next) {
153 			for (; offset < m->m_len; offset++, cmp++, max--) {
154 				if (max == 0 || *cmp == '\0') {
155 					return (1);
156 				} else if (*cmp != *(mtod(m, char *) + offset)) {
157 					return (0);
158 				}
159 			}
160 			if (max == 0 || *cmp == '\0')
161 				return (1);
162 			offset = 0;
163 		}
164 	}
165 	return (0);
166 }
167 
168 #define STRSETUP(sptr, slen, str) \
169 	do {	\
170 		sptr = str;	\
171 		slen = sizeof(str) - 1;	\
172 	} while(0)
173 
174 static void
175 sohashttpget(struct socket *so, void *arg, int waitflag)
176 {
177 
178 	if ((so->so_state & SS_CANTRCVMORE) == 0 && !sbfull(&so->so_rcv)) {
179 		struct mbuf *m;
180 		char *cmp;
181 		int	cmplen, cc;
182 
183 		m = so->so_rcv.ssb_mb;
184 		cc = so->so_rcv.ssb_cc - 1;
185 		if (cc < 1)
186 			return;
187 		switch (*mtod(m, char *)) {
188 		case 'G':
189 			STRSETUP(cmp, cmplen, "ET ");
190 			break;
191 		case 'H':
192 			STRSETUP(cmp, cmplen, "EAD ");
193 			break;
194 		default:
195 			goto fallout;
196 		}
197 		if (cc < cmplen) {
198 			if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) {
199 				DPRINT("short cc (%d) but mbufstrncmp ok", cc);
200 				return;
201 			} else {
202 				DPRINT("short cc (%d) mbufstrncmp failed", cc);
203 				goto fallout;
204 			}
205 		}
206 		if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) {
207 			DPRINT("mbufstrcmp ok");
208 			if (parse_http_version == 0)
209 				soishttpconnected(so, arg, waitflag);
210 			else
211 				soparsehttpvers(so, arg, waitflag);
212 			return;
213 		}
214 		DPRINT("mbufstrcmp bad");
215 	}
216 
217 fallout:
218 	DPRINT("fallout");
219 	so->so_upcall = NULL;
220 	atomic_clear_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
221 	soisconnected(so);
222 	return;
223 }
224 
225 static void
226 soparsehttpvers(struct socket *so, void *arg, int waitflag)
227 {
228 	struct mbuf *m, *n;
229 	int	i, cc, spaces, inspaces;
230 
231 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
232 		goto fallout;
233 
234 	m = so->so_rcv.ssb_mb;
235 	cc = so->so_rcv.ssb_cc;
236 	inspaces = spaces = 0;
237 	for (m = so->so_rcv.ssb_mb; m; m = n) {
238 		n = m->m_nextpkt;
239 		for (; m; m = m->m_next) {
240 			for (i = 0; i < m->m_len; i++, cc--) {
241 				switch (*(mtod(m, char *) + i)) {
242 				case ' ':
243 					if (!inspaces) {
244 						spaces++;
245 						inspaces = 1;
246 					}
247 					break;
248 				case '\r':
249 				case '\n':
250 					DPRINT("newline");
251 					goto fallout;
252 				default:
253 					if (spaces == 2) {
254 						/* make sure we have enough data left */
255 						if (cc < sizeof("HTTP/1.0") - 1) {
256 							if (mbufstrncmp(m, n, i, cc, "HTTP/1.") == 1) {
257 								DPRINT("mbufstrncmp ok");
258 								goto readmore;
259 							} else {
260 								DPRINT("mbufstrncmp bad");
261 								goto fallout;
262 							}
263 						} else if (mbufstrcmp(m, n, i, "HTTP/1.0") == 1 ||
264 									mbufstrcmp(m, n, i, "HTTP/1.1") == 1) {
265 								DPRINT("mbufstrcmp ok");
266 								soishttpconnected(so, arg, waitflag);
267 								return;
268 						} else {
269 							DPRINT("mbufstrcmp bad");
270 							goto fallout;
271 						}
272 					}
273 					inspaces = 0;
274 					break;
275 				}
276 			}
277 		}
278 	}
279 readmore:
280 	DPRINT("readmore");
281 	/*
282 	 * if we hit here we haven't hit something
283 	 * we don't understand or a newline, so try again
284 	 */
285 	so->so_upcall = soparsehttpvers;
286 	atomic_set_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
287 	return;
288 
289 fallout:
290 	DPRINT("fallout");
291 	so->so_upcall = NULL;
292 	atomic_clear_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
293 	soisconnected(so);
294 	return;
295 }
296 
297 
298 #define NCHRS 3
299 
300 static void
301 soishttpconnected(struct socket *so, void *arg, int waitflag)
302 {
303 	char a, b, c;
304 	struct mbuf *m, *n;
305 	int ccleft, copied;
306 
307 	DPRINT("start");
308 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
309 		goto gotit;
310 
311 	/*
312 	 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c
313 	 * copied - how much we've copied so far
314 	 * ccleft - how many bytes remaining in the socketbuffer
315 	 * just loop over the mbufs subtracting from 'ccleft' until we only
316 	 * have NCHRS left
317 	 */
318 	copied = 0;
319 	ccleft = so->so_rcv.ssb_cc;
320 	if (ccleft < NCHRS)
321 		goto readmore;
322 	a = b = c = '\0';
323 	for (m = so->so_rcv.ssb_mb; m; m = n) {
324 		n = m->m_nextpkt;
325 		for (; m; m = m->m_next) {
326 			ccleft -= m->m_len;
327 			if (ccleft <= NCHRS) {
328 				char *src;
329 				int tocopy;
330 
331 				tocopy = (NCHRS - ccleft) - copied;
332 				src = mtod(m, char *) + (m->m_len - tocopy);
333 
334 				while (tocopy--) {
335 					switch (copied++) {
336 					case 0:
337 						a = *src++;
338 						break;
339 					case 1:
340 						b = *src++;
341 						break;
342 					case 2:
343 						c = *src++;
344 						break;
345 					}
346 				}
347 			}
348 		}
349 	}
350 	if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) {
351 		/* we have all request headers */
352 		goto gotit;
353 	}
354 
355 readmore:
356 	so->so_upcall = soishttpconnected;
357 	atomic_set_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
358 	return;
359 
360 gotit:
361 	so->so_upcall = NULL;
362 	atomic_clear_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
363 	soisconnected(so);
364 	return;
365 }
366