xref: /dragonfly/sys/net/accf_http/accf_http.c (revision 984263bc)
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  */
29 
30 #define ACCEPT_FILTER_MOD
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/sysproto.h>
35 #include <sys/kernel.h>
36 #include <sys/proc.h>
37 #include <sys/malloc.h>
38 #include <sys/unistd.h>
39 #include <sys/file.h>
40 #include <sys/fcntl.h>
41 #include <sys/protosw.h>
42 #include <sys/sysctl.h>
43 #include <sys/socket.h>
44 #include <sys/socketvar.h>
45 #include <sys/stat.h>
46 #include <sys/mbuf.h>
47 #include <sys/resource.h>
48 #include <sys/sysent.h>
49 #include <sys/resourcevar.h>
50 
51 /* check for GET/HEAD */
52 static void sohashttpget(struct socket *so, void *arg, int waitflag);
53 /* check for HTTP/1.0 or HTTP/1.1 */
54 static void soparsehttpvers(struct socket *so, void *arg, int waitflag);
55 /* check for end of HTTP/1.x request */
56 static void soishttpconnected(struct socket *so, void *arg, int waitflag);
57 /* strcmp on an mbuf chain */
58 static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp);
59 /* strncmp on an mbuf chain */
60 static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset,
61 	int max, char *cmp);
62 /* socketbuffer is full */
63 static int sbfull(struct sockbuf *sb);
64 
65 static struct accept_filter accf_http_filter = {
66 	"httpready",
67 	sohashttpget,
68 	NULL,
69 	NULL
70 };
71 
72 static moduledata_t accf_http_mod = {
73 	"accf_http",
74 	accept_filt_generic_mod_event,
75 	&accf_http_filter
76 };
77 
78 DECLARE_MODULE(accf_http, accf_http_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
79 
80 static int parse_http_version = 1;
81 
82 SYSCTL_NODE(_net_inet_accf, OID_AUTO, http, CTLFLAG_RW, 0,
83 "HTTP accept filter");
84 SYSCTL_INT(_net_inet_accf_http, OID_AUTO, parsehttpversion, CTLFLAG_RW,
85 &parse_http_version, 1,
86 "Parse http version so that non 1.x requests work");
87 
88 #ifdef ACCF_HTTP_DEBUG
89 #define DPRINT(fmt, args...) \
90 	do {	\
91 		printf("%s:%d: " fmt "\n", __func__, __LINE__ , ##args);	\
92 	} while (0)
93 #else
94 #define DPRINT(fmt, args...)
95 #endif
96 
97 static int
98 sbfull(struct sockbuf *sb)
99 {
100 
101 	DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, mbcnt(%ld) >= mbmax(%ld): %d",
102 		sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat,
103 		sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax);
104 	return(sb->sb_cc >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax);
105 }
106 
107 /*
108  * start at mbuf m, (must provide npkt if exists)
109  * starting at offset in m compare characters in mbuf chain for 'cmp'
110  */
111 static int
112 mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp)
113 {
114 	struct mbuf *n;
115 
116 	for (;m != NULL; m = n) {
117 		n = npkt;
118 		if (npkt)
119 			npkt = npkt->m_nextpkt;
120 		for (; m; m = m->m_next) {
121 			for (; offset < m->m_len; offset++, cmp++) {
122 				if (*cmp == '\0') {
123 					return (1);
124 				} else if (*cmp != *(mtod(m, char *) + offset)) {
125 					return (0);
126 				}
127 			}
128 			if (*cmp == '\0')
129 				return (1);
130 			offset = 0;
131 		}
132 	}
133 	return (0);
134 }
135 
136 /*
137  * start at mbuf m, (must provide npkt if exists)
138  * starting at offset in m compare characters in mbuf chain for 'cmp'
139  * stop at 'max' characters
140  */
141 static int
142 mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int max, char *cmp)
143 {
144 	struct mbuf *n;
145 
146 	for (;m != NULL; m = n) {
147 		n = npkt;
148 		if (npkt)
149 			npkt = npkt->m_nextpkt;
150 		for (; m; m = m->m_next) {
151 			for (; offset < m->m_len; offset++, cmp++, max--) {
152 				if (max == 0 || *cmp == '\0') {
153 					return (1);
154 				} else if (*cmp != *(mtod(m, char *) + offset)) {
155 					return (0);
156 				}
157 			}
158 			if (max == 0 || *cmp == '\0')
159 				return (1);
160 			offset = 0;
161 		}
162 	}
163 	return (0);
164 }
165 
166 #define STRSETUP(sptr, slen, str) \
167 	do {	\
168 		sptr = str;	\
169 		slen = sizeof(str) - 1;	\
170 	} while(0)
171 
172 static void
173 sohashttpget(struct socket *so, void *arg, int waitflag)
174 {
175 
176 	if ((so->so_state & SS_CANTRCVMORE) == 0 && !sbfull(&so->so_rcv)) {
177 		struct mbuf *m;
178 		char *cmp;
179 		int	cmplen, cc;
180 
181 		m = so->so_rcv.sb_mb;
182 		cc = so->so_rcv.sb_cc - 1;
183 		if (cc < 1)
184 			return;
185 		switch (*mtod(m, char *)) {
186 		case 'G':
187 			STRSETUP(cmp, cmplen, "ET ");
188 			break;
189 		case 'H':
190 			STRSETUP(cmp, cmplen, "EAD ");
191 			break;
192 		default:
193 			goto fallout;
194 		}
195 		if (cc < cmplen) {
196 			if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) {
197 				DPRINT("short cc (%d) but mbufstrncmp ok", cc);
198 				return;
199 			} else {
200 				DPRINT("short cc (%d) mbufstrncmp failed", cc);
201 				goto fallout;
202 			}
203 		}
204 		if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) {
205 			DPRINT("mbufstrcmp ok");
206 			if (parse_http_version == 0)
207 				soishttpconnected(so, arg, waitflag);
208 			else
209 				soparsehttpvers(so, arg, waitflag);
210 			return;
211 		}
212 		DPRINT("mbufstrcmp bad");
213 	}
214 
215 fallout:
216 	DPRINT("fallout");
217 	so->so_upcall = NULL;
218 	so->so_rcv.sb_flags &= ~SB_UPCALL;
219 	soisconnected(so);
220 	return;
221 }
222 
223 static void
224 soparsehttpvers(struct socket *so, void *arg, int waitflag)
225 {
226 	struct mbuf *m, *n;
227 	int	i, cc, spaces, inspaces;
228 
229 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
230 		goto fallout;
231 
232 	m = so->so_rcv.sb_mb;
233 	cc = so->so_rcv.sb_cc;
234 	inspaces = spaces = 0;
235 	for (m = so->so_rcv.sb_mb; m; m = n) {
236 		n = m->m_nextpkt;
237 		for (; m; m = m->m_next) {
238 			for (i = 0; i < m->m_len; i++, cc--) {
239 				switch (*(mtod(m, char *) + i)) {
240 				case ' ':
241 					if (!inspaces) {
242 						spaces++;
243 						inspaces = 1;
244 					}
245 					break;
246 				case '\r':
247 				case '\n':
248 					DPRINT("newline");
249 					goto fallout;
250 				default:
251 					if (spaces == 2) {
252 						/* make sure we have enough data left */
253 						if (cc < sizeof("HTTP/1.0") - 1) {
254 							if (mbufstrncmp(m, n, i, cc, "HTTP/1.") == 1) {
255 								DPRINT("mbufstrncmp ok");
256 								goto readmore;
257 							} else {
258 								DPRINT("mbufstrncmp bad");
259 								goto fallout;
260 							}
261 						} else if (mbufstrcmp(m, n, i, "HTTP/1.0") == 1 ||
262 									mbufstrcmp(m, n, i, "HTTP/1.1") == 1) {
263 								DPRINT("mbufstrcmp ok");
264 								soishttpconnected(so, arg, waitflag);
265 								return;
266 						} else {
267 							DPRINT("mbufstrcmp bad");
268 							goto fallout;
269 						}
270 					}
271 					inspaces = 0;
272 					break;
273 				}
274 			}
275 		}
276 	}
277 readmore:
278 	DPRINT("readmore");
279 	/*
280 	 * if we hit here we haven't hit something
281 	 * we don't understand or a newline, so try again
282 	 */
283 	so->so_upcall = soparsehttpvers;
284 	so->so_rcv.sb_flags |= SB_UPCALL;
285 	return;
286 
287 fallout:
288 	DPRINT("fallout");
289 	so->so_upcall = NULL;
290 	so->so_rcv.sb_flags &= ~SB_UPCALL;
291 	soisconnected(so);
292 	return;
293 }
294 
295 
296 #define NCHRS 3
297 
298 static void
299 soishttpconnected(struct socket *so, void *arg, int waitflag)
300 {
301 	char a, b, c;
302 	struct mbuf *m, *n;
303 	int ccleft, copied;
304 
305 	DPRINT("start");
306 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
307 		goto gotit;
308 
309 	/*
310 	 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c
311 	 * copied - how much we've copied so far
312 	 * ccleft - how many bytes remaining in the socketbuffer
313 	 * just loop over the mbufs subtracting from 'ccleft' until we only
314 	 * have NCHRS left
315 	 */
316 	copied = 0;
317 	ccleft = so->so_rcv.sb_cc;
318 	if (ccleft < NCHRS)
319 		goto readmore;
320 	a = b = c = '\0';
321 	for (m = so->so_rcv.sb_mb; m; m = n) {
322 		n = m->m_nextpkt;
323 		for (; m; m = m->m_next) {
324 			ccleft -= m->m_len;
325 			if (ccleft <= NCHRS) {
326 				char *src;
327 				int tocopy;
328 
329 				tocopy = (NCHRS - ccleft) - copied;
330 				src = mtod(m, char *) + (m->m_len - tocopy);
331 
332 				while (tocopy--) {
333 					switch (copied++) {
334 					case 0:
335 						a = *src++;
336 						break;
337 					case 1:
338 						b = *src++;
339 						break;
340 					case 2:
341 						c = *src++;
342 						break;
343 					}
344 				}
345 			}
346 		}
347 	}
348 	if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) {
349 		/* we have all request headers */
350 		goto gotit;
351 	}
352 
353 readmore:
354 	so->so_upcall = soishttpconnected;
355 	so->so_rcv.sb_flags |= SB_UPCALL;
356 	return;
357 
358 gotit:
359 	so->so_upcall = NULL;
360 	so->so_rcv.sb_flags &= ~SB_UPCALL;
361 	soisconnected(so);
362 	return;
363 }
364