xref: /netbsd-src/sys/netinet/accf_http.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: accf_http.c,v 1.7 2009/09/02 14:56:57 tls Exp $	*/
2 
3 /*-
4  * Copyright (c) 2000 Paycounter, Inc.
5  * Author: Alfred Perlstein <alfred@paycounter.com>, <alfred@FreeBSD.org>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __KERNEL_RCSID(0, "$NetBSD: accf_http.c,v 1.7 2009/09/02 14:56:57 tls Exp $");
32 
33 #define ACCEPT_FILTER_MOD
34 
35 #include <sys/param.h>
36 #include <sys/kernel.h>
37 #include <sys/mbuf.h>
38 #include <sys/module.h>
39 #include <sys/signalvar.h>
40 #include <sys/sysctl.h>
41 #include <sys/socket.h>
42 #include <sys/socketvar.h>
43 
44 #include <netinet/accept_filter.h>
45 
46 MODULE(MODULE_CLASS_MISC, accf_httpready, NULL);
47 
48 /* check for GET/HEAD */
49 static void sohashttpget(struct socket *so, void *arg, int events, int waitflag);
50 /* check for HTTP/1.0 or HTTP/1.1 */
51 static void soparsehttpvers(struct socket *so, void *arg, int events, int waitflag);
52 /* check for end of HTTP/1.x request */
53 static void soishttpconnected(struct socket *so, void *arg, int events, int waitflag);
54 /* strcmp on an mbuf chain */
55 static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, const char *cmp);
56 /* strncmp on an mbuf chain */
57 static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset,
58 	int len, const char *cmp);
59 /* socketbuffer is full */
60 static int sbfull(struct sockbuf *sb);
61 
62 static struct accept_filter accf_http_filter = {
63 	.accf_name = "httpready",
64 	.accf_callback = sohashttpget,
65 };
66 
67 /*
68  * Names of HTTP Accept filter sysctl objects
69  */
70 
71 #define ACCFCTL_PARSEVER	1	/* Parse HTTP version */
72 
73 static int parse_http_version = 1;
74 
75 /* XXX pseudo-device */
76 void	accf_httpattach(int);
77 
78 void
79 accf_httpattach(int junk)
80 {
81 
82 }
83 
84 static int
85 accf_httpready_modcmd(modcmd_t cmd, void *arg)
86 {
87 	static struct sysctllog *clog;
88 	int error;
89 
90 	switch (cmd) {
91 	case MODULE_CMD_INIT:
92 		error = accept_filt_add(&accf_http_filter);
93 		if (error != 0) {
94 			return error;
95 		}
96 		sysctl_createv(&clog, 0, NULL, NULL,
97 		       CTLFLAG_PERMANENT,
98 		       CTLTYPE_NODE, "net", NULL,
99 		       NULL, 0, NULL, 0,
100 		       CTL_NET, CTL_EOL);
101 		sysctl_createv(&clog, 0, NULL, NULL,
102 		       CTLFLAG_PERMANENT,
103 		       CTLTYPE_NODE, "inet", NULL,
104 		       NULL, 0, NULL, 0,
105 		       CTL_NET, PF_INET, CTL_EOL);
106 		sysctl_createv(&clog, 0, NULL, NULL,
107 		       CTLFLAG_PERMANENT,
108 		       CTLTYPE_NODE, "accf", NULL,
109 		       NULL, 0, NULL, 0,
110 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, CTL_EOL);
111 		sysctl_createv(&clog, 0, NULL, NULL,
112 		       CTLFLAG_PERMANENT,
113 		       CTLTYPE_NODE, "http",
114 		       SYSCTL_DESCR("HTTP accept filter"),
115 		       NULL, 0, NULL, 0,
116 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, ACCF_HTTP, CTL_EOL);
117 		sysctl_createv(&clog, 0, NULL, NULL,
118 		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
119 		       CTLTYPE_INT, "parsehttpversion",
120 		       SYSCTL_DESCR("Parse http version so that non "
121 				    "1.x requests work"),
122 		       NULL, 0, &parse_http_version, 0,
123 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, ACCF_HTTP,
124 		       ACCFCTL_PARSEVER, CTL_EOL);
125 		return 0;
126 
127 	case MODULE_CMD_FINI:
128 		error = accept_filt_del(&accf_http_filter);
129 		if (error != 0) {
130 			return error;
131 		}
132 		sysctl_teardown(&clog);
133 		return 0;
134 
135 	default:
136 		return ENOTTY;
137 	}
138 }
139 
140 #ifdef ACCF_HTTP_DEBUG
141 #define DPRINT(fmt, args...)						\
142 	do {								\
143 		printf("%s:%d: " fmt "\n", __func__, __LINE__, ##args);	\
144 	} while (0)
145 #else
146 #define DPRINT(fmt, args...)
147 #endif
148 
149 static int
150 sbfull(struct sockbuf *sb)
151 {
152 
153 	DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, "
154 	    "mbcnt(%ld) >= mbmax(%ld): %d",
155 	    sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat,
156 	    sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax);
157 	return (sb->sb_cc >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax);
158 }
159 
160 /*
161  * start at mbuf m, (must provide npkt if exists)
162  * starting at offset in m compare characters in mbuf chain for 'cmp'
163  */
164 static int
165 mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, const char *cmp)
166 {
167 	struct mbuf *n;
168 
169 	for (; m != NULL; m = n) {
170 		n = npkt;
171 		if (npkt)
172 			npkt = npkt->m_nextpkt;
173 		for (; m; m = m->m_next) {
174 			for (; offset < m->m_len; offset++, cmp++) {
175 				if (*cmp == '\0')
176 					return (1);
177 				else if (*cmp != *(mtod(m, char *) + offset))
178 					return (0);
179 			}
180 			if (*cmp == '\0')
181 				return (1);
182 			offset = 0;
183 		}
184 	}
185 	return (0);
186 }
187 
188 /*
189  * start at mbuf m, (must provide npkt if exists)
190  * starting at offset in m compare characters in mbuf chain for 'cmp'
191  * stop at 'max' characters
192  */
193 static int
194 mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int len, const char *cmp)
195 {
196 	struct mbuf *n;
197 
198 	for (; m != NULL; m = n) {
199 		n = npkt;
200 		if (npkt)
201 			npkt = npkt->m_nextpkt;
202 		for (; m; m = m->m_next) {
203 			for (; offset < m->m_len; offset++, cmp++, len--) {
204 				if (len == 0 || *cmp == '\0')
205 					return (1);
206 				else if (*cmp != *(mtod(m, char *) + offset))
207 					return (0);
208 			}
209 			if (len == 0 || *cmp == '\0')
210 				return (1);
211 			offset = 0;
212 		}
213 	}
214 	return (0);
215 }
216 
217 #define STRSETUP(sptr, slen, str)					\
218 	do {								\
219 		sptr = str;						\
220 		slen = sizeof(str) - 1;					\
221 	} while(0)
222 
223 static void
224 sohashttpget(struct socket *so, void *arg, int events, int waitflag)
225 {
226 
227 	if ((so->so_state & SS_CANTRCVMORE) == 0 && !sbfull(&so->so_rcv)) {
228 		struct mbuf *m;
229 		const char *cmp;
230 		int	cmplen, cc;
231 
232 		m = so->so_rcv.sb_mb;
233 		cc = so->so_rcv.sb_cc - 1;
234 		if (cc < 1)
235 			return;
236 		switch (*mtod(m, char *)) {
237 		case 'G':
238 			STRSETUP(cmp, cmplen, "ET ");
239 			break;
240 		case 'H':
241 			STRSETUP(cmp, cmplen, "EAD ");
242 			break;
243 		default:
244 			goto fallout;
245 		}
246 		if (cc < cmplen) {
247 			if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) {
248 				DPRINT("short cc (%d) but mbufstrncmp ok", cc);
249 				return;
250 			} else {
251 				DPRINT("short cc (%d) mbufstrncmp failed", cc);
252 				goto fallout;
253 			}
254 		}
255 		if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) {
256 			DPRINT("mbufstrcmp ok");
257 			if (parse_http_version == 0)
258 				soishttpconnected(so, arg, events, waitflag);
259 			else
260 				soparsehttpvers(so, arg, events, waitflag);
261 			return;
262 		}
263 		DPRINT("mbufstrcmp bad");
264 	}
265 
266 fallout:
267 	DPRINT("fallout");
268 	so->so_upcall = NULL;
269 	so->so_rcv.sb_flags &= ~SB_UPCALL;
270 	soisconnected(so);
271 	return;
272 }
273 
274 static void
275 soparsehttpvers(struct socket *so, void *arg, int events, int waitflag)
276 {
277 	struct mbuf *m, *n;
278 	int	i, cc, spaces, inspaces;
279 
280 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
281 		goto fallout;
282 
283 	m = so->so_rcv.sb_mb;
284 	cc = so->so_rcv.sb_cc;
285 	inspaces = spaces = 0;
286 	for (m = so->so_rcv.sb_mb; m; m = n) {
287 		n = m->m_nextpkt;
288 		for (; m; m = m->m_next) {
289 			for (i = 0; i < m->m_len; i++, cc--) {
290 				switch (*(mtod(m, char *) + i)) {
291 				case ' ':
292 					/* tabs? '\t' */
293 					if (!inspaces) {
294 						spaces++;
295 						inspaces = 1;
296 					}
297 					break;
298 				case '\r':
299 				case '\n':
300 					DPRINT("newline");
301 					goto fallout;
302 				default:
303 					if (spaces != 2) {
304 						inspaces = 0;
305 						break;
306 					}
307 
308 					/*
309 					 * if we don't have enough characters
310 					 * left (cc < sizeof("HTTP/1.0") - 1)
311 					 * then see if the remaining ones
312 					 * are a request we can parse.
313 					 */
314 					if (cc < sizeof("HTTP/1.0") - 1) {
315 						if (mbufstrncmp(m, n, i, cc,
316 							"HTTP/1.") == 1) {
317 							DPRINT("ok");
318 							goto readmore;
319 						} else {
320 							DPRINT("bad");
321 							goto fallout;
322 						}
323 					} else if (
324 					    mbufstrcmp(m, n, i, "HTTP/1.0") ||
325 					    mbufstrcmp(m, n, i, "HTTP/1.1")) {
326 						DPRINT("ok");
327 						soishttpconnected(so,
328 						    arg, events, waitflag);
329 						return;
330 					} else {
331 						DPRINT("bad");
332 						goto fallout;
333 					}
334 				}
335 			}
336 		}
337 	}
338 readmore:
339 	DPRINT("readmore");
340 	/*
341 	 * if we hit here we haven't hit something
342 	 * we don't understand or a newline, so try again
343 	 */
344 	so->so_upcall = soparsehttpvers;
345 	so->so_rcv.sb_flags |= SB_UPCALL;
346 	return;
347 
348 fallout:
349 	DPRINT("fallout");
350 	so->so_upcall = NULL;
351 	so->so_rcv.sb_flags &= ~SB_UPCALL;
352 	soisconnected(so);
353 	return;
354 }
355 
356 
357 #define NCHRS 3
358 
359 static void
360 soishttpconnected(struct socket *so, void *arg, int events, int waitflag)
361 {
362 	char a, b, c;
363 	struct mbuf *m, *n;
364 	int ccleft, copied;
365 
366 	DPRINT("start");
367 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
368 		goto gotit;
369 
370 	/*
371 	 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c
372 	 * copied - how much we've copied so far
373 	 * ccleft - how many bytes remaining in the socketbuffer
374 	 * just loop over the mbufs subtracting from 'ccleft' until we only
375 	 * have NCHRS left
376 	 */
377 	copied = 0;
378 	ccleft = so->so_rcv.sb_cc;
379 	if (ccleft < NCHRS)
380 		goto readmore;
381 	a = b = c = '\0';
382 	for (m = so->so_rcv.sb_mb; m; m = n) {
383 		n = m->m_nextpkt;
384 		for (; m; m = m->m_next) {
385 			ccleft -= m->m_len;
386 			if (ccleft <= NCHRS) {
387 				char *src;
388 				int tocopy;
389 
390 				tocopy = (NCHRS - ccleft) - copied;
391 				src = mtod(m, char *) + (m->m_len - tocopy);
392 
393 				while (tocopy--) {
394 					switch (copied++) {
395 					case 0:
396 						a = *src++;
397 						break;
398 					case 1:
399 						b = *src++;
400 						break;
401 					case 2:
402 						c = *src++;
403 						break;
404 					}
405 				}
406 			}
407 		}
408 	}
409 	if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) {
410 		/* we have all request headers */
411 		goto gotit;
412 	}
413 
414 readmore:
415 	so->so_upcall = soishttpconnected;
416 	so->so_rcv.sb_flags |= SB_UPCALL;
417 	return;
418 
419 gotit:
420 	so->so_upcall = NULL;
421 	so->so_rcv.sb_flags &= ~SB_UPCALL;
422 	soisconnected(so);
423 	return;
424 }
425