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