xref: /netbsd-src/sys/netinet/accf_http.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: accf_http.c,v 1.9 2015/08/20 14:40:19 christos 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.9 2015/08/20 14:40:19 christos 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 #include "ioconf.h"
47 
48 MODULE(MODULE_CLASS_MISC, accf_httpready, NULL);
49 
50 /* check for GET/HEAD */
51 static void sohashttpget(struct socket *so, void *arg, int events, int waitflag);
52 /* check for HTTP/1.0 or HTTP/1.1 */
53 static void soparsehttpvers(struct socket *so, void *arg, int events, int waitflag);
54 /* check for end of HTTP/1.x request */
55 static void soishttpconnected(struct socket *so, void *arg, int events, int waitflag);
56 /* strcmp on an mbuf chain */
57 static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, const char *cmp);
58 /* strncmp on an mbuf chain */
59 static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset,
60 	int len, const char *cmp);
61 /* socketbuffer is full */
62 static int sbfull(struct sockbuf *sb);
63 
64 static struct accept_filter accf_http_filter = {
65 	.accf_name = "httpready",
66 	.accf_callback = sohashttpget,
67 };
68 
69 /*
70  * Names of HTTP Accept filter sysctl objects
71  */
72 
73 #define ACCFCTL_PARSEVER	1	/* Parse HTTP version */
74 
75 static int parse_http_version = 1;
76 
77 void
78 accf_httpattach(int junk)
79 {
80 
81 }
82 
83 static int
84 accf_httpready_modcmd(modcmd_t cmd, void *arg)
85 {
86 	static struct sysctllog *clog;
87 	int error;
88 
89 	switch (cmd) {
90 	case MODULE_CMD_INIT:
91 		error = accept_filt_add(&accf_http_filter);
92 		if (error != 0) {
93 			return error;
94 		}
95 		sysctl_createv(&clog, 0, NULL, NULL,
96 		       CTLFLAG_PERMANENT,
97 		       CTLTYPE_NODE, "inet", NULL,
98 		       NULL, 0, NULL, 0,
99 		       CTL_NET, PF_INET, CTL_EOL);
100 		sysctl_createv(&clog, 0, NULL, NULL,
101 		       CTLFLAG_PERMANENT,
102 		       CTLTYPE_NODE, "accf", NULL,
103 		       NULL, 0, NULL, 0,
104 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, CTL_EOL);
105 		sysctl_createv(&clog, 0, NULL, NULL,
106 		       CTLFLAG_PERMANENT,
107 		       CTLTYPE_NODE, "http",
108 		       SYSCTL_DESCR("HTTP accept filter"),
109 		       NULL, 0, NULL, 0,
110 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, ACCF_HTTP, CTL_EOL);
111 		sysctl_createv(&clog, 0, NULL, NULL,
112 		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
113 		       CTLTYPE_INT, "parsehttpversion",
114 		       SYSCTL_DESCR("Parse http version so that non "
115 				    "1.x requests work"),
116 		       NULL, 0, &parse_http_version, 0,
117 		       CTL_NET, PF_INET, SO_ACCEPTFILTER, ACCF_HTTP,
118 		       ACCFCTL_PARSEVER, CTL_EOL);
119 		return 0;
120 
121 	case MODULE_CMD_FINI:
122 		error = accept_filt_del(&accf_http_filter);
123 		if (error != 0) {
124 			return error;
125 		}
126 		sysctl_teardown(&clog);
127 		return 0;
128 
129 	default:
130 		return ENOTTY;
131 	}
132 }
133 
134 #ifdef ACCF_HTTP_DEBUG
135 #define DPRINT(fmt, args...)						\
136 	do {								\
137 		printf("%s:%d: " fmt "\n", __func__, __LINE__, ##args);	\
138 	} while (0)
139 #else
140 #define DPRINT(fmt, args...)
141 #endif
142 
143 static int
144 sbfull(struct sockbuf *sb)
145 {
146 
147 	DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, "
148 	    "mbcnt(%ld) >= mbmax(%ld): %d",
149 	    sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat,
150 	    sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax);
151 	return (sb->sb_cc >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax);
152 }
153 
154 /*
155  * start at mbuf m, (must provide npkt if exists)
156  * starting at offset in m compare characters in mbuf chain for 'cmp'
157  */
158 static int
159 mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, const char *cmp)
160 {
161 	struct mbuf *n;
162 
163 	for (; m != NULL; m = n) {
164 		n = npkt;
165 		if (npkt)
166 			npkt = npkt->m_nextpkt;
167 		for (; m; m = m->m_next) {
168 			for (; offset < m->m_len; offset++, cmp++) {
169 				if (*cmp == '\0')
170 					return (1);
171 				else if (*cmp != *(mtod(m, char *) + offset))
172 					return (0);
173 			}
174 			if (*cmp == '\0')
175 				return (1);
176 			offset = 0;
177 		}
178 	}
179 	return (0);
180 }
181 
182 /*
183  * start at mbuf m, (must provide npkt if exists)
184  * starting at offset in m compare characters in mbuf chain for 'cmp'
185  * stop at 'max' characters
186  */
187 static int
188 mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int len, const char *cmp)
189 {
190 	struct mbuf *n;
191 
192 	for (; m != NULL; m = n) {
193 		n = npkt;
194 		if (npkt)
195 			npkt = npkt->m_nextpkt;
196 		for (; m; m = m->m_next) {
197 			for (; offset < m->m_len; offset++, cmp++, len--) {
198 				if (len == 0 || *cmp == '\0')
199 					return (1);
200 				else if (*cmp != *(mtod(m, char *) + offset))
201 					return (0);
202 			}
203 			if (len == 0 || *cmp == '\0')
204 				return (1);
205 			offset = 0;
206 		}
207 	}
208 	return (0);
209 }
210 
211 #define STRSETUP(sptr, slen, str)					\
212 	do {								\
213 		sptr = str;						\
214 		slen = sizeof(str) - 1;					\
215 	} while(0)
216 
217 static void
218 sohashttpget(struct socket *so, void *arg, int events, int waitflag)
219 {
220 
221 	if ((so->so_state & SS_CANTRCVMORE) == 0 && !sbfull(&so->so_rcv)) {
222 		struct mbuf *m;
223 		const char *cmp;
224 		int	cmplen, cc;
225 
226 		m = so->so_rcv.sb_mb;
227 		cc = so->so_rcv.sb_cc - 1;
228 		if (cc < 1)
229 			return;
230 		switch (*mtod(m, char *)) {
231 		case 'G':
232 			STRSETUP(cmp, cmplen, "ET ");
233 			break;
234 		case 'H':
235 			STRSETUP(cmp, cmplen, "EAD ");
236 			break;
237 		default:
238 			goto fallout;
239 		}
240 		if (cc < cmplen) {
241 			if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) {
242 				DPRINT("short cc (%d) but mbufstrncmp ok", cc);
243 				return;
244 			} else {
245 				DPRINT("short cc (%d) mbufstrncmp failed", cc);
246 				goto fallout;
247 			}
248 		}
249 		if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) {
250 			DPRINT("mbufstrcmp ok");
251 			if (parse_http_version == 0)
252 				soishttpconnected(so, arg, events, waitflag);
253 			else
254 				soparsehttpvers(so, arg, events, waitflag);
255 			return;
256 		}
257 		DPRINT("mbufstrcmp bad");
258 	}
259 
260 fallout:
261 	DPRINT("fallout");
262 	so->so_upcall = NULL;
263 	so->so_rcv.sb_flags &= ~SB_UPCALL;
264 	soisconnected(so);
265 	return;
266 }
267 
268 static void
269 soparsehttpvers(struct socket *so, void *arg, int events, int waitflag)
270 {
271 	struct mbuf *m, *n;
272 	int	i, cc, spaces, inspaces;
273 
274 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
275 		goto fallout;
276 
277 	m = so->so_rcv.sb_mb;
278 	cc = so->so_rcv.sb_cc;
279 	inspaces = spaces = 0;
280 	for (m = so->so_rcv.sb_mb; m; m = n) {
281 		n = m->m_nextpkt;
282 		for (; m; m = m->m_next) {
283 			for (i = 0; i < m->m_len; i++, cc--) {
284 				switch (*(mtod(m, char *) + i)) {
285 				case ' ':
286 					/* tabs? '\t' */
287 					if (!inspaces) {
288 						spaces++;
289 						inspaces = 1;
290 					}
291 					break;
292 				case '\r':
293 				case '\n':
294 					DPRINT("newline");
295 					goto fallout;
296 				default:
297 					if (spaces != 2) {
298 						inspaces = 0;
299 						break;
300 					}
301 
302 					/*
303 					 * if we don't have enough characters
304 					 * left (cc < sizeof("HTTP/1.0") - 1)
305 					 * then see if the remaining ones
306 					 * are a request we can parse.
307 					 */
308 					if (cc < sizeof("HTTP/1.0") - 1) {
309 						if (mbufstrncmp(m, n, i, cc,
310 							"HTTP/1.") == 1) {
311 							DPRINT("ok");
312 							goto readmore;
313 						} else {
314 							DPRINT("bad");
315 							goto fallout;
316 						}
317 					} else if (
318 					    mbufstrcmp(m, n, i, "HTTP/1.0") ||
319 					    mbufstrcmp(m, n, i, "HTTP/1.1")) {
320 						DPRINT("ok");
321 						soishttpconnected(so,
322 						    arg, events, waitflag);
323 						return;
324 					} else {
325 						DPRINT("bad");
326 						goto fallout;
327 					}
328 				}
329 			}
330 		}
331 	}
332 readmore:
333 	DPRINT("readmore");
334 	/*
335 	 * if we hit here we haven't hit something
336 	 * we don't understand or a newline, so try again
337 	 */
338 	so->so_upcall = soparsehttpvers;
339 	so->so_rcv.sb_flags |= SB_UPCALL;
340 	return;
341 
342 fallout:
343 	DPRINT("fallout");
344 	so->so_upcall = NULL;
345 	so->so_rcv.sb_flags &= ~SB_UPCALL;
346 	soisconnected(so);
347 	return;
348 }
349 
350 
351 #define NCHRS 3
352 
353 static void
354 soishttpconnected(struct socket *so, void *arg, int events, int waitflag)
355 {
356 	char a, b, c;
357 	struct mbuf *m, *n;
358 	int ccleft, copied;
359 
360 	DPRINT("start");
361 	if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv))
362 		goto gotit;
363 
364 	/*
365 	 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c
366 	 * copied - how much we've copied so far
367 	 * ccleft - how many bytes remaining in the socketbuffer
368 	 * just loop over the mbufs subtracting from 'ccleft' until we only
369 	 * have NCHRS left
370 	 */
371 	copied = 0;
372 	ccleft = so->so_rcv.sb_cc;
373 	if (ccleft < NCHRS)
374 		goto readmore;
375 	a = b = c = '\0';
376 	for (m = so->so_rcv.sb_mb; m; m = n) {
377 		n = m->m_nextpkt;
378 		for (; m; m = m->m_next) {
379 			ccleft -= m->m_len;
380 			if (ccleft <= NCHRS) {
381 				char *src;
382 				int tocopy;
383 
384 				tocopy = (NCHRS - ccleft) - copied;
385 				src = mtod(m, char *) + (m->m_len - tocopy);
386 
387 				while (tocopy--) {
388 					switch (copied++) {
389 					case 0:
390 						a = *src++;
391 						break;
392 					case 1:
393 						b = *src++;
394 						break;
395 					case 2:
396 						c = *src++;
397 						break;
398 					}
399 				}
400 			}
401 		}
402 	}
403 	if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) {
404 		/* we have all request headers */
405 		goto gotit;
406 	}
407 
408 readmore:
409 	so->so_upcall = soishttpconnected;
410 	so->so_rcv.sb_flags |= SB_UPCALL;
411 	return;
412 
413 gotit:
414 	so->so_upcall = NULL;
415 	so->so_rcv.sb_flags &= ~SB_UPCALL;
416 	soisconnected(so);
417 	return;
418 }
419