diff options
-rw-r--r-- | sys/kern/uipc_accf.c | 25 | ||||
-rw-r--r-- | sys/netinet/accf_http.c | 342 | ||||
-rw-r--r-- | sys/sys/socketvar.h | 1 |
3 files changed, 277 insertions, 91 deletions
diff --git a/sys/kern/uipc_accf.c b/sys/kern/uipc_accf.c index e6b7d61..08cca1e 100644 --- a/sys/kern/uipc_accf.c +++ b/sys/kern/uipc_accf.c @@ -1,5 +1,6 @@ -/*- - * Copyright (c) 2000 Alfred Perlstein <alfred@FreeBSD.org> +/* + * Copyright (c) 2000 Paycounter, Inc. + * Author: Alfred Perlstein <alfred@paycounter.com>, <alfred@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +38,7 @@ #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/protosw.h> +#include <sys/sysctl.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/queue.h> @@ -46,6 +48,13 @@ static SLIST_HEAD(, accept_filter) accept_filtlsthd = MALLOC_DEFINE(M_ACCF, "accf", "accept filter data"); +static int unloadable = 0; + +SYSCTL_DECL(_net_inet); /* XXX: some header should do this for me */ +SYSCTL_NODE(_net_inet, OID_AUTO, accf, CTLFLAG_RW, 0, "Accept filters"); +SYSCTL_INT(_net_inet_accf, OID_AUTO, unloadable, CTLFLAG_RW, &unloadable, 0, + "Allow unload of accept filters (not recommended)"); + /* * must be passed a malloc'd structure so we don't explode if the kld * is unloaded, we leak the struct on deallocation to deal with this, @@ -121,12 +130,12 @@ accept_filt_generic_mod_event(module_t mod, int event, void *data) * is a bad thing. A simple fix would be to track the refcount * in the struct accept_filter. */ -#if 0 - s = splnet(); - error = accept_filt_del(accfp->accf_name); - splx(s); -#endif - error = EOPNOTSUPP; + if (unloadable != 0) { + s = splnet(); + error = accept_filt_del(accfp->accf_name); + splx(s); + } else + error = EOPNOTSUPP; break; case MOD_SHUTDOWN: diff --git a/sys/netinet/accf_http.c b/sys/netinet/accf_http.c index 09a4e9f..6f1c7a8 100644 --- a/sys/netinet/accf_http.c +++ b/sys/netinet/accf_http.c @@ -1,5 +1,6 @@ -/*- - * Copyright (c) 2000 Alfred Perlstein <alfred@FreeBSD.org> +/* + * Copyright (c) 2000 Paycounter, Inc. + * Author: Alfred Perlstein <alfred@paycounter.com>, <alfred@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +39,7 @@ #include <sys/file.h> #include <sys/fcntl.h> #include <sys/protosw.h> +#include <sys/sysctl.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/stat.h> @@ -46,16 +48,19 @@ #include <sys/sysent.h> #include <sys/resourcevar.h> -/* - * XXX: doesn't work with 0.9 requests, make a seperate filter - * based on this one if you want to decode those. - */ - -/* check for GET */ +/* check for GET/HEAD */ static void sohashttpget(struct socket *so, void *arg, int waitflag); -/* check for end of HTTP request */ +/* check for HTTP/1.0 or HTTP/1.1 */ +static void soparsehttpvers(struct socket *so, void *arg, int waitflag); +/* check for end of HTTP/1.x request */ static void soishttpconnected(struct socket *so, void *arg, int waitflag); -static char sbindex(struct mbuf **mp, int *begin, int end); +/* strcmp on an mbuf chain */ +static int mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp); +/* strncmp on an mbuf chain */ +static int mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, + int max, char *cmp); +/* socketbuffer is full */ +static int sbfull(struct sockbuf *sb); static struct accept_filter accf_http_filter = { "httpready", @@ -72,45 +77,139 @@ static moduledata_t accf_http_mod = { DECLARE_MODULE(accf_http, accf_http_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); +static int parse_http_version = 1; -static char -sbindex(struct mbuf **mp, int *begin, int end) +SYSCTL_NODE(_net_inet_accf, OID_AUTO, http, CTLFLAG_RW, 0, +"HTTP accept filter"); +SYSCTL_INT(_net_inet_accf_http, OID_AUTO, parsehttpversion, CTLFLAG_RW, +&parse_http_version, 1, +"Parse http version so that non 1.x requests work"); + +#ifdef ACCF_HTTP_DEBUG +#define DPRINT(fmt, args...) \ + do { \ + printf("%s:%d: " fmt "\n", __func__, __LINE__ , ##args); \ + } while (0) +#else +#define DPRINT(fmt, args...) +#endif + +static int +sbfull(struct sockbuf *sb) { - struct mbuf *m = *mp; - int diff = end - *begin + 1; - - while (m->m_len < diff) { - *begin += m->m_len; - diff -= m->m_len; - if (m->m_next) { - m = m->m_next; - } else if (m->m_nextpkt) { - m = m->m_nextpkt; - } else { - /* only happens if end > data in socket buffer */ - panic("sbindex: not enough data"); + + DPRINT("sbfull, cc(%ld) >= hiwat(%ld): %d, mbcnt(%ld) >= mbmax(%ld): %d", + sb->sb_cc, sb->sb_hiwat, sb->sb_cc >= sb->sb_hiwat, + sb->sb_mbcnt, sb->sb_mbmax, sb->sb_mbcnt >= sb->sb_mbmax); + return(sb->sb_cc >= sb->sb_hiwat || sb->sb_mbcnt >= sb->sb_mbmax); +} + +/* + * start at mbuf m, (must provide npkt if exists) + * starting at offset in m compare characters in mbuf chain for 'cmp' + */ +static int +mbufstrcmp(struct mbuf *m, struct mbuf *npkt, int offset, char *cmp) +{ + struct mbuf *n; + + for (;m != NULL; m = n) { + n = npkt; + if (npkt) + npkt = npkt->m_nextpkt; + for (; m; m = m->m_next) { + for (; offset < m->m_len; offset++, cmp++) { + if (*cmp == '\0') { + return (1); + } else if (*cmp != *(mtod(m, char *) + offset)) { + return (0); + } + } + offset = 0; } } - *mp = m; - return *(mtod(m, char *) + diff - 1); + return (0); } +/* + * start at mbuf m, (must provide npkt if exists) + * starting at offset in m compare characters in mbuf chain for 'cmp' + * stop at 'max' characters + */ +static int +mbufstrncmp(struct mbuf *m, struct mbuf *npkt, int offset, int max, char *cmp) +{ + struct mbuf *n; + + for (;m != NULL; m = n) { + n = npkt; + if (npkt) + npkt = npkt->m_nextpkt; + for (; m; m = m->m_next) { + for (; offset < m->m_len; offset++, cmp++, max--) { + if (max == 0 || *cmp == '\0') { + return (1); + } else if (*cmp != *(mtod(m, char *) + offset)) { + return (0); + } + } + offset = 0; + } + } + return (0); +} + +#define STRSETUP(sptr, slen, str) \ + do { \ + sptr = str; \ + slen = sizeof(str) - 1; \ + } while(0) + static void sohashttpget(struct socket *so, void *arg, int waitflag) { - if ((so->so_state & SS_CANTRCVMORE) == 0) { + if ((so->so_state & SS_CANTRCVMORE) == 0 || !sbfull(&so->so_rcv)) { struct mbuf *m; + char *cmp; + int cmplen, cc; - if (so->so_rcv.sb_cc < 6) - return; m = so->so_rcv.sb_mb; - if (bcmp(mtod(m, char *), "GET ", 4) == 0) { - soishttpconnected(so, arg, waitflag); + cc = so->so_rcv.sb_cc - 1; + if (cc < 1) + return; + switch (*mtod(m, char *)) { + case 'G': + STRSETUP(cmp, cmplen, "ET "); + break; + case 'H': + STRSETUP(cmp, cmplen, "EAD "); + break; + default: + goto fallout; + } + if (cc < cmplen) { + if (mbufstrncmp(m, m->m_nextpkt, 1, cc, cmp) == 1) { + DPRINT("short cc (%d) but mbufstrncmp ok", cc); + return; + } else { + DPRINT("short cc (%d) mbufstrncmp failed", cc); + goto fallout; + } + } + if (mbufstrcmp(m, m->m_nextpkt, 1, cmp) == 1) { + DPRINT("mbufstrcmp ok"); + if (parse_http_version == 0) + soishttpconnected(so, arg, waitflag); + else + soparsehttpvers(so, arg, waitflag); return; } + DPRINT("mbufstrcmp bad"); } +fallout: + DPRINT("fallout"); so->so_upcall = NULL; so->so_rcv.sb_flags &= ~SB_UPCALL; soisconnected(so); @@ -118,64 +217,141 @@ sohashttpget(struct socket *so, void *arg, int waitflag) } static void -soishttpconnected(struct socket *so, void *arg, int waitflag) +soparsehttpvers(struct socket *so, void *arg, int waitflag) { - char a, b, c; - struct mbuf *y, *z; - - if ((so->so_state & SS_CANTRCVMORE) == 0) { - /* seek to end and keep track of next to last mbuf */ - y = so->so_rcv.sb_mb; - while (y->m_nextpkt) - y = y->m_nextpkt; - z = y; - while (y->m_next) { - z = y; - y = y->m_next; - } - - if (z->m_len + y->m_len > 2) { - int index = y->m_len - 1; - - c = *(mtod(y, char *) + index--); - switch (index) { - case -1: - y = z; - index = y->m_len - 1; - b = *(mtod(y, char *) + index--); - break; - case 0: - b = *(mtod(y, char *) + index--); - y = z; - index = y->m_len - 1; - break; - default: - b = *(mtod(y, char *) + index--); - break; + struct mbuf *m, *n; + int i, cc, spaces, inspaces; + + if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv)) + goto fallout; + + m = so->so_rcv.sb_mb; + cc = so->so_rcv.sb_cc; + inspaces = spaces = 0; + for (m = so->so_rcv.sb_mb; m; m = n) { + n = m->m_nextpkt; + for (; m; m = m->m_next) { + for (i = 0; i < m->m_len; i++, cc--) { + switch (*(mtod(m, char *) + i)) { + case ' ': + if (!inspaces) { + spaces++; + inspaces = 1; + } + break; + case '\r': + case '\n': + DPRINT("newline"); + goto fallout; + default: + if (spaces == 2) { + /* make sure we have enough data left */ + if (cc < sizeof("HTTP/1.0") - 1) { + if (mbufstrncmp(m, n, i, cc, "HTTP/1.") == 1) { + DPRINT("mbufstrncmp ok"); + goto readmore; + } else { + DPRINT("mbufstrncmp bad"); + goto fallout; + } + } else if (mbufstrcmp(m, n, i, "HTTP/1.0") == 1 || + mbufstrcmp(m, n, i, "HTTP/1.1") == 1) { + DPRINT("mbufstrcmp ok"); + soishttpconnected(so, arg, waitflag); + return; + } else { + DPRINT("mbufstrcmp bad"); + goto fallout; + } + } + inspaces = 0; + break; + } } - a = *(mtod(y, char *) + index--); - } else { - int begin = 0; - int end = so->so_rcv.sb_cc - 3; - - y = so->so_rcv.sb_mb; - a = sbindex(&y, &begin, end++); - b = sbindex(&y, &begin, end++); - c = sbindex(&y, &begin, end++); } + } +readmore: + DPRINT("readmore"); + /* + * if we hit here we haven't hit something + * we don't understand or a newline, so try again + */ + so->so_upcall = soparsehttpvers; + so->so_rcv.sb_flags |= SB_UPCALL; + return; - if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) { - /* we have all request headers */ - goto done; - } else { - /* still need more data */ - so->so_upcall = soishttpconnected; - so->so_rcv.sb_flags |= SB_UPCALL; - return; +fallout: + DPRINT("fallout"); + so->so_upcall = NULL; + so->so_rcv.sb_flags &= ~SB_UPCALL; + soisconnected(so); + return; +} + + +#define NCHRS 3 + +static void +soishttpconnected(struct socket *so, void *arg, int waitflag) +{ + char a, b, c; + struct mbuf *m, *n; + int ccleft, copied; + + DPRINT("start"); + if ((so->so_state & SS_CANTRCVMORE) != 0 || sbfull(&so->so_rcv)) + goto gotit; + + /* + * Walk the socketbuffer and copy the last NCHRS (3) into a, b, and c + * copied - how much we've copied so far + * ccleft - how many bytes remaining in the socketbuffer + * just loop over the mbufs subtracting from 'ccleft' until we only + * have NCHRS left + */ + copied = 0; + ccleft = so->so_rcv.sb_cc; + if (ccleft < NCHRS) + goto readmore; + a = b = c = '\0'; + for (m = so->so_rcv.sb_mb; m; m = n) { + n = m->m_nextpkt; + for (; m; m = m->m_next) { + ccleft -= m->m_len; + if (ccleft <= NCHRS) { + char *src; + int tocopy; + + tocopy = (NCHRS - ccleft) - copied; + src = mtod(m, char *) + (m->m_len - tocopy); + + while (tocopy--) { + switch (copied++) { + case 0: + a = *src++; + break; + case 1: + b = *src++; + break; + case 2: + c = *src++; + break; + } + } + } } } + if (c == '\n' && (b == '\n' || (b == '\r' && a == '\n'))) { + /* we have all request headers */ + goto gotit; + } + +readmore: + so->so_upcall = soishttpconnected; + so->so_rcv.sb_flags |= SB_UPCALL; + return; -done: +gotit: so->so_upcall = NULL; so->so_rcv.sb_flags &= ~SB_UPCALL; soisconnected(so); diff --git a/sys/sys/socketvar.h b/sys/sys/socketvar.h index aee3191..5f2cd9a 100644 --- a/sys/sys/socketvar.h +++ b/sys/sys/socketvar.h @@ -404,6 +404,7 @@ int accept_filt_del __P((char *name)); struct accept_filter * accept_filt_get __P((char *name)); #ifdef ACCEPT_FILTER_MOD int accept_filt_generic_mod_event __P((module_t mod, int event, void *data)); +SYSCTL_DECL(_net_inet_accf); #endif /* ACCEPT_FILTER_MOD */ #endif /* _KERNEL */ |