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 | |