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