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