1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 #define ACCEPT_FILTER_MOD 32 33 #include <sys/param.h> 34 #include <sys/kernel.h> 35 #include <sys/mbuf.h> 36 #include <sys/module.h> 37 #include <sys/signalvar.h> 38 #include <sys/sysctl.h> 39 #include <sys/socketvar.h> 40 41 /* check for GET/HEAD */ 42 static int sohashttpget(struct socket *so, void *arg, int waitflag); 43 /* check for HTTP/1.0 or HTTP/1.1 */ 44 static int soparsehttpvers(struct socket *so, void *arg, int waitflag); 45 /* check for end of HTTP/1.x request */ 46 static int soishttpconnected(struct socket *so, void *arg, int waitflag); 47 /* strcmp on an mbuf chain */ 48 static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp); 49 /* strncmp on an mbuf chain */ 50 static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, 51 int max, char *cmp); 52 /* socketbuffer is full */ 53 static int sbfull(struct sockbuf *sb); 54 55 ACCEPT_FILTER_DEFINE(accf_http, "httpready", sohashttpget, NULL, NULL, 1); 56 57 static int parse_http_version = 1; 58 59 static SYSCTL_NODE(_net_inet_accf, OID_AUTO, http, 60 CTLFLAG_RW | CTLFLAG_MPSAFE, 0, 61 "HTTP accept filter"); 62 SYSCTL_INT(_net_inet_accf_http, OID_AUTO, parsehttpversion, CTLFLAG_RW, 63 &parse_http_version, 1, 64 "Parse http version so that non 1.x requests work"); 65 66 #ifdef ACCF_HTTP_DEBUG 67 #define DPRINT(fmt, args...) \ 68 do { \ 69 printf("%s:%d: " fmt "\n", __func__, __LINE__, ##args); \ 70 } while (0) 71 #else 72 #define DPRINT(fmt, args...) 73 #endif 74 75 static int 76 sbfull(struct sockbuf *sb) 77 { 78 79 DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, " 80 "mbcnt(%ld) >= mbmax(%ld): %d", 81 sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat, 82 sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax); 83 return (sbused(sb) >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax); 84 } 85 86 /* 87 * start at mbuf m, (must provide npkt if exists) 88 * starting at offset in m compare characters in mbuf chain for 'cmp' 89 */ 90 static int 91 mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp) 92 { 93 struct mbuf *n; 94 95 for (; m != NULL; m = n) { 96 n = npkt; 97 if (npkt) 98 npkt = npkt->m_nextpkt; 99 for (; m; m = m->m_next) { 100 for (; offset < m->m_len; offset++, cmp++) { 101 if (*cmp == '\0') 102 return (1); 103 else if (*cmp != *(mtod(m, char *) + offset)) 104 return (0); 105 } 106 if (*cmp == '\0') 107 return (1); 108 offset = 0; 109 } 110 } 111 return (0); 112 } 113 114 /* 115 * start at mbuf m, (must provide npkt if exists) 116 * starting at offset in m compare characters in mbuf chain for 'cmp' 117 * stop at 'max' characters 118 */ 119 static int 120 mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int max, char *cmp) 121 { 122 struct mbuf *n; 123 124 for (; m != NULL; m = n) { 125 n = npkt; 126 if (npkt) 127 npkt = npkt->m_nextpkt; 128 for (; m; m = m->m_next) { 129 for (; offset < m->m_len; offset++, cmp++, max--) { 130 if (max == 0 || *cmp == '\0') 131 return (1); 132 else if (*cmp != *(mtod(m, char *) + offset)) 133 return (0); 134 } 135 if (max == 0 || *cmp == '\0') 136 return (1); 137 offset = 0; 138 } 139 } 140 return (0); 141 } 142 143 #define STRSETUP(sptr, slen, str) \ 144 do { \ 145 sptr = str; \ 146 slen = sizeof(str) - 1; \ 147 } while(0) 148 149 static int 150 sohashttpget(struct socket *so, void *arg, int waitflag) 151 { 152 153 if ((so->so_rcv.sb_state & SBS_CANTRCVMORE) == 0 && 154 !sbfull(&so->so_rcv)) { 155 struct mbuf *m; 156 char *cmp; 157 int cmplen, cc; 158 159 m = so->so_rcv.sb_mb; 160 cc = sbavail(&so->so_rcv) - 1; 161 if (cc < 1) 162 return (SU_OK); 163 switch (*mtod(m, char *)) { 164 case 'G': 165 STRSETUP(cmp, cmplen, "ET "); 166 break; 167 case 'H': 168 STRSETUP(cmp, cmplen, "EAD "); 169 break; 170 default: 171 goto fallout; 172 } 173 if (cc < cmplen) { 174 if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) { 175 DPRINT("short cc (%d) but mbufstrncmp ok", cc); 176 return (SU_OK); 177 } else { 178 DPRINT("short cc (%d) mbufstrncmp failed", cc); 179 goto fallout; 180 } 181 } 182 if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) { 183 DPRINT("mbufstrcmp ok"); 184 if (parse_http_version == 0) 185 return (soishttpconnected(so, arg, waitflag)); 186 else 187 return (soparsehttpvers(so, arg, waitflag)); 188 } 189 DPRINT("mbufstrcmp bad"); 190 } 191 192 fallout: 193 DPRINT("fallout"); 194 return (SU_ISCONNECTED); 195 } 196 197 static int 198 soparsehttpvers(struct socket *so, void *arg, int waitflag) 199 { 200 struct mbuf *m, *n; 201 int i, cc, spaces, inspaces; 202 203 if ((so->so_rcv.sb_state & SBS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv)) 204 goto fallout; 205 206 m = so->so_rcv.sb_mb; 207 cc = sbavail(&so->so_rcv); 208 inspaces = spaces = 0; 209 for (m = so->so_rcv.sb_mb; m; m = n) { 210 n = m->m_nextpkt; 211 for (; m; m = m->m_next) { 212 for (i = 0; i < m->m_len; i++, cc--) { 213 switch (*(mtod(m, char *) + i)) { 214 case ' ': 215 /* tabs? '\t' */ 216 if (!inspaces) { 217 spaces++; 218 inspaces = 1; 219 } 220 break; 221 case '\r': 222 case '\n': 223 DPRINT("newline"); 224 goto fallout; 225 default: 226 if (spaces != 2) { 227 inspaces = 0; 228 break; 229 } 230 231 /* 232 * if we don't have enough characters 233 * left (cc < sizeof("HTTP/1.0") - 1) 234 * then see if the remaining ones 235 * are a request we can parse. 236 */ 237 if (cc < sizeof("HTTP/1.0") - 1) { 238 if (mbufstrncmp(m, n, i, cc, 239 "HTTP/1.") == 1) { 240 DPRINT("ok"); 241 goto readmore; 242 } else { 243 DPRINT("bad"); 244 goto fallout; 245 } 246 } else if ( 247 mbufstrcmp(m, n, i, "HTTP/1.0") || 248 mbufstrcmp(m, n, i, "HTTP/1.1")) { 249 DPRINT("ok"); 250 return (soishttpconnected(so, 251 arg, waitflag)); 252 } else { 253 DPRINT("bad"); 254 goto fallout; 255 } 256 } 257 } 258 } 259 } 260 readmore: 261 DPRINT("readmore"); 262 /* 263 * if we hit here we haven't hit something 264 * we don't understand or a newline, so try again 265 */ 266 soupcall_set(so, SO_RCV, soparsehttpvers, arg); 267 return (SU_OK); 268 269 fallout: 270 DPRINT("fallout"); 271 return (SU_ISCONNECTED); 272 } 273 274 #define NCHRS 3 275 276 static int 277 soishttpconnected(struct socket *so, void *arg, int waitflag) 278 { 279 char a, b, c; 280 struct mbuf *m, *n; 281 int ccleft, copied; 282 283 DPRINT("start"); 284 if ((so->so_rcv.sb_state & SBS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv)) 285 goto gotit; 286 287 /* 288 * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c 289 * copied - how much we've copied so far 290 * ccleft - how many bytes remaining in the socketbuffer 291 * just loop over the mbufs subtracting from 'ccleft' until we only 292 * have NCHRS left 293 */ 294 copied = 0; 295 ccleft = sbavail(&so->so_rcv); 296 if (ccleft < NCHRS) 297 goto readmore; 298 a = b = c = '\0'; 299 for (m = so->so_rcv.sb_mb; m; m = n) { 300 n = m->m_nextpkt; 301 for (; m; m = m->m_next) { 302 ccleft -= m->m_len; 303 if (ccleft <= NCHRS) { 304 char *src; 305 int tocopy; 306 307 tocopy = (NCHRS - ccleft) - copied; 308 src = mtod(m, char *) + (m->m_len - tocopy); 309 310 while (tocopy--) { 311 switch (copied++) { 312 case 0: 313 a = *src++; 314 break; 315 case 1: 316 b = *src++; 317 break; 318 case 2: 319 c = *src++; 320 break; 321 } 322 } 323 } 324 } 325 } 326 if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) { 327 /* we have all request headers */ 328 goto gotit; 329 } 330 331 readmore: 332 soupcall_set(so, SO_RCV, soishttpconnected, arg); 333 return (SU_OK); 334 335 gotit: 336 return (SU_ISCONNECTED); 337 } 338