[Please CC me in replies, I am currently not subscribed to this list.]
First a short introduction of the project I am currently working on: I am the main developer of Gibraltar, a Debian GNU/Linux based firewall bootable from a live CD-ROM. There is a commercial version with easy to use web interface, but also a free version (including commercial in-house uses) available for download.
For the next major release I would like to add HTTP content scanning support, including anti-virus scanning. Although there are many special-purpose proxies for that (e.g. privoxy), I decided that squid is definitely the better solution for a firewall. It's fast and lightweight enough in terms of memory usage and has many great features for authenticating users (e.g. NTLM). Unfortunately, in 2.5 there seems to be no support for content filtering.
Therefore, I am currently trying to forward-port the filter patches from
http://sites.inka.de/sites/bigred/devel/squid-filter.html and will put that work up on my server as soon as the porting is finished. It hasn't been updated since about a year and he last post I could locate is
http://www1.cn.squid-cache.org/mail-archive/squid-dev/200203/0137.html . Olaf, are you already working on a port ? I could not find a notice on the webpage.
If necessary, I would also be willing to act as a maintainer for the filtering code and update it for new squid releases (if Olaf is no longer interested or does currently not have time for it - thanks for the good work until now!) or integrate it upstream if that is desired.
Now, after a lengthy intro, my real problem :)
Although I have adapted everything to the new cbdata interface and have it building and loading correctly, I get a runtime assertion when at least one of the filter modules is loaded and it gets active (i.e. starting squid with a filter plugin and sitting there idle is ok, but the first requests will cause the assertion).
==> /var/log/squid/cache.log <== 2004/01/28 20:05:46| assertion failed: cbdata.c:185: "c->y == c"
It of course recovers by restarting. Before failing, a few requests are actually served and I have not yet tracked down what exactly triggers the assertion. However, I am quite sure that I have messed up badly when porting the code for the cbdata usage. I just tried to read other code that uses the CBDATA macros and mimic that usage. Attached is the module.c file with the commented out, old calls and my ported calls to the new interface. If any squid wizard/hacker/guru could give me a hint on how to use it correctly or where I have messed up, I would really appreciate it. If I have done something incredibly stupid, please forgive me as I just had my first 3 hours with squid source code. Slapping with a clue bat is ok :)
The modified lines around 79ff, 87, 206 and 532.
with best regards, Rene
/* * DEBUG: section 92 Module loader and hooks * AUTHOR: Olaf Titz * MODIFIED BY: Rene Mayrhofer (ported to squid 2.5X, 2004-01) * * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ * ---------------------------------------------------------- * * Squid is the result of efforts by numerous individuals from the * Internet community. Development is led by Duane Wessels of the * National Laboratory for Applied Network Research and funded by the * National Science Foundation. Squid is Copyrighted (C) 1998 by * Duane Wessels and the University of California San Diego. Please * see the COPYRIGHT file for full details. Squid incorporates * software developed and/or copyrighted by other sources. Please see * the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */
#include "squid.h"
#include "module.h"
/* ---------- Module loader, definitions ---------- */
/* This defines a common API between the Sun libdl and HP libdld.
We use shl_t for library handles and
void xdlsym(void **result, shl_t handle, const char *name);
instead of dlsym().
The rest follows the Sun API.
*/
#ifdef HAVE_LIBDL
#include <dlfcn.h>
/* Solaris/Linux/etc. */
typedef void *shl_t;
#define xdlsym(r, h, s) (r)=dlsym((h), (s))
#else
#ifdef HAVE_LIBDLD
#include <dl.h>
/* HPUX */
#define RTLD_NOW BIND_IMMEDIATE
#define RTLD_LAZY BIND_DEFERRED
#define RTLD_GLOBAL 0
#define dlerror() strerror(errno)
#define dlopen(m, f) shl_load((m), (f), 0)
#define xdlsym(r, h, s) \
if (shl_findsym(&(h), (s), TYPE_UNDEFINED, &(r))<0) (r)=NULL
#define dlclose(h) shl_unload((h))
#endif
#endif
#define MODULE_C
#include <classes.dh>
/* ---------- Object framework ---------- */
/*static void cbdataLfree(void *p, int n)
{
cbdataXfree(leakFree(p), n);
}*/
CBDATA_TYPE(Object);
CBDATA_TYPE(moduleOwner);
CBDATA_TYPE(moduleAuthCBData);
void *void_O_CTOR_(Object *this, _DTOR dtor)
{
this->destroy = dtor;
// cbdataAdd(this, cbdataLfree, 0);
CBDATA_INIT_TYPE(Object);
return this;
}
void Object_O_DTOR(Object *this)
{
#if 0
if (this->refCount <= 0)
cbdataFree(this);
#else
assert(this->refCount <= 0);
cbdataFree(this);
#endif
}
Ddef(moduleObject)
{
/*
next is handled in moduleUnloadAll()
owner is handled in moduleDestroyProxy()
description and trigger are not alloced
*/
UNREF(this->patFile);
}
/* ---------- Book-keeping of loaded modules ---------- */
/* The shared library object */
classdef(private shlibObject, Object, ({
struct _shlibObject *next, *prev; /* Chain (weak ref!) */
char *name; /* Module name */
shl_t hndl; /* dlopen handle */
}))
typedef struct _moduleOwner {
shlibObject *owner; /* Containing shlib */
void (*realDestroy)(void *); /* moduleObject destructor */
} moduleOwner;
/* The filter chains */
moduleObject *chain[FIL_END] = {NULL};
/* Registry */
shlibObject *modules = NULL;
shlibObject *currentlyLoading = NULL;
Ddef(shlibObject)
{
#if 0
if (this->refCount > 0) {
debug(92, 5) ("shlibUnload: revived %s\n", this->name);
return;
}
#endif
debug(92, 4) ("shlibUnload: unloading %s\n", this->name);
if (modules == this) {
assert(this->prev == NULL);
modules = this->next;
}
if (this->next)
this->next->prev = this->prev;
if (this->prev)
this->prev->next = this->next;
xfree(leakFree(this->name));
dlclose(this->hndl);
}
static void shlibUnloadCB(void *p)
{
shlibObject *o = p;
debug(92, 8) ("shlibUnloadCB: %s refCount=%d\n", o->name, o->refCount);
cbdataUnlock(o);
UNREF(o); /* calls the shlibObject dtor if needed */
}
/* The actual unloading via dlclose() of a module is deferred
because it must be called from the main loop directly, not via
UNREF. Otherwise, a module could possibly unload its own code
segment. With several seconds delay, a module which is unchanged
after reconfig will just be reused (but not its moduleObject(s)!)
*/
static void moduleDestroyProxy(void *obj)
{
moduleOwner *o = ((moduleObject*)obj)->info.owner;
debug(92, 8) ("moduleDestroyProxy: %s %s\n", o->owner->name,
((moduleObject*)obj)->description);
assert(cbdataValid(o));
o->realDestroy(obj);
cbdataLock(o->owner);
eventAddIsh("shlibUnload", shlibUnloadCB, o->owner, 15.0, 1);
cbdataUnlock(o);
cbdataFree(o);
}
/* ---------- Module loader, implementation ---------- */
/* Register a moduleObject. Called from the modules' moduleInit functions. */
void moduleRegister(moduleObject *theModule)
{
moduleObject *p;
moduleOwner *o;
if (theModule->info.version != MODULE_API_VERSION) {
debug(92, 1) ("moduleRegister: rejecting incompatible module\n");
return;
}
debug(92, 4) ("moduleRegister: registering %s %s\n",
theModule->description,
theModule->trigger ? theModule->trigger : "");
p = chain[theModule->chain.typ];
if (p) {
for (; p->chain.next; p = p->chain.next);
p->chain.next = REF(theModule);
} else {
p = REF(theModule);
chain[theModule->chain.typ] = p;
}
theModule->chain.next = NULL;
assert(currentlyLoading);
/* o = leakAdd(xmalloc(sizeof(moduleOwner)));
cbdataAdd(o, cbdataLfree, 0);*/
CBDATA_INIT_TYPE(moduleOwner);
o = cbdataAlloc(moduleOwner);
cbdataLock(o);
o->owner = REF(currentlyLoading);
o->realDestroy = theModule->destroy;
theModule->info.owner = o;
theModule->destroy = moduleDestroyProxy;
}
void moduleLoad(const char *module, const wordlist *args)
{
void (*initf)(const wordlist *);
const char *dle;
shl_t hndl;
shlibObject *sc;
const char *name = strrchr(module, '/');
if (name)
++name;
else
name = module;
debug(92, 4) ("moduleLoad: loading %s\n", name);
hndl = dlopen(module, RTLD_NOW|RTLD_GLOBAL);
if (!hndl) {
dle = dlerror();
if (!dle)
dle = "(unknown)";
debug(92, 1) ("moduleLoad: %s: dlopen error: %s\n", module, dle);
return;
}
xdlsym(initf, hndl, "moduleInit");
if (!initf) {
dle = dlerror();
if (!dle)
dle = "NULL";
debug(92, 1) ("moduleInit not found: %s\n", dle);
dlclose(hndl);
return;
}
for (sc=modules; sc; sc=sc->next) {
if (hndl == sc->hndl) {
#ifdef HAVE_LIBDL
dlclose(hndl);
/* for HAVE_LIBDLD: do not unload here! Refcounting seems to be
broken, at least for HPUX 10.20. */
#endif
break;
}
}
if (!sc) {
sc = new(shlibObject);
sc->next = modules;
sc->prev = NULL;
sc->name = leakAdd(xstrdup(name));
sc->hndl = hndl;
if (modules)
modules->prev = sc;
modules = sc;
}
currentlyLoading = REF(sc);
initf(args);
UNREF(currentlyLoading);
}
void moduleLoadAll(void)
{
load_module *list;
wordlist *w;
for (list = Config.modules; list; list = list->next) {
w = list->params;
moduleLoad(list->module, w);
}
}
void moduleUnloadAll(void)
{
int i, m = 0;
moduleObject *p, *p0;
for (i=0; i<FIL_END; ++i) {
for (p = chain[i]; p; p = p0) {
p0 = p->chain.next;
debug(92, 5) ("moduleUnloadAll: unregistering %s\n",
p->description);
UNREF(p);
++m;
}
chain[i] = NULL;
}
debug(92, 4) ("moduleUnloadAll: unregistered %d objects\n", m);
}
/* ---------- Filtering hooks ---------- */
char *canonType(const char *t)
{
char *x, *p;
if (!t)
return NULL;
p = x = xstrdup(t);
while (*p) {
if (*p==';') {
*p='\0';
return x;
}
*p = tolower(*p);
++p;
}
return x;
}
int mtmatch(const char *ct, const char *tr)
{
if (tr[0] == '*' && tr[1] == '/') {
for (; *ct && *ct != '/'; ++ct);
if (*ct)
++ct;
tr += 2;
}
while (*ct) {
if (*tr == '*')
return 1;
if (*ct != *tr)
return 0;
++ct;
++tr;
}
return (*tr == 0);
}
char *moduleUrlClean(const char *uri)
{
static char buf[MAX_URL];
xstrncpy(buf, uri, MAX_URL);
if (Config.onoff.strip_query_terms) {
char *p = strchr(buf, '?');
if (p)
p[1] = '\0';
}
if (stringHasCntl(buf))
return rfc1738_escape_unescaped(buf);
return buf;
}
char *moduleRedirect(const char *uri)
{
const char *res = uri, *tmp;
moduleObject *f;
for (f = chain[FIL_REDIRECT]; f; f = f->chain.next) {
tmp = f->filter(f, res);
if (tmp)
res = tmp;
}
return (char *)res; /* XX */
}
char *moduleRejecttype(const char *typ, const char *uri)
{
moduleObject *f;
typParam tp;
char *r = (char *)uri; /* XX */
tp.uri = r;
tp.typ = canonType(typ);
for (f = chain[FIL_REJECTTYPE]; f; f = f->chain.next) {
if (!*f->trigger || (tp.typ && mtmatch(tp.typ, f->trigger))) {
r = f->filter(f, &tp);
break;
}
}
xfree(tp.typ);
return r;
}
void moduleReqHeader(const char *uri, HttpHeader *hdr)
{
hdrParam hp;
moduleObject *f;
hp.uri = uri;
hp.hdr = hdr;
for (f = chain[FIL_REQHEADER]; f; f = f->chain.next) {
(void) f->filter(f, &hp);
}
}
void moduleRepHeader(const char *uri, HttpHeader *hdr)
{
hdrParam hp;
moduleObject *f;
hp.uri = uri;
hp.hdr = hdr;
for (f = chain[FIL_REPHEADER]; f; f = f->chain.next) {
(void) f->filter(f, &hp);
}
}
filterObject *moduleCFilterNew(const char *typ, const char *uri,
HttpReply *rep)
{
repParam rp;
moduleObject *f;
filterObject *fo = NULL, *fo1 = NULL;
rp.uri = uri;
rp.rep = rep;
if (typ) {
char *tt = canonType(typ);
for (f = chain[FIL_CONTFILTER]; f; f = f->chain.next) {
if (mtmatch(tt, f->trigger)) {
filterObject *n = f->filter(f, &rp);
if (n) {
n->owner = REF(f);
n->uri = leakAdd(xstrdup(uri));
if (fo) {
fo->next = REF(n);
fo = fo->next;
} else {
fo = fo1 = REF(n);
}
}
}
}
xfree(tt);
}
return fo1;
}
Ddef(filterObject)
{
UNREF(this->next);
UNREF(this->owner);
xfree(leakFree(this->uri));
}
void moduleCFilterDestroy(void *f)
{
filterObject *o = f;
UNREF(o);
}
int moduleCFilter(void *filter, MemBuf *target, const char *buf, int len)
{
filterObject *f;
MemBuf tmp[2];
int i = 0, r = 0;
char tmpu[2] = {0, 0};
for (f = filter; f->next; f = f->next) {
if (tmpu[i]) {
memBufReset(&tmp[i]);
} else {
memBufDefInit(&tmp[i]);
tmpu[i] = 1;
}
if (f->filter(f, &tmp[i], buf, len) < 0) {
debug(92, 4) ("moduleCFilter: aborted by %s\n",
f->owner->description);
r = -1;
break;
}
buf = tmp[i].buf;
len = tmp[i].size;
i = 1-i;
}
if (r >= 0) {
if (f->filter(f, target, buf, len) < 0) {
debug(92, 4) ("moduleCFilter: aborted by %s\n",
f->owner->description);
r = -1;
}
}
for (i=0; i<2; ++i)
if (tmpu[i])
memBufClean(&tmp[i]);
return r;
}
static void moduleAuthResult(void *data, char *result)
{
moduleAuthCBData *d = data;
moduleObject *f;
if (result == NULL) {
debug(92, 8) ("moduleAuthResult: pass\n");
if (cbdataValid(d))
d->callback(d->check, "OK");
goto out;
}
if (result != RREJECT) {
debug(92, 8) ("moduleAuthResult: challenge %s\n", result);
d->check->request->proxy_realm = result;
if (cbdataValid(d))
d->callback(d->check, NULL);
goto out;
}
if (!(f = d->next)) {
debug(92, 8) ("moduleAuthResult: all failed, try core auth\n");
authenticateStart(d->check->auth_user_request, d->callback, d->check);
goto out;
}
d->next = REF(f->chain.next);
debug(92, 8) ("moduleAuth: trying %s\n", f->description);
f->filter(f, d);
UNREF(f);
return;
out:
cbdataUnlock(d->check);
UNREF(d->next);
cbdataUnlock(d);
cbdataFree(d);
}
void moduleAuth(aclCheck_t *check, RH *callback)
{
moduleAuthCBData *d;
moduleObject *f;
if (!(f = REF(chain[FIL_AUTH]))) {
authenticateStart(check->auth_user_request, callback, check);
return;
}
/*d = xmalloc(sizeof(moduleAuthCBData));
cbdataAdd(d, cbdataXfree, 0);*/
CBDATA_INIT_TYPE(moduleAuthCBData);
d = cbdataAlloc(moduleAuthCBData);
cbdataLock(check);
d->check = check;
d->authResult = moduleAuthResult;
d->callback = callback;
cbdataLock(d);
d->next = REF(f->chain.next);
debug(92, 8) ("moduleAuth: trying %s\n", f->description);
f->filter(f, d);
UNREF(f);
}
